diff --git a/.drone.yml b/.drone.yml index 3241b6a4f..e1c976917 100644 --- a/.drone.yml +++ b/.drone.yml @@ -7,17 +7,6 @@ platform: arch: amd64 steps: -- name: restore-cache - image: drillster/drone-volume-cache - volumes: - - name: cache - path: /cache - settings: - restore: true - # ttl: 1 - mount: - - ./drone - - name: build image: cryptocom/chain-test:latest pull: if-not-exists @@ -70,17 +59,6 @@ steps: - LD_LIBRARY_PATH=/opt/intel/libsgx-enclave-common/aesm /opt/intel/libsgx-enclave-common/aesm/aesm_service - ./integration-tests/run_multinode.sh -- name: rebuild-cache - image: drillster/drone-volume-cache - volumes: - - name: cache - path: /cache - settings: - rebuild: true - # ttl: 1 - mount: - - ./drone - - name: teardown image: cryptocom/chain-test:latest pull: if-not-exists @@ -92,9 +70,6 @@ steps: - failure volumes: - - name: cache - host: - path: /tmp/drone-cache - name: sgx host: path: /dev/sgx @@ -138,44 +113,8 @@ trigger: event: - push ---- -kind: pipeline -type: exec -name: sgx-cargo-1804-hw2 - -platform: - os: linux - arch: amd64 - -steps: -- name: Build and Test - environment: - SPID: - from_secret: SPID - IAS_API_KEY: - from_secret: IAS_API_KEY - commands: - - ls -l /dev/sgx - - ls -l /var/run/aesmd/aesm.socket - - docker run --name hw2-${DRONE_COMMIT_SHA} --rm --env SPID=$SPID --env IAS_API_KEY=$IAS_API_KEY -v $PWD:/chain --device /dev/sgx cryptocom/chain:latest /bin/bash /chain/ci-scripts/tx-query-hw-test.sh -- name: Teardown - commands: - - docker stop hw2-${DRONE_COMMIT_SHA} || exit 0 - when: - status: - - success - - failure - -trigger: - branch: - - master - - staging - - trying - event: - - push - --- kind: signature -hmac: 4ffc692e5e8f67c7eb37a5f594e7bbb0edc78959d19547613c87b95021ad1331 +hmac: 711400689fced0ffba34c8f2c2029123026ea75b7c0c7a37b4e0b02d5859bb98 ... diff --git a/.gitignore b/.gitignore index 985db2d68..d59fb4175 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ id_ecdsa # integration tests temparary artifacts /integration-tests/*.so /integration-tests/bot/.venv + +**/chain-abci/Enclave_u.* diff --git a/.travis.yml b/.travis.yml index 349ee7df2..7a3d65238 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ clean_cache: &clean_cache language: rust sudo: required - dist: trusty + dist: xenial if: type = cron cache: directories: # cargo caching from https://docs.travis-ci.com/user/caching/#rust-cargo-cache @@ -22,7 +22,7 @@ clean_cache: &clean_cache rust: &rust language: rust sudo: required - dist: trusty + dist: xenial addons: apt: packages: @@ -34,6 +34,7 @@ rust: &rust - gcc - binutils-dev - libc6-dev + - libzmq3-dev cache: directories: - $HOME/.cargo @@ -47,11 +48,13 @@ rust: &rust env: - RUST_BACKTRACE=1 - RUSTFLAGS="-Ctarget-feature=+aes,+sse2,+sse4.1,+ssse3 -D warnings" - - PATH=$HOME/.local/bin:$PATH - - LD_LIBRARY_PATH=$HOME/lib - - PKG_CONFIG_PATH=$HOME/lib/pkgconfig + - PATH=$HOME/.local/bin:$PATH:/opt/sgxsdk/bin/x64 + - LD_LIBRARY_PATH=$HOME/lib:/opt/sgxsdk/sdk_libs + - PKG_CONFIG_PATH=$HOME/lib/pkgconfig:/opt/sgxsdk/pkgconfig + - SGX_SDK=/opt/sgxsdk + - SGX_MODE=SW before_install: # versions from https://github.com/erickt/rust-zmq/blob/master/.travis.yml - - ./ci-scripts/install_zeromq.sh + - ./ci-scripts/install_sgxsdk.sh - | if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then ./ci-scripts/install_kcov.sh @@ -77,6 +80,12 @@ rust: &rust if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then # a small hack, as kcov doesn't have an option to only build default members sed 's/"chain-tx-enclave/#"chain-tx-enclave/g' -i Cargo.toml; + # more hacks for kcov :( + sed 's/default = \[\]/default = \["mock-validation"\]/g' -i chain-abci/Cargo.toml; + sed 's/sgx/#sgx/g' -i chain-abci/Cargo.toml; + sed 's/enclave-u-common/#enclave-u-common/g' -i chain-abci/Cargo.toml; + sed 's/CARGO/_CARGO/g' -i chain-abci/build.rs; + travis_wait 30 cargo kcov --all; bash <(curl -s https://codecov.io/bash); fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 1beefb65a..33b744133 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,12 @@ * client uses argon2 function for its internal storage key derivation. * *client* [976](https://github.com/crypto-com/chain/pull/976) Missing MultiSig pubkey methods in ClientRPC and ClientCLI -- Rename client-rpc method `wallet_newMultiSigAddressPublicKey` to `multiSig_newAddressPublicKey` +* ADR-001: tx-validation-app subsumed by chain-abci and sealed transaction payloads are stored in chain-abci's storage ... ### Bug Fixes * *client* [969](https://github.com/crypto-com/chain/pull/969): client-cli incorrect fee display in history +* *chain-abci* [1008](https://github.com/crypto-com/chain/pull/1008): unbonded or unjailed validator cannot rejoin the validator set *January 3, 2020* diff --git a/Cargo.lock b/Cargo.lock index 3e11b23d3..cd27752f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -428,12 +428,14 @@ dependencies = [ "bit-vec 0.6.1", "blake2", "byteorder", + "cc", "chain-core", "chain-storage", "chain-tx-filter", "chain-tx-validation", "digest 0.8.1", "enclave-protocol", + "enclave-u-common", "env_logger 0.7.1", "hex 0.4.1", "integer-encoding", @@ -449,6 +451,8 @@ dependencies = [ "serde_derive", "serde_json", "serde_yaml", + "sgx_types", + "sgx_urts", "sha3", "starling", "structopt", @@ -3258,9 +3262,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "sled" -version = "0.30.3" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb8c32cb0e34e67ad74fae1a77f4635d0cc7ffc873088a0136f3c4849336d71" +checksum = "8fb6824dde66ad33bf20c6e8476f5b82b871bc8bc3c129a10ea2f7dae5060fa3" dependencies = [ "crc32fast", "crossbeam-epoch", @@ -3873,8 +3877,6 @@ version = "0.3.0" dependencies = [ "cc", "chain-core", - "client-common", - "client-core", "enclave-protocol", "enclave-u-common", "env_logger 0.7.1", @@ -3916,25 +3918,6 @@ dependencies = [ "zeroize 1.1.0", ] -[[package]] -name = "tx-validation-app" -version = "0.3.0" -dependencies = [ - "cc", - "chain-core", - "chain-tx-validation", - "enclave-protocol", - "enclave-u-common", - "env_logger 0.7.1", - "log 0.4.8", - "parity-scale-codec", - "secp256k1zkp", - "sgx_types", - "sgx_urts", - "sled", - "zmq", -] - [[package]] name = "tx-validation-enclave" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 6e816b483..2f1bf9ef3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ members = [ "test-common", "dev-utils", "enclave-protocol", - "chain-tx-enclave/tx-validation/app", "chain-tx-enclave/tx-validation/enclave", "chain-tx-enclave/tx-query/app", "chain-tx-enclave/tx-query/enclave", diff --git a/chain-abci/Cargo.toml b/chain-abci/Cargo.toml index 727198cd3..6bc0fbcd3 100644 --- a/chain-abci/Cargo.toml +++ b/chain-abci/Cargo.toml @@ -10,6 +10,7 @@ edition = "2018" mock-enc-dec = [] mock-validation = [] default = [] +sgx-test = [] [dependencies] abci = "0.6" @@ -37,8 +38,16 @@ structopt = "0.3" secp256k1zkp = { git = "https://github.com/crypto-com/rust-secp256k1-zkp.git", rev = "0125097a7bf6f939db0ce52e49803c5e0312bf5e", features = ["recovery", "endomorphism"] } blake2 = "0.8" parity-scale-codec = { features = ["derive"], version = "1.1" } + +[target.'cfg(target_os = "linux")'.dependencies] +enclave-u-common = { path = "../chain-tx-enclave/enclave-u-common" } +sgx_types = { rev = "v1.1.0", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +sgx_urts = { rev = "v1.1.0", git = "https://github.com/apache/teaclave-sgx-sdk.git" } zmq = "0.9" +[build-dependencies] +cc = "1.0" + [dev-dependencies] quickcheck = "0.9" # TODO: currently not maintained benchmarks diff --git a/chain-abci/build.rs b/chain-abci/build.rs new file mode 100644 index 000000000..a08fe6973 --- /dev/null +++ b/chain-abci/build.rs @@ -0,0 +1,60 @@ +use std::env; +use std::path::Path; +use std::process::Command; + +fn main() { + match env::var("CARGO_CFG_TARGET_OS").as_ref() { + Ok(os) if os == "linux" => { + let sdk_dir = env::var("SGX_SDK").unwrap_or_else(|_| "/opt/intel/sgxsdk".to_string()); + if !Path::new(&sdk_dir).exists() { + println!("cargo:warning=\"SGX SDK not found\""); + } else { + let is_sim = env::var("SGX_MODE").unwrap_or_else(|_| "HW".to_string()); + + #[cfg(target_arch = "x86")] + let edger8r = format!("{}/bin/x86/sgx_edger8r", sdk_dir); + #[cfg(not(target_arch = "x86"))] + let edger8r = format!("{}/bin/x64/sgx_edger8r", sdk_dir); + + Command::new(edger8r) + .args(&[ + "--untrusted", + "../chain-tx-enclave/tx-validation/enclave/Enclave.edl", + "--search-path", + &format!("{}/include", sdk_dir), + "--search-path", + "../chain-tx-enclave/rust-sgx-sdk/edl", + "--untrusted-dir", + ".", + ]) + .status() + .unwrap(); + + cc::Build::new() + .file("Enclave_u.c") + .include(&format!("{}/include", sdk_dir)) + .include("../chain-tx-enclave/rust-sgx-sdk/edl") + .compile("enclave.a"); + + #[cfg(target_arch = "x86")] + println!("cargo:rustc-link-search=native={}/lib", sdk_dir); + #[cfg(not(target_arch = "x86"))] + println!("cargo:rustc-link-search=native={}/lib64", sdk_dir); + + match is_sim.as_ref() { + "SW" => println!("cargo:rustc-link-lib=dylib=sgx_urts_sim"), + _ => println!("cargo:rustc-link-lib=dylib=sgx_urts"), // default to HW + } + + println!( + "cargo:rerun-if-changed=../chain-tx-enclave/tx-validation/enclave/Enclave.edl" + ); + } + } + _ => { + println!( + "cargo:warning=\"Enclave compilation and execution is only supported on Linux\"" + ); + } + } +} diff --git a/chain-abci/src/app/app_init.rs b/chain-abci/src/app/app_init.rs index 79bce61d0..b5e42cec6 100644 --- a/chain-abci/src/app/app_init.rs +++ b/chain-abci/src/app/app_init.rs @@ -5,12 +5,13 @@ use std::collections::BTreeMap; use std::convert::TryInto; use abci::*; -use enclave_protocol::{EnclaveRequest, EnclaveResponse}; use log::{info, warn}; use parity_scale_codec::{Decode, Encode}; use protobuf::Message; use serde::{Deserialize, Serialize}; +#[cfg(all(not(feature = "mock-validation"), target_os = "linux"))] +use crate::enclave_bridge::real::start_zmq; use crate::enclave_bridge::EnclaveProxy; use chain_core::common::MerkleTree; use chain_core::common::Timespec; @@ -29,7 +30,7 @@ use chain_core::tx::TxAux; use chain_storage::account::AccountWrapper; use chain_storage::account::StarlingFixedKey; use chain_storage::account::{pure_account_storage, AccountStorage}; -use chain_storage::{Storage, StorageConfig, StoredChainState}; +use chain_storage::{Storage, StoredChainState}; use chain_tx_validation::NodeChecker; pub use validator_state::ValidatorState; use validator_state::ValidatorStateHelper; @@ -275,13 +276,15 @@ impl ChainNodeApp { /// * `storage` - underlying storage to be used (in-mem or persistent) /// * `accounts` - underlying storage for account tries to be used (in-mem or persistent) /// * `tx_query_address` - address of tx query enclave to supply to clients (if any) + /// * `enclave_server` - connection string which ZeroMQ server wrapper around the transaction validation enclave will listen on pub fn new_with_storage( - mut tx_validator: T, + tx_validator: T, gah: &str, chain_id: &str, mut storage: Storage, accounts: AccountStorage, tx_query_address: Option, + enclave_server: Option, ) -> Self { let decoded_gah = hex::decode(gah).expect("failed to decode genesis app hash"); let mut genesis_app_hash = [0u8; HASH_SIZE_256]; @@ -289,6 +292,11 @@ impl ChainNodeApp { let chain_hex_id = hex::decode(&chain_id[chain_id.len() - 2..]) .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"))] + let _ = start_zmq(_conn_str, chain_hex_id, storage.get_read_only()); + } + if let Some(data) = storage.get_last_app_state() { info!("last app state stored"); let last_state = @@ -311,23 +319,14 @@ impl ChainNodeApp { } // TODO: genesis app hash check when embedded in enclave binary - let enclave_sanity_check = tx_validator.process_request(EnclaveRequest::CheckChain { - chain_hex_id, - last_app_hash: Some(last_state.last_apphash), - }); + let enclave_sanity_check = tx_validator.check_chain(chain_hex_id); match enclave_sanity_check { - EnclaveResponse::CheckChain(Ok(_)) => { + Ok(_) => { info!("enclave connection OK"); } - EnclaveResponse::CheckChain(Err(enc_app)) => { - let enc_app_str = match enc_app { - None => "None".to_string(), - Some(data) => hex::encode(data), - }; - panic!("enclave sanity check failed (either a binary for a different network is used or there is a problem with enclave process), \ - enclave app hash: {} (chain-abci app hash: {})", enc_app_str, hex::encode(last_state.last_apphash)); + Err(()) => { + panic!("enclave sanity check failed (either a binary for a different network is used or there is a problem with enclave process)"); } - _ => unreachable!("unexpected enclave response"), } ChainNodeApp::restore_from_storage( @@ -342,23 +341,14 @@ impl ChainNodeApp { } else { info!("no last app state stored"); // TODO: genesis app hash check when embedded in enclave binary - let enclave_sanity_check = tx_validator.process_request(EnclaveRequest::CheckChain { - chain_hex_id, - last_app_hash: None, - }); + let enclave_sanity_check = tx_validator.check_chain(chain_hex_id); match enclave_sanity_check { - EnclaveResponse::CheckChain(Ok(_)) => { + Ok(_) => { info!("enclave connection OK"); } - EnclaveResponse::CheckChain(Err(enc_app)) => { - let enc_app_str = match enc_app { - None => "None".to_string(), - Some(data) => hex::encode(data), - }; - panic!("enclave sanity check failed (either a binary for a different network is used or there is a problem with enclave process), \ - enclave app hash: {}", enc_app_str); + Err(()) => { + panic!("enclave sanity check failed (either a binary for a different network is used or there is a problem with enclave process)"); } - _ => unreachable!("unexpected enclave response"), } storage.write_genesis_chain_id(&genesis_app_hash, chain_id); ChainNodeApp { @@ -376,34 +366,6 @@ impl ChainNodeApp { } } - /// Creates a new App initialized according to a provided storage config (most likely persistent). - /// - /// # Arguments - /// - /// * `tx_validator` - ZMQ proxy to enclave TX validator - /// * `gah` - hex-encoded genesis app hash - /// * `chain_id` - the chain ID set in Tendermint genesis.json (our name convention is that the last two characters should be hex digits) - /// * `node_storage_config` - configuration for node storage (currently only the path, but TODO: more options, e.g. SSD or HDD params) - /// * `account_storage_config` - configuration for account storage - /// * `tx_query_address` - address of tx query enclave to supply to clients (if any) - pub fn new( - tx_validator: T, - gah: &str, - chain_id: &str, - node_storage_config: &StorageConfig<'_>, - account_storage_config: &StorageConfig<'_>, - tx_query_address: Option, - ) -> ChainNodeApp { - ChainNodeApp::new_with_storage( - tx_validator, - gah, - chain_id, - Storage::new(node_storage_config), - AccountStorage::new(Storage::new(account_storage_config), 20).expect("account db"), - tx_query_address, - ) - } - /// Handles InitChain requests: /// should validate initial genesis distribution, initialize everything in the key-value DB and check it matches the expected values /// provided as arguments. diff --git a/chain-abci/src/app/commit.rs b/chain-abci/src/app/commit.rs index 045413783..ad039206b 100644 --- a/chain-abci/src/app/commit.rs +++ b/chain-abci/src/app/commit.rs @@ -6,10 +6,7 @@ use chain_core::compute_app_hash; use chain_core::tx::data::input::{TxoIndex, TxoPointer}; use chain_core::tx::data::TxId; use chain_core::tx::{TxAux, TxEnclaveAux}; -use chain_core::ChainInfo; use chain_storage::Storage; -use enclave_protocol::{EnclaveRequest, EnclaveResponse}; -use log::debug; use parity_scale_codec::Encode; /// Given a db and a DB transaction, it will go through TX inputs and mark them as spent @@ -102,25 +99,7 @@ impl ChainNodeApp { self.storage .store_txs_merkle_tree(&app_hash, &tree.encode()); new_state.last_apphash = app_hash; - match self - .tx_validator - .process_request(EnclaveRequest::CommitBlock { - app_hash, - info: ChainInfo { - // TODO: fee computation in enclave? - min_fee_computed: top_level.network_params.calculate_fee(0).expect("base fee"), - chain_hex_id: self.chain_hex_id, - previous_block_time: new_state.block_time, - unbonding_period: top_level.network_params.get_unbonding_period(), - }, - }) { - EnclaveResponse::CommitBlock(Ok(_)) => { - debug!("enclave storage persisted"); - } - _ => { - panic!("persisting enclave storage failed"); - } - } + self.storage.store_chain_state( &new_state, new_state.last_block_height, diff --git a/chain-abci/src/app/end_block.rs b/chain-abci/src/app/end_block.rs index 2c2f947ab..cbfbef0b8 100644 --- a/chain-abci/src/app/end_block.rs +++ b/chain-abci/src/app/end_block.rs @@ -3,7 +3,7 @@ use crate::enclave_bridge::EnclaveProxy; use abci::{Event, KVPair, RequestEndBlock, ResponseEndBlock}; use chain_core::common::TendermintEventType; use chain_tx_filter::BlockFilter; -use enclave_protocol::{EnclaveRequest, EnclaveResponse}; +use enclave_protocol::{IntraEnclaveRequest, IntraEnclaveResponseOk}; use protobuf::RepeatedField; impl ChainNodeApp { @@ -11,8 +11,10 @@ impl ChainNodeApp { pub fn end_block_handler(&mut self, _req: &RequestEndBlock) -> ResponseEndBlock { let mut resp = ResponseEndBlock::new(); if !self.delivered_txs.is_empty() { - let end_block_resp = self.tx_validator.process_request(EnclaveRequest::EndBlock); - if let EnclaveResponse::EndBlock(Ok(maybe_filter)) = end_block_resp { + let end_block_resp = self + .tx_validator + .process_request(IntraEnclaveRequest::EndBlock); + if let Ok(IntraEnclaveResponseOk::EndBlock(maybe_filter)) = end_block_resp { if let Some(raw_filter) = maybe_filter { let filter = BlockFilter::from(&*raw_filter); diff --git a/chain-abci/src/enclave_bridge/mock.rs b/chain-abci/src/enclave_bridge/mock.rs index 5c6d30f89..88d211c1c 100644 --- a/chain-abci/src/enclave_bridge/mock.rs +++ b/chain-abci/src/enclave_bridge/mock.rs @@ -5,22 +5,20 @@ use abci::{RequestQuery, ResponseQuery}; use chain_core::state::account::DepositBondTx; #[cfg(feature = "mock-enc-dec")] use chain_core::tx::data::input::TxoIndex; -use chain_core::tx::data::TxId; use chain_core::tx::PlainTxAux; -use chain_core::tx::TransactionId; use chain_core::tx::TxEnclaveAux; use chain_core::tx::TxObfuscated; use chain_core::tx::TxWithOutputs; use chain_storage::Storage; use chain_tx_filter::BlockFilter; -use chain_tx_validation::{verify_bonded_deposit, verify_transfer, verify_unbonded_withdraw}; +use chain_tx_validation::{verify_bonded_deposit_core, verify_transfer, verify_unbonded_withdraw}; +use enclave_protocol::IntraEnclaveResponseOk; #[cfg(feature = "mock-enc-dec")] use enclave_protocol::{ DecryptionRequest, DecryptionRequestBody, DecryptionResponse, EncryptionRequest, EncryptionResponse, }; use log::warn; -use std::collections::HashMap; /// TODO: Remove #[cfg(not(feature = "mock-enc-dec"))] @@ -129,7 +127,6 @@ pub fn handle_enc_dec(_req: &RequestQuery, resp: &mut ResponseQuery, storage: &S pub struct MockClient { chain_hex_id: u8, - pub local_tx_store: HashMap, filter: BlockFilter, } @@ -137,19 +134,10 @@ impl MockClient { pub fn new(chain_hex_id: u8) -> Self { MockClient { chain_hex_id, - local_tx_store: HashMap::new(), filter: BlockFilter::default(), } } - fn lookup(&self, txid: &TxId) -> TxWithOutputs { - let tx = self - .local_tx_store - .get(txid) - .expect("mock is expected to be fed valid/existing TX"); - (*tx).clone() - } - fn add_view_keys(&mut self, plain_tx: &TxWithOutputs) { match plain_tx { TxWithOutputs::StakeWithdraw(tx) => { @@ -167,71 +155,99 @@ impl MockClient { } impl EnclaveProxy for MockClient { - fn process_request(&mut self, request: EnclaveRequest) -> EnclaveResponse { - match request { - EnclaveRequest::CheckChain { chain_hex_id, .. } => { - if chain_hex_id == self.chain_hex_id { - EnclaveResponse::CheckChain(Ok(())) - } else { - EnclaveResponse::CheckChain(Err(None)) - } - } - EnclaveRequest::EndBlock => { + fn check_chain(&self, network_id: u8) -> Result<(), ()> { + if self.chain_hex_id == network_id { + Ok(()) + } else { + Err(()) + } + } + + fn process_request(&mut self, request: IntraEnclaveRequest) -> IntraEnclaveResponse { + match &request { + IntraEnclaveRequest::EndBlock => { let maybe_filter = if self.filter.is_modified() { Some(Box::new(self.filter.get_raw())) } else { None }; self.filter.reset(); - EnclaveResponse::EndBlock(Ok(maybe_filter)) + Ok(IntraEnclaveResponseOk::EndBlock(maybe_filter)) + } + IntraEnclaveRequest::Encrypt(_) => { + // TODO: mock / simulate ? + Err(chain_tx_validation::Error::EnclaveRejected) } - EnclaveRequest::CommitBlock { .. } => EnclaveResponse::CommitBlock(Ok(())), - EnclaveRequest::VerifyTx(txrequest) => { - let (tx, account, info) = (txrequest.tx, txrequest.account, txrequest.info); - let (txpayload, inputs) = match &tx { - TxEnclaveAux::TransferTx { - inputs, - payload: TxObfuscated { txpayload, .. }, - .. - } => ( + 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| self.lookup(&x.id)).collect(), + inputs + .iter() + .map(|x| TxWithOutputs::decode(&mut x.as_slice()).expect("TODO mock")) + .collect(), ), - TxEnclaveAux::DepositStakeTx { - tx: DepositBondTx { inputs, .. }, - payload: TxObfuscated { txpayload, .. }, - .. - } => ( + ( + TxEnclaveAux::DepositStakeTx { + tx: DepositBondTx { .. }, + payload: TxObfuscated { txpayload, .. }, + .. + }, + Some(inputs), + ) => ( txpayload, - inputs.iter().map(|x| self.lookup(&x.id)).collect(), + inputs + .iter() + .map(|x| TxWithOutputs::decode(&mut x.as_slice()).expect("TODO mock")) + .collect(), ), - TxEnclaveAux::WithdrawUnbondedStakeTx { - payload: TxObfuscated { txpayload, .. }, - .. - } => (txpayload, vec![]), + ( + TxEnclaveAux::WithdrawUnbondedStakeTx { + payload: TxObfuscated { txpayload, .. }, + .. + }, + _, + ) => (txpayload, vec![]), + _ => unreachable!(), }; // FIXME let plain_tx = PlainTxAux::decode(&mut txpayload.as_slice()); match (tx, plain_tx) { (_, Ok(PlainTxAux::TransferTx(maintx, witness))) => { let result = verify_transfer(&maintx, &witness, info, inputs); - let fakesealed = if result.is_ok() { - let txid = maintx.id(); - let txwo = TxWithOutputs::Transfer(maintx); - self.add_view_keys(&txwo); - self.local_tx_store.insert(txid, txwo.clone()); - Some(Box::new(txwo.encode())) - } else { - None - }; - EnclaveResponse::VerifyTx(result.map(|x| (x, None, fakesealed))) + match result { + Ok(fee) => { + let txwo = TxWithOutputs::Transfer(maintx); + self.add_view_keys(&txwo); + + Ok(IntraEnclaveResponseOk::TxWithOutputs { + paid_fee: fee, + sealed_tx: txwo.encode(), + }) + } + Err(e) => Err(e), + } } ( TxEnclaveAux::DepositStakeTx { tx, .. }, Ok(PlainTxAux::DepositStakeTx(witness)), ) => { - let result = verify_bonded_deposit(&tx, &witness, info, inputs, account); - EnclaveResponse::VerifyTx(result.map(|(x, y)| (x, y, None))) + let result = verify_bonded_deposit_core(&tx, &witness, info, inputs); + match result { + Ok(input_coins) => { + Ok(IntraEnclaveResponseOk::DepositStakeTx { input_coins }) + } + Err(e) => Err(e), + } } (_, Ok(PlainTxAux::WithdrawUnbondedStakeTx(tx))) => { let result = verify_unbonded_withdraw( @@ -239,21 +255,22 @@ impl EnclaveProxy for MockClient { info, account.expect("account exists in withdraw"), ); - let fakesealed = if result.is_ok() { - let txid = tx.id(); - let txwo = TxWithOutputs::StakeWithdraw(tx); - self.add_view_keys(&txwo); - self.local_tx_store.insert(txid, txwo.clone()); - Some(Box::new(txwo.encode())) - } else { - None - }; - EnclaveResponse::VerifyTx(result.map(|(x, y)| (x, y, fakesealed))) + match result { + Ok((fee, _account)) => { + let txwo = TxWithOutputs::StakeWithdraw(tx); + self.add_view_keys(&txwo); + + Ok(IntraEnclaveResponseOk::TxWithOutputs { + paid_fee: fee, + sealed_tx: txwo.encode(), + }) + } + Err(e) => Err(e), + } } - _ => EnclaveResponse::UnknownRequest, + _ => unreachable!(), } } - _ => EnclaveResponse::UnknownRequest, } } } diff --git a/chain-abci/src/enclave_bridge/mod.rs b/chain-abci/src/enclave_bridge/mod.rs index 3771f5e96..4680b2785 100644 --- a/chain-abci/src/enclave_bridge/mod.rs +++ b/chain-abci/src/enclave_bridge/mod.rs @@ -1,40 +1,15 @@ -use enclave_protocol::{EnclaveRequest, EnclaveResponse, FLAGS}; +use enclave_protocol::{IntraEnclaveRequest, IntraEnclaveResponse}; use parity_scale_codec::{Decode, Encode}; -use std::sync::{Arc, Mutex}; -use zmq::Socket; /// TODO: feature-guard when workspaces can be built with --features flag: https://github.com/rust-lang/cargo/issues/5015 pub mod mock; -/// Abstracts over communication with an external process that does enclave calls -pub trait EnclaveProxy: Sync + Send + Sized { - fn process_request(&mut self, request: EnclaveRequest) -> EnclaveResponse; -} - -/// Provides communication with the enclave wrapper app over ZMQ -/// NOTE / WARNING: this connection is trusted / non-attested -/// (it's assumed Tendermint node, Chain ABCI app and enclave process would run on the same machine) -pub struct ZmqEnclaveClient { - socket: Arc>, -} +#[cfg(all(not(feature = "mock-validation"), target_os = "linux"))] +pub mod real; -impl ZmqEnclaveClient { - pub fn new(socket: Socket) -> Self { - ZmqEnclaveClient { - socket: Arc::new(Mutex::new(socket)), - } - } -} - -impl EnclaveProxy for ZmqEnclaveClient { - fn process_request(&mut self, request: EnclaveRequest) -> EnclaveResponse { - let asocket = Arc::clone(&self.socket); - let socket = asocket.lock().unwrap(); - let req = request.encode(); - socket.send(req, FLAGS).expect("request sending failed"); - let msg = socket - .recv_bytes(FLAGS) - .expect("failed to receive a response"); - EnclaveResponse::decode(&mut msg.as_slice()).expect("failed to parse a response") - } +/// Abstracts over communication with an external part that does enclave calls +pub trait EnclaveProxy: Sync + Send + Sized { + // sanity check for checking enclave initialization + fn check_chain(&self, network_id: u8) -> Result<(), ()>; + fn process_request(&mut self, request: IntraEnclaveRequest) -> IntraEnclaveResponse; } diff --git a/chain-tx-enclave/tx-validation/app/src/enclave_u/mod.rs b/chain-abci/src/enclave_bridge/real/enclave_u.rs similarity index 55% rename from chain-tx-enclave/tx-validation/app/src/enclave_u/mod.rs rename to chain-abci/src/enclave_bridge/real/enclave_u.rs index 53adb96dd..bafe9112a 100644 --- a/chain-tx-enclave/tx-validation/app/src/enclave_u/mod.rs +++ b/chain-abci/src/enclave_bridge/real/enclave_u.rs @@ -1,16 +1,9 @@ use sgx_types::*; -use chain_core::common::H256; -use chain_core::state::account::DepositBondTx; -use chain_core::state::account::StakedState; -use chain_core::tx::TxEnclaveAux; use chain_core::tx::TxObfuscated; use chain_tx_validation::Error; -use enclave_protocol::{ - IntraEnclaveRequest, IntraEnclaveResponse, IntraEnclaveResponseOk, VerifyOk, -}; +use enclave_protocol::{IntraEnclaveRequest, IntraEnclaveResponse, IntraEnclaveResponseOk}; use parity_scale_codec::{Decode, Encode}; -use sled::Tree; use std::mem::size_of; extern "C" { @@ -31,24 +24,17 @@ extern "C" { } -pub fn check_initchain( - eid: sgx_enclave_id_t, - chain_hex_id: u8, - last_app_hash: Option, -) -> Result<(), Option> { +pub fn check_initchain(eid: sgx_enclave_id_t, chain_hex_id: u8) -> Result<(), ()> { let mut retval: sgx_status_t = sgx_status_t::SGX_SUCCESS; let result = unsafe { ecall_initchain(eid, &mut retval, chain_hex_id) }; if retval == sgx_status_t::SGX_SUCCESS && result == retval { Ok(()) } else { - Err(last_app_hash) + Err(()) } } -pub fn end_block( - eid: sgx_enclave_id_t, - request: IntraEnclaveRequest, -) -> Result>, ()> { +pub fn end_block(eid: sgx_enclave_id_t, request: IntraEnclaveRequest) -> IntraEnclaveResponse { let request_buf: Vec = request.encode(); // Buffer size: Result(1)+Result(1)+Enum(1)+Option(1)+Box(0)+TxFilter(256) let mut response_buf: Vec = vec![0u8; 260]; @@ -67,11 +53,14 @@ pub fn end_block( if retval == sgx_status_t::SGX_SUCCESS && result == retval { let response = IntraEnclaveResponse::decode(&mut response_buf.as_slice()); match response { - Ok(Ok(IntraEnclaveResponseOk::EndBlock(maybe_filter))) => Ok(maybe_filter), - _ => Err(()), + Ok(resp) => resp, + Err(e) => { + log::error!("endblock response failed: {:?}", e); + Err(Error::EnclaveRejected) + } } } else { - Err(()) + Err(Error::EnclaveRejected) } } @@ -120,11 +109,7 @@ pub fn encrypt_tx( } } -pub fn check_tx( - eid: sgx_enclave_id_t, - request: IntraEnclaveRequest, - txdb: &mut Tree, -) -> Result { +pub fn check_tx(eid: sgx_enclave_id_t, request: IntraEnclaveRequest) -> IntraEnclaveResponse { let request_buf: Vec = request.encode(); let response_len = size_of::() + request_buf.len(); let mut response_buf: Vec = vec![0u8; response_len]; @@ -142,64 +127,10 @@ pub fn check_tx( }; if retval == sgx_status_t::SGX_SUCCESS && result == retval { let response = IntraEnclaveResponse::decode(&mut response_buf.as_slice()); - match (request, response) { - ( - IntraEnclaveRequest::ValidateTx { request, .. }, - Ok(Ok(IntraEnclaveResponseOk::TxWithOutputs { - paid_fee, - sealed_tx, - })), - ) => { - let _ = txdb - .insert(&request.tx.tx_id(), sealed_tx.clone()) - .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), Some(Box::new(sealed_tx)))) - } else { - Ok((paid_fee, None, Some(Box::new(sealed_tx)))) - } - } - ( - IntraEnclaveRequest::ValidateTx { request, .. }, - Ok(Ok(IntraEnclaveResponseOk::DepositStakeTx { input_coins })), - ) => { - let deposit_amount = - (input_coins - request.info.min_fee_computed.to_coin()).expect("init"); - let account = match (request.account, request.tx) { - (Some(mut a), _) => { - a.deposit(deposit_amount); - Some(a) - } - ( - None, - TxEnclaveAux::DepositStakeTx { - tx: - DepositBondTx { - to_staked_account, .. - }, - .. - }, - ) => Some(StakedState::new_init_bonded( - deposit_amount, - request.info.previous_block_time, - to_staked_account, - None, - )), - (_, _) => unreachable!("one shouldn't call this with other variants"), - }; - let fee = request.info.min_fee_computed; - Ok((fee, account, None)) - } - (_, Ok(Err(e))) => { - log::error!("get error response: {:?}", e); - Err(e) - } - (_req, _resp) => { - log::error!("unsupported or error response"); + match response { + Ok(resp) => resp, + Err(e) => { + log::error!("check tx response failed: {:?}", e); Err(Error::EnclaveRejected) } } diff --git a/chain-abci/src/enclave_bridge/real/mod.rs b/chain-abci/src/enclave_bridge/real/mod.rs new file mode 100644 index 000000000..95fc05fbe --- /dev/null +++ b/chain-abci/src/enclave_bridge/real/mod.rs @@ -0,0 +1,94 @@ +mod enclave_u; +mod server; +#[cfg(feature = "sgx-test")] +pub mod test; + +use crate::enclave_bridge::real::enclave_u::{check_initchain, check_tx, end_block}; +use crate::enclave_bridge::EnclaveProxy; +use chain_storage::ReadOnlyStorage; +use enclave_protocol::{IntraEnclaveRequest, IntraEnclaveResponse}; +use enclave_u_common::enclave_u::init_enclave; +use log::info; +use server::TxValidationServer; +use sgx_urts::SgxEnclave; +use std::sync::mpsc::channel; +use std::thread; + +pub const TX_VALIDATION_ENCLAVE_FILE: &str = "tx_validation_enclave.signed.so"; + +pub struct TxValidationApp { + enclave: SgxEnclave, +} + +impl Default for TxValidationApp { + fn default() -> Self { + info!("Attempting to launch TX Validation Enclave in debug mode"); + let enclave = match init_enclave(TX_VALIDATION_ENCLAVE_FILE, true) { + Ok(r) => { + info!( + "[+] Init TX Validation Server Enclave Successful {}!", + r.geteid() + ); + r + } + Err(x) => { + panic!( + "[-] Init TX Validation Sercer Enclave Failed {}!", + x.as_str() + ); + } + }; + Self { enclave } + } +} + +impl EnclaveProxy for TxValidationApp { + fn check_chain(&self, network_id: u8) -> Result<(), ()> { + check_initchain(self.enclave.geteid(), network_id) + } + + fn process_request(&mut self, request: IntraEnclaveRequest) -> IntraEnclaveResponse { + let eid = self.enclave.geteid(); + match &request { + IntraEnclaveRequest::EndBlock => end_block(eid, request), + IntraEnclaveRequest::Encrypt(_) => { + unreachable!("should be used only in TxValidationServer") + } + IntraEnclaveRequest::ValidateTx { .. } => check_tx(eid, request), + } + } +} + +/// It launches a ZMQ server that can server tx-query requests; +/// (used to be in a separate process -- tx-validation-app that had a custom storage; +/// now it's in a thread of chain-abci and shares its storage) +pub fn start_zmq( + zmq_conn_str: &str, + network_id: u8, + storage: ReadOnlyStorage, +) -> thread::JoinHandle<()> { + info!("Attempting to launch TX Validation Enclave (for tx-query / zmq) in debug mode"); + let enclave = match init_enclave(TX_VALIDATION_ENCLAVE_FILE, true) { + Ok(r) => { + info!( + "[+] Init TX Validation Server Enclave (for tx-query / zmq) Successful {}!", + r.geteid() + ); + r + } + Err(x) => { + panic!( + "[-] Init TX Validation Server Enclave (for tx-query / zmq) Failed {}!", + x.as_str() + ); + } + }; + let (sender, receiver) = channel(); + let mut server: TxValidationServer = + TxValidationServer::new(zmq_conn_str, enclave, storage, network_id, sender) + .expect("could not start a zmq server"); + info!("starting zmq server"); + let child_t = thread::spawn(move || server.execute()); + receiver.recv().unwrap(); + child_t +} diff --git a/chain-abci/src/enclave_bridge/real/server.rs b/chain-abci/src/enclave_bridge/real/server.rs new file mode 100644 index 000000000..384f6c59c --- /dev/null +++ b/chain-abci/src/enclave_bridge/real/server.rs @@ -0,0 +1,125 @@ +use crate::app::ChainNodeState; +use crate::enclave_bridge::real::enclave_u::encrypt_tx; +use chain_core::tx::data::TxId; +use chain_storage::ReadOnlyStorage; +use chain_tx_validation::ChainInfo; +use enclave_protocol::IntraEnclaveRequest; +use enclave_protocol::{EnclaveRequest, EnclaveResponse, IntraEncryptRequest, FLAGS}; +use parity_scale_codec::{Decode, Encode}; +use sgx_urts::SgxEnclave; +use std::sync::mpsc::Sender; +use zmq::{Context, Error, Socket, REP}; + +pub struct TxValidationServer { + socket: Socket, + enclave: SgxEnclave, + storage: ReadOnlyStorage, + network_id: u8, + start_signal: Sender<()>, +} + +impl TxValidationServer { + pub fn new( + connection_str: &str, + enclave: SgxEnclave, + storage: ReadOnlyStorage, + network_id: u8, + start_signal: Sender<()>, + ) -> Result { + let ctx = Context::new(); + let socket = ctx.socket(REP)?; + socket.bind(connection_str)?; + + Ok(TxValidationServer { + socket, + enclave, + storage, + network_id, + start_signal, + }) + } + + fn lookup_txids(&self, inputs: I) -> Option>> + where + I: IntoIterator + ExactSizeIterator, + { + let mut result = Vec::with_capacity(inputs.len()); + for input in inputs.into_iter() { + if let Some(txin) = self.storage.get_sealed_log(&input) { + result.push(txin); + } else { + return None; + } + } + Some(result) + } + + pub fn execute(&mut self) { + log::info!("running zmq server"); + self.start_signal.send(()).unwrap(); + loop { + if let Ok(msg) = self.socket.recv_bytes(FLAGS) { + log::debug!("received a message"); + let mcmd = EnclaveRequest::decode(&mut msg.as_slice()); + let resp = match mcmd { + Ok(EnclaveRequest::GetSealedTxData { txids }) => { + EnclaveResponse::GetSealedTxData(self.lookup_txids(txids.iter().copied())) + } + Ok(EnclaveRequest::EncryptTx(req)) => { + let result = { + let tx_inputs = match req.tx_inputs { + Some(inputs) => self.lookup_txids(inputs.iter().map(|x| x.id)), + _ => None, + }; + match self.storage.get_last_app_state() { + Some(state) => { + let last_state = ChainNodeState::decode(&mut state.as_slice()) + .expect("deserialize app state"); + // TODO: fee in enclave? + // FIXME: staked state not in request, but looked up? + let min_fee = last_state + .top_level + .network_params + .get_min_const_fee() + .expect("invalid fee policy"); + let info = ChainInfo { + min_fee_computed: min_fee, + chain_hex_id: self.network_id, + previous_block_time: last_state.block_time, + unbonding_period: last_state + .top_level + .network_params + .get_unbonding_period(), + }; + let request = IntraEncryptRequest { + txid: req.txid, + sealed_enc_request: req.sealed_enc_request, + tx_inputs, + info, + }; + encrypt_tx( + self.enclave.geteid(), + IntraEnclaveRequest::Encrypt(Box::new(request)), + ) + } + None => { + log::error!("can not find last app state"); + Err(chain_tx_validation::Error::EnclaveRejected) + } + } + }; + EnclaveResponse::EncryptTx(result) + } + Err(e) => { + log::error!("unknown request / failed to decode: {}", e); + EnclaveResponse::UnknownRequest + } + }; + let response = resp.encode(); + self.socket + .send(response, FLAGS) + .expect("reply sending failed"); + } + } + } +} diff --git a/chain-abci/src/enclave_bridge/real/test/mod.rs b/chain-abci/src/enclave_bridge/real/test/mod.rs new file mode 100644 index 000000000..126331d39 --- /dev/null +++ b/chain-abci/src/enclave_bridge/real/test/mod.rs @@ -0,0 +1,3 @@ +mod seal; + +pub use seal::test_sealing; diff --git a/chain-tx-enclave/tx-validation/app/src/test/mod.rs b/chain-abci/src/enclave_bridge/real/test/seal.rs similarity index 78% rename from chain-tx-enclave/tx-validation/app/src/test/mod.rs rename to chain-abci/src/enclave_bridge/real/test/seal.rs index 0bd046700..2d97aca66 100644 --- a/chain-tx-enclave/tx-validation/app/src/test/mod.rs +++ b/chain-abci/src/enclave_bridge/real/test/seal.rs @@ -1,16 +1,14 @@ -use crate::enclave_u::{check_initchain, check_tx, end_block}; -use crate::TX_VALIDATION_ENCLAVE_FILE; +use crate::enclave_bridge::real::enclave_u::{check_initchain, check_tx, end_block}; +use crate::enclave_bridge::real::TX_VALIDATION_ENCLAVE_FILE; use chain_core::common::MerkleTree; use chain_core::init::address::RedeemAddress; use chain_core::init::coin::Coin; -use chain_core::state::account::StakedStateDestination; use chain_core::state::account::{ StakedState, StakedStateAddress, StakedStateOpWitness, WithdrawUnbondedTx, }; use chain_core::tx::fee::Fee; use chain_core::tx::witness::tree::RawPubkey; 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::{ @@ -27,7 +25,9 @@ use chain_core::tx::{ }; use chain_core::ChainInfo; use chain_tx_validation::Error; -use enclave_protocol::{EncryptionRequest, IntraEnclaveRequest, VerifyTxRequest}; +use enclave_protocol::{ + EncryptionRequest, IntraEnclaveRequest, IntraEnclaveResponseOk, VerifyTxRequest, +}; use enclave_u_common::enclave_u::init_enclave; use env_logger::{Builder, WriteStyle}; use log::LevelFilter; @@ -37,7 +37,6 @@ use secp256k1::{ key::PublicKey, key::SecretKey, schnorrsig::schnorr_sign, Message, Secp256k1, Signing, }; use sgx_types::{sgx_enclave_id_t, sgx_status_t}; -use sled::Db; extern "C" { fn ecall_test_encrypt( @@ -89,11 +88,6 @@ fn get_account(account_address: &RedeemAddress) -> StakedState { const TEST_NETWORK_ID: u8 = 0xab; -fn cleanup(db: &mut Db) { - db.drop_tree(crate::META_KEYSPACE).expect("test meta tx"); - db.drop_tree(crate::TX_KEYSPACE).expect("test cleanup tx"); -} - /// Unfortunately the usual Rust unit-test facility can't be used with Apache Teaclave SGX SDK, /// so this has to be run as a normal app pub fn test_sealing() { @@ -103,13 +97,6 @@ pub fn test_sealing() { .filter(None, LevelFilter::Debug) .write_style(WriteStyle::Always) .init(); - let mut db = sled::open(".enclave-test").expect("failed to open a storage path"); - let mut metadb = db - .open_tree(crate::META_KEYSPACE) - .expect("failed to open a meta keyspace"); - let mut txdb = db - .open_tree(crate::TX_KEYSPACE) - .expect("failed to open a tx keyspace"); let enclave = match init_enclave(TX_VALIDATION_ENCLAVE_FILE, true) { Ok(r) => { @@ -121,16 +108,15 @@ pub fn test_sealing() { return; } }; - assert!(check_initchain(enclave.geteid(), TEST_NETWORK_ID, None).is_ok()); + assert!(check_initchain(enclave.geteid(), TEST_NETWORK_ID).is_ok()); let end_b = end_block(enclave.geteid(), IntraEnclaveRequest::EndBlock); match end_b { - Ok(b) => { + Ok(IntraEnclaveResponseOk::EndBlock(b)) => { debug!("request filter in the beginning"); assert!(b.is_none(), "empty filter"); } _ => { - cleanup(&mut db); assert!(false, "filter not returned"); } }; @@ -169,16 +155,7 @@ pub fn test_sealing() { previous_block_time: 1, unbonding_period: 0, }; - let tb = txdb.get(&txid); - match tb { - Ok(None) => { - debug!("new tx not in DB yet"); - } - _ => { - cleanup(&mut db); - assert!(false, "new tx already in db"); - } - }; + let request0 = IntraEnclaveRequest::ValidateTx { request: Box::new(VerifyTxRequest { tx: withdrawtx, @@ -187,29 +164,21 @@ pub fn test_sealing() { }), tx_inputs: None, }; - let r = check_tx(enclave.geteid(), request0, &mut txdb); + let r = check_tx(enclave.geteid(), request0); assert!(r.is_ok()); - let ta = txdb.get(&txid); - let sealedtx = match ta { - Ok(Some(tx)) => { - debug!("new tx in DB!"); - tx.to_vec() - } - _ => { - cleanup(&mut db); - assert!(false, "new tx not in db"); - vec![] - } + + let sealedtx = match r { + Ok(IntraEnclaveResponseOk::TxWithOutputs { sealed_tx, .. }) => sealed_tx, + _ => vec![], }; let end_b = end_block(enclave.geteid(), IntraEnclaveRequest::EndBlock); match end_b { - Ok(b) => { + Ok(IntraEnclaveResponseOk::EndBlock(b)) => { debug!("request filter after one tx"); assert!(b.unwrap().iter().any(|x| *x != 0u8), "non-empty filter"); } _ => { - cleanup(&mut db); assert!(false, "filter not returned"); } }; @@ -237,16 +206,6 @@ pub fn test_sealing() { ), }; - let tc = txdb.get(&txid1); - match tc { - Ok(None) => { - debug!("new 2nd tx not in DB yet"); - } - _ => { - assert!(false, "new 2nd tx already in db"); - } - }; - let request1 = IntraEnclaveRequest::ValidateTx { request: Box::new(VerifyTxRequest { tx: transfertx, @@ -256,18 +215,8 @@ pub fn test_sealing() { tx_inputs: Some(vec![sealedtx.clone()]), }; - let r2 = check_tx(enclave.geteid(), request1, &mut txdb); + let r2 = check_tx(enclave.geteid(), request1); assert!(r2.is_ok()); - let td = txdb.get(&txid1); - match td { - Ok(Some(_tx)) => { - debug!("new 2nd tx in DB!"); - } - _ => { - cleanup(&mut db); - assert!(false, "new 2nd tx not in db"); - } - }; let mut tx2 = Tx::new(); tx2.attributes = TxAttributes::new(TEST_NETWORK_ID); @@ -298,19 +247,19 @@ pub fn test_sealing() { tx_inputs: Some(vec![sealedtx]), }; - let r3 = check_tx(enclave.geteid(), request2, &mut txdb); + let r3 = check_tx(enclave.geteid(), request2); match r3 { Err(Error::ZeroCoin) => { debug!("invalid transaction rejected and error code returned"); } - x => { - cleanup(&mut db); + Err(x) => { panic!( "something else happened (tx not correctly rejected): {:?}", x ); } + Ok(_) => { + panic!("something else happened (tx accepted)"); + } }; - - cleanup(&mut db); } diff --git a/chain-abci/src/main.rs b/chain-abci/src/main.rs index be9795092..8588028af 100644 --- a/chain-abci/src/main.rs +++ b/chain-abci/src/main.rs @@ -2,17 +2,16 @@ use log::info; use std::fs::File; use std::net::SocketAddr; use std::path::{Path, PathBuf}; -#[cfg(not(feature = "mock-validation"))] -use zmq::{Context, REQ}; use chain_abci::app::ChainNodeApp; -#[cfg(feature = "mock-validation")] +#[cfg(any(feature = "mock-validation", not(target_os = "linux")))] use chain_abci::enclave_bridge::mock::MockClient; -#[cfg(not(feature = "mock-validation"))] -use chain_abci::enclave_bridge::ZmqEnclaveClient; +#[cfg(all(not(feature = "mock-validation"), 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::{StorageConfig, StorageType}; -#[cfg(feature = "mock-validation")] +use chain_storage::account::AccountStorage; +use chain_storage::{Storage, StorageConfig, StorageType}; +#[cfg(any(feature = "mock-validation", not(target_os = "linux")))] use log::warn; use serde::Deserialize; use std::io::BufReader; @@ -120,7 +119,7 @@ pub struct AbciOpt { #[structopt( short = "e", long = "enclave_server", - help = "Connection string (e.g. ipc://enclave.socket or tcp://127.0.0.1:25933) for ZeroMQ server wrapper around the transaction validation enclave." + help = "Connection string (e.g. ipc://enclave.socket or tcp://127.0.0.1:25933) on which ZeroMQ server wrapper around the transaction validation enclave will listen." )] enclave_server: Option, #[structopt( @@ -132,24 +131,25 @@ pub struct AbciOpt { } /// normal -#[cfg(not(feature = "mock-validation"))] -fn get_enclave_proxy(config: &Config) -> ZmqEnclaveClient { - let ctx = Context::new(); - let socket = ctx.socket(REQ).expect("failed to init zmq context"); - let endpoint = config.enclave_server.as_ref().unwrap().as_str(); - socket - .connect(endpoint) - .expect("failed to connect to enclave zmq wrapper"); - ZmqEnclaveClient::new(socket) +#[cfg(all(not(feature = "mock-validation"), target_os = "linux"))] +fn get_enclave_proxy() -> TxValidationApp { + TxValidationApp::default() } /// for development -#[cfg(feature = "mock-validation")] -fn get_enclave_proxy(_config: &Config) -> MockClient { +#[cfg(any(feature = "mock-validation", not(target_os = "linux")))] +fn get_enclave_proxy() -> MockClient { warn!("Using mock (non-enclave) infrastructure"); MockClient::new(get_network_id()) } +#[cfg(feature = "sgx-test")] +fn main() { + // Teaclave SGX SDK doesn't work with Rust unit testing facility + chain_abci::enclave_bridge::real::test::test_sealing(); +} + +#[cfg(not(feature = "sgx-test"))] fn main() { env_logger::init(); let opt = AbciOpt::from_args(); @@ -173,20 +173,26 @@ fn main() { get_network(), get_network_id() ); - let proxy = get_enclave_proxy(&config); + let tx_validator = get_enclave_proxy(); let host = config.host.parse().expect("invalid host"); let addr = SocketAddr::new(host, config.port); + let storage = Storage::new(&StorageConfig::new(&opt.data, StorageType::Node)); info!("starting up"); abci::run( addr, - ChainNodeApp::new( - proxy, + ChainNodeApp::new_with_storage( + tx_validator, &config.genesis_app_hash.unwrap(), &config.chain_id.unwrap(), - &StorageConfig::new(&opt.data, StorageType::Node), - &StorageConfig::new(&opt.data, StorageType::AccountTrie), + storage, + AccountStorage::new( + Storage::new(&StorageConfig::new(&opt.data, StorageType::AccountTrie)), + 20, + ) + .expect("account db"), config.tx_query, + config.enclave_server, ), ); } diff --git a/chain-abci/src/storage/mod.rs b/chain-abci/src/storage/mod.rs index 513c93adb..8927a0a11 100644 --- a/chain-abci/src/storage/mod.rs +++ b/chain-abci/src/storage/mod.rs @@ -14,7 +14,7 @@ use chain_tx_validation::{ verify_node_join, verify_unbonding, verify_unjailed, verify_unjailing, witness::verify_tx_recover_address, ChainInfo, Error, NodeChecker, }; -use enclave_protocol::{EnclaveRequest, EnclaveResponse, VerifyOk}; +use enclave_protocol::{IntraEnclaveRequest, IntraEnclaveResponseOk, SealedLog, VerifyOk}; /// checks that the account can be retrieved from the trie storage pub fn get_account( @@ -29,11 +29,15 @@ pub fn get_account( } } -fn check_spent_input_lookup(inputs: &[TxoPointer], storage: &Storage) -> Result<(), Error> { +fn check_spent_input_lookup( + inputs: &[TxoPointer], + storage: &Storage, +) -> Result, Error> { // check that there are inputs if inputs.is_empty() { return Err(Error::NoInputs); } + let mut result = Vec::with_capacity(inputs.len()); for txin in inputs.iter() { let txo = storage.lookup_input(txin); match txo { @@ -49,10 +53,16 @@ fn check_spent_input_lookup(inputs: &[TxoPointer], storage: &Storage) -> Result< Ok(InputStatus::Spent) => { return Err(Error::InputSpent); } - Ok(InputStatus::Unspent) => {} + Ok(InputStatus::Unspent) => { + result.push( + storage + .get_sealed_log(&txin.id) + .expect("valid unspent tx output should be stored"), + ); + } } } - Ok(()) + Ok(result) } /// Checks TX against the current DB, passes to the enclave and returns an `Error` if something fails. @@ -66,27 +76,21 @@ pub fn verify_enclave_tx( accounts: &AccountStorage, ) -> Result { match txaux { - TxEnclaveAux::TransferTx { - inputs, - no_of_outputs, - payload, - } => { - check_spent_input_lookup(&inputs, storage)?; - let response = tx_validator.process_request(EnclaveRequest::new_tx_request( - TxEnclaveAux::TransferTx { - inputs: inputs.clone(), - no_of_outputs: *no_of_outputs, - payload: payload.clone(), - }, - None, - extra_info, - )); + TxEnclaveAux::TransferTx { inputs, .. } => { + let tx_inputs = check_spent_input_lookup(&inputs, storage)?; + let response = tx_validator.process_request( + IntraEnclaveRequest::new_validate_transfer(txaux.clone(), extra_info, tx_inputs), + ); match response { - EnclaveResponse::VerifyTx(r) => r, - _ => Err(Error::EnclaveRejected), + Ok(IntraEnclaveResponseOk::TxWithOutputs { + paid_fee, + sealed_tx, + }) => Ok((paid_fee, None, Some(Box::new(sealed_tx)))), + Err(e) => Err(e), + _ => unreachable!("unexpected enclave response"), } } - TxEnclaveAux::DepositStakeTx { tx, payload } => { + TxEnclaveAux::DepositStakeTx { tx, .. } => { let maccount = get_account(&tx.to_staked_account, last_account_root_hash, accounts); let account = match maccount { Ok(a) => Some(a), @@ -99,55 +103,65 @@ pub fn verify_enclave_tx( verify_unjailed(account)?; } - check_spent_input_lookup(&tx.inputs, storage)?; + let tx_inputs = check_spent_input_lookup(&tx.inputs, storage)?; - let response = tx_validator.process_request(EnclaveRequest::new_tx_request( - TxEnclaveAux::DepositStakeTx { - tx: tx.clone(), - payload: payload.clone(), - }, - account, + let response = tx_validator.process_request(IntraEnclaveRequest::new_validate_deposit( + txaux.clone(), extra_info, + account.clone(), + tx_inputs, )); match response { - EnclaveResponse::VerifyTx(r) => r, - _ => Err(Error::EnclaveRejected), + Ok(IntraEnclaveResponseOk::DepositStakeTx { input_coins }) => { + let deposit_amount = (input_coins - extra_info.min_fee_computed.to_coin()) + .expect("diff with min fee in coins"); + let account = match account { + Some(mut a) => { + a.deposit(deposit_amount); + Some(a) + } + None => Some(StakedState::new_init_bonded( + deposit_amount, + extra_info.previous_block_time, + tx.to_staked_account, + None, + )), + }; + let fee = extra_info.min_fee_computed; + Ok((fee, account, None)) + } + Err(e) => Err(e), + _ => unreachable!("unexpected enclave response"), } } TxEnclaveAux::WithdrawUnbondedStakeTx { - payload: - TxObfuscated { - key_from, - init_vector, - txpayload, - txid, - }, + payload: TxObfuscated { txid, .. }, witness, - no_of_outputs, + .. } => { let account_address = verify_tx_recover_address(&witness, &txid); if let Err(_e) = account_address { return Err(Error::EcdsaCrypto); // FIXME: Err(Error::EcdsaCrypto(e)); } - let account = get_account(&account_address.unwrap(), last_account_root_hash, accounts)?; + let mut account = + get_account(&account_address.unwrap(), last_account_root_hash, accounts)?; verify_unjailed(&account)?; - let response = tx_validator.process_request(EnclaveRequest::new_tx_request( - TxEnclaveAux::WithdrawUnbondedStakeTx { - payload: TxObfuscated { - key_from: *key_from, - init_vector: *init_vector, - txpayload: txpayload.clone(), - txid: *txid, - }, - witness: witness.clone(), - no_of_outputs: *no_of_outputs, - }, - Some(account), - extra_info, - )); + let response = + tx_validator.process_request(IntraEnclaveRequest::new_validate_withdraw( + txaux.clone(), + extra_info, + account.clone(), + )); match response { - EnclaveResponse::VerifyTx(r) => r, - _ => Err(Error::EnclaveRejected), + Ok(IntraEnclaveResponseOk::TxWithOutputs { + paid_fee, + sealed_tx, + }) => { + account.withdraw(); + Ok((paid_fee, Some(account), Some(Box::new(sealed_tx)))) + } + Err(e) => Err(e), + _ => unreachable!("unexpected enclave response"), } } } diff --git a/chain-abci/tests/abci_app.rs b/chain-abci/tests/abci_app.rs index 7374f8ac3..52c4f1d35 100644 --- a/chain-abci/tests/abci_app.rs +++ b/chain-abci/tests/abci_app.rs @@ -47,7 +47,6 @@ use chain_storage::{ LookupItem, Storage, COL_NODE_INFO, GENESIS_APP_HASH_KEY, LAST_STATE_KEY, NUM_COLUMNS, }; use chain_tx_filter::BlockFilter; -use chain_tx_validation::TxWithOutputs; use hex::decode; use kvdb::KeyValueDB; use kvdb_memorydb::create; @@ -59,6 +58,7 @@ use std::convert::TryFrom; use std::convert::TryInto; use std::str::FromStr; use std::sync::Arc; +use test_common::chain_env::ChainEnv; pub fn get_enclave_bridge_mock() -> MockClient { MockClient::new(0) @@ -94,6 +94,7 @@ fn proper_hash_and_chainid_should_be_stored() { Storage::new_db(db.clone()), create_account_db(), None, + None, ); let decoded_gah = decode(example_hash).unwrap(); let stored_genesis = app.storage.get_genesis_app_hash(); @@ -114,6 +115,7 @@ fn too_long_hash_should_panic() { Storage::new_db(db.clone()), create_account_db(), None, + None, ); } @@ -129,6 +131,7 @@ fn chain_id_without_hex_digits_should_panic() { Storage::new_db(db.clone()), create_account_db(), None, + None, ); } @@ -144,6 +147,7 @@ fn nonhex_hash_should_panic() { Storage::new_db(db.clone()), create_account_db(), None, + None, ); } @@ -213,6 +217,7 @@ fn previously_stored_hash_should_match() { Storage::new_db(db.clone()), create_account_db(), None, + None, ); } @@ -284,6 +289,7 @@ fn init_chain_for(address: RedeemAddress) -> ChainNodeApp { Storage::new_db(db.clone()), create_account_db(), None, + None, ); let mut req = RequestInitChain::default(); req.set_time(t); @@ -366,6 +372,7 @@ fn init_chain_panics_with_different_app_hash() { Storage::new_db(db.clone()), create_account_db(), None, + None, ); let mut req = RequestInitChain::default(); req.set_app_state_bytes(serde_json::to_vec(&c).unwrap()); @@ -386,6 +393,7 @@ fn init_chain_panics_with_empty_app_bytes() { Storage::new_db(db.clone()), create_account_db(), None, + None, ); let req = RequestInitChain::default(); app.init_chain(&req); @@ -660,7 +668,7 @@ fn valid_commit_should_persist() { assert!(app .storage - .lookup_item(LookupItem::TxBody, &tx.id()) + .lookup_item(LookupItem::TxSealed, &tx.id()) .is_none()); assert!(app .storage @@ -674,7 +682,7 @@ fn valid_commit_should_persist() { assert_eq!(0, app.delivered_txs.len()); assert!(app .storage - .lookup_item(LookupItem::TxBody, &tx.id()) + .lookup_item(LookupItem::TxSealed, &tx.id()) .is_some()); assert!(app .storage @@ -738,60 +746,6 @@ fn query_should_return_an_account() { assert!(account.is_ok()); } -#[test] -fn query_should_return_proof_for_committed_tx() { - let (mut app, tx, witness, _) = deliver_valid_tx(); - let mut endreq = RequestEndBlock::default(); - endreq.set_height(10); - app.end_block(&endreq); - let cresp = app.commit(&RequestCommit::default()); - let mut qreq = RequestQuery::new(); - qreq.data = tx.id().to_vec(); - qreq.path = "store".into(); - qreq.prove = true; - let qresp = app.query(&qreq); - let returned_tx = TxWithOutputs::decode(&mut qresp.value.as_slice()).unwrap(); - match returned_tx { - TxWithOutputs::StakeWithdraw(stx) => { - assert_eq!(tx, stx); - } - _ => panic!("expected stake withdrawal to be returned to a query"), - } - - let proof = qresp.proof.unwrap(); - - assert_eq!(proof.ops.len(), 2); - - let mut transaction_root_hash = [0u8; 32]; - transaction_root_hash.copy_from_slice(proof.ops[0].key.as_slice()); - - let mut transaction_proof_data = proof.ops[0].data.as_slice(); - let transaction_proof = >::decode(&mut transaction_proof_data).unwrap(); - - assert!(transaction_proof.verify(&transaction_root_hash)); - - let rewards_pool_part = app - .last_state - .clone() - .unwrap() - .top_level - .rewards_pool - .hash(); - let mut bs = Vec::new(); - bs.extend(transaction_root_hash.to_vec()); - bs.extend(&app.last_state.clone().unwrap().top_level.account_root); - bs.extend(&rewards_pool_part); - bs.extend(&get_dummy_network_params().hash()); - - assert_eq!(txid_hash(&bs).to_vec(), cresp.data); - let mut qreq2 = RequestQuery::new(); - qreq2.data = tx.id().to_vec(); - qreq2.path = "witness".into(); - let qresp = app.query(&qreq2); - assert_eq!(qresp.value, witness.encode()); - assert_eq!(proof.ops[1].data, txid_hash(&qresp.value)); -} - fn block_commit(app: &mut ChainNodeApp, tx: TxAux, block_height: i64) { let mut creq = RequestCheckTx::default(); creq.set_tx(tx.encode()); @@ -1013,3 +967,76 @@ fn all_valid_tx_types_should_commit() { assert_eq!(account.nonce, 4); } } + +#[test] +fn query_should_return_proof_for_committed_tx() { + let (env, storage, account_storage) = + ChainEnv::new_with_customizer(Coin::max(), Coin::zero(), 2, |parameters| { + parameters.required_council_node_stake = (Coin::max() / 10).unwrap(); + }); + let mut app = env.chain_node(storage, account_storage); + let _rsp = app.init_chain(&env.req_init_chain()); + + app.begin_block(&env.req_begin_block(1, 0)); + + let tx_aux = env.unbond_tx((Coin::max() / 10).unwrap(), 0, 0); + let rsp_tx = app.deliver_tx(&RequestDeliverTx { + tx: tx_aux.encode(), + ..Default::default() + }); + + assert_eq!(0, rsp_tx.code); + + let _response_end_block = app.end_block(&RequestEndBlock { + height: 1, + ..Default::default() + }); + let cresp = app.commit(&RequestCommit::default()); + let mut qreq = RequestQuery::new(); + qreq.data = tx_aux.tx_id().to_vec(); + qreq.path = "store".into(); + qreq.prove = true; + let qresp = app.query(&qreq); + let returned_tx = UnbondTx::decode(&mut qresp.value.as_slice()).unwrap(); + match &tx_aux { + TxAux::UnbondStakeTx(stx, _) => { + assert_eq!(returned_tx, stx.clone()); + } + _ => unreachable!(), + } + + let proof = qresp.proof.unwrap(); + let merkle = MerkleTree::new(vec![tx_aux.tx_id()]); + assert_eq!(proof.ops.len(), 2); + + let mut transaction_root_hash = [0u8; 32]; + transaction_root_hash.copy_from_slice(proof.ops[0].key.as_slice()); + + let mut transaction_proof_data = proof.ops[0].data.as_slice(); + let transaction_proof = >::decode(&mut transaction_proof_data).unwrap(); + + assert!(transaction_proof.verify(&transaction_root_hash)); + assert_eq!(merkle.root_hash(), transaction_root_hash); + let last_state = app.last_state.clone().unwrap(); + assert_eq!( + compute_app_hash( + &merkle, + &last_state.top_level.account_root, + &last_state.top_level.rewards_pool, + &last_state.top_level.network_params + ) + .to_vec(), + cresp.data + ); + let mut qreq2 = RequestQuery::new(); + qreq2.data = tx_aux.tx_id().to_vec(); + qreq2.path = "witness".into(); + let qresp = app.query(&qreq2); + match &tx_aux { + TxAux::UnbondStakeTx(_, witness) => { + assert_eq!(qresp.value, witness.encode()); + } + _ => unreachable!(), + } + assert_eq!(proof.ops[1].data, txid_hash(&qresp.value)); +} diff --git a/chain-abci/tests/tx_validation.rs b/chain-abci/tests/tx_validation.rs index 6bb7b5e6d..af11b307e 100644 --- a/chain-abci/tests/tx_validation.rs +++ b/chain-abci/tests/tx_validation.rs @@ -35,7 +35,7 @@ use chain_core::tx::{TxAux, TxEnclaveAux}; use chain_storage::account::AccountStorage; use chain_storage::account::AccountWrapper; use chain_storage::account::StarlingFixedKey; -use chain_storage::{Storage, COL_TX_META, NUM_COLUMNS}; +use chain_storage::{Storage, COL_ENCLAVE_TX, COL_TX_META, NUM_COLUMNS}; use chain_tx_validation::{ verify_bonded_deposit, verify_transfer, verify_unbonded_withdraw, ChainInfo, Error, NodeChecker, TxWithOutputs, @@ -170,7 +170,6 @@ impl NodeChecker for NodeInfoWrap { fn prepate_init_tx( timelocked: bool, - mock_client: &mut MockClient, ) -> ( Arc, TxoPointer, @@ -189,9 +188,12 @@ fn prepate_init_tx( let old_tx_id = old_tx.id(); let mut inittx = db.transaction(); - mock_client - .local_tx_store - .insert(old_tx_id, TxWithOutputs::Transfer(old_tx)); + // FIXME: https://github.com/crypto-com/chain/issues/885 + inittx.put( + COL_ENCLAVE_TX, + &old_tx_id[..], + &TxWithOutputs::Transfer(old_tx).encode(), + ); inittx.put( COL_TX_META, @@ -205,7 +207,6 @@ fn prepate_init_tx( fn prepare_app_valid_transfer_tx( timelocked: bool, - mock_client: &mut MockClient, ) -> ( Arc, TxEnclaveAux, @@ -215,7 +216,7 @@ fn prepare_app_valid_transfer_tx( SecretKey, AccountStorage, ) { - let (db, txp, addr, merkle_tree, secret_key) = prepate_init_tx(timelocked, mock_client); + let (db, txp, addr, merkle_tree, secret_key) = prepate_init_tx(timelocked); let secp = Secp256k1::new(); let mut tx = Tx::new(); tx.add_input(txp); @@ -645,7 +646,6 @@ fn test_account_withdraw_verify_fail() { fn prepare_app_valid_deposit_tx( timelocked: bool, - mock_client: &mut MockClient, ) -> ( Arc, TxEnclaveAux, @@ -654,7 +654,7 @@ fn prepare_app_valid_deposit_tx( SecretKey, AccountStorage, ) { - let (db, txp, _, merkle_tree, secret_key) = prepate_init_tx(timelocked, mock_client); + let (db, txp, _, merkle_tree, secret_key) = prepate_init_tx(timelocked); let secp = Secp256k1::new(); let sk2 = SecretKey::from_slice(&[0x11; 32]).expect("32 bytes, within curve order"); let pk2 = PublicKey::from_secret_key(&secp, &sk2); @@ -690,7 +690,7 @@ const DEFAULT_CHAIN_ID: u8 = 0; #[test] fn existing_utxo_input_tx_should_verify() { let mut mock_bridge = get_enclave_bridge_mock(); - let (db, txaux, _, _, _, _, accounts) = prepare_app_valid_transfer_tx(false, &mut mock_bridge); + let (db, txaux, _, _, _, _, accounts) = prepare_app_valid_transfer_tx(false); let storage = Storage::new_db(db); let extra_info = get_chain_info_enc(&txaux); let last_account_root_hash = [0u8; 32]; @@ -703,7 +703,7 @@ fn existing_utxo_input_tx_should_verify() { &accounts, ); assert!(result.is_ok()); - let (db, txaux, _, _, _, accounts) = prepare_app_valid_deposit_tx(false, &mut mock_bridge); + let (db, txaux, _, _, _, accounts) = prepare_app_valid_deposit_tx(false); let storage = Storage::new_db(db); let result = verify_enclave_tx( &mut mock_bridge, @@ -730,8 +730,7 @@ where #[test] fn test_deposit_verify_fail() { let mut mock_bridge = get_enclave_bridge_mock(); - let (db, txaux, tx, witness, secret_key, accounts) = - prepare_app_valid_deposit_tx(false, &mut mock_bridge); + let (db, txaux, tx, witness, secret_key, accounts) = prepare_app_valid_deposit_tx(false); let storage = Storage::new_db(db.clone()); let extra_info = get_chain_info_enc(&txaux); let last_account_root_hash = [0u8; 32]; @@ -1021,7 +1020,7 @@ fn replace_tx_payload( fn test_transfer_verify_fail() { let mut mock_bridge = get_enclave_bridge_mock(); let (db, txaux, tx, witness, merkle_tree, secret_key, accounts) = - prepare_app_valid_transfer_tx(false, &mut mock_bridge); + prepare_app_valid_transfer_tx(false); let storage = Storage::new_db(db.clone()); let extra_info = get_chain_info_enc(&txaux); let last_account_root_hash = [0u8; 32]; @@ -1312,8 +1311,7 @@ fn test_transfer_verify_fail() { } // OutputInTimelock { - let (db, txaux, tx, witness, _, _, accounts) = - prepare_app_valid_transfer_tx(true, &mut mock_bridge); + let (db, txaux, tx, witness, _, _, accounts) = prepare_app_valid_transfer_tx(true); let storage = Storage::new_db(db); let addr = get_address(&Secp256k1::new(), &secret_key).0; let input_tx = get_old_tx(addr, true); diff --git a/chain-core/src/init/params.rs b/chain-core/src/init/params.rs index d6e558d1a..4e3603a32 100644 --- a/chain-core/src/init/params.rs +++ b/chain-core/src/init/params.rs @@ -137,6 +137,16 @@ impl NetworkParameters { } } + // TODO: will it be necessary? + pub fn get_min_const_fee(&self) -> Result { + match self { + NetworkParameters::Genesis(params) => { + let coin = Coin::new(params.initial_fee_policy.coefficient.to_integral())?; + Ok(Fee::new(coin)) + } + } + } + pub fn calculate_fee(&self, num_bytes: usize) -> Result { match self { NetworkParameters::Genesis(params) => { diff --git a/chain-storage/src/lib.rs b/chain-storage/src/lib.rs index 1029a14e2..ce27c66b7 100644 --- a/chain-storage/src/lib.rs +++ b/chain-storage/src/lib.rs @@ -27,8 +27,10 @@ pub const COL_MERKLE_PROOFS: u32 = 5; pub const COL_APP_HASHS: u32 = 6; /// Column for tracking app states: height => ChainNodeState, 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; /// Number of columns in DB -pub const NUM_COLUMNS: u32 = 8; +pub const NUM_COLUMNS: u32 = 9; pub const CHAIN_ID_KEY: &[u8] = b"chain_id"; pub const GENESIS_APP_HASH_KEY: &[u8] = b"genesis_app_hash"; @@ -78,6 +80,26 @@ pub struct Storage { temp_sealed_tx_store: BTreeMap>, } +pub struct ReadOnlyStorage { + db: Arc, +} + +impl ReadOnlyStorage { + pub fn get_last_app_state(&self) -> Option> { + self.db + .get(COL_NODE_INFO, LAST_STATE_KEY) + .expect("app state lookup") + .map(|x| x.to_vec()) + } + + pub fn get_sealed_log(&self, txid: &TxId) -> Option> { + self.db + .get(COL_ENCLAVE_TX, txid) + .expect("IO fail") + .map(|x| x.to_vec()) + } +} + pub trait StoredChainState { /// get the whole state encoded fn get_encoded(&self) -> Vec; @@ -92,15 +114,23 @@ pub enum LookupItem { TxWitness, TxMetaSpent, TxsMerkle, + TxSealed, } impl Storage { + pub fn get_read_only(&self) -> ReadOnlyStorage { + ReadOnlyStorage { + db: self.db.clone(), + } + } + pub fn lookup_item(&self, item_type: LookupItem, txid_or_app_hash: &H256) -> Option> { let col = match item_type { LookupItem::TxBody => COL_BODIES, LookupItem::TxWitness => COL_WITNESS, LookupItem::TxMetaSpent => COL_TX_META, LookupItem::TxsMerkle => COL_MERKLE_PROOFS, + LookupItem::TxSealed => COL_ENCLAVE_TX, }; self.db .get(col, txid_or_app_hash) @@ -246,8 +276,7 @@ impl Storage { pub fn persist_write(&mut self) -> std::io::Result<()> { if let Some(mut dbtx) = self.current_tx.take() { for (txid, sealed_log) in self.temp_sealed_tx_store.iter() { - // FIXME: same or separate column? - dbtx.put(COL_BODIES, txid, sealed_log); + dbtx.put(COL_ENCLAVE_TX, txid, sealed_log); } self.temp_sealed_tx_store.clear(); self.db.write(dbtx) diff --git a/chain-storage/src/tx.rs b/chain-storage/src/tx.rs index 5c8ab492e..064480334 100644 --- a/chain-storage/src/tx.rs +++ b/chain-storage/src/tx.rs @@ -52,7 +52,7 @@ impl Storage { pub fn get_sealed_log(&self, txid: &TxId) -> Option> { // FIXME match self.temp_sealed_tx_store.get(txid) { - None => self.lookup_item(LookupItem::TxBody, txid), + None => self.lookup_item(LookupItem::TxSealed, txid), Some(x) => Some(x.clone()), } } diff --git a/chain-tx-enclave/enclave-u-common/src/lib.rs b/chain-tx-enclave/enclave-u-common/src/lib.rs index 7c7c53250..3d588bbf5 100644 --- a/chain-tx-enclave/enclave-u-common/src/lib.rs +++ b/chain-tx-enclave/enclave-u-common/src/lib.rs @@ -1,11 +1 @@ pub mod enclave_u; - -pub fn storage_path() -> String { - match std::env::var("TX_ENCLAVE_STORAGE") { - Ok(path) => path, - Err(_) => ".enclave".to_owned(), - } -} - -pub const META_KEYSPACE: &[u8] = b"meta"; -pub const TX_KEYSPACE: &[u8] = b"tx"; diff --git a/chain-tx-enclave/tx-query/app/Cargo.toml b/chain-tx-enclave/tx-query/app/Cargo.toml index 587e9074a..333fe6bcf 100644 --- a/chain-tx-enclave/tx-query/app/Cargo.toml +++ b/chain-tx-enclave/tx-query/app/Cargo.toml @@ -8,7 +8,6 @@ edition = "2018" [features] default = [] -sgx-test = ["client-core", "client-common"] [dependencies] log = "0.4.8" @@ -21,8 +20,6 @@ chain-core = { path = "../../../chain-core" } enclave-protocol = { path = "../../../enclave-protocol" } secp256k1zkp = { git = "https://github.com/crypto-com/rust-secp256k1-zkp.git", rev = "0125097a7bf6f939db0ce52e49803c5e0312bf5e", features = ["recovery", "endomorphism"] } zmq = "0.9" -client-core = { path = "../../../client-core", optional = true } -client-common = { path = "../../../client-common", optional = true } [build-dependencies] cc = "1.0" diff --git a/chain-tx-enclave/tx-query/app/src/main.rs b/chain-tx-enclave/tx-query/app/src/main.rs index 4a17a4cd3..89eb28867 100644 --- a/chain-tx-enclave/tx-query/app/src/main.rs +++ b/chain-tx-enclave/tx-query/app/src/main.rs @@ -1,8 +1,5 @@ mod enclave_u; -#[cfg(feature = "sgx-test")] -mod test; - use crate::enclave_u::init_connection; use enclave_u::run_server; use enclave_u_common::enclave_u::init_enclave; diff --git a/chain-tx-enclave/tx-query/app/src/test/mod.rs b/chain-tx-enclave/tx-query/app/src/test/mod.rs deleted file mode 100644 index 10201d99b..000000000 --- a/chain-tx-enclave/tx-query/app/src/test/mod.rs +++ /dev/null @@ -1,270 +0,0 @@ -use crate::enclave_u::init_connection; -use crate::enclave_u::run_server; -use crate::enclave_u::ZMQ_SOCKET; -use crate::start_enclave; -use crate::TIMEOUT_SEC; -use chain_core::common::MerkleTree; -use chain_core::init::address::RedeemAddress; -use chain_core::init::coin::Coin; -use chain_core::state::account::{ - StakedState, StakedStateAddress, StakedStateDestination, StakedStateOpWitness, - WithdrawUnbondedTx, -}; -use chain_core::tx::fee::Fee; -use chain_core::tx::witness::tree::RawPubkey; -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}, - address::ExtendedAddr, - attribute::TxAttributes, - input::{TxoIndex, TxoPointer}, - output::TxOut, - Tx, TxId, - }, - witness::TxInWitness, - TxAux, TxEnclaveAux, -}; -use chain_core::ChainInfo; -use client_common::{PrivateKey, SignedTransaction}; -use client_core::cipher::DefaultTransactionObfuscation; -use client_core::cipher::TransactionObfuscation; -use enclave_protocol::FLAGS; -use enclave_protocol::{EnclaveRequest, EnclaveResponse}; -use enclave_u_common::enclave_u::init_enclave; -use env_logger::{Builder, WriteStyle}; -use log::LevelFilter; -use log::{debug, error, info, warn}; -use parity_scale_codec::{Decode, Encode}; -use secp256k1::{ - key::PublicKey, key::SecretKey, schnorrsig::schnorr_sign, Message, Secp256k1, Signing, -}; -use sgx_types::sgx_status_t; -use std::net::TcpListener; -use std::os::unix::io::AsRawFd; -use std::path::Path; -use std::process::Child; -use std::process::Command; -use std::sync::mpsc::channel; -use std::thread; -use std::time; - -pub fn get_ecdsa_witness( - secp: &Secp256k1, - txid: &TxId, - secret_key: &SecretKey, -) -> EcdsaSignature { - let message = Message::from_slice(&txid[..]).expect("32 bytes"); - let sig = secp.sign_recoverable(&message, &secret_key); - return sig; -} - -fn get_account(account_address: &RedeemAddress) -> StakedState { - StakedState::new_init_unbonded(Coin::one(), 0, StakedStateAddress::from(*account_address)) -} - -const TEST_NETWORK_ID: u8 = 0xab; - -pub fn fail_exit(validation: &mut Child) { - validation.kill(); - std::process::exit(1); -} - -pub fn test_integration() { - let mut builder = Builder::new(); - let validation_path = - std::env::var("TX_VALIDATION_BIN_DIR").unwrap_or("/root/sgx/tx-validation/bin/".to_owned()); - let query_server_host = std::env::var("TX_QUERY_APP_HOST").unwrap_or("0.0.0.0".to_owned()); - let query_server_port = std::env::var("TX_QUERY_APP_PORT").unwrap_or("3443".to_owned()); - let query_server_addr = format!("{}:{}", query_server_host, query_server_port); - let validation_dir = Path::new(&validation_path); - let connection_socket = format! {"ipc://{}integration.enclave", validation_path}; - builder - .filter(None, LevelFilter::Info) - .write_style(WriteStyle::Always) - .init(); - let mut validation = Command::new("./tx-validation-app") - .current_dir(validation_dir) - .env("TX_ENCLAVE_STORAGE", ".enclave-integration") - .env("RUST_LOG", "debug") - .args(&[&connection_socket]) - .spawn() - .expect("failed to start tx validation"); - init_connection(&connection_socket); - let (tx, rx) = channel(); - let t = thread::spawn(move || { - info!("Trying to launch TX Query server..."); - let enclave = start_enclave(); - info!("Running TX Query server..."); - - let listener = TcpListener::bind(query_server_addr).expect("failed to bind the TCP socket"); - - for _ in 0..3 { - tx.send(()).expect("Could not send signal on channel."); - match listener.accept() { - Ok((stream, addr)) => { - info!("new client: {:?}", addr); - let mut retval = sgx_status_t::SGX_SUCCESS; - let result = unsafe { - run_server(enclave.geteid(), &mut retval, stream.as_raw_fd(), -1) - }; - match result { - sgx_status_t::SGX_SUCCESS => { - info!("client query finished"); - } - e => { - error!("client query failed: {}", e); - } - } - } - Err(e) => info!("couldn't get client: {:?}", e), - } - } - }); - - let secp = Secp256k1::new(); - let secret_key = SecretKey::from_slice(&[0xcd; 32]).expect("32 bytes, within curve order"); - let public_key = PublicKey::from_secret_key(&secp, &secret_key); - let addr = RedeemAddress::from(&public_key); - - let merkle_tree = MerkleTree::new(vec![RawPubkey::from(public_key.serialize())]); - - let eaddr = ExtendedAddr::OrTree(merkle_tree.root_hash()); - let tx0 = WithdrawUnbondedTx::new( - 0, - vec![TxOut::new_with_timelock(eaddr.clone(), Coin::one(), 0)], - TxAttributes::new_with_access( - TEST_NETWORK_ID, - vec![TxAccessPolicy::new(public_key.clone(), TxAccess::AllData)], - ), - ); - let txid = &tx0.id(); - let witness0 = StakedStateOpWitness::new(get_ecdsa_witness(&secp, &txid, &secret_key)); - - let account = get_account(&addr); - let info = ChainInfo { - min_fee_computed: Fee::new(Coin::zero()), - chain_hex_id: TEST_NETWORK_ID, - previous_block_time: 1, - unbonding_period: 0, - }; - - ZMQ_SOCKET.with(|socket| { - info!("sending a block commit request"); - let request = EnclaveRequest::CommitBlock { - app_hash: [0u8; 32], - info, - }; - let req = request.encode(); - socket.send(req, FLAGS).expect("request sending failed"); - let msg = socket - .recv_bytes(FLAGS) - .expect("failed to receive a response"); - let resp = EnclaveResponse::decode(&mut msg.as_slice()).expect("enclave tx response"); - info!("received a block commit response"); - match resp { - EnclaveResponse::CommitBlock(Ok(_)) => { - info!("ok block commit response"); - } - _ => { - error!("failed block commit response"); - fail_exit(&mut validation); - } - } - rx.recv().expect("Could not receive from channel. 1"); - let c = DefaultTransactionObfuscation::new( - format!("localhost:{}", query_server_port), - "localhost".to_owned(), - ); - info!("sending a enc request"); - let wrapped = c.encrypt(SignedTransaction::WithdrawUnbondedStakeTransaction( - tx0, - Box::new(account.clone()), - witness0, - )); - let withdrawtx = match wrapped { - Ok(TxAux::EnclaveTx(tx)) => tx, - Ok(_) => { - error!("failed enc request"); - fail_exit(&mut validation); - panic!(); - } - Err(e) => { - error!("failed enc request, {:?}", e); - fail_exit(&mut validation); - panic!(); - } - }; - - info!("sending a TX request"); - let request = EnclaveRequest::new_tx_request(withdrawtx, Some(account), info); - let req = request.encode(); - socket.send(req, FLAGS).expect("request sending failed"); - let msg = socket - .recv_bytes(FLAGS) - .expect("failed to receive a response"); - let resp = EnclaveResponse::decode(&mut msg.as_slice()).expect("enclave tx response"); - info!("received a TX response"); - match resp { - EnclaveResponse::VerifyTx(Ok(_)) => { - info!("ok tx response"); - } - _ => { - error!("failed tx response"); - fail_exit(&mut validation); - } - } - let request2 = EnclaveRequest::CommitBlock { - app_hash: [0u8; 32], - info, - }; - let req2 = request2.encode(); - socket.send(req2, FLAGS).expect("request sending failed"); - let msg2 = socket - .recv_bytes(FLAGS) - .expect("failed to receive a response"); - let resp2 = EnclaveResponse::decode(&mut msg2.as_slice()).expect("enclave commit response"); - info!("received a commit response"); - match resp2 { - EnclaveResponse::CommitBlock(Ok(_)) => { - info!("ok commit response"); - } - _ => { - error!("failed commit response"); - fail_exit(&mut validation); - } - } - - rx.recv().expect("Could not receive from channel. 2"); - let txids = vec![*txid]; - let r1 = c.decrypt( - txids.as_slice(), - &PrivateKey::deserialize_from(&secret_key[..].to_vec()).expect("private key"), - ); - match r1 { - Ok(v) => { - // TODO: check tx details - assert_eq!(v.len(), 1, "expected one TX"); - } - Err(e) => { - error!("wrong decryption response: {}", e); - fail_exit(&mut validation); - } - } - rx.recv().expect("Could not receive from channel. 3"); - let r2 = c.decrypt(txids.as_slice(), &PrivateKey::new().expect("random key")); - match r2 { - Ok(v) => { - assert_eq!(v.len(), 0, "expected no TX"); - } - Err(e) => { - error!("wrong decryption response: {}", e); - fail_exit(&mut validation); - } - } - validation.kill(); - }); -} diff --git a/chain-tx-enclave/tx-query/enclave/Cargo.toml b/chain-tx-enclave/tx-query/enclave/Cargo.toml index 0031aa8e7..e7a3064ea 100644 --- a/chain-tx-enclave/tx-query/enclave/Cargo.toml +++ b/chain-tx-enclave/tx-query/enclave/Cargo.toml @@ -11,7 +11,6 @@ crate-type = ["staticlib"] [features] default = [] -sgx-test = [] [target.'cfg(not(target_env = "sgx"))'.dependencies] sgx_types = { rev = "v1.1.0", git = "https://github.com/apache/teaclave-sgx-sdk.git" } diff --git a/chain-tx-enclave/tx-validation/app/Cargo.toml b/chain-tx-enclave/tx-validation/app/Cargo.toml deleted file mode 100644 index 8a3250e3d..000000000 --- a/chain-tx-enclave/tx-validation/app/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "tx-validation-app" -version = "0.3.0" -authors = ["Crypto.com "] -description = "Application server wrapper around the transaction validation enclave." -readme = "../../README.md" -edition = "2018" - -[features] -sgx-test = [] - -[dependencies] -sled = "0.30.3" -zmq = "0.9" -log = "0.4.8" -env_logger = "0.7.0" -enclave-u-common = { path = "../../enclave-u-common" } -sgx_types = { rev = "v1.1.0", git = "https://github.com/apache/teaclave-sgx-sdk.git" } -sgx_urts = { rev = "v1.1.0", git = "https://github.com/apache/teaclave-sgx-sdk.git" } -chain-core = { path = "../../../chain-core" } -chain-tx-validation = { path = "../../../chain-tx-validation" } -enclave-protocol = { path = "../../../enclave-protocol" } -parity-scale-codec = { version = "1.0" } -secp256k1zkp = { git = "https://github.com/crypto-com/rust-secp256k1-zkp.git", rev = "0125097a7bf6f939db0ce52e49803c5e0312bf5e", features = ["recovery", "endomorphism"] } - -[build-dependencies] -cc = "1.0" diff --git a/chain-tx-enclave/tx-validation/app/README.md b/chain-tx-enclave/tx-validation/app/README.md new file mode 100644 index 000000000..0e3ba7161 --- /dev/null +++ b/chain-tx-enclave/tx-validation/app/README.md @@ -0,0 +1 @@ +tx-validation-app was moved to chain-abci \ No newline at end of file diff --git a/chain-tx-enclave/tx-validation/app/build.rs b/chain-tx-enclave/tx-validation/app/build.rs deleted file mode 100644 index edeb70843..000000000 --- a/chain-tx-enclave/tx-validation/app/build.rs +++ /dev/null @@ -1,44 +0,0 @@ -use std::env; -use std::process::Command; - -fn main() { - let sdk_dir = env::var("SGX_SDK").unwrap_or_else(|_| "/opt/intel/sgxsdk".to_string()); - let is_sim = env::var("SGX_MODE").unwrap_or_else(|_| "HW".to_string()); - - #[cfg(target_arch = "x86")] - let edger8r = format!("{}/bin/x86/sgx_edger8r", sdk_dir); - #[cfg(not(target_arch = "x86"))] - let edger8r = format!("{}/bin/x64/sgx_edger8r", sdk_dir); - - Command::new(edger8r) - .args(&[ - "--untrusted", - "../enclave/Enclave.edl", - "--search-path", - &format!("{}/include", sdk_dir), - "--search-path", - "../../rust-sgx-sdk/edl", - "--untrusted-dir", - ".", - ]) - .status() - .unwrap(); - - cc::Build::new() - .file("Enclave_u.c") - .include(&format!("{}/include", sdk_dir)) - .include("../../rust-sgx-sdk/edl") - .compile("enclave.a"); - - #[cfg(target_arch = "x86")] - println!("cargo:rustc-link-search=native={}/lib", sdk_dir); - #[cfg(not(target_arch = "x86"))] - println!("cargo:rustc-link-search=native={}/lib64", sdk_dir); - - match is_sim.as_ref() { - "SW" => println!("cargo:rustc-link-lib=dylib=sgx_urts_sim"), - _ => println!("cargo:rustc-link-lib=dylib=sgx_urts"), // default to HW - } - - println!("cargo:rerun-if-changed=../enclave/Enclave.edl"); -} diff --git a/chain-tx-enclave/tx-validation/app/src/main.rs b/chain-tx-enclave/tx-validation/app/src/main.rs deleted file mode 100644 index ff81056e2..000000000 --- a/chain-tx-enclave/tx-validation/app/src/main.rs +++ /dev/null @@ -1,53 +0,0 @@ -mod enclave_u; -mod server; -#[cfg(feature = "sgx-test")] -mod test; - -use crate::server::TxValidationServer; -use enclave_u_common::enclave_u::init_enclave; -use enclave_u_common::{storage_path, META_KEYSPACE, TX_KEYSPACE}; -use log::{error, info}; -use std::env; -use std::thread; - -pub const TX_VALIDATION_ENCLAVE_FILE: &str = "tx_validation_enclave.signed.so"; - -#[cfg(feature = "sgx-test")] -fn main() { - test::test_sealing(); -} - -#[cfg(not(feature = "sgx-test"))] -fn main() { - env_logger::init(); - let args: Vec = env::args().collect(); - if args.len() < 2 { - error!("Please provide the ZMQ connection string (e.g. \"tcp://127.0.0.1:25933\") as the first argument"); - std::process::exit(1); - } - let db = sled::open(storage_path()).expect("failed to open a storage path"); - let metadb = db - .open_tree(META_KEYSPACE) - .expect("failed to open a meta keyspace"); - let txdb = db - .open_tree(TX_KEYSPACE) - .expect("failed to open a tx keyspace"); - let enclave = match init_enclave(TX_VALIDATION_ENCLAVE_FILE, true) { - Ok(r) => { - info!("[+] Init Enclave Successful {}!", r.geteid()); - r - } - Err(x) => { - error!("[-] Init Enclave Failed {}!", x.as_str()); - return; - } - }; - - let child_t = thread::spawn(move || { - let mut server = TxValidationServer::new(&args[1], enclave, txdb, metadb) - .expect("could not start a zmq server"); - info!("starting zmq server"); - server.execute() - }); - child_t.join().expect("server thread failed"); -} diff --git a/chain-tx-enclave/tx-validation/app/src/server/mod.rs b/chain-tx-enclave/tx-validation/app/src/server/mod.rs deleted file mode 100644 index 2ed252d87..000000000 --- a/chain-tx-enclave/tx-validation/app/src/server/mod.rs +++ /dev/null @@ -1,198 +0,0 @@ -use crate::enclave_u::{check_initchain, check_tx, encrypt_tx, end_block}; -use chain_core::state::account::DepositBondTx; -use chain_core::tx::data::TxId; -use chain_core::tx::TxEnclaveAux; -use chain_core::ChainInfo; -use enclave_protocol::IntraEnclaveRequest; -use enclave_protocol::{ - is_basic_valid_tx_request, EnclaveRequest, EnclaveResponse, IntraEncryptRequest, FLAGS, -}; -use parity_scale_codec::{Decode, Encode}; -use sgx_urts::SgxEnclave; -use sled::Tree; -use zmq::{Context, Error, Socket, REP}; - -pub struct TxValidationServer { - socket: Socket, - enclave: SgxEnclave, - txdb: Tree, - metadb: Tree, - info: Option, -} - -const LAST_APP_HASH_KEY: &[u8] = b"last_apphash"; -const LAST_CHAIN_INFO_KEY: &[u8] = b"chain_info"; - -impl TxValidationServer { - pub fn new( - connection_str: &str, - enclave: SgxEnclave, - txdb: Tree, - metadb: Tree, - ) -> Result { - match metadb.get(LAST_CHAIN_INFO_KEY) { - 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") - }); - let ctx = Context::new(); - let socket = ctx.socket(REP)?; - socket.bind(connection_str)?; - - Ok(TxValidationServer { - socket, - enclave, - txdb, - metadb, - info, - }) - } - } - } - - fn lookup_txids(&self, inputs: I) -> Option>> - where - I: IntoIterator + ExactSizeIterator, - { - let mut result = Vec::with_capacity(inputs.len()); - for input in inputs.into_iter() { - if let Ok(Some(txin)) = self.txdb.get(input) { - result.push(txin.to_vec()); - } else { - return None; - } - } - Some(result) - } - - fn lookup(&self, tx: &TxEnclaveAux) -> Option>> { - match tx { - TxEnclaveAux::TransferTx { inputs, .. } => { - self.lookup_txids(inputs.iter().map(|x| x.id)) - } - TxEnclaveAux::DepositStakeTx { - tx: DepositBondTx { inputs, .. }, - .. - } => self.lookup_txids(inputs.iter().map(|x| x.id)), - _ => None, - } - } - - pub fn flush_all(&mut self) -> Result { - let _ = self.txdb.flush()?; - self.metadb.flush() - } - - pub fn execute(&mut self) { - log::info!("running zmq server"); - loop { - if let Ok(msg) = self.socket.recv_bytes(FLAGS) { - 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, - }) => { - log::debug!("check chain"); - match self.metadb.get(LAST_APP_HASH_KEY) { - 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]; - app_hash.copy_from_slice(&stored); - app_hash - }); - if last_app_hash == ss { - EnclaveResponse::CheckChain(check_initchain( - self.enclave.geteid(), - chain_hex_id, - ss, - )) - } else { - log::error!("app hash not match"); - EnclaveResponse::CheckChain(Err(ss)) - } - } - } - } - Ok(EnclaveRequest::EndBlock) => EnclaveResponse::EndBlock(end_block( - self.enclave.geteid(), - IntraEnclaveRequest::EndBlock, - )), - Ok(EnclaveRequest::CommitBlock { app_hash, info }) => { - let _ = self.metadb.insert(LAST_APP_HASH_KEY, &app_hash); - let _ = self.metadb.insert(LAST_CHAIN_INFO_KEY, &info.encode()[..]); - if self.flush_all().is_ok() { - 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 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(), - IntraEnclaveRequest::ValidateTx { - request: req, - tx_inputs: mtxins, - }, - &mut self.txdb, - )) - } - } - Ok(EnclaveRequest::GetSealedTxData { txids }) => { - EnclaveResponse::GetSealedTxData(self.lookup_txids(txids.iter().copied())) - } - Ok(EnclaveRequest::EncryptTx(req)) => { - let result = match self.info { - Some(info) => { - let tx_inputs = match req.tx_inputs { - Some(inputs) => self.lookup_txids(inputs.iter().map(|x| x.id)), - _ => None, - }; - let request = IntraEncryptRequest { - txid: req.txid, - sealed_enc_request: req.sealed_enc_request, - tx_inputs, - info, - }; - encrypt_tx( - self.enclave.geteid(), - IntraEnclaveRequest::Encrypt(Box::new(request)), - ) - } - _ => { - log::error!("can not find encrypted transaction"); - Err(chain_tx_validation::Error::EnclaveRejected) - } - }; - EnclaveResponse::EncryptTx(result) - } - Err(e) => { - log::error!("unknown request / failed to decode: {}", e); - EnclaveResponse::UnknownRequest - } - }; - let response = resp.encode(); - self.socket - .send(response, FLAGS) - .expect("reply sending failed"); - } - } - } -} diff --git a/chain-tx-enclave/tx-validation/make.sh b/chain-tx-enclave/tx-validation/make.sh index b1d5169d9..7c0ff75be 100755 --- a/chain-tx-enclave/tx-validation/make.sh +++ b/chain-tx-enclave/tx-validation/make.sh @@ -2,5 +2,5 @@ set -e source /root/.docker_bashrc -cargo build -p tx-validation-app +cargo build -p chain-abci make -C chain-tx-enclave/tx-validation diff --git a/ci-scripts/install_sgxsdk.sh b/ci-scripts/install_sgxsdk.sh new file mode 100755 index 000000000..e31a3dc25 --- /dev/null +++ b/ci-scripts/install_sgxsdk.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +wget https://download.01.org/intel-sgx/sgx-linux/2.8/distro/ubuntu16.04-server/sgx_linux_x64_sdk_2.8.100.3.bin +chmod +x sgx_linux_x64_sdk_2.8.100.3.bin +echo -e 'no\n/opt' | ./sgx_linux_x64_sdk_2.8.100.3.bin diff --git a/ci-scripts/install_zeromq.sh b/ci-scripts/install_zeromq.sh deleted file mode 100755 index 9e9b3640c..000000000 --- a/ci-scripts/install_zeromq.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -FILE=/home/travis/lib/libzmq.so -if [ ! -f "$FILE" ]; then - echo "$FILE does not exist" - wget https://github.com/jedisct1/libsodium/releases/download/1.0.16/libsodium-1.0.16.tar.gz - tar xvfz libsodium-1.0.16.tar.gz - cd libsodium-1.0.16 - ./configure --prefix=$HOME - make - make install - cd .. - - wget https://github.com/zeromq/libzmq/releases/download/v4.2.5/zeromq-4.2.5.tar.gz - tar xvfz zeromq-4.2.5.tar.gz - cd zeromq-4.2.5 - ./configure --prefix=$HOME --with-libsodium - make - make install - cd .. -fi diff --git a/ci-scripts/tx-query-hw-test.sh b/ci-scripts/tx-query-hw-test.sh deleted file mode 100755 index 7641d0b56..000000000 --- a/ci-scripts/tx-query-hw-test.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -set -e - -source /root/.docker_bashrc - -export SGX_MODE=HW -export NETWORK_ID=ab -export RUST_LOG=debug -export RUST_BACKTRACE=1 -export RUSTFLAGS=-Ctarget-feature=+aes,+sse2,+sse4.1,+ssse3 - -# check if the sgx device exist -if [ -e '/dev/sgx' ]; then - echo "found sgx device /dev/sgx" -elif [ -e '/dev/isgx' ]; then - echo "found sgx device /dev/isgx" -elif [ -e ${SGX_DEVICE} ] && [ ${SGX_DEVICE}x != x ]; then - echo "found sgx device ${SGX_DEVICE}" -else - echo "can not find sgx device ${SGX_DEVICE}" - exit 1 -fi - -LD_LIBRARY_PATH=/opt/intel/libsgx-enclave-common/aesm /opt/intel/libsgx-enclave-common/aesm/aesm_service & - -echo "[aesm_service] Running in background ..." -# Wait for aesm_service to initialize -sleep 1 - - -cd /chain/chain-tx-enclave/tx-validation -make clean -make -cargo build -p tx-validation-app - -export SGX_TEST=1 -cd /chain/chain-tx-enclave/tx-query -make clean -make -cd app -cargo build --features sgx-test - -export TX_VALIDATION_BIN_DIR=/chain/target/debug -export TX_QUERY_APP_PORT=`/chain/ci-scripts/find-free-port.sh` -cd ../../../target/debug -# assumes SPID + IAS_API_KEY environment variables are set from outside / docker -./tx-query-app diff --git a/ci-scripts/tx-validation-hw-test.sh b/ci-scripts/tx-validation-hw-test.sh index 55c62c440..6f2cef43c 100755 --- a/ci-scripts/tx-validation-hw-test.sh +++ b/ci-scripts/tx-validation-hw-test.sh @@ -31,7 +31,7 @@ sleep 1 cd /chain/chain-tx-enclave/tx-validation make clean make -cd app +cd ../../chain-abci cargo build --features sgx-test -cd ../../../target/debug -./tx-validation-app +cd ../target/debug +./chain-abci diff --git a/client-common/Cargo.toml b/client-common/Cargo.toml index deec47b39..d230bd35d 100644 --- a/client-common/Cargo.toml +++ b/client-common/Cargo.toml @@ -22,7 +22,7 @@ secstr = { version = "0.4.0", features = ["serde"] } zeroize = "1.1" serde = { version = "1.0", features = ["derive"] } chrono = { version = "0.4", features = ["serde"] } -sled = { version = "0.30.3", optional = true } +sled = { version = "0.31.0", optional = true } jsonrpc = { version = "0.11", optional = true } serde_json = { version = "1.0", optional = true } parity-scale-codec = { features = ["derive"], version = "1.1" } diff --git a/docker/build.sh b/docker/build.sh index b21f061e7..907dc313d 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -22,7 +22,6 @@ cd .. echo "Build $BUILD_MODE $BUILD_PROFILE" if [ $BUILD_MODE == "sgx" ]; then cargo build $CARGO_ARGS - cargo build $CARGO_ARGS -p tx-validation-app cargo build $CARGO_ARGS -p tx-query-app make -C chain-tx-enclave/tx-validation make -C chain-tx-enclave/tx-query diff --git a/enclave-protocol/src/lib.rs b/enclave-protocol/src/lib.rs index 2c929c0b3..2a4f25b27 100644 --- a/enclave-protocol/src/lib.rs +++ b/enclave-protocol/src/lib.rs @@ -65,6 +65,46 @@ pub enum IntraEnclaveRequest { Encrypt(Box), } +impl IntraEnclaveRequest { + pub fn new_validate_transfer( + tx: TxEnclaveAux, + info: ChainInfo, + tx_inputs: Vec, + ) -> Self { + Self::ValidateTx { + tx_inputs: Some(tx_inputs), + request: Box::new(VerifyTxRequest { + tx, + account: None, + info, + }), + } + } + + pub fn new_validate_deposit( + tx: TxEnclaveAux, + info: ChainInfo, + account: Option, + tx_inputs: Vec, + ) -> Self { + Self::ValidateTx { + tx_inputs: Some(tx_inputs), + request: Box::new(VerifyTxRequest { tx, account, info }), + } + } + + pub fn new_validate_withdraw(tx: TxEnclaveAux, info: ChainInfo, account: StakedState) -> Self { + Self::ValidateTx { + tx_inputs: None, + request: Box::new(VerifyTxRequest { + tx, + account: Some(account), + info, + }), + } + } +} + /// helper method to validate basic assumptions pub fn is_basic_valid_tx_request( request: &VerifyTxRequest, @@ -129,51 +169,21 @@ pub struct QueryEncryptRequest { pub tx_inputs: Option>, } -/// requests sent from chain-abci app to enclave wrapper server +/// requests sent from tx-query to chain-abci tx validation enclave app wrapper #[derive(Encode, Decode)] pub enum EnclaveRequest { - /// a sanity check (sends the chain network ID -- last byte / two hex digits convention) - /// during InitChain or startup (to test one connected to the correct process) - /// and the last processed app hash - /// FIXME: test genesis hash etc. - CheckChain { - chain_hex_id: u8, - last_app_hash: Option, - }, - /// "stateless" transaction validation requests (sends transaction + all required information) - /// double-spent / BitVec check done in chain-abci - VerifyTx(Box), - /// request to get the block's transaction filter and reset the existing one - EndBlock, - /// request to flush/persist storage + store the computed app hash + chain info (min_computed_fee is set to the constant factor in fee) - /// FIXME: enclave should be able to compute a part of app hash, so send the other parts and check the same app hash was computed - CommitBlock { app_hash: H256, info: ChainInfo }, /// request to get tx data sealed to "mrsigner" (requested by TQE -- they should be on the same machine) GetSealedTxData { txids: Vec }, /// request to encrypt tx by the current key (requested by TQE -- they should be on the same machine) EncryptTx(Box), } -impl EnclaveRequest { - pub fn new_tx_request(tx: TxEnclaveAux, account: Option, info: ChainInfo) -> Self { - EnclaveRequest::VerifyTx(Box::new(VerifyTxRequest { tx, account, info })) - } -} - pub type VerifyOk = (Fee, Option, Option>); -/// responses sent from enclave wrapper server to chain-abci app +/// responses sent from chain-abci tx validation enclave app wrapper to tx-query /// TODO: better error responses? #[derive(Encode, Decode)] pub enum EnclaveResponse { - /// returns OK if chain_hex_id matches the one embedded in enclave and last_app_hash matches (returns the last app hash if any) - CheckChain(Result<(), Option>), - /// returns the affected (account) state (if any) and paid fee if the TX is valid - VerifyTx(Result), - /// returns the transaction filter for the current block - EndBlock(Result>, ()>), - /// returns if the data was successfully persisted in the enclave's local storage - CommitBlock(Result<(), ()>), /// returns Some(sealed data payloads) or None (if any TXID was not found / invalid) GetSealedTxData(Option>), /// returns Ok(encrypted tx payload) if Tx was valid diff --git a/integration-tests/bot/chainbot.py b/integration-tests/bot/chainbot.py index 2795464ea..df09deb93 100755 --- a/integration-tests/bot/chainbot.py +++ b/integration-tests/bot/chainbot.py @@ -224,12 +224,11 @@ def programs(node, app_hash, root_path, cfg): def_env = { 'RUST_BACKTRACE': '1', 'RUST_LOG': 'info', + 'SGX_MODE': 'HW', } commands = [] if not cfg.get('mock_mode'): commands += [ - ('tx-validation', f"tx-validation-app tcp://0.0.0.0:{tx_validation_port}", - dict(def_env, SGX_MODE='HW', TX_ENCLAVE_STORAGE=node_path / Path('tx-validation'))), ('tx-query', f"tx-query-app 0.0.0.0:{tx_query_port} tcp://127.0.0.1:{tx_validation_port}", dict(def_env, SGX_MODE='HW', IAS_API_KEY=os.environ['IAS_API_KEY'], SPID=os.environ['SPID'], TX_ENCLAVE_STORAGE=node_path / Path('tx-query'))), ] diff --git a/integration-tests/multinode/byzantine_test.py b/integration-tests/multinode/byzantine_test.py index ad46530ac..eb84b5b32 100644 --- a/integration-tests/multinode/byzantine_test.py +++ b/integration-tests/multinode/byzantine_test.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 import os -import time from chainrpc import RPC from chainbot import SigningKey from common import UnixStreamXMLRPCClient, wait_for_validators, wait_for_port, wait_for_blocks, stop_node, wait_for_tx @@ -24,7 +23,6 @@ # stop node1 print('Stop node1') -time.sleep(5) # FIXME, remove after adr-001 implemented stop_node(supervisor, 'node1') print('Wait for 1 validators online') diff --git a/integration-tests/multinode/common.py b/integration-tests/multinode/common.py index 6c2a7bb88..1f7e95c6d 100644 --- a/integration-tests/multinode/common.py +++ b/integration-tests/multinode/common.py @@ -51,8 +51,6 @@ def wait_for_blocks(rpc, n): def stop_node(supervisor, name): supervisor.supervisor.stopProcess('%s:%s-%s' % (name, 'tendermint', name)) - print('Wait 3 seconds before stop other processes[FIXME, remove after adr-001]') - time.sleep(3) supervisor.supervisor.stopProcessGroup(name) diff --git a/integration-tests/multinode/jail_test.py b/integration-tests/multinode/jail_test.py index 7c43b8627..cb82111e2 100755 --- a/integration-tests/multinode/jail_test.py +++ b/integration-tests/multinode/jail_test.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 import os -import time from chainrpc import RPC from chainbot import SigningKey from common import UnixStreamXMLRPCClient, wait_for_validators, wait_for_port, wait_for_blocks, wait_for_tx, wait_for_blocktime, stop_node @@ -44,7 +43,6 @@ enckey = rpc.wallet.restore(TARGET_NODE_MNEMONIC, name='target') print('Stop', TARGET_NODE) -time.sleep(5) # FIXME, remove after adr-001 implemented stop_node(supervisor, TARGET_NODE) print('Waiting for', MISSED_BLOCK_THRESHOLD + 3, 'blocks') diff --git a/test-common/src/chain_env.rs b/test-common/src/chain_env.rs index 46258f31b..0e21839c8 100644 --- a/test-common/src/chain_env.rs +++ b/test-common/src/chain_env.rs @@ -286,6 +286,7 @@ impl ChainEnv { storage, account_storage, None, + None, ) }