From 3c0f63f3cae6fc18042ebfa4c5fce0c0d9bdd317 Mon Sep 17 00:00:00 2001 From: linning Date: Tue, 14 May 2024 02:06:49 +0800 Subject: [PATCH 1/5] Update MMR verification to also check the block number This is for the upcoming stateless FP because previous MMR only prove that there is a historical state root but stateless FP need to ensure this is the exact state root of a specific block. Note that XDM doesn't has this concern since it also check the channel nonce Signed-off-by: linning --- crates/pallet-domains/src/lib.rs | 31 +++++++++++++++++++++ crates/pallet-domains/src/tests.rs | 2 ++ crates/sp-subspace-mmr/src/lib.rs | 16 +++++++---- crates/subspace-runtime/src/lib.rs | 8 ++++-- domains/client/domain-operator/src/tests.rs | 16 ++++------- domains/pallets/messenger/src/lib.rs | 8 +++--- domains/runtime/auto-id/src/lib.rs | 8 +++--- domains/runtime/evm/src/lib.rs | 8 +++--- domains/test/runtime/evm/src/lib.rs | 8 +++--- test/subspace-test-primitives/src/lib.rs | 4 +-- test/subspace-test-runtime/src/lib.rs | 12 ++++---- 11 files changed, 80 insertions(+), 41 deletions(-) diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 3b4ef8288c..a59f489ad1 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -78,6 +78,7 @@ use sp_domains_fraud_proof::verification::{ use sp_runtime::traits::{BlockNumberProvider, CheckedSub, Hash, Header, One, Zero}; use sp_runtime::transaction_validity::TransactionPriority; use sp_runtime::{RuntimeAppPublic, SaturatedConversion, Saturating}; +use sp_subspace_mmr::{ConsensusChainMmrLeafProof, MmrProofVerifier}; pub use staking::OperatorConfig; use subspace_core_primitives::{BlockHash, PotOutput, SlotNumber, U256}; @@ -214,6 +215,7 @@ mod pallet { use sp_std::boxed::Box; use sp_std::collections::btree_set::BTreeSet; use sp_std::fmt::Debug; + use sp_subspace_mmr::MmrProofVerifier; use subspace_core_primitives::U256; use subspace_runtime_primitives::StorageFee; @@ -386,6 +388,16 @@ mod pallet { /// A hook to call after a domain is instantiated type OnDomainInstantiated: OnDomainInstantiated; + + /// Hash type of MMR + type MmrHash: Parameter + Member + Default + Clone; + + /// MMR proof verifier + type MmrProofVerifier: MmrProofVerifier< + Self::MmrHash, + BlockNumberFor, + StateRootOf, + >; } #[pallet::pallet] @@ -719,6 +731,10 @@ mod pallet { UnexpectedFraudProof, /// The bad receipt already reported by a previous fraud proof BadReceiptAlreadyReported, + /// Bad MMR proof, it may due to the proof is expired or it is generated against a different fork. + BadMmrProof, + /// Unexpected MMR proof + UnexpectedMmrProof, } impl From for Error { @@ -2248,6 +2264,21 @@ impl Pallet { let storage_fund_acc = storage_fund_account::(operator_id); T::Currency::reducible_balance(&storage_fund_acc, Preservation::Preserve, Fortitude::Polite) } + + pub fn verify_mmr_proof_and_extract_state_root( + mmr_leaf_proof: ConsensusChainMmrLeafProof, T::Hash, T::MmrHash>, + expected_block_number: BlockNumberFor, + ) -> Result { + let leaf_data = T::MmrProofVerifier::verify_proof_and_extract_leaf(mmr_leaf_proof) + .ok_or(FraudProofError::BadMmrProof)?; + + // Ensure it is a proof of the exact block that we expected + if expected_block_number != leaf_data.block_number() { + return Err(FraudProofError::UnexpectedMmrProof); + } + + Ok(leaf_data.state_root()) + } } impl sp_domains::DomainOwner for Pallet { diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index ed4a194db3..9ff57a55b8 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -294,6 +294,8 @@ impl pallet_domains::Config for Test { type DomainBundleSubmitted = (); type OnDomainInstantiated = (); type Balance = Balance; + type MmrHash = H256; + type MmrProofVerifier = (); } pub struct ExtrinsicStorageFees; diff --git a/crates/sp-subspace-mmr/src/lib.rs b/crates/sp-subspace-mmr/src/lib.rs index 1e91e4f8c6..a0d555d70b 100644 --- a/crates/sp-subspace-mmr/src/lib.rs +++ b/crates/sp-subspace-mmr/src/lib.rs @@ -39,12 +39,18 @@ pub enum MmrLeaf { V0(LeafDataV0), } -impl MmrLeaf { +impl MmrLeaf { pub fn state_root(&self) -> Hash { match self { MmrLeaf::V0(leaf) => leaf.state_root.clone(), } } + + pub fn block_number(&self) -> BlockNumber { + match self { + MmrLeaf::V0(leaf) => leaf.block_number.clone(), + } + } } /// MMR v0 leaf data @@ -116,15 +122,15 @@ impl Clone /// Trait to verify MMR proofs pub trait MmrProofVerifier { /// Returns consensus state root if the given MMR proof is valid - fn verify_proof_and_extract_consensus_state_root( + fn verify_proof_and_extract_leaf( mmr_leaf_proof: ConsensusChainMmrLeafProof, - ) -> Option; + ) -> Option>; } impl MmrProofVerifier for () { - fn verify_proof_and_extract_consensus_state_root( + fn verify_proof_and_extract_leaf( _mmr_leaf_proof: ConsensusChainMmrLeafProof, - ) -> Option { + ) -> Option> { None } } diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 2201979077..5b45fab084 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -501,9 +501,9 @@ impl sp_messenger::OnXDMRewards for OnXDMRewards { pub struct MmrProofVerifier; impl sp_subspace_mmr::MmrProofVerifier, Hash> for MmrProofVerifier { - fn verify_proof_and_extract_consensus_state_root( + fn verify_proof_and_extract_leaf( mmr_leaf_proof: ConsensusChainMmrLeafProof, Hash, mmr::Hash>, - ) -> Option { + ) -> Option { let ConsensusChainMmrLeafProof { consensus_block_number, opaque_mmr_leaf, @@ -524,7 +524,7 @@ impl sp_subspace_mmr::MmrProofVerifier, Hash> for Mm let leaf: mmr::Leaf = opaque_mmr_leaf.into_opaque_leaf().try_decode()?; - Some(leaf.state_root()) + Some(leaf) } } @@ -711,6 +711,8 @@ impl pallet_domains::Config for Runtime { type DomainBundleSubmitted = Messenger; type OnDomainInstantiated = Messenger; type Balance = Balance; + type MmrHash = mmr::Hash; + type MmrProofVerifier = MmrProofVerifier; } parameter_types! { diff --git a/domains/client/domain-operator/src/tests.rs b/domains/client/domain-operator/src/tests.rs index 5be3743bec..b509b217db 100644 --- a/domains/client/domain-operator/src/tests.rs +++ b/domains/client/domain-operator/src/tests.rs @@ -4414,11 +4414,9 @@ async fn test_verify_mmr_proof_stateless() { let res = ferdie .client .runtime_api() - .verify_proof_and_extract_consensus_state_root( - ferdie.client.info().best_hash, - proof.clone(), - ) - .unwrap(); + .verify_proof_and_extract_leaf(ferdie.client.info().best_hash, proof.clone()) + .unwrap() + .map(|leaf| leaf.state_root()); produce_blocks!(ferdie, alice, 1).await.unwrap(); @@ -4436,11 +4434,9 @@ async fn test_verify_mmr_proof_stateless() { let res = ferdie .client .runtime_api() - .verify_proof_and_extract_consensus_state_root( - ferdie.client.info().best_hash, - proof.clone(), - ) - .unwrap(); + .verify_proof_and_extract_leaf(ferdie.client.info().best_hash, proof.clone()) + .unwrap() + .map(|leaf| leaf.state_root()); assert_eq!(res, Some(expected_state_root)); produce_blocks!(ferdie, alice, 1).await.unwrap(); diff --git a/domains/pallets/messenger/src/lib.rs b/domains/pallets/messenger/src/lib.rs index e688f05fc1..e7a6c8fdda 100644 --- a/domains/pallets/messenger/src/lib.rs +++ b/domains/pallets/messenger/src/lib.rs @@ -1146,10 +1146,10 @@ mod pallet { // nonce should be either be next or in future. ensure!(xdm.nonce >= next_nonce, InvalidTransaction::Call); - let state_root = T::MmrProofVerifier::verify_proof_and_extract_consensus_state_root( - xdm.proof.consensus_mmr_proof(), - ) - .ok_or(InvalidTransaction::BadProof)?; + 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() diff --git a/domains/runtime/auto-id/src/lib.rs b/domains/runtime/auto-id/src/lib.rs index 5d2c5d545b..21f30ac013 100644 --- a/domains/runtime/auto-id/src/lib.rs +++ b/domains/runtime/auto-id/src/lib.rs @@ -321,9 +321,9 @@ type MmrHash = ::Output; pub struct MmrProofVerifier; impl sp_subspace_mmr::MmrProofVerifier, Hash> for MmrProofVerifier { - fn verify_proof_and_extract_consensus_state_root( + fn verify_proof_and_extract_leaf( mmr_leaf_proof: ConsensusChainMmrLeafProof, Hash, MmrHash>, - ) -> Option { + ) -> Option> { let ConsensusChainMmrLeafProof { opaque_mmr_leaf: opaque_leaf, proof, @@ -332,9 +332,9 @@ impl sp_subspace_mmr::MmrProofVerifier, Hash> for MmrP let leaf: MmrLeaf = opaque_leaf.into_opaque_leaf().try_decode()?; - let state_root = leaf.state_root(); + verify_mmr_proof(vec![EncodableOpaqueLeaf::from_leaf(&leaf)], proof.encode()) - .then_some(state_root) + .then_some(leaf) } } diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index 639968f579..a5c83be475 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -450,9 +450,9 @@ type MmrHash = ::Output; pub struct MmrProofVerifier; impl sp_subspace_mmr::MmrProofVerifier, Hash> for MmrProofVerifier { - fn verify_proof_and_extract_consensus_state_root( + fn verify_proof_and_extract_leaf( mmr_leaf_proof: ConsensusChainMmrLeafProof, Hash, MmrHash>, - ) -> Option { + ) -> Option> { let ConsensusChainMmrLeafProof { opaque_mmr_leaf: opaque_leaf, proof, @@ -461,9 +461,9 @@ impl sp_subspace_mmr::MmrProofVerifier, Hash> for MmrP let leaf: MmrLeaf = opaque_leaf.into_opaque_leaf().try_decode()?; - let state_root = leaf.state_root(); + verify_mmr_proof(vec![EncodableOpaqueLeaf::from_leaf(&leaf)], proof.encode()) - .then_some(state_root) + .then_some(leaf) } } diff --git a/domains/test/runtime/evm/src/lib.rs b/domains/test/runtime/evm/src/lib.rs index f4e57ccd14..3ad492455a 100644 --- a/domains/test/runtime/evm/src/lib.rs +++ b/domains/test/runtime/evm/src/lib.rs @@ -436,9 +436,9 @@ type MmrHash = ::Output; pub struct MmrProofVerifier; impl sp_subspace_mmr::MmrProofVerifier, Hash> for MmrProofVerifier { - fn verify_proof_and_extract_consensus_state_root( + fn verify_proof_and_extract_leaf( mmr_leaf_proof: ConsensusChainMmrLeafProof, Hash, MmrHash>, - ) -> Option { + ) -> Option> { let ConsensusChainMmrLeafProof { opaque_mmr_leaf: opaque_leaf, proof, @@ -447,9 +447,9 @@ impl sp_subspace_mmr::MmrProofVerifier, Hash> for MmrP let leaf: MmrLeaf = opaque_leaf.into_opaque_leaf().try_decode()?; - let state_root = leaf.state_root(); + verify_mmr_proof(vec![EncodableOpaqueLeaf::from_leaf(&leaf)], proof.encode()) - .then_some(state_root) + .then_some(leaf) } } diff --git a/test/subspace-test-primitives/src/lib.rs b/test/subspace-test-primitives/src/lib.rs index 7a6151aab0..8271c81bf1 100644 --- a/test/subspace-test-primitives/src/lib.rs +++ b/test/subspace-test-primitives/src/lib.rs @@ -5,7 +5,7 @@ use codec::{Decode, Encode}; use sp_core::H256; use sp_messenger::messages::{ChainId, ChannelId}; use sp_runtime::traits::NumberFor; -use sp_subspace_mmr::ConsensusChainMmrLeafProof; +use sp_subspace_mmr::{ConsensusChainMmrLeafProof, MmrLeaf}; sp_api::decl_runtime_apis! { /// Api for querying onchain state in the test @@ -21,6 +21,6 @@ sp_api::decl_runtime_apis! { fn get_open_channel_for_chain(dst_chain_id: ChainId) -> Option; /// Verify the mmr proof statelessly and extract the state root. - fn verify_proof_and_extract_consensus_state_root(proof: ConsensusChainMmrLeafProof, Block::Hash, H256>) -> Option; + fn verify_proof_and_extract_leaf(proof: ConsensusChainMmrLeafProof, Block::Hash, H256>) -> Option, Block::Hash>>; } } diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index eaf21993a2..0ef8a6a7c6 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -544,9 +544,9 @@ parameter_types! { pub struct MmrProofVerifier; impl sp_subspace_mmr::MmrProofVerifier, Hash> for MmrProofVerifier { - fn verify_proof_and_extract_consensus_state_root( + fn verify_proof_and_extract_leaf( mmr_leaf_proof: ConsensusChainMmrLeafProof, Hash, mmr::Hash>, - ) -> Option { + ) -> Option { let ConsensusChainMmrLeafProof { consensus_block_number, opaque_mmr_leaf, @@ -567,7 +567,7 @@ impl sp_subspace_mmr::MmrProofVerifier, Hash> for Mm let leaf: mmr::Leaf = opaque_mmr_leaf.into_opaque_leaf().try_decode()?; - Some(leaf.state_root()) + Some(leaf) } } @@ -743,6 +743,8 @@ impl pallet_domains::Config for Runtime { type DomainBundleSubmitted = Messenger; type OnDomainInstantiated = Messenger; type Balance = Balance; + type MmrHash = mmr::Hash; + type MmrProofVerifier = MmrProofVerifier; } parameter_types! { @@ -1594,8 +1596,8 @@ impl_runtime_apis! { Messenger::get_open_channel_for_chain(dst_chain_id).map(|(c, _)| c) } - fn verify_proof_and_extract_consensus_state_root(mmr_leaf_proof: ConsensusChainMmrLeafProof, ::Hash, H256>) -> Option { - >::verify_proof_and_extract_consensus_state_root(mmr_leaf_proof) + fn verify_proof_and_extract_leaf(mmr_leaf_proof: ConsensusChainMmrLeafProof, ::Hash, H256>) -> Option { + >::verify_proof_and_extract_leaf(mmr_leaf_proof) } } From 306e041d816a6b8a978afef8f994c4475556d4e3 Mon Sep 17 00:00:00 2001 From: linning Date: Tue, 14 May 2024 03:22:54 +0800 Subject: [PATCH 2/5] Update stateless fraud proof defination Notice some of the fraud proof unit tests in pallet-domains is removed in this commit because we already have end-to-end fraud proof integration tests in domain-operator while these unit tests are mannully constructing the fraud proof and only cover some of the verification steps thus just remove them instead of updating them base on the new defination Signed-off-by: linning --- crates/pallet-domains/Cargo.toml | 4 +- crates/pallet-domains/src/tests.rs | 563 +----------------- crates/sp-domains-fraud-proof/Cargo.toml | 2 + .../sp-domains-fraud-proof/src/fraud_proof.rs | 395 +++++------- domains/client/domain-operator/src/tests.rs | 74 ++- test/subspace-test-service/src/lib.rs | 5 +- 6 files changed, 191 insertions(+), 852 deletions(-) diff --git a/crates/pallet-domains/Cargo.toml b/crates/pallet-domains/Cargo.toml index bb1268406e..3e6b3f6baa 100644 --- a/crates/pallet-domains/Cargo.toml +++ b/crates/pallet-domains/Cargo.toml @@ -28,6 +28,7 @@ sp-domains-fraud-proof = { version = "0.1.0", default-features = false, path = " sp-io = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } sp-runtime = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } sp-std = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } +sp-subspace-mmr = { version = "0.1.0", default-features = false, path = "../sp-subspace-mmr" } sp-version = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d", features = ["serde"] } subspace-core-primitives = { version = "0.1.0", default-features = false, path = "../subspace-core-primitives" } subspace-runtime-primitives = { version = "0.1.0", default-features = false, path = "../subspace-runtime-primitives" } @@ -38,8 +39,6 @@ hex-literal = "0.4.1" pallet-timestamp = { git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } pallet-block-fees = { version = "0.1.0", default-features = false, path = "../../domains/pallets/block-fees" } sp-externalities = { git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } -sp-state-machine = { git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } -sp-trie = { git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } [features] default = ["std"] @@ -60,6 +59,7 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "sp-subspace-mmr/std", "sp-version/std", "subspace-core-primitives/std", "subspace-runtime-primitives/std", diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 9ff57a55b8..5a4c06928d 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -5,7 +5,7 @@ use crate::{ self as pallet_domains, BalanceOf, BlockSlot, BlockTree, BlockTreeNodes, BundleError, Config, ConsensusBlockHash, DomainBlockNumberFor, DomainHashingFor, DomainRegistry, ExecutionInbox, ExecutionReceiptOf, FraudProofError, FungibleHoldId, HeadReceiptNumber, NextDomainId, - Operators, ReceiptHashFor, + Operators, }; use codec::{Decode, Encode, MaxEncodedLen}; use domain_runtime_primitives::opaque::Header as DomainHeader; @@ -19,38 +19,22 @@ use frame_system::mocking::MockUncheckedExtrinsic; use frame_system::pallet_prelude::*; use scale_info::TypeInfo; use sp_core::crypto::Pair; -use sp_core::storage::{StateVersion, StorageKey}; use sp_core::{Get, H256, U256}; use sp_domains::merkle_tree::MerkleTree; -use sp_domains::proof_provider_and_verifier::StorageProofProvider; use sp_domains::storage::RawGenesis; use sp_domains::{ - BundleHeader, ChainId, DomainId, DomainsHoldIdentifier, ExecutionReceipt, ExtrinsicDigest, - InboxedBundle, InvalidBundleType, OpaqueBundle, OperatorAllowList, OperatorId, OperatorPair, - ProofOfElection, RuntimeType, SealedBundleHeader, StakingHoldIdentifier, -}; -use sp_domains_fraud_proof::fraud_proof::{ - FraudProof, InvalidBlockFeesProof, InvalidBundlesFraudProof, InvalidDomainBlockHashProof, - InvalidExtrinsicsRootProof, ValidBundleDigest, -}; -use sp_domains_fraud_proof::{ - DomainChainAllowlistUpdateExtrinsic, DomainInherentExtrinsic, DomainInherentExtrinsicData, - DomainStorageKeyRequest, FraudProofExtension, FraudProofHostFunctions, - FraudProofVerificationInfoRequest, FraudProofVerificationInfoResponse, SetCodeExtrinsic, - StatelessDomainRuntimeCall, + BundleHeader, ChainId, DomainId, DomainsHoldIdentifier, ExecutionReceipt, InboxedBundle, + OpaqueBundle, OperatorAllowList, OperatorId, OperatorPair, ProofOfElection, RuntimeType, + SealedBundleHeader, StakingHoldIdentifier, }; +use sp_domains_fraud_proof::fraud_proof::FraudProof; use sp_runtime::traits::{ AccountIdConversion, BlakeTwo256, BlockNumberProvider, Hash as HashT, IdentityLookup, One, }; use sp_runtime::transaction_validity::TransactionValidityError; -use sp_runtime::{BuildStorage, Digest, OpaqueExtrinsic, Saturating}; -use sp_state_machine::backend::AsTrieBackend; -use sp_state_machine::{prove_read, Backend, TrieBackendBuilder}; -use sp_std::sync::Arc; -use sp_trie::trie_types::TrieDBMutBuilderV1; -use sp_trie::{LayoutV1, PrefixedMemoryDB, StorageProof, TrieMut}; +use sp_runtime::{BuildStorage, OpaqueExtrinsic, Saturating}; use sp_version::RuntimeVersion; -use subspace_core_primitives::{Randomness, U256 as P256}; +use subspace_core_primitives::U256 as P256; use subspace_runtime_primitives::{Moment, StorageFee, SSC}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -334,175 +318,6 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities { t.into() } -pub(crate) struct MockDomainFraudProofExtension { - block_randomness: Randomness, - timestamp: Moment, - runtime_code: Vec, - tx_range: bool, - is_inherent: bool, - is_decodable: bool, - domain_total_stake: Balance, - bundle_slot_probability: (u64, u64), - operator_stake: Balance, - maybe_illegal_extrinsic_index: Option, - is_valid_xdm: Option, -} - -impl FraudProofHostFunctions for MockDomainFraudProofExtension { - fn get_fraud_proof_verification_info( - &self, - _consensus_block_hash: H256, - fraud_proof_verification_info_req: FraudProofVerificationInfoRequest, - ) -> Option { - let response = match fraud_proof_verification_info_req { - FraudProofVerificationInfoRequest::BlockRandomness => { - FraudProofVerificationInfoResponse::BlockRandomness(self.block_randomness) - } - FraudProofVerificationInfoRequest::DomainTimestampExtrinsic(_) => { - FraudProofVerificationInfoResponse::DomainTimestampExtrinsic( - UncheckedExtrinsic::new_unsigned( - pallet_timestamp::Call::::set { - now: self.timestamp, - } - .into(), - ) - .encode(), - ) - } - FraudProofVerificationInfoRequest::ConsensusChainByteFeeExtrinsic(_) => { - FraudProofVerificationInfoResponse::ConsensusChainByteFeeExtrinsic( - UncheckedExtrinsic::new_unsigned( - pallet_block_fees::Call::::set_next_consensus_chain_byte_fee { - transaction_byte_fee: Default::default(), - } - .into(), - ) - .encode(), - ) - } - FraudProofVerificationInfoRequest::DomainBundleBody { .. } => { - FraudProofVerificationInfoResponse::DomainBundleBody(Default::default()) - } - FraudProofVerificationInfoRequest::DomainRuntimeCode(_) => { - FraudProofVerificationInfoResponse::DomainRuntimeCode(Default::default()) - } - FraudProofVerificationInfoRequest::DomainSetCodeExtrinsic(_) => { - FraudProofVerificationInfoResponse::DomainSetCodeExtrinsic( - SetCodeExtrinsic::EncodedExtrinsic( - UncheckedExtrinsic::new_unsigned( - domain_pallet_executive::Call::::set_code { - code: self.runtime_code.clone(), - } - .into(), - ) - .encode(), - ), - ) - } - FraudProofVerificationInfoRequest::TxRangeCheck { .. } => { - FraudProofVerificationInfoResponse::TxRangeCheck(self.tx_range) - } - FraudProofVerificationInfoRequest::InherentExtrinsicCheck { .. } => { - FraudProofVerificationInfoResponse::InherentExtrinsicCheck(self.is_inherent) - } - FraudProofVerificationInfoRequest::ExtrinsicDecodableCheck { .. } => { - FraudProofVerificationInfoResponse::ExtrinsicDecodableCheck(self.is_decodable) - } - FraudProofVerificationInfoRequest::DomainElectionParams { .. } => { - FraudProofVerificationInfoResponse::DomainElectionParams { - domain_total_stake: self.domain_total_stake, - bundle_slot_probability: self.bundle_slot_probability, - } - } - FraudProofVerificationInfoRequest::OperatorStake { .. } => { - FraudProofVerificationInfoResponse::OperatorStake(self.operator_stake) - } - FraudProofVerificationInfoRequest::CheckExtrinsicsInSingleContext { .. } => { - FraudProofVerificationInfoResponse::CheckExtrinsicsInSingleContext( - self.maybe_illegal_extrinsic_index, - ) - } - FraudProofVerificationInfoRequest::StorageKey { .. } => { - FraudProofVerificationInfoResponse::StorageKey(None) - } - FraudProofVerificationInfoRequest::XDMValidationCheck { .. } => { - FraudProofVerificationInfoResponse::XDMValidationCheck(self.is_valid_xdm) - } - FraudProofVerificationInfoRequest::DomainChainsAllowlistUpdateExtrinsic(_) => { - FraudProofVerificationInfoResponse::DomainChainAllowlistUpdateExtrinsic( - DomainChainAllowlistUpdateExtrinsic::None, - ) - } - }; - - Some(response) - } - - fn derive_bundle_digest( - &self, - _consensus_block_hash: H256, - _domain_id: DomainId, - _bundle_body: Vec, - ) -> Option { - Some(H256::random()) - } - - fn derive_bundle_digest_v2( - &self, - _domain_runtime_code: Vec, - _bundle_body: Vec, - ) -> Option { - Some(H256::random()) - } - - fn execution_proof_check( - &self, - _domain_id: (u32, H256), - _pre_state_root: H256, - _encoded_proof: Vec, - _execution_method: &str, - _call_data: &[u8], - _domain_runtime_code: Vec, - ) -> Option> { - None - } - - fn check_extrinsics_in_single_context( - &self, - _domain_runtime_code: Vec, - _domain_block_id: (u32, H256), - _domain_block_state_root: H256, - _bundle_extrinsics: Vec, - _encoded_proof: Vec, - ) -> Option> { - None - } - - fn construct_domain_inherent_extrinsic( - &self, - _domain_runtime_code: Vec, - _domain_inherent_extrinsic_data: DomainInherentExtrinsicData, - ) -> Option { - None - } - - fn domain_storage_key( - &self, - _domain_runtime_code: Vec, - _req: DomainStorageKeyRequest, - ) -> Option> { - None - } - - fn domain_runtime_call( - &self, - _domain_runtime_code: Vec, - _call: StatelessDomainRuntimeCall, - ) -> Option { - None - } -} - pub(crate) fn new_test_ext_with_extensions() -> sp_io::TestExternalities { let version = RuntimeVersion { spec_name: "test".into(), @@ -928,370 +743,6 @@ fn test_invalid_fraud_proof() { }); } -#[test] -fn test_invalid_block_fees_fraud_proof() { - let creator = 0u128; - let operator_id = 1u64; - let head_domain_number = 10; - let mut ext = new_test_ext_with_extensions(); - ext.execute_with(|| { - let domain_id = register_genesis_domain(creator, vec![operator_id]); - extend_block_tree_from_zero(domain_id, operator_id, head_domain_number + 2); - assert_eq!( - HeadReceiptNumber::::get(domain_id), - head_domain_number - ); - - let bad_receipt_at = 8; - let mut domain_block = get_block_tree_node_at::(domain_id, bad_receipt_at).unwrap(); - - let bad_receipt_hash = domain_block - .execution_receipt - .hash::>(); - let (fraud_proof, root) = generate_invalid_block_fees_fraud_proof::( - domain_id, - bad_receipt_hash, - // set different reward in the storage and generate proof for that value - sp_domains::BlockFees::new( - domain_block - .execution_receipt - .block_fees - .domain_execution_fee - + 1, - domain_block - .execution_receipt - .block_fees - .consensus_storage_fee - + 1, - domain_block.execution_receipt.block_fees.burned_balance + 1, - ), - ); - domain_block.execution_receipt.final_state_root = root; - BlockTreeNodes::::insert(bad_receipt_hash, domain_block); - assert_ok!(Domains::validate_fraud_proof(&fraud_proof),); - }); -} - -fn generate_invalid_block_fees_fraud_proof( - domain_id: DomainId, - bad_receipt_hash: ReceiptHashFor, - block_fees: sp_domains::BlockFees>, -) -> (FraudProof<::DomainHeader>, T::Hash) { - let storage_key = sp_domains::operator_block_fees_final_key(); - let mut root = T::Hash::default(); - let mut mdb = PrefixedMemoryDB::::default(); - { - let mut trie = TrieDBMutBuilderV1::new(&mut mdb, &mut root).build(); - trie.insert(&storage_key, &block_fees.encode()).unwrap(); - }; - - let backend = TrieBackendBuilder::new(mdb, root).build(); - let (root, storage_proof) = storage_proof_for_key::(backend, StorageKey(storage_key)); - ( - FraudProof::InvalidBlockFees(InvalidBlockFeesProof { - domain_id, - bad_receipt_hash, - storage_proof, - }), - root, - ) -} - -fn storage_proof_for_key + AsTrieBackend>( - backend: B, - key: StorageKey, -) -> (T::Hash, StorageProof) { - let state_version = sp_runtime::StateVersion::default(); - let root = backend.storage_root(std::iter::empty(), state_version).0; - let proof = StorageProof::new(prove_read(backend, &[key]).unwrap().iter_nodes().cloned()); - (root, proof) -} - -#[test] -fn test_invalid_domain_extrinsic_root_proof() { - let creator = 0u128; - let operator_id = 1u64; - let head_domain_number = 10; - let mut ext = new_test_ext_with_extensions(); - let fraud_proof = ext.execute_with(|| { - let domain_id = register_genesis_domain(creator, vec![operator_id]); - extend_block_tree_from_zero(domain_id, operator_id, head_domain_number + 2); - assert_eq!( - HeadReceiptNumber::::get(domain_id), - head_domain_number - ); - - let bad_receipt_at = 8; - let valid_bundle_digests = [ValidBundleDigest { - bundle_index: 0, - bundle_digest: vec![(Some(vec![1, 2, 3]), ExtrinsicDigest::Data(vec![4, 5, 6]))], - }]; - let mut domain_block = get_block_tree_node_at::(domain_id, bad_receipt_at).unwrap(); - let bad_receipt = &mut domain_block.execution_receipt; - bad_receipt.inboxed_bundles = { - valid_bundle_digests - .iter() - .map(|vbd| { - InboxedBundle::valid(BlakeTwo256::hash_of(&vbd.bundle_digest), H256::random()) - }) - .collect() - }; - bad_receipt.domain_block_extrinsic_root = H256::random(); - - let bad_receipt_hash = bad_receipt.hash::>(); - let fraud_proof = - generate_invalid_domain_extrinsic_root_fraud_proof::(domain_id, bad_receipt_hash); - let (consensus_block_number, consensus_block_hash) = ( - bad_receipt.consensus_block_number, - bad_receipt.consensus_block_hash, - ); - ConsensusBlockHash::::insert(domain_id, consensus_block_number, consensus_block_hash); - BlockTreeNodes::::insert(bad_receipt_hash, domain_block); - fraud_proof - }); - - let fraud_proof_ext = FraudProofExtension::new(Arc::new(MockDomainFraudProofExtension { - block_randomness: Randomness::from([1u8; 32]), - timestamp: 1000, - runtime_code: vec![1, 2, 3, 4], - tx_range: true, - is_inherent: true, - is_decodable: true, - domain_total_stake: 100 * SSC, - operator_stake: 10 * SSC, - bundle_slot_probability: (0, 0), - maybe_illegal_extrinsic_index: None, - is_valid_xdm: None, - })); - ext.register_extension(fraud_proof_ext); - - ext.execute_with(|| { - assert_ok!(Domains::validate_fraud_proof(&fraud_proof),); - }) -} - -fn generate_invalid_domain_extrinsic_root_fraud_proof( - domain_id: DomainId, - bad_receipt_hash: ReceiptHashFor, -) -> FraudProof { - let valid_bundle_digests = vec![ValidBundleDigest { - bundle_index: 0, - bundle_digest: vec![(Some(vec![1, 2, 3]), ExtrinsicDigest::Data(vec![4, 5, 6]))], - }]; - - FraudProof::InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof { - domain_id, - bad_receipt_hash, - valid_bundle_digests, - }) -} - -#[test] -fn test_true_invalid_bundles_inherent_extrinsic_proof() { - let creator = 0u128; - let operator_id = 1u64; - let head_domain_number = 10; - let mut ext = new_test_ext_with_extensions(); - let fraud_proof = ext.execute_with(|| { - let domain_id = register_genesis_domain(creator, vec![operator_id]); - extend_block_tree_from_zero(domain_id, operator_id, head_domain_number + 2); - assert_eq!( - HeadReceiptNumber::::get(domain_id), - head_domain_number - ); - - let inherent_extrinsic = vec![1, 2, 3].encode(); - let extrinsics = vec![inherent_extrinsic]; - let bundle_extrinsic_root = - BlakeTwo256::ordered_trie_root(extrinsics.clone(), StateVersion::V1); - - let bad_receipt_at = 8; - let mut domain_block = get_block_tree_node_at::(domain_id, bad_receipt_at).unwrap(); - let bad_receipt = &mut domain_block.execution_receipt; - // bad receipt marks this particular bundle as valid even though bundle contains inherent extrinsic - bad_receipt.inboxed_bundles = - vec![InboxedBundle::valid(H256::random(), bundle_extrinsic_root)]; - bad_receipt.domain_block_extrinsic_root = H256::random(); - - let bad_receipt_hash = bad_receipt.hash::>(); - let fraud_proof = generate_invalid_bundle_inherent_extrinsic_fraud_proof::( - domain_id, - bad_receipt_hash, - 0, - 0, - extrinsics, - true, - ); - let (consensus_block_number, consensus_block_hash) = ( - bad_receipt.consensus_block_number, - bad_receipt.consensus_block_hash, - ); - ConsensusBlockHash::::insert(domain_id, consensus_block_number, consensus_block_hash); - BlockTreeNodes::::insert(bad_receipt_hash, domain_block); - fraud_proof - }); - - let fraud_proof_ext = FraudProofExtension::new(Arc::new(MockDomainFraudProofExtension { - block_randomness: Randomness::from([1u8; 32]), - timestamp: 1000, - runtime_code: vec![1, 2, 3, 4], - tx_range: true, - // return `true` indicating this is an inherent extrinsic - is_inherent: true, - is_decodable: true, - domain_total_stake: 100 * SSC, - operator_stake: 10 * SSC, - bundle_slot_probability: (0, 0), - maybe_illegal_extrinsic_index: None, - is_valid_xdm: None, - })); - ext.register_extension(fraud_proof_ext); - - ext.execute_with(|| { - assert_ok!(Domains::validate_fraud_proof(&fraud_proof),); - }) -} - -#[test] -fn test_false_invalid_bundles_inherent_extrinsic_proof() { - let creator = 0u128; - let operator_id = 1u64; - let head_domain_number = 10; - let mut ext = new_test_ext_with_extensions(); - let fraud_proof = ext.execute_with(|| { - let domain_id = register_genesis_domain(creator, vec![operator_id]); - extend_block_tree_from_zero(domain_id, operator_id, head_domain_number + 2); - assert_eq!( - HeadReceiptNumber::::get(domain_id), - head_domain_number - ); - - let non_inherent_extrinsic = vec![1, 2, 3].encode(); - let extrinsics = vec![non_inherent_extrinsic]; - let bundle_extrinsic_root = - BlakeTwo256::ordered_trie_root(extrinsics.clone(), StateVersion::V1); - - let bad_receipt_at = 8; - let mut domain_block = get_block_tree_node_at::(domain_id, bad_receipt_at).unwrap(); - let bad_receipt = &mut domain_block.execution_receipt; - // bad receipt marks this bundle as invalid even though bundle do not contain inherent extrinsic. - bad_receipt.inboxed_bundles = vec![InboxedBundle::invalid( - InvalidBundleType::InherentExtrinsic(0), - bundle_extrinsic_root, - )]; - bad_receipt.domain_block_extrinsic_root = H256::random(); - - let bad_receipt_hash = bad_receipt.hash::>(); - let fraud_proof = generate_invalid_bundle_inherent_extrinsic_fraud_proof::( - domain_id, - bad_receipt_hash, - 0, - 0, - extrinsics, - false, - ); - let (consensus_block_number, consensus_block_hash) = ( - bad_receipt.consensus_block_number, - bad_receipt.consensus_block_hash, - ); - ConsensusBlockHash::::insert(domain_id, consensus_block_number, consensus_block_hash); - BlockTreeNodes::::insert(bad_receipt_hash, domain_block); - fraud_proof - }); - - let fraud_proof_ext = FraudProofExtension::new(Arc::new(MockDomainFraudProofExtension { - block_randomness: Randomness::from([1u8; 32]), - timestamp: 1000, - runtime_code: vec![1, 2, 3, 4], - tx_range: true, - // return `false` indicating this is not an inherent extrinsic - is_inherent: false, - is_decodable: true, - domain_total_stake: 100 * SSC, - operator_stake: 10 * SSC, - bundle_slot_probability: (0, 0), - maybe_illegal_extrinsic_index: None, - is_valid_xdm: None, - })); - ext.register_extension(fraud_proof_ext); - - ext.execute_with(|| { - assert_ok!(Domains::validate_fraud_proof(&fraud_proof),); - }) -} - -fn generate_invalid_bundle_inherent_extrinsic_fraud_proof( - domain_id: DomainId, - bad_receipt_hash: ReceiptHashFor, - bundle_index: u32, - bundle_extrinsic_index: u32, - bundle_extrinsics: Vec>, - is_true_invalid_fraud_proof: bool, -) -> FraudProof { - let extrinsic_inclusion_proof = - StorageProofProvider::>::generate_enumerated_proof_of_inclusion( - bundle_extrinsics.as_slice(), - bundle_extrinsic_index, - ) - .unwrap(); - FraudProof::InvalidBundles(InvalidBundlesFraudProof { - domain_id, - bad_receipt_hash, - bundle_index, - invalid_bundle_type: InvalidBundleType::InherentExtrinsic(bundle_extrinsic_index), - proof_data: extrinsic_inclusion_proof, - is_true_invalid_fraud_proof, - }) -} - -#[test] -fn test_invalid_domain_block_hash_fraud_proof() { - let creator = 0u128; - let operator_id = 1u64; - let head_domain_number = 10; - let mut ext = new_test_ext_with_extensions(); - ext.execute_with(|| { - let domain_id = register_genesis_domain(creator, vec![operator_id]); - extend_block_tree_from_zero(domain_id, operator_id, head_domain_number + 2); - assert_eq!( - HeadReceiptNumber::::get(domain_id), - head_domain_number - ); - - let bad_receipt_at = 8; - let mut domain_block = get_block_tree_node_at::(domain_id, bad_receipt_at).unwrap(); - let (root, digest_storage_proof) = - generate_invalid_domain_block_hash_fraud_proof::(Digest::default()); - domain_block.execution_receipt.final_state_root = root; - domain_block.execution_receipt.domain_block_hash = H256::random(); - let bad_receipt_hash = domain_block - .execution_receipt - .hash::>(); - BlockTreeNodes::::insert(bad_receipt_hash, domain_block); - let fraud_proof = FraudProof::InvalidDomainBlockHash(InvalidDomainBlockHashProof { - domain_id, - bad_receipt_hash, - digest_storage_proof, - }); - assert_ok!(Domains::validate_fraud_proof(&fraud_proof),); - }); -} - -fn generate_invalid_domain_block_hash_fraud_proof( - digest: Digest, -) -> (T::Hash, StorageProof) { - let digest_storage_key = sp_domains::system_digest_final_key(); - let mut root = T::Hash::default(); - let mut mdb = PrefixedMemoryDB::::default(); - { - let mut trie = TrieDBMutBuilderV1::new(&mut mdb, &mut root).build(); - trie.insert(&digest_storage_key, &digest.encode()).unwrap(); - }; - - let backend = TrieBackendBuilder::new(mdb, root).build(); - storage_proof_for_key::(backend, StorageKey(digest_storage_key)) -} - #[test] fn test_basic_fraud_proof_processing() { let creator = 0u128; diff --git a/crates/sp-domains-fraud-proof/Cargo.toml b/crates/sp-domains-fraud-proof/Cargo.toml index 1258fccb11..8650e2ba4a 100644 --- a/crates/sp-domains-fraud-proof/Cargo.toml +++ b/crates/sp-domains-fraud-proof/Cargo.toml @@ -31,6 +31,7 @@ sp-messenger = { version = "0.1.0", default-features = false, path = "../../doma sp-runtime = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } sp-runtime-interface = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } sp-state-machine = { optional = true, git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } +sp-subspace-mmr = { version = "0.1.0", default-features = false, path = "../sp-subspace-mmr" } sp-std = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } sp-trie = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } subspace-core-primitives = { version = "0.1.0", default-features = false, path = "../subspace-core-primitives" } @@ -90,6 +91,7 @@ std = [ "sp-runtime-interface/std", "sp-std/std", "sp-state-machine/std", + "sp-subspace-mmr/std", "sp-trie/std", "subspace-core-primitives/std", "subspace-runtime-primitives/std", diff --git a/crates/sp-domains-fraud-proof/src/fraud_proof.rs b/crates/sp-domains-fraud-proof/src/fraud_proof.rs index 4a4cc2e87b..4ddbaa9706 100644 --- a/crates/sp-domains-fraud-proof/src/fraud_proof.rs +++ b/crates/sp-domains-fraud-proof/src/fraud_proof.rs @@ -1,21 +1,22 @@ #[cfg(not(feature = "std"))] extern crate alloc; +use crate::storage_proof::{self, *}; #[cfg(not(feature = "std"))] use alloc::vec::Vec; use codec::{Decode, Encode}; use core::fmt; use scale_info::TypeInfo; -use sp_consensus_slots::Slot; use sp_core::H256; use sp_domain_digests::AsPredigest; use sp_domains::proof_provider_and_verifier::StorageProofVerifier; use sp_domains::{ BundleValidity, DomainId, ExecutionReceiptFor, ExtrinsicDigest, HeaderHashFor, - HeaderHashingFor, InvalidBundleType, SealedBundleHeader, + HeaderHashingFor, InvalidBundleType, }; use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT}; use sp_runtime::{Digest, DigestItem}; +use sp_subspace_mmr::ConsensusChainMmrLeafProof; use sp_trie::StorageProof; use subspace_runtime_primitives::Balance; @@ -419,335 +420,221 @@ pub struct InvalidBundlesFraudProof { pub is_true_invalid_fraud_proof: bool, } -impl InvalidBundlesFraudProof { - pub fn new( - bad_receipt_hash: ReceiptHash, - domain_id: DomainId, - bundle_index: u32, - invalid_bundle_type: InvalidBundleType, - proof_data: StorageProof, - is_true_invalid_fraud_proof: bool, - ) -> Self { - Self { - bad_receipt_hash, - domain_id, - bundle_index, - invalid_bundle_type, - proof_data, - is_true_invalid_fraud_proof, - } - } +#[derive(Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] +pub struct FraudProof { + pub domain_id: DomainId, + /// Hash of the bad receipt this fraud proof targeted + pub bad_receipt_hash: HeaderHashFor, + /// The MMR proof for the consensus state root that used to verify the storage proof + /// + /// It is set `None` if the specific fraud proof variant doesn't contains storage proof + pub maybe_mmr_proof: Option>, + /// The domain runtime code storage proof + /// + /// It is set `None` if the specific fraud proof variant doesn't required domain runtime code + /// 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, } -/// Fraud proof. -// TODO: Revisit when fraud proof v2 is implemented. #[allow(clippy::large_enum_variant)] -#[derive(Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] -pub enum FraudProof { - InvalidStateTransition(InvalidStateTransitionProof>), - InvalidTransaction(InvalidTransactionProof>), - ImproperTransactionSortition(ImproperTransactionSortitionProof>), - InvalidBlockFees(InvalidBlockFeesProof>), - InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof>), - ValidBundle(ValidBundleProof>), - InvalidDomainBlockHash(InvalidDomainBlockHashProof>), - InvalidBundles(InvalidBundlesFraudProof>), - InvalidTransfers(InvalidTransfersProof>), +#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] +pub enum FraudProofVariant { + InvalidStateTransition(InvalidStateTransitionProof), + ValidBundle(ValidBundleProof), + InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof), + InvalidBundles(InvalidBundlesProof), + InvalidDomainBlockHash(InvalidDomainBlockHashProof), + InvalidBlockFees(InvalidBlockFeesProof), + InvalidTransfers(InvalidTransfersProof), // Dummy fraud proof only used in test and benchmark // // NOTE: the `Dummy` must be the last variant, because the `#[cfg(..)]` will apply to // all the variants after it. #[cfg(any(feature = "std", feature = "runtime-benchmarks"))] - Dummy { - /// Id of the domain this fraud proof targeted - domain_id: DomainId, - /// Hash of the bad receipt this fraud proof targeted - bad_receipt_hash: HeaderHashFor, - }, + Dummy, } -impl FraudProof { +impl FraudProof { pub fn domain_id(&self) -> DomainId { - match self { - Self::InvalidStateTransition(proof) => proof.domain_id, - Self::InvalidTransaction(proof) => proof.domain_id, - Self::ImproperTransactionSortition(proof) => proof.domain_id, - #[cfg(any(feature = "std", feature = "runtime-benchmarks"))] - Self::Dummy { domain_id, .. } => *domain_id, - Self::InvalidBlockFees(proof) => proof.domain_id(), - Self::InvalidExtrinsicsRoot(proof) => proof.domain_id, - Self::InvalidBundles(proof) => proof.domain_id, - Self::ValidBundle(proof) => proof.domain_id, - Self::InvalidDomainBlockHash(proof) => proof.domain_id, - Self::InvalidTransfers(proof) => proof.domain_id, - } + self.domain_id } - pub fn targeted_bad_receipt_hash(&self) -> Option> { - match self { - Self::InvalidStateTransition(proof) => Some(proof.bad_receipt_hash), - Self::InvalidTransaction(proof) => Some(proof.bad_receipt_hash), - Self::ImproperTransactionSortition(proof) => Some(proof.bad_receipt_hash), - #[cfg(any(feature = "std", feature = "runtime-benchmarks"))] - Self::Dummy { - bad_receipt_hash, .. - } => Some(*bad_receipt_hash), - Self::InvalidExtrinsicsRoot(proof) => Some(proof.bad_receipt_hash), - Self::InvalidBlockFees(proof) => Some(proof.bad_receipt_hash()), - Self::ValidBundle(proof) => Some(proof.bad_receipt_hash), - Self::InvalidBundles(proof) => Some(proof.bad_receipt_hash), - Self::InvalidDomainBlockHash(proof) => Some(proof.bad_receipt_hash), - Self::InvalidTransfers(proof) => Some(proof.bad_receipt_hash), + pub fn targeted_bad_receipt_hash(&self) -> HeaderHashFor { + self.bad_receipt_hash + } + + pub fn is_unexpected_domain_runtime_code_proof(&self) -> bool { + // The invalid domain block hash fraud proof doesn't use the domain runtime code + // during its verification so it is unexpected to see `maybe_domain_runtime_code_proof` + // set to `Some` + self.maybe_domain_runtime_code_proof.is_some() + && matches!(self.proof, FraudProofVariant::InvalidDomainBlockHash(_)) + } + + pub fn is_unexpected_mmr_proof(&self) -> bool { + if self.maybe_mmr_proof.is_none() { + return false; } + // Only the `InvalidExtrinsicsRoot`, `InvalidBundles` and `ValidBundle` fraud proof + // are using the MMR proof during verifiction, for other fraud proofs it is unexpected + // to see `maybe_mmr_proof` set to `Some` + !matches!( + self.proof, + FraudProofVariant::InvalidExtrinsicsRoot(_) + | FraudProofVariant::InvalidBundles(_) + | FraudProofVariant::ValidBundle(_) + ) } #[cfg(any(feature = "std", feature = "runtime-benchmarks"))] pub fn dummy_fraud_proof( domain_id: DomainId, bad_receipt_hash: HeaderHashFor, - ) -> FraudProof { - FraudProof::Dummy { + ) -> FraudProof { + Self { domain_id, bad_receipt_hash, + maybe_mmr_proof: None, + maybe_domain_runtime_code_proof: None, + proof: FraudProofVariant::Dummy, } } } -impl FraudProof { +impl FraudProof +where + Number: Encode, + Hash: Encode, + MmrHash: Encode, +{ pub fn hash(&self) -> HeaderHashFor { HeaderHashingFor::::hash(&self.encode()) } } -impl fmt::Debug for FraudProof { +impl fmt::Debug + for FraudProof +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let domain_id = self.domain_id(); - let bad_receipt_hash = self.targeted_bad_receipt_hash(); - match self { - Self::InvalidStateTransition(_) => { - write!( - f, - "InvalidStateTransitionFraudProof({domain_id:?}#{bad_receipt_hash:?})" - ) + let fp_target = + scale_info::prelude::format!("{:?}#{:?}", self.domain_id, self.bad_receipt_hash); + match &self.proof { + FraudProofVariant::InvalidStateTransition(_) => { + write!(f, "InvalidStateTransitionFraudProof({fp_target})") } - Self::InvalidTransaction(_) => { - write!( - f, - "InvalidTransactionFraudProof({domain_id:?}#{bad_receipt_hash:?})" - ) + FraudProofVariant::InvalidExtrinsicsRoot(_) => { + write!(f, "InvalidExtrinsicsRootFraudProof({fp_target})") } - Self::ImproperTransactionSortition(_) => { - write!( - f, - "ImproperTransactionSortitionFraudProof({domain_id:?}#{bad_receipt_hash:?})" - ) + FraudProofVariant::InvalidBlockFees(_) => { + write!(f, "InvalidBlockFeesFraudProof({fp_target})") } - Self::InvalidExtrinsicsRoot(_) => { - write!( - f, - "InvalidExtrinsicsRootFraudProof({domain_id:?}#{bad_receipt_hash:?})" - ) + FraudProofVariant::ValidBundle(_) => { + write!(f, "ValidBundleFraudProof({fp_target})") } - Self::InvalidBlockFees(_) => { + FraudProofVariant::InvalidBundles(proof) => { write!( f, - "InvalidBlockFeesFraudProof({domain_id:?}#{bad_receipt_hash:?})" + "InvalidBundlesFraudProof(type: {:?}, target: {fp_target})", + proof.invalid_bundle_type ) } - Self::ValidBundle(_) => { - write!( - f, - "ValidBundleFraudProof({domain_id:?}#{bad_receipt_hash:?})" - ) + FraudProofVariant::InvalidDomainBlockHash(_) => { + write!(f, "InvalidDomainBlockHashFraudProof({fp_target})") } - Self::InvalidBundles(_) => { - write!( - f, - "InvalidBundlesFraudProof({domain_id:?}#{bad_receipt_hash:?})" - ) - } - Self::InvalidDomainBlockHash(_) => { - write!( - f, - "InvalidDomainBlockHashFraudProof({domain_id:?}#{bad_receipt_hash:?})" - ) - } - Self::InvalidTransfers(_) => { - write!( - f, - "InvalidTransfersFraudProof({domain_id:?}#{bad_receipt_hash:?})" - ) + FraudProofVariant::InvalidTransfers(_) => { + write!(f, "InvalidTransfersFraudProof({fp_target})") } #[cfg(any(feature = "std", feature = "runtime-benchmarks"))] - Self::Dummy { .. } => { - write!(f, "DummyFraudProof({domain_id:?}#{bad_receipt_hash:?})") + FraudProofVariant::Dummy => { + write!(f, "DummyFraudProof({fp_target})") } } } } +/// Represents a valid bundle index and all the extrinsics within that bundle. +#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] +pub struct ValidBundleDigest { + /// Index of this bundle in the original list of bundles in the consensus block. + pub bundle_index: u32, + /// `Vec<(tx_signer, tx_hash)>` of all extrinsics + pub bundle_digest: Vec<( + Option, + ExtrinsicDigest, + )>, +} + +// Domain runtime code at a specific block +#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] +pub struct DomainRuntimeCodeAt { + pub mmr_proof: ConsensusChainMmrLeafProof, + pub domain_runtime_code_proof: DomainRuntimeCodeProof, +} + /// Proves an invalid state transition by challenging the trace at specific index in a bad receipt. #[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] -pub struct InvalidStateTransitionProof { - /// The id of the domain this fraud proof targeted - pub domain_id: DomainId, - /// Hash of the bad receipt in which an invalid trace occurred. - pub bad_receipt_hash: ReceiptHash, +pub struct InvalidStateTransitionProof { /// Proof recorded during the computation. - pub proof: StorageProof, + pub execution_proof: StorageProof, /// Execution phase. pub execution_phase: ExecutionPhase, } -pub fn dummy_invalid_state_transition_proof( - domain_id: DomainId, -) -> InvalidStateTransitionProof { - InvalidStateTransitionProof { - domain_id, - bad_receipt_hash: ReceiptHash::default(), - proof: StorageProof::empty(), - execution_phase: ExecutionPhase::FinalizeBlock { - mismatch: FinalizeBlockMismatch::StateRoot, - }, - } -} - -/// Represents a bundle equivocation proof. An equivocation happens when an executor -/// produces more than one bundle on the same slot. The proof of equivocation -/// are the given distinct bundle headers that were signed by the validator and which -/// include the slot number. -#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] -pub struct BundleEquivocationProof { - /// The id of the domain this fraud proof targeted - pub domain_id: DomainId, - /// The slot at which the equivocation happened. - pub slot: Slot, - // TODO: The generic type should be `` - // TODO: `SealedBundleHeader` contains `ExecutionReceipt` which make the size of the proof - // large, revisit when proceeding to fraud proof v2. - /// The first header involved in the equivocation. - pub first_header: SealedBundleHeader, - /// The second header involved in the equivocation. - pub second_header: SealedBundleHeader, +/// Fraud proof for the valid bundles in `ExecutionReceipt::inboxed_bundles` +#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] +pub struct ValidBundleProof { + /// The targetted bundle with proof + pub bundle_with_proof: OpaqueBundleWithProof, } -impl BundleEquivocationProof -where - Number: Clone + From + Encode, - Hash: Clone + Default + Encode, - DomainHeader: HeaderT, -{ - /// Returns the hash of this bundle equivocation proof. - pub fn hash(&self) -> HeaderHashFor { - HeaderHashingFor::::hash_of(self) - } +#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] +pub struct InvalidExtrinsicsRootProof { + /// Valid Bundle digests + pub valid_bundle_digests: Vec, + /// Block randomness storage proof + pub block_randomness_proof: BlockRandomnessProof, + /// The storage proof used during verification + pub domain_inherent_extrinsic_data_proof: DomainInherentExtrinsicDataProof, } -/// Represents an invalid transaction proof. -#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] -pub struct InvalidTransactionProof { - /// The id of the domain this fraud proof targeted - pub domain_id: DomainId, - /// Hash of the bad receipt this fraud proof targeted - pub bad_receipt_hash: DomainHash, - /// Number of the block at which the invalid transaction occurred. - pub domain_block_number: u32, - /// Hash of the domain block corresponding to `block_number`. - pub domain_block_hash: DomainHash, - // TODO: Verifiable invalid extrinsic. - pub invalid_extrinsic: Vec, - /// Storage witness needed for verifying this proof. - pub storage_proof: StorageProof, +#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] +pub enum InvalidBundlesProofData { + Extrinsic(StorageProof), + Bundle(OpaqueBundleWithProof), + BundleAndExecution { + bundle_with_proof: OpaqueBundleWithProof, + execution_proof: StorageProof, + }, } -/// Represents an invalid transaction proof. -#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] -pub struct ImproperTransactionSortitionProof { - /// The id of the domain this fraud proof targeted - pub domain_id: DomainId, - /// Hash of the bad receipt this fraud proof targeted - pub bad_receipt_hash: ReceiptHash, +#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] +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, } /// Represents an invalid block fees proof. #[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] -pub struct InvalidBlockFeesProof { - /// The id of the domain this fraud proof targeted - pub domain_id: DomainId, - /// Hash of the bad receipt this fraud proof targeted - pub bad_receipt_hash: ReceiptHash, +pub struct InvalidBlockFeesProof { /// Storage witness needed for verifying this proof. pub storage_proof: StorageProof, } /// Represents an invalid transfers proof. #[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] -pub struct InvalidTransfersProof { - /// The id of the domain this fraud proof targeted - pub domain_id: DomainId, - /// Hash of the bad receipt this fraud proof targeted - pub bad_receipt_hash: ReceiptHash, +pub struct InvalidTransfersProof { /// Storage witness needed for verifying this proof. pub storage_proof: StorageProof, } /// Represents an invalid domain block hash fraud proof. #[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] -pub struct InvalidDomainBlockHashProof { - /// The id of the domain this fraud proof targeted - pub domain_id: DomainId, - /// Hash of the bad receipt this fraud proof targeted - pub bad_receipt_hash: ReceiptHash, +pub struct InvalidDomainBlockHashProof { /// Digests storage proof that is used to derive Domain block hash. pub digest_storage_proof: StorageProof, } - -/// Represents a valid bundle index and all the extrinsics within that bundle. -#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] -pub struct ValidBundleDigest { - /// Index of this bundle in the original list of bundles in the consensus block. - pub bundle_index: u32, - /// `Vec<(tx_signer, tx_hash)>` of all extrinsics - pub bundle_digest: Vec<( - Option, - ExtrinsicDigest, - )>, -} - -/// Represents an Invalid domain extrinsics root proof with necessary info for verification. -#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] -pub struct InvalidExtrinsicsRootProof { - /// The id of the domain this fraud proof targeted - pub domain_id: DomainId, - /// Hash of the bad receipt this fraud proof targeted - pub bad_receipt_hash: ReceiptHash, - /// Valid Bundle digests - pub valid_bundle_digests: Vec, -} - -impl InvalidBlockFeesProof { - pub(crate) fn domain_id(&self) -> DomainId { - self.domain_id - } - - pub(crate) fn bad_receipt_hash(&self) -> ReceiptHash { - self.bad_receipt_hash - } -} - -/// Fraud proof for the valid bundles in `ExecutionReceipt::inboxed_bundles` -#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] -pub struct ValidBundleProof { - /// The id of the domain this fraud proof targeted - pub domain_id: DomainId, - /// The targetted bad receipt - pub bad_receipt_hash: ReceiptHash, - /// The index of the targetted bundle - pub bundle_index: u32, -} - -/// Digest storage key in frame_system. -/// Unfortunately, the digest storage is private and not possible to derive the key from it directly. -pub fn system_digest_final_key() -> Vec { - frame_support::storage::storage_prefix("System".as_ref(), "Digest".as_ref()).to_vec() -} diff --git a/domains/client/domain-operator/src/tests.rs b/domains/client/domain-operator/src/tests.rs index b509b217db..504f300931 100644 --- a/domains/client/domain-operator/src/tests.rs +++ b/domains/client/domain-operator/src/tests.rs @@ -1,6 +1,4 @@ -use crate::domain_block_processor::{ - generate_mmr_proof, DomainBlockProcessor, PendingConsensusBlocks, -}; +use crate::domain_block_processor::{DomainBlockProcessor, PendingConsensusBlocks}; use crate::domain_bundle_producer::DomainBundleProducer; use crate::domain_bundle_proposer::DomainBundleProposer; use crate::fraud_proof::{FraudProofGenerator, TraceDiffType}; @@ -17,6 +15,7 @@ use futures::StreamExt; use pallet_messenger::ChainAllowlistUpdate; use sc_client_api::{Backend, BlockBackend, BlockchainEvents, HeaderBackend}; use sc_consensus::SharedBlockImport; +use sc_domains::generate_mmr_proof; use sc_service::{BasePath, Role}; use sc_transaction_pool::error::Error as PoolError; use sc_transaction_pool_api::error::Error as TxPoolError; @@ -34,7 +33,7 @@ use sp_domains::{ InvalidBundleType, Transfers, }; use sp_domains_fraud_proof::fraud_proof::{ - ApplyExtrinsicMismatch, ExecutionPhase, FinalizeBlockMismatch, FraudProof, + ApplyExtrinsicMismatch, ExecutionPhase, FinalizeBlockMismatch, FraudProofVariant, InvalidBlockFeesProof, InvalidDomainBlockHashProof, InvalidExtrinsicsRootProof, InvalidTransfersProof, }; @@ -956,8 +955,8 @@ async fn test_bad_invalid_state_transition_proof_is_rejected() { ferdie.clear_tx_pool().await.unwrap(); // Modify fraud proof's mismatch index to a higher value and try to submit it. - match &fraud_proof { - FraudProof::InvalidStateTransition(invalid_state_transition_fraud_proof) => { + match &fraud_proof.proof { + FraudProofVariant::InvalidStateTransition(invalid_state_transition_fraud_proof) => { match &invalid_state_transition_fraud_proof.execution_phase { ExecutionPhase::ApplyExtrinsic { extrinsic_proof, @@ -970,7 +969,7 @@ async fn test_bad_invalid_state_transition_proof_is_rejected() { extrinsic_proof: extrinsic_proof.clone(), mismatch: ApplyExtrinsicMismatch::StateRoot(u32::MAX), }; - fraud_proof = FraudProof::InvalidStateTransition( + fraud_proof.proof = FraudProofVariant::InvalidStateTransition( modified_invalid_state_transition_fraud_proof, ); } @@ -983,7 +982,7 @@ async fn test_bad_invalid_state_transition_proof_is_rejected() { ExecutionPhase::FinalizeBlock { mismatch: FinalizeBlockMismatch::Longer(u32::MAX), }; - fraud_proof = FraudProof::InvalidStateTransition( + fraud_proof.proof = FraudProofVariant::InvalidStateTransition( modified_invalid_state_transition_fraud_proof, ); } @@ -1174,7 +1173,7 @@ async fn test_invalid_state_transition_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 FraudProof::InvalidStateTransition(proof) = fp { + if let FraudProofVariant::InvalidStateTransition(proof) = &fp.proof { match (trace_diff_type, mismatch_trace_index) { (TraceDiffType::Mismatch, mismatch_trace_index) => match mismatch_trace_index { 0 => assert!(matches!( @@ -1359,7 +1358,7 @@ async fn test_true_invalid_bundles_inherent_extrinsic_proof_creation_and_verific // 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 FraudProof::InvalidBundles(proof) = fp { + if let FraudProofVariant::InvalidBundles(proof) = &fp.proof { if let InvalidBundleType::InherentExtrinsic(_) = proof.invalid_bundle_type { assert!(proof.is_true_invalid_fraud_proof); return true; @@ -1472,7 +1471,7 @@ async fn test_false_invalid_bundles_inherent_extrinsic_proof_creation_and_verifi // 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 FraudProof::InvalidBundles(proof) = fp { + if let FraudProofVariant::InvalidBundles(proof) = &fp.proof { if let InvalidBundleType::InherentExtrinsic(_) = proof.invalid_bundle_type { assert!(!proof.is_true_invalid_fraud_proof); return true; @@ -1625,7 +1624,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 FraudProof::InvalidBundles(proof) = fp { + if let FraudProofVariant::InvalidBundles(proof) = &fp.proof { if let InvalidBundleType::IllegalTx(extrinsic_index) = proof.invalid_bundle_type { assert!(proof.is_true_invalid_fraud_proof); assert_eq!(extrinsic_index, 0); @@ -1790,7 +1789,7 @@ async fn test_true_invalid_bundles_illegal_extrinsic_proof_creation_and_verifica // 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 FraudProof::InvalidBundles(proof) = fp { + if let FraudProofVariant::InvalidBundles(proof) = &fp.proof { if let InvalidBundleType::IllegalTx(extrinsic_index) = proof.invalid_bundle_type { assert!(proof.is_true_invalid_fraud_proof); assert_eq!(extrinsic_index, 2); @@ -1923,7 +1922,7 @@ async fn test_false_invalid_bundles_illegal_extrinsic_proof_creation_and_verific // 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 FraudProof::InvalidBundles(proof) = fp { + if let FraudProofVariant::InvalidBundles(proof) = &fp.proof { if let InvalidBundleType::IllegalTx(extrinsic_index) = proof.invalid_bundle_type { assert!(!proof.is_true_invalid_fraud_proof); assert_eq!(extrinsic_index, 1); @@ -2023,8 +2022,8 @@ async fn test_invalid_block_fees_proof_creation() { // Wait for the fraud proof that target the bad ER let wait_for_fraud_proof_fut = ferdie.wait_for_fraud_proof(move |fp| { matches!( - fp, - FraudProof::InvalidBlockFees(InvalidBlockFeesProof { .. }) + fp.proof, + FraudProofVariant::InvalidBlockFees(InvalidBlockFeesProof { .. }) ) }); @@ -2125,8 +2124,8 @@ async fn test_invalid_transfers_fraud_proof() { // Wait for the fraud proof that target the bad ER let wait_for_fraud_proof_fut = ferdie.wait_for_fraud_proof(move |fp| { matches!( - fp, - FraudProof::InvalidTransfers(InvalidTransfersProof { .. }) + fp.proof, + FraudProofVariant::InvalidTransfers(InvalidTransfersProof { .. }) ) }); @@ -2222,8 +2221,8 @@ async fn test_invalid_domain_block_hash_proof_creation() { // Wait for the fraud proof that target the bad ER let wait_for_fraud_proof_fut = ferdie.wait_for_fraud_proof(move |fp| { matches!( - fp, - FraudProof::InvalidDomainBlockHash(InvalidDomainBlockHashProof { .. }) + fp.proof, + FraudProofVariant::InvalidDomainBlockHash(InvalidDomainBlockHashProof { .. }) ) }); @@ -2319,8 +2318,8 @@ async fn test_invalid_domain_extrinsics_root_proof_creation() { // Wait for the fraud proof that target the bad ER let wait_for_fraud_proof_fut = ferdie.wait_for_fraud_proof(move |fp| { matches!( - fp, - FraudProof::InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof { .. }) + fp.proof, + FraudProofVariant::InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof { .. }) ) }); @@ -2592,12 +2591,9 @@ async fn test_valid_bundle_proof_generation_and_verification() { ) .into() }; - let proof_to_tx = |proof| { + let proof_to_tx = |fraud_proof| { subspace_test_runtime::UncheckedExtrinsic::new_unsigned( - pallet_domains::Call::submit_fraud_proof { - fraud_proof: Box::new(FraudProof::ValidBundle(proof)), - } - .into(), + pallet_domains::Call::submit_fraud_proof { fraud_proof }.into(), ) .into() }; @@ -2651,26 +2647,27 @@ async fn test_valid_bundle_proof_generation_and_verification() { pallet_domains::Call::submit_fraud_proof { fraud_proof }, ) = ext.function { - if let FraudProof::ValidBundle(proof) = *fraud_proof { + if let FraudProofVariant::ValidBundle(ref proof) = fraud_proof.proof { // The fraud proof is targetting the `bad_receipt` assert_eq!( - proof.bad_receipt_hash, + fraud_proof.bad_receipt_hash, bad_receipt.hash::>() ); // If the fraud proof target a non-exist receipt then it is invalid - let mut bad_proof = proof.clone(); - bad_proof.bad_receipt_hash = H256::random(); + let mut bad_fraud_proof = fraud_proof.clone(); + bad_fraud_proof.bad_receipt_hash = H256::random(); assert!(ferdie - .submit_transaction(proof_to_tx(bad_proof)) + .submit_transaction(proof_to_tx(bad_fraud_proof)) .await .is_err()); // If the fraud proof point to non-exist bundle then it is invalid - let mut bad_proof = proof.clone(); - bad_proof.bundle_index = u32::MAX; + let (mut bad_fraud_proof, mut bad_proof) = (fraud_proof.clone(), proof.clone()); + bad_proof.bundle_with_proof.bundle_index = u32::MAX; + bad_fraud_proof.proof = FraudProofVariant::ValidBundle(bad_proof); assert!(ferdie - .submit_transaction(proof_to_tx(bad_proof)) + .submit_transaction(proof_to_tx(bad_fraud_proof)) .await .is_err()); @@ -4081,9 +4078,9 @@ async fn test_bad_receipt_chain() { // Wait for a fraud proof that target the first bad ER let wait_for_fraud_proof_fut = ferdie.wait_for_fraud_proof(move |fp| { matches!( - fp, - FraudProof::InvalidDomainBlockHash(InvalidDomainBlockHashProof { .. }) - ) && fp.targeted_bad_receipt_hash() == Some(bad_receipt_hash) + fp.proof, + FraudProofVariant::InvalidDomainBlockHash(InvalidDomainBlockHashProof { .. }) + ) && fp.targeted_bad_receipt_hash() == bad_receipt_hash }); // Produce more bundle with bad ER that use previous bad ER as parent @@ -4365,6 +4362,7 @@ async fn test_handle_duplicated_tx_with_diff_nonce_in_previous_bundle() { assert_eq!(alice.account_nonce(), nonce + 3); } +// TODO: add test to ensure MMR proof from diff fork wil be rejected #[tokio::test(flavor = "multi_thread")] async fn test_verify_mmr_proof_stateless() { use subspace_test_primitives::OnchainStateApi as _; diff --git a/test/subspace-test-service/src/lib.rs b/test/subspace-test-service/src/lib.rs index 89faaf83d6..a6f18d573f 100644 --- a/test/subspace-test-service/src/lib.rs +++ b/test/subspace-test-service/src/lib.rs @@ -97,7 +97,8 @@ use subspace_test_runtime::{ use substrate_frame_rpc_system::AccountNonceApi; use substrate_test_client::{RpcHandlersExt, RpcTransactionError, RpcTransactionOutput}; -type FraudProofFor = FraudProof<::Header>; +type FraudProofFor = + FraudProof, ::Hash, ::Header, H256>; const MAX_PRODUCE_BUNDLE_TRY: usize = 10; @@ -796,7 +797,7 @@ impl MockConsensusNode { fraud_proof_predict: FP, ) -> Pin + Send>> where - FP: Fn(&FraudProofFor) -> bool + Send + 'static, + FP: Fn(&FraudProofFor) -> bool + Send + 'static, { let tx_pool = self.transaction_pool.clone(); let mut import_tx_stream = self.transaction_pool.import_notification_stream(); From aa0e7a824f0499c1ae3cfee04c20f1907086b524 Mon Sep 17 00:00:00 2001 From: linning Date: Tue, 14 May 2024 03:33:00 +0800 Subject: [PATCH 3/5] Upate stateless fraud proof generation Signed-off-by: linning --- crates/pallet-domains/src/lib.rs | 9 + crates/sp-domains/src/lib.rs | 5 +- crates/subspace-fake-runtime-api/src/lib.rs | 4 + crates/subspace-runtime/src/lib.rs | 4 + domains/client/domain-operator/Cargo.toml | 1 + .../domain-operator/src/bundle_processor.rs | 2 + .../src/domain_block_processor.rs | 113 +-- .../domain-operator/src/domain_worker.rs | 4 +- .../client/domain-operator/src/fraud_proof.rs | 811 ++++++++++++------ .../client/domain-operator/src/operator.rs | 4 +- test/subspace-test-runtime/src/lib.rs | 4 + 11 files changed, 628 insertions(+), 333 deletions(-) diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index a59f489ad1..7d7537f6e1 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -2265,6 +2265,15 @@ impl Pallet { T::Currency::reducible_balance(&storage_fund_acc, Preservation::Preserve, Fortitude::Polite) } + pub fn is_domain_runtime_updraded_since( + domain_id: DomainId, + at: BlockNumberFor, + ) -> Option { + Self::runtime_id(domain_id) + .and_then(RuntimeRegistry::::get) + .map(|runtime_obj| runtime_obj.updated_at >= at) + } + pub fn verify_mmr_proof_and_extract_state_root( mmr_leaf_proof: ConsensusChainMmrLeafProof, T::Hash, T::MmrHash>, expected_block_number: BlockNumberFor, diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index 8dcc6656af..d8a0fcf242 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -1315,7 +1315,7 @@ pub fn operator_block_fees_final_key() -> Vec { sp_api::decl_runtime_apis! { /// API necessary for domains pallet. - #[api_version(3)] + #[api_version(4)] pub trait DomainsApi { /// Submits the transaction bundle via an unsigned extrinsic. fn submit_bundle_unsigned(opaque_bundle: OpaqueBundle, Block::Hash, DomainHeader, Balance>); @@ -1398,6 +1398,9 @@ sp_api::decl_runtime_apis! { /// Return the balance of the storage fund account fn storage_fund_account_balance(operator_id: OperatorId) -> Balance; + + /// Return if the domain runtime code is upgraded since `at` + fn is_domain_runtime_updraded_since(domain_id: DomainId, at: NumberFor) -> Option; } pub trait BundleProducerElectionApi { diff --git a/crates/subspace-fake-runtime-api/src/lib.rs b/crates/subspace-fake-runtime-api/src/lib.rs index e6c9908589..292ab34977 100644 --- a/crates/subspace-fake-runtime-api/src/lib.rs +++ b/crates/subspace-fake-runtime-api/src/lib.rs @@ -300,6 +300,10 @@ sp_api::impl_runtime_apis! { fn storage_fund_account_balance(_operator_id: OperatorId) -> Balance { unreachable!() } + + fn is_domain_runtime_updraded_since(_domain_id: DomainId, _at: NumberFor) -> Option { + unreachable!() + } } impl sp_domains::BundleProducerElectionApi for Runtime { diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 5b45fab084..e36c25bfd7 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -1259,6 +1259,10 @@ impl_runtime_apis! { fn storage_fund_account_balance(operator_id: OperatorId) -> Balance { Domains::storage_fund_account_balance(operator_id) } + + fn is_domain_runtime_updraded_since(domain_id: DomainId, at: NumberFor) -> Option { + Domains::is_domain_runtime_updraded_since(domain_id, at) + } } impl sp_domains::BundleProducerElectionApi for Runtime { diff --git a/domains/client/domain-operator/Cargo.toml b/domains/client/domain-operator/Cargo.toml index 64d09560af..af7042e543 100644 --- a/domains/client/domain-operator/Cargo.toml +++ b/domains/client/domain-operator/Cargo.toml @@ -14,6 +14,7 @@ futures-timer = "3.0.3" parking_lot = "0.12.2" sc-client-api = { git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } sc-consensus = { git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } +sc-domains = { version = "0.1.0", path = "../../../crates/sc-domains" } sc-transaction-pool = { git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } sc-transaction-pool-api = { git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } sc-utils = { git = "https://github.com/subspace/polkadot-sdk", rev = "808269708cf5375526755797e8f9a9986016727d" } diff --git a/domains/client/domain-operator/src/bundle_processor.rs b/domains/client/domain-operator/src/bundle_processor.rs index d65185aea0..3f3b7c18bf 100644 --- a/domains/client/domain-operator/src/bundle_processor.rs +++ b/domains/client/domain-operator/src/bundle_processor.rs @@ -15,6 +15,7 @@ use sp_domains::core_api::DomainCoreApi; use sp_domains::{DomainId, DomainsApi, ReceiptValidity}; use sp_domains_fraud_proof::FraudProofApi; use sp_messenger::MessengerApi; +use sp_mmr_primitives::MmrApi; use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; use sp_runtime::{Digest, DigestItem}; use sp_weights::constants::WEIGHT_REF_TIME_PER_MILLIS; @@ -148,6 +149,7 @@ where CClient::Api: DomainsApi + MessengerApi + FraudProofApi + + MmrApi> + 'static, Backend: sc_client_api::Backend + 'static, E: CodeExecutor, diff --git a/domains/client/domain-operator/src/domain_block_processor.rs b/domains/client/domain-operator/src/domain_block_processor.rs index 798f5374da..5c0f351116 100644 --- a/domains/client/domain-operator/src/domain_block_processor.rs +++ b/domains/client/domain-operator/src/domain_block_processor.rs @@ -12,7 +12,7 @@ use sc_consensus::{ StorageChanges, }; use sc_transaction_pool_api::OffchainTransactionPoolFactory; -use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_api::{ApiError, ApiExt, ProvideRuntimeApi}; use sp_blockchain::{HashAndNumber, HeaderBackend, HeaderMetadata}; use sp_consensus::{BlockOrigin, SyncOracle}; use sp_core::traits::CodeExecutor; @@ -20,13 +20,12 @@ use sp_core::H256; use sp_domains::core_api::DomainCoreApi; use sp_domains::merkle_tree::MerkleTree; use sp_domains::{BundleValidity, DomainId, DomainsApi, ExecutionReceipt, HeaderHashingFor}; -use sp_domains_fraud_proof::fraud_proof::{FraudProof, ValidBundleProof}; +use sp_domains_fraud_proof::fraud_proof::FraudProof; use sp_domains_fraud_proof::FraudProofApi; use sp_messenger::MessengerApi; use sp_mmr_primitives::MmrApi; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor, One, Zero}; use sp_runtime::{Digest, Saturating}; -use sp_subspace_mmr::ConsensusChainMmrLeafProof; use std::cmp::Ordering; use std::collections::VecDeque; use std::str::FromStr; @@ -686,6 +685,7 @@ where pub(crate) struct ReceiptsChecker where + Block: BlockT, CBlock: BlockT, { pub(crate) domain_id: DomainId, @@ -744,7 +744,9 @@ where + ProofProvider + ProvideRuntimeApi + 'static, - CClient::Api: DomainsApi + FraudProofApi, + CClient::Api: DomainsApi + + FraudProofApi + + MmrApi>, Backend: sc_client_api::Backend + 'static, E: CodeExecutor, { @@ -760,16 +762,23 @@ where } if let Some(mismatched_receipts) = self.find_mismatch_receipt(consensus_block_hash)? { - let fraud_proof = self.generate_fraud_proof(mismatched_receipts)?; - - tracing::info!("Submit fraud proof: {fraud_proof:?}"); let consensus_best_hash = self.consensus_client.info().best_hash; - let mut runtime_api = self.consensus_client.runtime_api(); - runtime_api.register_extension( - self.consensus_offchain_tx_pool_factory - .offchain_transaction_pool(consensus_best_hash), - ); - runtime_api.submit_fraud_proof_unsigned(consensus_best_hash, fraud_proof)?; + let mut consensus_runtime_api = self.consensus_client.runtime_api(); + let domains_api_version = consensus_runtime_api + .api_version::>(consensus_best_hash) + .map_err(sp_blockchain::Error::RuntimeApiError)? + .ok_or_else(|| { + sp_blockchain::Error::RuntimeApiError(ApiError::Application( + format!("DomainsApi not found at: {:?}", consensus_best_hash).into(), + )) + })?; + + // New `DomainsApi` introduced in version 4 is required for generating fraud proof + // TODO: remove before next network + if domains_api_version >= 4 { + let fraud_proof_v2 = self.generate_fraud_proof(mismatched_receipts)?; + // TODO: submit fraud proof + } } Ok(()) @@ -850,10 +859,12 @@ where } } + #[allow(clippy::type_complexity)] pub fn generate_fraud_proof( &self, mismatched_receipts: MismatchedReceipts, - ) -> sp_blockchain::Result> { + ) -> sp_blockchain::Result, CBlock::Hash, Block::Header, H256>> + { let MismatchedReceipts { local_receipt, bad_receipt, @@ -869,14 +880,22 @@ where }) = find_inboxed_bundles_mismatch::(&local_receipt, &bad_receipt)? { return match mismatch_type { - BundleMismatchType::Valid => Ok(FraudProof::ValidBundle(ValidBundleProof { - domain_id: self.domain_id, - bad_receipt_hash, - bundle_index, - })), + BundleMismatchType::Valid => self + .fraud_proof_generator + .generate_valid_bundle_proof( + self.domain_id, + &local_receipt, + bundle_index as usize, + bad_receipt_hash, + ) + .map_err(|err| { + sp_blockchain::Error::Application(Box::from(format!( + "Failed to generate valid bundles fraud proof: {err}" + ))) + }), _ => self .fraud_proof_generator - .generate_invalid_bundle_field_proof( + .generate_invalid_bundle_proof( self.domain_id, &local_receipt, mismatch_type, @@ -885,7 +904,7 @@ where ) .map_err(|err| { sp_blockchain::Error::Application(Box::from(format!( - "Failed to generate invalid bundles field fraud proof: {err}" + "Failed to generate invalid bundles fraud proof: {err}" ))) }), }; @@ -979,58 +998,6 @@ where } } -/// Generate MMR proof for the block `to_prove` in the current best fork. The returned proof -/// can be later used to verify stateless (without query offchain MMR leaf) and extract the state -/// root at `to_prove`. -// TODO: remove `dead_code` after it is used in fraud proof generation -#[allow(dead_code)] -pub(crate) fn generate_mmr_proof( - consensus_client: &Arc, - to_prove: NumberFor, -) -> sp_blockchain::Result, CBlock::Hash, H256>> -where - CBlock: BlockT, - CClient: HeaderBackend + ProvideRuntimeApi + 'static, - CClient::Api: MmrApi>, -{ - let api = consensus_client.runtime_api(); - let prove_at_hash = consensus_client.info().best_hash; - let prove_at_number = consensus_client.info().best_number; - - if to_prove >= prove_at_number { - return Err(sp_blockchain::Error::Application(Box::from(format!( - "Can't generate MMR proof for block {to_prove:?} >= best block {prove_at_number:?}" - )))); - } - - let (mut leaves, proof) = api - // NOTE: the mmr leaf data is added in the next block so to generate the MMR proof of - // block `to_prove` we need to use `to_prove + 1` here. - .generate_proof( - prove_at_hash, - vec![to_prove + One::one()], - Some(prove_at_number), - )? - .map_err(|err| { - sp_blockchain::Error::Application(Box::from(format!( - "Failed to generate MMR proof: {err}" - ))) - })?; - debug_assert!(leaves.len() == 1, "should always be of length 1"); - let leaf = leaves - .pop() - .ok_or(sp_blockchain::Error::Application(Box::from( - "Unexpected missing mmr leaf".to_string(), - )))?; - - Ok(ConsensusChainMmrLeafProof { - consensus_block_number: prove_at_number, - consensus_block_hash: prove_at_hash, - opaque_mmr_leaf: leaf, - proof, - }) -} - #[cfg(test)] mod tests { use super::*; diff --git a/domains/client/domain-operator/src/domain_worker.rs b/domains/client/domain-operator/src/domain_worker.rs index 7233d0fc64..72b162714a 100644 --- a/domains/client/domain-operator/src/domain_worker.rs +++ b/domains/client/domain-operator/src/domain_worker.rs @@ -33,6 +33,7 @@ use sp_domains::core_api::DomainCoreApi; use sp_domains::{BundleProducerElectionApi, DomainsApi, OpaqueBundle, OperatorId}; use sp_domains_fraud_proof::FraudProofApi; use sp_messenger::MessengerApi; +use sp_mmr_primitives::MmrApi; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; use sp_transaction_pool::runtime_api::TaggedTransactionQueue; use std::pin::pin; @@ -92,7 +93,8 @@ pub(super) async fn start_worker< CClient::Api: DomainsApi + MessengerApi + BundleProducerElectionApi - + FraudProofApi, + + FraudProofApi + + MmrApi>, TransactionPool: sc_transaction_pool_api::TransactionPool::Hash> + 'static, Backend: sc_client_api::Backend + 'static, diff --git a/domains/client/domain-operator/src/fraud_proof.rs b/domains/client/domain-operator/src/fraud_proof.rs index 89a4e6065a..0cd2e3336b 100644 --- a/domains/client/domain-operator/src/fraud_proof.rs +++ b/domains/client/domain-operator/src/fraud_proof.rs @@ -5,23 +5,30 @@ use domain_block_builder::{BlockBuilder, RecordProof}; use domain_runtime_primitives::opaque::AccountId; use domain_runtime_primitives::CheckExtrinsicsValidityError; use sc_client_api::{AuxStore, BlockBackend, ProofProvider}; -use sp_api::{ApiExt, ProvideRuntimeApi}; +use sc_domains::FPStorageKeyProvider; +use sp_api::{ApiError, ApiExt, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; use sp_core::traits::CodeExecutor; use sp_core::H256; use sp_domain_digests::AsPredigest; use sp_domains::core_api::DomainCoreApi; use sp_domains::proof_provider_and_verifier::StorageProofProvider; -use sp_domains::{DomainId, DomainsApi, ExtrinsicDigest, HeaderHashingFor, InvalidBundleType}; +use sp_domains::{ + DomainId, DomainsApi, DomainsDigestItem, ExtrinsicDigest, HeaderHashingFor, InvalidBundleType, + RuntimeId, +}; use sp_domains_fraud_proof::execution_prover::ExecutionProver; use sp_domains_fraud_proof::fraud_proof::{ - ApplyExtrinsicMismatch, ExecutionPhase, FinalizeBlockMismatch, FraudProof, - InvalidBlockFeesProof, InvalidBundlesFraudProof, InvalidDomainBlockHashProof, - InvalidExtrinsicsRootProof, InvalidStateTransitionProof, InvalidTransfersProof, - ValidBundleDigest, + ApplyExtrinsicMismatch, DomainRuntimeCodeAt, ExecutionPhase, FinalizeBlockMismatch, FraudProof, + FraudProofVariant, InvalidBlockFeesProof, InvalidBundlesProof, InvalidBundlesProofData, + InvalidDomainBlockHashProof, InvalidExtrinsicsRootProof, InvalidStateTransitionProof, + InvalidTransfersProof, ValidBundleDigest, ValidBundleProof, }; +use sp_domains_fraud_proof::storage_proof::{self, *}; +use sp_domains_fraud_proof::FraudProofApi; +use sp_mmr_primitives::MmrApi; use sp_runtime::generic::BlockId; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor, One}; use sp_runtime::{Digest, DigestItem}; use sp_trie::LayoutV1; use std::marker::PhantomData; @@ -68,17 +75,20 @@ pub enum FraudProofError { is_true_invalid: bool, extrinsics_validity_response: Result<(), CheckExtrinsicsValidityError>, }, + #[error("Fail to generate the storage proof")] + StorageProof(#[from] storage_proof::GenerationError), } -pub struct FraudProofGenerator { +pub struct FraudProofGenerator { client: Arc, consensus_client: Arc, backend: Arc, code_executor: Arc, + storage_key_provider: FPStorageKeyProvider, _phantom: PhantomData<(Block, CBlock)>, } -impl Clone +impl Clone for FraudProofGenerator { fn clone(&self) -> Self { @@ -87,11 +97,15 @@ impl Clone consensus_client: self.consensus_client.clone(), backend: self.backend.clone(), code_executor: self.code_executor.clone(), + storage_key_provider: self.storage_key_provider.clone(), _phantom: self._phantom, } } } +type FraudProofFor = + FraudProof, ::Hash, DomainHeader, H256>; + impl FraudProofGenerator where @@ -111,7 +125,9 @@ where + ProvideRuntimeApi + ProofProvider + 'static, - CClient::Api: DomainsApi, + CClient::Api: DomainsApi + + MmrApi> + + FraudProofApi, Backend: sc_client_api::Backend + Send + Sync + 'static, E: CodeExecutor, { @@ -123,216 +139,314 @@ where ) -> Self { Self { client, - consensus_client, + consensus_client: consensus_client.clone(), backend, code_executor, + storage_key_provider: FPStorageKeyProvider::new(consensus_client), _phantom: Default::default(), } } - pub(crate) fn generate_invalid_block_fees_proof( + #[allow(clippy::type_complexity)] + fn maybe_generate_domain_runtime_code_proof_for_receipt( &self, domain_id: DomainId, local_receipt: &ExecutionReceiptFor, - bad_receipt_hash: Block::Hash, - ) -> Result, FraudProofError> { - let block_hash = local_receipt.domain_block_hash; - let key = sp_domains::operator_block_fees_final_key(); - let proof = self - .client - .read_proof(block_hash, &mut [key.as_slice()].into_iter())?; - Ok(FraudProof::InvalidBlockFees(InvalidBlockFeesProof { - domain_id, - bad_receipt_hash, - storage_proof: proof, - })) + ) -> Result< + Option, ::Hash, H256>>, + FraudProofError, + > { + // NOTE: domain runtime code is take affect in the next block, so we need to get + // the doamin runtime code of the parent block, which is what used to derived the + // ER. + let (parent_consensus_number, parent_consensus_hash) = { + let consensus_header = self.consensus_header(local_receipt.consensus_block_hash)?; + ( + local_receipt.consensus_block_number - One::one(), + *consensus_header.parent_hash(), + ) + }; + + let best_hash = self.consensus_client.info().best_hash; + let runtime_api = self.consensus_client.runtime_api(); + let runtime_id = runtime_api + .runtime_id(best_hash, domain_id)? + .ok_or_else(|| { + sp_blockchain::Error::Application( + "Failed to get domain runtime id".to_string().into(), + ) + })?; + let is_domain_runtime_updraded_since = runtime_api + .is_domain_runtime_updraded_since(best_hash, domain_id, parent_consensus_number)? + .ok_or_else(|| { + sp_blockchain::Error::Application( + "Failed to get domain runtime object".to_string().into(), + ) + })?; + + if is_domain_runtime_updraded_since { + let mmr_proof = + sc_domains::generate_mmr_proof(&self.consensus_client, parent_consensus_number)?; + let domain_runtime_code_proof = DomainRuntimeCodeProof::generate( + self.consensus_client.as_ref(), + parent_consensus_hash, + runtime_id, + &self.storage_key_provider, + )?; + Ok(Some(DomainRuntimeCodeAt { + mmr_proof, + domain_runtime_code_proof, + })) + } else { + Ok(None) + } } - pub(crate) fn generate_invalid_transfers_proof( + pub(crate) fn generate_invalid_state_transition_proof( &self, domain_id: DomainId, + execution_phase: ExecutionPhase, local_receipt: &ExecutionReceiptFor, + bad_receipt_trace_length: usize, bad_receipt_hash: Block::Hash, - ) -> Result, FraudProofError> { + ) -> Result, FraudProofError> { let block_hash = local_receipt.domain_block_hash; - let runtime_api = self.client.runtime_api(); - let key = runtime_api.transfers_storage_key(block_hash)?; - let proof = self - .client - .read_proof(block_hash, &mut [key.as_slice()].into_iter())?; - Ok(FraudProof::InvalidTransfers(InvalidTransfersProof { + let block_number = local_receipt.domain_block_number; + let header = self.header(block_hash)?; + let parent_header = self.header(*header.parent_hash())?; + + let prover = ExecutionProver::new(self.backend.clone(), self.code_executor.clone()); + + let inherent_digests = Digest { + logs: vec![DigestItem::consensus_block_info( + local_receipt.consensus_block_hash, + )], + }; + + let extrinsics = self.block_body(block_hash)?; + let max_extrinsic_index = extrinsics.len() - 1; + let encoded_extrinsics: Vec<_> = extrinsics.iter().map(Encode::encode).collect(); + + let block_builder = BlockBuilder::new( + &*self.client, + parent_header.hash(), + *parent_header.number(), + RecordProof::No, + inherent_digests.clone(), + &*self.backend, + extrinsics.into(), + // NOTE: the inherent extrinsic is already contained in the above `extrinsics`, which + // is getting from the block body, thus it is okay to pass `maybe_inherent_data` as + // `None` and `is_gemini_3h` as `false`, the latter is only used when `maybe_inherent_data` + // is `Some`. + None, + false, + )?; + + let (storage_changes, call_data) = match &execution_phase { + ExecutionPhase::InitializeBlock => ( + None, + Block::Header::new( + block_number, + Default::default(), + Default::default(), + parent_header.hash(), + inherent_digests, + ) + .encode(), + ), + ExecutionPhase::ApplyExtrinsic { mismatch, .. } => { + let extrinsic_index = match mismatch { + ApplyExtrinsicMismatch::StateRoot(trace_mismatch_index) => { + (trace_mismatch_index - 1) as usize + } + ApplyExtrinsicMismatch::Shorter => (bad_receipt_trace_length - 1) - 1, + }; + + let target_extrinsic = encoded_extrinsics.get(extrinsic_index).ok_or( + FraudProofError::OutOfBoundsExtrinsicIndex { + index: extrinsic_index, + max: max_extrinsic_index, + }, + )?; + + ( + Some(block_builder.prepare_storage_changes_before(extrinsic_index)?), + target_extrinsic.clone(), + ) + } + ExecutionPhase::FinalizeBlock { .. } => ( + Some(block_builder.prepare_storage_changes_before_finalize_block()?), + Vec::new(), + ), + }; + + let delta_changes = storage_changes.map(|storage_changes| { + ( + storage_changes.transaction, + storage_changes.transaction_storage_root, + ) + }); + + let execution_proof = prover.prove_execution( + parent_header.hash(), + &execution_phase, + call_data.as_slice(), + delta_changes, + )?; + + let maybe_domain_runtime_code_proof = + self.maybe_generate_domain_runtime_code_proof_for_receipt(domain_id, local_receipt)?; + + let invalid_state_transition_proof = FraudProof { domain_id, bad_receipt_hash, - storage_proof: proof, - })) - } + maybe_mmr_proof: None, + maybe_domain_runtime_code_proof, + proof: FraudProofVariant::InvalidStateTransition(InvalidStateTransitionProof { + execution_proof, + execution_phase, + }), + }; - pub(crate) fn generate_invalid_domain_block_hash_proof( - &self, - domain_id: DomainId, - local_receipt: &ExecutionReceiptFor, - bad_receipt_hash: Block::Hash, - ) -> Result, FraudProofError> { - let block_hash = local_receipt.domain_block_hash; - let digest_key = sp_domains::system_digest_final_key(); - let digest_storage_proof = self - .client - .read_proof(block_hash, &mut [digest_key.as_slice()].into_iter())?; - Ok(FraudProof::InvalidDomainBlockHash( - InvalidDomainBlockHashProof { - domain_id, - bad_receipt_hash, - digest_storage_proof, - }, - )) + Ok(invalid_state_transition_proof) } - pub(crate) fn generate_invalid_bundle_field_proof( + pub(crate) fn generate_valid_bundle_proof( &self, domain_id: DomainId, local_receipt: &ExecutionReceiptFor, - mismatch_type: BundleMismatchType, - bundle_index: u32, + bundle_index: usize, bad_receipt_hash: Block::Hash, - ) -> Result, FraudProofError> { + ) -> Result, FraudProofError> { let consensus_block_hash = local_receipt.consensus_block_hash; + let consensus_block_number = local_receipt.consensus_block_number; let consensus_extrinsics = self .consensus_client .block_body(consensus_block_hash)? .ok_or(FraudProofError::MissingConsensusExtrinsics)?; - let bundles = self + let mut bundles = self .consensus_client .runtime_api() .extract_successful_bundles(consensus_block_hash, domain_id, consensus_extrinsics)?; - let bundle = bundles - .get(bundle_index as usize) - .ok_or(FraudProofError::MissingBundle { - bundle_index: bundle_index as usize, - })?; + if bundle_index >= bundles.len() { + return Err(FraudProofError::MissingBundle { bundle_index }); + } + let bundle = bundles.swap_remove(bundle_index); - let (invalid_type, is_true_invalid) = match mismatch_type { - BundleMismatchType::TrueInvalid(invalid_type) => (invalid_type, true), - BundleMismatchType::FalseInvalid(invalid_type) => (invalid_type, false), - BundleMismatchType::Valid => { - return Err(sp_blockchain::Error::Application( - "Unexpected bundle mismatch type, this should not happen" - .to_string() - .into(), - ) - .into()); - } + let bundle_with_proof = OpaqueBundleWithProof::generate( + &self.storage_key_provider, + self.consensus_client.as_ref(), + domain_id, + consensus_block_hash, + bundle, + bundle_index as u32, + )?; + + let mmr_proof = + sc_domains::generate_mmr_proof(&self.consensus_client, consensus_block_number)?; + + let maybe_domain_runtime_code_proof = + self.maybe_generate_domain_runtime_code_proof_for_receipt(domain_id, local_receipt)?; + + let valid_bundle_proof = FraudProof { + domain_id, + bad_receipt_hash, + maybe_mmr_proof: Some(mmr_proof), + maybe_domain_runtime_code_proof, + proof: FraudProofVariant::ValidBundle(ValidBundleProof { bundle_with_proof }), }; - let extrinsic_index = invalid_type.extrinsic_index(); - let encoded_extrinsics: Vec<_> = bundle.extrinsics.iter().map(Encode::encode).collect(); - let proof_data = if let InvalidBundleType::IllegalTx(expected_extrinsic_index) = - invalid_type - { - if expected_extrinsic_index as usize >= bundle.extrinsics.len() { - return Err(FraudProofError::OutOfBoundsExtrinsicIndex { - index: expected_extrinsic_index as usize, - max: bundle.extrinsics.len(), - }); - } + Ok(valid_bundle_proof) + } - let domain_block_parent_hash = *self - .client - .header(local_receipt.domain_block_hash)? - .ok_or_else(|| { - FraudProofError::Blockchain(sp_blockchain::Error::MissingHeader(format!( - "{:?}", - local_receipt.domain_block_hash - ))) - })? - .parent_hash(); - - let domain_block_parent_number = self - .client - .block_number_from_id(&BlockId::Hash(domain_block_parent_hash))? - .ok_or_else(|| { - FraudProofError::Blockchain(sp_blockchain::Error::Backend(format!( - "unable to get block number for domain block:{:?}", - domain_block_parent_hash - ))) - })?; + pub(crate) fn generate_invalid_domain_extrinsics_root_proof( + &self, + domain_id: DomainId, + local_receipt: &ExecutionReceiptFor, + bad_receipt_hash: Block::Hash, + ) -> Result, FraudProofError> { + let consensus_block_hash = local_receipt.consensus_block_hash; + let consensus_block_number = local_receipt.consensus_block_number; - let mut runtime_api_instance = self.client.runtime_api(); - runtime_api_instance.record_proof(); - let proof_recorder = runtime_api_instance - .proof_recorder() - .expect("we enabled proof recording just above; qed"); - - let mut block_extrinsics = vec![]; - for (extrinsic_index, extrinsic) in bundle - .extrinsics - .iter() - .enumerate() - .take((expected_extrinsic_index + 1) as usize) - { - let encoded_extrinsic = extrinsic.encode(); - block_extrinsics.push( - ::Extrinsic::decode(&mut encoded_extrinsic.as_slice()) - .map_err(|decoding_error| { - FraudProofError::UnableToDecodeOpaqueBundleExtrinsic { - extrinsic_index, - decoding_error, - } - })?, - ); - } + let valid_bundle_digests = + self.generate_valid_bundle_digest_for_receipt(domain_id, local_receipt)?; - let validation_response = runtime_api_instance.check_extrinsics_and_do_pre_dispatch( - domain_block_parent_hash, - block_extrinsics, - domain_block_parent_number, - domain_block_parent_hash, - )?; + let mmr_proof = + sc_domains::generate_mmr_proof(&self.consensus_client, consensus_block_number)?; - // If the proof is true invalid then validation response should not be Ok. - // If the proof is false invalid then validation response should not be Err. - // OR - // If it is true invalid and expected extrinsic index does not match - if (is_true_invalid == validation_response.is_ok()) - || (is_true_invalid - && validation_response - .as_ref() - .is_err_and(|e| e.extrinsic_index != expected_extrinsic_index)) - { - return Err(FraudProofError::InvalidIllegalTxFraudProofExtrinsicIndex { - index: expected_extrinsic_index as usize, - is_true_invalid, - extrinsics_validity_response: validation_response, - }); - } + let maybe_domain_runtime_code_proof = + self.maybe_generate_domain_runtime_code_proof_for_receipt(domain_id, local_receipt)?; - proof_recorder.drain_storage_proof() - } else { - StorageProofProvider::< - LayoutV1>, - >::generate_enumerated_proof_of_inclusion( - encoded_extrinsics.as_slice(), extrinsic_index, - ) - .ok_or(FraudProofError::FailToGenerateProofOfInclusion)? - }; + let block_randomness_proof = BlockRandomnessProof::generate( + self.consensus_client.as_ref(), + consensus_block_hash, + (), + &self.storage_key_provider, + )?; - Ok(FraudProof::InvalidBundles(InvalidBundlesFraudProof::new( - bad_receipt_hash, + let maybe_runtime_id = + self.is_domain_runtime_updraded_at(domain_id, consensus_block_hash)?; + let domain_inherent_extrinsic_data_proof = DomainInherentExtrinsicDataProof::generate( + &self.storage_key_provider, + self.consensus_client.as_ref(), domain_id, - bundle_index, - invalid_type, - proof_data, - is_true_invalid, - ))) + consensus_block_hash, + maybe_runtime_id, + )?; + + let invalid_domain_extrinsics_root_proof = FraudProof { + domain_id, + bad_receipt_hash, + maybe_mmr_proof: Some(mmr_proof), + maybe_domain_runtime_code_proof, + proof: FraudProofVariant::InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof { + valid_bundle_digests, + block_randomness_proof, + domain_inherent_extrinsic_data_proof, + }), + }; + + Ok(invalid_domain_extrinsics_root_proof) } - pub(crate) fn generate_invalid_domain_extrinsics_root_proof( + pub fn is_domain_runtime_updraded_at( + &self, + domain_id: DomainId, + at: CBlock::Hash, + ) -> Result, FraudProofError> { + let header = + self.consensus_client + .header(at)? + .ok_or(sp_blockchain::Error::MissingHeader(format!( + "No header found for {at:?}" + )))?; + + let runtime_id = self + .consensus_client + .runtime_api() + .runtime_id(at, domain_id)? + .ok_or(sp_blockchain::Error::Application(Box::from(format!( + "No RuntimeId found for {domain_id:?}" + ))))?; + + let is_runtime_upgraded = header + .digest() + .logs + .iter() + .filter_map(|log| log.as_domain_runtime_upgrade()) + .any(|upgraded_runtime_id| upgraded_runtime_id == runtime_id); + + Ok(is_runtime_upgraded.then_some(runtime_id)) + } + + pub(crate) fn generate_valid_bundle_digest_for_receipt( &self, domain_id: DomainId, local_receipt: &ExecutionReceiptFor, - bad_receipt_hash: Block::Hash, - ) -> Result, FraudProofError> { + ) -> Result, FraudProofError> { let consensus_block_hash = local_receipt.consensus_block_hash; let consensus_extrinsics = self .consensus_client @@ -388,118 +502,295 @@ where }); } - Ok(FraudProof::InvalidExtrinsicsRoot( - InvalidExtrinsicsRootProof { - domain_id, - bad_receipt_hash, - valid_bundle_digests, - }, - )) + Ok(valid_bundle_digests) } - pub(crate) fn generate_invalid_state_transition_proof( + pub(crate) fn generate_invalid_bundle_proof( &self, domain_id: DomainId, - execution_phase: ExecutionPhase, local_receipt: &ExecutionReceiptFor, - bad_receipt_trace_length: usize, + mismatch_type: BundleMismatchType, + bundle_index: u32, bad_receipt_hash: Block::Hash, - ) -> Result, FraudProofError> { - let block_hash = local_receipt.domain_block_hash; - let block_number = local_receipt.domain_block_number; - let header = self.header(block_hash)?; - let parent_header = self.header(*header.parent_hash())?; + ) -> Result, FraudProofError> { + let consensus_block_hash = local_receipt.consensus_block_hash; + let consensus_block_number = local_receipt.consensus_block_number; + + let bundle = { + let consensus_extrinsics = self + .consensus_client + .block_body(consensus_block_hash)? + .ok_or(FraudProofError::MissingConsensusExtrinsics)?; + + let mut bundles = self + .consensus_client + .runtime_api() + .extract_successful_bundles( + consensus_block_hash, + domain_id, + consensus_extrinsics, + )?; + if bundles.len() <= bundle_index as usize { + return Err(FraudProofError::MissingBundle { + bundle_index: bundle_index as usize, + }); + } - let prover = ExecutionProver::new(self.backend.clone(), self.code_executor.clone()); + bundles.swap_remove(bundle_index as usize) + }; - let inherent_digests = Digest { - logs: vec![DigestItem::consensus_block_info( - local_receipt.consensus_block_hash, - )], + let (invalid_type, is_true_invalid) = match mismatch_type { + BundleMismatchType::TrueInvalid(invalid_type) => (invalid_type, true), + BundleMismatchType::FalseInvalid(invalid_type) => (invalid_type, false), + BundleMismatchType::Valid => { + return Err(sp_blockchain::Error::Application( + "Unexpected bundle mismatch type, this should not happen" + .to_string() + .into(), + ) + .into()); + } }; - let extrinsics = self.block_body(block_hash)?; - let max_extrinsic_index = extrinsics.len() - 1; - let encoded_extrinsics: Vec<_> = extrinsics.iter().map(Encode::encode).collect(); + let extrinsic_index = invalid_type.extrinsic_index(); + let encoded_extrinsics: Vec<_> = bundle.extrinsics.iter().map(Encode::encode).collect(); + let proof_data = match invalid_type { + InvalidBundleType::IllegalTx(expected_extrinsic_index) => { + if expected_extrinsic_index as usize >= bundle.extrinsics.len() { + return Err(FraudProofError::OutOfBoundsExtrinsicIndex { + index: expected_extrinsic_index as usize, + max: bundle.extrinsics.len(), + }); + } - let block_builder = BlockBuilder::new( - &*self.client, - parent_header.hash(), - *parent_header.number(), - RecordProof::No, - inherent_digests.clone(), - &*self.backend, - extrinsics.into(), - // NOTE: the inherent extrinsic is already contained in the above `extrinsics`, which - // is getting from the block body, thus it is okay to pass `maybe_inherent_data` as - // `None` and `is_gemini_3h` as `false`, the latter is only used when `maybe_inherent_data` - // is `Some`. - None, - false, - )?; + let domain_block_parent_hash = *self + .client + .header(local_receipt.domain_block_hash)? + .ok_or_else(|| { + FraudProofError::Blockchain(sp_blockchain::Error::MissingHeader(format!( + "{:?}", + local_receipt.domain_block_hash + ))) + })? + .parent_hash(); + + let domain_block_parent_number = self + .client + .block_number_from_id(&BlockId::Hash(domain_block_parent_hash))? + .ok_or_else(|| { + FraudProofError::Blockchain(sp_blockchain::Error::Backend(format!( + "unable to get block number for domain block:{:?}", + domain_block_parent_hash + ))) + })?; - let (storage_changes, call_data) = match &execution_phase { - ExecutionPhase::InitializeBlock => ( - None, - Block::Header::new( - block_number, - Default::default(), - Default::default(), - parent_header.hash(), - inherent_digests, - ) - .encode(), - ), - ExecutionPhase::ApplyExtrinsic { mismatch, .. } => { - let extrinsic_index = match mismatch { - ApplyExtrinsicMismatch::StateRoot(trace_mismatch_index) => { - (trace_mismatch_index - 1) as usize - } - ApplyExtrinsicMismatch::Shorter => (bad_receipt_trace_length - 1) - 1, - }; + let mut runtime_api_instance = self.client.runtime_api(); + runtime_api_instance.record_proof(); + let proof_recorder = runtime_api_instance + .proof_recorder() + .expect("we enabled proof recording just above; qed"); + + let mut block_extrinsics = vec![]; + for (extrinsic_index, extrinsic) in bundle + .extrinsics + .iter() + .enumerate() + .take((expected_extrinsic_index + 1) as usize) + { + let encoded_extrinsic = extrinsic.encode(); + block_extrinsics.push( + ::Extrinsic::decode(&mut encoded_extrinsic.as_slice()) + .map_err(|decoding_error| { + FraudProofError::UnableToDecodeOpaqueBundleExtrinsic { + extrinsic_index, + decoding_error, + } + })?, + ); + } - let target_extrinsic = encoded_extrinsics.get(extrinsic_index).ok_or( - FraudProofError::OutOfBoundsExtrinsicIndex { - index: extrinsic_index, - max: max_extrinsic_index, - }, + let validation_response = runtime_api_instance + .check_extrinsics_and_do_pre_dispatch( + domain_block_parent_hash, + block_extrinsics, + domain_block_parent_number, + domain_block_parent_hash, + )?; + + // If the proof is true invalid then validation response should not be Ok. + // If the proof is false invalid then validation response should not be Err. + // OR + // If it is true invalid and expected extrinsic index does not match + if (is_true_invalid == validation_response.is_ok()) + || (is_true_invalid + && validation_response + .as_ref() + .is_err_and(|e| e.extrinsic_index != expected_extrinsic_index)) + { + return Err(FraudProofError::InvalidIllegalTxFraudProofExtrinsicIndex { + index: expected_extrinsic_index as usize, + is_true_invalid, + extrinsics_validity_response: validation_response, + }); + } + + let execution_proof = proof_recorder.drain_storage_proof(); + + let bundle_with_proof = OpaqueBundleWithProof::generate( + &self.storage_key_provider, + self.consensus_client.as_ref(), + domain_id, + consensus_block_hash, + bundle, + bundle_index, )?; - ( - Some(block_builder.prepare_storage_changes_before(extrinsic_index)?), - target_extrinsic.clone(), + InvalidBundlesProofData::BundleAndExecution { + bundle_with_proof, + execution_proof, + } + } + InvalidBundleType::OutOfRangeTx(_) => { + let bundle_with_proof = OpaqueBundleWithProof::generate( + &self.storage_key_provider, + self.consensus_client.as_ref(), + domain_id, + consensus_block_hash, + bundle, + bundle_index, + )?; + + InvalidBundlesProofData::Bundle(bundle_with_proof) + } + InvalidBundleType::UndecodableTx(_) | InvalidBundleType::InherentExtrinsic(_) => { + let extrinsic_proof = StorageProofProvider::< + LayoutV1>, + >::generate_enumerated_proof_of_inclusion( + encoded_extrinsics.as_slice(), + extrinsic_index, ) + .ok_or(FraudProofError::FailToGenerateProofOfInclusion)?; + + InvalidBundlesProofData::Extrinsic(extrinsic_proof) + } + InvalidBundleType::InvalidXDM(_) => { + return Err(sp_blockchain::Error::Application( + "Unexpected invalid bundle type, this should not happen" + .to_string() + .into(), + ) + .into()) } - ExecutionPhase::FinalizeBlock { .. } => ( - Some(block_builder.prepare_storage_changes_before_finalize_block()?), - Vec::new(), - ), }; - let delta_changes = storage_changes.map(|storage_changes| { - ( - storage_changes.transaction, - storage_changes.transaction_storage_root, - ) - }); + let mmr_proof = + sc_domains::generate_mmr_proof(&self.consensus_client, consensus_block_number)?; - let execution_proof = prover.prove_execution( - parent_header.hash(), - &execution_phase, - call_data.as_slice(), - delta_changes, - )?; + let maybe_domain_runtime_code_proof = + self.maybe_generate_domain_runtime_code_proof_for_receipt(domain_id, local_receipt)?; - let invalid_state_transition_proof = InvalidStateTransitionProof { + let invalid_bundle_proof = FraudProof { domain_id, bad_receipt_hash, - proof: execution_proof, - execution_phase, + maybe_mmr_proof: Some(mmr_proof), + maybe_domain_runtime_code_proof, + proof: FraudProofVariant::InvalidBundles(InvalidBundlesProof { + bundle_index, + invalid_bundle_type: invalid_type, + is_true_invalid_fraud_proof: is_true_invalid, + proof_data, + }), }; + Ok(invalid_bundle_proof) + } - Ok(FraudProof::InvalidStateTransition( - invalid_state_transition_proof, - )) + pub(crate) fn generate_invalid_block_fees_proof( + &self, + domain_id: DomainId, + local_receipt: &ExecutionReceiptFor, + bad_receipt_hash: Block::Hash, + ) -> Result, FraudProofError> { + let block_hash = local_receipt.domain_block_hash; + + let runtime_api = self.client.runtime_api(); + let api_version = runtime_api + .api_version::>(block_hash) + .map_err(sp_blockchain::Error::RuntimeApiError)? + .ok_or_else(|| { + sp_blockchain::Error::RuntimeApiError(ApiError::Application( + format!("DomainCoreApi not found at: {:?}", block_hash).into(), + )) + })?; + let key = if api_version >= 2 { + runtime_api.block_fees_storage_key(block_hash)? + } else { + sp_domains::operator_block_fees_final_key() + }; + let storage_proof = self + .client + .read_proof(block_hash, &mut [key.as_slice()].into_iter())?; + + let maybe_domain_runtime_code_proof = + self.maybe_generate_domain_runtime_code_proof_for_receipt(domain_id, local_receipt)?; + + let invalid_block_fees_proof = FraudProof { + domain_id, + bad_receipt_hash, + maybe_mmr_proof: None, + maybe_domain_runtime_code_proof, + proof: FraudProofVariant::InvalidBlockFees(InvalidBlockFeesProof { storage_proof }), + }; + Ok(invalid_block_fees_proof) + } + + pub(crate) fn generate_invalid_transfers_proof( + &self, + domain_id: DomainId, + local_receipt: &ExecutionReceiptFor, + bad_receipt_hash: Block::Hash, + ) -> Result, FraudProofError> { + let block_hash = local_receipt.domain_block_hash; + let runtime_api = self.client.runtime_api(); + let key = runtime_api.transfers_storage_key(block_hash)?; + let storage_proof = self + .client + .read_proof(block_hash, &mut [key.as_slice()].into_iter())?; + + let maybe_domain_runtime_code_proof = + self.maybe_generate_domain_runtime_code_proof_for_receipt(domain_id, local_receipt)?; + + let invalid_transfers_proof = FraudProof { + domain_id, + bad_receipt_hash, + maybe_mmr_proof: None, + maybe_domain_runtime_code_proof, + proof: FraudProofVariant::InvalidTransfers(InvalidTransfersProof { storage_proof }), + }; + Ok(invalid_transfers_proof) + } + + pub(crate) fn generate_invalid_domain_block_hash_proof( + &self, + domain_id: DomainId, + local_receipt: &ExecutionReceiptFor, + bad_receipt_hash: Block::Hash, + ) -> Result, FraudProofError> { + let block_hash = local_receipt.domain_block_hash; + let digest_key = sp_domains::system_digest_final_key(); + let digest_storage_proof = self + .client + .read_proof(block_hash, &mut [digest_key.as_slice()].into_iter())?; + + let invalid_domain_block_hash = FraudProof { + domain_id, + bad_receipt_hash, + maybe_mmr_proof: None, + maybe_domain_runtime_code_proof: None, + proof: FraudProofVariant::InvalidDomainBlockHash(InvalidDomainBlockHashProof { + digest_storage_proof, + }), + }; + Ok(invalid_domain_block_hash) } fn header(&self, hash: Block::Hash) -> Result { @@ -508,6 +799,12 @@ where .ok_or_else(|| sp_blockchain::Error::Backend(format!("Header not found for {hash:?}"))) } + fn consensus_header(&self, hash: CBlock::Hash) -> Result { + self.consensus_client.header(hash)?.ok_or_else(|| { + sp_blockchain::Error::Backend(format!("Consensus header not found for {hash:?}")) + }) + } + fn block_body(&self, at: Block::Hash) -> Result, sp_blockchain::Error> { self.client.block_body(at)?.ok_or_else(|| { sp_blockchain::Error::Backend(format!("Block body not found for {at:?}")) diff --git a/domains/client/domain-operator/src/operator.rs b/domains/client/domain-operator/src/operator.rs index df1c2d9163..5c38a34b5b 100644 --- a/domains/client/domain-operator/src/operator.rs +++ b/domains/client/domain-operator/src/operator.rs @@ -19,6 +19,7 @@ use sp_domains::{BundleProducerElectionApi, DomainsApi}; use sp_domains_fraud_proof::FraudProofApi; use sp_keystore::KeystorePtr; use sp_messenger::MessengerApi; +use sp_mmr_primitives::MmrApi; use sp_runtime::traits::{Block as BlockT, NumberFor}; use sp_transaction_pool::runtime_api::TaggedTransactionQueue; use std::sync::Arc; @@ -92,7 +93,8 @@ where CClient::Api: DomainsApi + MessengerApi + BundleProducerElectionApi - + FraudProofApi, + + FraudProofApi + + MmrApi>, Backend: sc_client_api::Backend + Send + Sync + 'static, TransactionPool: sc_transaction_pool_api::TransactionPool::Hash> + 'static, diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 0ef8a6a7c6..10de3dc60b 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -1427,6 +1427,10 @@ impl_runtime_apis! { fn storage_fund_account_balance(operator_id: OperatorId) -> Balance { Domains::storage_fund_account_balance(operator_id) } + + fn is_domain_runtime_updraded_since(domain_id: DomainId, at: NumberFor) -> Option { + Domains::is_domain_runtime_updraded_since(domain_id, at) + } } impl sp_domains::BundleProducerElectionApi for Runtime { From 6c73f42c51056989bfcf55324570abb0c0adde65 Mon Sep 17 00:00:00 2001 From: linning Date: Tue, 14 May 2024 03:41:10 +0800 Subject: [PATCH 4/5] Introduce stateless fraud proof to the consensus runtime Signed-off-by: linning --- Cargo.lock | 5 +- crates/pallet-domains/src/lib.rs | 558 +++++++++++------- crates/pallet-domains/src/tests.rs | 1 + .../sp-domains-fraud-proof/src/fraud_proof.rs | 145 ++--- crates/sp-domains-fraud-proof/src/lib.rs | 7 +- .../src/runtime_interface.rs | 10 +- .../src/verification.rs | 375 ++++++------ crates/subspace-fake-runtime-api/src/lib.rs | 4 +- crates/subspace-runtime/src/domains.rs | 3 +- crates/subspace-runtime/src/lib.rs | 5 +- .../src/domain_block_processor.rs | 22 +- test/subspace-test-runtime/src/lib.rs | 7 +- 12 files changed, 630 insertions(+), 512 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9479a607be..585424cf2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2745,6 +2745,7 @@ dependencies = [ "sc-cli", "sc-client-api", "sc-consensus", + "sc-domains", "sc-service", "sc-transaction-pool", "sc-transaction-pool-api", @@ -7574,9 +7575,8 @@ dependencies = [ "sp-externalities", "sp-io", "sp-runtime", - "sp-state-machine", "sp-std", - "sp-trie", + "sp-subspace-mmr", "sp-version", "subspace-core-primitives", "subspace-runtime-primitives", @@ -11592,6 +11592,7 @@ dependencies = [ "sp-runtime-interface", "sp-state-machine", "sp-std", + "sp-subspace-mmr", "sp-trie", "subspace-core-primitives", "subspace-runtime-primitives", diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 7d7537f6e1..cf7f1fd8a6 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -67,8 +67,10 @@ use sp_domains::{ DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT, EMPTY_EXTRINSIC_ROOT, }; use sp_domains_fraud_proof::fraud_proof::{ - FraudProof, InvalidBlockFeesProof, InvalidDomainBlockHashProof, + DomainRuntimeCodeAt, FraudProof, FraudProofVariant, InvalidBlockFeesProof, + InvalidDomainBlockHashProof, InvalidTransfersProof, }; +use sp_domains_fraud_proof::storage_proof::{self, BasicStorageProof, DomainRuntimeCodeProof}; use sp_domains_fraud_proof::verification::{ verify_invalid_block_fees_fraud_proof, verify_invalid_bundles_fraud_proof, verify_invalid_domain_block_hash_fraud_proof, @@ -118,6 +120,13 @@ pub type OpaqueBundleOf = OpaqueBundle< BalanceOf, >; +pub type FraudProofFor = FraudProof< + BlockNumberFor, + ::Hash, + ::DomainHeader, + ::MmrHash, +>; + /// Parameters used to verify proof of election. #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] pub(crate) struct ElectionVerificationParams { @@ -146,6 +155,8 @@ const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); /// 100 as the maximum number of bundle per block for benchmarking. const MAX_BUNLDE_PER_BLOCK: u32 = 100; +pub(crate) type StateRootOf = <::Hashing as Hash>::Output; + #[frame_support::pallet] mod pallet { #![allow(clippy::large_enum_variant)] @@ -181,8 +192,8 @@ mod pallet { use crate::DomainHashingFor; use crate::{ BalanceOf, BlockSlot, BlockTreeNodeFor, DomainBlockNumberFor, ElectionVerificationParams, - HoldIdentifier, NominatorId, OpaqueBundleOf, ReceiptHashFor, MAX_BUNLDE_PER_BLOCK, - STORAGE_VERSION, + FraudProofFor, HoldIdentifier, NominatorId, OpaqueBundleOf, ReceiptHashFor, StateRootOf, + MAX_BUNLDE_PER_BLOCK, STORAGE_VERSION, }; #[cfg(not(feature = "std"))] use alloc::string::String; @@ -205,7 +216,7 @@ mod pallet { DomainsTransfersTracker, EpochIndex, GenesisDomain, OnDomainInstantiated, OperatorAllowList, OperatorId, OperatorPublicKey, RuntimeId, RuntimeObject, RuntimeType, }; - use sp_domains_fraud_proof::fraud_proof::FraudProof; + use sp_domains_fraud_proof::storage_proof::{self, FraudProofStorageKeyProvider}; use sp_domains_fraud_proof::InvalidTransactionCode; use sp_runtime::traits::{ AtLeast32BitUnsigned, BlockNumberProvider, CheckEqual, CheckedAdd, Header as HeaderT, @@ -398,6 +409,9 @@ mod pallet { BlockNumberFor, StateRootOf, >; + + /// Fraud proof storage key provider + type FraudProofStorageKeyProvider: FraudProofStorageKeyProvider; } #[pallet::pallet] @@ -413,7 +427,6 @@ mod pallet { #[pallet::storage] pub(super) type SuccessfulFraudProofs = StorageMap<_, Identity, DomainId, Vec, ValueQuery>; - /// Stores the next runtime id. #[pallet::storage] pub(super) type NextRuntimeId = StorageValue<_, RuntimeId, ValueQuery>; @@ -735,6 +748,22 @@ mod pallet { BadMmrProof, /// Unexpected MMR proof UnexpectedMmrProof, + /// Missing MMR proof + MissingMmrProof, + /// Domain runtime not found + RuntimeNotFound, + /// The domain runtime code proof is not provided + DomainRuntimeCodeProofNotFound, + /// The domain runtime code proof is unexpected + UnexpectedDomainRuntimeCodeProof, + /// The storage proof is invalid + StorageProof(storage_proof::VerificationError), + } + + impl From for FraudProofError { + fn from(err: storage_proof::VerificationError) -> Self { + FraudProofError::StorageProof(err) + } } impl From for Error { @@ -1086,7 +1115,7 @@ mod pallet { Ok(Some(actual_weight.min(Self::max_submit_bundle_weight())).into()) } - #[pallet::call_index(1)] + #[pallet::call_index(15)] #[pallet::weight(( T::WeightInfo::submit_fraud_proof().saturating_add( T::WeightInfo::handle_bad_receipt(MAX_BUNLDE_PER_BLOCK) @@ -1096,65 +1125,63 @@ mod pallet { ))] pub fn submit_fraud_proof( origin: OriginFor, - fraud_proof: Box>, + fraud_proof: Box>, ) -> DispatchResultWithPostInfo { ensure_none(origin)?; log::trace!(target: "runtime::domains", "Processing fraud proof: {fraud_proof:?}"); - let domain_id = fraud_proof.domain_id(); #[cfg(not(feature = "runtime-benchmarks"))] let mut actual_weight = T::WeightInfo::submit_fraud_proof(); #[cfg(feature = "runtime-benchmarks")] let actual_weight = T::WeightInfo::submit_fraud_proof(); - if let Some(bad_receipt_hash) = fraud_proof.targeted_bad_receipt_hash() { - let head_receipt_number = HeadReceiptNumber::::get(domain_id); - let bad_receipt_number = BlockTreeNodes::::get(bad_receipt_hash) - .ok_or::>(FraudProofError::BadReceiptNotFound.into())? - .execution_receipt - .domain_block_number; - // The `head_receipt_number` must greater than or equal to any existing receipt, including - // the bad receipt, otherwise the fraud proof should be rejected due to `BadReceiptNotFound`, - // double check here to make it more robust. - ensure!( - head_receipt_number >= bad_receipt_number, - Error::::from(FraudProofError::BadReceiptNotFound), - ); + let domain_id = fraud_proof.domain_id(); + let bad_receipt_hash = fraud_proof.targeted_bad_receipt_hash(); + let head_receipt_number = HeadReceiptNumber::::get(domain_id); + let bad_receipt_number = BlockTreeNodes::::get(bad_receipt_hash) + .ok_or::>(FraudProofError::BadReceiptNotFound.into())? + .execution_receipt + .domain_block_number; + // The `head_receipt_number` must greater than or equal to any existing receipt, including + // the bad receipt, otherwise the fraud proof should be rejected due to `BadReceiptNotFound`, + // double check here to make it more robust. + ensure!( + head_receipt_number >= bad_receipt_number, + Error::::from(FraudProofError::BadReceiptNotFound), + ); - // Prune the bad ER and slash the submitter, the descendants of the bad ER (i.e. all ERs in - // `[bad_receipt_number + 1..head_receipt_number]` ) and the corresponding submitter will be - // pruned/slashed lazily as the domain progressed. - // - // NOTE: Skip the following staking related operations when benchmarking the - // `submit_fraud_proof` call, these operations will be benchmarked separately. - #[cfg(not(feature = "runtime-benchmarks"))] - { - let block_tree_node = prune_receipt::(domain_id, bad_receipt_number) - .map_err(Error::::from)? - .ok_or::>(FraudProofError::BadReceiptNotFound.into())?; - - actual_weight = - actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt( - (block_tree_node.operator_ids.len() as u32).min(MAX_BUNLDE_PER_BLOCK), - )); - - do_slash_operators::( - block_tree_node.operator_ids.into_iter(), - SlashedReason::BadExecutionReceipt(bad_receipt_hash), - ) - .map_err(Error::::from)?; - } + // Prune the bad ER and slash the submitter, the descendants of the bad ER (i.e. all ERs in + // `[bad_receipt_number + 1..head_receipt_number]` ) and the corresponding submitter will be + // pruned/slashed lazily as the domain progressed. + // + // NOTE: Skip the following staking related operations when benchmarking the + // `submit_fraud_proof` call, these operations will be benchmarked separately. + #[cfg(not(feature = "runtime-benchmarks"))] + { + let block_tree_node = prune_receipt::(domain_id, bad_receipt_number) + .map_err(Error::::from)? + .ok_or::>(FraudProofError::BadReceiptNotFound.into())?; + + actual_weight = actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt( + (block_tree_node.operator_ids.len() as u32).min(MAX_BUNLDE_PER_BLOCK), + )); + + do_slash_operators::( + block_tree_node.operator_ids.into_iter(), + SlashedReason::BadExecutionReceipt(bad_receipt_hash), + ) + .map_err(Error::::from)?; + } - // Update the head receipt number to `bad_receipt_number - 1` - let new_head_receipt_number = bad_receipt_number.saturating_sub(One::one()); - HeadReceiptNumber::::insert(domain_id, new_head_receipt_number); + // Update the head receipt number to `bad_receipt_number - 1` + let new_head_receipt_number = bad_receipt_number.saturating_sub(One::one()); + HeadReceiptNumber::::insert(domain_id, new_head_receipt_number); - Self::deposit_event(Event::FraudProofProcessed { - domain_id, - new_head_receipt_number: Some(new_head_receipt_number), - }); - } + Self::deposit_event(Event::FraudProofProcessed { + domain_id, + new_head_receipt_number: Some(new_head_receipt_number), + }); SuccessfulFraudProofs::::append(domain_id, fraud_proof.hash()); @@ -1876,174 +1903,247 @@ impl Pallet { } fn validate_fraud_proof( - fraud_proof: &FraudProof, + fraud_proof: &FraudProofFor, ) -> Result<(DomainId, TransactionPriority), FraudProofError> { - let tag_and_priority = if let Some(bad_receipt_hash) = - fraud_proof.targeted_bad_receipt_hash() - { - let bad_receipt = BlockTreeNodes::::get(bad_receipt_hash) - .ok_or(FraudProofError::BadReceiptNotFound)? - .execution_receipt; - let domain_block_number = bad_receipt.domain_block_number; + let domain_id = fraud_proof.domain_id(); + let bad_receipt_hash = fraud_proof.targeted_bad_receipt_hash(); + let bad_receipt = BlockTreeNodes::::get(bad_receipt_hash) + .ok_or(FraudProofError::BadReceiptNotFound)? + .execution_receipt; + let bad_receipt_domain_block_number = bad_receipt.domain_block_number; - ensure!( - !bad_receipt.domain_block_number.is_zero(), - FraudProofError::ChallengingGenesisReceipt - ); + ensure!( + !bad_receipt_domain_block_number.is_zero(), + FraudProofError::ChallengingGenesisReceipt + ); - ensure!( - !Self::is_bad_er_pending_to_prune( - fraud_proof.domain_id(), - bad_receipt.domain_block_number - ), - FraudProofError::BadReceiptAlreadyReported, - ); + ensure!( + !Self::is_bad_er_pending_to_prune(domain_id, bad_receipt_domain_block_number), + FraudProofError::BadReceiptAlreadyReported, + ); - match fraud_proof { - FraudProof::InvalidBlockFees(InvalidBlockFeesProof { storage_proof, .. }) => { - verify_invalid_block_fees_fraud_proof::< - T::Block, - DomainBlockNumberFor, - T::DomainHash, - BalanceOf, - DomainHashingFor, - >(bad_receipt, storage_proof) - .map_err(|err| { - log::error!( - target: "runtime::domains", - "Block fees proof verification failed: {err:?}" - ); - FraudProofError::InvalidBlockFeesFraudProof - })?; - } - FraudProof::InvalidTransfers(req) => { - verify_invalid_transfers_fraud_proof::< - T::Block, - DomainBlockNumberFor, - T::DomainHash, - BalanceOf, - DomainHashingFor, - >(bad_receipt, req) - .map_err(|err| { - log::error!( - target: "runtime::domains", - "Domain transfers proof verification failed: {err:?}" - ); - FraudProofError::InvalidTransfersFraudProof - })?; - } - FraudProof::InvalidDomainBlockHash(InvalidDomainBlockHashProof { - digest_storage_proof, - .. - }) => { - let parent_receipt = - BlockTreeNodes::::get(bad_receipt.parent_domain_block_receipt_hash) - .ok_or(FraudProofError::ParentReceiptNotFound)? - .execution_receipt; - verify_invalid_domain_block_hash_fraud_proof::< - T::Block, - BalanceOf, - T::DomainHeader, - >( - bad_receipt, - digest_storage_proof.clone(), - parent_receipt.domain_block_hash, - ) - .map_err(|err| { - log::error!( - target: "runtime::domains", - "Invalid Domain block hash proof verification failed: {err:?}" - ); - FraudProofError::InvalidDomainBlockHashFraudProof - })?; - } - FraudProof::InvalidExtrinsicsRoot(proof) => { - verify_invalid_domain_extrinsics_root_fraud_proof::< - T::Block, - BalanceOf, - T::Hashing, - T::DomainHeader, - >(bad_receipt, proof) - .map_err(|err| { - log::error!( - target: "runtime::domains", - "Invalid Domain extrinsic root proof verification failed: {err:?}" - ); - FraudProofError::InvalidExtrinsicRootFraudProof - })?; - } - FraudProof::InvalidStateTransition(proof) => { - let bad_receipt_parent = - BlockTreeNodes::::get(bad_receipt.parent_domain_block_receipt_hash) - .ok_or(FraudProofError::ParentReceiptNotFound)? - .execution_receipt; - - verify_invalid_state_transition_fraud_proof::< - T::Block, - T::DomainHeader, - BalanceOf, - >(bad_receipt, bad_receipt_parent, proof) - .map_err(|err| { - log::error!( - target: "runtime::domains", - "Invalid State transition proof verification failed: {err:?}" - ); - FraudProofError::InvalidStateTransitionFraudProof - })?; - } - FraudProof::InvalidBundles(invalid_bundles_fraud_proof) => { - let bad_receipt_parent = - BlockTreeNodes::::get(bad_receipt.parent_domain_block_receipt_hash) - .ok_or(FraudProofError::ParentReceiptNotFound)? - .execution_receipt; - - verify_invalid_bundles_fraud_proof::>( - bad_receipt, - bad_receipt_parent, - invalid_bundles_fraud_proof, - ) - .map_err(|err| { - log::error!( - target: "runtime::domains", - "Invalid Bundle proof verification failed: {err:?}" - ); - FraudProofError::InvalidBundleFraudProof - })?; - } - FraudProof::ValidBundle(proof) => verify_valid_bundle_fraud_proof::< + ensure!( + !fraud_proof.is_unexpected_domain_runtime_code_proof(), + FraudProofError::UnexpectedDomainRuntimeCodeProof, + ); + + ensure!( + !fraud_proof.is_unexpected_mmr_proof(), + FraudProofError::UnexpectedMmrProof, + ); + + let maybe_state_root = match &fraud_proof.maybe_mmr_proof { + Some(mmr_proof) => Some(Self::verify_mmr_proof_and_extract_state_root( + mmr_proof.clone(), + bad_receipt.consensus_block_number, + )?), + None => None, + }; + + match &fraud_proof.proof { + FraudProofVariant::InvalidBlockFees(InvalidBlockFeesProof { storage_proof }) => { + let domain_runtime_code = Self::get_domain_runtime_code_for_receipt( + domain_id, + &bad_receipt, + fraud_proof.maybe_domain_runtime_code_proof.clone(), + )?; + + verify_invalid_block_fees_fraud_proof::< + T::Block, + DomainBlockNumberFor, + T::DomainHash, + BalanceOf, + DomainHashingFor, + >(bad_receipt, storage_proof, domain_runtime_code) + .map_err(|err| { + log::error!( + target: "runtime::domains", + "Block fees proof verification failed: {err:?}" + ); + FraudProofError::InvalidBlockFeesFraudProof + })?; + } + FraudProofVariant::InvalidTransfers(InvalidTransfersProof { storage_proof }) => { + let domain_runtime_code = Self::get_domain_runtime_code_for_receipt( + domain_id, + &bad_receipt, + fraud_proof.maybe_domain_runtime_code_proof.clone(), + )?; + + verify_invalid_transfers_fraud_proof::< T::Block, DomainBlockNumberFor, T::DomainHash, BalanceOf, - >(bad_receipt, proof) + DomainHashingFor, + >(bad_receipt, storage_proof, domain_runtime_code) + .map_err(|err| { + log::error!( + target: "runtime::domains", + "Domain transfers proof verification failed: {err:?}" + ); + FraudProofError::InvalidTransfersFraudProof + })?; + } + FraudProofVariant::InvalidDomainBlockHash(InvalidDomainBlockHashProof { + digest_storage_proof, + }) => { + let parent_receipt = + BlockTreeNodes::::get(bad_receipt.parent_domain_block_receipt_hash) + .ok_or(FraudProofError::ParentReceiptNotFound)? + .execution_receipt; + verify_invalid_domain_block_hash_fraud_proof::< + T::Block, + BalanceOf, + T::DomainHeader, + >( + bad_receipt, + digest_storage_proof.clone(), + parent_receipt.domain_block_hash, + ) + .map_err(|err| { + log::error!( + target: "runtime::domains", + "Invalid Domain block hash proof verification failed: {err:?}" + ); + FraudProofError::InvalidDomainBlockHashFraudProof + })?; + } + FraudProofVariant::InvalidExtrinsicsRoot(proof) => { + let domain_runtime_code = Self::get_domain_runtime_code_for_receipt( + domain_id, + &bad_receipt, + fraud_proof.maybe_domain_runtime_code_proof.clone(), + )?; + let runtime_id = + Self::runtime_id(domain_id).ok_or(FraudProofError::RuntimeNotFound)?; + let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?; + + verify_invalid_domain_extrinsics_root_fraud_proof::< + T::Block, + BalanceOf, + T::DomainHeader, + T::Hashing, + T::FraudProofStorageKeyProvider, + >( + bad_receipt, + proof, + domain_id, + runtime_id, + state_root, + domain_runtime_code, + ) + .map_err(|err| { + log::error!( + target: "runtime::domains", + "Invalid Domain extrinsic root proof verification failed: {err:?}" + ); + FraudProofError::InvalidExtrinsicRootFraudProof + })?; + } + FraudProofVariant::InvalidStateTransition(proof) => { + let domain_runtime_code = Self::get_domain_runtime_code_for_receipt( + domain_id, + &bad_receipt, + fraud_proof.maybe_domain_runtime_code_proof.clone(), + )?; + let bad_receipt_parent = + BlockTreeNodes::::get(bad_receipt.parent_domain_block_receipt_hash) + .ok_or(FraudProofError::ParentReceiptNotFound)? + .execution_receipt; + + verify_invalid_state_transition_fraud_proof::< + T::Block, + T::DomainHeader, + BalanceOf, + >(bad_receipt, bad_receipt_parent, proof, domain_runtime_code) + .map_err(|err| { + log::error!( + target: "runtime::domains", + "Invalid State transition proof verification failed: {err:?}" + ); + FraudProofError::InvalidStateTransitionFraudProof + })?; + } + FraudProofVariant::InvalidBundles(proof) => { + let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?; + let domain_runtime_code = Self::get_domain_runtime_code_for_receipt( + domain_id, + &bad_receipt, + fraud_proof.maybe_domain_runtime_code_proof.clone(), + )?; + + let bad_receipt_parent = + BlockTreeNodes::::get(bad_receipt.parent_domain_block_receipt_hash) + .ok_or(FraudProofError::ParentReceiptNotFound)? + .execution_receipt; + + verify_invalid_bundles_fraud_proof::< + T::Block, + T::DomainHeader, + BalanceOf, + T::FraudProofStorageKeyProvider, + >( + bad_receipt, + bad_receipt_parent, + proof, + domain_id, + state_root, + domain_runtime_code, + ) + .map_err(|err| { + log::error!( + target: "runtime::domains", + "Invalid Bundle proof verification failed: {err:?}" + ); + FraudProofError::InvalidBundleFraudProof + })?; + } + FraudProofVariant::ValidBundle(proof) => { + let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?; + let domain_runtime_code = Self::get_domain_runtime_code_for_receipt( + domain_id, + &bad_receipt, + fraud_proof.maybe_domain_runtime_code_proof.clone(), + )?; + + verify_valid_bundle_fraud_proof::< + T::Block, + T::DomainHeader, + BalanceOf, + T::FraudProofStorageKeyProvider, + >( + bad_receipt, + proof, + domain_id, + state_root, + domain_runtime_code, + ) .map_err(|err| { log::error!( target: "runtime::domains", "Valid bundle proof verification failed: {err:?}" ); FraudProofError::BadValidBundleFraudProof - })?, - _ => return Err(FraudProofError::UnexpectedFraudProof), + })? } + #[cfg(any(feature = "std", feature = "runtime-benchmarks"))] + FraudProofVariant::Dummy => {} + } - // The priority of fraud proof is determined by how many blocks left before the bad ER - // is confirmed, the less the more emergency it is, thus give a higher priority. - let block_before_bad_er_confirm = domain_block_number.saturating_sub( - Self::latest_confirmed_domain_block_number(fraud_proof.domain_id()), - ); - let priority = - TransactionPriority::MAX - block_before_bad_er_confirm.saturated_into::(); + // The priority of fraud proof is determined by how many blocks left before the bad ER + // is confirmed, the less the more emergency it is, thus give a higher priority. + let block_before_bad_er_confirm = bad_receipt_domain_block_number.saturating_sub( + Self::latest_confirmed_domain_block_number(fraud_proof.domain_id()), + ); + let priority = + TransactionPriority::MAX - block_before_bad_er_confirm.saturated_into::(); - // Use the domain id as tag thus the consensus node only accept one fraud proof for a - // specific domain at a time - let tag = fraud_proof.domain_id(); + // Use the domain id as tag thus the consensus node only accept one fraud proof for a + // specific domain at a time + let tag = fraud_proof.domain_id(); - (tag, priority) - } else { - return Err(FraudProofError::UnexpectedFraudProof); - }; - - Ok(tag_and_priority) + Ok((tag, priority)) } /// Return operators specific election verification params for Proof of Election verification. @@ -2265,6 +2365,58 @@ impl Pallet { T::Currency::reducible_balance(&storage_fund_acc, Preservation::Preserve, Fortitude::Polite) } + // Get the domain runtime code that used to derive `receipt`, if the runtime code still present in + // the state then get it from the state otherwise from the `maybe_domain_runtime_code_at` prood. + pub fn get_domain_runtime_code_for_receipt( + domain_id: DomainId, + receipt: &ExecutionReceiptOf, + maybe_domain_runtime_code_at: Option< + DomainRuntimeCodeAt, T::Hash, T::MmrHash>, + >, + ) -> Result, FraudProofError> { + let runtime_id = Self::runtime_id(domain_id).ok_or(FraudProofError::RuntimeNotFound)?; + let current_runtime_obj = + RuntimeRegistry::::get(runtime_id).ok_or(FraudProofError::RuntimeNotFound)?; + + // NOTE: domain runtime code is taking affect in the next block, so to get the domain runtime code + // that used to derive `receipt` we need to use runtime code at `parent_receipt.consensus_block_number` + let at = { + let parent_receipt = BlockTreeNodes::::get(receipt.parent_domain_block_receipt_hash) + .ok_or(FraudProofError::ParentReceiptNotFound)? + .execution_receipt; + parent_receipt.consensus_block_number + }; + + let is_domain_runtime_updraded = current_runtime_obj.updated_at >= at; + + let mut runtime_obj = match (is_domain_runtime_updraded, maybe_domain_runtime_code_at) { + // The domain runtime is upgraded since `at`, the domain runtime code in `at` is not available + // so `domain_runtime_code_proof` must be provided + (true, None) => return Err(FraudProofError::DomainRuntimeCodeProofNotFound), + (true, Some(domain_runtime_code_at)) => { + let DomainRuntimeCodeAt { + mmr_proof, + domain_runtime_code_proof, + } = domain_runtime_code_at; + + let state_root = Self::verify_mmr_proof_and_extract_state_root(mmr_proof, at)?; + + >::verify::< + T::FraudProofStorageKeyProvider, + >(domain_runtime_code_proof, runtime_id, &state_root)? + } + // Domain runtime code in `at` is available in the state so `domain_runtime_code_proof` + // is unexpected + (false, Some(_)) => return Err(FraudProofError::UnexpectedDomainRuntimeCodeProof), + (false, None) => current_runtime_obj, + }; + let code = runtime_obj + .raw_genesis + .take_runtime_code() + .ok_or(storage_proof::VerificationError::RuntimeCodeNotFound)?; + Ok(code) + } + pub fn is_domain_runtime_updraded_since( domain_id: DomainId, at: BlockNumberFor, @@ -2325,7 +2477,7 @@ where } /// Submits an unsigned extrinsic [`Call::submit_fraud_proof`]. - pub fn submit_fraud_proof_unsigned(fraud_proof: FraudProof) { + pub fn submit_fraud_proof_unsigned(fraud_proof: FraudProofFor) { let call = Call::submit_fraud_proof { fraud_proof: Box::new(fraud_proof), }; diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 5a4c06928d..c66377beca 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -280,6 +280,7 @@ impl pallet_domains::Config for Test { type Balance = Balance; type MmrHash = H256; type MmrProofVerifier = (); + type FraudProofStorageKeyProvider = (); } pub struct ExtrinsicStorageFees; diff --git a/crates/sp-domains-fraud-proof/src/fraud_proof.rs b/crates/sp-domains-fraud-proof/src/fraud_proof.rs index 4ddbaa9706..296687f6ac 100644 --- a/crates/sp-domains-fraud-proof/src/fraud_proof.rs +++ b/crates/sp-domains-fraud-proof/src/fraud_proof.rs @@ -225,9 +225,6 @@ impl ExecutionPhase { #[derive(Debug)] #[cfg_attr(feature = "thiserror", derive(thiserror::Error))] pub enum VerificationError { - /// Hash of the consensus block being challenged not found. - #[cfg_attr(feature = "thiserror", error("consensus block hash not found"))] - ConsensusBlockHashNotFound, /// Failed to pass the execution proof check. #[cfg_attr( feature = "thiserror", @@ -259,59 +256,9 @@ pub enum VerificationError { error("Failed to decode the header from verifying `finalize_block`: {0}") )] HeaderDecode(codec::Error), - /// Transaction validity check passes. - #[cfg_attr(feature = "thiserror", error("Valid transaction"))] - ValidTransaction, - /// State not found in the storage proof. - #[cfg_attr( - feature = "thiserror", - error("State under storage key ({0:?}) not found in the storage proof") - )] - StateNotFound(Vec), - /// Decode error. - #[cfg(feature = "std")] - #[cfg_attr(feature = "thiserror", error("Decode error: {0}"))] - Decode(#[from] codec::Error), - /// Runtime api error. - #[cfg(feature = "std")] - #[cfg_attr(feature = "thiserror", error("Runtime api error: {0}"))] - RuntimeApi(#[from] sp_api::ApiError), - /// Runtime api error. - #[cfg(feature = "std")] - #[cfg_attr(feature = "thiserror", error("Client error: {0}"))] - Client(#[from] sp_blockchain::Error), /// Invalid storage proof. #[cfg_attr(feature = "thiserror", error("Invalid stroage proof"))] InvalidStorageProof, - /// Can not find signer from the domain extrinsic. - #[cfg_attr( - feature = "thiserror", - error("Can not find signer from the domain extrinsic") - )] - SignerNotFound, - /// Domain state root not found. - #[cfg_attr(feature = "thiserror", error("Domain state root not found"))] - DomainStateRootNotFound, - /// Fail to get runtime code. - // The `String` here actually repersenting the `sc_executor_common::error::WasmError` - // error, but it will be improper to use `WasmError` directly here since it will make - // `sp-domain` (a runtime crate) depend on `sc_executor_common` (a client crate). - #[cfg(feature = "std")] - #[cfg_attr(feature = "thiserror", error("Failed to get runtime code: {0}"))] - RuntimeCode(String), - #[cfg_attr(feature = "thiserror", error("Failed to get domain runtime code"))] - FailedToGetDomainRuntimeCode, - #[cfg_attr( - feature = "thiserror", - error("Failed to get domain transfers storage key") - )] - FailedToGetDomainTransfersStorageKey, - #[cfg(feature = "std")] - #[cfg_attr( - feature = "thiserror", - error("Oneshot error when verifying fraud proof in tx pool: {0}") - )] - Oneshot(String), #[cfg_attr( feature = "thiserror", error("The receipt's execution_trace have less than 2 traces") @@ -326,33 +273,6 @@ pub enum VerificationError { /// Invalid bundle digest #[cfg_attr(feature = "thiserror", error("Invalid Bundle Digest"))] InvalidBundleDigest, - /// Failed to get block randomness - #[cfg_attr(feature = "thiserror", error("Failed to get block randomness"))] - FailedToGetBlockRandomness, - /// Failed to derive domain timestamp extrinsic - #[cfg_attr( - feature = "thiserror", - error("Failed to derive domain timestamp extrinsic") - )] - FailedToDeriveDomainTimestampExtrinsic, - /// Failed to derive consensus chain byte fee extrinsic - #[cfg_attr( - feature = "thiserror", - error("Failed to derive consensus chain byte fee extrinsic") - )] - FailedToDeriveConsensusChainByteFeeExtrinsic, - /// Failed to derive domain set code extrinsic - #[cfg_attr( - feature = "thiserror", - error("Failed to derive domain set code extrinsic") - )] - FailedToDeriveDomainSetCodeExtrinsic, - /// Failed to derive domain chain allowlist extrinsic - #[cfg_attr( - feature = "thiserror", - error("Failed to derive domain chain allowlist extrinsic") - )] - FailedToDeriveDomainChainAllowlistExtrinsic, /// Bundle with requested index not found in execution receipt #[cfg_attr( feature = "thiserror", @@ -369,15 +289,6 @@ pub enum VerificationError { fraud_proof_invalid_type_of_proof: InvalidBundleType, targeted_entry_bundle: BundleValidity, }, - /// Tx range host function did not return response (returned None) - #[cfg_attr( - feature = "thiserror", - error("Tx range host function did not return a response (returned None)") - )] - FailedToGetResponseFromTxRangeHostFn, - /// Failed to get the bundle body - #[cfg_attr(feature = "thiserror", error("Failed to get the bundle body"))] - FailedToGetDomainBundleBody, /// Failed to derive bundle digest #[cfg_attr(feature = "thiserror", error("Failed to derive bundle digest"))] FailedToDeriveBundleDigest, @@ -387,37 +298,53 @@ pub enum VerificationError { error("The target valid bundle not found from the target bad receipt") )] TargetValidBundleNotFound, - /// Failed to check if a given extrinsic is inherent or not. + /// Failed to check extrinsics in single context #[cfg_attr( feature = "thiserror", - error("Failed to check if a given extrinsic is inherent or not") + error("Failed to check extrinsics in single context") )] - FailedToCheckInherentExtrinsic, - /// Failed to check if a given extrinsic is inherent or not. - #[cfg_attr(feature = "thiserror", error("Failed to validate given XDM"))] - FailedToValidateXDM, - /// Failed to check if a given extrinsic is decodable or not. + FailedToCheckExtrinsicsInSingleContext, #[cfg_attr( feature = "thiserror", - error("Failed to check if a given extrinsic is decodable or not") + error( + "Bad MMR proof, the proof is probably expired or is generated against a different fork" + ) )] - FailedToCheckExtrinsicDecodable, - /// Failed to check extrinsics in single context + BadMmrProof, + #[cfg_attr(feature = "thiserror", error("Unexpected MMR proof"))] + UnexpectedMmrProof, + #[cfg_attr(feature = "thiserror", error("Failed to verify storage proof"))] + StorageProof(storage_proof::VerificationError), + /// Failed to derive domain inherent extrinsic #[cfg_attr( feature = "thiserror", - error("Failed to check extrinsics in single context") + error("Failed to derive domain inherent extrinsic") )] - FailedToCheckExtrinsicsInSingleContext, + FailedToDeriveDomainInherentExtrinsic, + /// Failed to derive domain storage key + #[cfg_attr(feature = "thiserror", error("Failed to derive domain storage key"))] + FailedToGetDomainStorageKey, + /// Unexpected invalid bundle proof data + #[cfg_attr(feature = "thiserror", error("Unexpected invalid bundle proof data"))] + UnexpectedInvalidBundleProofData, + /// Extrinsic with requested index not found in bundle + #[cfg_attr( + feature = "thiserror", + error("Extrinsic with requested index not found in bundle") + )] + ExtrinsicNotFound, + /// Failed to get domain runtime call response + #[cfg_attr( + feature = "thiserror", + error("Failed to get domain runtime call response") + )] + FailedToGetDomainRuntimeCallResponse, } -#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] -pub struct InvalidBundlesFraudProof { - pub bad_receipt_hash: ReceiptHash, - pub domain_id: DomainId, - pub bundle_index: u32, - pub invalid_bundle_type: InvalidBundleType, - pub proof_data: StorageProof, - pub is_true_invalid_fraud_proof: bool, +impl From for VerificationError { + fn from(err: storage_proof::VerificationError) -> Self { + Self::StorageProof(err) + } } #[derive(Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] diff --git a/crates/sp-domains-fraud-proof/src/lib.rs b/crates/sp-domains-fraud-proof/src/lib.rs index 8cfbf12e62..15ebddd0a3 100644 --- a/crates/sp-domains-fraud-proof/src/lib.rs +++ b/crates/sp-domains-fraud-proof/src/lib.rs @@ -49,7 +49,7 @@ pub use runtime_interface::fraud_proof_runtime_interface::HostFunctions; use scale_info::TypeInfo; use sp_core::H256; use sp_domains::{DomainAllowlistUpdates, DomainId, OperatorId}; -use sp_runtime::traits::Header as HeaderT; +use sp_runtime::traits::{Header as HeaderT, NumberFor}; use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidity}; use sp_runtime::OpaqueExtrinsic; use sp_runtime_interface::pass_by; @@ -395,15 +395,16 @@ impl PassBy for StatelessDomainRuntimeCall { sp_api::decl_runtime_apis! { /// API necessary for fraud proof. + #[api_version(2)] pub trait FraudProofApi { /// Submit the fraud proof via an unsigned extrinsic. - fn submit_fraud_proof_unsigned(fraud_proof: FraudProof); + fn submit_fraud_proof_unsigned(fraud_proof: FraudProof, Block::Hash, DomainHeader, H256>); /// Extract the fraud proof handled successfully from the given extrinsics. fn extract_fraud_proofs( domain_id: DomainId, extrinsics: Vec, - ) -> Vec>; + ) -> Vec, Block::Hash, DomainHeader, H256>>; /// Reture the storage key used in fraud proof fn fraud_proof_storage_key(req: FraudProofStorageKeyRequest) -> Vec; diff --git a/crates/sp-domains-fraud-proof/src/runtime_interface.rs b/crates/sp-domains-fraud-proof/src/runtime_interface.rs index e925d69e40..2143ac2efe 100644 --- a/crates/sp-domains-fraud-proof/src/runtime_interface.rs +++ b/crates/sp-domains-fraud-proof/src/runtime_interface.rs @@ -46,7 +46,7 @@ pub trait FraudProofRuntimeInterface { } /// Derive the bundle digest for the given bundle body. - #[version(2, register_only)] + #[version(2)] fn derive_bundle_digest( &mut self, domain_runtime_code: Vec, @@ -103,7 +103,7 @@ pub trait FraudProofRuntimeInterface { ) } - #[version(1, register_only)] + #[version(1)] fn check_extrinsics_in_single_context( &mut self, domain_runtime_code: Vec, @@ -123,7 +123,7 @@ pub trait FraudProofRuntimeInterface { ) } - #[version(1, register_only)] + #[version(1)] fn construct_domain_inherent_extrinsic( &mut self, domain_runtime_code: Vec, @@ -137,7 +137,7 @@ pub trait FraudProofRuntimeInterface { ) } - #[version(1, register_only)] + #[version(1)] fn domain_storage_key( &mut self, domain_runtime_code: Vec, @@ -148,7 +148,7 @@ pub trait FraudProofRuntimeInterface { .domain_storage_key(domain_runtime_code, req) } - #[version(1, register_only)] + #[version(1)] fn domain_runtime_call( &mut self, domain_runtime_code: Vec, diff --git a/crates/sp-domains-fraud-proof/src/verification.rs b/crates/sp-domains-fraud-proof/src/verification.rs index 307a789825..b11e430e7d 100644 --- a/crates/sp-domains-fraud-proof/src/verification.rs +++ b/crates/sp-domains-fraud-proof/src/verification.rs @@ -2,14 +2,13 @@ extern crate alloc; use crate::fraud_proof::{ - InvalidBundlesFraudProof, InvalidExtrinsicsRootProof, InvalidStateTransitionProof, - InvalidTransfersProof, ValidBundleProof, VerificationError, + InvalidBundlesProof, InvalidBundlesProofData, InvalidExtrinsicsRootProof, + InvalidStateTransitionProof, ValidBundleProof, VerificationError, }; -use crate::fraud_proof_runtime_interface::get_fraud_proof_verification_info; +use crate::storage_proof::*; use crate::{ - fraud_proof_runtime_interface, DomainChainAllowlistUpdateExtrinsic, - FraudProofVerificationInfoRequest, FraudProofVerificationInfoResponse, SetCodeExtrinsic, - StorageKeyRequest, + fraud_proof_runtime_interface, DomainInherentExtrinsic, DomainStorageKeyRequest, + StatelessDomainRuntimeCall, }; #[cfg(not(feature = "std"))] use alloc::vec::Vec; @@ -22,8 +21,9 @@ use sp_domains::extrinsics::{deduplicate_and_shuffle_extrinsics, extrinsics_shuf use sp_domains::proof_provider_and_verifier::StorageProofVerifier; use sp_domains::valued_trie::valued_ordered_trie_root; use sp_domains::{ - BlockFees, BundleValidity, ExecutionReceipt, ExtrinsicDigest, HeaderHashFor, HeaderHashingFor, - HeaderNumberFor, InboxedBundle, InvalidBundleType, Transfers, + BlockFees, BundleValidity, DomainId, ExecutionReceipt, ExtrinsicDigest, HeaderHashFor, + HeaderHashingFor, HeaderNumberFor, InboxedBundle, InvalidBundleType, RuntimeId, Transfers, + INITIAL_DOMAIN_TX_RANGE, }; use sp_runtime::generic::Digest; use sp_runtime::traits::{ @@ -31,11 +31,17 @@ use sp_runtime::traits::{ }; use sp_runtime::{OpaqueExtrinsic, SaturatedConversion}; use sp_trie::{LayoutV1, StorageProof}; -use subspace_core_primitives::Randomness; +use subspace_core_primitives::{Randomness, U256}; use trie_db::node::Value; /// Verifies invalid domain extrinsic root fraud proof. -pub fn verify_invalid_domain_extrinsics_root_fraud_proof( +pub fn verify_invalid_domain_extrinsics_root_fraud_proof< + CBlock, + Balance, + DomainHeader, + Hashing, + SKP, +>( bad_receipt: ExecutionReceipt< NumberFor, CBlock::Hash, @@ -43,55 +49,45 @@ pub fn verify_invalid_domain_extrinsics_root_fraud_proof, Balance, >, - fraud_proof: &InvalidExtrinsicsRootProof>, + fraud_proof: &InvalidExtrinsicsRootProof, + domain_id: DomainId, + runtime_id: RuntimeId, + state_root: CBlock::Hash, + domain_runtime_code: Vec, ) -> Result<(), VerificationError> where CBlock: BlockT, - Hashing: Hasher, DomainHeader: HeaderT, DomainHeader::Hash: Into + PartialEq + Copy, + Hashing: Hasher, + SKP: FraudProofStorageKeyProvider, { let InvalidExtrinsicsRootProof { valid_bundle_digests, - domain_id, + block_randomness_proof, + domain_inherent_extrinsic_data_proof, .. } = fraud_proof; - let consensus_block_hash = bad_receipt.consensus_block_hash; - let block_randomness = get_fraud_proof_verification_info( - H256::from_slice(consensus_block_hash.as_ref()), - FraudProofVerificationInfoRequest::BlockRandomness, - ) - .and_then(|resp| resp.into_block_randomness()) - .ok_or(VerificationError::FailedToGetBlockRandomness)?; - - let domain_timestamp_extrinsic = get_fraud_proof_verification_info( - H256::from_slice(consensus_block_hash.as_ref()), - FraudProofVerificationInfoRequest::DomainTimestampExtrinsic(*domain_id), - ) - .and_then(|resp| resp.into_domain_timestamp_extrinsic()) - .ok_or(VerificationError::FailedToDeriveDomainTimestampExtrinsic)?; - - let maybe_domain_set_code_extrinsic = get_fraud_proof_verification_info( - H256::from_slice(consensus_block_hash.as_ref()), - FraudProofVerificationInfoRequest::DomainSetCodeExtrinsic(*domain_id), - ) - .map(|resp| resp.into_domain_set_code_extrinsic()) - .ok_or(VerificationError::FailedToDeriveDomainSetCodeExtrinsic)?; + let domain_inherent_extrinsic_data = domain_inherent_extrinsic_data_proof + .verify::(domain_id, runtime_id, &state_root)?; - let consensus_chain_byte_fee_extrinsic = get_fraud_proof_verification_info( - H256::from_slice(consensus_block_hash.as_ref()), - FraudProofVerificationInfoRequest::ConsensusChainByteFeeExtrinsic(*domain_id), - ) - .and_then(|resp| resp.into_consensus_chain_byte_fee_extrinsic()) - .ok_or(VerificationError::FailedToDeriveConsensusChainByteFeeExtrinsic)?; + let block_randomness = >::verify::( + block_randomness_proof.clone(), + (), + &state_root, + )?; - let domain_chain_allowlist_extrinsic = get_fraud_proof_verification_info( - H256::from_slice(consensus_block_hash.as_ref()), - FraudProofVerificationInfoRequest::DomainChainsAllowlistUpdateExtrinsic(*domain_id), + let DomainInherentExtrinsic { + domain_timestamp_extrinsic, + maybe_domain_chain_allowlist_extrinsic, + consensus_chain_byte_fee_extrinsic, + maybe_domain_set_code_extrinsic, + } = fraud_proof_runtime_interface::construct_domain_inherent_extrinsic( + domain_runtime_code, + domain_inherent_extrinsic_data, ) - .map(|resp| resp.into_domain_chain_allowlist_update_extrinsic()) - .ok_or(VerificationError::FailedToDeriveDomainChainAllowlistExtrinsic)?; + .ok_or(VerificationError::FailedToDeriveDomainInherentExtrinsic)?; let bad_receipt_valid_bundle_digests = bad_receipt.valid_bundle_digests(); if valid_bundle_digests.len() != bad_receipt_valid_bundle_digests.len() { @@ -137,18 +133,14 @@ where >(consensus_chain_byte_fee_extrinsic); ordered_extrinsics.push_front(transaction_byte_fee_extrinsic); - if let DomainChainAllowlistUpdateExtrinsic::EncodedExtrinsic(domain_chain_allowlist_extrinsic) = - domain_chain_allowlist_extrinsic - { + if let Some(domain_chain_allowlist_extrinsic) = maybe_domain_chain_allowlist_extrinsic { let domain_set_code_extrinsic = ExtrinsicDigest::new::< LayoutV1>, >(domain_chain_allowlist_extrinsic); ordered_extrinsics.push_front(domain_set_code_extrinsic); } - if let SetCodeExtrinsic::EncodedExtrinsic(domain_set_code_extrinsic) = - maybe_domain_set_code_extrinsic - { + if let Some(domain_set_code_extrinsic) = maybe_domain_set_code_extrinsic { let domain_set_code_extrinsic = ExtrinsicDigest::new::< LayoutV1>, >(domain_set_code_extrinsic); @@ -179,41 +171,40 @@ where } /// Verifies valid bundle fraud proof. -pub fn verify_valid_bundle_fraud_proof( +pub fn verify_valid_bundle_fraud_proof( bad_receipt: ExecutionReceipt< NumberFor, CBlock::Hash, - DomainNumber, - DomainHash, + HeaderNumberFor, + HeaderHashFor, Balance, >, - fraud_proof: &ValidBundleProof, -) -> Result<(), VerificationError> + fraud_proof: &ValidBundleProof, CBlock::Hash, DomainHeader>, + domain_id: DomainId, + state_root: CBlock::Hash, + domain_runtime_code: Vec, +) -> Result<(), VerificationError> where CBlock: BlockT, CBlock::Hash: Into, - DomainHash: Copy + Into, + DomainHeader: HeaderT, + DomainHeader::Hash: Into + PartialEq + Copy, + SKP: FraudProofStorageKeyProvider, { let ValidBundleProof { - domain_id, - bundle_index, - .. + bundle_with_proof, .. } = fraud_proof; - let bundle_body = get_fraud_proof_verification_info( - bad_receipt.consensus_block_hash.into(), - FraudProofVerificationInfoRequest::DomainBundleBody { - domain_id: *domain_id, - bundle_index: *bundle_index, - }, - ) - .and_then(FraudProofVerificationInfoResponse::into_bundle_body) - .ok_or(VerificationError::FailedToGetDomainBundleBody)?; + bundle_with_proof.verify::(domain_id, &state_root)?; + let OpaqueBundleWithProof { + bundle, + bundle_index, + .. + } = bundle_with_proof; let valid_bundle_digest = fraud_proof_runtime_interface::derive_bundle_digest( - bad_receipt.consensus_block_hash.into(), - *domain_id, - bundle_body, + domain_runtime_code, + bundle.extrinsics.clone(), ) .ok_or(VerificationError::FailedToDeriveBundleDigest)?; @@ -244,7 +235,8 @@ pub fn verify_invalid_state_transition_fraud_proof, - fraud_proof: &InvalidStateTransitionProof>, + fraud_proof: &InvalidStateTransitionProof, + domain_runtime_code: Vec, ) -> Result<(), VerificationError> where CBlock: BlockT, @@ -254,19 +246,11 @@ where DomainHeader::Number: UniqueSaturatedInto + From, { let InvalidStateTransitionProof { - domain_id, - proof, + execution_proof, execution_phase, .. } = fraud_proof; - let domain_runtime_code = fraud_proof_runtime_interface::get_fraud_proof_verification_info( - bad_receipt.consensus_block_hash.into(), - FraudProofVerificationInfoRequest::DomainRuntimeCode(*domain_id), - ) - .and_then(FraudProofVerificationInfoResponse::into_domain_runtime_code) - .ok_or(VerificationError::FailedToGetDomainRuntimeCode)?; - let (pre_state_root, post_state_root) = execution_phase .pre_post_state_root::(&bad_receipt, &bad_receipt_parent)?; @@ -279,7 +263,7 @@ where bad_receipt_parent.domain_block_hash.into(), ), pre_state_root, - proof.encode(), + execution_proof.encode(), execution_phase.execution_method(), call_data.as_ref(), domain_runtime_code, @@ -362,20 +346,24 @@ pub fn verify_invalid_block_fees_fraud_proof< Balance, >, storage_proof: &StorageProof, + domain_runtime_code: Vec, ) -> Result<(), VerificationError> where CBlock: BlockT, Balance: PartialEq + Decode, DomainHashing: Hasher, { - let storage_key = StorageKey(sp_domains::operator_block_fees_final_key()); - let storage_proof = storage_proof.clone(); + let storage_key = fraud_proof_runtime_interface::domain_storage_key( + domain_runtime_code, + DomainStorageKeyRequest::BlockFees, + ) + .ok_or(VerificationError::FailedToGetDomainStorageKey)?; let block_fees = StorageProofVerifier::::get_decoded_value::>( &bad_receipt.final_state_root, - storage_proof, - storage_key, + storage_proof.clone(), + StorageKey(storage_key), ) .map_err(|_| VerificationError::InvalidStorageProof)?; @@ -402,7 +390,8 @@ pub fn verify_invalid_transfers_fraud_proof< DomainHash, Balance, >, - proof: &InvalidTransfersProof, + storage_proof: &StorageProof, + domain_runtime_code: Vec, ) -> Result<(), VerificationError> where CBlock: BlockT, @@ -410,26 +399,15 @@ where Balance: PartialEq + Decode, DomainHashing: Hasher, { - let InvalidTransfersProof { - domain_id, - storage_proof, - .. - } = proof; - - let storage_key = get_fraud_proof_verification_info( - bad_receipt.consensus_block_hash.into(), - FraudProofVerificationInfoRequest::StorageKey { - domain_id: *domain_id, - req: StorageKeyRequest::Transfers, - }, + let storage_key = fraud_proof_runtime_interface::domain_storage_key( + domain_runtime_code, + DomainStorageKeyRequest::Transfers, ) - .and_then(FraudProofVerificationInfoResponse::into_storage_key) - .ok_or(VerificationError::FailedToGetDomainTransfersStorageKey)?; - let storage_proof = storage_proof.clone(); + .ok_or(VerificationError::FailedToGetDomainStorageKey)?; let transfers = StorageProofVerifier::::get_decoded_value::>( &bad_receipt.final_state_root, - storage_proof, + storage_proof.clone(), StorageKey(storage_key), ) .map_err(|_| VerificationError::InvalidStorageProof)?; @@ -453,7 +431,9 @@ fn check_expected_bundle_entry( HeaderHashFor, Balance, >, - invalid_bundle_fraud_proof: &InvalidBundlesFraudProof, + bundle_index: u32, + invalid_bundle_type: InvalidBundleType, + is_true_invalid_fraud_proof: bool, ) -> Result>, VerificationError> where CBlock: BlockT, @@ -461,11 +441,10 @@ where { let targeted_invalid_bundle_entry = bad_receipt .inboxed_bundles - .get(invalid_bundle_fraud_proof.bundle_index as usize) + .get(bundle_index as usize) .ok_or(VerificationError::BundleNotFound)?; - let invalid_bundle_type = invalid_bundle_fraud_proof.invalid_bundle_type.clone(); - let is_expected = if !invalid_bundle_fraud_proof.is_true_invalid_fraud_proof { + let is_expected = if !is_true_invalid_fraud_proof { // `FalseInvalid` // The proof trying to prove `bad_receipt_bundle`'s `invalid_bundle_type` is wrong, // so the proof should contains the same `invalid_bundle_type` @@ -490,7 +469,7 @@ where if !is_expected { return Err(VerificationError::UnexpectedTargetedBundleEntry { - bundle_index: invalid_bundle_fraud_proof.bundle_index, + bundle_index, fraud_proof_invalid_type_of_proof: invalid_bundle_type, targeted_entry_bundle: targeted_invalid_bundle_entry.bundle.clone(), }); @@ -516,7 +495,7 @@ fn get_extrinsic_from_proof( .map_err(|_e| VerificationError::InvalidProof) } -pub fn verify_invalid_bundles_fraud_proof( +pub fn verify_invalid_bundles_fraud_proof( bad_receipt: ExecutionReceipt< NumberFor, CBlock::Hash, @@ -531,129 +510,167 @@ pub fn verify_invalid_bundles_fraud_proof( HeaderHashFor, Balance, >, - invalid_bundles_fraud_proof: &InvalidBundlesFraudProof>, + invalid_bundles_fraud_proof: &InvalidBundlesProof< + NumberFor, + ::Hash, + DomainHeader, + >, + domain_id: DomainId, + state_root: CBlock::Hash, + domain_runtime_code: Vec, ) -> Result<(), VerificationError> where CBlock: BlockT, DomainHeader: HeaderT, CBlock::Hash: Into, DomainHeader::Hash: Into, + SKP: FraudProofStorageKeyProvider, { + let InvalidBundlesProof { + bundle_index, + invalid_bundle_type, + is_true_invalid_fraud_proof, + proof_data, + .. + } = invalid_bundles_fraud_proof; + let (bundle_index, is_true_invalid_fraud_proof) = (*bundle_index, *is_true_invalid_fraud_proof); + let invalid_bundle_entry = check_expected_bundle_entry::( &bad_receipt, - invalid_bundles_fraud_proof, + bundle_index, + invalid_bundle_type.clone(), + is_true_invalid_fraud_proof, )?; - match &invalid_bundles_fraud_proof.invalid_bundle_type { + match &invalid_bundle_type { InvalidBundleType::OutOfRangeTx(extrinsic_index) => { - let extrinsic = get_extrinsic_from_proof::( - *extrinsic_index, - invalid_bundle_entry.extrinsics_root, - invalid_bundles_fraud_proof.proof_data.clone(), - )?; - let is_tx_in_range = get_fraud_proof_verification_info( - H256::from_slice(bad_receipt.consensus_block_hash.as_ref()), - FraudProofVerificationInfoRequest::TxRangeCheck { - domain_id: invalid_bundles_fraud_proof.domain_id, - bundle_index: invalid_bundles_fraud_proof.bundle_index, - opaque_extrinsic: extrinsic, + let bundle = match proof_data { + InvalidBundlesProofData::Bundle(bundle_with_proof) + if bundle_with_proof.bundle_index == bundle_index => + { + bundle_with_proof.verify::(domain_id, &state_root)?; + bundle_with_proof.bundle.clone() + } + _ => return Err(VerificationError::UnexpectedInvalidBundleProofData), + }; + + let opaque_extrinsic = bundle + .extrinsics + .get(*extrinsic_index as usize) + .cloned() + .ok_or(VerificationError::ExtrinsicNotFound)?; + + let domain_tx_range = U256::MAX / INITIAL_DOMAIN_TX_RANGE; + let bundle_vrf_hash = + U256::from_be_bytes(bundle.sealed_header.header.proof_of_election.vrf_hash()); + + let is_tx_in_range = fraud_proof_runtime_interface::domain_runtime_call( + domain_runtime_code, + StatelessDomainRuntimeCall::IsTxInRange { + opaque_extrinsic, + domain_tx_range, + bundle_vrf_hash, }, ) - .and_then(FraudProofVerificationInfoResponse::into_tx_range_check) - .ok_or(VerificationError::FailedToGetResponseFromTxRangeHostFn)?; + .ok_or(VerificationError::FailedToGetDomainRuntimeCallResponse)?; // If it is true invalid fraud proof then tx must not be in range and // if it is false invalid fraud proof then tx must be in range for fraud // proof to be considered valid. - if is_tx_in_range == invalid_bundles_fraud_proof.is_true_invalid_fraud_proof { + if is_tx_in_range == is_true_invalid_fraud_proof { return Err(VerificationError::InvalidProof); } Ok(()) } InvalidBundleType::InherentExtrinsic(extrinsic_index) => { - let extrinsic = get_extrinsic_from_proof::( - *extrinsic_index, - invalid_bundle_entry.extrinsics_root, - invalid_bundles_fraud_proof.proof_data.clone(), - )?; - let is_inherent = get_fraud_proof_verification_info( - H256::from_slice(bad_receipt.consensus_block_hash.as_ref()), - FraudProofVerificationInfoRequest::InherentExtrinsicCheck { - domain_id: invalid_bundles_fraud_proof.domain_id, - opaque_extrinsic: extrinsic, - }, + let opaque_extrinsic = { + let extrinsic_storage_proof = match proof_data { + InvalidBundlesProofData::Extrinsic(p) => p.clone(), + _ => return Err(VerificationError::UnexpectedInvalidBundleProofData), + }; + get_extrinsic_from_proof::( + *extrinsic_index, + invalid_bundle_entry.extrinsics_root, + extrinsic_storage_proof, + )? + }; + let is_inherent = fraud_proof_runtime_interface::domain_runtime_call( + domain_runtime_code, + StatelessDomainRuntimeCall::IsInherentExtrinsic(opaque_extrinsic), ) - .and_then(FraudProofVerificationInfoResponse::into_inherent_extrinsic_check) - .ok_or(VerificationError::FailedToCheckInherentExtrinsic)?; + .ok_or(VerificationError::FailedToGetDomainRuntimeCallResponse)?; // Proof to be considered valid only, // If it is true invalid fraud proof then extrinsic must be an inherent and // If it is false invalid fraud proof then extrinsic must not be an inherent - if is_inherent == invalid_bundles_fraud_proof.is_true_invalid_fraud_proof { + if is_inherent == is_true_invalid_fraud_proof { Ok(()) } else { Err(VerificationError::InvalidProof) } } InvalidBundleType::IllegalTx(extrinsic_index) => { - let mut bundle_body = get_fraud_proof_verification_info( - bad_receipt.consensus_block_hash.into(), - FraudProofVerificationInfoRequest::DomainBundleBody { - domain_id: invalid_bundles_fraud_proof.domain_id, - bundle_index: invalid_bundles_fraud_proof.bundle_index, - }, - ) - .and_then(FraudProofVerificationInfoResponse::into_bundle_body) - .ok_or(VerificationError::FailedToGetDomainBundleBody)?; - - let extrinsics = bundle_body + let (mut bundle, execution_proof) = match proof_data { + InvalidBundlesProofData::BundleAndExecution { + bundle_with_proof, + execution_proof, + } if bundle_with_proof.bundle_index == bundle_index => { + bundle_with_proof.verify::(domain_id, &state_root)?; + (bundle_with_proof.bundle.clone(), execution_proof.clone()) + } + _ => return Err(VerificationError::UnexpectedInvalidBundleProofData), + }; + + let extrinsics = bundle + .extrinsics .drain(..) .take((*extrinsic_index + 1) as usize) .collect(); // Make host call for check extrinsic in single context - let check_extrinsic_result = get_fraud_proof_verification_info( - bad_receipt.consensus_block_hash.into(), - FraudProofVerificationInfoRequest::CheckExtrinsicsInSingleContext { - domain_id: invalid_bundles_fraud_proof.domain_id, - domain_block_number: bad_receipt_parent.domain_block_number.saturated_into(), - domain_block_hash: bad_receipt_parent.domain_block_hash.into(), - domain_block_state_root: bad_receipt_parent.final_state_root.into(), + let check_extrinsic_result = + fraud_proof_runtime_interface::check_extrinsics_in_single_context( + domain_runtime_code, + ( + bad_receipt_parent.domain_block_number.saturated_into(), + bad_receipt_parent.domain_block_hash.into(), + ), + bad_receipt_parent.final_state_root.into(), extrinsics, - storage_proof: invalid_bundles_fraud_proof.proof_data.clone(), - }, - ) - .and_then(FraudProofVerificationInfoResponse::into_single_context_extrinsic_check) - .ok_or(VerificationError::FailedToCheckExtrinsicsInSingleContext)?; + execution_proof.encode(), + ) + .ok_or(VerificationError::FailedToCheckExtrinsicsInSingleContext)?; let is_extrinsic_invalid = check_extrinsic_result == Some(*extrinsic_index); // Proof to be considered valid only, // If it is true invalid fraud proof then extrinsic must be an invalid extrinsic and // If it is false invalid fraud proof then extrinsic must not be an invalid extrinsic - if is_extrinsic_invalid == invalid_bundles_fraud_proof.is_true_invalid_fraud_proof { + if is_extrinsic_invalid == is_true_invalid_fraud_proof { Ok(()) } else { Err(VerificationError::InvalidProof) } } InvalidBundleType::UndecodableTx(extrinsic_index) => { - let extrinsic = get_extrinsic_from_proof::( - *extrinsic_index, - invalid_bundle_entry.extrinsics_root, - invalid_bundles_fraud_proof.proof_data.clone(), - )?; - let is_decodable = get_fraud_proof_verification_info( - H256::from_slice(bad_receipt.consensus_block_hash.as_ref()), - FraudProofVerificationInfoRequest::ExtrinsicDecodableCheck { - domain_id: invalid_bundles_fraud_proof.domain_id, - opaque_extrinsic: extrinsic, - }, + let opaque_extrinsic = { + let extrinsic_storage_proof = match proof_data { + InvalidBundlesProofData::Extrinsic(p) => p.clone(), + _ => return Err(VerificationError::UnexpectedInvalidBundleProofData), + }; + get_extrinsic_from_proof::( + *extrinsic_index, + invalid_bundle_entry.extrinsics_root, + extrinsic_storage_proof, + )? + }; + let is_decodable = fraud_proof_runtime_interface::domain_runtime_call( + domain_runtime_code, + StatelessDomainRuntimeCall::IsDecodableExtrinsic(opaque_extrinsic), ) - .and_then(FraudProofVerificationInfoResponse::into_extrinsic_decodable_check) - .ok_or(VerificationError::FailedToCheckExtrinsicDecodable)?; + .ok_or(VerificationError::FailedToGetDomainRuntimeCallResponse)?; - if is_decodable == invalid_bundles_fraud_proof.is_true_invalid_fraud_proof { + if is_decodable == is_true_invalid_fraud_proof { return Err(VerificationError::InvalidProof); } Ok(()) diff --git a/crates/subspace-fake-runtime-api/src/lib.rs b/crates/subspace-fake-runtime-api/src/lib.rs index 292ab34977..2075cdf478 100644 --- a/crates/subspace-fake-runtime-api/src/lib.rs +++ b/crates/subspace-fake-runtime-api/src/lib.rs @@ -402,14 +402,14 @@ sp_api::impl_runtime_apis! { } impl sp_domains_fraud_proof::FraudProofApi for Runtime { - fn submit_fraud_proof_unsigned(_fraud_proof: FraudProof) { + fn submit_fraud_proof_unsigned(_fraud_proof: FraudProof, ::Hash, DomainHeader, H256>) { unreachable!() } fn extract_fraud_proofs( _domain_id: DomainId, _extrinsics: Vec<::Extrinsic>, - ) -> Vec> { + ) -> Vec, ::Hash, DomainHeader, H256>> { unreachable!() } diff --git a/crates/subspace-runtime/src/domains.rs b/crates/subspace-runtime/src/domains.rs index 7f2a781e29..dae3288df0 100644 --- a/crates/subspace-runtime/src/domains.rs +++ b/crates/subspace-runtime/src/domains.rs @@ -5,6 +5,7 @@ use crate::{Balance, Block, Domains, RuntimeCall, UncheckedExtrinsic}; #[cfg(not(feature = "std"))] use alloc::vec::Vec; use domain_runtime_primitives::opaque::Header as DomainHeader; +use sp_core::H256; use sp_domains::DomainId; use sp_domains_fraud_proof::fraud_proof::FraudProof; use sp_runtime::traits::{Block as BlockT, NumberFor}; @@ -44,7 +45,7 @@ pub(crate) fn extract_bundle( pub(crate) fn extract_fraud_proofs( domain_id: DomainId, extrinsics: Vec, -) -> Vec> { +) -> Vec, ::Hash, DomainHeader, H256>> { let successful_fraud_proofs = Domains::successful_fraud_proofs(domain_id); extrinsics .into_iter() diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index e36c25bfd7..807a5d7261 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -713,6 +713,7 @@ impl pallet_domains::Config for Runtime { type Balance = Balance; type MmrHash = mmr::Hash; type MmrProofVerifier = MmrProofVerifier; + type FraudProofStorageKeyProvider = StorageKeyProvider; } parameter_types! { @@ -1361,14 +1362,14 @@ impl_runtime_apis! { } impl sp_domains_fraud_proof::FraudProofApi for Runtime { - fn submit_fraud_proof_unsigned(fraud_proof: FraudProof) { + fn submit_fraud_proof_unsigned(fraud_proof: FraudProof, ::Hash, DomainHeader, H256>) { Domains::submit_fraud_proof_unsigned(fraud_proof) } fn extract_fraud_proofs( domain_id: DomainId, extrinsics: Vec<::Extrinsic>, - ) -> Vec> { + ) -> Vec, ::Hash, DomainHeader, H256>> { crate::domains::extract_fraud_proofs(domain_id, extrinsics) } diff --git a/domains/client/domain-operator/src/domain_block_processor.rs b/domains/client/domain-operator/src/domain_block_processor.rs index 5c0f351116..41216d99b9 100644 --- a/domains/client/domain-operator/src/domain_block_processor.rs +++ b/domains/client/domain-operator/src/domain_block_processor.rs @@ -764,6 +764,14 @@ where if let Some(mismatched_receipts) = self.find_mismatch_receipt(consensus_block_hash)? { let consensus_best_hash = self.consensus_client.info().best_hash; let mut consensus_runtime_api = self.consensus_client.runtime_api(); + let fraud_proof_api_version = consensus_runtime_api + .api_version::>(consensus_best_hash) + .map_err(sp_blockchain::Error::RuntimeApiError)? + .ok_or_else(|| { + sp_blockchain::Error::RuntimeApiError(ApiError::Application( + format!("FraudProofApi not found at: {:?}", consensus_best_hash).into(), + )) + })?; let domains_api_version = consensus_runtime_api .api_version::>(consensus_best_hash) .map_err(sp_blockchain::Error::RuntimeApiError)? @@ -773,11 +781,19 @@ where )) })?; - // New `DomainsApi` introduced in version 4 is required for generating fraud proof + // New `DomainsApi` introduced in version 4 is required for generating fraud proof and + // new `FraudProofApi` in version 2 is required for submitting fraud proof // TODO: remove before next network - if domains_api_version >= 4 { + if domains_api_version >= 4 && fraud_proof_api_version >= 2 { let fraud_proof_v2 = self.generate_fraud_proof(mismatched_receipts)?; - // TODO: submit fraud proof + + tracing::info!("Submit fraud proof: {fraud_proof_v2:?}"); + consensus_runtime_api.register_extension( + self.consensus_offchain_tx_pool_factory + .offchain_transaction_pool(consensus_best_hash), + ); + consensus_runtime_api + .submit_fraud_proof_unsigned(consensus_best_hash, fraud_proof_v2)?; } } diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 10de3dc60b..cc13c08cfe 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -745,6 +745,7 @@ impl pallet_domains::Config for Runtime { type Balance = Balance; type MmrHash = mmr::Hash; type MmrProofVerifier = MmrProofVerifier; + type FraudProofStorageKeyProvider = StorageKeyProvider; } parameter_types! { @@ -1070,7 +1071,7 @@ fn extract_bundle( pub(crate) fn extract_fraud_proofs( domain_id: DomainId, extrinsics: Vec, -) -> Vec> { +) -> Vec, ::Hash, DomainHeader, H256>> { let successful_fraud_proofs = Domains::successful_fraud_proofs(domain_id); extrinsics .into_iter() @@ -1529,14 +1530,14 @@ impl_runtime_apis! { } impl sp_domains_fraud_proof::FraudProofApi for Runtime { - fn submit_fraud_proof_unsigned(fraud_proof: FraudProof) { + fn submit_fraud_proof_unsigned(fraud_proof: FraudProof, ::Hash, DomainHeader, H256>) { Domains::submit_fraud_proof_unsigned(fraud_proof) } fn extract_fraud_proofs( domain_id: DomainId, extrinsics: Vec<::Extrinsic>, - ) -> Vec> { + ) -> Vec, ::Hash, DomainHeader, H256>> { extract_fraud_proofs(domain_id, extrinsics) } From a2610d13e53af19eff2ea745ac4db196890616ee Mon Sep 17 00:00:00 2001 From: linning Date: Tue, 14 May 2024 03:42:13 +0800 Subject: [PATCH 5/5] Remove the unused extract_fraud_proofs runtime api Signed-off-by: linning --- crates/pallet-domains/src/lib.rs | 12 ---------- crates/sp-domains-fraud-proof/src/lib.rs | 6 ----- crates/subspace-fake-runtime-api/src/lib.rs | 7 ------ crates/subspace-runtime/src/domains.rs | 21 ----------------- crates/subspace-runtime/src/lib.rs | 7 ------ test/subspace-test-runtime/src/lib.rs | 26 --------------------- 6 files changed, 79 deletions(-) diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index cf7f1fd8a6..daf5a43b0f 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -423,10 +423,6 @@ mod pallet { #[pallet::storage] pub type SuccessfulBundles = StorageMap<_, Identity, DomainId, Vec, ValueQuery>; - /// Fraud proofs submitted successfully in current block. - #[pallet::storage] - pub(super) type SuccessfulFraudProofs = - StorageMap<_, Identity, DomainId, Vec, ValueQuery>; /// Stores the next runtime id. #[pallet::storage] pub(super) type NextRuntimeId = StorageValue<_, RuntimeId, ValueQuery>; @@ -1183,8 +1179,6 @@ mod pallet { new_head_receipt_number: Some(new_head_receipt_number), }); - SuccessfulFraudProofs::::append(domain_id, fraud_proof.hash()); - Ok(Some(actual_weight).into()) } @@ -1537,8 +1531,6 @@ mod pallet { T::DomainBundleSubmitted::domain_bundle_submitted(domain_id); } - let _ = SuccessfulFraudProofs::::clear(u32::MAX, None); - Weight::zero() } @@ -1662,10 +1654,6 @@ impl Pallet { SuccessfulBundles::::get(domain_id) } - pub fn successful_fraud_proofs(domain_id: DomainId) -> Vec { - SuccessfulFraudProofs::::get(domain_id) - } - pub fn domain_runtime_code(domain_id: DomainId) -> Option> { RuntimeRegistry::::get(Self::runtime_id(domain_id)?) .and_then(|mut runtime_object| runtime_object.raw_genesis.take_runtime_code()) diff --git a/crates/sp-domains-fraud-proof/src/lib.rs b/crates/sp-domains-fraud-proof/src/lib.rs index 15ebddd0a3..039e0682ce 100644 --- a/crates/sp-domains-fraud-proof/src/lib.rs +++ b/crates/sp-domains-fraud-proof/src/lib.rs @@ -400,12 +400,6 @@ sp_api::decl_runtime_apis! { /// Submit the fraud proof via an unsigned extrinsic. fn submit_fraud_proof_unsigned(fraud_proof: FraudProof, Block::Hash, DomainHeader, H256>); - /// Extract the fraud proof handled successfully from the given extrinsics. - fn extract_fraud_proofs( - domain_id: DomainId, - extrinsics: Vec, - ) -> Vec, Block::Hash, DomainHeader, H256>>; - /// Reture the storage key used in fraud proof fn fraud_proof_storage_key(req: FraudProofStorageKeyRequest) -> Vec; } diff --git a/crates/subspace-fake-runtime-api/src/lib.rs b/crates/subspace-fake-runtime-api/src/lib.rs index 2075cdf478..dfcba5a3a5 100644 --- a/crates/subspace-fake-runtime-api/src/lib.rs +++ b/crates/subspace-fake-runtime-api/src/lib.rs @@ -406,13 +406,6 @@ sp_api::impl_runtime_apis! { unreachable!() } - fn extract_fraud_proofs( - _domain_id: DomainId, - _extrinsics: Vec<::Extrinsic>, - ) -> Vec, ::Hash, DomainHeader, H256>> { - unreachable!() - } - fn fraud_proof_storage_key(_req: FraudProofStorageKeyRequest) -> Vec { unreachable!() } diff --git a/crates/subspace-runtime/src/domains.rs b/crates/subspace-runtime/src/domains.rs index dae3288df0..6dbbb0dc43 100644 --- a/crates/subspace-runtime/src/domains.rs +++ b/crates/subspace-runtime/src/domains.rs @@ -5,9 +5,7 @@ use crate::{Balance, Block, Domains, RuntimeCall, UncheckedExtrinsic}; #[cfg(not(feature = "std"))] use alloc::vec::Vec; use domain_runtime_primitives::opaque::Header as DomainHeader; -use sp_core::H256; use sp_domains::DomainId; -use sp_domains_fraud_proof::fraud_proof::FraudProof; use sp_runtime::traits::{Block as BlockT, NumberFor}; pub(crate) fn extract_successful_bundles( @@ -41,22 +39,3 @@ pub(crate) fn extract_bundle( _ => None, } } - -pub(crate) fn extract_fraud_proofs( - domain_id: DomainId, - extrinsics: Vec, -) -> Vec, ::Hash, DomainHeader, H256>> { - let successful_fraud_proofs = Domains::successful_fraud_proofs(domain_id); - extrinsics - .into_iter() - .filter_map(|uxt| match uxt.function { - RuntimeCall::Domains(pallet_domains::Call::submit_fraud_proof { fraud_proof }) - if fraud_proof.domain_id() == domain_id - && successful_fraud_proofs.contains(&fraud_proof.hash()) => - { - Some(*fraud_proof) - } - _ => None, - }) - .collect() -} diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 807a5d7261..3bac5fd595 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -1366,13 +1366,6 @@ impl_runtime_apis! { Domains::submit_fraud_proof_unsigned(fraud_proof) } - fn extract_fraud_proofs( - domain_id: DomainId, - extrinsics: Vec<::Extrinsic>, - ) -> Vec, ::Hash, DomainHeader, H256>> { - crate::domains::extract_fraud_proofs(domain_id, extrinsics) - } - 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 cc13c08cfe..bb9d071b1b 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -1068,25 +1068,6 @@ fn extract_bundle( } } -pub(crate) fn extract_fraud_proofs( - domain_id: DomainId, - extrinsics: Vec, -) -> Vec, ::Hash, DomainHeader, H256>> { - let successful_fraud_proofs = Domains::successful_fraud_proofs(domain_id); - extrinsics - .into_iter() - .filter_map(|uxt| match uxt.function { - RuntimeCall::Domains(pallet_domains::Call::submit_fraud_proof { fraud_proof }) - if fraud_proof.domain_id() == domain_id - && successful_fraud_proofs.contains(&fraud_proof.hash()) => - { - Some(*fraud_proof) - } - _ => None, - }) - .collect() -} - struct RewardAddress([u8; 32]); impl From for RewardAddress { @@ -1534,13 +1515,6 @@ impl_runtime_apis! { Domains::submit_fraud_proof_unsigned(fraud_proof) } - fn extract_fraud_proofs( - domain_id: DomainId, - extrinsics: Vec<::Extrinsic>, - ) -> Vec, ::Hash, DomainHeader, H256>> { - extract_fraud_proofs(domain_id, extrinsics) - } - fn fraud_proof_storage_key(req: FraudProofStorageKeyRequest) -> Vec { ::storage_key(req) }