diff --git a/Cargo.lock b/Cargo.lock index 51e4866c37..6ead53b387 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2421,6 +2421,7 @@ dependencies = [ "finality-grandpa", "frame-system", "hash-db", + "itp-ocall-api", "itp-settings", "itp-sgx-io", "itp-storage", @@ -2571,6 +2572,35 @@ dependencies = [ "sgx_types", ] +[[package]] +name = "itp-extrinsics-factory" +version = "0.8.0" +dependencies = [ + "itp-nonce-cache", + "itp-settings", + "itp-types", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-scale-codec", + "sgx_tstd", + "sgx_types", + "sp-core", + "sp-runtime", + "substrate-api-client", + "thiserror 1.0.29", + "thiserror 1.0.9", +] + +[[package]] +name = "itp-nonce-cache" +version = "0.8.0" +dependencies = [ + "lazy_static", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "sgx_tstd", + "thiserror 1.0.29", + "thiserror 1.0.9", +] + [[package]] name = "itp-ocall-api" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 849710d78c..23adad12f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,41 +1,41 @@ [workspace] resolver = "2" members = [ + "app-libs/stf", "cli", - "core/light-client", "core/direct-rpc-server", - "core/tls-websocket-server", + "core/light-client", "core/rest-client", + "core/rpc-client", + "core/rpc-server", + "core/tls-websocket-server", "core-primitives/api-client-extensions", "core-primitives/enclave-api", "core-primitives/enclave-api/ffi", - "core-primitives/types", + "core-primitives/extrinsics-factory", + "core-primitives/nonce-cache", "core-primitives/ocall-api", - "core-primitives/storage-verified", - "core-primitives/teerex-storage", "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/storage-verified", + "core-primitives/teerex-storage", "core-primitives/test", "core-primitives/types", - "sidechain/sidechain-crate", - "sidechain/validateer-fetch", - "sidechain/primitives", + "service", "sidechain/consensus/aura", "sidechain/consensus/common", "sidechain/consensus/slots", + "sidechain/primitives", "sidechain/rpc-handler", + "sidechain/sidechain-crate", "sidechain/state", "sidechain/top-pool", "sidechain/top-pool-rpc-author", - "app-libs/stf", - "service", - "core/rpc-client", - "core/rpc-server", - # "enclave", + "sidechain/validateer-fetch", ] #[patch."https://github.com/integritee-network/pallet-teerex.git"] diff --git a/core-primitives/extrinsics-factory/Cargo.toml b/core-primitives/extrinsics-factory/Cargo.toml new file mode 100644 index 0000000000..7f021ac615 --- /dev/null +++ b/core-primitives/extrinsics-factory/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "itp-extrinsics-factory" +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 = [ + "itp-nonce-cache/std", + "itp-types/std", + "log/std", + "substrate-api-client/std", + "thiserror", +] +sgx = [ + "itp-nonce-cache/sgx", + "sgx_tstd", + "thiserror_sgx", +] + +[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 } +substrate-api-client = { default-features = false, git = "https://github.com/scs/substrate-api-client", branch = "master" } + +# local dependencies +itp-nonce-cache = { path = "../nonce-cache", default-features = false } +itp-settings = { path = "../settings", 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-core = { version = "4.0.0-dev", default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "master" } +sp-runtime = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "master"} \ No newline at end of file diff --git a/core-primitives/extrinsics-factory/src/error.rs b/core-primitives/extrinsics-factory/src/error.rs new file mode 100644 index 0000000000..0780bac692 --- /dev/null +++ b/core-primitives/extrinsics-factory/src/error.rs @@ -0,0 +1,47 @@ +/* + 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; + +/// extrinsics factory error +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Nonce cache error: {0}")] + NonceCache(#[from] itp_nonce_cache::error::Error), + #[error("SGX error, status: {0}")] + Sgx(sgx_status_t), + #[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()) + } +} diff --git a/core-primitives/extrinsics-factory/src/lib.rs b/core-primitives/extrinsics-factory/src/lib.rs new file mode 100644 index 0000000000..3150f9125a --- /dev/null +++ b/core-primitives/extrinsics-factory/src/lib.rs @@ -0,0 +1,161 @@ +/* + 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; + +// 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; +} + +use codec::Encode; +use error::Result; +use itp_nonce_cache::{MutateNonce, Nonce}; +use itp_settings::node::{RUNTIME_SPEC_VERSION, RUNTIME_TRANSACTION_VERSION}; +use itp_types::OpaqueCall; +use sp_core::{Pair, H256}; +use sp_runtime::{MultiSignature, OpaqueExtrinsic}; +use std::{sync::Arc, vec::Vec}; +use substrate_api_client::compose_extrinsic_offline; + +pub mod error; + +/// Create extrinsics from opaque calls +/// +/// Also increases the nonce counter for each extrinsic that is created. +pub trait CreateExtrinsics { + fn create_extrinsics(&self, calls: &[OpaqueCall]) -> Result>; +} + +/// Extrinsics factory +pub struct ExtrinsicsFactory +where + Signer: Pair, + Signer::Signature: Into, + NonceCache: MutateNonce, +{ + genesis_hash: H256, + signer: Signer, + nonce_cache: Arc, +} + +impl ExtrinsicsFactory +where + Signer: Pair, + Signer::Signature: Into, + NonceCache: MutateNonce, +{ + pub fn new(genesis_hash: H256, signer: Signer, nonce_cache: Arc) -> Self { + ExtrinsicsFactory { genesis_hash, signer, nonce_cache } + } +} + +impl CreateExtrinsics for ExtrinsicsFactory +where + Signer: Pair, + Signer::Signature: Into, + NonceCache: MutateNonce, +{ + fn create_extrinsics(&self, calls: &[OpaqueCall]) -> Result> { + let mut nonce_lock = self.nonce_cache.load_for_mutation()?; + let mut nonce_value = nonce_lock.0; + + let extrinsics_buffer: Vec = calls + .iter() + .map(|call| { + let xt = compose_extrinsic_offline!( + self.signer.clone(), + call, + nonce_value, + Era::Immortal, + self.genesis_hash, + self.genesis_hash, + RUNTIME_SPEC_VERSION, + RUNTIME_TRANSACTION_VERSION + ) + .encode(); + nonce_value += 1; + xt + }) + .map(|xt| { + OpaqueExtrinsic::from_bytes(&xt) + .expect("A previously encoded extrinsic has valid codec; qed.") + }) + .collect(); + + *nonce_lock = Nonce(nonce_value); + + Ok(extrinsics_buffer) + } +} + +#[cfg(test)] +pub mod tests { + + use super::*; + use itp_nonce_cache::{GetNonce, Nonce, NonceCache, NonceValue}; + use sp_core::ed25519; + //use substrate_api_client::extrinsic::xt_primitives::UncheckedExtrinsicV4; + + #[test] + pub fn creating_xts_increases_nonce_for_each_xt() { + let nonce_cache = Arc::new(NonceCache::default()); + let extrinsics_factory = + ExtrinsicsFactory::new(test_genesis_hash(), test_account(), nonce_cache.clone()); + + let opaque_calls = [OpaqueCall(vec![3u8; 42]), OpaqueCall(vec![12u8, 78])]; + let xts = extrinsics_factory.create_extrinsics(&opaque_calls).unwrap(); + + assert_eq!(opaque_calls.len(), xts.len()); + assert_eq!(nonce_cache.get_nonce().unwrap(), Nonce(opaque_calls.len() as NonceValue)); + } + + // #[test] + // pub fn xts_have_increasing_nonce() { + // let nonce_cache = Arc::new(NonceCache::default()); + // nonce_cache.set_nonce(Nonce(34)).unwrap(); + // let extrinsics_factory = + // ExtrinsicsFactory::new(test_genesis_hash(), test_account(), nonce_cache); + // + // let opaque_calls = + // [OpaqueCall(vec![3u8; 42]), OpaqueCall(vec![12u8, 78]), OpaqueCall(vec![15u8, 12])]; + // let xts: Vec> = extrinsics_factory + // .create_extrinsics(&opaque_calls) + // .unwrap() + // .iter() + // .map(|mut x| UncheckedExtrinsicV4::::decode(&mut x)) + // .collect(); + // + // assert_eq!(xts.len(), opaque_calls.len()); + // assert_eq!(xts[0].signature.unwrap().2 .2, 34u128); + // } + + fn test_account() -> ed25519::Pair { + ed25519::Pair::from_seed(b"42315678901234567890123456789012") + } + + fn test_genesis_hash() -> H256 { + H256::from_slice(&[56u8; 32]) + } +} diff --git a/core-primitives/nonce-cache/Cargo.toml b/core-primitives/nonce-cache/Cargo.toml new file mode 100644 index 0000000000..3b8ffec2f7 --- /dev/null +++ b/core-primitives/nonce-cache/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "itp-nonce-cache" +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 = [ + "log/std", + "thiserror", +] +sgx = [ + "sgx_tstd", + "thiserror_sgx", +] + +[dependencies] +# sgx dependencies +sgx_tstd = { rev = "v1.1.3", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +# local dependencies + +# 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 } +lazy_static = { version = "1.1.0", features = ["spin_no_std"] } \ No newline at end of file diff --git a/core-primitives/nonce-cache/src/error.rs b/core-primitives/nonce-cache/src/error.rs new file mode 100644 index 0000000000..6b1731a77e --- /dev/null +++ b/core-primitives/nonce-cache/src/error.rs @@ -0,0 +1,32 @@ +/* + 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 std::boxed::Box; + +pub type Result = core::result::Result; + +/// nonce cache error +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Nonce lock is poisoned")] + LockPoisoning, + #[error(transparent)] + Other(#[from] Box), +} diff --git a/core-primitives/nonce-cache/src/lib.rs b/core-primitives/nonce-cache/src/lib.rs new file mode 100644 index 0000000000..4a2ded9103 --- /dev/null +++ b/core-primitives/nonce-cache/src/lib.rs @@ -0,0 +1,80 @@ +/* + 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)] +#![feature(assert_matches)] + +#[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; + +// 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; +} + +#[cfg(feature = "std")] +use std::sync::RwLockWriteGuard; +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::sync::SgxRwLockWriteGuard as RwLockWriteGuard; + +use crate::error::Result; +use lazy_static::lazy_static; +use std::sync::Arc; + +pub use nonce_cache::NonceCache; + +lazy_static! { + /// Global instance of a nonce cache + /// + /// Concurrent access is managed internally, using RW locks + pub static ref GLOBAL_NONCE_CACHE: Arc = Default::default(); +} + +pub mod error; +pub mod nonce_cache; + +pub type NonceValue = u32; + +/// Nonce type (newtype wrapper for NonceValue) +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct Nonce(pub NonceValue); + +impl Default for Nonce { + fn default() -> Self { + Nonce(0) + } +} + +/// Trait to mutate a nonce. +/// +/// Used in a combination of loading a lock and then writing the updated +/// value back, returning the lock again. +pub trait MutateNonce { + /// load a nonce with the intention to mutate it. lock is released once it goes out of scope + fn load_for_mutation(&self) -> Result>; +} + +/// Trait to get a nonce. +/// +/// +pub trait GetNonce { + fn get_nonce(&self) -> Result; +} diff --git a/core-primitives/nonce-cache/src/nonce_cache.rs b/core-primitives/nonce-cache/src/nonce_cache.rs new file mode 100644 index 0000000000..af55045cd0 --- /dev/null +++ b/core-primitives/nonce-cache/src/nonce_cache.rs @@ -0,0 +1,101 @@ +/* + 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 std::sync::SgxRwLock as RwLock; +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::sync::SgxRwLockWriteGuard as RwLockWriteGuard; + +#[cfg(feature = "std")] +use std::sync::RwLock; +#[cfg(feature = "std")] +use std::sync::RwLockWriteGuard; + +use crate::{ + error::{Error, Result}, + GetNonce, MutateNonce, Nonce, +}; + +/// Local nonce cache +/// +/// stores the nonce internally, protected by a RW lock for concurrent access +#[derive(Default)] +pub struct NonceCache { + nonce_lock: RwLock, +} + +impl NonceCache { + pub fn new(nonce_lock: RwLock) -> Self { + NonceCache { nonce_lock } + } +} + +impl MutateNonce for NonceCache { + fn load_for_mutation(&self) -> Result> { + self.nonce_lock.write().map_err(|_| Error::LockPoisoning) + } +} + +impl GetNonce for NonceCache { + fn get_nonce(&self) -> Result { + let nonce_lock = self.nonce_lock.read().map_err(|_| Error::LockPoisoning)?; + Ok(*nonce_lock) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use std::{sync::Arc, thread}; + + #[test] + pub fn nonce_defaults_to_zero() { + let nonce_cache = NonceCache::default(); + assert_eq!(Nonce(0), nonce_cache.get_nonce().unwrap()); + } + + #[test] + pub fn set_nonce_works() { + let nonce_cache = NonceCache::default(); + let mut nonce_lock = nonce_cache.load_for_mutation().unwrap(); + *nonce_lock = Nonce(42); + std::mem::drop(nonce_lock); + assert_eq!(Nonce(42), nonce_cache.get_nonce().unwrap()); + } + + #[test] + pub fn concurrent_read_access_blocks_until_write_is_done() { + let nonce_cache = Arc::new(NonceCache::default()); + + let mut nonce_write_lock = nonce_cache.load_for_mutation().unwrap(); + + // spawn a new thread that reads the nonce + // this thread should be blocked until the write lock is released, i.e. until + // the new nonce is written. We can verify this, by trying to read that nonce variable + // that will be inserted further down below + let new_thread_nonce_cache = nonce_cache.clone(); + let join_handle = thread::spawn(move || { + let nonce_read = new_thread_nonce_cache.get_nonce().unwrap(); + assert_eq!(Nonce(3108), nonce_read); + }); + + *nonce_write_lock = Nonce(3108); + std::mem::drop(nonce_write_lock); + + join_handle.join().unwrap(); + } +} diff --git a/core-primitives/stf-state-handler/src/handle_state.rs b/core-primitives/stf-state-handler/src/handle_state.rs index 8f2d3a31a6..419849758f 100644 --- a/core-primitives/stf-state-handler/src/handle_state.rs +++ b/core-primitives/stf-state-handler/src/handle_state.rs @@ -35,6 +35,11 @@ pub trait HandleState { /// return a state fn load_initialized(&self, shard: &ShardIdentifier) -> Result; + /// Load the state in order to mutate it + /// + /// Returns a write lock to protect against any concurrent access as long as + /// the lock is held. Finalize the operation by calling `write` and returning + /// the lock again. fn load_for_mutation( &self, shard: &ShardIdentifier, diff --git a/core/light-client/Cargo.toml b/core/light-client/Cargo.toml index edf775fe01..7b32e8827c 100644 --- a/core/light-client/Cargo.toml +++ b/core/light-client/Cargo.toml @@ -23,6 +23,7 @@ std = [ "sp-trie/std", # local deps + "itp-ocall-api/std", "itp-storage/std", "itp-sgx-io/std", ] @@ -51,6 +52,7 @@ thiserror-sgx = { package = "thiserror", git = "https://github.com/mesalock-linu itp-sgx-io = { path = "../../core-primitives/sgx/io", default-features = false } itp-settings = { path = "../../core-primitives/settings", default-features = false } itp-storage = { path = "../../core-primitives/storage", default-features = false } +itp-ocall-api = { path = "../../core-primitives/ocall-api", default-features = false } # substrate deps frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "master" } diff --git a/core/light-client/src/lib.rs b/core/light-client/src/lib.rs index 32aacd4eb9..c9528095ed 100644 --- a/core/light-client/src/lib.rs +++ b/core/light-client/src/lib.rs @@ -54,6 +54,7 @@ pub mod io; // reexport useful types. pub use finality_grandpa::BlockNumberOps; +use itp_ocall_api::EnclaveOnChainOCallApi; pub use sp_finality_grandpa::{AuthorityList, SetId}; pub type RelayId = u64; @@ -102,6 +103,13 @@ where extrinsic: OpaqueExtrinsic, ) -> Result<(), Error>; + /// sends encoded extrinsics to the parentchain + fn send_extrinsics( + &mut self, + ocall_api: &OCallApi, + extrinsics: Vec, + ) -> Result<(), Error>; + fn check_xt_inclusion(&mut self, relay_id: RelayId, block: &Block) -> Result<(), Error>; } @@ -346,6 +354,20 @@ where Ok(()) } + fn send_extrinsics( + &mut self, + ocall_api: &OCallApi, + extrinsics: Vec, + ) -> Result<(), Error> { + for xt in extrinsics.iter() { + self.submit_xt_to_be_included(self.num_relays(), xt.clone()).unwrap(); + } + + ocall_api + .send_to_parentchain(extrinsics) + .map_err(|e| Error::Other(format!("Failed to send extrinsics: {}", e).into())) + } + fn check_xt_inclusion(&mut self, relay_id: RelayId, block: &Block) -> Result<(), Error> { let relay = self.tracked_relays.get_mut(&relay_id).ok_or(Error::NoSuchRelayExists)?; diff --git a/enclave-runtime/Cargo.lock b/enclave-runtime/Cargo.lock index 771fa6db90..1ff899ffcb 100644 --- a/enclave-runtime/Cargo.lock +++ b/enclave-runtime/Cargo.lock @@ -473,6 +473,8 @@ dependencies = [ "itc-light-client", "itc-tls-websocket-server", "itertools", + "itp-extrinsics-factory", + "itp-nonce-cache", "itp-ocall-api", "itp-settings", "itp-sgx-crypto", @@ -1162,6 +1164,7 @@ dependencies = [ "finality-grandpa", "frame-system", "hash-db", + "itp-ocall-api", "itp-settings", "itp-sgx-io", "itp-storage", @@ -1215,6 +1218,33 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +[[package]] +name = "itp-extrinsics-factory" +version = "0.8.0" +dependencies = [ + "itp-nonce-cache", + "itp-settings", + "itp-types", + "log 0.4.14 (git+https://github.com/mesalock-linux/log-sgx)", + "parity-scale-codec", + "sgx_tstd", + "sgx_types", + "sp-core", + "sp-runtime", + "substrate-api-client", + "thiserror 1.0.9", +] + +[[package]] +name = "itp-nonce-cache" +version = "0.8.0" +dependencies = [ + "lazy_static", + "log 0.4.14 (git+https://github.com/mesalock-linux/log-sgx)", + "sgx_tstd", + "thiserror 1.0.9", +] + [[package]] name = "itp-ocall-api" version = "0.8.0" diff --git a/enclave-runtime/Cargo.toml b/enclave-runtime/Cargo.toml index 6a8a3d396b..a0b5e0be3e 100644 --- a/enclave-runtime/Cargo.toml +++ b/enclave-runtime/Cargo.toml @@ -82,7 +82,9 @@ ita-stf = { path = "../app-libs/stf", default-features = false, features = ["sgx itc-light-client = { path = "../core/light-client", default-features = false, features = ["sgx"] } itc-tls-websocket-server = { path = "../core/tls-websocket-server", default-features = false, features = ["sgx"] } itc-direct-rpc-server = { path = "../core/direct-rpc-server", default-features = false, features = ["sgx"] } +itp-extrinsics-factory = { path = "../core-primitives/extrinsics-factory", default-features = false, features = ["sgx"]} itp-ocall-api = { path = "../core-primitives/ocall-api", default-features = false } +itp-nonce-cache = { path = "../core-primitives/nonce-cache", default-features = false, features = ["sgx"] } 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"] } diff --git a/enclave-runtime/src/error.rs b/enclave-runtime/src/error.rs index cd098b390a..64f61049e9 100644 --- a/enclave-runtime/src/error.rs +++ b/enclave-runtime/src/error.rs @@ -27,6 +27,7 @@ pub enum Error { Codec(codec::Error), Crypto(itp_sgx_crypto::Error), ChainStorage(itp_storage_verifier::Error), + ExtrinsicsFactory(itp_extrinsics_factory::error::Error), IO(std::io::Error), LightClient(itc_light_client::error::Error), Sgx(sgx_status_t), diff --git a/enclave-runtime/src/lib.rs b/enclave-runtime/src/lib.rs index a399ece91f..452d0232fd 100644 --- a/enclave-runtime/src/lib.rs +++ b/enclave-runtime/src/lib.rs @@ -54,9 +54,11 @@ use itc_direct_rpc_server::{ rpc_ws_handler::RpcWsHandler, }; use itc_light_client::{ - io::LightClientSeal, BlockNumberOps, HashFor, LightClientState, NumberFor, Validator, + io::LightClientSeal, BlockNumberOps, LightClientState, NumberFor, Validator, }; use itc_tls_websocket_server::{connection::TungsteniteWsConnection, run_ws_server}; +use itp_extrinsics_factory::{CreateExtrinsics, ExtrinsicsFactory}; +use itp_nonce_cache::{MutateNonce, Nonce, GLOBAL_NONCE_CACHE}; use itp_ocall_api::{EnclaveAttestationOCallApi, EnclaveOnChainOCallApi}; use itp_settings::{ node::{ @@ -95,7 +97,6 @@ use its_sidechain::{ traits::{AuthorApi, GetAuthor, OnBlockCreated, SendState}, }, }; -use lazy_static::lazy_static; use log::*; use sgx_externalities::{SgxExternalities, SgxExternalitiesTrait}; use sgx_types::sgx_status_t; @@ -104,14 +105,9 @@ use sp_finality_grandpa::VersionedAuthorityList; use sp_runtime::{ generic::SignedBlock as SignedBlockG, traits::{Block as BlockT, Header as HeaderT}, - MultiSignature, OpaqueExtrinsic, -}; -use std::{ - slice, - sync::{Arc, SgxRwLock}, - time::Duration, - vec::Vec, + MultiSignature, }; +use std::{slice, sync::Arc, time::Duration, vec::Vec}; use substrate_api_client::{ compose_extrinsic_offline, extrinsic::xt_primitives::UncheckedExtrinsicV4, }; @@ -146,17 +142,6 @@ pub const CERTEXPIRYDAYS: i64 = 90i64; pub type Hash = sp_core::H256; pub type AuthorityPair = sp_core::ed25519::Pair; -lazy_static! { - /// the enclave's parentchain nonce - /// - /// This should be abstracted away better in the future. Currently, this only exists - /// because we produce sidechain blocks faster that parentchain chain blocks. So for now this - /// design suffices. Later, we also need to sync across parallel ecalls that might both result - /// in parentchain xt's. Then we should probably think about how we want to abstract the global - /// nonce. - static ref NONCE: SgxRwLock = SgxRwLock::new(Default::default()); -} - #[no_mangle] pub unsafe extern "C" fn init() -> sgx_status_t { // initialize the logging environment in the enclave @@ -241,7 +226,15 @@ pub unsafe extern "C" fn get_ecc_signing_pubkey(pubkey: *mut u8, pubkey_size: u3 pub unsafe extern "C" fn set_nonce(nonce: *const u32) -> sgx_status_t { log::info!("[Ecall Set Nonce] Setting the nonce of the enclave to: {}", *nonce); - *NONCE.write().expect("Encountered poisoned NONCE lock") = *nonce; + let mut nonce_lock = match GLOBAL_NONCE_CACHE.load_for_mutation() { + Ok(l) => l, + Err(e) => { + error!("Failed to set nonce in enclave: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + + *nonce_lock = Nonce(*nonce); sgx_status_t::SGX_SUCCESS } @@ -271,12 +264,14 @@ pub unsafe extern "C" fn mock_register_enclave_xt( let signer = Ed25519Seal::unseal().unwrap(); let call = ([TEEREX_MODULE, REGISTER_ENCLAVE], mre, url); - let mut nonce = NONCE.write().expect("Encountered poisoned NONCE lock"); + let nonce_cache = GLOBAL_NONCE_CACHE.clone(); + let mut nonce_lock = nonce_cache.load_for_mutation().expect("Nonce lock poisoning"); + let nonce_value = nonce_lock.0; let xt = compose_extrinsic_offline!( signer, call, - *nonce, + nonce_value, Era::Immortal, genesis_hash, genesis_hash, @@ -285,53 +280,13 @@ pub unsafe extern "C" fn mock_register_enclave_xt( ) .encode(); - *nonce += 1; + *nonce_lock = Nonce(nonce_value + 1); + std::mem::drop(nonce_lock); write_slice_and_whitespace_pad(extrinsic_slice, xt); sgx_status_t::SGX_SUCCESS } -fn create_extrinsics( - genesis_hash: HashFor, - signer: Signer, - calls: Vec, - mut nonce: u32, -) -> Result<(Vec, u32)> -where - PB: BlockT, - Signer: Pair, - Signer::Public: Encode, - Signer::Signature: Into, -{ - // get information for composing the extrinsic - debug!("Restored ECC pubkey: {:?}", signer.public()); - - let extrinsics_buffer: Vec = calls - .into_iter() - .map(|call| { - let xt = compose_extrinsic_offline!( - signer.clone(), - call, - nonce, - Era::Immortal, - genesis_hash, - genesis_hash, - RUNTIME_SPEC_VERSION, - RUNTIME_TRANSACTION_VERSION - ) - .encode(); - nonce += 1; - xt - }) - .map(|xt| { - OpaqueExtrinsic::from_bytes(&xt) - .expect("A previously encoded extrinsic has valid codec; qed.") - }) - .collect(); - - Ok((extrinsics_buffer, nonce)) -} - /// this is reduced to the side chain block import RPC interface (i.e. worker-worker communication) /// the entire rest of the RPC server is run inside the enclave and does not use this e-call function anymore #[no_mangle] @@ -612,7 +567,6 @@ where let (_light_client_lock, _side_chain_lock) = EnclaveLock::write_all()?; let mut validator = LightClientSeal::::unseal()?; - let mut nonce = NONCE.write().expect("Encountered poisoned NONCE lock"); let authority = Ed25519Seal::unseal()?; @@ -625,6 +579,9 @@ where 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(); + let genesis_hash = validator.genesis_hash(validator.num_relays())?; + let extrinsics_factory = + ExtrinsicsFactory::new(genesis_hash, authority.clone(), GLOBAL_NONCE_CACHE.clone()); match yield_next_slot(duration_now(), SLOT_DURATION, latest_onchain_header, &mut LastSlotSeal)? { @@ -632,13 +589,13 @@ where let shards = state_handler.list_shards()?; let env = ProposerFactory::new(rpc_author, stf_executor, authority.clone()); - *nonce = exec_aura_on_slot::<_, _, SignedSidechainBlock, _, _, _>( + exec_aura_on_slot::<_, _, SignedSidechainBlock, _, _, _, _>( slot, authority, &mut validator, + &extrinsics_factory, OcallApi, env, - *nonce, shards, )? }, @@ -689,16 +646,17 @@ where let mut validator = LightClientSeal::::unseal()?; let signer = Ed25519Seal::unseal()?; - let mut nonce = NONCE.write().expect("Encountered poisoned NONCE lock"); let stf_executor = StfExecutor::new(Arc::new(OcallApi), Arc::new(GlobalFileStateHandler)); + let genesis_hash = validator.genesis_hash(validator.num_relays())?; + let extrinsics_factory = + ExtrinsicsFactory::new(genesis_hash, signer, GLOBAL_NONCE_CACHE.clone()); - *nonce = sync_blocks_on_light_client( + sync_blocks_on_light_client( blocks_to_sync, &mut validator, - signer, + &extrinsics_factory, &OcallApi, &stf_executor, - *nonce, )?; // store updated state in light client in case we fail afterwards. @@ -707,23 +665,20 @@ where Ok(()) } -fn sync_blocks_on_light_client( +fn sync_blocks_on_light_client( blocks_to_sync: Vec>, validator: &mut V, - signer: Signer, + extrinsics_factory: &ExtrinsicsFactory, on_chain_ocall_api: &OCallApi, stf_executor: &StfExecutor, - nonce: u32, -) -> Result +) -> Result<()> where PB: BlockT, NumberFor: BlockNumberOps, V: Validator + LightClientState, OCallApi: EnclaveOnChainOCallApi + EnclaveAttestationOCallApi, StfExecutor: StfUpdateState + StfExecuteTrustedCall + StfExecuteShieldFunds, - Signer: Pair, - Signer::Public: Encode, - Signer::Signature: Into, + ExtrinsicsFactory: CreateExtrinsics, { let mut calls = Vec::::new(); let mrenclave: ShardIdentifier = on_chain_ocall_api.get_mrenclave_of_self()?.m.into(); @@ -768,42 +723,11 @@ where ))); } - prepare_and_send_xts::<_, _, _, Signer>(validator, signer, on_chain_ocall_api, calls, nonce) -} - -fn prepare_and_send_xts( - validator: &mut V, - signer: Signer, - ocall_api: &OCallApi, - calls: Vec, - nonce: u32, -) -> Result -where - Block: BlockT, - NumberFor: BlockNumberOps, - V: Validator + LightClientState, - OCallApi: EnclaveOnChainOCallApi, - Signer: Pair, - Signer::Public: Encode, - Signer::Signature: Into, -{ - // store extrinsics in light client for finalization check - let (extrinsics, nonce_updated) = create_extrinsics::( - validator.genesis_hash(validator.num_relays()).unwrap(), - signer, - calls, - nonce, - )?; - - for xt in extrinsics.iter() { - validator.submit_xt_to_be_included(validator.num_relays(), xt.clone()).unwrap(); - } + let xts = extrinsics_factory.create_extrinsics(calls.as_slice())?; - ocall_api - .send_to_parentchain(extrinsics) - .map_err(|e| Error::Other(format!("Failed to send extrinsics: {}", e).into()))?; + validator.send_extrinsics(on_chain_ocall_api, xts)?; - Ok(nonce_updated) + Ok(()) } /// Execute pending trusted operations for all shards until the [`max_exec_duration`] is reached. diff --git a/enclave-runtime/src/sidechain_impl.rs b/enclave-runtime/src/sidechain_impl.rs index f2997d8054..31cc3bec75 100644 --- a/enclave-runtime/src/sidechain_impl.rs +++ b/enclave-runtime/src/sidechain_impl.rs @@ -3,10 +3,11 @@ //! Todo: Once we have put the `top_pool` stuff in an entirely different crate we can //! move most parts here to the sidechain crate. -use crate::{execute_top_pool_trusted_calls, prepare_and_send_xts, Result as EnclaveResult}; +use crate::{execute_top_pool_trusted_calls, Result as EnclaveResult}; use codec::Encode; use core::time::Duration; use itc_light_client::{BlockNumberOps, LightClientState, NumberFor, Validator}; +use itp_extrinsics_factory::CreateExtrinsics; use itp_ocall_api::{EnclaveAttestationOCallApi, EnclaveOnChainOCallApi, EnclaveSidechainOCallApi}; use itp_settings::sidechain::SLOT_DURATION; use itp_sgx_crypto::{Aes, AesSeal}; @@ -14,6 +15,7 @@ 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 itp_types::OpaqueCall; use its_sidechain::{ aura::{Aura, AuraVerifier, SlotClaimStrategy}, consensus_common::{BlockImport, Environment, Error as ConsensusError, Proposal, Proposer}, @@ -131,15 +133,23 @@ where } /// Executes aura for the given `slot` -pub fn exec_aura_on_slot( +pub fn exec_aura_on_slot< + Authority, + PB, + SB, + OCallApi, + LightValidator, + PEnvironment, + ExtrinsicsFactory, +>( slot: SlotInfo, authority: Authority, validator: &mut LightValidator, + extrinsics_factory: &ExtrinsicsFactory, ocall_api: OCallApi, proposer_environment: PEnvironment, - nonce: u32, shards: Vec>, -) -> EnclaveResult +) -> EnclaveResult<()> where // setting the public type is necessary due to some non-generic downstream code. PB: Block, @@ -153,16 +163,14 @@ where LightValidator: Validator + LightClientState + Clone + Send + Sync + 'static, NumberFor: BlockNumberOps, PEnvironment: Environment + Send + Sync, + ExtrinsicsFactory: CreateExtrinsics, { log::info!("[Aura] Executing aura for slot: {:?}", slot); - let mut aura = Aura::<_, _, SB, PEnvironment, _>::new( - authority.clone(), - ocall_api.clone(), - proposer_environment, - ) - .with_claim_strategy(SlotClaimStrategy::Always) - .with_allow_delayed_proposal(true); + let mut aura = + Aura::<_, _, SB, PEnvironment, _>::new(authority, ocall_api.clone(), proposer_environment) + .with_claim_strategy(SlotClaimStrategy::Always) + .with_allow_delayed_proposal(true); let (blocks, xts): (Vec<_>, Vec<_>) = PerShardSlotWorkerScheduler::on_slot(&mut aura, slot, shards) @@ -171,15 +179,13 @@ where .unzip(); ocall_api.propose_sidechain_blocks(blocks)?; - let signer = authority; + let opaque_calls: Vec = xts.into_iter().flatten().collect(); + + let xts = extrinsics_factory.create_extrinsics(opaque_calls.as_slice())?; - prepare_and_send_xts::<_, _, _, _>( - validator, - signer, - &ocall_api, - xts.into_iter().flatten().collect(), - nonce, - ) + validator.send_extrinsics(&ocall_api, xts)?; + + Ok(()) } /// Not used now as we skip block import currently.