From b12021b9a09d6547b2c595849b0283ed377e1567 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Mon, 21 Mar 2022 15:43:09 +0800 Subject: [PATCH 01/10] Prepare the FraudProof impl A FraudProof consists of three parts: `pre_state_root`, `post_state_root` and the storage proof used to reconstruct the partial trie. --- cumulus/client/cirrus-executor/src/lib.rs | 55 ++++++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/cumulus/client/cirrus-executor/src/lib.rs b/cumulus/client/cirrus-executor/src/lib.rs index 2d7cf452b229c..eacfd25deff5e 100644 --- a/cumulus/client/cirrus-executor/src/lib.rs +++ b/cumulus/client/cirrus-executor/src/lib.rs @@ -24,12 +24,16 @@ mod processor; #[cfg(test)] mod tests; +use codec::{Decode, Encode}; use sc_client_api::{AuxStore, BlockBackend}; use sc_utils::mpsc::TracingUnboundedSender; use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_consensus::BlockStatus; -use sp_core::traits::{CodeExecutor, SpawnNamed}; +use sp_core::{ + traits::{CodeExecutor, SpawnNamed}, + H256, +}; use sp_inherents::CreateInherentDataProviders; use sp_runtime::{ generic::BlockId, @@ -286,6 +290,12 @@ where ); } + fn header(&self, at: Block::Hash) -> Result { + self.client + .header(BlockId::Hash(at))? + .ok_or(sp_blockchain::Error::Backend(format!("Header not found for {:?}", at))) + } + async fn wait_for_local_receipt( &self, block_hash: Block::Hash, @@ -361,6 +371,8 @@ where pub enum GossipMessageError { #[error("Bundle equivocation error")] BundleEquivocation, + #[error("State root not using H256")] + InvalidStateRootType, #[error(transparent)] Client(#[from] sp_blockchain::Error), #[error(transparent)] @@ -500,7 +512,7 @@ where // TODO: What happens for this obvious error? if local_receipt.trace.len() != execution_receipt.trace.len() {} - if let Some(_local_trace_idx) = local_receipt + if let Some(local_trace_idx) = local_receipt .trace .iter() .enumerate() @@ -514,11 +526,40 @@ where } }, ) { - // TODO: generate a fraud proof - let fraud_proof = FraudProof { - pre_state_root: sp_core::H256::random(), - post_state_root: sp_core::H256::random(), - proof: StorageProof::empty(), + let header = self.header(execution_receipt.secondary_hash)?; + let parent_header = self.header(*header.parent_hash())?; + + // TODO: avoid the encode & decode? + let as_h256 = |state_root: &Block::Hash| { + H256::decode(&mut state_root.encode().as_slice()) + .map_err(|_| Self::Error::InvalidStateRootType) + }; + + let fraud_proof = if local_trace_idx == 0 { + // `initialize_block` execution proof. + let pre_state_root = as_h256(parent_header.state_root())?; + let post_state_root = as_h256(&execution_receipt.trace[0])?; + + let proof = todo!("Create `initialize_block` proof"); + + FraudProof { pre_state_root, post_state_root, proof } + } else if local_trace_idx == local_receipt.trace.len() - 1 { + // `finalize_block` execution proof. + let pre_state_root = as_h256(&execution_receipt.trace[local_trace_idx - 1])?; + let post_state_root = as_h256(&execution_receipt.trace[local_trace_idx])?; + + let proof = todo!("Create `finalize_block` proof"); + + FraudProof { pre_state_root, post_state_root, proof } + } else { + // Regular extrinsic execution proof. + let pre_state_root = as_h256(&execution_receipt.trace[local_trace_idx - 1])?; + let post_state_root = as_h256(&execution_receipt.trace[local_trace_idx])?; + + let proof = todo!("Create extrinsic proof"); + + // TODO: proof should be a CompactProof. + FraudProof { pre_state_root, post_state_root, proof } }; self.submit_fraud_proof(fraud_proof); From d8c7eb594a7b15cee708756dd7a96fcb443fe2d3 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Mon, 21 Mar 2022 16:10:13 +0800 Subject: [PATCH 02/10] Introduce cirrus-fraud-proof to create and verify more fine-grained execution proof - `cirrus_fraud_proof::prove_execution` was initially proposed in https://github.com/paritytech/substrate/pull/11019, an extended version of [original `prove_execution`(https://github.com/paritytech/substrate/blob/ded44948e2d5a398abcb4e342b0513cb690961bb/primitives/state-machine/src/lib.rs#L555) in sp-state-machine] by adding an entra optional argument `delta_changes`. - `cirrus_fraud_proof::check_execution_proof` is simply an API of `sp_state_machine::execution_proof_check`. --- Cargo.lock | 15 +++ Cargo.toml | 1 + cumulus/client/fraud-proof/Cargo.toml | 23 ++++ cumulus/client/fraud-proof/src/lib.rs | 170 ++++++++++++++++++++++++++ 4 files changed, 209 insertions(+) create mode 100644 cumulus/client/fraud-proof/Cargo.toml create mode 100644 cumulus/client/fraud-proof/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 8bc540ee0ff57..ab6e8511ac270 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -928,6 +928,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "cirrus-fraud-proof" +version = "0.1.0" +dependencies = [ + "hash-db", + "parity-scale-codec", + "sc-client-api", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-runtime", + "sp-state-machine", + "sp-trie", +] + [[package]] name = "cirrus-node-primitives" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 5d1d6b2228ac4..6143231c9fe33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "cumulus/client/consensus/common", "cumulus/client/consensus/relay-chain", "cumulus/client/executor-gossip", + "cumulus/client/fraud-proof", "cumulus/pallets/executive", "cumulus/parachain-template/node", "cumulus/parachain-template/runtime", diff --git a/cumulus/client/fraud-proof/Cargo.toml b/cumulus/client/fraud-proof/Cargo.toml new file mode 100644 index 0000000000000..d7a3c406fb39f --- /dev/null +++ b/cumulus/client/fraud-proof/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "cirrus-fraud-proof" +version = "0.1.0" +authors = ["Liu-Cheng Xu "] +edition = "2021" +license = "GPL-3.0-or-later" +homepage = "https://subspace.network" +repository = "https://github.com/subspace/subspace/" +description = "Subspace fraud proof utilities" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.2", features = ["derive"] } +hash-db = "0.15.2" +sc-client-api = { git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" } +sp-api = { git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" } +sp-blockchain = { git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" } +sp-core = { git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" } +sp-runtime = { git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" } +sp-state-machine = { git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" } +sp-trie = { git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" } diff --git a/cumulus/client/fraud-proof/src/lib.rs b/cumulus/client/fraud-proof/src/lib.rs new file mode 100644 index 0000000000000..67f940ab3a733 --- /dev/null +++ b/cumulus/client/fraud-proof/src/lib.rs @@ -0,0 +1,170 @@ +//! Subspace fraud proof +//! +//! This crates provides the feature of generating and verifying the execution proof used in +//! the Subspace fraud proof mechanism. The execution is more fine-grained than the entire +//! block execution, block execution hooks (`initialize_block` and `finalize_block`) and any +//! specific extrinsic execution are supported. + +#![warn(missing_docs)] + +use codec::Codec; +use hash_db::{HashDB, Hasher, Prefix}; +use sc_client_api::backend; +use sp_api::{StateBackend, StorageProof}; +use sp_core::{ + traits::{CodeExecutor, SpawnNamed}, + H256, +}; +use sp_runtime::{ + generic::BlockId, + traits::{BlakeTwo256, Block as BlockT, HashFor}, +}; +use sp_state_machine::{TrieBackend, TrieBackendStorage}; +use sp_trie::DBValue; +use std::sync::Arc; + +/// Returns a storage proof which can be used to reconstruct a partial state trie to re-run +/// the execution by someone who does not own the whole state. +// TODO: too many arguments, but no need to refactor it right now as the API of execution proof +// on the primary node might have some other considerations, e.g., RuntimeCode will be fetched +// another way. +#[allow(clippy::too_many_arguments)] +pub fn prove_execution< + Block: BlockT, + B: backend::Backend, + Exec: CodeExecutor + 'static, + Spawn: SpawnNamed + Send + 'static, + DB: HashDB, DBValue>, +>( + backend: &Arc, + executor: &Exec, + spawn_handle: Spawn, + at: &BlockId, + method: &str, + call_data: &[u8], + delta_changes: Option<(DB, Block::Hash)>, +) -> sp_blockchain::Result { + let state = backend.state_at(*at)?; + + let trie_backend = state.as_trie_backend().ok_or_else(|| { + Box::new(sp_state_machine::ExecutionError::UnableToGenerateProof) + as Box + })?; + + let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(trie_backend); + let runtime_code = + state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; + + if let Some((delta, post_delta_root)) = delta_changes { + let delta_backend = create_delta_backend(trie_backend, delta, post_delta_root); + sp_state_machine::prove_execution_on_trie_backend( + &delta_backend, + &mut Default::default(), + executor, + spawn_handle, + method, + call_data, + &runtime_code, + ) + .map(|(_ret, proof)| proof) + .map_err(Into::into) + } else { + sp_state_machine::prove_execution_on_trie_backend( + trie_backend, + &mut Default::default(), + executor, + spawn_handle, + method, + call_data, + &runtime_code, + ) + .map(|(_ret, proof)| proof) + .map_err(Into::into) + } +} + +/// Runs the execution using the partial state constructed from the given storage proof and +/// returns the execution result. +/// +/// The execution result contains the information of state root after applying the execution +/// so that it can be used to compare with the one specified in the fraud proof. +// TODO: too many arguments. +#[allow(clippy::too_many_arguments)] +pub fn check_execution_proof< + Block: BlockT, + B: backend::Backend, + Exec: CodeExecutor + 'static, + Spawn: SpawnNamed + Send + 'static, +>( + backend: &Arc, + executor: &Exec, + spawn_handle: Spawn, + at: &BlockId, + method: &str, + call_data: &[u8], + pre_execution_root: H256, + proof: StorageProof, +) -> sp_blockchain::Result> { + let state = backend.state_at(*at)?; + + let trie_backend = state.as_trie_backend().ok_or_else(|| { + Box::new(sp_state_machine::ExecutionError::UnableToGenerateProof) + as Box + })?; + + let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(trie_backend); + let runtime_code = + state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; + + sp_state_machine::execution_proof_check::( + pre_execution_root, + proof, + &mut Default::default(), + executor, + spawn_handle, + method, + call_data, + &runtime_code, + ) + .map_err(Into::into) +} + +/// Create a new trie backend with memory DB delta changes. +/// +/// This can be used to verify any extrinsic-specific execution on the combined state of `backend` +/// and `delta`. +fn create_delta_backend<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher, DB: HashDB>( + backend: &'a TrieBackend, + delta: DB, + post_delta_root: H::Out, +) -> TrieBackend, H> +where + H::Out: Codec, +{ + let essence = backend.essence(); + let delta_backend = DeltaBackend { + backend: essence.backend_storage(), + delta, + _phantom: std::marker::PhantomData::, + }; + TrieBackend::new(delta_backend, post_delta_root) +} + +struct DeltaBackend<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher, DB: HashDB> { + backend: &'a S, + delta: DB, + _phantom: std::marker::PhantomData, +} + +impl<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher, DB: HashDB> + TrieBackendStorage for DeltaBackend<'a, S, H, DB> +{ + type Overlay = S::Overlay; + + fn get(&self, key: &H::Out, prefix: Prefix) -> Result, String> { + match HashDB::get(&self.delta, key, prefix) { + Some(v) => Ok(Some(v)), + None => Ok(self.backend.get(key, prefix)?), + } + } +} From ea390db88d48fef7de675d40484576c1d9cff357 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Mon, 21 Mar 2022 16:28:04 +0800 Subject: [PATCH 03/10] Extract BlockBuilder::collect_storage_changes() --- cumulus/client/block-builder/src/lib.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/cumulus/client/block-builder/src/lib.rs b/cumulus/client/block-builder/src/lib.rs index 265143bfa3856..c1d9042be5684 100644 --- a/cumulus/client/block-builder/src/lib.rs +++ b/cumulus/client/block-builder/src/lib.rs @@ -223,6 +223,16 @@ where Ok(()) } + fn collect_storage_changes( + &self, + ) -> Result, Block>, Error> { + let state = self.backend.state_at(self.block_id)?; + let parent_hash = self.parent_hash; + self.api + .into_storage_changes(&state, parent_hash) + .map_err(sp_blockchain::Error::StorageChanges) + } + /// Consume the builder to build a valid `Block` containing all pushed extrinsics. /// /// Returns the build `Block`, the changes to the storage and an optional `StorageProof` @@ -245,13 +255,7 @@ where let proof = self.api.extract_proof(); - let state = self.backend.state_at(self.block_id)?; - let parent_hash = self.parent_hash; - - let storage_changes = self - .api - .into_storage_changes(&state, parent_hash) - .map_err(|e| sp_blockchain::Error::StorageChanges(e))?; + let storage_changes = self.collect_storage_changes()?; Ok(BuiltBlock { block: ::new(header, self.extrinsics), From f6b8280a8debac91b96d24debc3097825ede1688 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Mon, 21 Mar 2022 16:33:13 +0800 Subject: [PATCH 04/10] Implement the extrinsic execution proof --- Cargo.lock | 2 + cumulus/client/block-builder/src/lib.rs | 50 +++++++++++++++++++ cumulus/client/cirrus-executor/Cargo.toml | 1 + cumulus/client/cirrus-executor/src/lib.rs | 60 ++++++++++++++++++++++- cumulus/client/cirrus-service/Cargo.toml | 1 + cumulus/client/cirrus-service/src/lib.rs | 3 ++ 6 files changed, 115 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab6e8511ac270..8f71b508059ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -840,6 +840,7 @@ version = "0.1.0" dependencies = [ "cirrus-block-builder", "cirrus-client-executor-gossip", + "cirrus-fraud-proof", "cirrus-node-primitives", "cirrus-primitives", "cirrus-test-service", @@ -924,6 +925,7 @@ dependencies = [ "sp-core", "sp-inherents", "sp-runtime", + "sp-trie", "subspace-service", "tracing", ] diff --git a/cumulus/client/block-builder/src/lib.rs b/cumulus/client/block-builder/src/lib.rs index c1d9042be5684..545aa17381ab2 100644 --- a/cumulus/client/block-builder/src/lib.rs +++ b/cumulus/client/block-builder/src/lib.rs @@ -194,6 +194,22 @@ where }) } + /// Create a new instance of builder with given extrinsics. + pub fn with_extrinsics( + api: &'a A, + parent_hash: Block::Hash, + parent_number: NumberFor, + record_proof: RecordProof, + inherent_digests: Digest, + backend: &'a B, + extrinsics: Vec, + ) -> Result { + let mut block_builder = + Self::new(api, parent_hash, parent_number, record_proof, inherent_digests, backend)?; + block_builder.extrinsics = extrinsics; + Ok(block_builder) + } + /// Sets the extrinsics. pub fn set_extrinsics(&mut self, extrinsics: Vec) { self.extrinsics = extrinsics; @@ -233,6 +249,40 @@ where .map_err(sp_blockchain::Error::StorageChanges) } + /// Returns the state before executing the extrinsic at given extrinsic index. + pub fn prepare_storage_changes_before( + &self, + extrinsic_index: usize, + ) -> Result, Block>, Error> { + for (index, xt) in self.extrinsics.iter().enumerate() { + if index == extrinsic_index { + return Ok(self.collect_storage_changes()?) + } + + // TODO: rethink what to do if an error occurs when executing the transaction. + self.api.execute_in_transaction(|api| { + let res = api.apply_extrinsic_with_context( + &self.block_id, + ExecutionContext::BlockConstruction, + xt.clone(), + ); + match res { + Ok(Ok(_)) => TransactionOutcome::Commit(Ok(())), + Ok(Err(tx_validity)) => TransactionOutcome::Rollback(Err( + ApplyExtrinsicFailed::Validity(tx_validity).into(), + )), + Err(e) => TransactionOutcome::Rollback(Err(Error::from(e))), + } + })?; + } + + Err(Error::Execution(Box::new(format!( + "Invalid extrinsic index, got: {}, max: {}", + extrinsic_index, + self.extrinsics.len() + )))) + } + /// Consume the builder to build a valid `Block` containing all pushed extrinsics. /// /// Returns the build `Block`, the changes to the storage and an optional `StorageProof` diff --git a/cumulus/client/cirrus-executor/Cargo.toml b/cumulus/client/cirrus-executor/Cargo.toml index eae4d2a0762e2..0aa58ac4d9f35 100644 --- a/cumulus/client/cirrus-executor/Cargo.toml +++ b/cumulus/client/cirrus-executor/Cargo.toml @@ -42,6 +42,7 @@ polkadot-node-subsystem = { path = "../../../polkadot/node/subsystem" } cirrus-block-builder = { path = "../block-builder" } cirrus-client-executor-gossip = { path = "../executor-gossip" } cirrus-node-primitives = { path = "../../../crates/cirrus-node-primitives" } +cirrus-fraud-proof = { path = "../fraud-proof" } cirrus-primitives = { path = "../../primitives" } sp-executor = { path = "../../../crates/sp-executor" } subspace-core-primitives = { path = "../../../crates/subspace-core-primitives" } diff --git a/cumulus/client/cirrus-executor/src/lib.rs b/cumulus/client/cirrus-executor/src/lib.rs index eacfd25deff5e..fe1df1268c4f6 100644 --- a/cumulus/client/cirrus-executor/src/lib.rs +++ b/cumulus/client/cirrus-executor/src/lib.rs @@ -24,6 +24,7 @@ mod processor; #[cfg(test)] mod tests; +use cirrus_block_builder::{BlockBuilder, RecordProof}; use codec::{Decode, Encode}; use sc_client_api::{AuxStore, BlockBackend}; use sc_utils::mpsc::TracingUnboundedSender; @@ -37,7 +38,7 @@ use sp_core::{ use sp_inherents::CreateInherentDataProviders; use sp_runtime::{ generic::BlockId, - traits::{Block as BlockT, Header as HeaderT, Zero}, + traits::{Block as BlockT, HashFor, Header as HeaderT, Zero}, }; use sp_trie::StorageProof; @@ -101,6 +102,11 @@ impl Clone } } +type TransactionFor = + <>::State as sc_client_api::backend::StateBackend< + HashFor, + >>::Transaction; + impl Executor where @@ -118,6 +124,7 @@ where Error = sp_consensus::Error, >, Backend: sc_client_api::Backend + Send + Sync + 'static, + TransactionFor: sp_trie::HashDBT, sp_trie::DBValue>, TransactionPool: sc_transaction_pool_api::TransactionPool + 'static, CIDP: CreateInherentDataProviders + 'static, E: CodeExecutor, @@ -296,6 +303,49 @@ where .ok_or(sp_blockchain::Error::Backend(format!("Header not found for {:?}", at))) } + fn block_body(&self, at: Block::Hash) -> Result, sp_blockchain::Error> { + self.client + .block_body(&BlockId::Hash(at))? + .ok_or(sp_blockchain::Error::Backend(format!("Block body not found for {:?}", at))) + } + + fn create_extrinsic_execution_proof( + &self, + extrinsic_index: usize, + parent_header: &Block::Header, + current_hash: Block::Hash, + ) -> Result { + let extrinsics = self.block_body(current_hash)?; + + let encoded_extrinsic = extrinsics[extrinsic_index].encode(); + + let block_builder = BlockBuilder::with_extrinsics( + &*self.client, + parent_header.hash(), + *parent_header.number(), + RecordProof::No, + Default::default(), + &*self.backend, + extrinsics, + )?; + let storage_changes = block_builder.prepare_storage_changes_before(extrinsic_index)?; + + let delta = storage_changes.transaction; + let post_delta_root = storage_changes.transaction_storage_root; + + let execution_proof = cirrus_fraud_proof::prove_execution( + &self.backend, + &*self.code_executor, + self.spawner.clone() as Box, + &BlockId::Hash(parent_header.hash()), + "BlockBuilder_apply_extrinsic", + &encoded_extrinsic, + Some((delta, post_delta_root)), + )?; + + Ok(execution_proof) + } + async fn wait_for_local_receipt( &self, block_hash: Block::Hash, @@ -404,6 +454,7 @@ where Error = sp_consensus::Error, >, Backend: sc_client_api::Backend + Send + Sync + 'static, + TransactionFor: sp_trie::HashDBT, sp_trie::DBValue>, TransactionPool: sc_transaction_pool_api::TransactionPool + 'static, CIDP: CreateInherentDataProviders + 'static, E: CodeExecutor, @@ -556,7 +607,11 @@ where let pre_state_root = as_h256(&execution_receipt.trace[local_trace_idx - 1])?; let post_state_root = as_h256(&execution_receipt.trace[local_trace_idx])?; - let proof = todo!("Create extrinsic proof"); + let proof = self.create_extrinsic_execution_proof( + local_trace_idx - 1, + &parent_header, + execution_receipt.secondary_hash, + )?; // TODO: proof should be a CompactProof. FraudProof { pre_state_root, post_state_root, proof } @@ -607,6 +662,7 @@ pub async fn start_executor + Send + Sync + 'static, + TransactionFor: sp_trie::HashDBT, sp_trie::DBValue>, Spawner: SpawnNamed + Clone + Send + Sync + 'static, Client: HeaderBackend + BlockBackend diff --git a/cumulus/client/cirrus-service/Cargo.toml b/cumulus/client/cirrus-service/Cargo.toml index d626f8a489e09..240d29a0a71cf 100644 --- a/cumulus/client/cirrus-service/Cargo.toml +++ b/cumulus/client/cirrus-service/Cargo.toml @@ -25,6 +25,7 @@ sp-consensus = { git = "https://github.com/paritytech/substrate", rev = "c364008 sp-core = { git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" } sp-inherents = { git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" } sp-runtime = { git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" } +sp-trie = { git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" } # Other deps codec = { package = "parity-scale-codec", version = "3.1.2" } diff --git a/cumulus/client/cirrus-service/src/lib.rs b/cumulus/client/cirrus-service/src/lib.rs index 5d972769dda84..25662a453b2b1 100644 --- a/cumulus/client/cirrus-service/src/lib.rs +++ b/cumulus/client/cirrus-service/src/lib.rs @@ -120,6 +120,9 @@ where >, Spawner: SpawnNamed + Clone + Send + Sync + 'static, Backend: BackendT + 'static, + <>::State as sc_client_api::backend::StateBackend< + sp_api::HashFor, + >>::Transaction: sp_trie::HashDBT, sp_trie::DBValue>, IQ: ImportQueue + 'static, TP: TransactionPool + 'static, CIDP: CreateInherentDataProviders + 'static, From 6be950f72183e9dfa22e6574e030e5d1505389ee Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Mon, 21 Mar 2022 16:40:07 +0800 Subject: [PATCH 05/10] Add test for creating and verifying the extrinsic execution proof --- Cargo.lock | 1 + cumulus/client/cirrus-executor/Cargo.toml | 1 + cumulus/client/cirrus-executor/src/tests.rs | 148 +++++++++++++++++++- 3 files changed, 149 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 8f71b508059ed..51b90c46fd135 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -849,6 +849,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "merkletree", + "pallet-balances", "parity-scale-codec", "parking_lot 0.12.0", "polkadot-node-subsystem", diff --git a/cumulus/client/cirrus-executor/Cargo.toml b/cumulus/client/cirrus-executor/Cargo.toml index 0aa58ac4d9f35..7328a2631a03a 100644 --- a/cumulus/client/cirrus-executor/Cargo.toml +++ b/cumulus/client/cirrus-executor/Cargo.toml @@ -60,6 +60,7 @@ version = "0.10.0" [dev-dependencies] cirrus-test-service = { path = "../../test/service" } +pallet-balances = { git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" } sc-cli = { git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" } sp-keyring = { git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" } substrate-test-runtime = { path = "../../../substrate/substrate-test-runtime" } diff --git a/cumulus/client/cirrus-executor/src/tests.rs b/cumulus/client/cirrus-executor/src/tests.rs index ef1e7eb2d709b..fa7f4115233e6 100644 --- a/cumulus/client/cirrus-executor/src/tests.rs +++ b/cumulus/client/cirrus-executor/src/tests.rs @@ -1,9 +1,13 @@ +use cirrus_block_builder::{BlockBuilder, RecordProof}; +use cirrus_primitives::{Hash, SecondaryApi}; use cirrus_test_service::{ run_primary_chain_validator_node, Keyring::{Alice, Charlie, Dave}, }; +use codec::{Decode, Encode}; use sc_client_api::HeaderBackend; -use sp_runtime::generic::BlockId; +use sp_api::ProvideRuntimeApi; +use sp_runtime::{generic::BlockId, traits::Header as HeaderT}; #[substrate_test_utils::test] async fn test_executor_full_node_catching_up() { @@ -41,3 +45,145 @@ async fn test_executor_full_node_catching_up() { "Executor authority node and full node must have the same state" ); } + +#[substrate_test_utils::test] +async fn execution_proof_creation_and_verification_should_work() { + let mut builder = sc_cli::LoggerBuilder::new(""); + builder.with_colors(false); + let _ = builder.init(); + + let tokio_handle = tokio::runtime::Handle::current(); + + // start alice + let alice = run_primary_chain_validator_node(tokio_handle.clone(), Alice, vec![], true); + + // run cirrus charlie (a secondary chain authority node) + let charlie = cirrus_test_service::TestNodeBuilder::new(tokio_handle.clone(), Charlie) + .connect_to_relay_chain_node(&alice) + .build() + .await; + + // run cirrus dave (a secondary chain full node) + let dave = cirrus_test_service::TestNodeBuilder::new(tokio_handle, Dave) + .connect_to_parachain_node(&charlie) + .connect_to_relay_chain_node(&alice) + .build() + .await; + + // dave is able to sync blocks. + futures::future::join(charlie.wait_for_blocks(3), dave.wait_for_blocks(3)).await; + + let transfer_to_charlie = cirrus_test_service::construct_extrinsic( + &charlie.client, + pallet_balances::Call::transfer { + dest: cirrus_test_service::runtime::Address::Id(Charlie.public().into()), + value: 8, + }, + Alice.into(), + false, + 0, + ); + let transfer_to_dave = cirrus_test_service::construct_extrinsic( + &charlie.client, + pallet_balances::Call::transfer { + dest: cirrus_test_service::runtime::Address::Id(Dave.public().into()), + value: 8, + }, + Alice.into(), + false, + 1, + ); + let transfer_to_charlie_again = cirrus_test_service::construct_extrinsic( + &charlie.client, + pallet_balances::Call::transfer { + dest: cirrus_test_service::runtime::Address::Id(Charlie.public().into()), + value: 88, + }, + Alice.into(), + false, + 2, + ); + + let test_txs = vec![ + transfer_to_charlie.clone(), + transfer_to_dave.clone(), + transfer_to_charlie_again.clone(), + ]; + + for tx in test_txs.iter() { + charlie.send_extrinsic(tx.clone()).await.expect("Failed to send extrinsic"); + } + + // Wait until the test txs are included in the next block. + charlie.wait_for_blocks(1).await; + + let best_hash = charlie.client.info().best_hash; + let header = charlie.client.header(&BlockId::Hash(best_hash)).unwrap().unwrap(); + let parent_header = + charlie.client.header(&BlockId::Hash(*header.parent_hash())).unwrap().unwrap(); + + let create_block_builder = || { + BlockBuilder::with_extrinsics( + &*charlie.client, + parent_header.hash(), + *parent_header.number(), + RecordProof::No, + Default::default(), + &*charlie.backend, + test_txs.clone().into_iter().map(Into::into).collect(), + ) + .unwrap() + }; + + let intermediate_roots = charlie + .client + .runtime_api() + .intermediate_roots(&BlockId::Hash(best_hash)) + .expect("Get intermediate roots"); + println!( + "intermediate_roots: {:?}", + intermediate_roots.clone().into_iter().map(Hash::from).collect::>() + ); + + // Test extrinsic execution. + for (target_extrinsic_index, xt) in test_txs.clone().into_iter().enumerate() { + let storage_changes = create_block_builder() + .prepare_storage_changes_before(target_extrinsic_index) + .unwrap_or_else(|_| { + panic!("Get StorageChanges before extrinsic #{}", target_extrinsic_index) + }); + + let delta = storage_changes.transaction; + let post_delta_root = storage_changes.transaction_storage_root; + + let storage_proof = cirrus_fraud_proof::prove_execution( + &charlie.backend, + &*charlie.code_executor, + charlie.task_manager.spawn_handle(), + &BlockId::Hash(parent_header.hash()), + "BlockBuilder_apply_extrinsic", + &xt.encode(), + Some((delta, post_delta_root)), + ) + .expect("Create extrinsic execution proof"); + + let target_trace_root: Hash = intermediate_roots[target_extrinsic_index].into(); + assert_eq!(target_trace_root, post_delta_root); + + let execution_result = cirrus_fraud_proof::check_execution_proof( + &charlie.backend, + &*charlie.code_executor, + charlie.task_manager.spawn_handle(), + &BlockId::Hash(parent_header.hash()), + "SecondaryApi_apply_extrinsic_with_post_state_root", + &xt.encode(), + post_delta_root, + storage_proof, + ) + .expect("Check extrinsic execution proof"); + + let res = Vec::::decode(&mut execution_result.as_slice()).unwrap(); + let post_execution_root = Hash::decode(&mut res.as_slice()).unwrap(); + assert_eq!(post_execution_root, intermediate_roots[target_extrinsic_index + 1].into()); + } +} From 0ceffee8b3196f85f15ad94a557c8fd5c8277db2 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Mon, 21 Mar 2022 16:44:48 +0800 Subject: [PATCH 06/10] Add `SecondaryApi_initialize_block_with_post_state_root` Used for implementing the execution proof for `initialize_block` --- cumulus/pallets/executive/src/lib.rs | 6 ------ cumulus/parachain-template/runtime/src/lib.rs | 5 +++++ cumulus/primitives/src/lib.rs | 3 +++ cumulus/test/runtime/src/lib.rs | 5 +++++ cumulus/test/service/src/lib.rs | 2 +- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/cumulus/pallets/executive/src/lib.rs b/cumulus/pallets/executive/src/lib.rs index ee2cda4881eba..c9d348ab20966 100644 --- a/cumulus/pallets/executive/src/lib.rs +++ b/cumulus/pallets/executive/src/lib.rs @@ -315,12 +315,6 @@ impl< AllPalletsWithSystem, COnRuntimeUpgrade, >::finalize_block() - // NOTE: Somehow the executor will run into an error `state already discarded for ...` - // if we note the storage root after the origin `finalize_block`(This error might relate to - // the `execute_block`, but not for sure). Since we calculate the final state root anyway, - // this step can just be skipped. - // - // Pallet::::push_root(Self::storage_root()); } // TODO: https://github.com/paritytech/substrate/issues/10711 diff --git a/cumulus/parachain-template/runtime/src/lib.rs b/cumulus/parachain-template/runtime/src/lib.rs index 5a9b13be76557..267df33ac8f02 100644 --- a/cumulus/parachain-template/runtime/src/lib.rs +++ b/cumulus/parachain-template/runtime/src/lib.rs @@ -433,6 +433,11 @@ impl_runtime_apis! { ExecutivePallet::intermediate_roots() } + fn initialize_block_with_post_state_root(header: &::Header) -> Vec { + Executive::initialize_block(header); + Executive::storage_root() + } + fn apply_extrinsic_with_post_state_root(extrinsic: ::Extrinsic) -> Vec { let _ = Executive::apply_extrinsic(extrinsic); Executive::storage_root() diff --git a/cumulus/primitives/src/lib.rs b/cumulus/primitives/src/lib.rs index 1baa1335b54b7..1ab138ab33f53 100644 --- a/cumulus/primitives/src/lib.rs +++ b/cumulus/primitives/src/lib.rs @@ -80,6 +80,9 @@ sp_api::decl_runtime_apis! { /// Returns the intermediate storage roots in an encoded form. fn intermediate_roots() -> Vec<[u8; 32]>; + /// Returns the storage root after initializing the block. + fn initialize_block_with_post_state_root(header: &::Header) -> Vec; + /// Returns the storage root after applying the extrinsic. fn apply_extrinsic_with_post_state_root(extrinsic: ::Extrinsic) -> Vec; } diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs index 9b4881e270545..7659ece6cf599 100644 --- a/cumulus/test/runtime/src/lib.rs +++ b/cumulus/test/runtime/src/lib.rs @@ -434,6 +434,11 @@ impl_runtime_apis! { ExecutivePallet::intermediate_roots() } + fn initialize_block_with_post_state_root(header: &::Header) -> Vec { + Executive::initialize_block(header); + Executive::storage_root() + } + fn apply_extrinsic_with_post_state_root(extrinsic: ::Extrinsic) -> Vec { let _ = Executive::apply_extrinsic(extrinsic); Executive::storage_root() diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index 94f650dc4a6c5..a749245583b67 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -555,7 +555,7 @@ impl TestNode { self.client.wait_for_blocks(count) } - /// Construct and sned an extrinsic to this node. + /// Construct and send an extrinsic to this node. pub async fn construct_and_send_extrinsic( &self, function: impl Into, From 6083197efde422205c24a9c772e2325e58d47f51 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Mon, 21 Mar 2022 16:49:13 +0800 Subject: [PATCH 07/10] Implement the execution proof for `initialize_block` Also update the test accordingly. --- cumulus/client/cirrus-executor/src/lib.rs | 25 +++++++++++- cumulus/client/cirrus-executor/src/tests.rs | 44 ++++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/cumulus/client/cirrus-executor/src/lib.rs b/cumulus/client/cirrus-executor/src/lib.rs index fe1df1268c4f6..2231983a4b65a 100644 --- a/cumulus/client/cirrus-executor/src/lib.rs +++ b/cumulus/client/cirrus-executor/src/lib.rs @@ -586,12 +586,35 @@ where .map_err(|_| Self::Error::InvalidStateRootType) }; + // TODO: abstract the execution proof impl to be reusable in the test. let fraud_proof = if local_trace_idx == 0 { // `initialize_block` execution proof. let pre_state_root = as_h256(parent_header.state_root())?; let post_state_root = as_h256(&execution_receipt.trace[0])?; - let proof = todo!("Create `initialize_block` proof"); + let new_header = Block::Header::new( + block_number, + Default::default(), + Default::default(), + parent_header.hash(), + Default::default(), + ); + + let proof = cirrus_fraud_proof::prove_execution::< + _, + _, + _, + _, + TransactionFor, + >( + &self.backend, + &*self.code_executor, + self.spawner.clone() as Box, + &BlockId::Hash(parent_header.hash()), + "SecondaryApi_initialize_block_with_post_state_root", // TODO: "Core_initalize_block" + &new_header.encode(), + None, + )?; FraudProof { pre_state_root, post_state_root, proof } } else if local_trace_idx == local_receipt.trace.len() - 1 { diff --git a/cumulus/client/cirrus-executor/src/tests.rs b/cumulus/client/cirrus-executor/src/tests.rs index fa7f4115233e6..998535d56c1c3 100644 --- a/cumulus/client/cirrus-executor/src/tests.rs +++ b/cumulus/client/cirrus-executor/src/tests.rs @@ -2,12 +2,16 @@ use cirrus_block_builder::{BlockBuilder, RecordProof}; use cirrus_primitives::{Hash, SecondaryApi}; use cirrus_test_service::{ run_primary_chain_validator_node, + runtime::Header, Keyring::{Alice, Charlie, Dave}, }; use codec::{Decode, Encode}; use sc_client_api::HeaderBackend; use sp_api::ProvideRuntimeApi; -use sp_runtime::{generic::BlockId, traits::Header as HeaderT}; +use sp_runtime::{ + generic::BlockId, + traits::{BlakeTwo256, Header as HeaderT}, +}; #[substrate_test_utils::test] async fn test_executor_full_node_catching_up() { @@ -145,6 +149,44 @@ async fn execution_proof_creation_and_verification_should_work() { intermediate_roots.clone().into_iter().map(Hash::from).collect::>() ); + // Test `initialize_block`. + let storage_proof = { + let new_header = Header::new( + *header.number(), + Default::default(), + Default::default(), + parent_header.hash(), + Default::default(), + ); + + cirrus_fraud_proof::prove_execution::<_, _, _, _, sp_trie::PrefixedMemoryDB>( + &charlie.backend, + &*charlie.code_executor, + charlie.task_manager.spawn_handle(), + &BlockId::Hash(parent_header.hash()), + "SecondaryApi_initialize_block_with_post_state_root", // TODO: "Core_initalize_block" + &new_header.encode(), + None, + ) + .expect("Create `initialize_block` proof") + }; + + let execution_result = cirrus_fraud_proof::check_execution_proof( + &charlie.backend, + &*charlie.code_executor, + charlie.task_manager.spawn_handle(), + &BlockId::Hash(parent_header.hash()), + "SecondaryApi_initialize_block_with_post_state_root", + &header.encode(), + *parent_header.state_root(), + storage_proof, + ) + .expect("Check `initialize_block` proof"); + + let res = Vec::::decode(&mut execution_result.as_slice()).unwrap(); + let post_execution_root = Hash::decode(&mut res.as_slice()).unwrap(); + assert_eq!(post_execution_root, intermediate_roots[0].into()); + // Test extrinsic execution. for (target_extrinsic_index, xt) in test_txs.clone().into_iter().enumerate() { let storage_changes = create_block_builder() From a2e5c248b89c838392127eab43d01cd6c3330706 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Mon, 21 Mar 2022 16:53:35 +0800 Subject: [PATCH 08/10] Implement the execution proof for `finalize_block` `Execution::finalize_block` returns the new header which contains the state root already. --- cumulus/client/block-builder/src/lib.rs | 8 +++++ cumulus/client/cirrus-executor/src/lib.rs | 25 +++++++++++++- cumulus/client/cirrus-executor/src/tests.rs | 37 +++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/cumulus/client/block-builder/src/lib.rs b/cumulus/client/block-builder/src/lib.rs index 545aa17381ab2..2bdc65857f716 100644 --- a/cumulus/client/block-builder/src/lib.rs +++ b/cumulus/client/block-builder/src/lib.rs @@ -283,6 +283,14 @@ where )))) } + /// Returns the state before finalizing the block. + pub fn prepare_storage_changes_before_finalize_block( + &self, + ) -> Result, Block>, Error> { + self.execute_extrinsics()?; + self.collect_storage_changes() + } + /// Consume the builder to build a valid `Block` containing all pushed extrinsics. /// /// Returns the build `Block`, the changes to the storage and an optional `StorageProof` diff --git a/cumulus/client/cirrus-executor/src/lib.rs b/cumulus/client/cirrus-executor/src/lib.rs index 2231983a4b65a..667100eaf0b33 100644 --- a/cumulus/client/cirrus-executor/src/lib.rs +++ b/cumulus/client/cirrus-executor/src/lib.rs @@ -622,7 +622,30 @@ where let pre_state_root = as_h256(&execution_receipt.trace[local_trace_idx - 1])?; let post_state_root = as_h256(&execution_receipt.trace[local_trace_idx])?; - let proof = todo!("Create `finalize_block` proof"); + let block_builder = BlockBuilder::with_extrinsics( + &*self.client, + parent_header.hash(), + *parent_header.number(), + RecordProof::No, + Default::default(), + &*self.backend, + self.block_body(execution_receipt.secondary_hash)?, + )?; + let storage_changes = + block_builder.prepare_storage_changes_before_finalize_block()?; + + let delta = storage_changes.transaction; + let post_delta_root = storage_changes.transaction_storage_root; + + let proof = cirrus_fraud_proof::prove_execution( + &self.backend, + &*self.code_executor, + self.spawner.clone() as Box, + &BlockId::Hash(parent_header.hash()), + "BlockBuilder_finalize_block", + Default::default(), + Some((delta, post_delta_root)), + )?; FraudProof { pre_state_root, post_state_root, proof } } else { diff --git a/cumulus/client/cirrus-executor/src/tests.rs b/cumulus/client/cirrus-executor/src/tests.rs index 998535d56c1c3..e02bcaa84fe01 100644 --- a/cumulus/client/cirrus-executor/src/tests.rs +++ b/cumulus/client/cirrus-executor/src/tests.rs @@ -228,4 +228,41 @@ async fn execution_proof_creation_and_verification_should_work() { let post_execution_root = Hash::decode(&mut res.as_slice()).unwrap(); assert_eq!(post_execution_root, intermediate_roots[target_extrinsic_index + 1].into()); } + + // Test `finalize_block` + let storage_changes = create_block_builder() + .prepare_storage_changes_before_finalize_block() + .expect("Get StorageChanges before `finalize_block`"); + + let delta = storage_changes.transaction; + let post_delta_root = storage_changes.transaction_storage_root; + + assert_eq!(post_delta_root, intermediate_roots.last().unwrap().into()); + + let storage_proof = cirrus_fraud_proof::prove_execution( + &charlie.backend, + &*charlie.code_executor, + charlie.task_manager.spawn_handle(), + &BlockId::Hash(parent_header.hash()), + "BlockBuilder_finalize_block", + Default::default(), + Some((delta, post_delta_root)), + ) + .expect("Create `finalize_block` proof"); + + let execution_result = cirrus_fraud_proof::check_execution_proof( + &charlie.backend, + &*charlie.code_executor, + charlie.task_manager.spawn_handle(), + &BlockId::Hash(parent_header.hash()), + "BlockBuilder_finalize_block", + Default::default(), + post_delta_root, + storage_proof, + ) + .expect("Check `finalize_block` proof"); + + let new_header = Header::decode(&mut execution_result.as_slice()).unwrap(); + let post_execution_root = *new_header.state_root(); + assert_eq!(post_execution_root, *header.state_root()); } From 6a8bfba0ba882c53e1059833b2d8c6b8e4b70ced Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Tue, 22 Mar 2022 11:04:58 +0800 Subject: [PATCH 09/10] Apply review suggestions --- cumulus/client/cirrus-executor/src/lib.rs | 41 ++++++++++++++--------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/cumulus/client/cirrus-executor/src/lib.rs b/cumulus/client/cirrus-executor/src/lib.rs index 667100eaf0b33..2014cca215e13 100644 --- a/cumulus/client/cirrus-executor/src/lib.rs +++ b/cumulus/client/cirrus-executor/src/lib.rs @@ -317,7 +317,13 @@ where ) -> Result { let extrinsics = self.block_body(current_hash)?; - let encoded_extrinsic = extrinsics[extrinsic_index].encode(); + let encoded_extrinsic = extrinsics + .get(extrinsic_index) + .ok_or(GossipMessageError::InvalidExtrinsicIndex { + index: extrinsic_index, + max: extrinsics.len() - 1, + })? + .encode(); let block_builder = BlockBuilder::with_extrinsics( &*self.client, @@ -332,7 +338,8 @@ where let delta = storage_changes.transaction; let post_delta_root = storage_changes.transaction_storage_root; - + // TODO: way to call some runtime api against any specific state instead of having + // to work with String API directly. let execution_proof = cirrus_fraud_proof::prove_execution( &self.backend, &*self.code_executor, @@ -423,6 +430,8 @@ pub enum GossipMessageError { BundleEquivocation, #[error("State root not using H256")] InvalidStateRootType, + #[error("Invalid extrinsic index for creating the execution proof, got: {index}, max: {max}")] + InvalidExtrinsicIndex { index: usize, max: usize }, #[error(transparent)] Client(#[from] sp_blockchain::Error), #[error(transparent)] @@ -563,20 +572,18 @@ where // TODO: What happens for this obvious error? if local_receipt.trace.len() != execution_receipt.trace.len() {} - if let Some(local_trace_idx) = local_receipt + if let Some((local_trace_idx, local_root)) = local_receipt .trace .iter() .enumerate() .zip(execution_receipt.trace.iter().enumerate()) - .find_map( - |((local_idx, local_root), (_, external_root))| { - if local_root != external_root { - Some(local_idx) - } else { - None - } - }, - ) { + .find_map(|((local_idx, local_root), (_, external_root))| { + if local_root != external_root { + Some((local_idx, local_root)) + } else { + None + } + }) { let header = self.header(execution_receipt.secondary_hash)?; let parent_header = self.header(*header.parent_hash())?; @@ -590,7 +597,7 @@ where let fraud_proof = if local_trace_idx == 0 { // `initialize_block` execution proof. let pre_state_root = as_h256(parent_header.state_root())?; - let post_state_root = as_h256(&execution_receipt.trace[0])?; + let post_state_root = as_h256(local_root)?; let new_header = Block::Header::new( block_number, @@ -600,6 +607,8 @@ where Default::default(), ); + // TODO: way to call some runtime api against any specific state instead of having + // to work with String API directly. let proof = cirrus_fraud_proof::prove_execution::< _, _, @@ -620,7 +629,7 @@ where } else if local_trace_idx == local_receipt.trace.len() - 1 { // `finalize_block` execution proof. let pre_state_root = as_h256(&execution_receipt.trace[local_trace_idx - 1])?; - let post_state_root = as_h256(&execution_receipt.trace[local_trace_idx])?; + let post_state_root = as_h256(local_root)?; let block_builder = BlockBuilder::with_extrinsics( &*self.client, @@ -637,6 +646,8 @@ where let delta = storage_changes.transaction; let post_delta_root = storage_changes.transaction_storage_root; + // TODO: way to call some runtime api against any specific state instead of having + // to work with String API directly. let proof = cirrus_fraud_proof::prove_execution( &self.backend, &*self.code_executor, @@ -651,7 +662,7 @@ where } else { // Regular extrinsic execution proof. let pre_state_root = as_h256(&execution_receipt.trace[local_trace_idx - 1])?; - let post_state_root = as_h256(&execution_receipt.trace[local_trace_idx])?; + let post_state_root = as_h256(local_root)?; let proof = self.create_extrinsic_execution_proof( local_trace_idx - 1, From 7b4932f2efc907e383412c1dc52f40087fbd2b2f Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Tue, 22 Mar 2022 11:21:52 +0800 Subject: [PATCH 10/10] Add an invalid execution proof test --- cumulus/client/cirrus-executor/src/tests.rs | 122 +++++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/cumulus/client/cirrus-executor/src/tests.rs b/cumulus/client/cirrus-executor/src/tests.rs index e02bcaa84fe01..65e749c412c62 100644 --- a/cumulus/client/cirrus-executor/src/tests.rs +++ b/cumulus/client/cirrus-executor/src/tests.rs @@ -6,7 +6,7 @@ use cirrus_test_service::{ Keyring::{Alice, Charlie, Dave}, }; use codec::{Decode, Encode}; -use sc_client_api::HeaderBackend; +use sc_client_api::{HeaderBackend, StorageProof}; use sp_api::ProvideRuntimeApi; use sp_runtime::{ generic::BlockId, @@ -266,3 +266,123 @@ async fn execution_proof_creation_and_verification_should_work() { let post_execution_root = *new_header.state_root(); assert_eq!(post_execution_root, *header.state_root()); } + +#[substrate_test_utils::test] +async fn invalid_execution_proof_should_not_work() { + let mut builder = sc_cli::LoggerBuilder::new(""); + builder.with_colors(false); + let _ = builder.init(); + + let tokio_handle = tokio::runtime::Handle::current(); + + // start alice + let alice = run_primary_chain_validator_node(tokio_handle.clone(), Alice, vec![], true); + + // run cirrus charlie (a secondary chain authority node) + let charlie = cirrus_test_service::TestNodeBuilder::new(tokio_handle.clone(), Charlie) + .connect_to_relay_chain_node(&alice) + .build() + .await; + + // run cirrus dave (a secondary chain full node) + let dave = cirrus_test_service::TestNodeBuilder::new(tokio_handle, Dave) + .connect_to_parachain_node(&charlie) + .connect_to_relay_chain_node(&alice) + .build() + .await; + + // dave is able to sync blocks. + futures::future::join(charlie.wait_for_blocks(3), dave.wait_for_blocks(3)).await; + + let transfer_to_charlie = cirrus_test_service::construct_extrinsic( + &charlie.client, + pallet_balances::Call::transfer { + dest: cirrus_test_service::runtime::Address::Id(Charlie.public().into()), + value: 8, + }, + Alice.into(), + false, + 0, + ); + + let transfer_to_charlie_again = cirrus_test_service::construct_extrinsic( + &charlie.client, + pallet_balances::Call::transfer { + dest: cirrus_test_service::runtime::Address::Id(Charlie.public().into()), + value: 8, + }, + Alice.into(), + false, + 1, + ); + + let test_txs = vec![transfer_to_charlie.clone(), transfer_to_charlie_again.clone()]; + + for tx in test_txs.iter() { + charlie.send_extrinsic(tx.clone()).await.expect("Failed to send extrinsic"); + } + + // Wait until the test txs are included in the next block. + charlie.wait_for_blocks(1).await; + + let best_hash = charlie.client.info().best_hash; + let header = charlie.client.header(&BlockId::Hash(best_hash)).unwrap().unwrap(); + let parent_header = + charlie.client.header(&BlockId::Hash(*header.parent_hash())).unwrap().unwrap(); + + let create_block_builder = || { + BlockBuilder::with_extrinsics( + &*charlie.client, + parent_header.hash(), + *parent_header.number(), + RecordProof::No, + Default::default(), + &*charlie.backend, + test_txs.clone().into_iter().map(Into::into).collect(), + ) + .unwrap() + }; + + let create_extrinsic_proof = |extrinsic_index: usize| { + let storage_changes = create_block_builder() + .prepare_storage_changes_before(extrinsic_index) + .unwrap_or_else(|_| panic!("Get StorageChanges before extrinsic #{}", extrinsic_index)); + + let delta = storage_changes.transaction; + let post_delta_root = storage_changes.transaction_storage_root; + + let proof = cirrus_fraud_proof::prove_execution( + &charlie.backend, + &*charlie.code_executor, + charlie.task_manager.spawn_handle(), + &BlockId::Hash(parent_header.hash()), + "BlockBuilder_apply_extrinsic", + &test_txs[extrinsic_index].encode(), + Some((delta.clone(), post_delta_root.clone())), + ) + .expect("Create extrinsic execution proof"); + + (proof, delta, post_delta_root) + }; + + let (proof0, _delta0, post_delta_root0) = create_extrinsic_proof(0); + let (proof1, _delta1, post_delta_root1) = create_extrinsic_proof(1); + + let check_proof = |post_delta_root: Hash, proof: StorageProof| { + cirrus_fraud_proof::check_execution_proof( + &charlie.backend, + &*charlie.code_executor, + charlie.task_manager.spawn_handle(), + &BlockId::Hash(parent_header.hash()), + "SecondaryApi_apply_extrinsic_with_post_state_root", + &transfer_to_charlie_again.encode(), + post_delta_root, + proof, + ) + }; + + assert!(check_proof(post_delta_root1, proof0.clone()).is_err()); + assert!(check_proof(post_delta_root0, proof1.clone()).is_err()); + assert!(check_proof(post_delta_root0, proof0).is_ok()); + assert!(check_proof(post_delta_root1, proof1).is_ok()); +}