diff --git a/Cargo.lock b/Cargo.lock index 5965ba9f8e..fd8370e99e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2635,6 +2635,26 @@ dependencies = [ "sgx_tstd", ] +[[package]] +name = "itp-stf-executor" +version = "0.8.0" +dependencies = [ + "ita-stf", + "itp-ocall-api", + "itp-stf-state-handler", + "itp-storage", + "itp-storage-verifier", + "itp-types", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-scale-codec", + "sgx-externalities", + "sgx_tstd", + "sgx_types", + "sp-runtime", + "thiserror 1.0.29", + "thiserror 1.0.9", +] + [[package]] name = "itp-stf-state-handler" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 71960b2a3a..849710d78c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "core-primitives/settings", "core-primitives/sgx/crypto", "core-primitives/sgx/io", + "core-primitives/stf-executor", "core-primitives/stf-state-handler", "core-primitives/storage", "core-primitives/test", diff --git a/app-libs/stf/Cargo.toml b/app-libs/stf/Cargo.toml index be3040e57f..dadcfd173d 100644 --- a/app-libs/stf/Cargo.toml +++ b/app-libs/stf/Cargo.toml @@ -12,7 +12,6 @@ sgx = [ "log-sgx", "sp-io", "sgx-runtime", - "derive_more", "itp-types", "its-primitives", "its-state", @@ -41,7 +40,7 @@ clap = { version = "2.33", optional = true } clap-nested = { version = "0.3.1", optional = true } log = { version = "0.4", optional = true } base58 = { version = "0.1", optional = true } -derive_more = { version = "0.99.5", optional = true } +derive_more = { version = "0.99.5" } hex = { version = "0.4.2", optional = true } codec = { version = "2.0.0", default-features = false, features = ["derive"], package = "parity-scale-codec" } sgx_tstd = { rev = "v1.1.3", features = ["untrusted_fs","net","backtrace"], git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } diff --git a/sidechain/top-pool-rpc-author/src/hash.rs b/app-libs/stf/src/hash.rs similarity index 86% rename from sidechain/top-pool-rpc-author/src/hash.rs rename to app-libs/stf/src/hash.rs index 5f22d00f19..8faf40a82c 100644 --- a/sidechain/top-pool-rpc-author/src/hash.rs +++ b/app-libs/stf/src/hash.rs @@ -15,16 +15,14 @@ */ -//! Extrinsic helpers for author RPC module. - +use crate::TrustedOperation; use codec::{Decode, Encode}; -use ita_stf::TrustedOperation; use std::vec::Vec; -/// RPC Trusted call or hash +/// Trusted operation Or hash /// /// Allows to refer to trusted calls either by its raw representation or its hash. -#[derive(Debug, Encode, Decode)] +#[derive(Clone, Debug, Encode, Decode)] pub enum TrustedOperationOrHash { /// The hash of the call. Hash(Hash), diff --git a/app-libs/stf/src/helpers.rs b/app-libs/stf/src/helpers.rs index c78a3f01a0..f18be20503 100644 --- a/app-libs/stf/src/helpers.rs +++ b/app-libs/stf/src/helpers.rs @@ -14,10 +14,7 @@ limitations under the License. */ -use crate::{ - stf_sgx_primitives::{types::*, StfError, StfResult}, - AccountId, Index, -}; +use crate::{stf_sgx_primitives::types::*, AccountId, Index, StfError, StfResult}; use codec::{Decode, Encode}; use itp_storage::{storage_double_map_key, storage_map_key, storage_value_key, StorageHasher}; use log_sgx::*; diff --git a/app-libs/stf/src/lib.rs b/app-libs/stf/src/lib.rs index 4e3e83afdb..c12a225c86 100644 --- a/app-libs/stf/src/lib.rs +++ b/app-libs/stf/src/lib.rs @@ -23,16 +23,21 @@ #![cfg_attr(all(not(target_env = "sgx"), not(feature = "std")), no_std)] #![cfg_attr(target_env = "sgx", feature(rustc_private))] +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + extern crate alloc; -use codec::{Compact, Decode, Encode}; #[cfg(feature = "std")] use my_node_runtime::Balance; #[cfg(feature = "std")] pub use my_node_runtime::Index; +use codec::{Compact, Decode, Encode}; +use derive_more::Display; use sp_core::{crypto::AccountId32, ed25519, sr25519, Pair, H256}; use sp_runtime::{traits::Verify, MultiSignature}; +use std::string::String; pub type Signature = MultiSignature; pub type AuthorityId = ::Signer; @@ -42,6 +47,24 @@ pub type BalanceTransferFn = ([u8; 2], AccountId, Compact); pub type ShardIdentifier = H256; +pub type StfResult = Result; + +#[derive(Debug, Display, PartialEq, Eq)] +pub enum StfError { + #[display(fmt = "Insufficient privileges {:?}, are you sure you are root?", _0)] + MissingPrivileges(AccountId), + #[display(fmt = "Error dispatching runtime call. {:?}", _0)] + Dispatch(String), + #[display(fmt = "Not enough funds to perform operation")] + MissingFunds, + #[display(fmt = "Account does not exist {:?}", _0)] + InexistentAccount(AccountId), + #[display(fmt = "Invalid Nonce {:?}", _0)] + InvalidNonce(Index), + StorageHashMismatch, + InvalidStorageDiff, +} + #[derive(Clone)] pub enum KeyPair { Sr25519(sr25519::Pair), @@ -69,6 +92,8 @@ impl From for KeyPair { } } +pub mod hash; + #[cfg(feature = "sgx")] pub mod stf_sgx; diff --git a/app-libs/stf/src/stf_sgx.rs b/app-libs/stf/src/stf_sgx.rs index 04c14f9abd..7157755c0a 100644 --- a/app-libs/stf/src/stf_sgx.rs +++ b/app-libs/stf/src/stf_sgx.rs @@ -1,11 +1,27 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + use crate::{ helpers::{ account_data, account_nonce, ensure_root, get_account_info, get_storage_value, increment_nonce, root, validate_nonce, }, - stf_sgx_primitives::{StfError, StfResult}, AccountData, AccountId, Getter, Index, PublicGetter, ShardIdentifier, State, StateTypeDiff, - Stf, TrustedCall, TrustedCallSigned, TrustedGetter, + Stf, StfError, StfResult, TrustedCall, TrustedCallSigned, TrustedGetter, }; use codec::Encode; use itp_settings::node::{TEEREX_MODULE, UNSHIELD}; @@ -57,7 +73,7 @@ impl Stf { ext } - pub fn get_state(ext: &mut State, getter: Getter) -> Option> { + pub fn get_state(ext: &mut impl SgxExternalitiesTrait, getter: Getter) -> Option> { ext.execute_with(|| match getter { Getter::trusted(g) => match g.getter { TrustedGetter::free_balance(who) => @@ -92,7 +108,7 @@ impl Stf { } pub fn execute( - ext: &mut State, + ext: &mut impl SgxExternalitiesTrait, call: TrustedCallSigned, calls: &mut Vec, ) -> StfResult<()> { @@ -213,14 +229,19 @@ impl Stf { }); } - pub fn update_layer_one_block_number(ext: &mut State, number: L1BlockNumer) { + pub fn update_layer_one_block_number( + ext: &mut impl SgxExternalitiesTrait, + number: L1BlockNumer, + ) { ext.execute_with(|| { let key = storage_value_key("System", "LayerOneNumber"); sp_io::storage::set(&key, &number.encode()); }); } - pub fn get_layer_one_block_number(ext: &mut State) -> Option { + pub fn get_layer_one_block_number( + ext: &mut impl SgxExternalitiesTrait, + ) -> Option { ext.execute_with(|| get_storage_value("System", "LayerOneNumber")) } @@ -251,11 +272,11 @@ impl Stf { key_hashes } - pub fn get_root(ext: &mut State) -> AccountId { + pub fn get_root(ext: &mut impl SgxExternalitiesTrait) -> AccountId { ext.execute_with(|| root()) } - pub fn account_nonce(ext: &mut State, account: &AccountId) -> Index { + pub fn account_nonce(ext: &mut impl SgxExternalitiesTrait, account: &AccountId) -> Index { ext.execute_with(|| { let nonce = account_nonce(account); debug!("Account {:?} nonce is {}", account.encode(), nonce); @@ -263,7 +284,10 @@ impl Stf { }) } - pub fn account_data(ext: &mut State, account: &AccountId) -> Option { + pub fn account_data( + ext: &mut impl SgxExternalitiesTrait, + account: &AccountId, + ) -> Option { ext.execute_with(|| account_data(account)) } } diff --git a/app-libs/stf/src/stf_sgx_primitives.rs b/app-libs/stf/src/stf_sgx_primitives.rs index b0cab2a133..dd77fff008 100644 --- a/app-libs/stf/src/stf_sgx_primitives.rs +++ b/app-libs/stf/src/stf_sgx_primitives.rs @@ -15,15 +15,10 @@ */ -use crate::{AccountId, Index}; use codec::{Decode, Encode}; -use derive_more::Display; use itp_types::H256; -use sgx_tstd as std; use std::prelude::v1::*; -pub type StfResult = Result; - pub mod types { pub use sgx_runtime::{Balance, Index}; pub type AccountData = balances::AccountData; @@ -73,19 +68,3 @@ impl StatePayload { } } } - -#[derive(Debug, Display, PartialEq, Eq)] -pub enum StfError { - #[display(fmt = "Insufficient privileges {:?}, are you sure you are root?", _0)] - MissingPrivileges(AccountId), - #[display(fmt = "Error dispatching runtime call. {:?}", _0)] - Dispatch(String), - #[display(fmt = "Not enough funds to perform operation")] - MissingFunds, - #[display(fmt = "Account does not exist {:?}", _0)] - InexistentAccount(AccountId), - #[display(fmt = "Invalid Nonce {:?}", _0)] - InvalidNonce(Index), - StorageHashMismatch, - InvalidStorageDiff, -} diff --git a/app-libs/stf/src/test_genesis.rs b/app-libs/stf/src/test_genesis.rs index 928c74f031..609ebd82d6 100644 --- a/app-libs/stf/src/test_genesis.rs +++ b/app-libs/stf/src/test_genesis.rs @@ -15,7 +15,7 @@ */ -use crate::{helpers::get_account_info, stf_sgx_primitives::StfError}; +use crate::{helpers::get_account_info, StfError}; use itp_storage::storage_value_key; use log_sgx::*; use sgx_externalities::SgxExternalitiesTrait; diff --git a/core-primitives/ocall-api/src/lib.rs b/core-primitives/ocall-api/src/lib.rs index d6e2d18b19..9a0c788bce 100644 --- a/core-primitives/ocall-api/src/lib.rs +++ b/core-primitives/ocall-api/src/lib.rs @@ -17,13 +17,15 @@ #![cfg_attr(not(feature = "std"), no_std)] +pub extern crate alloc; + +use alloc::vec::Vec; use codec::{Decode, Encode}; use core::fmt::Debug; use itp_types::{TrustedOperationStatus, WorkerRequest, WorkerResponse}; use its_primitives::traits::SignedBlock; use sgx_types::*; use sp_runtime::OpaqueExtrinsic; -use sp_std::prelude::Vec; /// Trait for the enclave to make o-calls related to remote attestation pub trait EnclaveAttestationOCallApi: Clone + Debug + Send + Sync { diff --git a/core-primitives/stf-executor/Cargo.toml b/core-primitives/stf-executor/Cargo.toml new file mode 100644 index 0000000000..fa0f622457 --- /dev/null +++ b/core-primitives/stf-executor/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "itp-stf-executor" +version = "0.8.0" +authors = ["Integritee AG "] +edition = "2018" +resolver = "2" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["std"] +std = [ + "ita-stf/std", + "itp-ocall-api/std", + "itp-stf-state-handler/std", + "itp-storage/std", + "itp-storage-verifier/std", + "sgx-externalities/std", + "sp-runtime/std", + "thiserror", +] +sgx = [ + "sgx_tstd", + "ita-stf/sgx", + "itp-stf-state-handler/sgx", + "itp-storage/sgx", + "sgx-externalities", + "thiserror_sgx", +] +test = [] + +[dependencies] +# sgx dependencies +sgx_types = { rev = "v1.1.3", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +sgx_tstd = { rev = "v1.1.3", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx-externalities = { default-features = false, git = "https://github.com/integritee-network/sgx-runtime", branch = "master", optional = true } + +# local dependencies +ita-stf = { path = "../../app-libs/stf", default-features = false } +itp-ocall-api = { path = "../ocall-api", default-features = false } +itp-stf-state-handler = { path = "../stf-state-handler", default-features = false } +itp-storage = { path = "../storage", default-features = false } +itp-storage-verifier = { path = "../storage-verified", default-features = false } +itp-types = { path = "../types", default-features = false } + +# sgx enabled external libraries +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +thiserror = { version = "1.0", optional = true } + +# no-std dependencies +log = { version = "0.4", default-features = false } +codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } +sp-runtime = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "master"} diff --git a/core-primitives/stf-executor/src/error.rs b/core-primitives/stf-executor/src/error.rs new file mode 100644 index 0000000000..b6933addd7 --- /dev/null +++ b/core-primitives/stf-executor/src/error.rs @@ -0,0 +1,65 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use sgx_types::sgx_status_t; +use std::{boxed::Box, format}; + +pub type Result = core::result::Result; + +/// STF-Executor error +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Trusted operation has invalid signature")] + OperationHasInvalidSignature, + #[error("SGX error, status: {0}")] + Sgx(sgx_status_t), + #[error("State handling error: {0}")] + StateHandler(#[from] itp_stf_state_handler::error::Error), + #[error("STF error: {0}")] + Stf(ita_stf::StfError), + #[error("Storage verified error: {0}")] + StorageVerified(itp_storage_verifier::Error), + #[error(transparent)] + Other(#[from] Box), +} + +impl From for Error { + fn from(sgx_status: sgx_status_t) -> Self { + Self::Sgx(sgx_status) + } +} + +impl From for Error { + fn from(e: codec::Error) -> Self { + Self::Other(format!("{:?}", e).into()) + } +} + +impl From for Error { + fn from(error: ita_stf::StfError) -> Self { + Self::Stf(error) + } +} + +impl From for Error { + fn from(error: itp_storage_verifier::Error) -> Self { + Self::StorageVerified(error) + } +} diff --git a/core-primitives/stf-executor/src/executor.rs b/core-primitives/stf-executor/src/executor.rs new file mode 100644 index 0000000000..e6919fd77f --- /dev/null +++ b/core-primitives/stf-executor/src/executor.rs @@ -0,0 +1,451 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +// #[cfg(all(not(feature = "std"), feature = "sgx"))] +// use crate::sgx_reexport_prelude::*; + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::untrusted::time::SystemTimeEx; + +use crate::{ + error::{Error, Result}, + traits::{ + StatePostProcessing, StfExecuteGenericUpdate, StfExecuteShieldFunds, + StfExecuteTimedCallsBatch, StfExecuteTimedGettersBatch, StfExecuteTrustedCall, + StfUpdateState, + }, + BatchExecutionResult, ExecutedOperation, ExecutionStatus, +}; +use codec::{Decode, Encode}; +use ita_stf::{ + hash::TrustedOperationOrHash, + stf_sgx::{shards_key_hash, storage_hashes_to_update_per_shard}, + AccountId, ShardIdentifier, StateTypeDiff, Stf, TrustedCall, TrustedCallSigned, + TrustedGetterSigned, +}; +use itp_ocall_api::{EnclaveAttestationOCallApi, EnclaveOnChainOCallApi}; +use itp_stf_state_handler::handle_state::HandleState; +use itp_storage::StorageEntryVerified; +use itp_storage_verifier::GetStorageVerified; +use itp_types::{Amount, OpaqueCall, H256}; +use log::*; +use sgx_externalities::SgxExternalitiesTrait; +use sp_runtime::{ + app_crypto::sp_core::blake2_256, + traits::{Block as BlockT, Header, UniqueSaturatedInto}, +}; +use std::{ + collections::HashMap, + fmt::Debug, + format, + marker::PhantomData, + result::Result as StdResult, + sync::Arc, + time::{Duration, SystemTime}, + vec::Vec, +}; + +/// STF Executor implementation +/// +/// FIXME: Next time we change this class, we should add some unit tests +/// (where they make sense and can be done with reasonable effort) +pub struct StfExecutor { + ocall_api: Arc, + state_handler: Arc, + _phantom_externalities: PhantomData, +} + +impl StfExecutor +where + OCallApi: EnclaveAttestationOCallApi + EnclaveOnChainOCallApi + GetStorageVerified, + StateHandler: HandleState, + ExternalitiesT: SgxExternalitiesTrait + Encode, +{ + pub fn new(ocall_api: Arc, state_handler: Arc) -> Self { + StfExecutor { ocall_api, state_handler, _phantom_externalities: Default::default() } + } + + /// Execute a trusted call on the STF + /// + /// We distinguish between an error in the execution, which maps to `Err` and + /// an invalid trusted call, which results in `Ok(ExecutionStatus::Failure)`. The latter + /// can be used to remove the trusted call from a queue. In the former case we might keep the + /// trusted call and just re-try the operation. + fn execute_trusted_call_on_stf( + &self, + state: &mut E, + stf_call_signed: &TrustedCallSigned, + header: &PB::Header, + shard: &ShardIdentifier, + post_processing: StatePostProcessing, + ) -> Result + where + PB: BlockT, + E: SgxExternalitiesTrait, + { + debug!("query mrenclave of self"); + let mrenclave = self.ocall_api.get_mrenclave_of_self()?; + //debug!("MRENCLAVE of self is {}", mrenclave.m.to_base58()); + + let top_or_hash = top_or_hash::(stf_call_signed.clone(), true); + + if let false = stf_call_signed.verify_signature(&mrenclave.m, &shard) { + error!("TrustedCallSigned: bad signature"); + // do not panic here or users will be able to shoot workers dead by supplying a bad signature + return Ok(ExecutedOperation::failed(top_or_hash)) + } + + // Necessary because light client sync may not be up to date + // see issue #208 + debug!("Update STF storage!"); + let storage_hashes = Stf::get_storage_hashes_to_update(&stf_call_signed); + let update_map = self + .ocall_api + .get_multiple_storages_verified(storage_hashes, header) + .map(into_map)?; + + Stf::update_storage(state, &update_map.into()); + + debug!("execute STF"); + let mut extrinsic_call_backs: Vec = Vec::new(); + if let Err(e) = Stf::execute(state, stf_call_signed.clone(), &mut extrinsic_call_backs) { + error!("Stf::execute failed: {:?}", e); + return Ok(ExecutedOperation::failed(top_or_hash)) + } + + let operation = stf_call_signed.clone().into_trusted_operation(true); + let operation_hash: H256 = blake2_256(&operation.encode()).into(); + debug!("Operation hash {:?}", operation_hash); + + if let StatePostProcessing::Prune = post_processing { + state.prune_state_diff(); + } + + Ok(ExecutedOperation::success(operation_hash, top_or_hash, extrinsic_call_backs)) + } +} + +impl StfExecuteTrustedCall + for StfExecutor +where + OCallApi: EnclaveAttestationOCallApi + EnclaveOnChainOCallApi + GetStorageVerified, + StateHandler: HandleState, + ExternalitiesT: SgxExternalitiesTrait + Encode, +{ + fn execute_trusted_call( + &self, + calls: &mut Vec, + stf_call_signed: &TrustedCallSigned, + header: &PB::Header, + shard: &ShardIdentifier, + post_processing: StatePostProcessing, + ) -> Result> + where + PB: BlockT, + { + // load state before executing any calls + let (state_lock, mut state) = self.state_handler.load_for_mutation(shard)?; + + let executed_call = self.execute_trusted_call_on_stf::( + &mut state, + stf_call_signed, + header, + shard, + post_processing, + )?; + + let (maybe_call_hash, mut extrinsic_callbacks) = match executed_call.status { + ExecutionStatus::Success(call_hash, e) => (Some(call_hash), e), + ExecutionStatus::Failure => (None, Vec::new()), + }; + + calls.append(&mut extrinsic_callbacks); + + trace!("Updating state of shard {:?}", shard); + self.state_handler.write(state, state_lock, shard)?; + + Ok(maybe_call_hash) + } +} + +impl StfExecuteShieldFunds + for StfExecutor +where + OCallApi: EnclaveAttestationOCallApi + EnclaveOnChainOCallApi + GetStorageVerified, + StateHandler: HandleState, + ExternalitiesT: SgxExternalitiesTrait + Encode, +{ + fn execute_shield_funds( + &self, + account: AccountId, + amount: Amount, + shard: &ShardIdentifier, + calls: &mut Vec, + ) -> Result { + let (state_lock, mut state) = self.state_handler.load_for_mutation(shard)?; + + let root = Stf::get_root(&mut state); + let nonce = Stf::account_nonce(&mut state, &root); + + let trusted_call = TrustedCallSigned::new( + TrustedCall::balance_shield(root, account, amount), + nonce, + Default::default(), //don't care about signature here + ); + + Stf::execute(&mut state, trusted_call, calls).map_err::(|e| e.into())?; + + self.state_handler.write(state, state_lock, shard).map_err(|e| e.into()) + } +} + +impl StfUpdateState + for StfExecutor +where + OCallApi: EnclaveAttestationOCallApi + EnclaveOnChainOCallApi + GetStorageVerified, + StateHandler: HandleState, + ExternalitiesT: SgxExternalitiesTrait + Encode, +{ + fn update_states(&self, header: &PB::Header) -> Result<()> + where + PB: BlockT, + { + debug!("Update STF storage upon block import!"); + let storage_hashes = Stf::storage_hashes_to_update_on_block(); + + if storage_hashes.is_empty() { + return Ok(()) + } + + // global requests they are the same for every shard + let state_diff_update: StateTypeDiff = self + .ocall_api + .get_multiple_storages_verified(storage_hashes, header) + .map(into_map)? + .into(); + + // look for new shards an initialize them + if let Some(maybe_shards) = state_diff_update.get(&shards_key_hash()) { + match maybe_shards { + Some(shards) => { + let shards: Vec = Decode::decode(&mut shards.as_slice())?; + + for shard_id in shards { + let (state_lock, mut state) = + self.state_handler.load_for_mutation(&shard_id)?; + trace!("Successfully loaded state, updating states ..."); + + // per shard (cid) requests + let per_shard_hashes = storage_hashes_to_update_per_shard(&shard_id); + let per_shard_update = self + .ocall_api + .get_multiple_storages_verified(per_shard_hashes, header) + .map(into_map)?; + + Stf::update_storage(&mut state, &per_shard_update.into()); + Stf::update_storage(&mut state, &state_diff_update); + + // block number is purged from the substrate state so it can't be read like other storage values + // The number conversion is a bit unfortunate, but I wanted to prevent making the stf generic for now + Stf::update_layer_one_block_number( + &mut state, + (*header.number()).unique_saturated_into(), + ); + + self.state_handler.write(state, state_lock, &shard_id)?; + } + }, + None => debug!("No shards are on the chain yet"), + }; + }; + Ok(()) + } +} + +impl StfExecuteTimedCallsBatch + for StfExecutor +where + OCallApi: EnclaveAttestationOCallApi + EnclaveOnChainOCallApi + GetStorageVerified, + StateHandler: HandleState, + ExternalitiesT: SgxExternalitiesTrait + Encode, +{ + type Externalities = ExternalitiesT; + + fn execute_timed_calls_batch( + &self, + trusted_calls: &[TrustedCallSigned], + header: &PB::Header, + shard: &ShardIdentifier, + max_exec_duration: Duration, + prepare_state_function: F, + ) -> Result + where + PB: BlockT, + F: FnOnce(Self::Externalities) -> Self::Externalities, + { + let ends_at = duration_now() + max_exec_duration; + + let (state_lock, state) = self.state_handler.load_for_mutation(shard)?; + + let previous_state_hash: H256 = state.using_encoded(blake2_256).into(); + + let mut state = prepare_state_function(state); // execute any pre-processing steps + let mut executed_calls = Vec::::new(); + + for trusted_call_signed in trusted_calls.into_iter() { + match self.execute_trusted_call_on_stf::( + &mut state, + &trusted_call_signed, + header, + shard, + StatePostProcessing::None, + ) { + Ok(executed_call) => { + executed_calls.push(executed_call); + }, + Err(e) => { + error!("Error executing trusted call (will not push top hash): {:?}", e); + }, + }; + + // Check time + if ends_at < duration_now() { + break + } + } + + self.state_handler + .write(state, state_lock, shard) + .map_err(|e| Error::StateHandler(e))?; + + Ok(BatchExecutionResult { executed_operations: executed_calls, previous_state_hash }) + } +} + +impl StfExecuteTimedGettersBatch + for StfExecutor +where + OCallApi: EnclaveAttestationOCallApi + EnclaveOnChainOCallApi + GetStorageVerified, + StateHandler: HandleState, + ExternalitiesT: SgxExternalitiesTrait + Encode, +{ + type Externalities = ExternalitiesT; + + fn execute_timed_getters_batch( + &self, + trusted_getters: &[TrustedGetterSigned], + shard: &ShardIdentifier, + max_exec_duration: Duration, + getter_callback: F, + ) -> Result<()> + where + F: Fn(&TrustedGetterSigned, Result>>), + { + let ends_at = duration_now() + max_exec_duration; + + // return early if we have no trusted getters, so we don't decrypt the state unnecessarily + if trusted_getters.is_empty() { + return Ok(()) + } + + // load state once per shard + let mut state = self.state_handler.load_initialized(&shard)?; + + for trusted_getter_signed in trusted_getters.into_iter() { + // get state + let getter_state = get_stf_state(trusted_getter_signed, &mut state); + + getter_callback(trusted_getter_signed, getter_state); + + // Check time + if ends_at < duration_now() { + return Ok(()) + } + } + + Ok(()) + } +} + +impl StfExecuteGenericUpdate + for StfExecutor +where + OCallApi: EnclaveAttestationOCallApi + EnclaveOnChainOCallApi + GetStorageVerified, + StateHandler: HandleState, + ExternalitiesT: SgxExternalitiesTrait + Encode, +{ + type Externalities = ExternalitiesT; + + fn execute_update( + &self, + shard: &ShardIdentifier, + update_function: F, + ) -> Result<(ResultT, H256)> + where + F: FnOnce(Self::Externalities) -> StdResult<(Self::Externalities, ResultT), ErrorT>, + ErrorT: Debug, + { + let (state_lock, state) = self.state_handler.load_for_mutation(&shard)?; + + let (new_state, result) = update_function(state).map_err(|e| { + Error::Other(format!("Failed to run update function on STF state: {:?}", e).into()) + })?; + + let new_state_hash = self + .state_handler + .write(new_state, state_lock, shard) + .map_err(|e| Error::StateHandler(e))?; + Ok((result, new_state_hash)) + } +} + +fn into_map( + storage_entries: Vec>>, +) -> HashMap, Option>> { + storage_entries.into_iter().map(|e| e.into_tuple()).collect() +} + +/// Returns current duration since unix epoch. +/// +/// TODO: Duplicated from sidechain/consensus/slots. Extract to a crate where it can be shared. +fn duration_now() -> Duration { + let now = SystemTime::now(); + now.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_else(|e| { + panic!("Current time {:?} is before unix epoch. Something is wrong: {:?}", now, e) + }) +} + +fn top_or_hash(tcs: TrustedCallSigned, direct: bool) -> TrustedOperationOrHash { + TrustedOperationOrHash::::Operation(tcs.into_trusted_operation(direct)) +} + +/// Execute a trusted getter on a state and return its value, if available. +/// +/// Also verifies the signature of the trusted getter and returns an error +/// if it's invalid. +fn get_stf_state( + trusted_getter_signed: &TrustedGetterSigned, + state: &mut E, +) -> Result>> { + debug!("verifying signature of TrustedGetterSigned"); + if let false = trusted_getter_signed.verify_signature() { + return Err(Error::OperationHasInvalidSignature) + } + + debug!("calling into STF to get state"); + Ok(Stf::get_state(state, trusted_getter_signed.clone().into())) +} diff --git a/core-primitives/stf-executor/src/lib.rs b/core-primitives/stf-executor/src/lib.rs new file mode 100644 index 0000000000..7917912ddd --- /dev/null +++ b/core-primitives/stf-executor/src/lib.rs @@ -0,0 +1,124 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +use ita_stf::hash::TrustedOperationOrHash; +use itp_types::{OpaqueCall, H256}; +use std::vec::Vec; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; +} + +pub mod error; +pub mod traits; + +#[cfg(feature = "sgx")] +pub mod executor; + +/// Execution status of a trusted operation +/// +/// In case of success, it includes the operation hash, as well as +/// any extrinsic callbacks (e.g. unshield extrinsics) that need to be executed on-chain +#[derive(Clone, Debug)] +pub enum ExecutionStatus { + Success(H256, Vec), + Failure, +} + +impl ExecutionStatus { + pub fn get_extrinsic_callbacks(&self) -> Vec { + match self { + ExecutionStatus::Success(_, opaque_calls) => opaque_calls.clone(), + _ => Vec::new(), + } + } + + pub fn get_executed_operation_hash(&self) -> Option { + match self { + ExecutionStatus::Success(operation_hash, _) => Some(*operation_hash), + _ => None, + } + } +} + +/// Information about an executed trusted operation +/// +/// +#[derive(Clone, Debug)] +pub struct ExecutedOperation { + pub status: ExecutionStatus, + pub trusted_operation_or_hash: TrustedOperationOrHash, +} + +impl ExecutedOperation { + /// constructor for a successfully executed trusted operation + pub fn success( + operation_hash: H256, + trusted_operation_or_hash: TrustedOperationOrHash, + extrinsic_call_backs: Vec, + ) -> Self { + ExecutedOperation { + status: ExecutionStatus::Success(operation_hash, extrinsic_call_backs), + trusted_operation_or_hash, + } + } + + /// constructor for a failed trusted operation execution + pub fn failed(trusted_operation_or_hash: TrustedOperationOrHash) -> Self { + ExecutedOperation { status: ExecutionStatus::Failure, trusted_operation_or_hash } + } + + /// returns if the executed operation was a success + pub fn is_success(&self) -> bool { + matches!(self.status, ExecutionStatus::Success(_, _)) + } +} + +/// Result of an execution on the STF +/// +/// Contains multiple executed operations +#[derive(Clone, Debug)] +pub struct BatchExecutionResult { + pub previous_state_hash: H256, + pub executed_operations: Vec, +} + +impl BatchExecutionResult { + pub fn get_extrinsic_callbacks(&self) -> Vec { + self.executed_operations + .iter() + .flat_map(|e| e.status.get_extrinsic_callbacks()) + .collect() + } + + pub fn get_executed_operation_hashes(&self) -> Vec { + self.executed_operations + .iter() + .flat_map(|ec| ec.status.get_executed_operation_hash()) + .collect() + } +} diff --git a/core-primitives/stf-executor/src/traits.rs b/core-primitives/stf-executor/src/traits.rs new file mode 100644 index 0000000000..f8ee15d539 --- /dev/null +++ b/core-primitives/stf-executor/src/traits.rs @@ -0,0 +1,113 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Result, BatchExecutionResult}; +use codec::Encode; +use ita_stf::{AccountId, ShardIdentifier, TrustedCallSigned, TrustedGetterSigned}; +use itp_types::{Amount, OpaqueCall, H256}; +use sgx_externalities::SgxExternalitiesTrait; +use sp_runtime::traits::Block as BlockT; +use std::{fmt::Debug, result::Result as StdResult, time::Duration, vec::Vec}; + +/// Post-processing steps after executing STF +pub enum StatePostProcessing { + None, + Prune, +} + +/// Execute shield funds on the STF +pub trait StfExecuteShieldFunds { + fn execute_shield_funds( + &self, + account: AccountId, + amount: Amount, + shard: &ShardIdentifier, + calls: &mut Vec, + ) -> Result; +} + +/// Execute a trusted call on the STF +pub trait StfExecuteTrustedCall { + fn execute_trusted_call( + &self, + calls: &mut Vec, + stf_call_signed: &TrustedCallSigned, + header: &PB::Header, + shard: &ShardIdentifier, + post_processing: StatePostProcessing, + ) -> Result> + where + PB: BlockT; +} + +/// Execute a batch of trusted calls within a given time window +/// +/// If the time expires, any remaining trusted calls will be ignored +/// All executed call hashes are returned. +pub trait StfExecuteTimedCallsBatch { + type Externalities: SgxExternalitiesTrait + Encode; + + fn execute_timed_calls_batch( + &self, + trusted_calls: &[TrustedCallSigned], + header: &PB::Header, + shard: &ShardIdentifier, + max_exec_duration: Duration, + prepare_state_function: F, + ) -> Result + where + PB: BlockT, + F: FnOnce(Self::Externalities) -> Self::Externalities; +} + +/// Execute a batch of trusted getter within a given time window +/// +/// +pub trait StfExecuteTimedGettersBatch { + type Externalities: SgxExternalitiesTrait + Encode; + + fn execute_timed_getters_batch( + &self, + trusted_getters: &[TrustedGetterSigned], + shard: &ShardIdentifier, + max_exec_duration: Duration, + getter_callback: F, + ) -> Result<()> + where + F: Fn(&TrustedGetterSigned, Result>>); +} + +/// Execute a generic function on the STF state +pub trait StfExecuteGenericUpdate { + type Externalities: SgxExternalitiesTrait + Encode; + + fn execute_update( + &self, + shard: &ShardIdentifier, + update_function: F, + ) -> Result<(ResultT, H256)> + where + F: FnOnce(Self::Externalities) -> StdResult<(Self::Externalities, ResultT), ErrorT>, + ErrorT: Debug; +} + +/// +pub trait StfUpdateState { + fn update_states(&self, header: &PB::Header) -> Result<()> + where + PB: BlockT; +} diff --git a/core-primitives/stf-state-handler/Cargo.toml b/core-primitives/stf-state-handler/Cargo.toml index 3ec5b454fd..4cec823429 100644 --- a/core-primitives/stf-state-handler/Cargo.toml +++ b/core-primitives/stf-state-handler/Cargo.toml @@ -14,6 +14,7 @@ std = [ "ita-stf/std", "itp-sgx-crypto/std", "itp-sgx-io/std", + "itp-types/std", "thiserror", ] sgx = [ diff --git a/core-primitives/stf-state-handler/src/global_file_state_handler.rs b/core-primitives/stf-state-handler/src/global_file_state_handler.rs index 8b7037452f..afb9dc0f4a 100644 --- a/core-primitives/stf-state-handler/src/global_file_state_handler.rs +++ b/core-primitives/stf-state-handler/src/global_file_state_handler.rs @@ -43,8 +43,9 @@ pub struct GlobalFileStateHandler; impl HandleState for GlobalFileStateHandler { type WriteLockPayload = (); + type StateT = StfState; - fn load_initialized(&self, shard: &ShardIdentifier) -> Result { + fn load_initialized(&self, shard: &ShardIdentifier) -> Result { let _state_read_lock = STF_STATE_LOCK.read().map_err(|_| Error::LockPoisoning)?; load_initialized_state(shard) } @@ -52,7 +53,7 @@ impl HandleState for GlobalFileStateHandler { fn load_for_mutation( &self, shard: &ShardIdentifier, - ) -> Result<(RwLockWriteGuard<'_, Self::WriteLockPayload>, StfState)> { + ) -> Result<(RwLockWriteGuard<'_, Self::WriteLockPayload>, Self::StateT)> { let state_write_lock = STF_STATE_LOCK.write().map_err(|_| Error::LockPoisoning)?; let loaded_state = load_initialized_state(shard)?; Ok((state_write_lock, loaded_state)) @@ -60,7 +61,7 @@ impl HandleState for GlobalFileStateHandler { fn write( &self, - state: StfState, + state: Self::StateT, _state_lock: RwLockWriteGuard<'_, Self::WriteLockPayload>, shard: &ShardIdentifier, ) -> Result { diff --git a/core-primitives/stf-state-handler/src/handle_state.rs b/core-primitives/stf-state-handler/src/handle_state.rs index b1d21a52d8..8f2d3a31a6 100644 --- a/core-primitives/stf-state-handler/src/handle_state.rs +++ b/core-primitives/stf-state-handler/src/handle_state.rs @@ -22,30 +22,30 @@ use std::sync::SgxRwLockWriteGuard as RwLockWriteGuard; use std::sync::RwLockWriteGuard; use crate::error::Result; -use ita_stf::State as StfState; use itp_types::{ShardIdentifier, H256}; /// Facade for handling STF state loading and storing (e.g. from file) pub trait HandleState { type WriteLockPayload; + type StateT; /// Load the state for a given shard /// /// Initializes the shard and state if necessary, so this is guaranteed to /// return a state - fn load_initialized(&self, shard: &ShardIdentifier) -> Result; + fn load_initialized(&self, shard: &ShardIdentifier) -> Result; fn load_for_mutation( &self, shard: &ShardIdentifier, - ) -> Result<(RwLockWriteGuard<'_, Self::WriteLockPayload>, StfState)>; + ) -> Result<(RwLockWriteGuard<'_, Self::WriteLockPayload>, Self::StateT)>; /// Writes the state (without the state diff) encrypted into the enclave /// /// Returns the hash of the saved state (independent of the diff!) fn write( &self, - state: StfState, + state: Self::StateT, state_lock: RwLockWriteGuard<'_, Self::WriteLockPayload>, shard: &ShardIdentifier, ) -> Result; diff --git a/core-primitives/stf-state-handler/src/lib.rs b/core-primitives/stf-state-handler/src/lib.rs index 0829a64775..8b44cf689b 100644 --- a/core-primitives/stf-state-handler/src/lib.rs +++ b/core-primitives/stf-state-handler/src/lib.rs @@ -31,14 +31,12 @@ pub mod sgx_reexport_prelude { } pub mod error; +pub mod handle_state; pub mod query_shard_state; #[cfg(feature = "sgx")] pub mod global_file_state_handler; -#[cfg(feature = "sgx")] -pub mod handle_state; - #[cfg(feature = "sgx")] pub use global_file_state_handler::GlobalFileStateHandler; diff --git a/core-primitives/test/src/mock/handle_state_mock.rs b/core-primitives/test/src/mock/handle_state_mock.rs index 7cffccd3a6..98342bc993 100644 --- a/core-primitives/test/src/mock/handle_state_mock.rs +++ b/core-primitives/test/src/mock/handle_state_mock.rs @@ -45,6 +45,7 @@ impl Default for HandleStateMock { impl HandleState for HandleStateMock { type WriteLockPayload = HashMap; + type StateT = StfState; fn load_initialized(&self, shard: &ShardIdentifier) -> Result { let maybe_state = self.state_map.read().unwrap().get(shard).map(|s| s.clone()); diff --git a/core-primitives/types/src/lib.rs b/core-primitives/types/src/lib.rs index 5af054fda1..b2063513d5 100644 --- a/core-primitives/types/src/lib.rs +++ b/core-primitives/types/src/lib.rs @@ -29,6 +29,7 @@ pub use substrate_api_client::{AccountData, AccountInfo}; pub type ShardIdentifier = H256; pub type BlockNumber = u32; +pub type Amount = u128; pub type Header = HeaderG; pub type Block = BlockG; pub type SignedBlock = SignedBlockG; @@ -64,7 +65,7 @@ pub type MrEnclave = [u8; 32]; pub type BlockHash = H256; pub type ConfirmCallFn = ([u8; 2], ShardIdentifier, H256, Vec); -pub type ShieldFundsFn = ([u8; 2], Vec, u128, ShardIdentifier); +pub type ShieldFundsFn = ([u8; 2], Vec, Amount, ShardIdentifier); pub type CallWorkerFn = ([u8; 2], Request); // Todo: move this improved enclave definition into a primitives crate in the pallet_teerex repo. diff --git a/enclave-runtime/Cargo.lock b/enclave-runtime/Cargo.lock index 9f329d7590..fc20e25d8c 100644 --- a/enclave-runtime/Cargo.lock +++ b/enclave-runtime/Cargo.lock @@ -477,6 +477,7 @@ dependencies = [ "itp-settings", "itp-sgx-crypto", "itp-sgx-io", + "itp-stf-executor", "itp-stf-state-handler", "itp-storage", "itp-storage-verifier", @@ -1263,6 +1264,25 @@ dependencies = [ "sgx_tstd", ] +[[package]] +name = "itp-stf-executor" +version = "0.8.0" +dependencies = [ + "ita-stf", + "itp-ocall-api", + "itp-stf-state-handler", + "itp-storage", + "itp-storage-verifier", + "itp-types", + "log 0.4.14 (git+https://github.com/mesalock-linux/log-sgx)", + "parity-scale-codec", + "sgx-externalities", + "sgx_tstd", + "sgx_types", + "sp-runtime", + "thiserror 1.0.9", +] + [[package]] name = "itp-stf-state-handler" version = "0.8.0" diff --git a/enclave-runtime/Cargo.toml b/enclave-runtime/Cargo.toml index 1041532b22..4dd0b96892 100644 --- a/enclave-runtime/Cargo.toml +++ b/enclave-runtime/Cargo.toml @@ -87,11 +87,12 @@ itp-settings = { path = "../core-primitives/settings" } itp-sgx-io = { path = "../core-primitives/sgx/io", default-features = false, features = ["sgx"] } itp-storage = { path = "../core-primitives/storage", default-features = false, features = ["sgx"] } itp-storage-verifier = { path = "../core-primitives/storage-verified", default-features = false } -itp-sgx-crypto= { path = "../core-primitives/sgx/crypto", default-features = false, features = ["sgx"] } +itp-sgx-crypto = { path = "../core-primitives/sgx/crypto", default-features = false, features = ["sgx"] } +itp-stf-executor = { path = "../core-primitives/stf-executor", default-features = false, features = ["sgx"] } itp-stf-state-handler = { path = "../core-primitives/stf-state-handler", default-features = false, features = ["sgx"] } itp-teerex-storage = { path = "../core-primitives/teerex-storage", default-features = false } itp-test = { path = "../core-primitives/test", default-features = false, optional = true } -itp-types = {path = "../core-primitives/types", default-features = false, features = ["sgx"] } +itp-types = { path = "../core-primitives/types", default-features = false, features = ["sgx"] } its-sidechain = { path = "../sidechain/sidechain-crate", default-features = false, features = ["sgx"] } # substrate deps diff --git a/enclave-runtime/src/error.rs b/enclave-runtime/src/error.rs index 21ca88dbd7..cd098b390a 100644 --- a/enclave-runtime/src/error.rs +++ b/enclave-runtime/src/error.rs @@ -33,6 +33,7 @@ pub enum Error { Consensus(its_sidechain::consensus_common::Error), Stf(String), StfStateHandler(itp_stf_state_handler::error::Error), + StfExecution(itp_stf_executor::error::Error), MutexAccess, Other(Box), } diff --git a/enclave-runtime/src/lib.rs b/enclave-runtime/src/lib.rs index f95ec58515..56bebf99d9 100644 --- a/enclave-runtime/src/lib.rs +++ b/enclave-runtime/src/lib.rs @@ -46,9 +46,8 @@ use crate::{ use base58::ToBase58; use codec::{alloc::string::String, Decode, Encode}; use ita_stf::{ - stf_sgx::{shards_key_hash, storage_hashes_to_update_per_shard}, - AccountId, Getter, ShardIdentifier, State as StfState, StatePayload, StateTypeDiff, Stf, - TrustedCall, TrustedCallSigned, TrustedGetterSigned, + hash::TrustedOperationOrHash, AccountId, Getter, ShardIdentifier, StatePayload, Stf, + TrustedCallSigned, TrustedGetterSigned, }; use itc_direct_rpc_server::{ create_determine_watch, rpc_connection_registry::ConnectionRegistry, @@ -71,11 +70,18 @@ use itp_sgx_crypto::{ }; use itp_sgx_io as io; use itp_sgx_io::SealedIO; +use itp_stf_executor::{ + executor::StfExecutor, + traits::{ + StatePostProcessing, StfExecuteGenericUpdate, StfExecuteShieldFunds, + StfExecuteTimedCallsBatch, StfExecuteTimedGettersBatch, StfExecuteTrustedCall, + StfUpdateState, + }, +}; use itp_stf_state_handler::{ handle_state::HandleState, query_shard_state::QueryShardState, GlobalFileStateHandler, }; -use itp_storage::{StorageEntryVerified, StorageProof}; -use itp_storage_verifier::GetStorageVerified; +use itp_storage::StorageProof; use itp_types::{Block, CallWorkerFn, Header, OpaqueCall, ShieldFundsFn, SignedBlock}; use its_sidechain::{ primitives::{ @@ -86,25 +92,22 @@ use its_sidechain::{ state::{LastBlockExt, SidechainDB, SidechainState, SidechainSystemExt}, top_pool_rpc_author::{ global_author_container::GlobalAuthorContainer, - hash::TrustedOperationOrHash, traits::{AuthorApi, GetAuthor, OnBlockCreated, SendState}, }, }; use lazy_static::lazy_static; use log::*; -use sgx_externalities::SgxExternalitiesTrait; +use sgx_externalities::{SgxExternalities, SgxExternalitiesTrait}; use sgx_types::sgx_status_t; use sp_core::{blake2_256, crypto::Pair, H256}; use sp_finality_grandpa::VersionedAuthorityList; use sp_runtime::{ generic::SignedBlock as SignedBlockG, - traits::{Block as BlockT, Header as HeaderT, UniqueSaturatedInto}, + traits::{Block as BlockT, Header as HeaderT}, MultiSignature, OpaqueExtrinsic, }; use std::{ - collections::HashMap, slice, - string::ToString, sync::{Arc, SgxRwLock}, time::Duration, vec::Vec, @@ -542,7 +545,8 @@ fn execute_top_pool_trusted_getters_on_all_shards() -> Result<()> { Error::MutexAccess })?; - let state_handler = GlobalFileStateHandler; + let state_handler = Arc::new(GlobalFileStateHandler); + let stf_executor = StfExecutor::new(Arc::new(OcallApi), state_handler.clone()); let shards = state_handler.list_shards()?; let mut remaining_shards = shards.len() as u32; @@ -564,8 +568,8 @@ fn execute_top_pool_trusted_getters_on_all_shards() -> Result<()> { match execute_top_pool_trusted_getters_on_shard( rpc_author.as_ref(), - &state_handler, - shard, + &stf_executor, + &shard, shard_exec_time, ) { Ok(()) => {}, @@ -615,6 +619,7 @@ where })?; let state_handler = Arc::new(GlobalFileStateHandler); + let stf_executor = Arc::new(StfExecutor::new(Arc::new(OcallApi), state_handler.clone())); let latest_onchain_header = validator.latest_finalized_header(validator.num_relays()).unwrap(); @@ -622,7 +627,7 @@ where { Some(slot) => { let shards = state_handler.list_shards()?; - let env = ProposerFactory::new(Arc::new(OcallApi), rpc_author, state_handler); + let env = ProposerFactory::new(rpc_author, stf_executor); exec_aura_on_slot::<_, _, SignedSidechainBlock, _, _, _>( slot, @@ -681,12 +686,13 @@ where let mut validator = LightClientSeal::::unseal()?; let mut nonce = NONCE.write().expect("Encountered poisoned NONCE lock"); + let stf_executor = StfExecutor::new(Arc::new(OcallApi), Arc::new(GlobalFileStateHandler)); sync_blocks_on_light_client( blocks_to_sync, &mut validator, &OcallApi, - &GlobalFileStateHandler, + &stf_executor, &mut *nonce, )?; @@ -696,11 +702,11 @@ where Ok(()) } -fn sync_blocks_on_light_client( +fn sync_blocks_on_light_client( blocks_to_sync: Vec>, validator: &mut V, on_chain_ocall_api: &OCallApi, - state_handler: &StateHandler, + stf_executor: &StfExecutor, nonce: &mut u32, ) -> Result<()> where @@ -708,9 +714,10 @@ where NumberFor: BlockNumberOps, V: Validator + LightClientState, OCallApi: EnclaveOnChainOCallApi + EnclaveAttestationOCallApi, - StateHandler: HandleState, + StfExecutor: StfUpdateState + StfExecuteTrustedCall + StfExecuteShieldFunds, { let mut calls = Vec::::new(); + let mrenclave: ShardIdentifier = on_chain_ocall_api.get_mrenclave_of_self()?.m.into(); debug!("Syncing light client!"); for signed_block in blocks_to_sync.into_iter() { @@ -727,17 +734,13 @@ where return Err(e.into()) } - if let Err(e) = update_states::( - signed_block.block.header().clone(), - on_chain_ocall_api, - state_handler, - ) { + if let Err(e) = stf_executor.update_states::(&signed_block.block.header()) { error!("Error performing state updates upon block import"); - return Err(e) + return Err(e.into()) } // execute indirect calls, incl. shielding and unshielding - match scan_block_for_relevant_xt(&signed_block.block, on_chain_ocall_api, state_handler) { + match scan_block_for_relevant_xt(&signed_block.block, stf_executor) { // push shield funds to opaque calls Ok(c) => calls.extend(c.into_iter()), Err(_) => error!("Error executing relevant extrinsics"), @@ -746,7 +749,6 @@ where // compose indirect block confirmation // should be changed to ParentchainBlockProcessed, see worker issue #457 let xt_block = [TEEREX_MODULE, BLOCK_CONFIRMED]; - let mrenclave: ShardIdentifier = on_chain_ocall_api.get_mrenclave_of_self()?.m.into(); let block_hash = signed_block.block.header().hash(); let prev_state_hash = signed_block.block.header().parent_hash(); calls.push(OpaqueCall::from_tuple(&( @@ -803,10 +805,10 @@ where /// /// Todo: This will probably be used again if we decide to make sidechain optional? #[allow(unused)] -fn execute_top_pool_trusted_calls_for_all_shards( - ocall_api: &OCallApi, +fn execute_top_pool_trusted_calls_for_all_shards( rpc_author: &RpcAuthor, state_handler: &StateHandler, + stf_executor: &StfExecutor, latest_onchain_header: &PB::Header, max_exec_duration: Duration, ) -> Result<(Vec, Vec)> @@ -814,10 +816,11 @@ where PB: BlockT, SB: SignedBlockT, SB::Block: SidechainBlockT, - OCallApi: EnclaveOnChainOCallApi + EnclaveAttestationOCallApi, RpcAuthor: AuthorApi + SendState + OnBlockCreated, - StateHandler: HandleState + QueryShardState, + StateHandler: QueryShardState, + StfExecutor: StfExecuteTimedCallsBatch + + StfExecuteGenericUpdate, { let shards = state_handler.list_shards()?; let mut calls: Vec = Vec::new(); @@ -838,10 +841,9 @@ where }, }; - match execute_top_pool_trusted_calls::( - ocall_api, + match execute_top_pool_trusted_calls::( rpc_author, - state_handler, + stf_executor, &latest_onchain_header, shard, shard_exec_time, @@ -869,10 +871,9 @@ where /// /// Todo: This function does too much, but it needs anyhow some refactoring here to make the code /// more readable. -fn execute_top_pool_trusted_calls( - on_chain_ocall: &OCallApi, +fn execute_top_pool_trusted_calls( rpc_author: &RpcAuthor, - state_handler: &StateHandler, + stf_executor: &StfExecutor, latest_onchain_header: &PB::Header, shard: H256, max_exec_duration: Duration, @@ -881,12 +882,10 @@ where PB: BlockT, SB: SignedBlockT, SB::Block: SidechainBlockT, - OCallApi: EnclaveOnChainOCallApi + EnclaveAttestationOCallApi, RpcAuthor: AuthorApi + OnBlockCreated, - StateHandler: HandleState, + StfExecutor: StfExecuteTimedCallsBatch + + StfExecuteGenericUpdate, { - let ends_at = duration_now() + max_exec_duration; - // retrieve trusted operations from pool let trusted_calls = rpc_author.get_pending_tops_separated(shard)?.0; @@ -905,67 +904,44 @@ where debug!("Got following trusted calls from pool: {:?}", trusted_calls); } - let mut calls = Vec::::new(); - let mut call_hashes = Vec::::new(); - - // load state before executing any calls - let (mut sidechain_db, state_lock) = state_handler - .load_for_mutation(&shard) - .map(|(l, s)| (SidechainDB::::new(s), l))?; - - let prev_state_hash = sidechain_db.state_hash(); - trace!("state apriori hash: {:?}", prev_state_hash); - - // update state needed for pallets - sidechain_db.set_block_number(&sidechain_db.get_block_number().map_or(1, |n| n + 1)); - sidechain_db.set_timestamp(&now_as_u64()); + let batch_execution_result = stf_executor.execute_timed_calls_batch::( + &trusted_calls, + latest_onchain_header, + &shard, + max_exec_duration, + |s| { + let mut sidechain_db = SidechainDB::::new(s); + sidechain_db.set_block_number(&sidechain_db.get_block_number().map_or(1, |n| n + 1)); + sidechain_db.set_timestamp(&now_as_u64()); + sidechain_db.ext + }, + )?; - // retrieve trusted operations from pool - let trusted_calls = rpc_author.get_pending_tops_separated(shard)?.0; + let mut extrinsic_callbacks = batch_execution_result.get_extrinsic_callbacks(); + let executed_operation_hashes = + batch_execution_result.get_executed_operation_hashes().iter().copied().collect(); - debug!("Got following trusted calls from pool: {:?}", trusted_calls); - // call execution - for trusted_call_signed in trusted_calls.into_iter() { - match handle_trusted_worker_call::( - &mut calls, - &mut sidechain_db.ext, - &trusted_call_signed, - latest_onchain_header, - shard, - on_chain_ocall, - ) { - Ok(hashes) => { - if let Some((_, op_hash)) = hashes { - call_hashes.push(op_hash) - } - rpc_author - .remove_top( - vec![top_or_hash(trusted_call_signed, true)], - shard, - hashes.is_some(), - ) - .unwrap(); - }, - Err(e) => - error!("Error performing worker call (will not push top hash): Error: {:?}", e), - }; - // Check time - if ends_at < duration_now() { - break - } + for executed_operation in batch_execution_result.executed_operations.iter() { + rpc_author + .remove_top( + vec![executed_operation.trusted_operation_or_hash.clone()], + shard, + executed_operation.is_success(), + ) + .map_err(|e| Error::Other(e.into()))?; } // Todo: this function should return here. Composing the block should be done by the caller. // create new block (side-chain) let block = match compose_block_and_confirmation::( latest_onchain_header, - call_hashes, + executed_operation_hashes, shard, - prev_state_hash, - &mut sidechain_db, + batch_execution_result.previous_state_hash, + stf_executor, ) { Ok((block_confirm, signed_block)) => { - calls.push(block_confirm); + extrinsic_callbacks.push(block_confirm); // Notify watching clients of InSidechainBlock let block = signed_block.block(); @@ -979,146 +955,125 @@ where }, }; - // save updated state after call executions - let _hash = state_handler.write(sidechain_db.ext, state_lock, &shard)?; - if block.is_none() { info!("[Enclave] did not produce a block for shard {:?}", shard); } - Ok((calls, block)) + Ok((extrinsic_callbacks, block)) } /// Execute pending trusted getters for the `shard` until `max_exec_duration` is reached. -fn execute_top_pool_trusted_getters_on_shard( +fn execute_top_pool_trusted_getters_on_shard( rpc_author: &RpcAuthor, - state_handler: &StateHandler, - shard: H256, + stf_executor: &StfExecutor, + shard: &ShardIdentifier, max_exec_duration: Duration, ) -> Result<()> where RpcAuthor: AuthorApi + SendState, - StateHandler: HandleState, + StfExecutor: StfExecuteTimedGettersBatch, { - let ends_at = duration_now() + max_exec_duration; - // retrieve trusted operations from pool - let trusted_getters = rpc_author.get_pending_tops_separated(shard)?.1; - - // return early if we have no trusted getters, so we don't decrypt the state unnecessarily - if trusted_getters.is_empty() { - return Ok(()) - } - - // load state once per shard - let mut state = state_handler - .load_initialized(&shard) - .map_err(|e| Error::Stf(format!("Error loading shard {:?}: Error: {:?}", shard, e)))?; - trace!("Successfully loaded stf state"); - - for trusted_getter_signed in trusted_getters.into_iter() { - let hash_of_getter = rpc_author.hash_of(&trusted_getter_signed.clone().into()); - - // get state - match get_stf_state(trusted_getter_signed, &mut state) { - Ok(r) => { - // let client know of current state - trace!("Updating client"); - match rpc_author.send_state(hash_of_getter, r.encode()) { - Ok(_) => trace!("Successfully updated client"), - Err(e) => error!("Could not send state to client {:?}", e), + let trusted_getters = rpc_author.get_pending_tops_separated(*shard)?.1; + + type StfExecutorResult = itp_stf_executor::error::Result; + + stf_executor + .execute_timed_getters_batch( + &trusted_getters, + &shard, + max_exec_duration, + |trusted_getter_signed: &TrustedGetterSigned, + state_result: StfExecutorResult>>| { + let hash_of_getter = rpc_author.hash_of(&trusted_getter_signed.clone().into()); + + match state_result { + Ok(r) => { + // let client know of current state + trace!("Updating client"); + match rpc_author.send_state(hash_of_getter, r.encode()) { + Ok(_) => trace!("Successfully updated client"), + Err(e) => error!("Could not send state to client {:?}", e), + } + }, + Err(e) => { + error!("failed to get stf state, skipping trusted getter ({:?})", e); + }, + }; + + // remove getter from pool + if let Err(e) = rpc_author.remove_top( + vec![TrustedOperationOrHash::Hash(hash_of_getter)], + *shard, + false, + ) { + error!("Error removing trusted operation from top pool: Error: {:?}", e); } }, - Err(e) => { - error!("failed to get stf state, skipping trusted getter ({:?})", e); - }, - }; - - // remove getter from pool - if let Err(e) = - rpc_author.remove_top(vec![TrustedOperationOrHash::Hash(hash_of_getter)], shard, false) - { - error!("Error removing trusted operation from top pool: Error: {:?}", e); - } - - // Check time - if ends_at < duration_now() { - return Ok(()) - } - } - - Ok(()) -} - -/// Execute a trusted getter on a state and return its value, if available. -/// -/// Also verifies the signature of the trusted getter and returns an error -/// if it's invalid. -fn get_stf_state( - trusted_getter_signed: TrustedGetterSigned, - state: &mut StfState, -) -> Result>> { - debug!("verifying signature of TrustedGetterSigned"); - if let false = trusted_getter_signed.verify_signature() { - return Err(Error::Stf("bad signature".to_string())) - } - - debug!("calling into STF to get state"); - Ok(Stf::get_state(state, trusted_getter_signed.into())) + ) + .map_err(Error::StfExecution) } /// Composes a sidechain block of a shard -fn compose_block_and_confirmation( +fn compose_block_and_confirmation( latest_onchain_header: &PB::Header, top_call_hashes: Vec, shard: ShardIdentifier, state_hash_apriori: H256, - db: &mut SidechainDB, + stf_executor: &StfExecutor, ) -> Result<(OpaqueCall, SB)> where PB: BlockT, SB: SignedBlockT, SB::Block: SidechainBlockT, - SidechainDB: LastBlockExt + SidechainState, + StfExecutor: StfExecuteGenericUpdate, { let signer_pair = Ed25519Seal::unseal()?; - let state_hash_new = db.state_hash(); - let (block_number, parent_hash) = match db.get_last_block() { - Some(block) => (block.block_number() + 1, block.hash()), - None => { - info!("Seems to be first sidechain block."); - (1, Default::default()) - }, - }; + let author_public = signer_pair.public(); + let (block, state_hash_new) = stf_executor.execute_update(&shard, |state| { + let mut db = SidechainDB::::new(state); + let state_hash_new = db.state_hash(); - if block_number != db.get_block_number().unwrap_or(0) { - return Err(Error::Other("[Sidechain] BlockNumber is not LastBlock's Number + 1".into())) - } + let (block_number, parent_hash) = match db.get_last_block() { + Some(block) => (block.block_number() + 1, block.hash()), + None => { + info!("Seems to be first sidechain block."); + (1, Default::default()) + }, + }; - // create encrypted payload - let mut payload: Vec = - StatePayload::new(state_hash_apriori, state_hash_new, db.ext().state_diff().clone()) - .encode(); - AesSeal::unseal().map(|key| key.encrypt(&mut payload))??; + if block_number != db.get_block_number().unwrap_or(0) { + return Err(Error::Other("[Sidechain] BlockNumber is not LastBlock's Number + 1".into())) + } - let block = SB::Block::new( - signer_pair.public(), - block_number, - parent_hash, - latest_onchain_header.hash(), - shard, - top_call_hashes, - payload, - now_as_u64(), - ); + // create encrypted payload + let mut payload: Vec = + StatePayload::new(state_hash_apriori, state_hash_new, db.ext().state_diff().clone()) + .encode(); + AesSeal::unseal().map(|key| key.encrypt(&mut payload))??; + + let block = SB::Block::new( + author_public, + block_number, + parent_hash, + latest_onchain_header.hash(), + shard, + top_call_hashes, + payload, + now_as_u64(), + ); + + db.set_last_block(&block); + + // state diff has been written to block, clean it for the next block. + db.ext_mut().prune_state_diff(); + + Ok((db.ext, block)) + })?; let block_hash = block.hash(); debug!("Block hash {}", block_hash); - db.set_last_block(&block); - - // state diff has been written to block, clean it for the next block. - db.ext_mut().prune_state_diff(); let xt_block = [TEEREX_MODULE, BLOCK_CONFIRMED]; let opaque_call = @@ -1126,77 +1081,16 @@ where Ok((opaque_call, block.sign_block(&signer_pair))) } -fn update_states( - header: PB::Header, - on_chain_ocall_api: &O, - state_handler: &StateHandler, -) -> Result<()> -where - PB: BlockT, - O: EnclaveOnChainOCallApi, - StateHandler: HandleState, -{ - debug!("Update STF storage upon block import!"); - let storage_hashes = Stf::storage_hashes_to_update_on_block(); - - if storage_hashes.is_empty() { - return Ok(()) - } - - // global requests they are the same for every shard - let state_diff_update: StateTypeDiff = on_chain_ocall_api - .get_multiple_storages_verified(storage_hashes, &header) - .map(into_map)? - .into(); - - // look for new shards an initialize them - if let Some(maybe_shards) = state_diff_update.get(&shards_key_hash()) { - match maybe_shards { - Some(shards) => { - let shards: Vec = Decode::decode(&mut shards.as_slice()) - .sgx_error_with_log("error decoding shards")?; - - for shard_id in shards { - let (state_lock, mut state) = state_handler.load_for_mutation(&shard_id)?; - trace!("Successfully loaded state, updating states ..."); - - // per shard (cid) requests - let per_shard_hashes = storage_hashes_to_update_per_shard(&shard_id); - let per_shard_update = on_chain_ocall_api - .get_multiple_storages_verified(per_shard_hashes, &header) - .map(into_map)?; - - Stf::update_storage(&mut state, &per_shard_update.into()); - Stf::update_storage(&mut state, &state_diff_update); - - // block number is purged from the substrate state so it can't be read like other storage values - // The number conversion is a bit unfortunate, but I wanted to prevent making the stf generic for now - Stf::update_layer_one_block_number( - &mut state, - (*header.number()).unique_saturated_into(), - ); - - state_handler.write(state, state_lock, &shard_id)?; - } - }, - None => debug!("No shards are on the chain yet"), - }; - }; - Ok(()) -} - /// Scans blocks for extrinsics that ask the enclave to execute some actions. /// Executes indirect invocation calls, as well as shielding and unshielding calls /// Returns all unshielding call confirmations as opaque calls -fn scan_block_for_relevant_xt( +fn scan_block_for_relevant_xt( block: &PB, - on_chain_ocall: &O, - state_handler: &StateHandler, + stf_executor: &StfExecutor, ) -> Result> where PB: BlockT, - O: EnclaveOnChainOCallApi + EnclaveAttestationOCallApi, - StateHandler: HandleState, + StfExecutor: StfUpdateState + StfExecuteTrustedCall + StfExecuteShieldFunds, { debug!("Scanning block {:?} for relevant xt", block.header().number()); let mut opaque_calls = Vec::::new(); @@ -1207,7 +1101,7 @@ where { // confirm call decodes successfully as well if xt.function.0 == [TEEREX_MODULE, SHIELD_FUNDS] { - if let Err(e) = handle_shield_funds_xt(&mut opaque_calls, xt, state_handler) { + if let Err(e) = handle_shield_funds_xt(&mut opaque_calls, xt, stf_executor) { error!("Error performing shield funds. Error: {:?}", e); } } @@ -1219,26 +1113,15 @@ where { if xt.function.0 == [TEEREX_MODULE, CALL_WORKER] { if let Ok((decrypted_trusted_call, shard)) = decrypt_unchecked_extrinsic(xt) { - // load state before executing any calls - let (state_lock, mut state) = state_handler.load_for_mutation(&shard)?; - // call execution - trace!("Handling trusted worker call of state: {:?}", state); - if let Err(e) = handle_trusted_worker_call::( - &mut opaque_calls, // necessary for unshielding - &mut state, + if let Err(e) = stf_executor.execute_trusted_call::( + &mut opaque_calls, &decrypted_trusted_call, - block.header(), - shard, - on_chain_ocall, + &block.header(), + &shard, + StatePostProcessing::Prune, // we only want to store the state diff for direct stuff. ) { - error!("Error performing worker call: Error: {:?}", e); + error!("Error executing trusted call: Error: {:?}", e); } - // save updated state - - // we only want to store the state diff for direct stuff. - state.prune_state_diff(); - trace!("Updating state of shard {:?}", shard); - state_handler.write(state, state_lock, &shard)?; } } } @@ -1247,44 +1130,33 @@ where Ok(opaque_calls) } -fn handle_shield_funds_xt( +fn handle_shield_funds_xt( calls: &mut Vec, xt: UncheckedExtrinsicV4, - state_handler: &StateHandler, + stf_executor: &StfExecutor, ) -> Result<()> where - StateHandler: HandleState, + StfExecutor: StfUpdateState + StfExecuteTrustedCall + StfExecuteShieldFunds, { let (call, account_encrypted, amount, shard) = xt.function.clone(); info!("Found ShieldFunds extrinsic in block: \nCall: {:?} \nAccount Encrypted {:?} \nAmount: {} \nShard: {}", call, account_encrypted, amount, shard.encode().to_base58(), ); - let (state_lock, mut state) = state_handler.load_for_mutation(&shard)?; - debug!("decrypt the call"); //let account_vec = Rsa3072KeyPair::decrypt(&account_encrypted)?; let account_vec = Rsa3072Seal::unseal().map(|key| key.decrypt(&account_encrypted))??; let account = AccountId::decode(&mut account_vec.as_slice()) .sgx_error_with_log("[ShieldFunds] Could not decode account")?; - let root = Stf::get_root(&mut state); - let nonce = Stf::account_nonce(&mut state, &root); - - if let Err(e) = Stf::execute( - &mut state, - TrustedCallSigned::new( - TrustedCall::balance_shield(root, account, amount), - nonce, - Default::default(), //don't care about signature here - ), - calls, - ) { - error!("Error performing Stf::execute. Error: {:?}", e); - return Ok(()) - } - let state_hash = state_handler.write(state, state_lock, &shard)?; + let state_hash = match stf_executor.execute_shield_funds(account, amount, &shard, calls) { + Ok(h) => h, + Err(e) => { + error!("Error executing shield funds. Error: {:?}", e); + return Ok(()) + }, + }; let xt_call = [TEEREX_MODULE, CALL_CONFIRMED]; let xt_hash = blake2_256(&xt.encode()); @@ -1312,59 +1184,3 @@ fn decrypt_unchecked_extrinsic( Ok(TrustedCallSigned::decode(&mut request_vec.as_slice()).map(|call| (call, shard))?) } - -fn handle_trusted_worker_call( - calls: &mut Vec, - state: &mut StfState, - stf_call_signed: &TrustedCallSigned, - header: &PB::Header, - shard: ShardIdentifier, - on_chain_ocall_api: &O, -) -> Result> -where - PB: BlockT, - O: EnclaveOnChainOCallApi + EnclaveAttestationOCallApi, -{ - debug!("query mrenclave of self"); - let mrenclave = on_chain_ocall_api.get_mrenclave_of_self()?; - debug!("MRENCLAVE of self is {}", mrenclave.m.to_base58()); - - if let false = stf_call_signed.verify_signature(&mrenclave.m, &shard) { - error!("TrustedCallSigned: bad signature"); - // do not panic here or users will be able to shoot workers dead by supplying a bad signature - return Ok(None) - } - - // Necessary because light client sync may not be up to date - // see issue #208 - debug!("Update STF storage!"); - let storage_hashes = Stf::get_storage_hashes_to_update(&stf_call_signed); - let update_map = on_chain_ocall_api - .get_multiple_storages_verified(storage_hashes, header) - .map(into_map)?; - Stf::update_storage(state, &update_map.into()); - - debug!("execute STF"); - if let Err(e) = Stf::execute(state, stf_call_signed.clone(), calls) { - error!("Error performing Stf::execute. Error: {:?}", e); - return Ok(None) - } - - let call_hash = blake2_256(&stf_call_signed.encode()); - let operation = stf_call_signed.clone().into_trusted_operation(true); - let operation_hash = blake2_256(&operation.encode()); - debug!("Operation hash {:?}", operation_hash); - debug!("Call hash {:?}", call_hash); - - Ok(Some((H256::from(call_hash), H256::from(operation_hash)))) -} - -fn into_map( - storage_entries: Vec>>, -) -> HashMap, Option>> { - storage_entries.into_iter().map(|e| e.into_tuple()).collect() -} - -fn top_or_hash(tcs: TrustedCallSigned, direct: bool) -> TrustedOperationOrHash { - TrustedOperationOrHash::::Operation(tcs.into_trusted_operation(direct)) -} diff --git a/enclave-runtime/src/sidechain_impl.rs b/enclave-runtime/src/sidechain_impl.rs index 961e341a5a..0b0246d99d 100644 --- a/enclave-runtime/src/sidechain_impl.rs +++ b/enclave-runtime/src/sidechain_impl.rs @@ -13,6 +13,7 @@ use itp_ocall_api::{EnclaveAttestationOCallApi, EnclaveOnChainOCallApi}; use itp_settings::sidechain::SLOT_DURATION; use itp_sgx_crypto::{Aes, AesSeal}; use itp_sgx_io::SealedIO; +use itp_stf_executor::traits::{StfExecuteGenericUpdate, StfExecuteTimedCallsBatch}; use itp_stf_state_handler::handle_state::HandleState; use itp_storage_verifier::GetStorageVerified; use its_sidechain::{ @@ -32,10 +33,9 @@ use sp_runtime::{traits::Block, MultiSignature}; use std::{marker::PhantomData, string::ToString, sync::Arc, vec::Vec}; ///! `SlotProposer` instance that has access to everything needed to propose a sidechain block -pub struct SlotProposer { - ocall_api: Arc, +pub struct SlotProposer { author: Arc, - state_handler: Arc, + stf_executor: Arc, parentchain_header: PB::Header, shard: ShardIdentifierFor, _phantom: PhantomData, @@ -43,40 +43,36 @@ pub struct SlotProposer { - ocall_api: Arc, +pub struct ProposerFactory { author: Arc, - state_handler: Arc, + stf_executor: Arc, _phantom: PhantomData, } -impl - ProposerFactory -{ - pub fn new( - ocall_api: Arc, - author: Arc, - state_handler: Arc, - ) -> Self { - Self { ocall_api, author, state_handler, _phantom: Default::default() } +impl ProposerFactory { + pub fn new(author: Arc, stf_executor: Arc) -> Self { + Self { author, stf_executor, _phantom: Default::default() } } } -impl, SB, OcallApi, Author, StateHandler> Environment - for ProposerFactory +impl, SB, Author, StfExecutor> Environment + for ProposerFactory where NumberFor: BlockNumberOps, SB: SignedBlock + 'static, SB::Block: SidechainBlockT, - OcallApi: EnclaveOnChainOCallApi + EnclaveAttestationOCallApi + GetStorageVerified + 'static, Author: AuthorApi + SendState + OnBlockCreated + Send + Sync, - StateHandler: HandleState + Send + Sync + 'static, + StfExecutor: StfExecuteTimedCallsBatch + + StfExecuteGenericUpdate + + Send + + Sync + + 'static, { - type Proposer = SlotProposer; + type Proposer = SlotProposer; type Error = ConsensusError; fn init( @@ -85,9 +81,8 @@ where shard: ShardIdentifierFor, ) -> Result { Ok(SlotProposer { - ocall_api: self.ocall_api.clone(), author: self.author.clone(), - state_handler: self.state_handler.clone(), + stf_executor: self.stf_executor.clone(), parentchain_header: parent_header, shard, _phantom: PhantomData, @@ -95,23 +90,24 @@ where } } -impl Proposer - for SlotProposer +impl Proposer for SlotProposer where PB: Block, NumberFor: BlockNumberOps, SB: SignedBlock, SB::Block: SidechainBlockT, - OcallApi: EnclaveOnChainOCallApi + EnclaveAttestationOCallApi, Author: AuthorApi + SendState + OnBlockCreated, - StateHandler: HandleState + Send + Sync + 'static, + StfExecutor: StfExecuteTimedCallsBatch + + StfExecuteGenericUpdate + + Send + + Sync + + 'static, { fn propose(&self, max_duration: Duration) -> Result, ConsensusError> { - let (calls, blocks) = execute_top_pool_trusted_calls::( - self.ocall_api.as_ref(), + let (calls, blocks) = execute_top_pool_trusted_calls::( self.author.as_ref(), - self.state_handler.as_ref(), + self.stf_executor.as_ref(), &self.parentchain_header, self.shard, max_duration, @@ -217,7 +213,7 @@ where SB: SignedBlock + 'static, SB::Block: SidechainBlockT, O: ValidateerFetch + GetStorageVerified + Send + Sync, - StateHandler: HandleState, + StateHandler: HandleState, { type Verifier = AuraVerifier, O>; type SidechainState = SidechainDB; diff --git a/enclave-runtime/src/tests.rs b/enclave-runtime/src/tests.rs index 255e3050dd..7f2c463fd0 100644 --- a/enclave-runtime/src/tests.rs +++ b/enclave-runtime/src/tests.rs @@ -34,6 +34,7 @@ use itp_settings::{ }; use itp_sgx_crypto::{AesSeal, StateCrypto}; use itp_sgx_io::SealedIO; +use itp_stf_executor::executor::StfExecutor; use itp_stf_state_handler::{handle_state::HandleState, query_shard_state::QueryShardState}; use itp_test::mock::{ handle_state_mock, handle_state_mock::HandleStateMock, @@ -56,7 +57,7 @@ use its_sidechain::{ }, }; use log::*; -use sgx_externalities::SgxExternalitiesTrait; +use sgx_externalities::{SgxExternalities, SgxExternalitiesTrait}; use sgx_tunittest::*; use sgx_types::size_t; use sp_core::{crypto::Pair, ed25519 as spEd25519, hashing::blake2_256, H256}; @@ -118,11 +119,16 @@ pub extern "C" fn test_main_entrance() -> size_t { fn test_compose_block_and_confirmation() { // given - let (_, state, shard, _, _, _) = test_setup(); + let (_, _, shard, _, _, state_handler) = test_setup(); + let stf_executor = StfExecutor::new(Arc::new(OcallApi), state_handler.clone()); let signed_top_hashes: Vec = vec![[94; 32].into(), [1; 32].into()].to_vec(); - let mut db = SidechainDB::new(state); + + let (lock, state) = state_handler.load_for_mutation(&shard).unwrap(); + let mut db = SidechainDB::::new(state); db.set_block_number(&1); + let previous_state_hash = db.state_hash(); + state_handler.write(db.ext, lock, &shard).unwrap(); // when let (opaque_call, signed_block) = @@ -130,8 +136,8 @@ fn test_compose_block_and_confirmation() { &latest_parentchain_header(), signed_top_hashes, shard, - db.state_hash(), - &mut db, + previous_state_hash, + &stf_executor, ) .unwrap(); @@ -215,6 +221,7 @@ fn test_differentiate_getter_and_call_works() { fn test_create_block_and_confirmation_works() { // given let (rpc_author, _, shard, mrenclave, shielding_key, state_handler) = test_setup(); + let stf_executor = StfExecutor::new(Arc::new(OcallApi), state_handler.clone()); let sender = funded_pair(); let receiver = unfunded_public(); @@ -231,9 +238,9 @@ fn test_create_block_and_confirmation_works() { // when let (confirm_calls, signed_blocks) = crate::execute_top_pool_trusted_calls_for_all_shards::( - &OcallApi, &rpc_author, state_handler.as_ref(), + &stf_executor, &latest_parentchain_header(), MAX_TRUSTED_OPS_EXEC_DURATION, ) @@ -264,6 +271,7 @@ fn test_create_block_and_confirmation_works() { fn test_create_state_diff() { // given let (rpc_author, _, shard, mrenclave, shielding_key, state_handler) = test_setup(); + let stf_executor = StfExecutor::new(Arc::new(OcallApi), state_handler.clone()); let sender = funded_pair(); let receiver = unfunded_public(); @@ -280,9 +288,9 @@ fn test_create_state_diff() { // when let (_, signed_blocks) = crate::execute_top_pool_trusted_calls_for_all_shards::( - &OcallApi, &rpc_author, state_handler.as_ref(), + &stf_executor, &latest_parentchain_header(), MAX_TRUSTED_OPS_EXEC_DURATION, ) @@ -310,6 +318,7 @@ fn test_create_state_diff() { fn test_executing_call_updates_account_nonce() { // given let (rpc_author, _, shard, mrenclave, shielding_key, state_handler) = test_setup(); + let stf_executor = StfExecutor::new(Arc::new(OcallApi), state_handler.clone()); let sender = funded_pair(); let receiver = unfunded_public(); @@ -322,9 +331,9 @@ fn test_executing_call_updates_account_nonce() { // when let (_, _) = crate::execute_top_pool_trusted_calls_for_all_shards::( - &OcallApi, &rpc_author, state_handler.as_ref(), + &stf_executor, &latest_parentchain_header(), MAX_TRUSTED_OPS_EXEC_DURATION, ) @@ -339,6 +348,7 @@ fn test_executing_call_updates_account_nonce() { fn test_invalid_nonce_call_is_not_executed() { // given let (rpc_author, _, shard, mrenclave, shielding_key, state_handler) = test_setup(); + let stf_executor = StfExecutor::new(Arc::new(OcallApi), state_handler.clone()); // create accounts let sender = funded_pair(); @@ -352,9 +362,9 @@ fn test_invalid_nonce_call_is_not_executed() { // when let (_, _) = crate::execute_top_pool_trusted_calls_for_all_shards::( - &OcallApi, &rpc_author, state_handler.as_ref(), + &stf_executor, &latest_parentchain_header(), MAX_TRUSTED_OPS_EXEC_DURATION, ) @@ -372,6 +382,7 @@ fn test_invalid_nonce_call_is_not_executed() { fn test_non_root_shielding_call_is_not_executed() { // given let (rpc_author, mut state, shard, mrenclave, shielding_key, state_handler) = test_setup(); + let stf_executor = StfExecutor::new(Arc::new(OcallApi), state_handler.clone()); let sender = funded_pair(); let sender_acc = sender.public().into(); @@ -386,9 +397,9 @@ fn test_non_root_shielding_call_is_not_executed() { // when let (_, _) = crate::execute_top_pool_trusted_calls_for_all_shards::( - &OcallApi, &rpc_author, state_handler.as_ref(), + &stf_executor, &latest_parentchain_header(), MAX_TRUSTED_OPS_EXEC_DURATION, ) @@ -423,7 +434,9 @@ fn get_current_shard_index( } /// returns an empty `State` with the corresponding `ShardIdentifier` -fn init_state(state_handler: &S) -> (State, ShardIdentifier) { +fn init_state>( + state_handler: &S, +) -> (State, ShardIdentifier) { let shard = ShardIdentifier::default(); let (lock, _) = state_handler.load_for_mutation(&shard).unwrap(); diff --git a/sidechain/state/src/lib.rs b/sidechain/state/src/lib.rs index 4e6b809dd9..40f6ef7f5e 100644 --- a/sidechain/state/src/lib.rs +++ b/sidechain/state/src/lib.rs @@ -49,7 +49,7 @@ use std::marker::PhantomData; /// Sidechain DB /// /// Todo: In the course of refactoring the STF (#269), verify if this struct is even needed. -/// It might be that we could implement everithing directly on `[SgxExternalities]`. +/// It might be that we could implement everything directly on `[SgxExternalities]`. #[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)] pub struct SidechainDB { /// Externalities diff --git a/sidechain/top-pool-rpc-author/src/author.rs b/sidechain/top-pool-rpc-author/src/author.rs index 1a0288e11f..9fb10a4d59 100644 --- a/sidechain/top-pool-rpc-author/src/author.rs +++ b/sidechain/top-pool-rpc-author/src/author.rs @@ -21,12 +21,11 @@ use crate::sgx_reexport_prelude::*; use crate::{ client_error::Error as ClientError, error::{Error as StateRpcError, Result}, - hash, top_filter::{AllowAllTopsFilter, Filter}, traits::{AuthorApi, OnBlockCreated, SendState}, }; use codec::{Decode, Encode}; -use ita_stf::{Getter, TrustedCallSigned, TrustedGetterSigned, TrustedOperation}; +use ita_stf::{hash, Getter, TrustedCallSigned, TrustedGetterSigned, TrustedOperation}; use itp_sgx_crypto::ShieldingCrypto; use itp_stf_state_handler::query_shard_state::QueryShardState; use itp_types::{BlockHash as SidechainBlockHash, ShardIdentifier}; diff --git a/sidechain/top-pool-rpc-author/src/lib.rs b/sidechain/top-pool-rpc-author/src/lib.rs index 246b0416d4..758291618c 100644 --- a/sidechain/top-pool-rpc-author/src/lib.rs +++ b/sidechain/top-pool-rpc-author/src/lib.rs @@ -37,7 +37,6 @@ pub mod author; pub mod author_container; pub mod client_error; pub mod error; -pub mod hash; pub mod pool_types; pub mod top_filter; pub mod traits; diff --git a/sidechain/top-pool-rpc-author/src/traits.rs b/sidechain/top-pool-rpc-author/src/traits.rs index 285d380a92..4546840358 100644 --- a/sidechain/top-pool-rpc-author/src/traits.rs +++ b/sidechain/top-pool-rpc-author/src/traits.rs @@ -18,8 +18,8 @@ #[cfg(all(not(feature = "std"), feature = "sgx"))] use crate::sgx_reexport_prelude::*; -use crate::{error::Result, hash}; -use ita_stf::{TrustedCallSigned, TrustedGetterSigned, TrustedOperation}; +use crate::error::Result; +use ita_stf::{hash, TrustedCallSigned, TrustedGetterSigned, TrustedOperation}; use itp_types::{BlockHash as SidechainBlockHash, ShardIdentifier, H256}; use its_top_pool::primitives::PoolFuture; use jsonrpc_core::Error as RpcError;