From 91afe97e70cfe6d15334a89f4e462d26fdb33e26 Mon Sep 17 00:00:00 2001 From: linfeng Date: Tue, 17 Dec 2019 18:09:42 +0800 Subject: [PATCH 1/2] Problem:(CRO-557) no way to import transactions (without a view key) solution: - add a interface `export_tx` for sender to download the the transaction - add a interface `import_tx` for receiver to import the transaction and get the output UTXO --- CHANGELOG.md | 2 + chain-core/src/tx/data/input.rs | 14 +- client-cli/src/command/transaction_command.rs | 34 ++++- client-common/src/lib.rs | 2 +- client-common/src/transaction.rs | 9 ++ client-core/src/service/wallet_service.rs | 11 +- client-core/src/transaction_builder.rs | 6 +- .../default_wallet_transaction_builder.rs | 16 ++- ...unauthorized_wallet_transaction_builder.rs | 7 +- client-core/src/wallet.rs | 15 ++ .../src/wallet/default_wallet_client.rs | 130 +++++++++++++++++- client-core/src/wallet/syncer.rs | 10 +- client-core/src/wallet/syncer_logic.rs | 25 ++-- client-rpc/src/rpc/wallet_rpc.rs | 18 +++ 14 files changed, 274 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f5fdbebe..6e0f35797 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ of Rust SGX SDK (0.1.0 used a beta version of 1.1.0). ### Breaking changes * *client* [703](https://github.com/crypto-com/chain/pull/703): HD wallet generate view key with a different account index. +* *client* [695](https://github.com/crypto-com/chain/pull/695): export and import transaction -- transactions that do not include receiver's view key can be exported, giving a base64 encoded plain transaction string which can be imported by the receiver. ### Improvements * *dev-utils* [692](https://github.com/crypto-com/chain/pull/692): dev-utils init command logs error when it goes wrong @@ -26,6 +27,7 @@ of Rust SGX SDK (0.1.0 used a beta version of 1.1.0). The release is a more complete alpha version meant to be deployed on the second iteration of the public testnet. There are no guarantees on future API and binary compatibility at this stage. + ## v0.1.0 ### Features diff --git a/chain-core/src/tx/data/input.rs b/chain-core/src/tx/data/input.rs index db94abc76..8492cc312 100644 --- a/chain-core/src/tx/data/input.rs +++ b/chain-core/src/tx/data/input.rs @@ -2,7 +2,11 @@ use std::fmt; use parity_scale_codec::{Decode, Encode}; #[cfg(not(feature = "mesalock_sgx"))] -use serde::de; +use serde::de::{ + self, + value::{Error as ValueError, StrDeserializer}, + IntoDeserializer, +}; #[cfg(not(feature = "mesalock_sgx"))] use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -56,7 +60,7 @@ where } #[inline] - fn visit_str(self, value: &str) -> Result + fn visit_str(self, value: &str) -> std::result::Result where E: de::Error, { @@ -94,3 +98,9 @@ impl TxoPointer { } } } + +#[cfg(not(feature = "mesalock_sgx"))] +pub fn str2txid>(s: S) -> Result { + let deserializer: StrDeserializer = s.as_ref().into_deserializer(); + deserialize_transaction_id(deserializer) +} diff --git a/client-cli/src/command/transaction_command.rs b/client-cli/src/command/transaction_command.rs index 813a1bb56..c8822ac19 100644 --- a/client-cli/src/command/transaction_command.rs +++ b/client-cli/src/command/transaction_command.rs @@ -14,7 +14,7 @@ use client_common::{Error, ErrorKind, PublicKey, Result, ResultExt}; use client_core::WalletClient; use client_network::NetworkOpsClient; use hex::decode; -use quest::{ask, text, yesno}; +use quest::{ask, success, text, yesno}; use secstr::SecUtf8; use structopt::StructOpt; use unicase::eq_ascii; @@ -62,6 +62,26 @@ pub enum TransactionCommand { #[structopt(name = "type", short, long, help = "Type of transaction to create")] transaction_type: TransactionType, }, + #[structopt( + name = "export", + about = "Export a plain transaction by a given transaction id" + )] + Export { + #[structopt(name = "name", short, long, help = "Name of wallet")] + name: String, + #[structopt(name = "id", short, long, help = "transaction id")] + id: String, + }, + #[structopt( + name = "import", + about = "Export a plain transaction by a given transaction id" + )] + Import { + #[structopt(name = "name", short, long, help = "Name of wallet")] + name: String, + #[structopt(name = "tx", short, long, help = "base64 encoded plain transaction")] + tx: String, + }, } impl TransactionCommand { @@ -75,6 +95,18 @@ impl TransactionCommand { name, transaction_type, } => new_transaction(wallet_client, network_ops_client, name, transaction_type), + TransactionCommand::Export { name, id } => { + let passphrase = ask_passphrase(None)?; + let tx = wallet_client.export_plain_tx(name, &passphrase, id)?; + success(&tx); + Ok(()) + } + TransactionCommand::Import { name, tx } => { + let passphrase = ask_passphrase(None)?; + let imported_amount = wallet_client.import_plain_tx(name, &passphrase, tx)?; + success(format!("import amount: {}", imported_amount).as_str()); + Ok(()) + } } } } diff --git a/client-common/src/lib.rs b/client-common/src/lib.rs index 0bd4dc4c7..5f073ff2b 100644 --- a/client-common/src/lib.rs +++ b/client-common/src/lib.rs @@ -17,7 +17,7 @@ pub use multi_sig_address::MultiSigAddress; #[doc(inline)] pub use storage::{SecureStorage, Storage}; #[doc(inline)] -pub use transaction::{SignedTransaction, Transaction}; +pub use transaction::{SignedTransaction, Transaction, TransactionInfo}; use secp256k1::{All, Secp256k1}; diff --git a/client-common/src/transaction.rs b/client-common/src/transaction.rs index 888295e06..6f5251e30 100644 --- a/client-common/src/transaction.rs +++ b/client-common/src/transaction.rs @@ -10,6 +10,15 @@ use chain_core::tx::data::{Tx, TxId}; use chain_core::tx::witness::TxWitness; use chain_core::tx::TransactionId; +/// A struct which the sender can download and the receiver can import +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode)] +pub struct TransactionInfo { + /// enum Transaction type + pub tx: Transaction, + /// block height when the tx broadcast + pub block_height: u64, +} + /// Enum containing different types of transactions #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode)] #[serde(tag = "type")] diff --git a/client-core/src/service/wallet_service.rs b/client-core/src/service/wallet_service.rs index 010b64e9b..5311b1199 100644 --- a/client-core/src/service/wallet_service.rs +++ b/client-core/src/service/wallet_service.rs @@ -3,6 +3,7 @@ use std::collections::BTreeSet; use parity_scale_codec::{Decode, Encode}; use secstr::SecUtf8; +use crate::service::{load_wallet_state, WalletState}; use chain_core::common::H256; use chain_core::init::address::RedeemAddress; use chain_core::state::account::StakedStateAddress; @@ -104,13 +105,21 @@ where WalletService { storage } } - /// Load wallet + /// Get the wallet from storage pub fn get_wallet(&self, name: &str, passphrase: &SecUtf8) -> Result { load_wallet(&self.storage, name, passphrase)?.err_kind(ErrorKind::InvalidInput, || { format!("Wallet with name ({}) not found", name) }) } + /// Get the wallet state from storage + pub fn get_wallet_state(&self, name: &str, passphrase: &SecUtf8) -> Result { + load_wallet_state(&self.storage, name, passphrase)? + .err_kind(ErrorKind::InvalidInput, || { + format!("WalletState with name ({}) not found", name) + }) + } + fn set_wallet(&self, name: &str, passphrase: &SecUtf8, wallet: Wallet) -> Result<()> { save_wallet(&self.storage, name, passphrase, &wallet) } diff --git a/client-core/src/transaction_builder.rs b/client-core/src/transaction_builder.rs index 1a2d9f256..01fd0c935 100644 --- a/client-core/src/transaction_builder.rs +++ b/client-core/src/transaction_builder.rs @@ -13,9 +13,10 @@ use chain_core::tx::data::address::ExtendedAddr; use chain_core::tx::data::attribute::TxAttributes; use chain_core::tx::data::output::TxOut; use chain_core::tx::TxAux; -use client_common::{Result, SignedTransaction}; +use client_common::{PrivateKey, Result, SignedTransaction, Transaction}; use crate::UnspentTransactions; +use chain_core::tx::data::TxId; /// Interface for wallet transaction building from output addresses and amount. /// This trait is also responsible for UTXO selection. @@ -42,4 +43,7 @@ pub trait WalletTransactionBuilder: Send + Sync { /// Obfuscates given signed transaction fn obfuscate(&self, signed_transaction: SignedTransaction) -> Result; + + /// Get a decrypted transaction by a given tx_id + fn decrypt_tx(&self, txid: TxId, private_key: &PrivateKey) -> Result; } diff --git a/client-core/src/transaction_builder/default_wallet_transaction_builder.rs b/client-core/src/transaction_builder/default_wallet_transaction_builder.rs index eb33c14f4..5d9a35a17 100644 --- a/client-core/src/transaction_builder/default_wallet_transaction_builder.rs +++ b/client-core/src/transaction_builder/default_wallet_transaction_builder.rs @@ -6,7 +6,9 @@ use chain_core::tx::data::attribute::TxAttributes; use chain_core::tx::data::output::TxOut; use chain_core::tx::fee::FeeAlgorithm; use chain_core::tx::TxAux; -use client_common::{ErrorKind, Result, ResultExt, SignedTransaction, Storage}; +use client_common::{ + ErrorKind, PrivateKey, Result, ResultExt, SignedTransaction, Storage, Transaction, +}; use crate::signer::WalletSignerManager; use crate::transaction_builder::RawTransferTransactionBuilder; @@ -14,6 +16,7 @@ use crate::{ SelectedUnspentTransactions, TransactionObfuscation, UnspentTransactions, WalletTransactionBuilder, }; +use chain_core::tx::{data::TxId, TransactionId}; /// Default implementation of `TransactionBuilder` /// @@ -71,6 +74,17 @@ where fn obfuscate(&self, signed_transaction: SignedTransaction) -> Result { self.transaction_obfuscation.encrypt(signed_transaction) } + + fn decrypt_tx(&self, txid: TxId, private_key: &PrivateKey) -> Result { + let tx = self + .transaction_obfuscation + .decrypt(&[txid], private_key)? + .iter() + .find(|t| t.id() == txid) + .map(Clone::clone) + .chain(|| (ErrorKind::InvalidInput, "can not find transaction"))?; + Ok(tx) + } } impl DefaultWalletTransactionBuilder diff --git a/client-core/src/transaction_builder/unauthorized_wallet_transaction_builder.rs b/client-core/src/transaction_builder/unauthorized_wallet_transaction_builder.rs index d211a9696..d8f293fc5 100644 --- a/client-core/src/transaction_builder/unauthorized_wallet_transaction_builder.rs +++ b/client-core/src/transaction_builder/unauthorized_wallet_transaction_builder.rs @@ -4,9 +4,10 @@ use chain_core::tx::data::address::ExtendedAddr; use chain_core::tx::data::attribute::TxAttributes; use chain_core::tx::data::output::TxOut; use chain_core::tx::TxAux; -use client_common::{ErrorKind, Result, SignedTransaction}; +use client_common::{ErrorKind, PrivateKey, Result, SignedTransaction, Transaction}; use crate::{UnspentTransactions, WalletTransactionBuilder}; +use chain_core::tx::data::TxId; /// Implementation of `WalletTransactionBuilder` which always returns /// permission denied @@ -29,4 +30,8 @@ impl WalletTransactionBuilder for UnauthorizedWalletTransactionBuilder { fn obfuscate(&self, _: SignedTransaction) -> Result { Err(ErrorKind::PermissionDenied.into()) } + + fn decrypt_tx(&self, _txid: TxId, _private_key: &PrivateKey) -> Result { + Err(ErrorKind::PermissionDenied.into()) + } } diff --git a/client-core/src/wallet.rs b/client-core/src/wallet.rs index e02a47625..2b0e5267a 100644 --- a/client-core/src/wallet.rs +++ b/client-core/src/wallet.rs @@ -219,6 +219,21 @@ pub trait WalletClient: Send + Sync { /// Broadcasts a transaction to Crypto.com Chain fn broadcast_transaction(&self, tx_aux: &TxAux) -> Result; + + /// When receiver's view key not included in the transaction, the receiver can't collect the outputs. + /// The sender have to get the plain transaction and send it to the receiver by email or something + /// so that the receiver can sync it into the wallet DB and get the outputs. + /// + /// # Return + /// + /// base64 encoded of `Transaction` json string + fn export_plain_tx(&self, name: &str, passphras: &SecUtf8, txid: &str) -> Result; + + /// import a plain transaction, put the outputs of the transaction into wallet DB + /// + /// # Return + /// the sum of unused outputs coin + fn import_plain_tx(&self, name: &str, passphrase: &SecUtf8, tx_str: &str) -> Result; } /// Interface for a generic wallet for multi-signature transactions diff --git a/client-core/src/wallet/default_wallet_client.rs b/client-core/src/wallet/default_wallet_client.rs index d89157221..cf410b997 100644 --- a/client-core/src/wallet/default_wallet_client.rs +++ b/client-core/src/wallet/default_wallet_client.rs @@ -1,3 +1,4 @@ +use bit_vec::BitVec; use std::collections::BTreeSet; use parity_scale_codec::Encode; @@ -12,26 +13,29 @@ use chain_core::init::coin::Coin; use chain_core::state::account::StakedStateAddress; use chain_core::tx::data::address::ExtendedAddr; use chain_core::tx::data::attribute::TxAttributes; -use chain_core::tx::data::input::TxoPointer; +use chain_core::tx::data::input::{str2txid, TxoPointer}; use chain_core::tx::data::output::TxOut; use chain_core::tx::data::Tx; use chain_core::tx::witness::tree::RawPubkey; use chain_core::tx::witness::{TxInWitness, TxWitness}; -use chain_core::tx::TxAux; -use client_common::tendermint::types::BroadcastTxResponse; +use chain_core::tx::{TransactionId, TxAux}; +use client_common::tendermint::types::{AbciQueryExt, BlockExt, BroadcastTxResponse}; use client_common::tendermint::{Client, UnauthorizedClient}; use client_common::{ Error, ErrorKind, PrivateKey, PublicKey, Result, ResultExt, SignedTransaction, Storage, + Transaction, TransactionInfo, }; use crate::service::*; use crate::transaction_builder::UnauthorizedWalletTransactionBuilder; use crate::types::WalletKind; use crate::types::{AddressType, BalanceChange, TransactionChange}; +use crate::wallet::syncer_logic::create_transaction_change; use crate::{ InputSelectionStrategy, Mnemonic, MultiSigWalletClient, UnspentTransactions, WalletClient, WalletTransactionBuilder, }; +use client_common::tendermint::types::Time; /// Default implementation of `WalletClient` based on `Storage` and `Index` #[derive(Debug, Default, Clone)] @@ -517,6 +521,88 @@ where self.tendermint_client .broadcast_transaction(&tx_aux.encode()) } + + fn export_plain_tx(&self, name: &str, passphrase: &SecUtf8, txid: &str) -> Result { + let txid = str2txid(txid).chain(|| (ErrorKind::InvalidInput, "invalid transaction id"))?; + let public_key = self.view_key(name, passphrase)?; + let private_key = self + .private_key(passphrase, &public_key)? + .chain(|| (ErrorKind::StorageError, "can not find private key"))?; + let tx = self.transaction_builder.decrypt_tx(txid, &private_key)?; + // get the block height + let tx_change = self + .wallet_state_service + .get_transaction_history(name, passphrase, false)? + .filter(|change| BalanceChange::NoChange != change.balance_change) + .find(|tx_change| tx_change.transaction_id == tx.id()) + .chain(|| { + ( + ErrorKind::InvalidInput, + "no transaction find by transaction id", + ) + })?; + + let tx_info = TransactionInfo { + tx, + block_height: tx_change.block_height, + }; + + let tx_str = serde_json::to_string(&tx_info) + .chain(|| (ErrorKind::InvalidInput, "invalid transaction id"))?; + Ok(base64::encode(&tx_str)) + } + + /// import a plain base64 encoded plain transaction + fn import_plain_tx(&self, name: &str, passphrase: &SecUtf8, tx_str: &str) -> Result { + let tx_raw = base64::decode(tx_str) + .chain(|| (ErrorKind::DecryptionError, "Unable to decrypt transaction"))?; + let tx_info: TransactionInfo = serde_json::from_slice(&tx_raw) + .chain(|| (ErrorKind::DecryptionError, "Unable to decrypt transaction"))?; + // check if the output is spent or not + let v = self + .tendermint_client + .query("meta", &tx_info.tx.id().to_vec())? + .bytes()?; + let bit_flag = BitVec::from_bytes(&v); + let spent_flags: Result> = tx_info + .tx + .outputs() + .iter() + .enumerate() + .map(|(index, _output)| { + bit_flag + .get(index) + .chain(|| (ErrorKind::InvalidInput, "check failed in enclave")) + }) + .collect(); + let mut memento = WalletStateMemento::default(); + // check if tx belongs to the block + let block = self.tendermint_client.block(tx_info.block_height)?; + if !block.enclave_transaction_ids()?.contains(&tx_info.tx.id()) { + return Err(Error::new( + ErrorKind::InvalidInput, + "block height and transaction not match", + )); + } + let wallet = self.wallet_service.get_wallet(name, passphrase)?; + + let wallet_state = self.wallet_service.get_wallet_state(name, passphrase)?; + + let imported_value = import_transaction( + &wallet, + &wallet_state, + &mut memento, + &tx_info.tx, + tx_info.block_height, + block.header.time, + spent_flags?, + ) + .chain(|| (ErrorKind::InvalidInput, "import error"))?; + + self.wallet_state_service + .apply_memento(name, passphrase, &memento)?; + Ok(imported_value) + } } impl MultiSigWalletClient for DefaultWalletClient @@ -718,3 +804,41 @@ fn parse_feedback(feedback: Option<&Feedback>) -> String { } } } + +fn import_transaction( + wallet: &Wallet, + wallet_state: &WalletState, + memento: &mut WalletStateMemento, + transaction: &Transaction, + block_height: u64, + block_time: Time, + spent_flag: Vec, +) -> Result { + let transaction_change = + create_transaction_change(wallet, wallet_state, transaction, block_height, block_time) + .chain(|| (ErrorKind::InvalidInput, "create transaction change failed"))?; + let mut value = Coin::zero(); + let transfer_addresses = wallet.transfer_addresses(); + for (i, (output, spent)) in transaction_change + .outputs + .iter() + .zip(spent_flag) + .enumerate() + { + // Only add unspent transaction if output address belongs to current wallet + if transfer_addresses.contains(&output.address) && !spent { + memento.add_unspent_transaction( + TxoPointer::new(transaction_change.transaction_id, i), + output.clone(), + ); + value = (value + output.value).chain(|| { + ( + ErrorKind::InvalidInput, + "invalid coin in outputs of transaction", + ) + })?; + } + } + memento.add_transaction_change(transaction_change); + Ok(value) +} diff --git a/client-core/src/wallet/syncer.rs b/client-core/src/wallet/syncer.rs index 44c184cbf..30bbae3c6 100644 --- a/client-core/src/wallet/syncer.rs +++ b/client-core/src/wallet/syncer.rs @@ -260,8 +260,7 @@ impl<'a, S: SecureStorage, C: Client, D: TxDecryptor> WalletSyncerImpl<'a, S, C, } } - fn save(&mut self, memento: &WalletStateMemento) -> Result<()> { - service::save_sync_state(&self.env.storage, &self.env.name, &self.sync_state)?; + fn update_state(&mut self, memento: &WalletStateMemento) -> Result<()> { self.wallet_state = service::modify_wallet_state( &self.env.storage, &self.env.name, @@ -271,6 +270,12 @@ impl<'a, S: SecureStorage, C: Client, D: TxDecryptor> WalletSyncerImpl<'a, S, C, Ok(()) } + fn save(&mut self, memento: &WalletStateMemento) -> Result<()> { + service::save_sync_state(&self.env.storage, &self.env.name, &self.sync_state)?; + self.update_state(memento)?; + Ok(()) + } + fn handle_batch(&mut self, blocks: NonEmpty) -> Result<()> { let enclave_txids = blocks .iter() @@ -370,7 +375,6 @@ impl<'a, S: SecureStorage, C: Client, D: TxDecryptor> WalletSyncerImpl<'a, S, C, self.handle_batch(non_empty_batch)?; } } - Ok(()) } diff --git a/client-core/src/wallet/syncer_logic.rs b/client-core/src/wallet/syncer_logic.rs index 4376669b3..9fda4749f 100644 --- a/client-core/src/wallet/syncer_logic.rs +++ b/client-core/src/wallet/syncer_logic.rs @@ -20,7 +20,7 @@ use crate::types::{BalanceChange, TransactionChange, TransactionInput, Transacti use crate::WalletStateMemento; #[derive(Error, Debug)] -pub(crate) enum SyncerLogicError { +pub enum SyncerLogicError { #[error("Total input amount exceeds maximum allowed value(txid: {0})")] TotalInputOutOfBound(String), #[error("Total output amount exceeds maximum allowed value(txid: {0})")] @@ -76,15 +76,13 @@ pub(crate) fn handle_blocks( Ok(memento) } -/// Update WalletStateMemento with transaction -pub(crate) fn handle_transaction( +pub fn create_transaction_change( wallet: &Wallet, wallet_state: &WalletState, - memento: &mut WalletStateMemento, transaction: &Transaction, block_height: u64, block_time: Time, -) -> Result<(), SyncerLogicError> { +) -> Result { let transaction_id = transaction.id(); let outputs = transaction.outputs().to_vec(); let transaction_type = TransactionType::from(transaction); @@ -100,16 +98,20 @@ pub(crate) fn handle_transaction( block_height, block_time, }; - - on_transaction_change(wallet, memento, transaction_change); - Ok(()) + Ok(transaction_change) } -fn on_transaction_change( +/// Update WalletStateMemento with transaction +pub(crate) fn handle_transaction( wallet: &Wallet, + wallet_state: &WalletState, memento: &mut WalletStateMemento, - transaction_change: TransactionChange, -) { + transaction: &Transaction, + block_height: u64, + block_time: Time, +) -> Result<(), SyncerLogicError> { + let transaction_change = + create_transaction_change(wallet, wallet_state, transaction, block_height, block_time)?; for input in transaction_change.inputs.iter() { memento.remove_unspent_transaction(input.pointer.clone()); } @@ -127,6 +129,7 @@ fn on_transaction_change( } memento.add_transaction_change(transaction_change); + Ok(()) } fn decorate_inputs( diff --git a/client-rpc/src/rpc/wallet_rpc.rs b/client-rpc/src/rpc/wallet_rpc.rs index 8fba1e8ed..a8309fc57 100644 --- a/client-rpc/src/rpc/wallet_rpc.rs +++ b/client-rpc/src/rpc/wallet_rpc.rs @@ -91,6 +91,12 @@ pub trait WalletRpc: Send + Sync { limit: usize, reversed: bool, ) -> Result>; + + #[rpc(name = "wallet_exportTransaction")] + fn export_plain_tx(&self, request: WalletRequest, txid: String) -> Result; + + #[rpc(name = "wallet_importTransaction")] + fn import_plain_tx(&self, request: WalletRequest, tx: String) -> Result; } pub struct WalletRpcImpl @@ -340,6 +346,18 @@ where } } + fn export_plain_tx(&self, request: WalletRequest, txid: String) -> Result { + self.client + .export_plain_tx(&request.name, &request.passphrase, &txid) + .map_err(to_rpc_error) + } + + fn import_plain_tx(&self, request: WalletRequest, tx: String) -> Result { + self.client + .import_plain_tx(&request.name, &request.passphrase, &tx) + .map_err(to_rpc_error) + } + fn transactions( &self, request: WalletRequest, From 006ed0cad94e4167884e257691fa0c482d9e1ffe Mon Sep 17 00:00:00 2001 From: linfeng Date: Wed, 25 Dec 2019 14:28:37 +0800 Subject: [PATCH 2/2] Problem:(CRO-661) not enough logging in debug enclave execution --- .../tx-validation/app/src/enclave_u/mod.rs | 31 ++++++++++++++++--- .../tx-validation/app/src/server/mod.rs | 31 +++++++++++++------ .../tx-validation/enclave/Cargo.toml | 1 + .../tx-validation/enclave/src/lib.rs | 8 ++++- .../tx-validation/enclave/src/obfuscate.rs | 8 +++-- .../tx-validation/enclave/src/validate.rs | 19 ++++++++++-- enclave-protocol/src/lib.rs | 14 ++++++--- 7 files changed, 86 insertions(+), 26 deletions(-) diff --git a/chain-tx-enclave/tx-validation/app/src/enclave_u/mod.rs b/chain-tx-enclave/tx-validation/app/src/enclave_u/mod.rs index fd9bdc736..63c17ac90 100644 --- a/chain-tx-enclave/tx-validation/app/src/enclave_u/mod.rs +++ b/chain-tx-enclave/tx-validation/app/src/enclave_u/mod.rs @@ -96,10 +96,21 @@ pub fn encrypt_tx( let response = IntraEnclaveResponse::decode(&mut response_buf.as_slice()); match response { Ok(Ok(IntraEnclaveResponseOk::Encrypt(obftx))) => Ok(obftx), - Ok(Err(e)) => Err(e), - _ => Err(Error::EnclaveRejected), + Ok(Ok(_)) => { + log::error!("encrypt unsupported tx"); + Err(Error::EnclaveRejected) + }, + Ok(Err(e)) => { + log::error!("encrypt tx error: {:?}", e); + Err(Error::EnclaveRejected) + }, + Err(e) => { + log::error!("encrypt tx response failed: {:?}", e); + Err(Error::EnclaveRejected) + } } } else { + log::error!("sgx status error: retval: {:?}, ecall result: {:?}", retval, result); Err(Error::EnclaveRejected) } } @@ -136,7 +147,10 @@ pub fn check_tx( ) => { let _ = txdb .insert(&request.tx.tx_id(), sealed_tx) - .map_err(|_| Error::IoError)?; + .map_err(|e| { + log::error!("insert tx id to db failed: {:?}", e); + Error::IoError + })?; if let Some(mut account) = request.account { account.withdraw(); Ok((paid_fee, Some(account))) @@ -175,10 +189,17 @@ pub fn check_tx( let fee = request.info.min_fee_computed; Ok((fee, account)) } - (_, Ok(Err(e))) => Err(e), - (_, _) => Err(Error::EnclaveRejected), + (_, Ok(Err(e))) => { + log::error!("get error response: {:?}", e); + Err(e) + }, + (_req, _resp) => { + log::error!("unsupported or error response"); + Err(Error::EnclaveRejected) + }, } } else { + log::error!("sgx status error: retval: {:?}, ecall result: {:?}", retval, result); Err(Error::EnclaveRejected) } } diff --git a/chain-tx-enclave/tx-validation/app/src/server/mod.rs b/chain-tx-enclave/tx-validation/app/src/server/mod.rs index 84a73548d..45290b488 100644 --- a/chain-tx-enclave/tx-validation/app/src/server/mod.rs +++ b/chain-tx-enclave/tx-validation/app/src/server/mod.rs @@ -7,7 +7,6 @@ use enclave_protocol::IntraEnclaveRequest; use enclave_protocol::{ is_basic_valid_tx_request, EnclaveRequest, EnclaveResponse, IntraEncryptRequest, FLAGS, }; -use log::{debug, info}; use parity_scale_codec::{Decode, Encode}; use sgx_urts::SgxEnclave; use sled::Tree; @@ -32,7 +31,10 @@ impl TxValidationServer { metadb: Tree, ) -> Result { match metadb.get(LAST_CHAIN_INFO_KEY) { - Err(_) => Err(Error::EFAULT), + Err(e) => { + log::error!("get last chain info failed: {:?}", e); + Err(Error::EFAULT) + }, Ok(s) => { let info = s.map(|stored| { ChainInfo::decode(&mut stored.as_ref()).expect("stored chain info corrupted") @@ -86,19 +88,22 @@ impl TxValidationServer { } pub fn execute(&mut self) { - info!("running zmq server"); + log::info!("running zmq server"); loop { if let Ok(msg) = self.socket.recv_bytes(FLAGS) { - debug!("received a message"); + log::debug!("received a message"); let mcmd = EnclaveRequest::decode(&mut msg.as_slice()); let resp = match mcmd { Ok(EnclaveRequest::CheckChain { chain_hex_id, last_app_hash, }) => { - debug!("check chain"); + log::debug!("check chain"); match self.metadb.get(LAST_APP_HASH_KEY) { - Err(_) => EnclaveResponse::CheckChain(Err(None)), + Err(e) => { + log::error!("get last app hash failed: {:?}", e); + EnclaveResponse::CheckChain(Err(None)) + }, Ok(s) => { let ss = s.map(|stored| { let mut app_hash = [0u8; 32]; @@ -112,6 +117,7 @@ impl TxValidationServer { ss, )) } else { + log::error!("app hash not match"); EnclaveResponse::CheckChain(Err(ss)) } } @@ -128,14 +134,16 @@ impl TxValidationServer { self.info = Some(info); EnclaveResponse::CommitBlock(Ok(())) } else { + log::error!("flush data failed when commit block"); EnclaveResponse::CommitBlock(Err(())) } } Ok(EnclaveRequest::VerifyTx(req)) => { let chid = req.info.chain_hex_id; let mtxins = self.lookup(&req.tx); - if is_basic_valid_tx_request(&req, &mtxins, chid).is_err() { - EnclaveResponse::UnknownRequest + if let Err(e) = is_basic_valid_tx_request(&req, &mtxins, chid) { + log::error!("verify transaction failed: {:?}", e); + EnclaveResponse::UnknownRequest } else { EnclaveResponse::VerifyTx(check_tx( self.enclave.geteid(), @@ -170,12 +178,15 @@ impl TxValidationServer { IntraEnclaveRequest::Encrypt(Box::new(request)), ) } - _ => Err(chain_tx_validation::Error::EnclaveRejected), + _ => { + log::error!("can not find encrypted transaction"); + Err(chain_tx_validation::Error::EnclaveRejected) + }, }; EnclaveResponse::EncryptTx(result) } Err(e) => { - debug!("unknown request / failed to decode: {}", e); + log::error!("unknown request / failed to decode: {}", e); EnclaveResponse::UnknownRequest } }; diff --git a/chain-tx-enclave/tx-validation/enclave/Cargo.toml b/chain-tx-enclave/tx-validation/enclave/Cargo.toml index 660e08cab..6a4f9851e 100644 --- a/chain-tx-enclave/tx-validation/enclave/Cargo.toml +++ b/chain-tx-enclave/tx-validation/enclave/Cargo.toml @@ -33,4 +33,5 @@ lazy_static = { version = "1.4", features = ["spin_no_std"] } enclave-t-common = { path = "../../enclave-t-common" } aes-gcm-siv = "0.3" aead = "0.2" +log = "0.4.8" zeroize = { version = "1.0", default-features = false } diff --git a/chain-tx-enclave/tx-validation/enclave/src/lib.rs b/chain-tx-enclave/tx-validation/enclave/src/lib.rs index 4c6937ff4..39b5dad49 100644 --- a/chain-tx-enclave/tx-validation/enclave/src/lib.rs +++ b/chain-tx-enclave/tx-validation/enclave/src/lib.rs @@ -25,6 +25,7 @@ pub extern "C" fn ecall_initchain(chain_hex_id: u8) -> sgx_status_t { if chain_hex_id == NETWORK_HEX_ID { sgx_status_t::SGX_SUCCESS } else { + log::error!("network hex id not match"); sgx_status_t::SGX_ERROR_INVALID_PARAMETER } } @@ -45,7 +46,12 @@ pub extern "C" fn ecall_check_tx( Ok(IntraEnclaveRequest::Encrypt(request)) => { obfuscate::handle_encrypt_request(request, response_buf, response_len) } - _ => { + Ok(s) => { + log::error!("unsupported request"); + return sgx_status_t::SGX_ERROR_INVALID_PARAMETER; + } + Err(e) => { + log::error!("ecall check tx failed: {:?}", e); return sgx_status_t::SGX_ERROR_INVALID_PARAMETER; } } diff --git a/chain-tx-enclave/tx-validation/enclave/src/obfuscate.rs b/chain-tx-enclave/tx-validation/enclave/src/obfuscate.rs index 7c489f6e7..5a56428e8 100644 --- a/chain-tx-enclave/tx-validation/enclave/src/obfuscate.rs +++ b/chain-tx-enclave/tx-validation/enclave/src/obfuscate.rs @@ -123,7 +123,8 @@ fn unseal_request(request: &mut IntraEncryptRequest) -> Option x, - Err(_) => { + Err(e) => { + log::error!("unsal data failed: {:?}", e); return None; } }; @@ -134,7 +135,10 @@ fn unseal_request(request: &mut IntraEncryptRequest) -> Option Some(o), - Err(_) => None, + Err(e) => { + log::error!("decode encryption request failed: {:?}", e); + None + }, } } diff --git a/chain-tx-enclave/tx-validation/enclave/src/validate.rs b/chain-tx-enclave/tx-validation/enclave/src/validate.rs index 85979b6b0..892bd9395 100644 --- a/chain-tx-enclave/tx-validation/enclave/src/validate.rs +++ b/chain-tx-enclave/tx-validation/enclave/src/validate.rs @@ -68,12 +68,16 @@ fn construct_sealed_response( ) -> Result { let to_seal = to_seal_tx.encode(); match result { - Err(e) => Ok(Err(e)), + Err(e) => { + log::error!("encode tx witoutputs failed: {:?}", e); + Ok(Err(e)) + }, Ok(fee) => { let sealing_result = SgxSealedData::<[u8]>::seal_data(txid, &to_seal); let sealed_data = match sealing_result { Ok(x) => x, Err(ret) => { + log::error!("sgx failed to seal data: {:?}", ret); return Err(ret); } }; @@ -89,6 +93,7 @@ fn construct_sealed_response( sealed_log_size as u32, ); if sealed_r.is_none() { + log::error!("decode sealed data to raw failed"); return Err(sgx_status_t::SGX_ERROR_INVALID_PARAMETER); } } @@ -128,6 +133,7 @@ pub(crate) fn write_back_response( } sgx_status_t::SGX_SUCCESS } else { + log::error!("response length exceeds the limit"); sgx_status_t::SGX_ERROR_INVALID_PARAMETER } } @@ -148,7 +154,8 @@ pub(crate) fn handle_validate_tx( response_buf: *mut u8, response_len: u32, ) -> sgx_status_t { - if is_basic_valid_tx_request(&request, &tx_inputs, crate::NETWORK_HEX_ID).is_err() { + if let Err(e) = is_basic_valid_tx_request(&request, &tx_inputs, crate::NETWORK_HEX_ID) { + log::error!("check request failed: {}", e); return sgx_status_t::SGX_ERROR_INVALID_PARAMETER; } match (tx_inputs, request.tx) { @@ -166,6 +173,7 @@ pub(crate) fn handle_validate_tx( match (plaintx, unsealed_inputs) { (Ok(PlainTxAux::TransferTx(tx, witness)), Some(inputs)) => { if tx.id() != payload.txid || tx.outputs.len() as TxoIndex != no_of_outputs { + log::error!("input invalid txid or outputs index not match!"); return sgx_status_t::SGX_ERROR_INVALID_PARAMETER; } let result = verify_transfer(&tx, &witness, request.info, inputs); @@ -177,6 +185,7 @@ pub(crate) fn handle_validate_tx( write_back_response(response, response_buf, response_len) } _ => { + log::error!("can not find plain transfer transaction or unsealed inputs"); return sgx_status_t::SGX_ERROR_INVALID_PARAMETER; } } @@ -191,6 +200,7 @@ pub(crate) fn handle_validate_tx( write_back_response(response, response_buf, response_len) } _ => { + log::error!("can not get plain deposit stake transaction or unsealed inputs"); return sgx_status_t::SGX_ERROR_INVALID_PARAMETER; } } @@ -204,7 +214,8 @@ pub(crate) fn handle_validate_tx( }, ) => { let address = verify_tx_recover_address(&witness, &payload.txid); - if address.is_err() { + if let Err(e) = address { + log::error!("get recover address failed: {:?}", e); return sgx_status_t::SGX_ERROR_INVALID_PARAMETER; } let plaintx = decrypt(&payload); @@ -225,11 +236,13 @@ pub(crate) fn handle_validate_tx( write_back_response(response, response_buf, response_len) } _ => { + log::error!("invalid parameter"); return sgx_status_t::SGX_ERROR_INVALID_PARAMETER; } } } (_, _) => { + log::error!("invalid parameter"); return sgx_status_t::SGX_ERROR_INVALID_PARAMETER; } } diff --git a/enclave-protocol/src/lib.rs b/enclave-protocol/src/lib.rs index 963526935..7cffae7e0 100644 --- a/enclave-protocol/src/lib.rs +++ b/enclave-protocol/src/lib.rs @@ -68,24 +68,28 @@ pub fn is_basic_valid_tx_request( request: &VerifyTxRequest, tx_inputs: &Option>, chain_hex_id: u8, -) -> Result<(), ()> { +) -> Result<(), std::string::String> { if request.info.chain_hex_id != chain_hex_id { - return Err(()); + return Err("hex id mismatch".into()); } match request.tx { TxEnclaveAux::DepositStakeTx { .. } => match tx_inputs { Some(ref i) if !i.is_empty() => Ok(()), - _ => Err(()), + _ => { + Err("sealed log is empty".into()) + }, }, TxEnclaveAux::TransferTx { .. } => match tx_inputs { Some(ref i) if !i.is_empty() => Ok(()), - _ => Err(()), + _ => { + Err("sealed log is empty".into()) + }, }, TxEnclaveAux::WithdrawUnbondedStakeTx { .. } => { if request.account.is_some() { Ok(()) } else { - Err(()) + Err("request account is empty".into()) } } }