diff --git a/Cargo.lock b/Cargo.lock index 09de168e0f..50ba7d4d99 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", @@ -11606,6 +11606,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/Cargo.toml b/crates/pallet-domains/Cargo.toml index 6308f42bab..30313c1ed8 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 = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } sp-runtime = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } sp-std = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } +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 = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee", 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 = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } 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 = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } -sp-state-machine = { git = "https://github.com/subspace/polkadot-sdk", rev = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } -sp-trie = { git = "https://github.com/subspace/polkadot-sdk", rev = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } [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/lib.rs b/crates/pallet-domains/src/lib.rs index 638920a82c..f19b42ab55 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -69,8 +69,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, @@ -80,6 +82,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}; @@ -119,6 +122,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 { @@ -147,6 +157,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)] @@ -182,8 +194,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; @@ -207,7 +219,7 @@ mod pallet { OperatorAllowList, OperatorId, OperatorPublicKey, OperatorSignature, 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, @@ -217,6 +229,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; @@ -389,6 +402,19 @@ 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, + >; + + /// Fraud proof storage key provider + type FraudProofStorageKeyProvider: FraudProofStorageKeyProvider; } #[pallet::pallet] @@ -400,11 +426,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>; @@ -722,6 +743,26 @@ 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, + /// 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 { @@ -1073,7 +1114,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) @@ -1083,67 +1124,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), - ); - - // 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); + 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), + ); - Self::deposit_event(Event::FraudProofProcessed { - domain_id, - new_head_receipt_number: Some(new_head_receipt_number), - }); + // 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)?; } - SuccessfulFraudProofs::::append(domain_id, fraud_proof.hash()); + // 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), + }); Ok(Some(actual_weight).into()) } @@ -1505,8 +1542,6 @@ mod pallet { T::DomainBundleSubmitted::domain_bundle_submitted(domain_id); } - let _ = SuccessfulFraudProofs::::clear(u32::MAX, None); - Weight::zero() } @@ -1630,10 +1665,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()) @@ -1871,174 +1902,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, - >(bad_receipt, proof) + 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, + 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::(); - - // 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(); + // 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::(); - (tag, priority) - } else { - return Err(FraudProofError::UnexpectedFraudProof); - }; + // 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(); - Ok(tag_and_priority) + Ok((tag, priority)) } /// Return operators specific election verification params for Proof of Election verification. @@ -2259,6 +2363,82 @@ impl Pallet { let storage_fund_acc = storage_fund_account::(operator_id); 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, + ) -> 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, + ) -> 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 { @@ -2296,7 +2476,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 ed4a194db3..c66377beca 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; @@ -294,6 +278,9 @@ impl pallet_domains::Config for Test { type DomainBundleSubmitted = (); type OnDomainInstantiated = (); type Balance = Balance; + type MmrHash = H256; + type MmrProofVerifier = (); + type FraudProofStorageKeyProvider = (); } pub struct ExtrinsicStorageFees; @@ -332,175 +319,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(), @@ -926,370 +744,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 0431764462..f1bde7e316 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 = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } sp-runtime-interface = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } sp-state-machine = { optional = true, git = "https://github.com/subspace/polkadot-sdk", rev = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } +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 = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } sp-trie = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } 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..296687f6ac 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; @@ -224,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", @@ -258,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") @@ -325,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", @@ -368,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, @@ -386,368 +298,270 @@ 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) + } } -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/crates/sp-domains-fraud-proof/src/lib.rs b/crates/sp-domains-fraud-proof/src/lib.rs index 8cfbf12e62..039e0682ce 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,10 @@ 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); - - /// Extract the fraud proof handled successfully from the given extrinsics. - fn extract_fraud_proofs( - domain_id: DomainId, - extrinsics: Vec, - ) -> Vec>; + fn submit_fraud_proof_unsigned(fraud_proof: FraudProof, Block::Hash, DomainHeader, H256>); /// Reture the storage key used in fraud proof fn fraud_proof_storage_key(req: FraudProofStorageKeyRequest) -> Vec; 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 fd7e1c35f9..ff982507e1 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/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index 7eb068fb3b..cf4c541f9b 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -1325,7 +1325,7 @@ pub struct OperatorSigningKeyProofOfOwnershipData { 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>); @@ -1408,6 +1408,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/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-fake-runtime-api/src/lib.rs b/crates/subspace-fake-runtime-api/src/lib.rs index e6c9908589..dfcba5a3a5 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 { @@ -398,14 +402,7 @@ sp_api::impl_runtime_apis! { } impl sp_domains_fraud_proof::FraudProofApi for Runtime { - fn submit_fraud_proof_unsigned(_fraud_proof: FraudProof) { - unreachable!() - } - - fn extract_fraud_proofs( - _domain_id: DomainId, - _extrinsics: Vec<::Extrinsic>, - ) -> Vec> { + fn submit_fraud_proof_unsigned(_fraud_proof: FraudProof, ::Hash, DomainHeader, H256>) { unreachable!() } diff --git a/crates/subspace-runtime/src/domains.rs b/crates/subspace-runtime/src/domains.rs index 7f2a781e29..6dbbb0dc43 100644 --- a/crates/subspace-runtime/src/domains.rs +++ b/crates/subspace-runtime/src/domains.rs @@ -6,7 +6,6 @@ use crate::{Balance, Block, Domains, RuntimeCall, UncheckedExtrinsic}; use alloc::vec::Vec; use domain_runtime_primitives::opaque::Header as DomainHeader; 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( @@ -40,22 +39,3 @@ pub(crate) fn extract_bundle( _ => None, } } - -pub(crate) fn extract_fraud_proofs( - domain_id: DomainId, - extrinsics: Vec, -) -> Vec> { - 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 f6e3e48e88..a939af3089 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -467,9 +467,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, @@ -490,7 +490,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) } } @@ -677,6 +677,9 @@ impl pallet_domains::Config for Runtime { type DomainBundleSubmitted = Messenger; type OnDomainInstantiated = Messenger; type Balance = Balance; + type MmrHash = mmr::Hash; + type MmrProofVerifier = MmrProofVerifier; + type FraudProofStorageKeyProvider = StorageKeyProvider; } parameter_types! { @@ -1223,6 +1226,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 { @@ -1321,17 +1328,10 @@ 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> { - crate::domains::extract_fraud_proofs(domain_id, extrinsics) - } - fn fraud_proof_storage_key(req: FraudProofStorageKeyRequest) -> Vec { ::storage_key(req) } diff --git a/domains/client/domain-operator/Cargo.toml b/domains/client/domain-operator/Cargo.toml index 54c51d9f82..0539c74521 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 = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } sc-consensus = { git = "https://github.com/subspace/polkadot-sdk", rev = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } +sc-domains = { version = "0.1.0", path = "../../../crates/sc-domains" } sc-transaction-pool = { git = "https://github.com/subspace/polkadot-sdk", rev = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } sc-transaction-pool-api = { git = "https://github.com/subspace/polkadot-sdk", rev = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } sc-utils = { git = "https://github.com/subspace/polkadot-sdk", rev = "9b8cdb87de8f1c0e6b48c468b6196d1d99eeabee" } 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 6fe23b4209..634faa58b6 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,39 @@ 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 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)? + .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 and + // new `FraudProofApi` in version 2 is required for submitting fraud proof + // TODO: remove before next network + if domains_api_version >= 4 && fraud_proof_api_version >= 2 { + let fraud_proof_v2 = self.generate_fraud_proof(mismatched_receipts)?; + + 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)?; + } } Ok(()) @@ -850,10 +875,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 +896,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 +920,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 +1014,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..74f5c7cb53 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, + consensus_block_hash, + maybe_runtime_id, + )?; + + let invalid_domain_extrinsics_root_proof = FraudProof { domain_id, - bundle_index, - invalid_type, - proof_data, - is_true_invalid, - ))) + 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,287 @@ 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) } - 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_bundle_proof = FraudProof { + domain_id, + bad_receipt_hash, + 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) + } + + 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_state_transition_proof = InvalidStateTransitionProof { + let invalid_transfers_proof = FraudProof { domain_id, bad_receipt_hash, - proof: execution_proof, - execution_phase, + maybe_mmr_proof: None, + maybe_domain_runtime_code_proof, + proof: FraudProofVariant::InvalidTransfers(InvalidTransfersProof { storage_proof }), }; + Ok(invalid_transfers_proof) + } - Ok(FraudProof::InvalidStateTransition( - invalid_state_transition_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 +791,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/domains/client/domain-operator/src/tests.rs b/domains/client/domain-operator/src/tests.rs index 5be3743bec..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 _; @@ -4414,11 +4412,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 +4432,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..bb9d071b1b 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,9 @@ impl pallet_domains::Config for Runtime { type DomainBundleSubmitted = Messenger; type OnDomainInstantiated = Messenger; type Balance = Balance; + type MmrHash = mmr::Hash; + type MmrProofVerifier = MmrProofVerifier; + type FraudProofStorageKeyProvider = StorageKeyProvider; } parameter_types! { @@ -1065,25 +1068,6 @@ fn extract_bundle( } } -pub(crate) fn extract_fraud_proofs( - domain_id: DomainId, - extrinsics: Vec, -) -> Vec> { - 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 { @@ -1425,6 +1409,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 { @@ -1523,17 +1511,10 @@ 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> { - extract_fraud_proofs(domain_id, extrinsics) - } - fn fraud_proof_storage_key(req: FraudProofStorageKeyRequest) -> Vec { ::storage_key(req) } @@ -1594,8 +1575,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) } } 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();