diff --git a/CHANGELOG.md b/CHANGELOG.md index def3b10ef..5e1820977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,15 @@ *Unreleased* ## v0.5.0 ### Breaking changes + +- *chain-storage* [1466](https://github.com/crypto-com/chain/pull/1466): add a colume to store historical staking versions + ### Features + +- *chain-abci* [1458](https://github.com/crypto-com/chain/pull/1458): new abci_query path "sealed", query sealed log of transaction id. + ### Improvements + ### Bug Fixes *April 22, 2020* diff --git a/Cargo.lock b/Cargo.lock index 83a2e5b08..52dc7c2e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -501,6 +501,7 @@ dependencies = [ "kvdb", "kvdb-memorydb", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mock-utils", "parity-scale-codec", "protobuf", "quickcheck", @@ -744,6 +745,7 @@ dependencies = [ "jsonrpc-core", "lazy_static", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mock-utils", "non-empty-vec", "num-bigint 0.2.6", "parity-scale-codec", @@ -2487,6 +2489,14 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb967d27c8ae70e45f44a8f6b615f292fd865cf07f72573b1edd75f399790422" +[[package]] +name = "mock-utils" +version = "0.1.0" +dependencies = [ + "chain-core", + "parity-scale-codec", +] + [[package]] name = "native-tls" version = "0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 798c9237d..82faeec6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ members = [ "chain-tx-enclave/tx-query/enclave", "chain-tx-enclave/tx-query-next/app-runner", "chain-tx-enclave/tx-query-next/enclave-app", + "chain-tx-enclave/mock-utils", "cro-clib", ] diff --git a/chain-abci/Cargo.toml b/chain-abci/Cargo.toml index dd8ae439d..9d1683be0 100644 --- a/chain-abci/Cargo.toml +++ b/chain-abci/Cargo.toml @@ -7,9 +7,9 @@ readme = "README.md" edition = "2018" [features] -mock-validation = [] default = [] sgx-test = [] +mock-enclave = [] [dependencies] abci = "0.7" @@ -18,6 +18,7 @@ chain-storage = { path = "../chain-storage" } chain-tx-filter = { path = "../chain-tx-filter" } chain-tx-validation = { path = "../chain-tx-validation" } enclave-protocol = { path = "../enclave-protocol" } +mock-utils = { path = "../chain-tx-enclave/mock-utils" } log = "0.4.8" env_logger = "0.7.1" bit-vec = { version = "0.6.1", features = ["serde_no_std"] } diff --git a/chain-abci/fuzz/Cargo.toml b/chain-abci/fuzz/Cargo.toml index 880d38ed6..705ea5513 100644 --- a/chain-abci/fuzz/Cargo.toml +++ b/chain-abci/fuzz/Cargo.toml @@ -23,7 +23,7 @@ hex = "0.4" [dependencies.chain-abci] path = ".." -features = ["mock-validation"] +features = ["mock-enclave"] # Prevent this from interfering with workspaces [workspace] diff --git a/chain-abci/src/app/app_init.rs b/chain-abci/src/app/app_init.rs index ea411a992..c3712ca7e 100644 --- a/chain-abci/src/app/app_init.rs +++ b/chain-abci/src/app/app_init.rs @@ -8,7 +8,7 @@ use parity_scale_codec::{Decode, Encode}; use protobuf::Message; use serde::{Deserialize, Serialize}; -#[cfg(all(not(feature = "mock-validation"), target_os = "linux"))] +#[cfg(all(not(feature = "mock-enclave"), target_os = "linux"))] use crate::enclave_bridge::real::start_zmq; use crate::enclave_bridge::EnclaveProxy; use crate::staking::StakingTable; @@ -67,6 +67,10 @@ impl StoredChainState for ChainNodeState { fn get_last_app_hash(&self) -> H256 { self.last_apphash } + + fn get_staking_version(&self) -> Version { + self.staking_version + } } impl ChainNodeState { @@ -290,7 +294,7 @@ impl ChainNodeApp { .expect("failed to decode two last hex digits in chain ID")[0]; if let (Some(_), Some(_conn_str)) = (tx_query_address.as_ref(), enclave_server.as_ref()) { - #[cfg(all(not(feature = "mock-validation"), target_os = "linux"))] + #[cfg(all(not(feature = "mock-enclave"), target_os = "linux"))] let _ = start_zmq(_conn_str, chain_hex_id, storage.get_read_only()); } diff --git a/chain-abci/src/app/query.rs b/chain-abci/src/app/query.rs index 394b37993..2e1e39d51 100644 --- a/chain-abci/src/app/query.rs +++ b/chain-abci/src/app/query.rs @@ -190,6 +190,27 @@ impl ChainNodeApp { resp.code = 3; } } + "staking" => { + let height: BlockHeight = _req.height.try_into().expect("Invalid block height"); + let mversion = if height == BlockHeight::genesis() { + self.last_state.as_ref().map(|state| state.staking_version) + } else { + self.storage.get_historical_staking_version(height) + }; + let account_address = StakedStateAddress::try_from(_req.data.as_slice()); + if let (Some(version), Ok(address)) = (mversion, account_address) { + let (maccount, proof) = get_with_proof(&self.storage, version, &address); + resp.value = serde_json::to_string(&( + maccount, + if _req.prove { Some(proof) } else { None }, + )) + .unwrap() + .into_bytes(); + } else { + resp.log += "account lookup failed (either invalid address or node not correctly restored / initialized)"; + resp.code = 3; + } + } "state" => { if self.tx_query_address.is_none() { resp.code = 1; @@ -226,6 +247,14 @@ impl ChainNodeApp { .expect("Unable to serialize validator metadata into json") .into_bytes(); } + "sealed" => { + self.lookup( + &mut resp, + LookupItem::TxSealed, + &_req.data[..], + "sealed log not found", + ); + } _ => { resp.log += "invalid path"; resp.code = 1; diff --git a/chain-abci/src/enclave_bridge/mock.rs b/chain-abci/src/enclave_bridge/mock.rs index daf072ccd..2bcf81fcc 100644 --- a/chain-abci/src/enclave_bridge/mock.rs +++ b/chain-abci/src/enclave_bridge/mock.rs @@ -1,14 +1,11 @@ -#![allow(dead_code)] -///! TODO: feature-guard when workspaces can be built with --features flag: https://github.com/rust-lang/cargo/issues/5015 -use super::*; use chain_core::state::account::DepositBondTx; -use chain_core::tx::PlainTxAux; -use chain_core::tx::TxEnclaveAux; -use chain_core::tx::TxObfuscated; -use chain_core::tx::TxWithOutputs; +use chain_core::tx::{PlainTxAux, TxEnclaveAux, TxWithOutputs}; use chain_tx_filter::BlockFilter; use chain_tx_validation::{verify_bonded_deposit_core, verify_transfer, verify_unbonded_withdraw}; use enclave_protocol::IntraEnclaveResponseOk; +use mock_utils::{decrypt, seal, unseal}; + +use super::*; pub struct MockClient { chain_hex_id: u8, @@ -60,57 +57,31 @@ impl EnclaveProxy for MockClient { Ok(IntraEnclaveResponseOk::EndBlock(maybe_filter)) } IntraEnclaveRequest::Encrypt(_) => { - // TODO: mock / simulate ? + // In mock mode, client will do the encryption on their own. Err(chain_tx_validation::Error::EnclaveRejected) } IntraEnclaveRequest::ValidateTx { request, tx_inputs } => { let (tx, account, info) = (request.tx.clone(), request.account.clone(), request.info); - let (txpayload, inputs) = match (&tx, tx_inputs) { - ( - TxEnclaveAux::TransferTx { - payload: TxObfuscated { txpayload, .. }, - .. - }, - Some(inputs), - ) => ( - txpayload, - inputs - .iter() - .map(|x| TxWithOutputs::decode(&mut x.as_slice()).expect("TODO mock")) - .collect(), - ), + let (payload, inputs) = match (&tx, tx_inputs) { + (TxEnclaveAux::TransferTx { payload, .. }, Some(inputs)) => { + (payload, inputs.iter().map(|log| unseal(&log)).collect()) + } ( TxEnclaveAux::DepositStakeTx { tx: DepositBondTx { .. }, - payload: TxObfuscated { txpayload, .. }, + payload, .. }, Some(inputs), - ) => ( - txpayload, - inputs - .iter() - .map(|x| TxWithOutputs::decode(&mut x.as_slice()).expect("TODO mock")) - .collect(), - ), - ( - TxEnclaveAux::WithdrawUnbondedStakeTx { - payload: TxObfuscated { txpayload, .. }, - .. - }, - _, - ) => (txpayload, vec![]), + ) => (payload, inputs.iter().map(|log| unseal(&log)).collect()), + (TxEnclaveAux::WithdrawUnbondedStakeTx { payload, .. }, _) => (payload, vec![]), _ => unreachable!(), }; - // FIXME - let plain_tx = PlainTxAux::decode(&mut txpayload.as_slice()); + let plain_tx = decrypt(&payload); match (tx, plain_tx) { - ( - TxEnclaveAux::TransferTx { .. }, - Ok(PlainTxAux::TransferTx(maintx, witness)), - ) => { + (TxEnclaveAux::TransferTx { .. }, PlainTxAux::TransferTx(maintx, witness)) => { let result = verify_transfer(&maintx, &witness, &info, inputs); match result { Ok(fee) => { @@ -119,7 +90,7 @@ impl EnclaveProxy for MockClient { Ok(IntraEnclaveResponseOk::TxWithOutputs { paid_fee: fee, - sealed_tx: txwo.encode(), + sealed_tx: seal(&txwo), }) } Err(e) => Err(e), @@ -127,7 +98,7 @@ impl EnclaveProxy for MockClient { } ( TxEnclaveAux::DepositStakeTx { tx, .. }, - Ok(PlainTxAux::DepositStakeTx(witness)), + PlainTxAux::DepositStakeTx(witness), ) => { let result = verify_bonded_deposit_core(&tx, &witness, &info, inputs); match result { @@ -139,19 +110,20 @@ impl EnclaveProxy for MockClient { } ( TxEnclaveAux::WithdrawUnbondedStakeTx { .. }, - Ok(PlainTxAux::WithdrawUnbondedStakeTx(tx)), + PlainTxAux::WithdrawUnbondedStakeTx(tx), ) => { - let fee = verify_unbonded_withdraw( + let result = verify_unbonded_withdraw( &tx, &info, &account.expect("account exists in withdraw"), - )?; + ); + let fee = result?; let txwo = TxWithOutputs::StakeWithdraw(tx); self.add_view_keys(&txwo); Ok(IntraEnclaveResponseOk::TxWithOutputs { paid_fee: fee, - sealed_tx: txwo.encode(), + sealed_tx: seal(&txwo), }) } _ => Err(chain_tx_validation::Error::EnclaveRejected), diff --git a/chain-abci/src/enclave_bridge/mod.rs b/chain-abci/src/enclave_bridge/mod.rs index 4680b2785..f28663c11 100644 --- a/chain-abci/src/enclave_bridge/mod.rs +++ b/chain-abci/src/enclave_bridge/mod.rs @@ -1,10 +1,9 @@ use enclave_protocol::{IntraEnclaveRequest, IntraEnclaveResponse}; -use parity_scale_codec::{Decode, Encode}; /// TODO: feature-guard when workspaces can be built with --features flag: https://github.com/rust-lang/cargo/issues/5015 pub mod mock; -#[cfg(all(not(feature = "mock-validation"), target_os = "linux"))] +#[cfg(all(not(feature = "mock-enclave"), target_os = "linux"))] pub mod real; /// Abstracts over communication with an external part that does enclave calls diff --git a/chain-abci/src/main.rs b/chain-abci/src/main.rs index e3f944f67..b3ab7d470 100644 --- a/chain-abci/src/main.rs +++ b/chain-abci/src/main.rs @@ -4,13 +4,13 @@ use std::net::SocketAddr; use std::path::{Path, PathBuf}; use chain_abci::app::ChainNodeApp; -#[cfg(any(feature = "mock-validation", not(target_os = "linux")))] +#[cfg(any(feature = "mock-enclave", not(target_os = "linux")))] use chain_abci::enclave_bridge::mock::MockClient; -#[cfg(all(not(feature = "mock-validation"), target_os = "linux"))] +#[cfg(all(not(feature = "mock-enclave"), target_os = "linux"))] use chain_abci::enclave_bridge::real::TxValidationApp; use chain_core::init::network::{get_network, get_network_id, init_chain_id}; use chain_storage::{Storage, StorageConfig, StorageType}; -#[cfg(any(feature = "mock-validation", not(target_os = "linux")))] +#[cfg(any(feature = "mock-enclave", not(target_os = "linux")))] use log::warn; use serde::Deserialize; use std::io::BufReader; @@ -130,13 +130,13 @@ pub struct AbciOpt { } /// normal -#[cfg(all(not(feature = "mock-validation"), target_os = "linux"))] +#[cfg(all(not(feature = "mock-enclave"), target_os = "linux"))] fn get_enclave_proxy() -> TxValidationApp { TxValidationApp::default() } /// for development -#[cfg(any(feature = "mock-validation", not(target_os = "linux")))] +#[cfg(any(feature = "mock-enclave", not(target_os = "linux")))] fn get_enclave_proxy() -> MockClient { warn!("Using mock (non-enclave) infrastructure"); MockClient::new(get_network_id()) diff --git a/chain-abci/src/staking/table.rs b/chain-abci/src/staking/table.rs index c88460a2e..5601e03be 100644 --- a/chain-abci/src/staking/table.rs +++ b/chain-abci/src/staking/table.rs @@ -747,10 +747,10 @@ impl StakingTable { pub(crate) fn set_staking( heap: &mut impl StoreStaking, staking: StakedState, - minimal_required_staking: Coin, + _minimal_required_staking: Coin, ) { #[cfg(debug_assertions)] - staking.check_invariants(minimal_required_staking); + staking.check_invariants(_minimal_required_staking); heap.set_staking(staking) } diff --git a/chain-abci/tests/abci_app.rs b/chain-abci/tests/abci_app.rs index 57a1baee9..d7fda6767 100644 --- a/chain-abci/tests/abci_app.rs +++ b/chain-abci/tests/abci_app.rs @@ -26,9 +26,6 @@ use chain_core::state::{ChainState, RewardsPoolState}; use chain_core::tx::fee::{LinearFee, Milli}; use chain_core::tx::witness::tree::RawXOnlyPubkey; use chain_core::tx::witness::EcdsaSignature; -use chain_core::tx::PlainTxAux; -use chain_core::tx::TransactionId; -use chain_core::tx::TxObfuscated; use chain_core::tx::{ data::{ access::{TxAccess, TxAccessPolicy}, @@ -39,7 +36,7 @@ use chain_core::tx::{ txid_hash, Tx, TxId, }, witness::{TxInWitness, TxWitness}, - TxAux, TxEnclaveAux, TxPublicAux, + PlainTxAux, TransactionId, TxAux, TxEnclaveAux, TxPublicAux, }; use chain_storage::buffer::Get; use chain_storage::{ @@ -49,6 +46,7 @@ use chain_tx_filter::BlockFilter; use hex::decode; use kvdb::KeyValueDB; use kvdb_memorydb::create; +use mock_utils::encrypt; use parity_scale_codec::{Decode, Encode}; use secp256k1::schnorrsig::schnorr_sign; use secp256k1::{key::PublicKey, key::SecretKey, key::XOnlyPublicKey, Message, Secp256k1, Signing}; @@ -416,7 +414,7 @@ fn prepare_app_valid_tx() -> (ChainNodeApp, TxAux, WithdrawUnbondedT // leftover -- in previous tests, it was all paid as a fee TxOut::new_with_timelock( ExtendedAddr::OrTree([2; 32]), - Coin::new(9999999999899999667).unwrap(), + Coin::new(9999999999899999651).unwrap(), 0, ), ], @@ -424,16 +422,10 @@ fn prepare_app_valid_tx() -> (ChainNodeApp, TxAux, WithdrawUnbondedT ); let witness = StakedStateOpWitness::new(get_ecdsa_witness(&secp, &tx.id(), &secret_key)); - // TODO: mock enc let txaux = TxAux::EnclaveTx(TxEnclaveAux::WithdrawUnbondedStakeTx { no_of_outputs: tx.outputs.len() as TxoSize, witness: witness.clone(), - payload: TxObfuscated { - txid: tx.id(), - key_from: BlockHeight::genesis(), - init_vector: [0; 12], - txpayload: PlainTxAux::WithdrawUnbondedStakeTx(tx.clone()).encode(), - }, + payload: encrypt(&PlainTxAux::WithdrawUnbondedStakeTx(tx.clone()), tx.id()), }); (app, txaux, tx) } @@ -444,7 +436,7 @@ fn check_tx_should_accept_valid_tx() { let mut creq = RequestCheckTx::default(); creq.set_tx(txaux.encode()); let cresp = app.check_tx(&creq); - assert_eq!(0, cresp.code); + assert_eq!(0, cresp.code, "{}", cresp.log); } #[test] @@ -568,7 +560,7 @@ fn deliver_tx_should_add_tx_events() { assert_eq!(2, valid_tx_event.attributes.len()); // the unit test transaction just three outputs: 1 CRO + 1 carson / base unit + the rest assert_eq!( - "0.00000331", + "0.00000347", String::from_utf8(valid_tx_event.attributes[0].value.clone()).unwrap() ); assert_eq!( @@ -760,8 +752,21 @@ fn query_should_return_an_account() { qreq.data = hex::decode(&addr).unwrap(); qreq.path = "account".into(); let qresp = app.query(&qreq); - let account = StakedState::decode(&mut qresp.value.as_slice()); - assert!(account.is_ok()); + let account = StakedState::decode(&mut qresp.value.as_slice()).unwrap(); + assert_eq!(account.address, StakedStateAddress::from_str(addr).unwrap()); +} + +#[test] +fn staking_query_should_return_an_account() { + let addr = "fe7c045110b8dbf29765047380898919c5cb56f9"; + let mut app = init_chain_for(addr.parse().unwrap()); + let mut qreq = RequestQuery::new(); + qreq.data = hex::decode(&addr).unwrap(); + qreq.path = "staking".into(); + let qresp = app.query(&qreq); + let (account, _): (StakedState, serde_json::Value) = + serde_json::from_slice(&qresp.value).unwrap(); + assert_eq!(account.address, StakedStateAddress::from_str(addr).unwrap()); } fn block_commit(app: &mut ChainNodeApp, tx: TxAux, block_height: i64) { @@ -819,7 +824,7 @@ fn all_valid_tx_types_should_commit() { TxOut::new_with_timelock(eaddr.clone(), Coin::one(), 0), TxOut::new_with_timelock(eaddr.clone(), (Coin::one() + Coin::one()).unwrap(), 0), TxOut::new_with_timelock(eaddr.clone(), Coin::one(), 0), - TxOut::new_with_timelock(eaddr.clone(), Coin::new(9999999999599999618).unwrap(), 0), // rest + TxOut::new_with_timelock(eaddr.clone(), Coin::new(9999999999599999602).unwrap(), 0), // rest ], TxAttributes::new_with_access(0, vec![TxAccessPolicy::new(public_key, TxAccess::AllData)]), ); @@ -828,12 +833,7 @@ fn all_valid_tx_types_should_commit() { let withdrawtx = TxAux::EnclaveTx(TxEnclaveAux::WithdrawUnbondedStakeTx { no_of_outputs: tx0.outputs.len() as TxoSize, witness: witness0, - payload: TxObfuscated { - txid: tx0.id(), - key_from: BlockHeight::genesis(), - init_vector: [0u8; 12], - txpayload: PlainTxAux::WithdrawUnbondedStakeTx(tx0).encode(), - }, + payload: encrypt(&PlainTxAux::WithdrawUnbondedStakeTx(tx0.clone()), tx0.id()), }); { let account = get_account(&addr, &app).expect("acount not exist"); @@ -852,7 +852,7 @@ fn all_valid_tx_types_should_commit() { let utxo1 = TxoPointer::new(*txid, 0); let mut tx1 = Tx::new(); tx1.add_input(utxo1); - tx1.add_output(TxOut::new(eaddr, Coin::from(99999716u32))); + tx1.add_output(TxOut::new(eaddr, Coin::from(99999700u32))); let txid1 = tx1.id(); let witness1 = vec![TxInWitness::TreeSig( schnorr_sign(&secp, &Message::from_slice(&txid1).unwrap(), &secret_key), @@ -865,12 +865,7 @@ fn all_valid_tx_types_should_commit() { let transfertx = TxAux::EnclaveTx(TxEnclaveAux::TransferTx { inputs: tx1.inputs.clone(), no_of_outputs: tx1.outputs.len() as TxoSize, - payload: TxObfuscated { - txid: tx1.id(), - key_from: BlockHeight::genesis(), - init_vector: [0u8; 12], - txpayload: plain_txaux.encode(), - }, + payload: encrypt(&plain_txaux, tx1.id()), }); { let spent_utxos = get_tx_meta(&txid, &app); @@ -894,12 +889,7 @@ fn all_valid_tx_types_should_commit() { .into(); let depositx = TxAux::EnclaveTx(TxEnclaveAux::DepositStakeTx { tx: tx2.clone(), - payload: TxObfuscated { - txid: tx2.id(), - key_from: BlockHeight::genesis(), - init_vector: [0u8; 12], - txpayload: PlainTxAux::DepositStakeTx(witness2).encode(), - }, + payload: encrypt(&PlainTxAux::DepositStakeTx(witness2), tx2.id()), }); { let spent_utxos0 = get_tx_meta(&txid, &app); @@ -929,12 +919,7 @@ fn all_valid_tx_types_should_commit() { .into(); let depositx = TxAux::EnclaveTx(TxEnclaveAux::DepositStakeTx { tx: tx3.clone(), - payload: TxObfuscated { - txid: tx3.id(), - key_from: BlockHeight::genesis(), - init_vector: [0u8; 12], - txpayload: PlainTxAux::DepositStakeTx(witness3).encode(), - }, + payload: encrypt(&PlainTxAux::DepositStakeTx(witness3), tx3.id()), }); { let spent_utxos0 = get_tx_meta(txid, &app); diff --git a/chain-abci/tests/tx_validation.rs b/chain-abci/tests/tx_validation.rs index cee660a8b..3ec2aa6ee 100644 --- a/chain-abci/tests/tx_validation.rs +++ b/chain-abci/tests/tx_validation.rs @@ -47,7 +47,7 @@ use chain_tx_validation::{ }; use kvdb::KeyValueDB; use kvdb_memorydb::create; -use parity_scale_codec::Encode; +use mock_utils::{encrypt, encrypt_payload, seal}; use secp256k1::schnorrsig::schnorr_sign; use secp256k1::{key::PublicKey, key::SecretKey, key::XOnlyPublicKey, Message, Secp256k1, Signing}; use std::fmt::Debug; @@ -205,11 +205,10 @@ fn prepate_init_tx( let old_tx_id = old_tx.id(); let mut inittx = db.transaction(); - // FIXME: https://github.com/crypto-com/chain/issues/885 inittx.put( COL_ENCLAVE_TX, &old_tx_id[..], - &TxWithOutputs::Transfer(old_tx).encode(), + &seal(&TxWithOutputs::Transfer(old_tx)), ); inittx.put( @@ -240,20 +239,14 @@ fn prepare_app_valid_transfer_tx( tx.add_output(TxOut::new(addr, Coin::new(9).unwrap())); let sk2 = SecretKey::from_slice(&[0x11; 32]).expect("32 bytes, within curve order"); let addr2 = get_address(&secp, &sk2).0; - tx.add_output(TxOut::new(addr2, Coin::new(99999665).unwrap())); + tx.add_output(TxOut::new(addr2, Coin::new(99999649).unwrap())); let witness: Vec = vec![get_tx_witness(secp, &tx.id(), &secret_key, &merkle_tree)]; let plain_txaux = PlainTxAux::new(tx.clone(), witness.clone().into()); - // TODO: mock enc let txaux = TxEnclaveAux::TransferTx { inputs: tx.inputs.clone(), no_of_outputs: tx.outputs.len() as TxoSize, - payload: TxObfuscated { - txid: tx.id(), - key_from: BlockHeight::genesis(), - init_vector: [0; 12], - txpayload: plain_txaux.encode(), - }, + payload: encrypt(&plain_txaux, tx.id()), }; ( db.clone(), @@ -395,21 +388,15 @@ fn prepare_app_valid_withdraw_tx( let outputs = vec![ TxOut::new_with_timelock(addr1, Coin::new(9).unwrap(), 0), - TxOut::new_with_timelock(addr2, Coin::new(99999744).unwrap(), 0), + TxOut::new_with_timelock(addr2, Coin::new(99999728).unwrap(), 0), ]; let tx = WithdrawUnbondedTx::new(1, outputs, TxAttributes::new(DEFAULT_CHAIN_ID)); let witness = get_account_op_witness(secp, &tx.id(), &secret_key); - // TODO: mock enc let txaux = TxEnclaveAux::WithdrawUnbondedStakeTx { no_of_outputs: tx.outputs.len() as TxoSize, witness: witness.clone(), - payload: TxObfuscated { - txid: tx.id(), - key_from: BlockHeight::genesis(), - init_vector: [0; 12], - txpayload: PlainTxAux::WithdrawUnbondedStakeTx(tx.clone()).encode(), - }, + payload: encrypt(&PlainTxAux::WithdrawUnbondedStakeTx(tx.clone()), tx.id()), }; (txaux, tx, witness, account, secret_key, storage) } @@ -418,14 +405,14 @@ fn prepare_app_valid_withdraw_tx( fn existing_account_withdraw_tx_should_verify() { let (txaux, _, _, _, _, storage) = prepare_app_valid_withdraw_tx(0); let extra_info = get_chain_info_enc(&txaux); - let result = verify_enclave_tx( + verify_enclave_tx( &mut get_enclave_bridge_mock(), &txaux, &extra_info, 0, &storage, - ); - assert!(result.is_ok()); + ) + .unwrap(); } #[test] @@ -595,15 +582,9 @@ fn prepare_app_valid_deposit_tx( ); let witness: Vec = vec![get_tx_witness(secp, &tx.id(), &secret_key, &merkle_tree)]; - // TODO: mock enc let txaux = TxEnclaveAux::DepositStakeTx { tx: tx.clone(), - payload: TxObfuscated { - txid: tx.id(), - key_from: BlockHeight::genesis(), - init_vector: [0u8; 12], - txpayload: PlainTxAux::DepositStakeTx(witness.clone().into()).encode(), - }, + payload: encrypt(&PlainTxAux::DepositStakeTx(witness.clone().into()), tx.id()), }; ( db.clone(), @@ -862,7 +843,7 @@ fn replace_tx_payload( txid: tx.id(), key_from, init_vector, - txpayload: plain_tx.encode(), + txpayload: encrypt_payload(&plain_tx), }, }, ( @@ -882,7 +863,7 @@ fn replace_tx_payload( txid: tx.id(), key_from, init_vector, - txpayload: plain_tx.encode(), + txpayload: encrypt_payload(&plain_tx), }, }, ( @@ -904,7 +885,7 @@ fn replace_tx_payload( txid: tx.id(), key_from, init_vector, - txpayload: plain_tx.encode(), + txpayload: encrypt_payload(&plain_tx), }, }, _ => unreachable!(), @@ -1194,12 +1175,7 @@ fn prepare_withdraw_transaction(secret_key: &SecretKey) -> TxEnclaveAux { TxEnclaveAux::WithdrawUnbondedStakeTx { no_of_outputs: tx.outputs.len() as TxoSize, witness, - payload: TxObfuscated { - txid: tx.id(), - key_from: BlockHeight::genesis(), - init_vector: [0; 12], - txpayload: PlainTxAux::WithdrawUnbondedStakeTx(tx).encode(), - }, + payload: encrypt(&PlainTxAux::WithdrawUnbondedStakeTx(tx.clone()), tx.id()), } } @@ -1220,12 +1196,7 @@ fn prepare_deposit_transaction( TxEnclaveAux::DepositStakeTx { tx: tx.clone(), - payload: TxObfuscated { - txid: tx.id(), - key_from: BlockHeight::genesis(), - init_vector: [0u8; 12], - txpayload: PlainTxAux::DepositStakeTx(witness.into()).encode(), - }, + payload: encrypt(&PlainTxAux::DepositStakeTx(witness.into()), tx.id()), } } diff --git a/chain-storage/src/api.rs b/chain-storage/src/api.rs index d99197259..544cce44d 100644 --- a/chain-storage/src/api.rs +++ b/chain-storage/src/api.rs @@ -1,8 +1,9 @@ use std::collections::BTreeMap; use bit_vec::BitVec; -use parity_scale_codec::Encode; +use parity_scale_codec::{Decode, Encode}; +use crate::jellyfish::Version; use chain_core::common::H256; use chain_core::state::tendermint::BlockHeight; use chain_core::tx::data::{ @@ -13,7 +14,7 @@ use chain_core::tx::data::{ use super::buffer::{GetKV, StoreKV}; use super::{ LookupItem, StoredChainState, CHAIN_ID_KEY, COL_APP_HASHS, COL_APP_STATES, COL_EXTRA, - COL_NODE_INFO, GENESIS_APP_HASH_KEY, LAST_STATE_KEY, + COL_NODE_INFO, COL_STAKING_VERSIONS, GENESIS_APP_HASH_KEY, LAST_STATE_KEY, }; pub fn get_last_app_state(db: &impl GetKV) -> Option> { @@ -65,6 +66,11 @@ pub fn get_historical_app_hash(db: &impl GetKV, height: BlockHeight) -> Option Option { + let sah = db.get(&(COL_STAKING_VERSIONS, height.encode()))?; + Version::decode(&mut sah.as_slice()).ok() +} + pub fn store_chain_state( db: &mut impl StoreKV, genesis_state: &T, @@ -80,6 +86,10 @@ pub fn store_chain_state( (COL_APP_HASHS, encoded_height.clone()), genesis_state.get_last_app_hash().to_vec(), ); + db.set( + (COL_STAKING_VERSIONS, encoded_height.clone()), + genesis_state.get_staking_version().encode(), + ); if write_history_states { db.set( (COL_APP_STATES, encoded_height), diff --git a/chain-storage/src/lib.rs b/chain-storage/src/lib.rs index 7e31e23b8..a98f15c3a 100644 --- a/chain-storage/src/lib.rs +++ b/chain-storage/src/lib.rs @@ -29,7 +29,7 @@ pub const COL_NODE_INFO: u32 = 4; pub const COL_MERKLE_PROOFS: u32 = 5; /// Column for tracking app hashes: height => app hash pub const COL_APP_HASHS: u32 = 6; -/// Column for tracking app states: height => ChainNodeState, only available when tx_query_address set +/// Column for tracking app states: height => ChainState, only available when tx_query_address set pub const COL_APP_STATES: u32 = 7; /// Column for sealed transction payload: TxId => sealed tx payload (to MRSIGNER on a particular machine) pub const COL_ENCLAVE_TX: u32 = 8; @@ -37,8 +37,10 @@ pub const COL_ENCLAVE_TX: u32 = 8; pub const COL_TRIE_NODE: u32 = 9; /// Column for staled node key in merkle trie pub const COL_TRIE_STALED: u32 = 10; +/// Column to store block height -> staking version +pub const COL_STAKING_VERSIONS: u32 = 11; /// Number of columns in DB -pub const NUM_COLUMNS: u32 = 11; +pub const NUM_COLUMNS: u32 = 12; pub const CHAIN_ID_KEY: &[u8] = b"chain_id"; pub const GENESIS_APP_HASH_KEY: &[u8] = b"genesis_app_hash"; @@ -134,6 +136,8 @@ pub trait StoredChainState { fn get_encoded_top_level(&self) -> Vec; /// the last committed application hash fn get_last_app_hash(&self) -> H256; + /// the staking version + fn get_staking_version(&self) -> Version; } #[repr(u32)] @@ -219,6 +223,10 @@ impl Storage { get_historical_state(self, height) } + pub fn get_historical_staking_version(&self, height: BlockHeight) -> Option { + get_historical_staking_version(self, height) + } + pub fn get_historical_app_hash(&self, height: BlockHeight) -> Option { get_historical_app_hash(self, height) } diff --git a/chain-tx-enclave/mock-utils/Cargo.toml b/chain-tx-enclave/mock-utils/Cargo.toml new file mode 100644 index 000000000..09cf15ae9 --- /dev/null +++ b/chain-tx-enclave/mock-utils/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "mock-utils" +version = "0.1.0" +authors = ["Crypto.com "] +description = "Mock enclave utilities" +readme = "../../README.md" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chain-core = { path = "../../chain-core" } +parity-scale-codec = { features = ["derive"], version = "1.3" } diff --git a/chain-tx-enclave/mock-utils/src/lib.rs b/chain-tx-enclave/mock-utils/src/lib.rs new file mode 100644 index 000000000..e55c7c9b6 --- /dev/null +++ b/chain-tx-enclave/mock-utils/src/lib.rs @@ -0,0 +1,50 @@ +use parity_scale_codec::{Decode, Encode}; + +use chain_core::state::tendermint::BlockHeight; +use chain_core::tx::{data::TxId, PlainTxAux, TxObfuscated, TxWithOutputs}; + +const ENCRYPTION_KEY: u8 = 0x0f; +const SEAL_KEY: u8 = 0xf0; +pub fn decrypt(payload: &TxObfuscated) -> PlainTxAux { + let unpad = unpad_payload(&payload.txpayload); + let bs = unpad.iter().map(|b| b ^ ENCRYPTION_KEY).collect::>(); + PlainTxAux::decode(&mut bs.as_slice()).unwrap() +} + +fn unpad_payload(payload: &[u8]) -> &[u8] { + &payload[0..payload.len() - 16] +} + +pub fn seal(tx: &TxWithOutputs) -> Vec { + tx.encode() + .into_iter() + .map(|b| b ^ SEAL_KEY) + .collect::>() +} +pub fn unseal(payload: &[u8]) -> TxWithOutputs { + let bytes = payload.iter().map(|b| b ^ SEAL_KEY).collect::>(); + TxWithOutputs::decode(&mut bytes.as_slice()).unwrap() +} +fn pad_payload(payload: &[u8]) -> Vec { + let mut result = Vec::with_capacity(payload.len() + 16); + result.extend_from_slice(payload); + result.extend_from_slice(&[0; 16]); + result +} +pub fn encrypt_payload(plain: &PlainTxAux) -> Vec { + pad_payload( + &plain + .encode() + .into_iter() + .map(|b| b ^ ENCRYPTION_KEY) + .collect::>(), + ) +} +pub fn encrypt(plain: &PlainTxAux, txid: TxId) -> TxObfuscated { + TxObfuscated { + key_from: BlockHeight::genesis(), + init_vector: [0; 12], + txpayload: encrypt_payload(plain), + txid, + } +} diff --git a/chain-tx-enclave/tx-validation/enclave/src/obfuscate.rs b/chain-tx-enclave/tx-validation/enclave/src/obfuscate.rs index bab973730..3a320a262 100644 --- a/chain-tx-enclave/tx-validation/enclave/src/obfuscate.rs +++ b/chain-tx-enclave/tx-validation/enclave/src/obfuscate.rs @@ -77,7 +77,7 @@ pub extern "C" fn ecall_test_encrypt( .expect("construct plain payload"), ) } - Ok(EncryptionRequest::WithdrawStake(tx, witness)) => { + Ok(EncryptionRequest::WithdrawStake(tx, _witness)) => { let txid = tx.id(); encrypt( TxToObfuscate::from(PlainTxAux::WithdrawUnbondedStakeTx(tx), txid) diff --git a/client-cli/Cargo.toml b/client-cli/Cargo.toml index 6ded1ad4d..001992a38 100644 --- a/client-cli/Cargo.toml +++ b/client-cli/Cargo.toml @@ -8,6 +8,7 @@ build = "build.rs" [features] default = [] mock-hardware-wallet = ["client-core/mock-hardware-wallet"] +mock-enclave = [] [dependencies] chain-core = { path = "../chain-core"} diff --git a/client-cli/src/command.rs b/client-cli/src/command.rs index 198baa55c..ee7ab44be 100644 --- a/client-cli/src/command.rs +++ b/client-cli/src/command.rs @@ -9,6 +9,8 @@ use chrono::{DateTime, Local, NaiveDateTime, Utc}; use cli_table::format::{CellFormat, Color, Justify}; use cli_table::{Cell, Row, Table}; use hex::encode; +#[cfg(feature = "mock-enclave")] +use log::warn; use pbr::ProgressBar; use quest::{ask, success}; use structopt::StructOpt; @@ -16,11 +18,11 @@ use structopt::StructOpt; use chain_core::init::coin::Coin; use chain_core::state::account::StakedStateAddress; use client_common::storage::SledStorage; +#[cfg(not(feature = "mock-enclave"))] use client_common::tendermint::types::AbciQueryExt; use client_common::tendermint::types::GenesisExt; use client_common::tendermint::{Client, WebsocketRpcClient}; use client_common::{ErrorKind, Result, ResultExt, SecKey, Storage}; -use client_core::cipher::DefaultTransactionObfuscation; use client_core::signer::WalletSignerManager; use client_core::transaction_builder::DefaultWalletTransactionBuilder; use client_core::types::BalanceChange; @@ -42,6 +44,11 @@ use client_core::service::MockHardwareService; use once_cell::sync::Lazy; use std::env; +#[cfg(feature = "mock-enclave")] +use client_core::cipher::mock::MockAbciTransactionObfuscation; +#[cfg(not(feature = "mock-enclave"))] +use client_core::cipher::DefaultTransactionObfuscation; + static VERSION: Lazy = Lazy::new(|| { format!( "{} {}:{}\n {}\n{}", @@ -202,6 +209,7 @@ pub enum Command { } /// normal +#[cfg(not(feature = "mock-enclave"))] fn get_tx_query(tendermint_client: WebsocketRpcClient) -> Result { let result = tendermint_client.query("txquery", &[])?.bytes(); let address = std::str::from_utf8(&result).chain(|| { @@ -223,6 +231,15 @@ fn get_tx_query(tendermint_client: WebsocketRpcClient) -> Result Result> { + warn!("WARNING: Using mock (non-enclave) infrastructure"); + Ok(MockAbciTransactionObfuscation::new(tendermint_client)) +} + impl Command { pub fn execute(&self) -> Result<()> { match self { diff --git a/client-common/src/transaction.rs b/client-common/src/transaction.rs index 6c7d43f00..742715d8d 100644 --- a/client-common/src/transaction.rs +++ b/client-common/src/transaction.rs @@ -10,7 +10,7 @@ use chain_core::tx::data::input::TxoPointer; use chain_core::tx::data::output::TxOut; use chain_core::tx::data::{Tx, TxId}; use chain_core::tx::witness::TxWitness; -use chain_core::tx::TransactionId; +use chain_core::tx::{PlainTxAux, TransactionId, TxWithOutputs}; /// A struct which the sender can download and the receiver can import #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode)] @@ -111,6 +111,15 @@ impl TransactionId for Transaction { } } +impl From for Transaction { + fn from(tx: TxWithOutputs) -> Self { + match tx { + TxWithOutputs::Transfer(tx) => Self::TransferTransaction(tx), + TxWithOutputs::StakeWithdraw(tx) => Self::WithdrawUnbondedStakeTransaction(tx), + } + } +} + /// Enum representing a signed transaction #[derive(Debug, Clone, PartialEq, Encode, Decode)] pub enum SignedTransaction { @@ -133,3 +142,15 @@ impl TransactionId for SignedTransaction { } } } + +impl Into for SignedTransaction { + fn into(self) -> PlainTxAux { + match self { + Self::TransferTransaction(tx, witness) => PlainTxAux::TransferTx(tx, witness), + Self::DepositStakeTransaction(_tx, witness) => PlainTxAux::DepositStakeTx(witness), + Self::WithdrawUnbondedStakeTransaction(tx, _witness) => { + PlainTxAux::WithdrawUnbondedStakeTx(tx) + } + } + } +} diff --git a/client-core/Cargo.toml b/client-core/Cargo.toml index 2ba4eea1a..b8400cf87 100644 --- a/client-core/Cargo.toml +++ b/client-core/Cargo.toml @@ -11,6 +11,7 @@ client-common = { path = "../client-common" } chain-tx-filter = { path = "../chain-tx-filter" } enclave-protocol = { path = "../enclave-protocol" } chain-tx-validation = { path = "../chain-tx-validation" } +mock-utils = { path = "../chain-tx-enclave/mock-utils" } secp256k1zkp = { git = "https://github.com/crypto-com/rust-secp256k1-zkp.git", rev = "745bc8d8dc80cb921d5788e863a3536d3b6498a1", features = ["serde", "zeroize", "rand", "recovery", "endomorphism", "musig"] } parity-scale-codec = { features = ["derive"], version = "1.3" } chrono = { version = "0.4", features = ["serde"] } diff --git a/client-core/src/cipher.rs b/client-core/src/cipher.rs index 9ee6265ee..da16fffd8 100644 --- a/client-core/src/cipher.rs +++ b/client-core/src/cipher.rs @@ -2,6 +2,7 @@ mod default; pub mod cert; +pub mod mock; pub mod sgx; pub use default::DefaultTransactionObfuscation; diff --git a/client-core/src/cipher/mock.rs b/client-core/src/cipher/mock.rs new file mode 100644 index 000000000..9a1c10d22 --- /dev/null +++ b/client-core/src/cipher/mock.rs @@ -0,0 +1,196 @@ +//! mock transaction obfuscator +use std::convert::TryInto; + +use chain_core::tx::data::TxId; +use chain_core::tx::{TransactionId, TxAux, TxEnclaveAux, TxWithOutputs}; +use client_common::tendermint::Client; +use client_common::{PrivateKey, Result, SignedTransaction, Transaction}; +use mock_utils::{encrypt, unseal}; + +use crate::TransactionObfuscation; + +/// Implementation of transaction cipher which uses Tendermint ABCI to encrypt/decrypt transactions +#[derive(Debug, Clone)] +pub struct MockAbciTransactionObfuscation +where + C: Client, +{ + client: C, +} + +impl MockAbciTransactionObfuscation +where + C: Client, +{ + /// Creates a new instance of `MockAbciTransactionObfuscation` + #[inline] + pub fn new(client: C) -> Self { + Self { client } + } + /// Same constructor as `DefaultTransactionObfuscation` + pub fn from_tx_query(client: &C) -> Result { + Ok(Self::new(client.clone())) + } +} + +impl TransactionObfuscation for MockAbciTransactionObfuscation +where + C: Client, +{ + fn decrypt(&self, txids: &[TxId], private_key: &PrivateKey) -> Result> { + if txids.is_empty() { + return Ok(vec![]); + } + + let rsps = txids + .iter() + .map(|txid| self.client.query("sealed", txid)) + .collect::>>() + .expect("abci_query failed"); + + let sealed_logs = rsps + .into_iter() + .map(|rsp| rsp.value.expect("sealed log query failed")) + .collect::>(); + + let txs = sealed_logs + .into_iter() + .filter_map(|sealed| checked_unseal(&sealed, private_key)) + .map(Transaction::from) + .collect::>(); + + Ok(txs) + } + + fn encrypt(&self, transaction: SignedTransaction) -> Result { + let payload = encrypt(&transaction.clone().into(), transaction.id()); + let enclave_tx = match transaction { + SignedTransaction::TransferTransaction(tx, _) => TxEnclaveAux::TransferTx { + inputs: tx.inputs.clone(), + no_of_outputs: tx.outputs.len().try_into().unwrap(), + payload, + }, + SignedTransaction::DepositStakeTransaction(tx, _) => { + TxEnclaveAux::DepositStakeTx { tx, payload } + } + SignedTransaction::WithdrawUnbondedStakeTransaction(tx, witness) => { + TxEnclaveAux::WithdrawUnbondedStakeTx { + no_of_outputs: tx.outputs.len().try_into().unwrap(), + witness, + payload, + } + } + }; + Ok(TxAux::EnclaveTx(enclave_tx)) + } +} + +fn checked_unseal(payload: &[u8], _private_key: &PrivateKey) -> Option { + let tx = unseal(payload); + // TODO check view key + Some(tx) +} + +#[cfg(test)] +mod tests { + use super::*; + + use chain_core::state::ChainState; + use chain_core::tx::data::Tx; + use chain_core::tx::witness::TxWitness; + use chain_core::tx::{TxEnclaveAux, TxWithOutputs}; + use client_common::tendermint::lite; + use client_common::tendermint::types::*; + use client_common::PrivateKey; + use mock_utils::seal; + + #[derive(Clone)] + struct MockClient; + + impl Client for MockClient { + fn genesis(&self) -> Result { + unreachable!() + } + + fn status(&self) -> Result { + unreachable!() + } + + fn block(&self, _height: u64) -> Result { + unreachable!() + } + + fn block_batch<'a, T: Iterator>(&self, _heights: T) -> Result> { + unreachable!() + } + + fn block_results(&self, _height: u64) -> Result { + unreachable!() + } + + fn block_results_batch<'a, T: Iterator>( + &self, + _heights: T, + ) -> Result> { + unreachable!() + } + + fn block_batch_verified<'a, T: Clone + Iterator>( + &self, + _state: lite::TrustedState, + _heights: T, + ) -> Result<(Vec, lite::TrustedState)> { + unreachable!() + } + + fn broadcast_transaction(&self, _transaction: &[u8]) -> Result { + unreachable!() + } + + fn query(&self, _path: &str, _data: &[u8]) -> Result { + Ok(AbciQuery { + value: Some(seal(&TxWithOutputs::Transfer(Tx::default()))), + ..Default::default() + }) + } + + fn query_state_batch>( + &self, + _heights: T, + ) -> Result> { + unreachable!() + } + } + + #[test] + fn check_decryption() { + let cipher = MockAbciTransactionObfuscation::new(MockClient); + + assert_eq!( + 1, + cipher + .decrypt(&[[0; 32]], &PrivateKey::new().unwrap()) + .unwrap() + .len() + ) + } + + #[test] + fn check_encryption() { + let cipher = MockAbciTransactionObfuscation::new(MockClient); + + let encrypted_transaction = cipher + .encrypt(SignedTransaction::TransferTransaction( + Tx::default(), + TxWitness::default(), + )) + .unwrap(); + + match encrypted_transaction { + TxAux::EnclaveTx(TxEnclaveAux::TransferTx { no_of_outputs, .. }) => { + assert_eq!(0, no_of_outputs) + } + _ => unreachable!(), + } + } +} 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 84e1fefbd..c271a05cc 100644 --- a/client-core/src/transaction_builder/default_wallet_transaction_builder.rs +++ b/client-core/src/transaction_builder/default_wallet_transaction_builder.rs @@ -389,11 +389,7 @@ mod default_wallet_transaction_builder_tests { } })) .unwrap(); - // FIXME: all these unit tests don't account for 16-byte tag in encryption - // in those mock encrypt stuff, while the estimation does :/ - assert!( - (output_value + fee).unwrap() <= (input_value - Coin::from(16u32)).unwrap() - ); + assert!((output_value + fee).unwrap() <= input_value); for (i, input) in transaction.inputs.iter().enumerate() { let address = if input.id == [3; 32] { diff --git a/client-core/src/transaction_builder/raw_transfer_transaction_builder.rs b/client-core/src/transaction_builder/raw_transfer_transaction_builder.rs index b5afe9712..60659273d 100644 --- a/client-core/src/transaction_builder/raw_transfer_transaction_builder.rs +++ b/client-core/src/transaction_builder/raw_transfer_transaction_builder.rs @@ -555,13 +555,13 @@ mod raw_transfer_transaction_builder_tests { use chain_core::common::MerkleTree; use chain_core::common::H256; use chain_core::init::MAX_COIN; - use chain_core::state::tendermint::BlockHeight; use chain_core::tx::data::address::ExtendedAddr; use chain_core::tx::data::input::TxoSize; use chain_core::tx::fee::{LinearFee, Milli}; use chain_core::tx::witness::tree::RawXOnlyPubkey; - use chain_core::tx::{TxEnclaveAux, TxObfuscated}; + use chain_core::tx::TxEnclaveAux; use client_common::{MultiSigAddress, PrivateKey, PublicKey, Transaction}; + use mock_utils::encrypt; use crate::signer::{KeyPairSigner, Signer}; use crate::unspent_transactions::SelectedUnspentTransactions; @@ -1296,19 +1296,12 @@ mod raw_transfer_transaction_builder_tests { } fn encrypt(&self, transaction: SignedTransaction) -> Result { - let txpayload = transaction.encode(); - match transaction { - SignedTransaction::TransferTransaction(tx, _) => { + SignedTransaction::TransferTransaction(ref tx, _) => { Ok(TxAux::EnclaveTx(TxEnclaveAux::TransferTx { inputs: tx.inputs.clone(), no_of_outputs: tx.outputs.len() as TxoSize, - payload: TxObfuscated { - txid: [0; 32], - key_from: BlockHeight::genesis(), - init_vector: [0u8; 12], - txpayload, - }, + payload: encrypt(&transaction.clone().into(), [0; 32]), })) } _ => unreachable!(), diff --git a/client-rpc/Cargo.toml b/client-rpc/Cargo.toml index a5b75a34f..ad67af7db 100644 --- a/client-rpc/Cargo.toml +++ b/client-rpc/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Calvin Lau "] edition = "2018" [features] +mock-enclave = [] default = [] [dependencies] diff --git a/client-rpc/src/server.rs b/client-rpc/src/server.rs index 62716d1c6..a9bb8c6ad 100644 --- a/client-rpc/src/server.rs +++ b/client-rpc/src/server.rs @@ -4,6 +4,8 @@ use crate::rpc::staking_rpc::{StakingRpc, StakingRpcImpl}; use crate::rpc::sync_rpc::{SyncRpc, SyncRpcImpl}; use crate::rpc::transaction_rpc::{TransactionRpc, TransactionRpcImpl}; use crate::rpc::wallet_rpc::{WalletRpc, WalletRpcImpl}; +#[cfg(feature = "mock-enclave")] +use log::warn; use std::fmt::Debug; use std::net::SocketAddr; use std::thread; @@ -15,7 +17,6 @@ use client_common::storage::SledStorage; use client_common::tendermint::types::GenesisExt; use client_common::tendermint::{Client, WebsocketRpcClient}; use client_common::{ErrorKind, Result}; -use client_core::cipher::DefaultTransactionObfuscation; use client_core::service::HwKeyService; use client_core::signer::WalletSignerManager; use client_core::transaction_builder::DefaultWalletTransactionBuilder; @@ -25,7 +26,16 @@ use client_network::network_ops::DefaultNetworkOpsClient; use jsonrpc_core::{self, IoHandler}; use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder}; +#[cfg(feature = "mock-enclave")] +use client_core::cipher::mock::MockAbciTransactionObfuscation; +#[cfg(not(feature = "mock-enclave"))] +use client_core::cipher::DefaultTransactionObfuscation; + +#[cfg(not(feature = "mock-enclave"))] type AppTransactionCipher = DefaultTransactionObfuscation; +#[cfg(feature = "mock-enclave")] +type AppTransactionCipher = MockAbciTransactionObfuscation; + type AppTxBuilder = DefaultWalletTransactionBuilder; type AppWalletClient = DefaultWalletClient; type AppOpsClient = DefaultNetworkOpsClient< @@ -49,8 +59,10 @@ pub(crate) struct Server { } /// normal -fn get_tx_query(tendermint_client: WebsocketRpcClient) -> Result { - DefaultTransactionObfuscation::from_tx_query(&tendermint_client) +fn get_tx_query(tendermint_client: WebsocketRpcClient) -> Result { + #[cfg(feature = "mock-enclave")] + warn!("{}", "WARNING: Using mock (non-enclave) infrastructure"); + AppTransactionCipher::from_tx_query(&tendermint_client) } impl Server { diff --git a/cro-clib/Cargo.toml b/cro-clib/Cargo.toml index b87efbc04..443eae685 100644 --- a/cro-clib/Cargo.toml +++ b/cro-clib/Cargo.toml @@ -27,3 +27,6 @@ libc = "0.2.68" [build-dependencies] cbindgen = "0.14.0" + +[features] +mock-enclave = ["client-rpc/mock-enclave"] diff --git a/dev-utils/Cargo.toml b/dev-utils/Cargo.toml index 813d0a7ef..f2806176d 100644 --- a/dev-utils/Cargo.toml +++ b/dev-utils/Cargo.toml @@ -22,4 +22,4 @@ quest = "0.3" secstr = "0.4.0" [features] -mock-validation = ["chain-abci/mock-validation"] +mock-enclave = ["chain-abci/mock-enclave"] diff --git a/docker/build.sh b/docker/build.sh index 26009fcce..fc0b25191 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -25,9 +25,9 @@ if [ $BUILD_MODE == "sgx" ]; then make -C chain-tx-enclave/tx-validation make -C chain-tx-enclave/tx-query else - cargo build $CARGO_ARGS --manifest-path client-rpc/Cargo.toml - cargo build $CARGO_ARGS --manifest-path client-cli/Cargo.toml - cargo build $CARGO_ARGS --manifest-path cro-clib/Cargo.toml - cargo build $CARGO_ARGS --features mock-validation --manifest-path dev-utils/Cargo.toml - cargo build $CARGO_ARGS --features mock-validation --manifest-path chain-abci/Cargo.toml + cargo build $CARGO_ARGS --features mock-enclave --manifest-path client-rpc/Cargo.toml + cargo build $CARGO_ARGS --features mock-enclave --manifest-path client-cli/Cargo.toml + cargo build $CARGO_ARGS --features mock-enclave --manifest-path cro-clib/Cargo.toml + cargo build $CARGO_ARGS --features mock-enclave --manifest-path dev-utils/Cargo.toml + cargo build $CARGO_ARGS --features mock-enclave --manifest-path chain-abci/Cargo.toml fi diff --git a/docker/unittest.sh b/docker/unittest.sh index 9d05b6272..58df53be4 100755 --- a/docker/unittest.sh +++ b/docker/unittest.sh @@ -14,10 +14,10 @@ echo "Test $BUILD_MODE $BUILD_PROFILE" if [ $BUILD_MODE == "sgx" ]; then cargo test $CARGO_ARGS else - cargo test $CARGO_ARGS --manifest-path client-rpc/Cargo.toml - cargo test $CARGO_ARGS --manifest-path client-cli/Cargo.toml - cargo test $CARGO_ARGS --features mock-validation --manifest-path dev-utils/Cargo.toml - cargo test $CARGO_ARGS --features mock-validation --manifest-path chain-abci/Cargo.toml + cargo test $CARGO_ARGS --features mock-enclave --manifest-path client-rpc/Cargo.toml + cargo test $CARGO_ARGS --features mock-enclave --manifest-path client-cli/Cargo.toml + cargo test $CARGO_ARGS --features mock-enclave --manifest-path dev-utils/Cargo.toml + cargo test $CARGO_ARGS --features mock-enclave --manifest-path chain-abci/Cargo.toml for pkg in \ client-common \ client-network \ diff --git a/integration-tests/bot/chainrpc.py b/integration-tests/bot/chainrpc.py index 92a210073..4acdab0f2 100755 --- a/integration-tests/bot/chainrpc.py +++ b/integration-tests/bot/chainrpc.py @@ -3,6 +3,7 @@ import logging import base64 import binascii +import json import fire from jsonrpcclient import request @@ -48,6 +49,16 @@ def fix_address(addr): return addr +def fix_address_hex(addr): + 'fire convert staking addr to int automatically, fix it.' + if addr.startswith('0x'): + return addr[2:] + if isinstance(addr, int): + return '%040x' % addr + else: + return addr + + class BaseService: def __init__(self, base_port=None): self.base_port = base_port if base_port is not None else config('BASE_PORT', 26650, cast=int) @@ -279,7 +290,7 @@ def unconfirmed_txs(self): def latest_height(self): return self.status()['sync_info']['latest_block_height'] - def validators(self, height=None, page = 0, num_per_page = 100): + def validators(self, height=None, page=0, num_per_page=100): return self.call_chain('validators', str(height) if height is not None else None, str(page), str(num_per_page)) def block(self, height='latest'): @@ -299,7 +310,10 @@ def commit(self, height='latest'): return self.call_chain('commit', str(height)) def query(self, path, data=None, height=None, proof=False): - return self.call_chain('abci_query', path, fix_address(data), str(height) if height is not None else None, proof) + return self.call_chain( + 'abci_query', path, fix_address_hex(data), + str(height) if height is not None else None, proof + ) def broadcast_tx_commit(self, tx): return self.call_chain('broadcast_tx_commit', tx) @@ -314,8 +328,23 @@ def tx(self, txid, include_proof=False): txid = base64.b64encode(binascii.unhexlify(txid)).decode() return self.call_chain('tx', txid, include_proof) - def tx_search(self, query, include_proof=False, page=1, per_page=100, order_by="asc"): - return self.call_chain('tx_search', query=query, prove=include_proof, page=str(page), per_page=str(per_page), order_by = order_by) + def tx_search(self, query, include_proof=False, + page=1, per_page=100, order_by="asc"): + return self.call_chain( + 'tx_search', query=query, prove=include_proof, + page=str(page), per_page=str(per_page), + order_by=order_by + ) + + def staking(self, address, height=None, prove=False): + rsp = self.query("staking", address, height, prove) + rsp = rsp['response'] + assert rsp['code'] == 0, rsp + state, proof = json.loads(base64.b64decode(rsp['value'])) + if prove: + return state, proof + else: + return state class RPC: