From 6f6e175f9c30a8b4b01573b78e57f82a2c17fb2e Mon Sep 17 00:00:00 2001 From: linning Date: Sat, 6 Jul 2024 01:31:30 +0800 Subject: [PATCH 01/10] Introduce the extract_xdm_mmr_proof runtime API Signed-off-by: linning --- crates/sc-domains/src/lib.rs | 2 +- .../src/host_functions.rs | 26 ++++----- crates/subspace-fake-runtime-api/Cargo.toml | 1 + crates/subspace-fake-runtime-api/src/lib.rs | 7 ++- .../src/malicious_bundle_producer.rs | 4 +- crates/subspace-runtime/src/lib.rs | 12 ++++- crates/subspace-service/src/lib.rs | 8 +-- .../block-preprocessor/src/inherents.rs | 12 +++-- domains/client/block-preprocessor/src/lib.rs | 6 ++- .../src/stateless_runtime.rs | 54 +++++++++++++------ .../src/message_listener.rs | 2 +- .../domain-operator/src/bundle_processor.rs | 4 +- .../src/domain_block_processor.rs | 10 ++-- .../src/domain_bundle_producer.rs | 6 ++- .../src/domain_bundle_proposer.rs | 5 +- .../domain-operator/src/domain_worker.rs | 4 +- .../client/domain-operator/src/fraud_proof.rs | 6 ++- .../client/domain-operator/src/operator.rs | 4 +- domains/client/relayer/src/lib.rs | 4 +- domains/client/relayer/src/worker.rs | 2 +- .../src/host_functions.rs | 9 ++-- domains/primitives/messenger/src/lib.rs | 11 +++- domains/runtime/auto-id/src/lib.rs | 12 ++++- domains/runtime/evm/src/lib.rs | 12 ++++- domains/service/src/domain.rs | 18 ++++--- domains/test/runtime/evm/src/lib.rs | 12 ++++- domains/test/service/src/domain.rs | 4 +- test/subspace-test-runtime/src/lib.rs | 12 ++++- test/subspace-test-service/src/lib.rs | 2 +- 29 files changed, 190 insertions(+), 81 deletions(-) diff --git a/crates/sc-domains/src/lib.rs b/crates/sc-domains/src/lib.rs index d5305d61c0..0e69621f2f 100644 --- a/crates/sc-domains/src/lib.rs +++ b/crates/sc-domains/src/lib.rs @@ -82,7 +82,7 @@ where CBlock::Hash: From + Into, CClient: HeaderBackend + ProvideRuntimeApi + 'static, CClient::Api: MmrApi> - + MessengerApi + + MessengerApi, CBlock::Hash> + DomainsApi, Executor: CodeExecutor + RuntimeVersionOf, { diff --git a/crates/sp-domains-fraud-proof/src/host_functions.rs b/crates/sp-domains-fraud-proof/src/host_functions.rs index 69ac1e7996..ce5c595219 100644 --- a/crates/sp-domains-fraud-proof/src/host_functions.rs +++ b/crates/sp-domains-fraud-proof/src/host_functions.rs @@ -165,7 +165,7 @@ where Client: BlockBackend + HeaderBackend + ProvideRuntimeApi, Client::Api: DomainsApi + BundleProducerElectionApi - + MessengerApi, + + MessengerApi, Block::Hash>, Executor: CodeExecutor + RuntimeVersionOf, EFC: Fn(Arc, Arc) -> Box> + Send + Sync, { @@ -186,7 +186,7 @@ where let runtime_code = self.get_domain_runtime_code(consensus_block_hash, domain_id)?; let timestamp = runtime_api.timestamp(consensus_block_hash.into()).ok()?; - let domain_stateless_runtime = StatelessRuntime::::new( + let domain_stateless_runtime = StatelessRuntime::::new( self.domain_executor.clone(), runtime_code.into(), ); @@ -208,7 +208,7 @@ where .domain_chains_allowlist_update(consensus_block_hash.into(), domain_id) .ok()??; - let domain_stateless_runtime = StatelessRuntime::::new( + let domain_stateless_runtime = StatelessRuntime::::new( self.domain_executor.clone(), runtime_code.into(), ); @@ -230,7 +230,7 @@ where .ok()?; let runtime_code = self.get_domain_runtime_code(consensus_block_hash, domain_id)?; - let domain_stateless_runtime = StatelessRuntime::::new( + let domain_stateless_runtime = StatelessRuntime::::new( self.domain_executor.clone(), runtime_code.into(), ); @@ -280,7 +280,7 @@ where if let Some(upgraded_runtime) = maybe_upgraded_runtime { let runtime_code = self.get_domain_runtime_code(consensus_block_hash, domain_id)?; - let domain_stateless_runtime = StatelessRuntime::::new( + let domain_stateless_runtime = StatelessRuntime::::new( self.domain_executor.clone(), runtime_code.into(), ); @@ -371,7 +371,7 @@ where opaque_extrinsic: OpaqueExtrinsic, ) -> Option { let runtime_code = self.get_domain_runtime_code(consensus_block_hash, domain_id)?; - let mut domain_stateless_runtime = StatelessRuntime::::new( + let mut domain_stateless_runtime = StatelessRuntime::::new( self.domain_executor.clone(), runtime_code.into(), ); @@ -416,7 +416,7 @@ where req: StorageKeyRequest, ) -> Option> { let runtime_code = self.get_domain_runtime_code(consensus_block_hash, domain_id)?; - let domain_stateless_runtime = StatelessRuntime::::new( + let domain_stateless_runtime = StatelessRuntime::::new( self.domain_executor.clone(), runtime_code.into(), ); @@ -485,7 +485,7 @@ where Client: BlockBackend + HeaderBackend + ProvideRuntimeApi, Client::Api: DomainsApi + BundleProducerElectionApi - + MessengerApi, + + MessengerApi, Block::Hash>, Executor: CodeExecutor + RuntimeVersionOf, EFC: Fn(Arc, Arc) -> Box> + Send + Sync, { @@ -644,7 +644,7 @@ where extrinsics.push(ext); } - let domain_stateless_runtime = StatelessRuntime::::new( + let domain_stateless_runtime = StatelessRuntime::::new( self.domain_executor.clone(), domain_runtime_code.into(), ); @@ -746,7 +746,7 @@ where maybe_sudo_runtime_call, } = domain_inherent_extrinsic_data; - let domain_stateless_runtime = StatelessRuntime::::new( + let domain_stateless_runtime = StatelessRuntime::::new( self.domain_executor.clone(), domain_runtime_code.into(), ); @@ -806,7 +806,7 @@ where domain_runtime_code: Vec, req: DomainStorageKeyRequest, ) -> Option> { - let domain_stateless_runtime = StatelessRuntime::::new( + let domain_stateless_runtime = StatelessRuntime::::new( self.domain_executor.clone(), domain_runtime_code.into(), ); @@ -823,7 +823,7 @@ where domain_runtime_code: Vec, call: StatelessDomainRuntimeCall, ) -> Option { - let domain_stateless_runtime = StatelessRuntime::::new( + let domain_stateless_runtime = StatelessRuntime::::new( self.domain_executor.clone(), domain_runtime_code.into(), ); @@ -868,7 +868,7 @@ where domain_runtime_code: Vec, bundle_body: Vec, ) -> Option { - let domain_stateless_runtime = StatelessRuntime::::new( + let domain_stateless_runtime = StatelessRuntime::::new( self.domain_executor.clone(), domain_runtime_code.into(), ); diff --git a/crates/subspace-fake-runtime-api/Cargo.toml b/crates/subspace-fake-runtime-api/Cargo.toml index b5db0cb9f1..746ed614e6 100644 --- a/crates/subspace-fake-runtime-api/Cargo.toml +++ b/crates/subspace-fake-runtime-api/Cargo.toml @@ -33,6 +33,7 @@ sp-objects = { default-features = false, path = "../sp-objects" } sp-offchain = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "0cbfcb0232bbf71ac5b14cc8c99bf043cec420ef" } sp-runtime = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "0cbfcb0232bbf71ac5b14cc8c99bf043cec420ef" } sp-session = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "0cbfcb0232bbf71ac5b14cc8c99bf043cec420ef" } +sp-subspace-mmr = { default-features = false, path = "../sp-subspace-mmr" } sp-transaction-pool = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "0cbfcb0232bbf71ac5b14cc8c99bf043cec420ef" } sp-version = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "0cbfcb0232bbf71ac5b14cc8c99bf043cec420ef" } subspace-core-primitives = { version = "0.1.0", default-features = false, path = "../subspace-core-primitives" } diff --git a/crates/subspace-fake-runtime-api/src/lib.rs b/crates/subspace-fake-runtime-api/src/lib.rs index 84ea54d855..a454b755ae 100644 --- a/crates/subspace-fake-runtime-api/src/lib.rs +++ b/crates/subspace-fake-runtime-api/src/lib.rs @@ -37,6 +37,7 @@ use sp_messenger::messages::{ use sp_runtime::traits::{Block as BlockT, NumberFor}; use sp_runtime::transaction_validity::{TransactionSource, TransactionValidity}; use sp_runtime::{ApplyExtrinsicResult, ExtrinsicInclusionMode}; +use sp_subspace_mmr::ConsensusChainMmrLeafProof; use sp_version::RuntimeVersion; use std::collections::btree_map::BTreeMap; use std::collections::btree_set::BTreeSet; @@ -360,13 +361,17 @@ sp_api::impl_runtime_apis! { } } - impl sp_messenger::MessengerApi for Runtime { + impl sp_messenger::MessengerApi::Hash> for Runtime { fn is_xdm_valid( _extrinsic: Vec, ) -> Option { unreachable!() } + fn extract_xdm_mmr_proof(_ext: &::Extrinsic) -> Option::Hash, sp_core::H256>> { + unreachable!() + } + fn confirmed_domain_block_storage_key(_domain_id: DomainId) -> Vec { unreachable!() } diff --git a/crates/subspace-malicious-operator/src/malicious_bundle_producer.rs b/crates/subspace-malicious-operator/src/malicious_bundle_producer.rs index 643620824e..b381e548c2 100644 --- a/crates/subspace-malicious-operator/src/malicious_bundle_producer.rs +++ b/crates/subspace-malicious-operator/src/malicious_bundle_producer.rs @@ -26,7 +26,8 @@ use sp_domains::{ }; use sp_keyring::Sr25519Keyring; use sp_keystore::{Keystore, KeystorePtr}; -use sp_runtime::traits::Block as BlockT; +use sp_messenger::MessengerApi; +use sp_runtime::traits::{Block as BlockT, NumberFor}; use sp_runtime::{generic, RuntimeAppPublic}; use sp_transaction_pool::runtime_api::TaggedTransactionQueue; use std::error::Error; @@ -104,6 +105,7 @@ where + 'static, Client::Api: BlockBuilder + DomainCoreApi + + MessengerApi, ::Hash> + TaggedTransactionQueue, CClient: HeaderBackend + ProvideRuntimeApi + 'static, CClient::Api: DomainsApi::Header> diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 8592cc78ee..c51f3250fc 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -1303,13 +1303,23 @@ impl_runtime_apis! { } } - impl sp_messenger::MessengerApi for Runtime { + impl sp_messenger::MessengerApi::Hash> for Runtime { fn is_xdm_valid( extrinsic: Vec, ) -> Option { is_xdm_valid(extrinsic) } + fn extract_xdm_mmr_proof(ext: &::Extrinsic) -> Option::Hash, sp_core::H256>> { + match &ext.function { + RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg }) + | RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => { + Some(msg.proof.consensus_mmr_proof()) + } + _ => None, + } + } + fn confirmed_domain_block_storage_key(domain_id: DomainId) -> Vec { Domains::confirmed_domain_block_storage_key(domain_id) } diff --git a/crates/subspace-service/src/lib.rs b/crates/subspace-service/src/lib.rs index 907ef0aa67..162cf8c517 100644 --- a/crates/subspace-service/src/lib.rs +++ b/crates/subspace-service/src/lib.rs @@ -257,7 +257,7 @@ where + DomainsApi + BundleProducerElectionApi + MmrApi> - + MessengerApi, + + MessengerApi, Block::Hash>, { fn extensions_for( &self, @@ -468,7 +468,7 @@ where + BundleProducerElectionApi + ObjectsApi + MmrApi> - + MessengerApi, + + MessengerApi, ::Hash>, { let telemetry = config .telemetry_endpoints @@ -636,7 +636,7 @@ where + FraudProofApi + SubspaceApi + MmrApi> - + MessengerApi, + + MessengerApi, ::Hash>, { /// Task manager. pub task_manager: TaskManager, @@ -699,7 +699,7 @@ where + FraudProofApi + ObjectsApi + MmrApi - + MessengerApi, + + MessengerApi, ::Hash>, { let PartialComponents { client, diff --git a/domains/client/block-preprocessor/src/inherents.rs b/domains/client/block-preprocessor/src/inherents.rs index b40ec7cc7d..95ab56561a 100644 --- a/domains/client/block-preprocessor/src/inherents.rs +++ b/domains/client/block-preprocessor/src/inherents.rs @@ -17,7 +17,7 @@ use sp_blockchain::HeaderBackend; use sp_domains::{DomainId, DomainsApi, DomainsDigestItem}; use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvider}; use sp_messenger::MessengerApi; -use sp_runtime::traits::{Block as BlockT, Header}; +use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; use sp_timestamp::InherentType; use std::error::Error; use std::sync::Arc; @@ -32,7 +32,8 @@ where CBlock: BlockT, Block: BlockT, CClient: ProvideRuntimeApi + HeaderBackend, - CClient::Api: DomainsApi + MessengerApi, + CClient::Api: + DomainsApi + MessengerApi, CBlock::Hash>, { let create_inherent_data_providers = CreateInherentDataProvider::new(consensus_client, Some(consensus_block_hash), domain_id); @@ -172,7 +173,8 @@ where Block: BlockT, CBlock: BlockT, CClient: ProvideRuntimeApi + HeaderBackend, - CClient::Api: DomainsApi + MessengerApi, + CClient::Api: + DomainsApi + MessengerApi, CBlock::Hash>, { type InherentDataProviders = ( sp_timestamp::InherentDataProvider, @@ -215,7 +217,9 @@ where // TODO: remove version check before next network let messenger_api_version = runtime_api - .api_version::>(consensus_block_hash)? + .api_version::, CBlock::Hash>>( + consensus_block_hash, + )? // safe to return default version as 1 since there will always be version 1. .unwrap_or(1); diff --git a/domains/client/block-preprocessor/src/lib.rs b/domains/client/block-preprocessor/src/lib.rs index 2f8cd477be..3686936f8f 100644 --- a/domains/client/block-preprocessor/src/lib.rs +++ b/domains/client/block-preprocessor/src/lib.rs @@ -119,14 +119,16 @@ where CBlock::Hash: From, NumberFor: From>, Client: HeaderBackend + ProvideRuntimeApi + 'static, - Client::Api: DomainCoreApi + MessengerApi, + Client::Api: DomainCoreApi + MessengerApi, CBlock::Hash>, CClient: HeaderBackend + BlockBackend + ProvideRuntimeApi + Send + Sync + 'static, - CClient::Api: DomainsApi + MessengerApi, + CClient::Api: DomainsApi + + MessengerApi, CBlock::Hash> + + MmrApi>, ReceiptValidator: ValidateReceipt, { pub fn new( diff --git a/domains/client/block-preprocessor/src/stateless_runtime.rs b/domains/client/block-preprocessor/src/stateless_runtime.rs index f41c871b63..5cfc11ea31 100644 --- a/domains/client/block-preprocessor/src/stateless_runtime.rs +++ b/domains/client/block-preprocessor/src/stateless_runtime.rs @@ -30,16 +30,17 @@ use subspace_runtime_primitives::Moment; /// - This perfectly fits the runtime APIs that are purely stateless, but it's also usable /// for the stateful APIs. If some states are used inside a runtime api, these states must /// be provided and set before dispatching otherwise [`StatelessRuntime`] may give invalid output. -pub struct StatelessRuntime { +pub struct StatelessRuntime { executor: Arc, runtime_code: Cow<'static, [u8]>, storage: Storage, extension_factory: Box>, - _marker: PhantomData, + _marker: PhantomData<(CBlock, Block)>, } -impl Core for StatelessRuntime +impl Core for StatelessRuntime where + CBlock: BlockT, Block: BlockT, Executor: CodeExecutor + RuntimeVersionOf, { @@ -53,8 +54,9 @@ where } } -impl DomainCoreApi for StatelessRuntime +impl DomainCoreApi for StatelessRuntime where + CBlock: BlockT, Block: BlockT, Executor: CodeExecutor + RuntimeVersionOf, { @@ -68,8 +70,10 @@ where } } -impl MessengerApi for StatelessRuntime +impl MessengerApi, CBlock::Hash> + for StatelessRuntime where + CBlock: BlockT, Block: BlockT, NumberFor: Codec, Executor: CodeExecutor + RuntimeVersionOf, @@ -84,9 +88,10 @@ where } } -impl RelayerApi, BlockNumber, BlockHash> - for StatelessRuntime +impl RelayerApi, BlockNumber, BlockHash> + for StatelessRuntime where + CBlock: BlockT, Block: BlockT, NumberFor: Codec, Executor: CodeExecutor + RuntimeVersionOf, @@ -101,8 +106,9 @@ where } } -impl DomainSudoApi for StatelessRuntime +impl DomainSudoApi for StatelessRuntime where + CBlock: BlockT, Block: BlockT, NumberFor: Codec, Executor: CodeExecutor + RuntimeVersionOf, @@ -117,7 +123,7 @@ where } } -impl FetchRuntimeCode for StatelessRuntime { +impl FetchRuntimeCode for StatelessRuntime { fn fetch_runtime_code(&self) -> Option> { Some(self.runtime_code.clone()) } @@ -125,8 +131,9 @@ impl FetchRuntimeCode for StatelessRuntime { pub type ExtractSignerResult = Vec<(Option, ::Extrinsic)>; -impl StatelessRuntime +impl StatelessRuntime where + CBlock: BlockT, Block: BlockT, Executor: CodeExecutor + RuntimeVersionOf, { @@ -200,16 +207,17 @@ where } pub fn outbox_storage_key(&self, message_key: MessageKey) -> Result, ApiError> { - let storage_key = >::outbox_storage_key( - self, - Default::default(), - message_key, - )?; + let storage_key = + , CBlock::Hash>>::outbox_storage_key( + self, + Default::default(), + message_key, + )?; Ok(storage_key) } pub fn inbox_response_storage_key(&self, message_key: MessageKey) -> Result, ApiError> { - let storage_key = >::inbox_response_storage_key( + let storage_key = , CBlock::Hash>>::inbox_response_storage_key( self, Default::default(), message_key, @@ -287,7 +295,19 @@ where } pub fn is_valid_xdm(&self, extrinsic: Vec) -> Result, ApiError> { - >::is_xdm_valid(self, Default::default(), extrinsic) + , CBlock::Hash>>>::is_xdm_valid(self, Default::default(), extrinsic) + } + + pub fn extract_xdm_mmr_proof( + &self, + extrinsic: &::Extrinsic, + ) -> Result>, ApiError> { + , CBlock::Hash>>::extract_xdm_mmr_proof( + self, + Default::default(), + extrinsic, + ) + .map(|maybe_proof| maybe_proof.map(|p| p.encode())) } pub fn decode_extrinsic( diff --git a/domains/client/cross-domain-message-gossip/src/message_listener.rs b/domains/client/cross-domain-message-gossip/src/message_listener.rs index 10c877e2c4..5ce69640a4 100644 --- a/domains/client/cross-domain-message-gossip/src/message_listener.rs +++ b/domains/client/cross-domain-message-gossip/src/message_listener.rs @@ -395,7 +395,7 @@ where let storage_key = match storage_key_cache.get(&(runtime_hash, self_chain_id, channel_id)) { None => { let domain_stateless_runtime = - StatelessRuntime::::new(executor.clone(), domain_runtime.into()); + StatelessRuntime::::new(executor.clone(), domain_runtime.into()); let storage_key = StorageKey( domain_stateless_runtime.channel_storage_key(self_chain_id, channel_id)?, ); diff --git a/domains/client/domain-operator/src/bundle_processor.rs b/domains/client/domain-operator/src/bundle_processor.rs index 3f3b7c18bf..2cfcaa65e3 100644 --- a/domains/client/domain-operator/src/bundle_processor.rs +++ b/domains/client/domain-operator/src/bundle_processor.rs @@ -137,7 +137,7 @@ where + Finalizer + 'static, Client::Api: DomainCoreApi - + MessengerApi + + MessengerApi, CBlock::Hash> + sp_block_builder::BlockBuilder + sp_api::ApiExt, CClient: HeaderBackend @@ -147,7 +147,7 @@ where + ProvideRuntimeApi + 'static, CClient::Api: DomainsApi - + MessengerApi + + MessengerApi, CBlock::Hash> + FraudProofApi + MmrApi> + 'static, diff --git a/domains/client/domain-operator/src/domain_block_processor.rs b/domains/client/domain-operator/src/domain_block_processor.rs index 9ca84c266e..d570cb20c6 100644 --- a/domains/client/domain-operator/src/domain_block_processor.rs +++ b/domains/client/domain-operator/src/domain_block_processor.rs @@ -122,7 +122,9 @@ where + BlockBackend + ProvideRuntimeApi + 'static, - CClient::Api: DomainsApi + MessengerApi + 'static, + CClient::Api: DomainsApi + + MessengerApi, CBlock::Hash> + + 'static, Backend: sc_client_api::Backend + 'static, { /// Returns a list of consensus blocks waiting to be processed if any. @@ -728,8 +730,10 @@ where + AuxStore + ProvideRuntimeApi + 'static, - Client::Api: - DomainCoreApi + sp_block_builder::BlockBuilder + sp_api::ApiExt, + Client::Api: DomainCoreApi + + sp_block_builder::BlockBuilder + + sp_api::ApiExt + + MessengerApi, CBlock::Hash>, CClient: HeaderBackend + BlockBackend + ProofProvider diff --git a/domains/client/domain-operator/src/domain_bundle_producer.rs b/domains/client/domain-operator/src/domain_bundle_producer.rs index c19e8dad41..8b8651e6f4 100644 --- a/domains/client/domain-operator/src/domain_bundle_producer.rs +++ b/domains/client/domain-operator/src/domain_bundle_producer.rs @@ -14,6 +14,7 @@ use sp_domains::{ OperatorSignature, SealedBundleHeader, }; use sp_keystore::KeystorePtr; +use sp_messenger::MessengerApi; use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; use sp_runtime::RuntimeAppPublic; use sp_transaction_pool::runtime_api::TaggedTransactionQueue; @@ -79,7 +80,10 @@ where NumberFor: Into>, NumberFor: Into>, Client: HeaderBackend + BlockBackend + AuxStore + ProvideRuntimeApi, - Client::Api: BlockBuilder + DomainCoreApi + TaggedTransactionQueue, + Client::Api: BlockBuilder + + DomainCoreApi + + TaggedTransactionQueue + + MessengerApi, CBlock::Hash>, CClient: HeaderBackend + ProvideRuntimeApi, CClient::Api: DomainsApi + BundleProducerElectionApi, TransactionPool: diff --git a/domains/client/domain-operator/src/domain_bundle_proposer.rs b/domains/client/domain-operator/src/domain_bundle_proposer.rs index 657f76baa6..4a4fd3fefc 100644 --- a/domains/client/domain-operator/src/domain_bundle_proposer.rs +++ b/domains/client/domain-operator/src/domain_bundle_proposer.rs @@ -95,7 +95,10 @@ where CBlock: BlockT, NumberFor: Into>, Client: HeaderBackend + BlockBackend + AuxStore + ProvideRuntimeApi, - Client::Api: BlockBuilder + DomainCoreApi + TaggedTransactionQueue, + Client::Api: BlockBuilder + + DomainCoreApi + + TaggedTransactionQueue + + MessengerApi, CBlock::Hash>, CClient: HeaderBackend + ProvideRuntimeApi, CClient::Api: DomainsApi, TransactionPool: diff --git a/domains/client/domain-operator/src/domain_worker.rs b/domains/client/domain-operator/src/domain_worker.rs index 72b162714a..384e78a5b2 100644 --- a/domains/client/domain-operator/src/domain_worker.rs +++ b/domains/client/domain-operator/src/domain_worker.rs @@ -79,7 +79,7 @@ pub(super) async fn start_worker< + Finalizer + 'static, Client::Api: DomainCoreApi - + MessengerApi + + MessengerApi, CBlock::Hash> + BlockBuilder + sp_api::ApiExt + TaggedTransactionQueue, @@ -91,7 +91,7 @@ pub(super) async fn start_worker< + BlockchainEvents + 'static, CClient::Api: DomainsApi - + MessengerApi + + MessengerApi, CBlock::Hash> + BundleProducerElectionApi + FraudProofApi + MmrApi>, diff --git a/domains/client/domain-operator/src/fraud_proof.rs b/domains/client/domain-operator/src/fraud_proof.rs index b4d0716673..23cceb217e 100644 --- a/domains/client/domain-operator/src/fraud_proof.rs +++ b/domains/client/domain-operator/src/fraud_proof.rs @@ -118,8 +118,10 @@ where + ProvideRuntimeApi + ProofProvider + 'static, - Client::Api: - sp_block_builder::BlockBuilder + sp_api::ApiExt + DomainCoreApi, + Client::Api: sp_block_builder::BlockBuilder + + sp_api::ApiExt + + DomainCoreApi + + MessengerApi, CBlock::Hash>, CClient: HeaderBackend + BlockBackend + ProvideRuntimeApi diff --git a/domains/client/domain-operator/src/operator.rs b/domains/client/domain-operator/src/operator.rs index 5c38a34b5b..ade922cc6f 100644 --- a/domains/client/domain-operator/src/operator.rs +++ b/domains/client/domain-operator/src/operator.rs @@ -77,7 +77,7 @@ where + Finalizer + 'static, Client::Api: DomainCoreApi - + MessengerApi + + MessengerApi, CBlock::Hash> + sp_block_builder::BlockBuilder + sp_api::ApiExt + TaggedTransactionQueue, @@ -91,7 +91,7 @@ where + Sync + 'static, CClient::Api: DomainsApi - + MessengerApi + + MessengerApi, CBlock::Hash> + BundleProducerElectionApi + FraudProofApi + MmrApi>, diff --git a/domains/client/relayer/src/lib.rs b/domains/client/relayer/src/lib.rs index 187a3b52ae..fb423e8772 100644 --- a/domains/client/relayer/src/lib.rs +++ b/domains/client/relayer/src/lib.rs @@ -431,7 +431,7 @@ where CClient: HeaderBackend + ProvideRuntimeApi + ProofProvider + AuxStore, CClient::Api: DomainsApi - + MessengerApi + + MessengerApi, CBlock::Hash> + MmrApi>, Client::Api: RelayerApi, NumberFor, CBlock::Hash>, { @@ -520,7 +520,7 @@ where CBlock: BlockT, CClient: HeaderBackend + ProvideRuntimeApi + ProofProvider, CClient::Api: DomainsApi - + MessengerApi + + MessengerApi, CBlock::Hash> + MmrApi>, { let consensus_chain_mmr_proof = construct_consensus_mmr_proof( diff --git a/domains/client/relayer/src/worker.rs b/domains/client/relayer/src/worker.rs index 6831681a75..6cccf1345f 100644 --- a/domains/client/relayer/src/worker.rs +++ b/domains/client/relayer/src/worker.rs @@ -220,7 +220,7 @@ pub async fn relay_domain_messages( + ProofProvider + AuxStore, CClient::Api: DomainsApi - + MessengerApi + + MessengerApi, CBlock::Hash> + MmrApi>, SO: SyncOracle + Send, { diff --git a/domains/primitives/messenger-host-functions/src/host_functions.rs b/domains/primitives/messenger-host-functions/src/host_functions.rs index 343ee60807..f604f5ed4c 100644 --- a/domains/primitives/messenger-host-functions/src/host_functions.rs +++ b/domains/primitives/messenger-host-functions/src/host_functions.rs @@ -8,7 +8,7 @@ use sp_core::H256; use sp_domains::{DomainId, DomainsApi}; use sp_messenger::messages::ChainId; pub use sp_messenger::MessengerApi; -use sp_runtime::traits::{Block as BlockT, Header}; +use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; use std::marker::PhantomData; use std::sync::Arc; @@ -61,7 +61,7 @@ where &self, consensus_block_hash: Block::Hash, domain_id: DomainId, - ) -> Option> { + ) -> Option> { let runtime_api = self.consensus_client.runtime_api(); // Use the parent hash to get the actual used domain runtime code // TODO: update once we can get the actual used domain runtime code by `consensus_block_hash` @@ -81,7 +81,7 @@ where .ok() .flatten() .map(|(data, _)| data.raw_genesis.into_storage())?; - let mut domain_stateless_runtime = StatelessRuntime::::new( + let mut domain_stateless_runtime = StatelessRuntime::::new( self.domain_executor.clone(), domain_runtime.into(), ); @@ -98,7 +98,8 @@ where Block::Hash: From, DomainBlock: BlockT, Client: HeaderBackend + ProvideRuntimeApi, - Client::Api: MessengerApi + DomainsApi, + Client::Api: + MessengerApi, Block::Hash> + DomainsApi, Executor: CodeExecutor + RuntimeVersionOf, { fn get_storage_key(&self, req: StorageKeyRequest) -> Option> { diff --git a/domains/primitives/messenger/src/lib.rs b/domains/primitives/messenger/src/lib.rs index e4608c4a31..0aad2e3e78 100644 --- a/domains/primitives/messenger/src/lib.rs +++ b/domains/primitives/messenger/src/lib.rs @@ -34,6 +34,7 @@ use frame_support::inherent::InherentData; use frame_support::inherent::{InherentIdentifier, IsFatalError}; use messages::{BlockMessagesWithStorageKey, ChannelId, CrossDomainMessage, MessageId}; use sp_domains::{ChainId, DomainAllowlistUpdates, DomainId}; +use sp_subspace_mmr::ConsensusChainMmrLeafProof; #[cfg(feature = "std")] use std::collections::BTreeSet; @@ -195,14 +196,20 @@ sp_api::decl_runtime_apis! { } /// Api to provide XDM extraction from Runtime Calls. - #[api_version(3)] - pub trait MessengerApi { + #[api_version(4)] + pub trait MessengerApi + where + CNumber: Encode + Decode, + CHash: Encode + Decode, + { /// Returns `Some(true)` if valid XDM or `Some(false)` if not /// Returns None if this is not an XDM fn is_xdm_valid( extrinsic: Vec ) -> Option; + // Extract the MMR proof from the XDM + fn extract_xdm_mmr_proof(ext: &Block::Extrinsic) -> Option>; /// Returns the confirmed domain block storage for given domain. fn confirmed_domain_block_storage_key(domain_id: DomainId) -> Vec; diff --git a/domains/runtime/auto-id/src/lib.rs b/domains/runtime/auto-id/src/lib.rs index cd232d3637..8e2d2f882e 100644 --- a/domains/runtime/auto-id/src/lib.rs +++ b/domains/runtime/auto-id/src/lib.rs @@ -858,13 +858,23 @@ impl_runtime_apis! { } } - impl sp_messenger::MessengerApi for Runtime { + impl sp_messenger::MessengerApi for Runtime { fn is_xdm_valid( extrinsic: Vec, ) -> Option { is_xdm_valid(extrinsic) } + fn extract_xdm_mmr_proof(ext: &::Extrinsic) -> Option> { + match &ext.function { + RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg }) + | RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => { + Some(msg.proof.consensus_mmr_proof()) + } + _ => None, + } + } + fn confirmed_domain_block_storage_key(_domain_id: DomainId) -> Vec { // invalid call from Domain runtime vec![] diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index 35dc0f776a..4846a402ce 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -1255,13 +1255,23 @@ impl_runtime_apis! { } } - impl sp_messenger::MessengerApi for Runtime { + impl sp_messenger::MessengerApi for Runtime { fn is_xdm_valid( extrinsic: Vec, ) -> Option { is_xdm_valid(extrinsic) } + fn extract_xdm_mmr_proof(ext: &::Extrinsic) -> Option> { + match &ext.0.function { + RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg }) + | RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => { + Some(msg.proof.consensus_mmr_proof()) + } + _ => None, + } + } + fn confirmed_domain_block_storage_key(_domain_id: DomainId) -> Vec { // invalid call from Domain runtime vec![] diff --git a/domains/service/src/domain.rs b/domains/service/src/domain.rs index 42cc8ab135..4f21129718 100644 --- a/domains/service/src/domain.rs +++ b/domains/service/src/domain.rs @@ -74,7 +74,8 @@ where + Send + Sync + 'static, - CClient::Api: DomainsApi + MessengerApi, + CClient::Api: + DomainsApi + MessengerApi, CBlock::Hash>, RuntimeApi: ConstructRuntimeApi> + Send + Sync + 'static, RuntimeApi::RuntimeApi: ApiExt + Metadata @@ -85,7 +86,7 @@ where + TaggedTransactionQueue + TransactionPaymentRuntimeApi + DomainCoreApi - + MessengerApi + + MessengerApi, CBlock::Hash> + RelayerApi, NumberFor, CBlock::Hash>, AccountId: Encode + Decode, { @@ -147,10 +148,13 @@ where + Send + Sync + 'static, - CClient::Api: - DomainsApi + MessengerApi + MmrApi>, + CClient::Api: DomainsApi + + MessengerApi, CBlock::Hash> + + MmrApi>, RuntimeApi: ConstructRuntimeApi> + Send + Sync + 'static, - RuntimeApi::RuntimeApi: TaggedTransactionQueue + MessengerApi + ApiExt, + RuntimeApi::RuntimeApi: TaggedTransactionQueue + + MessengerApi, CBlock::Hash> + + ApiExt, BIMP: BlockImportProvider>, { let telemetry = config @@ -264,7 +268,7 @@ where + 'static, CClient::Api: DomainsApi + RelayerApi, NumberFor, CBlock::Hash> - + MessengerApi + + MessengerApi, CBlock::Hash> + BundleProducerElectionApi + FraudProofApi + MmrApi>, @@ -279,7 +283,7 @@ where + OffchainWorkerApi + SessionKeys + DomainCoreApi - + MessengerApi + + MessengerApi, CBlock::Hash> + TaggedTransactionQueue + AccountNonceApi + TransactionPaymentRuntimeApi diff --git a/domains/test/runtime/evm/src/lib.rs b/domains/test/runtime/evm/src/lib.rs index d44e6e4e35..d1030e4089 100644 --- a/domains/test/runtime/evm/src/lib.rs +++ b/domains/test/runtime/evm/src/lib.rs @@ -1215,13 +1215,23 @@ impl_runtime_apis! { } } - impl sp_messenger::MessengerApi for Runtime { + impl sp_messenger::MessengerApi for Runtime { fn is_xdm_valid( extrinsic: Vec, ) -> Option { is_xdm_valid(extrinsic) } + fn extract_xdm_mmr_proof(ext: &::Extrinsic) -> Option> { + match &ext.0.function { + RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg }) + | RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => { + Some(msg.proof.consensus_mmr_proof()) + } + _ => None, + } + } + fn confirmed_domain_block_storage_key(_domain_id: DomainId) -> Vec { vec![] } diff --git a/domains/test/service/src/domain.rs b/domains/test/service/src/domain.rs index 3f3e27a39b..8d5d5a06d4 100644 --- a/domains/test/service/src/domain.rs +++ b/domains/test/service/src/domain.rs @@ -83,7 +83,7 @@ where + OffchainWorkerApi + SessionKeys + DomainCoreApi - + MessengerApi + + MessengerApi, ::Hash> + TaggedTransactionQueue + AccountNonceApi + TransactionPaymentRuntimeApi @@ -137,7 +137,7 @@ where + TaggedTransactionQueue + AccountNonceApi + TransactionPaymentRuntimeApi - + MessengerApi + + MessengerApi, ::Hash> + RelayerApi, NumberFor, ::Hash> + OnchainStateApi + EthereumRuntimeRPCApi, diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index a2c93dcec4..301b6286ee 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -1500,13 +1500,23 @@ impl_runtime_apis! { } } - impl sp_messenger::MessengerApi for Runtime { + impl sp_messenger::MessengerApi::Hash> for Runtime { fn is_xdm_valid( extrinsic: Vec, ) -> Option { is_xdm_valid(extrinsic) } + fn extract_xdm_mmr_proof(ext: &::Extrinsic) -> Option::Hash, sp_core::H256>> { + match &ext.function { + RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg }) + | RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => { + Some(msg.proof.consensus_mmr_proof()) + } + _ => None, + } + } + fn confirmed_domain_block_storage_key(domain_id: DomainId) -> Vec { Domains::confirmed_domain_block_storage_key(domain_id) } diff --git a/test/subspace-test-service/src/lib.rs b/test/subspace-test-service/src/lib.rs index 6a0be3b226..42f407e811 100644 --- a/test/subspace-test-service/src/lib.rs +++ b/test/subspace-test-service/src/lib.rs @@ -252,7 +252,7 @@ where Client: BlockBackend + HeaderBackend + ProvideRuntimeApi + 'static, Client::Api: DomainsApi + BundleProducerElectionApi - + MessengerApi + + MessengerApi, Block::Hash> + MmrApi>, Executor: CodeExecutor + sc_executor::RuntimeVersionOf, CBackend: BackendT + 'static, From fa05c5a3044ff026177e55ca49cf6a68b4ae94a3 Mon Sep 17 00:00:00 2001 From: linning Date: Sat, 6 Jul 2024 01:40:14 +0800 Subject: [PATCH 02/10] Re-introduce the InvalidBundleType::InvalidXDM and perform the check The check is now defined as checking the validity of the mmr proof of the XDM Signed-off-by: linning --- .../src/verification.rs | 3 ++ crates/sp-domains/src/lib.rs | 8 +++-- crates/sp-domains/src/tests.rs | 1 + domains/client/block-preprocessor/Cargo.toml | 2 ++ domains/client/block-preprocessor/src/lib.rs | 34 ++++++++++++++++++- .../client/domain-operator/src/fraud_proof.rs | 3 ++ 6 files changed, 48 insertions(+), 3 deletions(-) diff --git a/crates/sp-domains-fraud-proof/src/verification.rs b/crates/sp-domains-fraud-proof/src/verification.rs index d8dd69057c..d26c6cd1bf 100644 --- a/crates/sp-domains-fraud-proof/src/verification.rs +++ b/crates/sp-domains-fraud-proof/src/verification.rs @@ -734,5 +734,8 @@ where } Ok(()) } + InvalidBundleType::InvalidXDM(extrinsic_index) => { + todo!() + } } } diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index 6a5e16323a..31589dce3f 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -1082,8 +1082,6 @@ pub enum ReceiptValidity { /// Bundle invalidity type /// /// Each type contains the index of the first invalid extrinsic within the bundle -// TODO: `#[codec(index = 3)]` is reserved for the reomved `InvalidBundleType::InvalidXDM` -// we can only reuse index 3 in the next network #[derive(Debug, Decode, Encode, TypeInfo, Clone, PartialEq, Eq)] pub enum InvalidBundleType { /// Failed to decode the opaque extrinsic. @@ -1095,6 +1093,9 @@ pub enum InvalidBundleType { /// Transaction is illegal (unable to pay the fee, etc). #[codec(index = 2)] IllegalTx(u32), + /// Transaction is an invalid XDM. + #[codec(index = 3)] + InvalidXDM(u32), /// Transaction is an inherent extrinsic. #[codec(index = 4)] InherentExtrinsic(u32), @@ -1112,6 +1113,7 @@ impl InvalidBundleType { Self::UndecodableTx(i) => *i, Self::OutOfRangeTx(i) => *i, Self::IllegalTx(i) => *i, + Self::InvalidXDM(i) => *i, Self::InherentExtrinsic(i) => *i, // NOTE: the `InvalidBundleWeight` is targetting the whole bundle not a specific // single extrinsic, as `extrinsic_index` is used as the order to check the extrinsic @@ -1130,6 +1132,7 @@ impl InvalidBundleType { Self::UndecodableTx(_) => 1, Self::OutOfRangeTx(_) => 2, Self::InherentExtrinsic(_) => 3, + Self::InvalidXDM(_) => 4, Self::IllegalTx(_) => 5, Self::InvalidBundleWeight => 6, }; @@ -1150,6 +1153,7 @@ impl InvalidBundleType { Self::UndecodableTx(i) => Some(*i), Self::OutOfRangeTx(i) => Some(*i), Self::IllegalTx(i) => Some(*i), + Self::InvalidXDM(i) => Some(*i), Self::InherentExtrinsic(i) => Some(*i), Self::InvalidBundleWeight => None, } diff --git a/crates/sp-domains/src/tests.rs b/crates/sp-domains/src/tests.rs index e58687401c..38c3b86cbb 100644 --- a/crates/sp-domains/src/tests.rs +++ b/crates/sp-domains/src/tests.rs @@ -230,6 +230,7 @@ fn test_invalid_bundle_type_checking_order() { 1 => InvalidBundleType::UndecodableTx(extrinsic_index), 2 => InvalidBundleType::OutOfRangeTx(extrinsic_index), 3 => InvalidBundleType::InherentExtrinsic(extrinsic_index), + 4 => InvalidBundleType::InvalidXDM(extrinsic_index), 5 => InvalidBundleType::IllegalTx(extrinsic_index), 6 => InvalidBundleType::InvalidBundleWeight, _ => unreachable!(), diff --git a/domains/client/block-preprocessor/Cargo.toml b/domains/client/block-preprocessor/Cargo.toml index 29beb8c43b..bb0b89bb1a 100644 --- a/domains/client/block-preprocessor/Cargo.toml +++ b/domains/client/block-preprocessor/Cargo.toml @@ -28,8 +28,10 @@ sp-executive = { version = "0.1.0", path = "../../primitives/executive" } sp-externalities = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "0cbfcb0232bbf71ac5b14cc8c99bf043cec420ef" } sp-inherents = { git = "https://github.com/subspace/polkadot-sdk", rev = "0cbfcb0232bbf71ac5b14cc8c99bf043cec420ef" } sp-messenger = { version = "0.1.0", path = "../../primitives/messenger" } +sp-mmr-primitives = { git = "https://github.com/subspace/polkadot-sdk", rev = "0cbfcb0232bbf71ac5b14cc8c99bf043cec420ef" } sp-runtime = { git = "https://github.com/subspace/polkadot-sdk", rev = "0cbfcb0232bbf71ac5b14cc8c99bf043cec420ef" } sp-state-machine = { git = "https://github.com/subspace/polkadot-sdk", rev = "0cbfcb0232bbf71ac5b14cc8c99bf043cec420ef" } +sp-subspace-mmr = { default-features = false, path = "../../../crates/sp-subspace-mmr" } sp-timestamp = { git = "https://github.com/subspace/polkadot-sdk", rev = "0cbfcb0232bbf71ac5b14cc8c99bf043cec420ef" } sp-weights = { git = "https://github.com/subspace/polkadot-sdk", rev = "0cbfcb0232bbf71ac5b14cc8c99bf043cec420ef" } sp-version = { git = "https://github.com/subspace/polkadot-sdk", rev = "0cbfcb0232bbf71ac5b14cc8c99bf043cec420ef" } diff --git a/domains/client/block-preprocessor/src/lib.rs b/domains/client/block-preprocessor/src/lib.rs index 3686936f8f..5bdda0e933 100644 --- a/domains/client/block-preprocessor/src/lib.rs +++ b/domains/client/block-preprocessor/src/lib.rs @@ -18,7 +18,7 @@ use crate::inherents::is_runtime_upgraded; use codec::Encode; use domain_runtime_primitives::opaque::AccountId; use sc_client_api::BlockBackend; -use sp_api::ProvideRuntimeApi; +use sp_api::{ApiError, ApiExt, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; use sp_core::H256; use sp_domains::core_api::DomainCoreApi; @@ -28,8 +28,10 @@ use sp_domains::{ InvalidBundleType, OpaqueBundle, OpaqueBundles, ReceiptValidity, }; use sp_messenger::MessengerApi; +use sp_mmr_primitives::MmrApi; use sp_runtime::traits::{Block as BlockT, Hash as HashT, NumberFor}; use sp_state_machine::LayoutV1; +use sp_subspace_mmr::ConsensusChainMmrLeafProof; use sp_weights::Weight; use std::collections::VecDeque; use std::marker::PhantomData; @@ -260,6 +262,15 @@ where .client .number(at)? .ok_or(sp_blockchain::Error::MissingHeader(at.to_string()))?; + + let api_version = runtime_api + .api_version::, CBlock::Hash>>(at) + .map_err(sp_blockchain::Error::RuntimeApiError)? + .ok_or_else(|| { + sp_blockchain::Error::RuntimeApiError(ApiError::Application( + format!("MessengerApi not found at: {:?}", at).into(), + )) + })?; // Check the validity of each extrinsic // @@ -300,6 +311,27 @@ where )); } + if api_version >= 4 { + if let Some(xdm_mmr_proof) = + runtime_api.extract_xdm_mmr_proof(at, &extrinsic)? + { + let ConsensusChainMmrLeafProof { + opaque_mmr_leaf, + proof, + .. + } = xdm_mmr_proof; + + if consensus_runtime_api + .verify_proof(at_consensus_hash, vec![opaque_mmr_leaf], proof)? + .is_err() + { + return Ok(BundleValidity::Invalid(InvalidBundleType::InvalidXDM( + index as u32, + ))); + } + } + } + // Using one instance of runtime_api throughout the loop in order to maintain context // between them. // Using `check_extrinsics_and_do_pre_dispatch` instead of `check_transaction_validity` diff --git a/domains/client/domain-operator/src/fraud_proof.rs b/domains/client/domain-operator/src/fraud_proof.rs index 23cceb217e..88672ad5ac 100644 --- a/domains/client/domain-operator/src/fraud_proof.rs +++ b/domains/client/domain-operator/src/fraud_proof.rs @@ -706,6 +706,9 @@ where InvalidBundlesProofData::Extrinsic(extrinsic_proof) } + InvalidBundleType::InvalidXDM(extrinsic_index) => { + todo!() + } } }; From 1cd547287f3f8f7f152fc0a34c7f00d53728e3c6 Mon Sep 17 00:00:00 2001 From: linning Date: Sat, 6 Jul 2024 01:44:04 +0800 Subject: [PATCH 03/10] Extract the XDM mmr proof check from the illegal tx check The XDM mmr proof check is performed separately, and a dedicated fraud proof will be introduced to cover this check Signed-off-by: linning --- crates/sp-subspace-mmr/src/lib.rs | 19 ++- domains/pallets/messenger/src/lib.rs | 204 ++++++++++++++++++--------- domains/runtime/auto-id/src/lib.rs | 6 +- domains/runtime/evm/src/lib.rs | 6 +- domains/test/runtime/evm/src/lib.rs | 6 +- 5 files changed, 167 insertions(+), 74 deletions(-) diff --git a/crates/sp-subspace-mmr/src/lib.rs b/crates/sp-subspace-mmr/src/lib.rs index 41a428b27c..eea9387055 100644 --- a/crates/sp-subspace-mmr/src/lib.rs +++ b/crates/sp-subspace-mmr/src/lib.rs @@ -26,6 +26,10 @@ pub use runtime_interface::domain_mmr_runtime_interface::HostFunctions as Domain pub use runtime_interface::subspace_mmr_runtime_interface::HostFunctions; pub use runtime_interface::{domain_mmr_runtime_interface, subspace_mmr_runtime_interface}; +#[cfg(not(feature = "std"))] +extern crate alloc; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; use codec::{Codec, Decode, Encode}; use scale_info::TypeInfo; use sp_mmr_primitives::{EncodableOpaqueLeaf, LeafProof as MmrProof}; @@ -120,14 +124,25 @@ impl Clone } /// Trait to verify MMR proofs -pub trait MmrProofVerifier { +pub trait MmrProofVerifier { /// Returns consensus state root if the given MMR proof is valid fn verify_proof_and_extract_leaf( mmr_leaf_proof: ConsensusChainMmrLeafProof, ) -> Option>; + + fn extract_leaf_without_verifying( + mmr_leaf_proof: ConsensusChainMmrLeafProof, + ) -> Option> { + mmr_leaf_proof + .opaque_mmr_leaf + .into_opaque_leaf() + .try_decode() + } } -impl MmrProofVerifier for () { +impl + MmrProofVerifier for () +{ fn verify_proof_and_extract_leaf( _mmr_leaf_proof: ConsensusChainMmrLeafProof, ) -> Option> { diff --git a/domains/pallets/messenger/src/lib.rs b/domains/pallets/messenger/src/lib.rs index e8a42f1501..b82dca11bc 100644 --- a/domains/pallets/messenger/src/lib.rs +++ b/domains/pallets/messenger/src/lib.rs @@ -41,8 +41,7 @@ use scale_info::TypeInfo; use sp_core::U256; use sp_domains::{DomainAllowlistUpdates, DomainId}; use sp_messenger::messages::{ - ChainId, Channel, ChannelId, ChannelState, CrossDomainMessage, FeeModel, Message, MessageId, - Nonce, + ChainId, Channel, ChannelId, ChannelState, CrossDomainMessage, FeeModel, MessageId, Nonce, }; use sp_runtime::traits::{Extrinsic, Hash}; use sp_runtime::DispatchError; @@ -69,10 +68,11 @@ pub(crate) type FungibleHoldId = /// A validated relay message. #[derive(Debug)] -pub struct ValidatedRelayMessage { - msg: Message, +pub struct ValidatedRelayMessage { + msg_nonce: Nonce, + dst_chain_id: ChainId, + channel_id: ChannelId, next_nonce: Nonce, - should_init_channel: bool, } /// Parameter to update chain allow list. @@ -123,6 +123,7 @@ mod pallet { use alloc::collections::BTreeSet; #[cfg(not(feature = "std"))] use alloc::vec::Vec; + use core::cmp::Ordering; use frame_support::ensure; use frame_support::pallet_prelude::*; use frame_support::traits::fungible::{Inspect, InspectHold, Mutate, MutateHold}; @@ -377,42 +378,26 @@ mod pallet { fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> { match call { Call::relay_message { msg: xdm } => { - let ValidatedRelayMessage { - msg, - next_nonce, - should_init_channel, - } = Self::validate_relay_message(xdm)?; - if msg.nonce != next_nonce { - log::error!( - "Unexpected message nonce, channel next nonce {:?}, msg nonce {:?}", - next_nonce, - msg.nonce, - ); - return Err(if msg.nonce < next_nonce { - InvalidTransaction::Stale - } else { - InvalidTransaction::Future - } - .into()); - } - Self::pre_dispatch_relay_message(msg, should_init_channel) + let consensus_state_root = T::MmrProofVerifier::verify_proof_and_extract_leaf( + xdm.proof.consensus_mmr_proof(), + ) + .ok_or(InvalidTransaction::BadProof)? + .state_root(); + + Self::validate_relay_message(xdm, true, consensus_state_root)?; + + Ok(()) } Call::relay_message_response { msg: xdm } => { - let (msg, next_nonce) = Self::validate_relay_message_response(xdm)?; - if msg.nonce != next_nonce { - log::error!( - "Unexpected message response nonce, channel next nonce {:?}, msg nonce {:?}", - next_nonce, - msg.nonce, - ); - return Err(if msg.nonce < next_nonce { - InvalidTransaction::Stale - } else { - InvalidTransaction::Future - } - .into()); - } - Self::pre_dispatch_relay_message_response(msg) + let consensus_state_root = T::MmrProofVerifier::verify_proof_and_extract_leaf( + xdm.proof.consensus_mmr_proof(), + ) + .ok_or(InvalidTransaction::BadProof)? + .state_root(); + + Self::validate_relay_message_response(xdm, true, consensus_state_root)?; + + Ok(()) } // always accept inherent extrinsic Call::update_domain_allowlist { .. } => Ok(()), @@ -424,19 +409,26 @@ mod pallet { fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { match call { Call::relay_message { msg: xdm } => { + let consensus_state_root = T::MmrProofVerifier::verify_proof_and_extract_leaf( + xdm.proof.consensus_mmr_proof(), + ) + .ok_or(InvalidTransaction::BadProof)? + .state_root(); + let ValidatedRelayMessage { - msg, + msg_nonce, + dst_chain_id, + channel_id, next_nonce, - should_init_channel: _, - } = Self::validate_relay_message(xdm)?; + } = Self::validate_relay_message(xdm, false, consensus_state_root)?; let mut valid_tx_builder = ValidTransaction::with_tag_prefix("MessengerInbox"); // Only add the requires tag if the msg nonce is in future - if msg.nonce > next_nonce { + if msg_nonce > next_nonce { valid_tx_builder = valid_tx_builder.and_requires(( - msg.dst_chain_id, - msg.channel_id, - msg.nonce - Nonce::one(), + dst_chain_id, + channel_id, + msg_nonce - Nonce::one(), )); }; valid_tx_builder @@ -444,21 +436,32 @@ mod pallet { // fraud proof .priority(1) .longevity(TransactionLongevity::MAX) - .and_provides((msg.dst_chain_id, msg.channel_id, msg.nonce)) + .and_provides((dst_chain_id, channel_id, msg_nonce)) .propagate(true) .build() } Call::relay_message_response { msg: xdm } => { - let (msg, next_nonce) = Self::validate_relay_message_response(xdm)?; + let consensus_state_root = T::MmrProofVerifier::verify_proof_and_extract_leaf( + xdm.proof.consensus_mmr_proof(), + ) + .ok_or(InvalidTransaction::BadProof)? + .state_root(); + + let ValidatedRelayMessage { + msg_nonce, + dst_chain_id, + channel_id, + next_nonce, + } = Self::validate_relay_message_response(xdm, false, consensus_state_root)?; let mut valid_tx_builder = ValidTransaction::with_tag_prefix("MessengerOutboxResponse"); // Only add the requires tag if the msg nonce is in future - if msg.nonce > next_nonce { + if msg_nonce > next_nonce { valid_tx_builder = valid_tx_builder.and_requires(( - msg.dst_chain_id, - msg.channel_id, - msg.nonce - Nonce::one(), + dst_chain_id, + channel_id, + msg_nonce - Nonce::one(), )); }; valid_tx_builder @@ -466,7 +469,7 @@ mod pallet { // fraud proof .priority(1) .longevity(TransactionLongevity::MAX) - .and_provides((msg.dst_chain_id, msg.channel_id, msg.nonce)) + .and_provides((dst_chain_id, channel_id, msg_nonce)) .propagate(true) .build() } @@ -1033,7 +1036,9 @@ mod pallet { pub fn validate_relay_message( xdm: &CrossDomainMessage, T::Hash, T::MmrHash>, - ) -> Result>, TransactionValidityError> { + pre_dispatch: bool, + consensus_state_root: StateRootOf, + ) -> Result { let (next_nonce, maybe_channel) = match Channels::::get(xdm.src_chain_id, xdm.channel_id) { None => { @@ -1066,7 +1071,7 @@ mod pallet { ); // verify and decode message - let msg = Self::do_verify_xdm(next_nonce, key, xdm)?; + let msg = Self::do_verify_xdm(next_nonce, key, consensus_state_root, xdm)?; let is_valid_call = match &msg.payload { VersionedPayload::V0(payload) => match payload { @@ -1104,11 +1109,26 @@ mod pallet { return Err(InvalidTransaction::Call.into()); } - Ok(ValidatedRelayMessage { - msg, + // Reject stale message and in future message when in `pre_dispatch` + match msg.nonce.cmp(&next_nonce) { + Ordering::Less => return Err(InvalidTransaction::Stale.into()), + Ordering::Greater if pre_dispatch => return Err(InvalidTransaction::Future.into()), + _ => {} + } + + let validated_relay_msg = ValidatedRelayMessage { + msg_nonce: msg.nonce, + dst_chain_id: msg.dst_chain_id, + channel_id: msg.channel_id, next_nonce, - should_init_channel: maybe_channel.is_none(), - }) + }; + + if pre_dispatch { + let should_init_channel = maybe_channel.is_none(); + Self::pre_dispatch_relay_message(msg, should_init_channel)?; + } + + Ok(validated_relay_msg) } pub(crate) fn pre_dispatch_relay_message( @@ -1151,7 +1171,9 @@ mod pallet { pub fn validate_relay_message_response( xdm: &CrossDomainMessage, T::Hash, T::MmrHash>, - ) -> Result<(Message>, Nonce), TransactionValidityError> { + pre_dispatch: bool, + consensus_state_root: StateRootOf, + ) -> Result { // channel should be open and message should be present in outbox let next_nonce = match Channels::::get(xdm.src_chain_id, xdm.channel_id) { @@ -1178,9 +1200,27 @@ mod pallet { ); // verify, decode, and store the message - let msg = Self::do_verify_xdm(next_nonce, key, xdm)?; + let msg = Self::do_verify_xdm(next_nonce, key, consensus_state_root, xdm)?; + + // Reject stale message and in future message when in `pre_dispatch` + match msg.nonce.cmp(&next_nonce) { + Ordering::Less => return Err(InvalidTransaction::Stale.into()), + Ordering::Greater if pre_dispatch => return Err(InvalidTransaction::Future.into()), + _ => {} + } + + let validated_relay_msg = ValidatedRelayMessage { + msg_nonce: msg.nonce, + dst_chain_id: msg.dst_chain_id, + channel_id: msg.channel_id, + next_nonce, + }; - Ok((msg, next_nonce)) + if pre_dispatch { + Self::pre_dispatch_relay_message_response(msg)?; + } + + Ok(validated_relay_msg) } pub(crate) fn pre_dispatch_relay_message_response( @@ -1199,6 +1239,7 @@ mod pallet { pub(crate) fn do_verify_xdm( next_nonce: Nonce, storage_key: StorageKey, + consensus_state_root: StateRootOf, xdm: &CrossDomainMessage, T::Hash, T::MmrHash>, ) -> Result>, TransactionValidityError> { // channel should be either already be created or match the next channelId for chain. @@ -1215,11 +1256,6 @@ mod pallet { InvalidTransaction::Custom(crate::verification_errors::INVALID_NONCE) ); - let state_root = - T::MmrProofVerifier::verify_proof_and_extract_leaf(xdm.proof.consensus_mmr_proof()) - .ok_or(InvalidTransaction::BadProof)? - .state_root(); - // if the message is from domain, verify domain confirmation proof let state_root = if let Some(domain_proof) = xdm.proof.domain_proof().clone() && let Some(domain_id) = xdm.src_chain_id.maybe_domain_chain() @@ -1231,7 +1267,7 @@ mod pallet { StorageProofVerifier::::get_decoded_value::< sp_domains::ConfirmedDomainBlock, T::Hash>, >( - &state_root, + &consensus_state_root, domain_proof, StorageKey(confirmed_domain_block_storage_key), ) @@ -1245,7 +1281,7 @@ mod pallet { })? .state_root } else { - state_root + consensus_state_root }; // verify and decode the message @@ -1292,6 +1328,36 @@ mod pallet { pub fn updated_channels() -> BTreeSet<(ChainId, ChannelId)> { UpdatedChannels::::get() } + + pub fn pre_dispatch_with_trusted_mmr_proof( + call: &Call, + ) -> Result<(), TransactionValidityError> { + match call { + Call::relay_message { msg: xdm } => { + let consensus_state_root = T::MmrProofVerifier::extract_leaf_without_verifying( + xdm.proof.consensus_mmr_proof(), + ) + .ok_or(InvalidTransaction::BadProof)? + .state_root(); + + Self::validate_relay_message(xdm, true, consensus_state_root)?; + + Ok(()) + } + Call::relay_message_response { msg: xdm } => { + let consensus_state_root = T::MmrProofVerifier::extract_leaf_without_verifying( + xdm.proof.consensus_mmr_proof(), + ) + .ok_or(InvalidTransaction::BadProof)? + .state_root(); + + Self::validate_relay_message_response(xdm, true, consensus_state_root)?; + + Ok(()) + } + call => ::pre_dispatch(call), + } + } } } diff --git a/domains/runtime/auto-id/src/lib.rs b/domains/runtime/auto-id/src/lib.rs index 8e2d2f882e..e9869cf120 100644 --- a/domains/runtime/auto-id/src/lib.rs +++ b/domains/runtime/auto-id/src/lib.rs @@ -598,7 +598,11 @@ fn check_transaction_and_do_pre_dispatch_inner( .map(|_| ()), // unsigned transaction None => { - Runtime::pre_dispatch(&xt.function).map(|_| ())?; + if let RuntimeCall::Messenger(call) = &xt.function { + Messenger::pre_dispatch_with_trusted_mmr_proof(call)?; + } else { + Runtime::pre_dispatch(&xt.function).map(|_| ())?; + } SignedExtra::pre_dispatch_unsigned(&xt.function, &dispatch_info, encoded_len) .map(|_| ()) } diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index 4846a402ce..a2df9749c9 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -992,7 +992,11 @@ fn check_transaction_and_do_pre_dispatch_inner( .map(|_| ()) } CheckedSignature::Unsigned => { - Runtime::pre_dispatch(&xt.function).map(|_| ())?; + if let RuntimeCall::Messenger(call) = &xt.function { + Messenger::pre_dispatch_with_trusted_mmr_proof(call)?; + } else { + Runtime::pre_dispatch(&xt.function).map(|_| ())?; + } SignedExtra::pre_dispatch_unsigned(&xt.function, &dispatch_info, encoded_len) .map(|_| ()) } diff --git a/domains/test/runtime/evm/src/lib.rs b/domains/test/runtime/evm/src/lib.rs index d1030e4089..2e9a39535c 100644 --- a/domains/test/runtime/evm/src/lib.rs +++ b/domains/test/runtime/evm/src/lib.rs @@ -958,7 +958,11 @@ fn check_transaction_and_do_pre_dispatch_inner( .map(|_| ()) } CheckedSignature::Unsigned => { - Runtime::pre_dispatch(&xt.function).map(|_| ())?; + if let RuntimeCall::Messenger(call) = &xt.function { + Messenger::pre_dispatch_with_trusted_mmr_proof(call)?; + } else { + Runtime::pre_dispatch(&xt.function).map(|_| ())?; + } SignedExtra::pre_dispatch_unsigned(&xt.function, &dispatch_info, encoded_len) .map(|_| ()) } From 8c910153d05b8b170e415cb732a206f2ba75d62e Mon Sep 17 00:00:00 2001 From: linning Date: Sat, 6 Jul 2024 01:48:53 +0800 Subject: [PATCH 04/10] Introduce the extract_xdm_mmr_proof host function for the invalid XDM fraud proof verification Signed-off-by: linning --- .../src/host_functions.rs | 22 +++++++++++++++++++ .../src/runtime_interface.rs | 11 ++++++++++ 2 files changed, 33 insertions(+) diff --git a/crates/sp-domains-fraud-proof/src/host_functions.rs b/crates/sp-domains-fraud-proof/src/host_functions.rs index ce5c595219..213634f3c6 100644 --- a/crates/sp-domains-fraud-proof/src/host_functions.rs +++ b/crates/sp-domains-fraud-proof/src/host_functions.rs @@ -115,6 +115,12 @@ pub trait FraudProofHostFunctions: Send + Sync { domain_runtime_code: Vec, bundle_body: Vec, ) -> Option; + + fn extract_xdm_mmr_proof( + &self, + domain_runtime_code: Vec, + opaque_extrinsic: Vec, + ) -> Option>>; } sp_externalities::decl_extension! { @@ -883,6 +889,22 @@ where } Some(estimated_bundle_weight) } + + fn extract_xdm_mmr_proof( + &self, + domain_runtime_code: Vec, + opaque_extrinsic: Vec, + ) -> Option>> { + let domain_stateless_runtime = StatelessRuntime::::new( + self.domain_executor.clone(), + domain_runtime_code.into(), + ); + let extrinsic = + ::Extrinsic::decode(&mut opaque_extrinsic.as_slice()).ok()?; + domain_stateless_runtime + .extract_xdm_mmr_proof(&extrinsic) + .ok() + } } type CreateProofCheckBackedResult = Result< diff --git a/crates/sp-domains-fraud-proof/src/runtime_interface.rs b/crates/sp-domains-fraud-proof/src/runtime_interface.rs index 352debe272..1d97cc30ab 100644 --- a/crates/sp-domains-fraud-proof/src/runtime_interface.rs +++ b/crates/sp-domains-fraud-proof/src/runtime_interface.rs @@ -170,4 +170,15 @@ pub trait FraudProofRuntimeInterface { .expect("No `FraudProofExtension` associated for the current context!") .bundle_weight(domain_runtime_code, bundle_body) } + + #[version(1)] + fn extract_xdm_mmr_proof( + &mut self, + domain_runtime_code: Vec, + opaque_extrinsic: Vec, + ) -> Option>> { + self.extension::() + .expect("No `FraudProofExtension` associated for the current context!") + .extract_xdm_mmr_proof(domain_runtime_code, opaque_extrinsic) + } } From 59d9054fadc6a1812d0761df8a737d08609965a6 Mon Sep 17 00:00:00 2001 From: linning Date: Sat, 6 Jul 2024 01:57:57 +0800 Subject: [PATCH 05/10] Add invalid XDM fraud proof (and its storage proof) defination Signed-off-by: linning --- crates/pallet-domains/src/lib.rs | 3 +- crates/sc-domains/src/lib.rs | 4 +- .../sp-domains-fraud-proof/src/fraud_proof.rs | 26 ++++-- crates/sp-domains-fraud-proof/src/lib.rs | 2 +- .../src/storage_proof.rs | 90 +++++++++++++------ .../src/verification.rs | 11 ++- crates/subspace-fake-runtime-api/src/lib.rs | 2 +- crates/subspace-runtime/src/lib.rs | 11 ++- test/subspace-test-runtime/src/lib.rs | 11 ++- 9 files changed, 111 insertions(+), 49 deletions(-) diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 88398b6c5b..ec0ab860c4 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -421,7 +421,7 @@ mod pallet { >; /// Fraud proof storage key provider - type FraudProofStorageKeyProvider: FraudProofStorageKeyProvider; + type FraudProofStorageKeyProvider: FraudProofStorageKeyProvider>; /// Hook to handle chain rewards. type OnChainRewards: OnChainRewards>; @@ -2207,6 +2207,7 @@ impl Pallet { verify_invalid_bundles_fraud_proof::< T::Block, T::DomainHeader, + T::MmrHash, BalanceOf, T::FraudProofStorageKeyProvider, >( diff --git a/crates/sc-domains/src/lib.rs b/crates/sc-domains/src/lib.rs index 0e69621f2f..ceb014580f 100644 --- a/crates/sc-domains/src/lib.rs +++ b/crates/sc-domains/src/lib.rs @@ -134,7 +134,7 @@ impl FPStorageKeyProvider FraudProofStorageKeyProviderInstance +impl FraudProofStorageKeyProviderInstance> for FPStorageKeyProvider where CBlock: BlockT, @@ -142,7 +142,7 @@ where CClient: HeaderBackend + ProvideRuntimeApi + 'static, CClient::Api: FraudProofApi, { - fn storage_key(&self, req: FraudProofStorageKeyRequest) -> Option> { + fn storage_key(&self, req: FraudProofStorageKeyRequest>) -> Option> { let best_hash = self.consensus_client.info().best_hash; self.consensus_client .runtime_api() diff --git a/crates/sp-domains-fraud-proof/src/fraud_proof.rs b/crates/sp-domains-fraud-proof/src/fraud_proof.rs index fab23fb60f..c474f25be8 100644 --- a/crates/sp-domains-fraud-proof/src/fraud_proof.rs +++ b/crates/sp-domains-fraud-proof/src/fraud_proof.rs @@ -339,6 +339,10 @@ pub enum VerificationError { /// Failed to get bundle weight #[cfg_attr(feature = "thiserror", error("Failed to get bundle weight"))] FailedToGetBundleWeight, + #[cfg_attr(feature = "thiserror", error("Failed to extract xdm mmr proof"))] + FailedToGetExtractXdmMmrProof, + #[cfg_attr(feature = "thiserror", error("Failed to decode xdm mmr proof"))] + FailedToDecodeXdmMmrProof, } impl From for VerificationError { @@ -362,16 +366,16 @@ pub struct FraudProof { /// or the required domain runtime code is available from the current runtime state. pub maybe_domain_runtime_code_proof: Option>, /// The specific fraud proof variant - pub proof: FraudProofVariant, + pub proof: FraudProofVariant, } #[allow(clippy::large_enum_variant)] #[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] -pub enum FraudProofVariant { +pub enum FraudProofVariant { InvalidStateTransition(InvalidStateTransitionProof), ValidBundle(ValidBundleProof), InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof), - InvalidBundles(InvalidBundlesProof), + InvalidBundles(InvalidBundlesProof), InvalidDomainBlockHash(InvalidDomainBlockHashProof), InvalidBlockFees(InvalidBlockFeesProof), InvalidTransfers(InvalidTransfersProof), @@ -526,23 +530,33 @@ pub struct InvalidExtrinsicsRootProof { pub domain_inherent_extrinsic_data_proof: DomainInherentExtrinsicDataProof, } +#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] +pub struct MmrRootProof { + pub mmr_proof: ConsensusChainMmrLeafProof, + pub mmr_root_storage_proof: MmrRootStorageProof, +} + #[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] -pub enum InvalidBundlesProofData { +pub enum InvalidBundlesProofData { Extrinsic(StorageProof), Bundle(OpaqueBundleWithProof), BundleAndExecution { bundle_with_proof: OpaqueBundleWithProof, execution_proof: StorageProof, }, + InvalidXDMProofData { + extrinsic_proof: StorageProof, + mmr_root_proof: Option>, + }, } #[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] -pub struct InvalidBundlesProof { +pub struct InvalidBundlesProof { pub bundle_index: u32, pub invalid_bundle_type: InvalidBundleType, pub is_true_invalid_fraud_proof: bool, /// Proof data of the invalid bundle - pub proof_data: InvalidBundlesProofData, + pub proof_data: InvalidBundlesProofData, } /// Represents an invalid block fees proof. diff --git a/crates/sp-domains-fraud-proof/src/lib.rs b/crates/sp-domains-fraud-proof/src/lib.rs index 6df65200b7..665f59587a 100644 --- a/crates/sp-domains-fraud-proof/src/lib.rs +++ b/crates/sp-domains-fraud-proof/src/lib.rs @@ -404,6 +404,6 @@ sp_api::decl_runtime_apis! { fn submit_fraud_proof_unsigned(fraud_proof: FraudProof, Block::Hash, DomainHeader, H256>); /// Reture the storage key used in fraud proof - fn fraud_proof_storage_key(req: FraudProofStorageKeyRequest) -> Vec; + fn fraud_proof_storage_key(req: FraudProofStorageKeyRequest>) -> Vec; } } diff --git a/crates/sp-domains-fraud-proof/src/storage_proof.rs b/crates/sp-domains-fraud-proof/src/storage_proof.rs index c23cc0224d..2ced96ff0b 100644 --- a/crates/sp-domains-fraud-proof/src/storage_proof.rs +++ b/crates/sp-domains-fraud-proof/src/storage_proof.rs @@ -13,6 +13,7 @@ use sp_domains::{ }; use sp_runtime::generic::Digest; use sp_runtime::traits::{Block as BlockT, HashingFor, Header as HeaderT, NumberFor}; +use sp_std::marker::PhantomData; use sp_std::vec::Vec; use sp_trie::StorageProof; use subspace_core_primitives::Randomness; @@ -48,10 +49,11 @@ pub enum VerificationError { TransfersStorageProof(StorageProofVerificationError), ExtrinsicStorageProof(StorageProofVerificationError), DomainSudoCallStorageProof(StorageProofVerificationError), + MmrRootStorageProof(StorageProofVerificationError), } #[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] -pub enum FraudProofStorageKeyRequest { +pub enum FraudProofStorageKeyRequest { BlockRandomness, Timestamp, SuccessfulBundles(DomainId), @@ -61,9 +63,10 @@ pub enum FraudProofStorageKeyRequest { RuntimeRegistry(RuntimeId), DynamicCostOfStorage, DomainSudoCall(DomainId), + MmrRoot(Number), } -impl FraudProofStorageKeyRequest { +impl FraudProofStorageKeyRequest { fn into_error(self, err: StorageProofVerificationError) -> VerificationError { match self { Self::BlockRandomness => VerificationError::BlockRandomnessStorageProof(err), @@ -79,24 +82,25 @@ impl FraudProofStorageKeyRequest { FraudProofStorageKeyRequest::DomainSudoCall(_) => { VerificationError::DomainSudoCallStorageProof(err) } + Self::MmrRoot(_) => VerificationError::MmrRootStorageProof(err), } } } /// Trait to get storage keys in the runtime i.e. when verifying the storage proof -pub trait FraudProofStorageKeyProvider { - fn storage_key(req: FraudProofStorageKeyRequest) -> Vec; +pub trait FraudProofStorageKeyProvider { + fn storage_key(req: FraudProofStorageKeyRequest) -> Vec; } -impl FraudProofStorageKeyProvider for () { - fn storage_key(_req: FraudProofStorageKeyRequest) -> Vec { +impl FraudProofStorageKeyProvider for () { + fn storage_key(_req: FraudProofStorageKeyRequest) -> Vec { Default::default() } } /// Trait to get storage keys in the client i.e. when generating the storage proof -pub trait FraudProofStorageKeyProviderInstance { - fn storage_key(&self, req: FraudProofStorageKeyRequest) -> Option>; +pub trait FraudProofStorageKeyProviderInstance { + fn storage_key(&self, req: FraudProofStorageKeyRequest) -> Option>; } macro_rules! impl_storage_proof { @@ -120,10 +124,13 @@ pub trait BasicStorageProof: type StorageValue: Decode; type Key = (); - fn storage_key_request(key: Self::Key) -> FraudProofStorageKeyRequest; + fn storage_key_request(key: Self::Key) -> FraudProofStorageKeyRequest>; #[cfg(feature = "std")] - fn generate, SKPI: FraudProofStorageKeyProviderInstance>( + fn generate< + PP: ProofProvider, + SKPI: FraudProofStorageKeyProviderInstance>, + >( proof_provider: &PP, block_hash: Block::Hash, key: Self::Key, @@ -138,7 +145,7 @@ pub trait BasicStorageProof: Ok(storage_proof.into()) } - fn verify( + fn verify>>( self, key: Self::Key, state_root: &Block::Hash, @@ -161,7 +168,7 @@ impl_storage_proof!(SuccessfulBundlesProof); impl BasicStorageProof for SuccessfulBundlesProof { type StorageValue = Vec; type Key = DomainId; - fn storage_key_request(key: Self::Key) -> FraudProofStorageKeyRequest { + fn storage_key_request(key: Self::Key) -> FraudProofStorageKeyRequest> { FraudProofStorageKeyRequest::SuccessfulBundles(key) } } @@ -172,7 +179,7 @@ pub struct BlockRandomnessProof(StorageProof); impl_storage_proof!(BlockRandomnessProof); impl BasicStorageProof for BlockRandomnessProof { type StorageValue = Randomness; - fn storage_key_request(_key: Self::Key) -> FraudProofStorageKeyRequest { + fn storage_key_request(_key: Self::Key) -> FraudProofStorageKeyRequest> { FraudProofStorageKeyRequest::BlockRandomness } } @@ -184,7 +191,7 @@ impl_storage_proof!(DomainChainsAllowlistUpdateStorageProof); impl BasicStorageProof for DomainChainsAllowlistUpdateStorageProof { type StorageValue = DomainAllowlistUpdates; type Key = DomainId; - fn storage_key_request(key: Self::Key) -> FraudProofStorageKeyRequest { + fn storage_key_request(key: Self::Key) -> FraudProofStorageKeyRequest> { FraudProofStorageKeyRequest::DomainAllowlistUpdates(key) } } @@ -195,7 +202,7 @@ pub struct TimestampStorageProof(StorageProof); impl_storage_proof!(TimestampStorageProof); impl BasicStorageProof for TimestampStorageProof { type StorageValue = Moment; - fn storage_key_request(_key: Self::Key) -> FraudProofStorageKeyRequest { + fn storage_key_request(_key: Self::Key) -> FraudProofStorageKeyRequest> { FraudProofStorageKeyRequest::Timestamp } } @@ -206,7 +213,7 @@ pub struct DynamicCostOfStorageProof(StorageProof); impl_storage_proof!(DynamicCostOfStorageProof); impl BasicStorageProof for DynamicCostOfStorageProof { type StorageValue = bool; - fn storage_key_request(_key: Self::Key) -> FraudProofStorageKeyRequest { + fn storage_key_request(_key: Self::Key) -> FraudProofStorageKeyRequest> { FraudProofStorageKeyRequest::DynamicCostOfStorage } } @@ -217,7 +224,7 @@ pub struct ConsensusTransactionByteFeeProof(StorageProof); impl_storage_proof!(ConsensusTransactionByteFeeProof); impl BasicStorageProof for ConsensusTransactionByteFeeProof { type StorageValue = BlockTransactionByteFee; - fn storage_key_request(_key: Self::Key) -> FraudProofStorageKeyRequest { + fn storage_key_request(_key: Self::Key) -> FraudProofStorageKeyRequest> { FraudProofStorageKeyRequest::TransactionByteFee } } @@ -228,7 +235,7 @@ pub struct BlockDigestProof(StorageProof); impl_storage_proof!(BlockDigestProof); impl BasicStorageProof for BlockDigestProof { type StorageValue = Digest; - fn storage_key_request(_key: Self::Key) -> FraudProofStorageKeyRequest { + fn storage_key_request(_key: Self::Key) -> FraudProofStorageKeyRequest> { FraudProofStorageKeyRequest::BlockDigest } } @@ -240,7 +247,7 @@ impl_storage_proof!(DomainSudoCallStorageProof); impl BasicStorageProof for DomainSudoCallStorageProof { type StorageValue = DomainSudoCall; type Key = DomainId; - fn storage_key_request(key: Self::Key) -> FraudProofStorageKeyRequest { + fn storage_key_request(key: Self::Key) -> FraudProofStorageKeyRequest> { FraudProofStorageKeyRequest::DomainSudoCall(key) } } @@ -260,7 +267,7 @@ impl_storage_proof!(DomainRuntimeCodeProof); impl BasicStorageProof for DomainRuntimeCodeProof { type StorageValue = RuntimeObject, Block::Hash>; type Key = RuntimeId; - fn storage_key_request(key: Self::Key) -> FraudProofStorageKeyRequest { + fn storage_key_request(key: Self::Key) -> FraudProofStorageKeyRequest> { FraudProofStorageKeyRequest::RuntimeRegistry(key) } } @@ -284,7 +291,7 @@ where pub fn generate< Block: BlockT, PP: ProofProvider, - SKP: FraudProofStorageKeyProviderInstance, + SKP: FraudProofStorageKeyProviderInstance>, >( storage_key_provider: &SKP, proof_provider: &PP, @@ -308,7 +315,7 @@ where } /// Verify if the `bundle` does commit to the given `state_root` - pub fn verify( + pub fn verify>>( &self, domain_id: DomainId, state_root: &Block::Hash, @@ -343,7 +350,7 @@ impl MaybeDomainRuntimeUpgradedProof { pub fn generate< Block: BlockT, PP: ProofProvider, - SKP: FraudProofStorageKeyProviderInstance, + SKP: FraudProofStorageKeyProviderInstance>, >( storage_key_provider: &SKP, proof_provider: &PP, @@ -368,7 +375,7 @@ impl MaybeDomainRuntimeUpgradedProof { }) } - pub fn verify( + pub fn verify>>( &self, runtime_id: RuntimeId, state_root: &Block::Hash, @@ -422,7 +429,7 @@ impl DomainInherentExtrinsicDataProof { pub fn generate< Block: BlockT, PP: ProofProvider, - SKP: FraudProofStorageKeyProviderInstance, + SKP: FraudProofStorageKeyProviderInstance>, >( storage_key_provider: &SKP, proof_provider: &PP, @@ -483,7 +490,7 @@ impl DomainInherentExtrinsicDataProof { }) } - pub fn verify( + pub fn verify>>( &self, domain_id: DomainId, runtime_id: RuntimeId, @@ -548,3 +555,34 @@ impl DomainInherentExtrinsicDataProof { }) } } + +#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] +pub struct MmrRootStorageProof { + storage_proof: StorageProof, + _phantom_data: PhantomData, +} + +impl From for MmrRootStorageProof { + fn from(storage_proof: StorageProof) -> Self { + MmrRootStorageProof { + storage_proof, + _phantom_data: Default::default(), + } + } +} + +impl From> for StorageProof { + fn from(p: MmrRootStorageProof) -> StorageProof { + p.storage_proof + } +} + +impl BasicStorageProof + for MmrRootStorageProof +{ + type StorageValue = MmrHash; + type Key = NumberFor; + fn storage_key_request(key: Self::Key) -> FraudProofStorageKeyRequest> { + FraudProofStorageKeyRequest::MmrRoot(key) + } +} diff --git a/crates/sp-domains-fraud-proof/src/verification.rs b/crates/sp-domains-fraud-proof/src/verification.rs index d26c6cd1bf..34eabeb4c0 100644 --- a/crates/sp-domains-fraud-proof/src/verification.rs +++ b/crates/sp-domains-fraud-proof/src/verification.rs @@ -60,7 +60,7 @@ where DomainHeader: HeaderT, DomainHeader::Hash: Into + PartialEq + Copy, Hashing: Hasher, - SKP: FraudProofStorageKeyProvider, + SKP: FraudProofStorageKeyProvider>, { let InvalidExtrinsicsRootProof { valid_bundle_digests, @@ -198,7 +198,7 @@ where CBlock::Hash: Into, DomainHeader: HeaderT, DomainHeader::Hash: Into + PartialEq + Copy, - SKP: FraudProofStorageKeyProvider, + SKP: FraudProofStorageKeyProvider>, { let ValidBundleProof { bundle_with_proof, .. @@ -514,7 +514,7 @@ fn get_extrinsic_from_proof( }) } -pub fn verify_invalid_bundles_fraud_proof( +pub fn verify_invalid_bundles_fraud_proof( bad_receipt: ExecutionReceipt< NumberFor, CBlock::Hash, @@ -532,6 +532,7 @@ pub fn verify_invalid_bundles_fraud_proof( invalid_bundles_fraud_proof: &InvalidBundlesProof< NumberFor, ::Hash, + MmrHash, DomainHeader, >, domain_id: DomainId, @@ -543,7 +544,8 @@ where DomainHeader: HeaderT, CBlock::Hash: Into, DomainHeader::Hash: Into, - SKP: FraudProofStorageKeyProvider, + MmrHash: Decode + Clone, + SKP: FraudProofStorageKeyProvider>, { let InvalidBundlesProof { bundle_index, @@ -574,6 +576,7 @@ where bundle_with_proof.verify::(domain_id, &state_root)?; } InvalidBundlesProofData::Extrinsic(_) => {} + InvalidBundlesProofData::InvalidXDMProofData { .. } => {} } // Fast path to check if the fraud proof is targetting a bad receipt that claim a non-exist extrinsic diff --git a/crates/subspace-fake-runtime-api/src/lib.rs b/crates/subspace-fake-runtime-api/src/lib.rs index a454b755ae..f6dca2ccff 100644 --- a/crates/subspace-fake-runtime-api/src/lib.rs +++ b/crates/subspace-fake-runtime-api/src/lib.rs @@ -424,7 +424,7 @@ sp_api::impl_runtime_apis! { unreachable!() } - fn fraud_proof_storage_key(_req: FraudProofStorageKeyRequest) -> Vec { + fn fraud_proof_storage_key(_req: FraudProofStorageKeyRequest>) -> Vec { unreachable!() } } diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index c51f3250fc..66efa10b8e 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -902,8 +902,8 @@ impl From for AccountId32 { } pub struct StorageKeyProvider; -impl FraudProofStorageKeyProvider for StorageKeyProvider { - fn storage_key(req: FraudProofStorageKeyRequest) -> Vec { +impl FraudProofStorageKeyProvider> for StorageKeyProvider { + fn storage_key(req: FraudProofStorageKeyRequest>) -> Vec { match req { FraudProofStorageKeyRequest::BlockRandomness => { pallet_subspace::BlockRandomness::::hashed_key().to_vec() @@ -930,6 +930,9 @@ impl FraudProofStorageKeyProvider for StorageKeyProvider { FraudProofStorageKeyRequest::DomainSudoCall(domain_id) => { pallet_domains::DomainSudoCalls::::hashed_key_for(domain_id) } + FraudProofStorageKeyRequest::MmrRoot(block_number) => { + pallet_subspace_mmr::MmrRootHashes::::hashed_key_for(block_number) + } } } } @@ -1372,8 +1375,8 @@ impl_runtime_apis! { Domains::submit_fraud_proof_unsigned(fraud_proof) } - fn fraud_proof_storage_key(req: FraudProofStorageKeyRequest) -> Vec { - ::storage_key(req) + fn fraud_proof_storage_key(req: FraudProofStorageKeyRequest>) -> Vec { + >>::storage_key(req) } } diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 301b6286ee..f35c373136 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -1119,8 +1119,8 @@ impl From for AccountId32 { } pub struct StorageKeyProvider; -impl FraudProofStorageKeyProvider for StorageKeyProvider { - fn storage_key(req: FraudProofStorageKeyRequest) -> Vec { +impl FraudProofStorageKeyProvider> for StorageKeyProvider { + fn storage_key(req: FraudProofStorageKeyRequest>) -> Vec { match req { FraudProofStorageKeyRequest::BlockRandomness => { pallet_subspace::BlockRandomness::::hashed_key().to_vec() @@ -1147,6 +1147,9 @@ impl FraudProofStorageKeyProvider for StorageKeyProvider { FraudProofStorageKeyRequest::DomainSudoCall(domain_id) => { pallet_domains::DomainSudoCalls::::hashed_key_for(domain_id) } + FraudProofStorageKeyRequest::MmrRoot(block_number) => { + pallet_subspace_mmr::MmrRootHashes::::hashed_key_for(block_number) + } } } } @@ -1569,8 +1572,8 @@ impl_runtime_apis! { Domains::submit_fraud_proof_unsigned(fraud_proof) } - fn fraud_proof_storage_key(req: FraudProofStorageKeyRequest) -> Vec { - ::storage_key(req) + fn fraud_proof_storage_key(req: FraudProofStorageKeyRequest>) -> Vec { + >>::storage_key(req) } } From cd964277ca391be47d28f188ecf199df11f9a0c4 Mon Sep 17 00:00:00 2001 From: linning Date: Sat, 6 Jul 2024 02:08:43 +0800 Subject: [PATCH 06/10] Add invalid XDM fraud proof generation and verification Signed-off-by: linning --- crates/pallet-domains/src/lib.rs | 1 + .../src/verification.rs | 74 +++++++++++++++- crates/sp-subspace-mmr/src/lib.rs | 7 ++ crates/subspace-runtime/src/lib.rs | 11 ++- .../client/domain-operator/src/fraud_proof.rs | 88 ++++++++++++++++++- test/subspace-test-runtime/src/lib.rs | 11 ++- 6 files changed, 181 insertions(+), 11 deletions(-) diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index ec0ab860c4..0dc964b023 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -2210,6 +2210,7 @@ impl Pallet { T::MmrHash, BalanceOf, T::FraudProofStorageKeyProvider, + T::MmrProofVerifier, >( bad_receipt, bad_receipt_parent, diff --git a/crates/sp-domains-fraud-proof/src/verification.rs b/crates/sp-domains-fraud-proof/src/verification.rs index 34eabeb4c0..fe5674e3a5 100644 --- a/crates/sp-domains-fraud-proof/src/verification.rs +++ b/crates/sp-domains-fraud-proof/src/verification.rs @@ -3,7 +3,7 @@ extern crate alloc; use crate::fraud_proof::{ InvalidBundlesProof, InvalidBundlesProofData, InvalidExtrinsicsRootProof, - InvalidStateTransitionProof, ValidBundleProof, VerificationError, + InvalidStateTransitionProof, MmrRootProof, ValidBundleProof, VerificationError, }; use crate::storage_proof::{self, *}; use crate::{ @@ -30,6 +30,7 @@ use sp_runtime::traits::{ Block as BlockT, Hash, Header as HeaderT, NumberFor, UniqueSaturatedInto, }; use sp_runtime::{OpaqueExtrinsic, SaturatedConversion}; +use sp_subspace_mmr::{ConsensusChainMmrLeafProof, MmrProofVerifier}; use sp_trie::{LayoutV1, StorageProof}; use subspace_core_primitives::{Randomness, U256}; use trie_db::node::Value; @@ -514,7 +515,7 @@ fn get_extrinsic_from_proof( }) } -pub fn verify_invalid_bundles_fraud_proof( +pub fn verify_invalid_bundles_fraud_proof( bad_receipt: ExecutionReceipt< NumberFor, CBlock::Hash, @@ -546,6 +547,7 @@ where DomainHeader::Hash: Into, MmrHash: Decode + Clone, SKP: FraudProofStorageKeyProvider>, + MPV: MmrProofVerifier, CBlock::Hash>, { let InvalidBundlesProof { bundle_index, @@ -738,7 +740,73 @@ where Ok(()) } InvalidBundleType::InvalidXDM(extrinsic_index) => { - todo!() + let (extrinsic_proof, maybe_mmr_root_proof) = match proof_data { + InvalidBundlesProofData::InvalidXDMProofData { + extrinsic_proof, + mmr_root_proof, + } => (extrinsic_proof.clone(), mmr_root_proof.clone()), + _ => return Err(VerificationError::UnexpectedInvalidBundleProofData), + }; + + let opaque_extrinsic = get_extrinsic_from_proof::( + *extrinsic_index, + bundle_extrinsic_root, + extrinsic_proof, + )?; + + let maybe_xdm_mmr_proof = fraud_proof_runtime_interface::extract_xdm_mmr_proof( + domain_runtime_code, + opaque_extrinsic.encode(), + ) + .ok_or(VerificationError::FailedToGetExtractXdmMmrProof)?; + + let (mmr_root_proof, consensus_chain_mmr_leaf_proof) = match maybe_xdm_mmr_proof { + Some(encoded_xdm_mmr_proof) => { + let consensus_chain_mmr_leaf_proof: ConsensusChainMmrLeafProof< + NumberFor, + CBlock::Hash, + MmrHash, + > = Decode::decode(&mut encoded_xdm_mmr_proof.as_ref()) + .map_err(|_| VerificationError::FailedToDecodeXdmMmrProof)?; + let mmr_root_proof = maybe_mmr_root_proof + .ok_or(VerificationError::UnexpectedInvalidBundleProofData)?; + (mmr_root_proof, consensus_chain_mmr_leaf_proof) + } + None => { + // `None` means this is not an XDM so this fraud proof has to be a fasle invalid fraud proof + // to be valid, also in this case the `maybe_mmr_root_proof` should `None` else it is also invalid + return if is_true_invalid_fraud_proof || maybe_mmr_root_proof.is_some() { + Err(VerificationError::InvalidProof) + } else { + Ok(()) + } + } + }; + + let mmr_root = { + let MmrRootProof { + mmr_proof, + mmr_root_storage_proof, + } = mmr_root_proof; + + let leaf_data = MPV::verify_proof_and_extract_leaf(mmr_proof) + .ok_or(VerificationError::BadMmrProof)?; + + as BasicStorageProof>::verify::( + mmr_root_storage_proof, + consensus_chain_mmr_leaf_proof.consensus_block_number, + &leaf_data.state_root(), + )? + }; + + // Verify the original XDM mmr proof stateless to get the original result + let is_valid_mmr_proof = + MPV::verify_proof_stateless(mmr_root, consensus_chain_mmr_leaf_proof).is_some(); + + if is_valid_mmr_proof == is_true_invalid_fraud_proof { + return Err(VerificationError::InvalidProof); + } + Ok(()) } } } diff --git a/crates/sp-subspace-mmr/src/lib.rs b/crates/sp-subspace-mmr/src/lib.rs index eea9387055..a280fddc1f 100644 --- a/crates/sp-subspace-mmr/src/lib.rs +++ b/crates/sp-subspace-mmr/src/lib.rs @@ -130,6 +130,13 @@ pub trait MmrProofVerifier { mmr_leaf_proof: ConsensusChainMmrLeafProof, ) -> Option>; + fn verify_proof_stateless( + _mmr_root: MmrHash, + _mmr_leaf_proof: ConsensusChainMmrLeafProof, + ) -> Option> { + None + } + fn extract_leaf_without_verifying( mmr_leaf_proof: ConsensusChainMmrLeafProof, ) -> Option> { diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 66efa10b8e..18c856674f 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -465,16 +465,21 @@ pub struct MmrProofVerifier; impl sp_subspace_mmr::MmrProofVerifier, Hash> for MmrProofVerifier { fn verify_proof_and_extract_leaf( mmr_leaf_proof: ConsensusChainMmrLeafProof, Hash, mmr::Hash>, + ) -> Option { + let mmr_root = SubspaceMmr::mmr_root_hash(mmr_leaf_proof.consensus_block_number)?; + Self::verify_proof_stateless(mmr_root, mmr_leaf_proof) + } + + fn verify_proof_stateless( + mmr_root: mmr::Hash, + mmr_leaf_proof: ConsensusChainMmrLeafProof, Hash, mmr::Hash>, ) -> Option { let ConsensusChainMmrLeafProof { - consensus_block_number, opaque_mmr_leaf, proof, .. } = mmr_leaf_proof; - let mmr_root = SubspaceMmr::mmr_root_hash(consensus_block_number)?; - pallet_mmr::verify_leaves_proof::( mmr_root, vec![mmr::DataOrHash::Data( diff --git a/domains/client/domain-operator/src/fraud_proof.rs b/domains/client/domain-operator/src/fraud_proof.rs index 88672ad5ac..6b4cd40341 100644 --- a/domains/client/domain-operator/src/fraud_proof.rs +++ b/domains/client/domain-operator/src/fraud_proof.rs @@ -22,10 +22,11 @@ use sp_domains_fraud_proof::fraud_proof::{ ApplyExtrinsicMismatch, DomainRuntimeCodeAt, ExecutionPhase, FinalizeBlockMismatch, FraudProof, FraudProofVariant, InvalidBlockFeesProof, InvalidBundlesProof, InvalidBundlesProofData, InvalidDomainBlockHashProof, InvalidExtrinsicsRootProof, InvalidStateTransitionProof, - InvalidTransfersProof, ValidBundleDigest, ValidBundleProof, + InvalidTransfersProof, MmrRootProof, ValidBundleDigest, ValidBundleProof, }; use sp_domains_fraud_proof::storage_proof::{self, *}; use sp_domains_fraud_proof::FraudProofApi; +use sp_messenger::MessengerApi; use sp_mmr_primitives::MmrApi; use sp_runtime::generic::BlockId; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor, One}; @@ -707,7 +708,90 @@ where InvalidBundlesProofData::Extrinsic(extrinsic_proof) } InvalidBundleType::InvalidXDM(extrinsic_index) => { - todo!() + let messenger_api_version = self + .client + .runtime_api() + .api_version::, CBlock::Hash>>( + local_receipt.domain_block_hash, + )? + .ok_or_else(|| { + sp_blockchain::Error::RuntimeApiError(ApiError::Application( + format!( + "MessengerApi not found at: {:?}", + local_receipt.domain_block_hash + ) + .into(), + )) + })?; + + let encoded_extrinsic = bundle.extrinsics[extrinsic_index as usize].encode(); + let extrinsic = + ::Extrinsic::decode(&mut encoded_extrinsic.as_slice()) + .map_err(|decoding_error| { + FraudProofError::UnableToDecodeOpaqueBundleExtrinsic { + extrinsic_index: extrinsic_index as usize, + decoding_error, + } + })?; + + let maybe_xdm_mmr_proof = if messenger_api_version >= 4 { + self.client + .runtime_api() + .extract_xdm_mmr_proof(local_receipt.domain_block_hash, &extrinsic)? + } else { + None + }; + + let mmr_root_proof = match maybe_xdm_mmr_proof { + // `None` this is not an XDM so not need to generate mmr root proof + None => None, + Some(xdm_mmr_proof) => { + let xdm_mmr_proof_constructed_at_number = + xdm_mmr_proof.consensus_block_number; + + // The MMR leaf is added to the state at the next block + let mmr_root_state_added_at_number = + xdm_mmr_proof_constructed_at_number + One::one(); + let mmr_root_state_added_at_hash = self.consensus_client.hash(mmr_root_state_added_at_number)?.ok_or_else(|| { + sp_blockchain::Error::Backend(format!( + "Consensus block hash for #{mmr_root_state_added_at_number:?} not found", + )) + })?; + + let mmr_proof = sc_domains::generate_mmr_proof( + &self.consensus_client, + mmr_root_state_added_at_number, + )?; + let mmr_root_storage_proof = MmrRootStorageProof::generate( + self.consensus_client.as_ref(), + mmr_root_state_added_at_hash, + xdm_mmr_proof_constructed_at_number, + &self.storage_key_provider, + )?; + Some(MmrRootProof { + mmr_proof, + mmr_root_storage_proof, + }) + } + }; + + let extrinsic_proof = { + let encoded_extrinsics: Vec<_> = + bundle.extrinsics.iter().map(Encode::encode).collect(); + + StorageProofProvider::< + LayoutV1>, + >::generate_enumerated_proof_of_inclusion( + encoded_extrinsics.as_slice(), + extrinsic_index, + ) + .ok_or(FraudProofError::FailToGenerateProofOfInclusion)? + }; + + InvalidBundlesProofData::InvalidXDMProofData { + extrinsic_proof, + mmr_root_proof, + } } } }; diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index f35c373136..271da5473d 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -549,16 +549,21 @@ pub struct MmrProofVerifier; impl sp_subspace_mmr::MmrProofVerifier, Hash> for MmrProofVerifier { fn verify_proof_and_extract_leaf( mmr_leaf_proof: ConsensusChainMmrLeafProof, Hash, mmr::Hash>, + ) -> Option { + let mmr_root = SubspaceMmr::mmr_root_hash(mmr_leaf_proof.consensus_block_number)?; + Self::verify_proof_stateless(mmr_root, mmr_leaf_proof) + } + + fn verify_proof_stateless( + mmr_root: mmr::Hash, + mmr_leaf_proof: ConsensusChainMmrLeafProof, Hash, mmr::Hash>, ) -> Option { let ConsensusChainMmrLeafProof { - consensus_block_number, opaque_mmr_leaf, proof, .. } = mmr_leaf_proof; - let mmr_root = SubspaceMmr::mmr_root_hash(consensus_block_number)?; - pallet_mmr::verify_leaves_proof::( mmr_root, vec![mmr::DataOrHash::Data( From ff845bf55a7ba4c9986da35369fccef2231bd9f1 Mon Sep 17 00:00:00 2001 From: linning Date: Sat, 6 Jul 2024 02:13:55 +0800 Subject: [PATCH 07/10] Introduce the is_consensus_block_finalized host function and use it to check the domain XDM Signed-off-by: linning --- crates/sc-domains/src/lib.rs | 13 ++++++++-- crates/sp-subspace-mmr/src/host_functions.rs | 20 +++++++++++++-- .../sp-subspace-mmr/src/runtime_interface.rs | 7 ++++++ crates/subspace-service/src/lib.rs | 25 +++++++++++++------ domains/runtime/auto-id/src/lib.rs | 9 ++++++- domains/runtime/evm/src/lib.rs | 9 ++++++- domains/service/src/domain.rs | 16 +++++++++--- domains/test/runtime/evm/src/lib.rs | 9 ++++++- test/subspace-test-service/src/lib.rs | 24 +++++++++++++++--- 9 files changed, 110 insertions(+), 22 deletions(-) diff --git a/crates/sc-domains/src/lib.rs b/crates/sc-domains/src/lib.rs index ceb014580f..f07a48f61b 100644 --- a/crates/sc-domains/src/lib.rs +++ b/crates/sc-domains/src/lib.rs @@ -61,14 +61,20 @@ pub type RuntimeExecutor = sc_executor::WasmExecutor; pub struct ExtensionsFactory { consensus_client: Arc, executor: Arc, + confirmation_depth_k: u32, _marker: PhantomData<(CBlock, Block)>, } impl ExtensionsFactory { - pub fn new(consensus_client: Arc, executor: Arc) -> Self { + pub fn new( + consensus_client: Arc, + executor: Arc, + confirmation_depth_k: u32, + ) -> Self { Self { consensus_client, executor, + confirmation_depth_k, _marker: Default::default(), } } @@ -93,7 +99,10 @@ where ) -> Extensions { let mut exts = Extensions::new(); exts.register(SubspaceMmrExtension::new(Arc::new( - SubspaceMmrHostFunctionsImpl::::new(self.consensus_client.clone()), + SubspaceMmrHostFunctionsImpl::::new( + self.consensus_client.clone(), + self.confirmation_depth_k, + ), ))); exts.register(MessengerExtension::new(Arc::new( diff --git a/crates/sp-subspace-mmr/src/host_functions.rs b/crates/sp-subspace-mmr/src/host_functions.rs index 1b9e738ce1..8dbdd6fc5a 100644 --- a/crates/sp-subspace-mmr/src/host_functions.rs +++ b/crates/sp-subspace-mmr/src/host_functions.rs @@ -4,7 +4,7 @@ use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_core::H256; pub use sp_mmr_primitives::{EncodableOpaqueLeaf, LeafProof, MmrApi}; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor, Saturating}; use std::marker::PhantomData; use std::sync::Arc; use subspace_core_primitives::BlockNumber; @@ -22,6 +22,9 @@ pub trait SubspaceMmrHostFunctions: Send + Sync { &self, block_number: subspace_core_primitives::BlockNumber, ) -> Option; + + // Return if the given consensus block is finalized + fn is_consensus_block_finalized(&self, block_number: BlockNumber) -> bool; } sp_externalities::decl_extension! { @@ -38,13 +41,15 @@ impl SubspaceMmrExtension { /// Implementation of MMR host function. pub struct SubspaceMmrHostFunctionsImpl { consensus_client: Arc, + confirmation_depth_k: BlockNumber, _phantom: PhantomData, } impl SubspaceMmrHostFunctionsImpl { - pub fn new(consensus_client: Arc) -> Self { + pub fn new(consensus_client: Arc, confirmation_depth_k: BlockNumber) -> Self { SubspaceMmrHostFunctionsImpl { consensus_client, + confirmation_depth_k, _phantom: Default::default(), } } @@ -96,4 +101,15 @@ where .expect("Header must be available. This is unrecoverable error") .map(|block_hash| block_hash.into()) } + + fn is_consensus_block_finalized(&self, block_number: BlockNumber) -> bool { + let block_number = NumberFor::::from(block_number); + let last_finalized_block = self + .consensus_client + .info() + .best_number + .saturating_sub(self.confirmation_depth_k.into()); + + block_number <= last_finalized_block + } } diff --git a/crates/sp-subspace-mmr/src/runtime_interface.rs b/crates/sp-subspace-mmr/src/runtime_interface.rs index 1c152c9453..c1661eac39 100644 --- a/crates/sp-subspace-mmr/src/runtime_interface.rs +++ b/crates/sp-subspace-mmr/src/runtime_interface.rs @@ -51,4 +51,11 @@ pub trait DomainMmrRuntimeInterface { .expect("No `SubspaceMmrExtension` associated for the current context!") .verify_mmr_proof(leaves, encoded_proof) } + + // Return if the given consensus block is finalized + fn is_consensus_block_finalized(&mut self, block_number: BlockNumber) -> bool { + self.extension::() + .expect("No `SubspaceMmrExtension` associated for the current context!") + .is_consensus_block_finalized(block_number) + } } diff --git a/crates/subspace-service/src/lib.rs b/crates/subspace-service/src/lib.rs index 162cf8c517..028c6295a4 100644 --- a/crates/subspace-service/src/lib.rs +++ b/crates/subspace-service/src/lib.rs @@ -236,6 +236,7 @@ struct SubspaceExtensionsFactory { backend: Arc, pot_verifier: PotVerifier, domains_executor: Arc, + confirmation_depth_k: BlockNumber, _pos_table: PhantomData<(PosTable, DomainBlock)>, } @@ -264,6 +265,7 @@ where _block_hash: Block::Hash, _block_number: NumberFor, ) -> Extensions { + let confirmation_depth_k = self.confirmation_depth_k; let mut exts = Extensions::new(); exts.register(KzgExtension::new(self.kzg.clone())); exts.register(PosExtension::new::()); @@ -389,16 +391,23 @@ where FraudProofHostFunctionsImpl::<_, _, DomainBlock, _, _>::new( self.client.clone(), self.domains_executor.clone(), - |client, executor| { + move |client, executor| { let extension_factory = - DomainsExtensionFactory::<_, Block, DomainBlock, _>::new(client, executor); + DomainsExtensionFactory::<_, Block, DomainBlock, _>::new( + client, + executor, + confirmation_depth_k, + ); Box::new(extension_factory) as Box> }, ), ))); exts.register(SubspaceMmrExtension::new(Arc::new( - SubspaceMmrHostFunctionsImpl::::new(self.client.clone()), + SubspaceMmrHostFunctionsImpl::::new( + self.client.clone(), + confirmation_depth_k, + ), ))); exts.register(MessengerExtension::new(Arc::new( @@ -495,6 +504,10 @@ where let client = Arc::new(client); let client_info = client.info(); + let chain_constants = client + .runtime_api() + .chain_constants(client_info.best_hash) + .map_err(|error| ServiceError::Application(error.into()))?; let pot_verifier = PotVerifier::new( PotSeed::from_genesis(client_info.genesis_hash.as_ref(), pot_external_entropy), @@ -509,6 +522,7 @@ where pot_verifier: pot_verifier.clone(), domains_executor: Arc::new(domains_executor), backend: backend.clone(), + confirmation_depth_k: chain_constants.confirmation_depth_k(), _pos_table: PhantomData, }); @@ -521,11 +535,6 @@ where let select_chain = sc_consensus::LongestChain::new(backend.clone()); - let chain_constants = client - .runtime_api() - .chain_constants(client_info.best_hash) - .map_err(|error| ServiceError::Application(error.into()))?; - let segment_headers_store = tokio::task::block_in_place(|| { SegmentHeadersStore::new(client.clone(), chain_constants.confirmation_depth_k()) }) diff --git a/domains/runtime/auto-id/src/lib.rs b/domains/runtime/auto-id/src/lib.rs index e9869cf120..241c05f0ce 100644 --- a/domains/runtime/auto-id/src/lib.rs +++ b/domains/runtime/auto-id/src/lib.rs @@ -62,7 +62,9 @@ pub use sp_runtime::{MultiAddress, Perbill, Permill}; use sp_std::collections::btree_set::BTreeSet; use sp_std::marker::PhantomData; use sp_std::prelude::*; -use sp_subspace_mmr::domain_mmr_runtime_interface::verify_mmr_proof; +use sp_subspace_mmr::domain_mmr_runtime_interface::{ + is_consensus_block_finalized, verify_mmr_proof, +}; use sp_subspace_mmr::{ConsensusChainMmrLeafProof, MmrLeaf}; use sp_version::RuntimeVersion; use subspace_runtime_primitives::{ @@ -327,11 +329,16 @@ impl sp_subspace_mmr::MmrProofVerifier, Hash> for MmrP mmr_leaf_proof: ConsensusChainMmrLeafProof, Hash, MmrHash>, ) -> Option> { let ConsensusChainMmrLeafProof { + consensus_block_number, opaque_mmr_leaf: opaque_leaf, proof, .. } = mmr_leaf_proof; + if !is_consensus_block_finalized(consensus_block_number) { + return None; + } + let leaf: MmrLeaf = opaque_leaf.into_opaque_leaf().try_decode()?; diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index a2df9749c9..39310757aa 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -77,7 +77,9 @@ use sp_std::cmp::{max, Ordering}; use sp_std::collections::btree_set::BTreeSet; use sp_std::marker::PhantomData; use sp_std::prelude::*; -use sp_subspace_mmr::domain_mmr_runtime_interface::verify_mmr_proof; +use sp_subspace_mmr::domain_mmr_runtime_interface::{ + is_consensus_block_finalized, verify_mmr_proof, +}; use sp_subspace_mmr::{ConsensusChainMmrLeafProof, MmrLeaf}; use sp_version::RuntimeVersion; use subspace_runtime_primitives::{ @@ -456,11 +458,16 @@ impl sp_subspace_mmr::MmrProofVerifier, Hash> for MmrP mmr_leaf_proof: ConsensusChainMmrLeafProof, Hash, MmrHash>, ) -> Option> { let ConsensusChainMmrLeafProof { + consensus_block_number, opaque_mmr_leaf: opaque_leaf, proof, .. } = mmr_leaf_proof; + if !is_consensus_block_finalized(consensus_block_number) { + return None; + } + let leaf: MmrLeaf = opaque_leaf.into_opaque_leaf().try_decode()?; diff --git a/domains/service/src/domain.rs b/domains/service/src/domain.rs index 4f21129718..94082d6335 100644 --- a/domains/service/src/domain.rs +++ b/domains/service/src/domain.rs @@ -122,6 +122,7 @@ fn new_partial( config: &ServiceConfiguration, consensus_client: Arc, block_import_provider: &BIMP, + confirmation_depth_k: NumberFor, ) -> Result< PartialComponents< FullClient, @@ -140,7 +141,7 @@ fn new_partial( > where CBlock: BlockT, - NumberFor: From>, + NumberFor: From> + Into, CBlock::Hash: From + Into, CClient: HeaderBackend + BlockBackend @@ -179,7 +180,11 @@ where let executor = Arc::new(executor); client.execution_extensions().set_extensions_factory( - ExtensionsFactory::<_, CBlock, Block, _>::new(consensus_client.clone(), executor.clone()), + ExtensionsFactory::<_, CBlock, Block, _>::new( + consensus_client.clone(), + executor.clone(), + confirmation_depth_k.into(), + ), ); let telemetry_worker_handle = telemetry.as_ref().map(|(worker, _)| worker.handle()); @@ -331,7 +336,12 @@ where // TODO: Do we even need block announcement on domain node? // domain_config.announce_block = false; - let params = new_partial(&domain_config, consensus_client.clone(), &provider)?; + let params = new_partial( + &domain_config, + consensus_client.clone(), + &provider, + confirmation_depth_k, + )?; let (mut telemetry, _telemetry_worker_handle, code_executor, block_import) = params.other; diff --git a/domains/test/runtime/evm/src/lib.rs b/domains/test/runtime/evm/src/lib.rs index 2e9a39535c..0cb1370b13 100644 --- a/domains/test/runtime/evm/src/lib.rs +++ b/domains/test/runtime/evm/src/lib.rs @@ -76,7 +76,9 @@ use sp_std::cmp::{max, Ordering}; use sp_std::collections::btree_set::BTreeSet; use sp_std::marker::PhantomData; use sp_std::prelude::*; -use sp_subspace_mmr::domain_mmr_runtime_interface::verify_mmr_proof; +use sp_subspace_mmr::domain_mmr_runtime_interface::{ + is_consensus_block_finalized, verify_mmr_proof, +}; use sp_subspace_mmr::{ConsensusChainMmrLeafProof, MmrLeaf}; use sp_version::RuntimeVersion; use subspace_runtime_primitives::{ @@ -442,11 +444,16 @@ impl sp_subspace_mmr::MmrProofVerifier, Hash> for MmrP mmr_leaf_proof: ConsensusChainMmrLeafProof, Hash, MmrHash>, ) -> Option> { let ConsensusChainMmrLeafProof { + consensus_block_number, opaque_mmr_leaf: opaque_leaf, proof, .. } = mmr_leaf_proof; + if !is_consensus_block_finalized(consensus_block_number) { + return None; + } + let leaf: MmrLeaf = opaque_leaf.into_opaque_leaf().try_decode()?; diff --git a/test/subspace-test-service/src/lib.rs b/test/subspace-test-service/src/lib.rs index 42f407e811..843caaf753 100644 --- a/test/subspace-test-service/src/lib.rs +++ b/test/subspace-test-service/src/lib.rs @@ -86,7 +86,7 @@ use std::marker::PhantomData; use std::pin::Pin; use std::sync::Arc; use std::time; -use subspace_core_primitives::{PotOutput, Solution}; +use subspace_core_primitives::{BlockNumber, PotOutput, Solution}; use subspace_runtime_primitives::opaque::Block; use subspace_runtime_primitives::{AccountId, Balance, Hash, Signature}; use subspace_service::transaction_pool::FullPool; @@ -207,6 +207,7 @@ struct MockExtensionsFactory { consensus_backend: Arc, executor: Arc, mock_pot_verifier: Arc, + confirmation_depth_k: BlockNumber, _phantom: PhantomData, } @@ -218,12 +219,14 @@ impl executor: Arc, mock_pot_verifier: Arc, consensus_backend: Arc, + confirmation_depth_k: BlockNumber, ) -> Self { Self { consensus_client, consensus_backend, executor, mock_pot_verifier, + confirmation_depth_k, _phantom: Default::default(), } } @@ -262,20 +265,28 @@ where _block_hash: Block::Hash, _block_number: NumberFor, ) -> Extensions { + let confirmation_depth_k = self.confirmation_depth_k; let mut exts = Extensions::new(); exts.register(FraudProofExtension::new(Arc::new( FraudProofHostFunctionsImpl::<_, _, DomainBlock, Executor, _>::new( self.consensus_client.clone(), self.executor.clone(), - |client, executor| { + move |client, executor| { let extension_factory = - DomainsExtensionFactory::<_, Block, DomainBlock, _>::new(client, executor); + DomainsExtensionFactory::<_, Block, DomainBlock, _>::new( + client, + executor, + confirmation_depth_k, + ); Box::new(extension_factory) as Box> }, ), ))); exts.register(SubspaceMmrExtension::new(Arc::new( - SubspaceMmrHostFunctionsImpl::::new(self.consensus_client.clone()), + SubspaceMmrHostFunctionsImpl::::new( + self.consensus_client.clone(), + confirmation_depth_k, + ), ))); exts.register(MessengerExtension::new(Arc::new( MessengerHostFunctionsImpl::::new( @@ -407,6 +418,10 @@ impl MockConsensusNode { let domain_executor = Arc::new(sc_service::new_wasm_executor(&config)); let client = Arc::new(client); let mock_pot_verifier = Arc::new(MockPotVerfier::default()); + let chain_constants = client + .runtime_api() + .chain_constants(client.info().best_hash) + .expect("Fail to get chain constants"); client .execution_extensions() .set_extensions_factory(MockExtensionsFactory::< @@ -419,6 +434,7 @@ impl MockConsensusNode { domain_executor.clone(), Arc::clone(&mock_pot_verifier), backend.clone(), + chain_constants.confirmation_depth_k(), )); let select_chain = sc_consensus::LongestChain::new(backend.clone()); From 4a48bed0047bf679d823d3c778012b9328b9b3ff Mon Sep 17 00:00:00 2001 From: linning Date: Sat, 6 Jul 2024 02:19:16 +0800 Subject: [PATCH 08/10] Update the is_xdm_valid runtime API to check the XDM mmr proof and double check the XDM before pushing it to the bundle Signed-off-by: linning --- .../src/host_functions.rs | 4 +- crates/subspace-fake-runtime-api/src/lib.rs | 4 +- crates/subspace-runtime/src/lib.rs | 42 ++++++++++++------- .../src/stateless_runtime.rs | 11 ++++- .../src/domain_bundle_proposer.rs | 23 ++++++++++ domains/primitives/messenger/src/lib.rs | 4 +- domains/runtime/auto-id/src/lib.rs | 33 ++++++++------- domains/runtime/evm/src/lib.rs | 33 ++++++++------- domains/test/runtime/evm/src/lib.rs | 33 ++++++++------- test/subspace-test-runtime/src/lib.rs | 42 ++++++++++++------- 10 files changed, 150 insertions(+), 79 deletions(-) diff --git a/crates/sp-domains-fraud-proof/src/host_functions.rs b/crates/sp-domains-fraud-proof/src/host_functions.rs index 213634f3c6..f2c08fd263 100644 --- a/crates/sp-domains-fraud-proof/src/host_functions.rs +++ b/crates/sp-domains-fraud-proof/src/host_functions.rs @@ -397,8 +397,10 @@ where domain_stateless_runtime.set_storage(domain_initial_state); let encoded_extrinsic = opaque_extrinsic.encode(); + let extrinsic = + ::Extrinsic::decode(&mut encoded_extrinsic.as_slice()).ok()?; domain_stateless_runtime - .is_valid_xdm(encoded_extrinsic) + .is_xdm_mmr_proof_valid(&extrinsic) .expect("Runtime api must not fail. This is an unrecoverable error") } diff --git a/crates/subspace-fake-runtime-api/src/lib.rs b/crates/subspace-fake-runtime-api/src/lib.rs index f6dca2ccff..76076b930c 100644 --- a/crates/subspace-fake-runtime-api/src/lib.rs +++ b/crates/subspace-fake-runtime-api/src/lib.rs @@ -362,8 +362,8 @@ sp_api::impl_runtime_apis! { } impl sp_messenger::MessengerApi::Hash> for Runtime { - fn is_xdm_valid( - _extrinsic: Vec, + fn is_xdm_mmr_proof_valid( + _ext: &::Extrinsic ) -> Option { unreachable!() } diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 18c856674f..e7285a5c59 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -869,19 +869,31 @@ fn extract_segment_headers(ext: &UncheckedExtrinsic) -> Option) -> Option { - if let Ok(ext) = UncheckedExtrinsic::decode(&mut encoded_ext.as_slice()) { - match &ext.function { - RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg }) => { - Some(Messenger::validate_relay_message(msg).is_ok()) - } - RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => { - Some(Messenger::validate_relay_message_response(msg).is_ok()) - } - _ => None, +fn is_xdm_mmr_proof_valid(ext: &::Extrinsic) -> Option { + match &ext.function { + RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg }) + | RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => { + let ConsensusChainMmrLeafProof { + consensus_block_number, + opaque_mmr_leaf, + proof, + .. + } = msg.proof.consensus_mmr_proof(); + + let mmr_root = SubspaceMmr::mmr_root_hash(consensus_block_number)?; + + Some( + pallet_mmr::verify_leaves_proof::( + mmr_root, + vec![mmr::DataOrHash::Data( + EncodableOpaqueLeaf(opaque_mmr_leaf.0.clone()).into_opaque_leaf(), + )], + proof, + ) + .is_ok(), + ) } - } else { - None + _ => None, } } @@ -1312,10 +1324,10 @@ impl_runtime_apis! { } impl sp_messenger::MessengerApi::Hash> for Runtime { - fn is_xdm_valid( - extrinsic: Vec, + fn is_xdm_mmr_proof_valid( + ext: &::Extrinsic ) -> Option { - is_xdm_valid(extrinsic) + is_xdm_mmr_proof_valid(ext) } fn extract_xdm_mmr_proof(ext: &::Extrinsic) -> Option::Hash, sp_core::H256>> { diff --git a/domains/client/block-preprocessor/src/stateless_runtime.rs b/domains/client/block-preprocessor/src/stateless_runtime.rs index 5cfc11ea31..37c460bbbb 100644 --- a/domains/client/block-preprocessor/src/stateless_runtime.rs +++ b/domains/client/block-preprocessor/src/stateless_runtime.rs @@ -294,8 +294,15 @@ where >::is_inherent_extrinsic(self, Default::default(), extrinsic) } - pub fn is_valid_xdm(&self, extrinsic: Vec) -> Result, ApiError> { - , CBlock::Hash>>>::is_xdm_valid(self, Default::default(), extrinsic) + pub fn is_xdm_mmr_proof_valid( + &self, + extrinsic: &::Extrinsic, + ) -> Result, ApiError> { + , CBlock::Hash>>::is_xdm_mmr_proof_valid( + self, + Default::default(), + extrinsic, + ) } pub fn extract_xdm_mmr_proof( diff --git a/domains/client/domain-operator/src/domain_bundle_proposer.rs b/domains/client/domain-operator/src/domain_bundle_proposer.rs index 4a4fd3fefc..7f39340282 100644 --- a/domains/client/domain-operator/src/domain_bundle_proposer.rs +++ b/domains/client/domain-operator/src/domain_bundle_proposer.rs @@ -11,6 +11,7 @@ use sp_domains::{ BundleHeader, DomainBundleLimit, DomainId, DomainsApi, ExecutionReceipt, HeaderHashingFor, OperatorId, ProofOfElection, }; +use sp_messenger::MessengerApi; use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT, NumberFor, One, Zero}; use sp_runtime::Percent; use sp_transaction_pool::runtime_api::TaggedTransactionQueue; @@ -190,6 +191,17 @@ where } } + pub(crate) fn messenger_api_version(&self, at: Block::Hash) -> sp_blockchain::Result { + self.client + .runtime_api() + .api_version::, CBlock::Hash>>(at)? + .ok_or_else(|| { + sp_blockchain::Error::RuntimeApiError(ApiError::Application( + format!("MessengerApi not found at: {at:?}").into(), + )) + }) + } + pub(crate) async fn propose_bundle_at( &mut self, proof_of_election: ProofOfElection, @@ -237,6 +249,8 @@ where // Header signature size + 64; + let messenger_api_version = self.messenger_api_version(parent_hash)?; + let mut extrinsics = Vec::new(); let mut estimated_bundle_weight = Weight::default(); let mut bundle_size = 0u32; @@ -319,6 +333,15 @@ where break; } + // Double check XDM before adding it to the bundle + if messenger_api_version >= 4 { + if let Some(false) = + runtime_api_instance.is_xdm_mmr_proof_valid(parent_hash, pending_tx_data)? + { + continue; + } + } + // Double check the transaction validity, because the tx pool are re-validate the transaction // in pool asynchronously so there is race condition that the operator imported a domain block // and start producing bundle immediately before the re-validation based on the latest block diff --git a/domains/primitives/messenger/src/lib.rs b/domains/primitives/messenger/src/lib.rs index 0aad2e3e78..e37fd9af15 100644 --- a/domains/primitives/messenger/src/lib.rs +++ b/domains/primitives/messenger/src/lib.rs @@ -204,8 +204,8 @@ sp_api::decl_runtime_apis! { { /// Returns `Some(true)` if valid XDM or `Some(false)` if not /// Returns None if this is not an XDM - fn is_xdm_valid( - extrinsic: Vec + fn is_xdm_mmr_proof_valid( + ext: &Block::Extrinsic ) -> Option; // Extract the MMR proof from the XDM diff --git a/domains/runtime/auto-id/src/lib.rs b/domains/runtime/auto-id/src/lib.rs index 241c05f0ce..de26a90d78 100644 --- a/domains/runtime/auto-id/src/lib.rs +++ b/domains/runtime/auto-id/src/lib.rs @@ -502,19 +502,24 @@ construct_runtime!( } ); -fn is_xdm_valid(encoded_ext: Vec) -> Option { - if let Ok(ext) = UncheckedExtrinsic::decode(&mut encoded_ext.as_slice()) { - match &ext.function { - RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg }) => { - Some(Messenger::validate_relay_message(msg).is_ok()) +fn is_xdm_mmr_proof_valid(ext: &::Extrinsic) -> Option { + match &ext.function { + RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg }) + | RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => { + let ConsensusChainMmrLeafProof { + consensus_block_number, + opaque_mmr_leaf, + proof, + .. + } = msg.proof.consensus_mmr_proof(); + + if !is_consensus_block_finalized(consensus_block_number) { + return Some(false); } - RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => { - Some(Messenger::validate_relay_message_response(msg).is_ok()) - } - _ => None, + + Some(verify_mmr_proof(vec![opaque_mmr_leaf], proof.encode())) } - } else { - None + _ => None, } } @@ -870,10 +875,10 @@ impl_runtime_apis! { } impl sp_messenger::MessengerApi for Runtime { - fn is_xdm_valid( - extrinsic: Vec, + fn is_xdm_mmr_proof_valid( + extrinsic: &::Extrinsic, ) -> Option { - is_xdm_valid(extrinsic) + is_xdm_mmr_proof_valid(extrinsic) } fn extract_xdm_mmr_proof(ext: &::Extrinsic) -> Option> { diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index 39310757aa..793677315a 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -805,19 +805,24 @@ impl fp_rpc::ConvertTransaction for TransactionConve } } -fn is_xdm_valid(encoded_ext: Vec) -> Option { - if let Ok(ext) = UncheckedExtrinsic::decode(&mut encoded_ext.as_slice()) { - match &ext.0.function { - RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg }) => { - Some(Messenger::validate_relay_message(msg).is_ok()) +fn is_xdm_mmr_proof_valid(ext: &::Extrinsic) -> Option { + match &ext.0.function { + RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg }) + | RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => { + let ConsensusChainMmrLeafProof { + consensus_block_number, + opaque_mmr_leaf, + proof, + .. + } = msg.proof.consensus_mmr_proof(); + + if !is_consensus_block_finalized(consensus_block_number) { + return Some(false); } - RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => { - Some(Messenger::validate_relay_message_response(msg).is_ok()) - } - _ => None, + + Some(verify_mmr_proof(vec![opaque_mmr_leaf], proof.encode())) } - } else { - None + _ => None, } } @@ -1267,10 +1272,10 @@ impl_runtime_apis! { } impl sp_messenger::MessengerApi for Runtime { - fn is_xdm_valid( - extrinsic: Vec, + fn is_xdm_mmr_proof_valid( + extrinsic: &::Extrinsic ) -> Option { - is_xdm_valid(extrinsic) + is_xdm_mmr_proof_valid(extrinsic) } fn extract_xdm_mmr_proof(ext: &::Extrinsic) -> Option> { diff --git a/domains/test/runtime/evm/src/lib.rs b/domains/test/runtime/evm/src/lib.rs index 0cb1370b13..d41baac83f 100644 --- a/domains/test/runtime/evm/src/lib.rs +++ b/domains/test/runtime/evm/src/lib.rs @@ -781,19 +781,24 @@ impl fp_rpc::ConvertTransaction for TransactionConve } } -fn is_xdm_valid(encoded_ext: Vec) -> Option { - if let Ok(ext) = UncheckedExtrinsic::decode(&mut encoded_ext.as_slice()) { - match &ext.0.function { - RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg }) => { - Some(Messenger::validate_relay_message(msg).is_ok()) +fn is_xdm_mmr_proof_valid(ext: &::Extrinsic) -> Option { + match &ext.0.function { + RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg }) + | RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => { + let ConsensusChainMmrLeafProof { + consensus_block_number, + opaque_mmr_leaf, + proof, + .. + } = msg.proof.consensus_mmr_proof(); + + if !is_consensus_block_finalized(consensus_block_number) { + return Some(false); } - RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => { - Some(Messenger::validate_relay_message_response(msg).is_ok()) - } - _ => None, + + Some(verify_mmr_proof(vec![opaque_mmr_leaf], proof.encode())) } - } else { - None + _ => None, } } @@ -1227,10 +1232,10 @@ impl_runtime_apis! { } impl sp_messenger::MessengerApi for Runtime { - fn is_xdm_valid( - extrinsic: Vec, + fn is_xdm_mmr_proof_valid( + extrinsic: &::Extrinsic ) -> Option { - is_xdm_valid(extrinsic) + is_xdm_mmr_proof_valid(extrinsic) } fn extract_xdm_mmr_proof(ext: &::Extrinsic) -> Option> { diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 271da5473d..58b4b6defc 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -928,19 +928,31 @@ fn extract_segment_headers(ext: &UncheckedExtrinsic) -> Option) -> Option { - if let Ok(ext) = UncheckedExtrinsic::decode(&mut encoded_ext.as_slice()) { - match &ext.function { - RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg }) => { - Some(Messenger::validate_relay_message(msg).is_ok()) - } - RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => { - Some(Messenger::validate_relay_message_response(msg).is_ok()) - } - _ => None, +fn is_xdm_mmr_proof_valid(ext: &::Extrinsic) -> Option { + match &ext.function { + RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg }) + | RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => { + let ConsensusChainMmrLeafProof { + consensus_block_number, + opaque_mmr_leaf, + proof, + .. + } = msg.proof.consensus_mmr_proof(); + + let mmr_root = SubspaceMmr::mmr_root_hash(consensus_block_number)?; + + Some( + pallet_mmr::verify_leaves_proof::( + mmr_root, + vec![mmr::DataOrHash::Data( + EncodableOpaqueLeaf(opaque_mmr_leaf.0.clone()).into_opaque_leaf(), + )], + proof, + ) + .is_ok(), + ) } - } else { - None + _ => None, } } @@ -1509,10 +1521,10 @@ impl_runtime_apis! { } impl sp_messenger::MessengerApi::Hash> for Runtime { - fn is_xdm_valid( - extrinsic: Vec, + fn is_xdm_mmr_proof_valid( + extrinsic: &::Extrinsic ) -> Option { - is_xdm_valid(extrinsic) + is_xdm_mmr_proof_valid(extrinsic) } fn extract_xdm_mmr_proof(ext: &::Extrinsic) -> Option::Hash, sp_core::H256>> { From c0be534c2d7f99057550d8becd5a81e075cec5c3 Mon Sep 17 00:00:00 2001 From: linning Date: Sat, 6 Jul 2024 02:21:17 +0800 Subject: [PATCH 09/10] Minor refactoring to the domain client Signed-off-by: linning --- domains/client/block-preprocessor/src/lib.rs | 62 ++++++++++++------- .../domain-operator/src/bundle_processor.rs | 2 +- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/domains/client/block-preprocessor/src/lib.rs b/domains/client/block-preprocessor/src/lib.rs index 5bdda0e933..63e9cca2c0 100644 --- a/domains/client/block-preprocessor/src/lib.rs +++ b/domains/client/block-preprocessor/src/lib.rs @@ -151,7 +151,7 @@ where pub fn preprocess_consensus_block( &self, consensus_block_hash: CBlock::Hash, - domain_hash: Block::Hash, + parent_domain_block: (Block::Hash, NumberFor), ) -> sp_blockchain::Result>> { let (primary_extrinsics, shuffling_seed) = prepare_domain_block_elements::( &*self.consensus_client, @@ -178,8 +178,12 @@ where .runtime_api() .domain_tx_range(consensus_block_hash, self.domain_id)?; - let (inboxed_bundles, extrinsics) = - self.compile_bundles_to_extrinsics(bundles, tx_range, domain_hash)?; + let (inboxed_bundles, extrinsics) = self.compile_bundles_to_extrinsics( + bundles, + tx_range, + parent_domain_block, + consensus_block_hash, + )?; let extrinsics = deduplicate_and_shuffle_extrinsics::<::Extrinsic>( extrinsics, @@ -199,7 +203,8 @@ where &self, bundles: OpaqueBundles, tx_range: U256, - at: Block::Hash, + (domain_hash, domain_number): (Block::Hash, NumberFor), + at_consensus_hash: CBlock::Hash, ) -> sp_blockchain::Result<( Vec>, Vec<(Option, Block::Extrinsic)>, @@ -210,9 +215,16 @@ where let runtime_api = self.client.runtime_api(); for bundle in bundles { let extrinsic_root = bundle.extrinsics_root(); - match self.check_bundle_validity(&bundle, &tx_range, at)? { + match self.check_bundle_validity( + &bundle, + &tx_range, + (domain_hash, domain_number), + at_consensus_hash, + )? { BundleValidity::Valid(extrinsics) => { - let extrinsics: Vec<_> = match runtime_api.extract_signer(at, extrinsics) { + let extrinsics: Vec<_> = match runtime_api + .extract_signer(domain_hash, extrinsics) + { Ok(res) => res, Err(e) => { tracing::error!(error = ?e, "Error at calling runtime api: extract_signer"); @@ -250,7 +262,8 @@ where &self, bundle: &OpaqueBundle, CBlock::Hash, Block::Header, Balance>, tx_range: &U256, - at: Block::Hash, + (domain_hash, domain_number): (Block::Hash, NumberFor), + at_consensus_hash: CBlock::Hash, ) -> sp_blockchain::Result> { let bundle_vrf_hash = U256::from_be_bytes(bundle.sealed_header.header.proof_of_election.vrf_hash()); @@ -258,26 +271,23 @@ where let mut extrinsics = Vec::with_capacity(bundle.extrinsics.len()); let mut estimated_bundle_weight = Weight::default(); - let domain_block_number = self - .client - .number(at)? - .ok_or(sp_blockchain::Error::MissingHeader(at.to_string()))?; - + let runtime_api = self.client.runtime_api(); + let consensus_runtime_api = self.consensus_client.runtime_api(); let api_version = runtime_api - .api_version::, CBlock::Hash>>(at) + .api_version::, CBlock::Hash>>(domain_hash) .map_err(sp_blockchain::Error::RuntimeApiError)? .ok_or_else(|| { sp_blockchain::Error::RuntimeApiError(ApiError::Application( - format!("MessengerApi not found at: {:?}", at).into(), + format!("MessengerApi not found at: {:?}", domain_hash).into(), )) })?; // Check the validity of each extrinsic // // NOTE: for each extrinsic the checking order must follow `InvalidBundleType::checking_order` - let runtime_api = self.client.runtime_api(); for (index, opaque_extrinsic) in bundle.extrinsics.iter().enumerate() { - let decode_result = runtime_api.decode_extrinsic(at, opaque_extrinsic.clone())?; + let decode_result = + runtime_api.decode_extrinsic(domain_hash, opaque_extrinsic.clone())?; let extrinsic = match decode_result { Ok(extrinsic) => extrinsic, Err(err) => { @@ -293,8 +303,12 @@ where } }; - let is_within_tx_range = - runtime_api.is_within_tx_range(at, &extrinsic, &bundle_vrf_hash, tx_range)?; + let is_within_tx_range = runtime_api.is_within_tx_range( + domain_hash, + &extrinsic, + &bundle_vrf_hash, + tx_range, + )?; if !is_within_tx_range { return Ok(BundleValidity::Invalid(InvalidBundleType::OutOfRangeTx( @@ -305,7 +319,7 @@ where // Check if this extrinsic is an inherent extrinsic. // If so, this is an invalid bundle since these extrinsics should not be included in the // bundle. Extrinsic is always decodable due to the check above. - if runtime_api.is_inherent_extrinsic(at, &extrinsic)? { + if runtime_api.is_inherent_extrinsic(domain_hash, &extrinsic)? { return Ok(BundleValidity::Invalid( InvalidBundleType::InherentExtrinsic(index as u32), )); @@ -313,7 +327,7 @@ where if api_version >= 4 { if let Some(xdm_mmr_proof) = - runtime_api.extract_xdm_mmr_proof(at, &extrinsic)? + runtime_api.extract_xdm_mmr_proof(domain_hash, &extrinsic)? { let ConsensusChainMmrLeafProof { opaque_mmr_leaf, @@ -338,10 +352,10 @@ where // to maintain side-effect in the storage buffer. let is_legal_tx = runtime_api .check_extrinsics_and_do_pre_dispatch( - at, + domain_hash, vec![extrinsic.clone()], - domain_block_number, - at, + domain_number, + domain_hash, )? .is_ok(); @@ -351,7 +365,7 @@ where ))); } - let tx_weight = runtime_api.extrinsic_weight(at, &extrinsic)?; + let tx_weight = runtime_api.extrinsic_weight(domain_hash, &extrinsic)?; estimated_bundle_weight = estimated_bundle_weight.saturating_add(tx_weight); extrinsics.push(extrinsic); diff --git a/domains/client/domain-operator/src/bundle_processor.rs b/domains/client/domain-operator/src/bundle_processor.rs index 2cfcaa65e3..6c47526c02 100644 --- a/domains/client/domain-operator/src/bundle_processor.rs +++ b/domains/client/domain-operator/src/bundle_processor.rs @@ -282,7 +282,7 @@ where let maybe_preprocess_result = self .domain_block_preprocessor - .preprocess_consensus_block(consensus_block_hash, parent_hash)?; + .preprocess_consensus_block(consensus_block_hash, (parent_hash, parent_number))?; let preprocess_took = start.elapsed().as_millis(); if preprocess_took >= SLOW_PREPROCESS_MILLIS.into() { From f165ffde087ca8976af57f7eb06d6ede62e89acd Mon Sep 17 00:00:00 2001 From: linning Date: Sat, 6 Jul 2024 02:38:29 +0800 Subject: [PATCH 10/10] Add invalid XDM integration tests Signed-off-by: linning --- Cargo.lock | 3 + .../src/verification.rs | 2 +- domains/client/domain-operator/src/tests.rs | 453 +++++++++++++++++- test/subspace-test-runtime/src/lib.rs | 4 +- 4 files changed, 452 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33b440eebd..06c836a87a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2676,8 +2676,10 @@ dependencies = [ "sp-inherents", "sp-keyring", "sp-messenger", + "sp-mmr-primitives", "sp-runtime", "sp-state-machine", + "sp-subspace-mmr", "sp-timestamp", "sp-version", "sp-weights", @@ -12688,6 +12690,7 @@ dependencies = [ "sp-offchain", "sp-runtime", "sp-session", + "sp-subspace-mmr", "sp-transaction-pool", "sp-version", "subspace-core-primitives", diff --git a/crates/sp-domains-fraud-proof/src/verification.rs b/crates/sp-domains-fraud-proof/src/verification.rs index fe5674e3a5..2cef0728f8 100644 --- a/crates/sp-domains-fraud-proof/src/verification.rs +++ b/crates/sp-domains-fraud-proof/src/verification.rs @@ -779,7 +779,7 @@ where Err(VerificationError::InvalidProof) } else { Ok(()) - } + }; } }; diff --git a/domains/client/domain-operator/src/tests.rs b/domains/client/domain-operator/src/tests.rs index 7a1ccf53bb..95864dfb27 100644 --- a/domains/client/domain-operator/src/tests.rs +++ b/domains/client/domain-operator/src/tests.rs @@ -6,7 +6,7 @@ use crate::tests::TxPoolError::InvalidTransaction as TxPoolInvalidTransaction; use crate::OperatorSlotInfo; use codec::{Decode, Encode}; use cross_domain_message_gossip::ChannelStorage; -use domain_runtime_primitives::{AccountIdConverter, Hash}; +use domain_runtime_primitives::{AccountId20Converter, AccountIdConverter, Hash}; use domain_test_primitives::{OnchainStateApi, TimestampApi}; use domain_test_service::evm_domain_test_runtime::{Header, UncheckedExtrinsic}; use domain_test_service::EcdsaKeyring::{Alice, Bob, Charlie, Eve}; @@ -40,15 +40,19 @@ use sp_domains_fraud_proof::fraud_proof::{ }; use sp_domains_fraud_proof::InvalidTransactionCode; use sp_messenger::messages::{CrossDomainMessage, Proof}; +use sp_messenger::MessengerApi; use sp_mmr_primitives::{EncodableOpaqueLeaf, LeafProof as MmrProof}; use sp_runtime::generic::{BlockId, DigestItem}; use sp_runtime::traits::{ BlakeTwo256, Block as BlockT, Convert, Hash as HashT, Header as HeaderT, Zero, }; -use sp_runtime::transaction_validity::InvalidTransaction; +use sp_runtime::transaction_validity::{ + InvalidTransaction, TransactionSource, TransactionValidityError, +}; use sp_runtime::OpaqueExtrinsic; use sp_state_machine::backend::AsTrieBackend; use sp_subspace_mmr::ConsensusChainMmrLeafProof; +use sp_transaction_pool::runtime_api::TaggedTransactionQueue; use sp_weights::Weight; use std::collections::BTreeMap; use std::sync::Arc; @@ -1558,7 +1562,7 @@ async fn test_true_invalid_bundles_illegal_xdm_proof_creation_and_verification() nonce: Default::default(), proof: Proof::Domain { consensus_chain_mmr_proof: ConsensusChainMmrLeafProof { - consensus_block_number: Default::default(), + consensus_block_number: 1, consensus_block_hash: Default::default(), opaque_mmr_leaf: EncodableOpaqueLeaf(vec![0, 1, 2]), proof: MmrProof { @@ -1627,7 +1631,7 @@ async fn test_true_invalid_bundles_illegal_xdm_proof_creation_and_verification() // Wait for the fraud proof that target the bad ER let wait_for_fraud_proof_fut = ferdie.wait_for_fraud_proof(move |fp| { if let FraudProofVariant::InvalidBundles(proof) = &fp.proof { - if let InvalidBundleType::IllegalTx(extrinsic_index) = proof.invalid_bundle_type { + if let InvalidBundleType::InvalidXDM(extrinsic_index) = proof.invalid_bundle_type { assert!(proof.is_true_invalid_fraud_proof); assert_eq!(extrinsic_index, 0); return true; @@ -4432,9 +4436,9 @@ async fn test_skip_empty_bundle_production() { .build_evm_node(Role::Authority, GENESIS_DOMAIN_ID, &mut ferdie) .await; - // Wait for `BlockTreePruningDepth + 1` blocks which is 16 + 1 in test + // Wait for `BlockTreePruningDepth + 1` blocks which is 10 + 1 in test // to enure the genesis ER is confirmed - produce_blocks!(ferdie, alice, 17).await.unwrap(); + produce_blocks!(ferdie, alice, 11).await.unwrap(); let consensus_block_number = ferdie.client.info().best_number; let domain_block_number = alice.client.info().best_number; @@ -4556,7 +4560,7 @@ async fn test_bad_receipt_chain() { // Produce more bundle with bad ER that use previous bad ER as parent let mut parent_bad_receipt_hash = bad_receipt_hash; let mut bad_receipt_descendants = vec![]; - for _ in 0..10 { + for _ in 0..7 { let slot = ferdie.produce_slot(); let bundle = bundle_producer .produce_bundle( @@ -5045,3 +5049,438 @@ async fn test_equivocated_bundle_check() { assert_eq!(ferdie.client.info().best_number, pre_ferdie_best_number + 1); assert_eq!(alice.client.info().best_number, pre_alice_best_number); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_xdm_false_invalid_fraud_proof() { + let directory = TempDir::new().expect("Must be able to create temporary directory"); + + let mut builder = sc_cli::LoggerBuilder::new(""); + builder.with_colors(false); + let _ = builder.init(); + + let tokio_handle = tokio::runtime::Handle::current(); + + // Start Ferdie with Alice Key since that is the sudo key + let mut ferdie = MockConsensusNode::run( + tokio_handle.clone(), + Sr25519Alice, + BasePath::new(directory.path().join("ferdie")), + ); + + // Run Alice (an evm domain) + let mut alice = domain_test_service::DomainNodeBuilder::new( + tokio_handle.clone(), + Alice, + BasePath::new(directory.path().join("alice")), + ) + .build_evm_node(Role::Authority, GENESIS_DOMAIN_ID, &mut ferdie) + .await; + + let bundle_to_tx = |opaque_bundle| { + subspace_test_runtime::UncheckedExtrinsic::new_unsigned( + pallet_domains::Call::submit_bundle { opaque_bundle }.into(), + ) + .into() + }; + + // Run the cross domain gossip message worker + ferdie.start_cross_domain_gossip_message_worker(); + + produce_blocks!(ferdie, alice, 3).await.unwrap(); + + // add domain to consensus chain allowlist + ferdie + .construct_and_send_extrinsic_with(pallet_sudo::Call::sudo { + call: Box::new(subspace_test_runtime::RuntimeCall::Messenger( + pallet_messenger::Call::update_consensus_chain_allowlist { + update: ChainAllowlistUpdate::Add(ChainId::Domain(GENESIS_DOMAIN_ID)), + }, + )), + }) + .await + .expect("Failed to construct and send consensus chain allowlist update"); + + // produce another block so allowlist on consensus is updated + produce_blocks!(ferdie, alice, 1).await.unwrap(); + + // add consensus chain to domain chain allow list + ferdie + .construct_and_send_extrinsic_with(subspace_test_runtime::RuntimeCall::Messenger( + pallet_messenger::Call::initiate_domain_update_chain_allowlist { + domain_id: GENESIS_DOMAIN_ID, + update: ChainAllowlistUpdate::Add(ChainId::Consensus), + }, + )) + .await + .expect("Failed to construct and send domain chain allowlist update"); + + // produce another block so allowlist on domain are updated + produce_blocks!(ferdie, alice, 1).await.unwrap(); + + // Open channel between the Consensus chain and EVM domains + alice + .construct_and_send_extrinsic(evm_domain_test_runtime::RuntimeCall::Messenger( + pallet_messenger::Call::initiate_channel { + dst_chain_id: ChainId::Consensus, + params: pallet_messenger::InitiateChannelParams { + max_outgoing_messages: 100, + }, + }, + )) + .await + .expect("Failed to construct and send extrinsic"); + // Wait until channel open + produce_blocks_until!(ferdie, alice, { + alice + .get_open_channel_for_chain(ChainId::Consensus) + .is_some() + }) + .await + .unwrap(); + + // Transfer balance through XDM + ferdie + .construct_and_send_extrinsic_with(pallet_transporter::Call::transfer { + dst_location: pallet_transporter::Location { + chain_id: ChainId::Domain(GENESIS_DOMAIN_ID), + account_id: AccountId20Converter::convert(Alice.to_account_id()), + }, + amount: 10, + }) + .await + .expect("Failed to construct and send extrinsic"); + + // Wait until a bundle that cantains the XDM + let mut maybe_opaque_bundle = None; + produce_blocks_until!(ferdie, alice, { + let alice_best_hash = alice.client.info().best_hash; + let (_, opaque_bundle) = ferdie.produce_slot_and_wait_for_bundle_submission().await; + for tx in opaque_bundle.extrinsics.iter() { + if alice + .client + .runtime_api() + .extract_xdm_mmr_proof(alice_best_hash, tx) + .unwrap() + .is_some() + { + maybe_opaque_bundle.replace(opaque_bundle); + break; + } + } + maybe_opaque_bundle.is_some() + }) + .await + .unwrap(); + + // Produce a block that contains the XDM + let opaque_bundle = maybe_opaque_bundle.unwrap(); + let bundle_extrinsic_root = opaque_bundle.extrinsics_root(); + let slot = ferdie.produce_slot(); + produce_block_with!( + ferdie.produce_block_with_slot_at( + slot, + ferdie.client.info().best_hash, + Some(vec![bundle_to_tx(opaque_bundle)]) + ), + alice + ) + .await + .unwrap(); + + // produce another bundle that marks the XDM as invalid. + let (slot, mut opaque_bundle) = ferdie.produce_slot_and_wait_for_bundle_submission().await; + let (bad_receipt_hash, bad_submit_bundle_tx) = { + let bad_receipt = &mut opaque_bundle.sealed_header.header.receipt; + bad_receipt.inboxed_bundles = vec![InboxedBundle::invalid( + InvalidBundleType::InvalidXDM(0), + bundle_extrinsic_root, + )]; + + opaque_bundle.sealed_header.signature = Sr25519Keyring::Alice + .pair() + .sign(opaque_bundle.sealed_header.pre_hash().as_ref()) + .into(); + ( + opaque_bundle.receipt().hash::(), + bundle_to_tx(opaque_bundle), + ) + }; + + // Wait for the fraud proof that target the bad ER + let wait_for_fraud_proof_fut = ferdie.wait_for_fraud_proof(move |fp| { + if let FraudProofVariant::InvalidBundles(proof) = &fp.proof { + if let InvalidBundleType::InvalidXDM(extrinsic_index) = proof.invalid_bundle_type { + assert!(!proof.is_true_invalid_fraud_proof); + assert_eq!(extrinsic_index, 0); + return true; + } + } + false + }); + + // Produce a consensus block that contains the `bad_submit_bundle_tx` and the bad receipt should + // be added to the consensus chain block tree + // Produce a block that contains the `bad_submit_bundle_tx` + produce_block_with!( + ferdie.produce_block_with_slot_at( + slot, + ferdie.client.info().best_hash, + Some(vec![bad_submit_bundle_tx]) + ), + alice + ) + .await + .unwrap(); + assert!(ferdie.does_receipt_exist(bad_receipt_hash).unwrap()); + + let _ = wait_for_fraud_proof_fut.await; + + // Produce a consensus block that contains the fraud proof, the fraud proof wil be verified + // and executed, thus pruned the bad receipt from the block tree + ferdie.produce_blocks(1).await.unwrap(); + assert!(!ferdie.does_receipt_exist(bad_receipt_hash).unwrap()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_stale_fork_xdm_true_invalid_fraud_proof() { + let directory = TempDir::new().expect("Must be able to create temporary directory"); + + let mut builder = sc_cli::LoggerBuilder::new(""); + builder.with_colors(false); + let _ = builder.init(); + + let tokio_handle = tokio::runtime::Handle::current(); + + // Start Ferdie with Alice Key since that is the sudo key + let mut ferdie = MockConsensusNode::run( + tokio_handle.clone(), + Sr25519Alice, + BasePath::new(directory.path().join("ferdie")), + ); + + // Run Alice (an evm domain) + let mut alice = domain_test_service::DomainNodeBuilder::new( + tokio_handle.clone(), + Alice, + BasePath::new(directory.path().join("alice")), + ) + .build_evm_node(Role::Authority, GENESIS_DOMAIN_ID, &mut ferdie) + .await; + + let bundle_to_tx = |opaque_bundle| { + subspace_test_runtime::UncheckedExtrinsic::new_unsigned( + pallet_domains::Call::submit_bundle { opaque_bundle }.into(), + ) + .into() + }; + + // Run the cross domain gossip message worker + ferdie.start_cross_domain_gossip_message_worker(); + + produce_blocks!(ferdie, alice, 3).await.unwrap(); + + // add domain to consensus chain allowlist + ferdie + .construct_and_send_extrinsic_with(pallet_sudo::Call::sudo { + call: Box::new(subspace_test_runtime::RuntimeCall::Messenger( + pallet_messenger::Call::update_consensus_chain_allowlist { + update: ChainAllowlistUpdate::Add(ChainId::Domain(GENESIS_DOMAIN_ID)), + }, + )), + }) + .await + .expect("Failed to construct and send consensus chain allowlist update"); + + // produce another block so allowlist on consensus is updated + produce_blocks!(ferdie, alice, 1).await.unwrap(); + + // add consensus chain to domain chain allow list + ferdie + .construct_and_send_extrinsic_with(subspace_test_runtime::RuntimeCall::Messenger( + pallet_messenger::Call::initiate_domain_update_chain_allowlist { + domain_id: GENESIS_DOMAIN_ID, + update: ChainAllowlistUpdate::Add(ChainId::Consensus), + }, + )) + .await + .expect("Failed to construct and send domain chain allowlist update"); + + // produce another block so allowlist on domain are updated + produce_blocks!(ferdie, alice, 1).await.unwrap(); + + // Open channel between the Consensus chain and EVM domains + alice + .construct_and_send_extrinsic(evm_domain_test_runtime::RuntimeCall::Messenger( + pallet_messenger::Call::initiate_channel { + dst_chain_id: ChainId::Consensus, + params: pallet_messenger::InitiateChannelParams { + max_outgoing_messages: 100, + }, + }, + )) + .await + .expect("Failed to construct and send extrinsic"); + // Wait until channel open + produce_blocks_until!(ferdie, alice, { + alice + .get_open_channel_for_chain(ChainId::Consensus) + .is_some() + }) + .await + .unwrap(); + + // Transfer balance through XDM + ferdie + .construct_and_send_extrinsic_with(pallet_transporter::Call::transfer { + dst_location: pallet_transporter::Location { + chain_id: ChainId::Domain(GENESIS_DOMAIN_ID), + account_id: AccountId20Converter::convert(Alice.to_account_id()), + }, + amount: 10, + }) + .await + .expect("Failed to construct and send extrinsic"); + + // Wait until a bundle that cantains the XDM + let mut maybe_opaque_bundle = None; + produce_blocks_until!(ferdie, alice, { + let alice_best_hash = alice.client.info().best_hash; + let (_, opaque_bundle) = ferdie.produce_slot_and_wait_for_bundle_submission().await; + for tx in opaque_bundle.extrinsics.iter() { + if alice + .client + .runtime_api() + .extract_xdm_mmr_proof(alice_best_hash, tx) + .unwrap() + .is_some() + { + maybe_opaque_bundle.replace(opaque_bundle); + break; + } + } + maybe_opaque_bundle.is_some() + }) + .await + .unwrap(); + + let xdm = maybe_opaque_bundle.unwrap().extrinsics[0].clone(); + let mmr_proof_constructed_at_number = alice + .client + .runtime_api() + .extract_xdm_mmr_proof(alice.client.info().best_hash, &xdm) + .unwrap() + .unwrap() + .consensus_block_number; + + // Construct a fork and make it the best fork so the XDM mmr proof is in the stale fork + let best_number = ferdie.client.info().best_number; + let mut fork_block_hash = ferdie + .client + .hash(mmr_proof_constructed_at_number - 2) + .unwrap() + .unwrap(); + for _ in 0..(best_number - mmr_proof_constructed_at_number) + 5 { + let slot = ferdie.produce_slot(); + fork_block_hash = ferdie + .produce_block_with_slot_at(slot, fork_block_hash, Some(vec![])) + .await + .unwrap(); + } + // The XDM should be rejected as its mmr proof is in the stale fork + let alice_best_hash = alice.client.info().best_hash; + let res = alice + .client + .runtime_api() + .validate_transaction( + alice_best_hash, + TransactionSource::External, + xdm.clone(), + alice_best_hash, + ) + .unwrap(); + assert_eq!( + res, + Err(TransactionValidityError::Invalid( + InvalidTransaction::BadProof + )) + ); + + // Construct an bundle that contain the invalid xdm and submit the bundle + let (slot, mut opaque_bundle) = ferdie.produce_slot_and_wait_for_bundle_submission().await; + let (bundle_extrinsic_root, bad_submit_bundle_tx) = { + opaque_bundle.extrinsics.push(xdm); + let extrinsics = opaque_bundle + .extrinsics + .iter() + .map(|ext| ext.encode()) + .collect(); + opaque_bundle.sealed_header.header.bundle_extrinsics_root = + BlakeTwo256::ordered_trie_root(extrinsics, StateVersion::V1); + opaque_bundle.sealed_header.signature = Sr25519Keyring::Alice + .pair() + .sign(opaque_bundle.sealed_header.pre_hash().as_ref()) + .into(); + (opaque_bundle.extrinsics_root(), bundle_to_tx(opaque_bundle)) + }; + produce_block_with!( + ferdie.produce_block_with_slot_at( + slot, + ferdie.client.info().best_hash, + Some(vec![bad_submit_bundle_tx]) + ), + alice + ) + .await + .unwrap(); + + // produce another bundle that marks the invalid xdm as valid. + let (slot, mut opaque_bundle) = ferdie.produce_slot_and_wait_for_bundle_submission().await; + let (bad_receipt_hash, bad_submit_bundle_tx) = { + let bad_receipt = &mut opaque_bundle.sealed_header.header.receipt; + assert!(bad_receipt.inboxed_bundles[0].is_invalid()); + bad_receipt.inboxed_bundles = + vec![InboxedBundle::valid(H256::random(), bundle_extrinsic_root)]; + opaque_bundle.sealed_header.signature = Sr25519Keyring::Alice + .pair() + .sign(opaque_bundle.sealed_header.pre_hash().as_ref()) + .into(); + ( + opaque_bundle.receipt().hash::(), + bundle_to_tx(opaque_bundle), + ) + }; + + // Wait for the fraud proof that target the bad ER + let wait_for_fraud_proof_fut = ferdie.wait_for_fraud_proof(move |fp| { + if let FraudProofVariant::InvalidBundles(proof) = &fp.proof { + if let InvalidBundleType::InvalidXDM(extrinsic_index) = proof.invalid_bundle_type { + assert!(proof.is_true_invalid_fraud_proof); + assert_eq!(extrinsic_index, 0); + return true; + } + } + false + }); + + // Produce a consensus block that contains the `bad_submit_bundle_tx` and the bad receipt should + // be added to the consensus chain block tree + // Produce a block that contains the `bad_submit_bundle_tx` + produce_block_with!( + ferdie.produce_block_with_slot_at( + slot, + ferdie.client.info().best_hash, + Some(vec![bad_submit_bundle_tx]) + ), + alice + ) + .await + .unwrap(); + assert!(ferdie.does_receipt_exist(bad_receipt_hash).unwrap()); + + let _ = wait_for_fraud_proof_fut.await; + + // Produce a consensus block that contains the fraud proof, the fraud proof wil be verified + // and executed, thus pruned the bad receipt from the block tree + ferdie.produce_blocks(1).await.unwrap(); + assert!(!ferdie.does_receipt_exist(bad_receipt_hash).unwrap()); +} diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 58b4b6defc..789fc7ce34 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -278,7 +278,7 @@ parameter_types! { pub const SlotProbability: (u64, u64) = SLOT_PROBABILITY; pub const ShouldAdjustSolutionRange: bool = false; pub const ExpectedVotesPerBlock: u32 = 9; - pub const ConfirmationDepthK: u32 = 100; + pub const ConfirmationDepthK: u32 = 5; pub const RecentSegments: HistorySize = HistorySize::new(NonZeroU64::new(5).unwrap()); pub const RecentHistoryFraction: (HistorySize, HistorySize) = ( HistorySize::new(NonZeroU64::new(1).unwrap()), @@ -682,7 +682,7 @@ parameter_types! { pub const MaxBundlesPerBlock: u32 = 10; pub const DomainInstantiationDeposit: Balance = 100 * SSC; pub const MaxDomainNameLength: u32 = 32; - pub const BlockTreePruningDepth: u32 = 16; + pub const BlockTreePruningDepth: u32 = 10; pub const StakeWithdrawalLockingPeriod: BlockNumber = 20; pub const StakeEpochDuration: DomainNumber = 5; pub TreasuryAccount: AccountId = PalletId(*b"treasury").into_account_truncating();