diff --git a/crates/pallet-domains/Cargo.toml b/crates/pallet-domains/Cargo.toml index 69bf51d552..cdba8192da 100644 --- a/crates/pallet-domains/Cargo.toml +++ b/crates/pallet-domains/Cargo.toml @@ -31,9 +31,9 @@ subspace-runtime-primitives = { version = "0.1.0", default-features = false, pat [dev-dependencies] pallet-balances = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "74509ce016358e7f56ed2825fd75c324f8c22ef9" } pallet-timestamp = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "74509ce016358e7f56ed2825fd75c324f8c22ef9" } +sp-externalities = { version = "0.19.0", git = "https://github.com/subspace/polkadot-sdk", rev = "74509ce016358e7f56ed2825fd75c324f8c22ef9" } sp-state-machine = { version = "0.28.0", git = "https://github.com/subspace/polkadot-sdk", rev = "74509ce016358e7f56ed2825fd75c324f8c22ef9" } sp-trie = { version = "22.0.0", git = "https://github.com/subspace/polkadot-sdk", rev = "74509ce016358e7f56ed2825fd75c324f8c22ef9" } -sp-externalities = { version = "0.19.0", git = "https://github.com/subspace/polkadot-sdk", rev = "74509ce016358e7f56ed2825fd75c324f8c22ef9" } [features] default = ["std"] diff --git a/crates/pallet-domains/src/benchmarking.rs b/crates/pallet-domains/src/benchmarking.rs index 14eb983f10..252fd8d865 100644 --- a/crates/pallet-domains/src/benchmarking.rs +++ b/crates/pallet-domains/src/benchmarking.rs @@ -36,11 +36,12 @@ mod benchmarks { let (_, operator_id) = register_helper_operator::(domain_id, T::Currency::minimum_balance()); - let mut receipt = BlockTree::::get::<_, T::DomainNumber>(domain_id, Zero::zero()) - .first() - .and_then(DomainBlocks::::get) - .expect("genesis receipt must exist") - .execution_receipt; + let mut receipt = + BlockTree::::get::<_, DomainBlockNumberFor>(domain_id, Zero::zero()) + .first() + .and_then(DomainBlocks::::get) + .expect("genesis receipt must exist") + .execution_receipt; for i in 1..=(block_tree_pruning_depth + 1) { let consensus_block_number = i.into(); let domain_block_number = i.into(); @@ -243,7 +244,7 @@ mod benchmarks { assert_eq!(domain_obj.owner_account_id, creator); assert!(DomainStakingSummary::::get(domain_id).is_some()); assert_eq!( - BlockTree::::get::<_, T::DomainNumber>(domain_id, Zero::zero()).len(), + BlockTree::::get::<_, DomainBlockNumberFor>(domain_id, Zero::zero()).len(), 1 ); assert_eq!(NextDomainId::::get(), domain_id + 1.into()); diff --git a/crates/pallet-domains/src/block_tree.rs b/crates/pallet-domains/src/block_tree.rs index 4d2f8189af..41d7a3dff1 100644 --- a/crates/pallet-domains/src/block_tree.rs +++ b/crates/pallet-domains/src/block_tree.rs @@ -2,8 +2,8 @@ use crate::pallet::StateRoots; use crate::{ - BalanceOf, BlockTree, Config, ConsensusBlockHash, DomainBlockDescendants, DomainBlocks, - ExecutionInbox, ExecutionReceiptOf, HeadReceiptNumber, InboxedBundleAuthor, + BalanceOf, BlockTree, Config, ConsensusBlockHash, DomainBlockDescendants, DomainBlockNumberFor, + DomainBlocks, ExecutionInbox, ExecutionReceiptOf, HeadReceiptNumber, InboxedBundleAuthor, }; use codec::{Decode, Encode}; use frame_support::{ensure, PalletError}; @@ -227,7 +227,7 @@ pub(crate) struct ConfirmedDomainBlockInfo { } pub(crate) type ProcessExecutionReceiptResult = - Result::DomainNumber, BalanceOf>>, Error>; + Result, BalanceOf>>, Error>; /// Process the execution receipt to add it to the block tree /// Returns the domain block number that was pruned, if any diff --git a/crates/pallet-domains/src/domain_registry.rs b/crates/pallet-domains/src/domain_registry.rs index f9da432656..1bd3471a9f 100644 --- a/crates/pallet-domains/src/domain_registry.rs +++ b/crates/pallet-domains/src/domain_registry.rs @@ -4,7 +4,8 @@ use crate::block_tree::import_genesis_receipt; use crate::pallet::DomainStakingSummary; use crate::staking::StakingSummary; use crate::{ - Config, DomainRegistry, ExecutionReceiptOf, HoldIdentifier, NextDomainId, RuntimeRegistry, + Config, DomainHashingFor, DomainRegistry, ExecutionReceiptOf, HoldIdentifier, NextDomainId, + RuntimeRegistry, }; use alloc::string::String; use codec::{Decode, Encode}; @@ -15,7 +16,10 @@ use frame_support::{ensure, PalletError}; use frame_system::pallet_prelude::*; use scale_info::TypeInfo; use sp_core::Get; -use sp_domains::{DomainId, DomainsDigestItem, OperatorAllowList, ReceiptHash, RuntimeId}; +use sp_domains::{ + derive_domain_block_hash, DomainId, DomainsDigestItem, OperatorAllowList, ReceiptHash, + RuntimeId, +}; use sp_runtime::traits::{CheckedAdd, Zero}; use sp_runtime::DigestItem; use sp_std::collections::btree_map::BTreeMap; @@ -126,9 +130,20 @@ pub(crate) fn do_instantiate_domain( let state_version = runtime_obj.version.state_version(); let raw_genesis = runtime_obj.into_complete_raw_genesis(domain_id); - let state_root = raw_genesis.state_root::(state_version); + let state_root = raw_genesis.state_root::>(state_version); + let genesis_block_hash = derive_domain_block_hash::( + Zero::zero(), + sp_domains::EMPTY_EXTRINSIC_ROOT.into(), + state_root, + Default::default(), + Default::default(), + ); - ExecutionReceiptOf::::genesis(state_root) + ExecutionReceiptOf::::genesis( + state_root, + sp_domains::EMPTY_EXTRINSIC_ROOT, + genesis_block_hash, + ) }; let genesis_receipt_hash = genesis_receipt.hash(); diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 0c784f7889..f165138a71 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -45,7 +45,7 @@ pub use pallet::*; use scale_info::TypeInfo; use sp_core::H256; use sp_domains::bundle_producer_election::{is_below_threshold, BundleProducerElectionParams}; -use sp_domains::fraud_proof::{FraudProof, InvalidTotalRewardsProof}; +use sp_domains::fraud_proof::{FraudProof, InvalidDomainBlockHashProof, InvalidTotalRewardsProof}; use sp_domains::verification::verify_invalid_total_rewards_fraud_proof; use sp_domains::{ DomainBlockLimit, DomainId, DomainInstanceData, ExecutionReceipt, OpaqueBundle, OperatorId, @@ -53,11 +53,13 @@ use sp_domains::{ EMPTY_EXTRINSIC_ROOT, }; use sp_domains_fraud_proof::fraud_proof_runtime_interface::get_fraud_proof_verification_info; -use sp_domains_fraud_proof::verification::verify_invalid_domain_extrinsics_root_fraud_proof; +use sp_domains_fraud_proof::verification::{ + verify_invalid_domain_block_hash_fraud_proof, verify_invalid_domain_extrinsics_root_fraud_proof, +}; use sp_domains_fraud_proof::{ FraudProofVerificationInfoRequest, FraudProofVerificationInfoResponse, }; -use sp_runtime::traits::{BlakeTwo256, CheckedSub, Hash, One, Zero}; +use sp_runtime::traits::{BlakeTwo256, CheckedSub, Hash, Header, One, Zero}; use sp_runtime::{RuntimeAppPublic, SaturatedConversion, Saturating}; use sp_std::boxed::Box; use sp_std::collections::btree_map::BTreeMap; @@ -82,7 +84,7 @@ pub trait HoldIdentifier { pub type ExecutionReceiptOf = ExecutionReceipt< BlockNumberFor, ::Hash, - ::DomainNumber, + DomainBlockNumberFor, ::DomainHash, BalanceOf, >; @@ -90,7 +92,7 @@ pub type ExecutionReceiptOf = ExecutionReceipt< pub type OpaqueBundleOf = OpaqueBundle< BlockNumberFor, ::Hash, - ::DomainNumber, + DomainBlockNumberFor, ::DomainHash, BalanceOf, >; @@ -102,6 +104,9 @@ pub(crate) struct ElectionVerificationParams { total_domain_stake: Balance, } +pub type DomainBlockNumberFor = <::DomainHeader as Header>::Number; +pub type DomainHashingFor = <::DomainHeader as Header>::Hashing; + #[frame_support::pallet] mod pallet { #![allow(clippy::large_enum_variant)] @@ -134,7 +139,8 @@ mod pallet { }; use crate::weights::WeightInfo; use crate::{ - BalanceOf, ElectionVerificationParams, HoldIdentifier, NominatorId, OpaqueBundleOf, + BalanceOf, DomainBlockNumberFor, ElectionVerificationParams, HoldIdentifier, NominatorId, + OpaqueBundleOf, }; use alloc::string::String; use codec::FullCodec; @@ -152,7 +158,7 @@ mod pallet { ReceiptHash, RuntimeId, RuntimeType, }; use sp_runtime::traits::{ - AtLeast32BitUnsigned, BlockNumberProvider, Bounded, CheckEqual, CheckedAdd, Hash, + AtLeast32BitUnsigned, BlockNumberProvider, CheckEqual, CheckedAdd, Header as HeaderT, MaybeDisplay, One, SimpleBitOps, Zero, }; use sp_runtime::SaturatedConversion; @@ -168,21 +174,6 @@ mod pallet { pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// Domain block number type. - type DomainNumber: Parameter - + Member - + MaybeSerializeDeserialize - + Debug - + MaybeDisplay - + AtLeast32BitUnsigned - + Default - + Bounded - + Copy - + sp_std::hash::Hash - + sp_std::str::FromStr - + MaxEncodedLen - + TypeInfo; - /// Domain block hash type. type DomainHash: Parameter + Member @@ -201,8 +192,8 @@ mod pallet { + Into + From; - /// The domain hashing algorithm. - type DomainHashing: Hash + TypeInfo; + /// The domain header type. + type DomainHeader: HeaderT; /// Same with `pallet_subspace::Config::ConfirmationDepthK`. #[pallet::constant] @@ -235,7 +226,7 @@ mod pallet { /// The block tree pruning depth. #[pallet::constant] - type BlockTreePruningDepth: Get; + type BlockTreePruningDepth: Get>; /// The maximum block size limit for all domain. #[pallet::constant] @@ -274,11 +265,11 @@ mod pallet { /// Minimum number of blocks after which any finalized withdrawals are released to nominators. #[pallet::constant] - type StakeWithdrawalLockingPeriod: Get; + type StakeWithdrawalLockingPeriod: Get>; /// Domain epoch transition interval #[pallet::constant] - type StakeEpochDuration: Get; + type StakeEpochDuration: Get>; /// Treasury account. #[pallet::constant] @@ -416,7 +407,7 @@ mod pallet { Identity, OperatorId, Identity, - T::DomainNumber, + DomainBlockNumberFor, Vec, BalanceOf>>, OptionQuery, >; @@ -424,8 +415,13 @@ mod pallet { /// A list of operators that are either unregistering or one more of the nominators /// are withdrawing some staked funds. #[pallet::storage] - pub(super) type PendingUnlocks = - StorageMap<_, Identity, (DomainId, T::DomainNumber), BTreeSet, OptionQuery>; + pub(super) type PendingUnlocks = StorageMap< + _, + Identity, + (DomainId, DomainBlockNumberFor), + BTreeSet, + OptionQuery, + >; /// A list operators who were slashed during the current epoch associated with the domain. /// When the epoch for a given domain is complete, operator total stake is moved to treasury and @@ -467,7 +463,7 @@ mod pallet { Identity, DomainId, Identity, - T::DomainNumber, + DomainBlockNumberFor, BTreeSet, ValueQuery, >; @@ -478,7 +474,13 @@ mod pallet { _, Identity, ReceiptHash, - DomainBlock, T::Hash, T::DomainNumber, T::DomainHash, BalanceOf>, + DomainBlock< + BlockNumberFor, + T::Hash, + DomainBlockNumberFor, + T::DomainHash, + BalanceOf, + >, OptionQuery, >; @@ -492,7 +494,7 @@ mod pallet { /// The head receipt number of each domain #[pallet::storage] pub(super) type HeadReceiptNumber = - StorageMap<_, Identity, DomainId, T::DomainNumber, ValueQuery>; + StorageMap<_, Identity, DomainId, DomainBlockNumberFor, ValueQuery>; /// State root mapped again each domain (block, hash) /// This acts as an index for other protocols like XDM to fetch state roots faster. @@ -500,7 +502,7 @@ mod pallet { pub(super) type StateRoots = StorageMap< _, Identity, - (DomainId, T::DomainNumber, T::DomainHash), + (DomainId, DomainBlockNumberFor, T::DomainHash), T::DomainHash, OptionQuery, >; @@ -527,7 +529,7 @@ mod pallet { _, ( NMapKey, - NMapKey, + NMapKey>, NMapKey>, ), Vec, @@ -547,7 +549,7 @@ mod pallet { /// domain block, also used as a mapping of consensus block number to domain block number. #[pallet::storage] pub(super) type HeadDomainNumber = - StorageMap<_, Identity, DomainId, T::DomainNumber, ValueQuery>; + StorageMap<_, Identity, DomainId, DomainBlockNumberFor, ValueQuery>; /// A temporary storage to hold any previous epoch details for a given domain /// if the epoch transitioned in this block so that all the submitted bundles @@ -601,6 +603,8 @@ mod pallet { DescendantsOfFraudulentERNotPruned, /// Invalid fraud proof since total rewards are not mismatched. InvalidTotalRewardsFraudProof(sp_domains::verification::VerificationError), + /// Invalid domain block hash fraud proof. + InvalidDomainBlockHashFraudProof(sp_domains::verification::VerificationError), /// Invalid domain extrinsic fraud proof InvalidExtrinsicRootFraudProof(sp_domains::verification::VerificationError), /// Failed to get block randomness. @@ -609,6 +613,8 @@ mod pallet { FailedToGetDomainTimestampExtrinsic, /// Received invalid Verification info from host function. ReceivedInvalidVerificationInfo, + /// Parent receipt not found. + ParentReceiptNotFound, } impl From for Error { @@ -719,7 +725,7 @@ mod pallet { }, FraudProofProcessed { domain_id: DomainId, - new_head_receipt_number: Option, + new_head_receipt_number: Option>, }, DomainOperatorAllowListUpdated { domain_id: DomainId, @@ -1327,13 +1333,13 @@ impl Pallet { .and_then(|mut runtime_object| runtime_object.raw_genesis.take_runtime_code()) } - pub fn domain_best_number(domain_id: DomainId) -> Option { + pub fn domain_best_number(domain_id: DomainId) -> Option> { Some(HeadDomainNumber::::get(domain_id)) } pub fn domain_state_root( domain_id: DomainId, - domain_block_number: T::DomainNumber, + domain_block_number: DomainBlockNumberFor, domain_block_hash: T::DomainHash, ) -> Option { StateRoots::::get((domain_id, domain_block_number, domain_block_hash)) @@ -1361,7 +1367,7 @@ impl Pallet { } pub fn genesis_state_root(domain_id: DomainId) -> Option { - BlockTree::::get(domain_id, T::DomainNumber::zero()) + BlockTree::::get(domain_id, DomainBlockNumberFor::::zero()) .first() .and_then(DomainBlocks::::get) .map(|block| block.execution_receipt.final_state_root.into()) @@ -1529,13 +1535,32 @@ impl Pallet { FraudProof::InvalidTotalRewards(InvalidTotalRewardsProof { storage_proof, .. }) => { verify_invalid_total_rewards_fraud_proof::< T::Block, - T::DomainNumber, + DomainBlockNumberFor, T::DomainHash, BalanceOf, T::Hashing, >(bad_receipt, storage_proof) .map_err(FraudProofError::InvalidTotalRewardsFraudProof)?; } + FraudProof::InvalidDomainBlockHash(InvalidDomainBlockHashProof { + digest_storage_proof, + .. + }) => { + let parent_receipt = + DomainBlocks::::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(FraudProofError::InvalidDomainBlockHashFraudProof)?; + } FraudProof::InvalidExtrinsicsRoot(proof) => { let consensus_block_hash = bad_receipt.consensus_block_hash; let block_randomness = match get_fraud_proof_verification_info( @@ -1564,11 +1589,11 @@ impl Pallet { verify_invalid_domain_extrinsics_root_fraud_proof::< T::Block, - T::DomainNumber, + DomainBlockNumberFor, T::DomainHash, BalanceOf, T::Hashing, - T::DomainHashing, + DomainHashingFor, >( bad_receipt, proof, @@ -1683,17 +1708,17 @@ impl Pallet { } /// Returns the best execution chain number. - pub fn head_receipt_number(domain_id: DomainId) -> T::DomainNumber { + pub fn head_receipt_number(domain_id: DomainId) -> DomainBlockNumberFor { HeadReceiptNumber::::get(domain_id) } /// Returns the block number of oldest execution receipt. - pub fn oldest_receipt_number(domain_id: DomainId) -> T::DomainNumber { + pub fn oldest_receipt_number(domain_id: DomainId) -> DomainBlockNumberFor { Self::head_receipt_number(domain_id).saturating_sub(Self::block_tree_pruning_depth()) } /// Returns the block tree pruning depth. - pub fn block_tree_pruning_depth() -> T::DomainNumber { + pub fn block_tree_pruning_depth() -> DomainBlockNumberFor { T::BlockTreePruningDepth::get() } @@ -1724,7 +1749,7 @@ impl Pallet { /// Returns if there are any ERs in the challenge period that have non empty extrinsics. /// Note that Genesis ER is also considered special and hence non empty pub fn non_empty_er_exists(domain_id: DomainId) -> bool { - if BlockTree::::contains_key(domain_id, T::DomainNumber::zero()) { + if BlockTree::::contains_key(domain_id, DomainBlockNumberFor::::zero()) { return true; } diff --git a/crates/pallet-domains/src/staking_epoch.rs b/crates/pallet-domains/src/staking_epoch.rs index fa52e654d2..6dd5002fed 100644 --- a/crates/pallet-domains/src/staking_epoch.rs +++ b/crates/pallet-domains/src/staking_epoch.rs @@ -8,7 +8,8 @@ use crate::pallet::{ }; use crate::staking::{Error as TransitionError, Nominator, OperatorStatus, Withdraw}; use crate::{ - BalanceOf, Config, ElectionVerificationParams, FungibleHoldId, HoldIdentifier, NominatorId, + BalanceOf, Config, DomainBlockNumberFor, ElectionVerificationParams, FungibleHoldId, + HoldIdentifier, NominatorId, }; use codec::{Decode, Encode}; use frame_support::traits::fungible::{InspectHold, Mutate, MutateHold}; @@ -37,7 +38,7 @@ pub enum Error { /// Returns true of the epoch indeed was finished. pub(crate) fn do_finalize_domain_current_epoch( domain_id: DomainId, - domain_block_number: T::DomainNumber, + domain_block_number: DomainBlockNumberFor, ) -> Result { // Reset pending staking operation count to 0 PendingStakingOperationCount::::set(domain_id, 0); @@ -62,7 +63,7 @@ pub(crate) fn do_finalize_domain_current_epoch( #[cfg(any(not(feature = "runtime-benchmarks"), test))] pub(crate) fn do_unlock_pending_withdrawals( domain_id: DomainId, - domain_block_number: T::DomainNumber, + domain_block_number: DomainBlockNumberFor, ) -> Result<(), Error> { if let Some(operator_ids) = PendingUnlocks::::take((domain_id, domain_block_number)) { PendingOperatorUnlocks::::try_mutate(|unlocking_operator_ids| { @@ -181,7 +182,7 @@ fn switch_operator(operator_id: OperatorId) -> Result<(), TransitionE fn do_finalize_operator_deregistrations( domain_id: DomainId, - domain_block_number: T::DomainNumber, + domain_block_number: DomainBlockNumberFor, ) -> Result<(), Error> { let stake_withdrawal_locking_period = T::StakeWithdrawalLockingPeriod::get(); let unlock_block_number = domain_block_number @@ -297,7 +298,7 @@ fn release_pending_deposits(operator_id: OperatorId) -> Result<(), Tr #[cfg(any(not(feature = "runtime-benchmarks"), test))] fn unlock_nominator_withdrawals( operator_id: OperatorId, - domain_block_number: T::DomainNumber, + domain_block_number: DomainBlockNumberFor, ) -> Result<(), Error> { let pending_unlock_hold_id = T::HoldIdentifier::staking_pending_unlock(operator_id); match PendingNominatorUnlocks::::take(operator_id, domain_block_number) { @@ -338,7 +339,7 @@ pub struct PendingNominatorUnlock { pub(crate) fn do_finalize_domain_staking( domain_id: DomainId, - domain_block_number: T::DomainNumber, + domain_block_number: DomainBlockNumberFor, ) -> Result { DomainStakingSummary::::try_mutate(domain_id, |maybe_stake_summary| { let stake_summary = maybe_stake_summary @@ -381,7 +382,7 @@ pub(crate) fn do_finalize_domain_staking( fn finalize_operator_pending_transfers( operator_id: OperatorId, - domain_block_number: T::DomainNumber, + domain_block_number: DomainBlockNumberFor, ) -> Result, TransitionError> { Operators::::try_mutate(operator_id, |maybe_operator| { let operator = maybe_operator @@ -422,7 +423,7 @@ fn finalize_pending_withdrawals( operator_id: OperatorId, total_stake: &mut BalanceOf, total_shares: &mut T::Share, - domain_block_number: T::DomainNumber, + domain_block_number: DomainBlockNumberFor, ) -> Result<(), TransitionError> { let staked_hold_id = T::HoldIdentifier::staking_staked(operator_id); let pending_unlock_hold_id = T::HoldIdentifier::staking_pending_unlock(operator_id); @@ -477,7 +478,7 @@ fn finalize_nominator_withdrawal( withdraw: Withdraw>, total_stake: &mut BalanceOf, total_shares: &mut T::Share, - unlock_at: T::DomainNumber, + unlock_at: DomainBlockNumberFor, ) -> Result<(), TransitionError> { let (withdrew_stake, withdrew_shares) = match withdraw { Withdraw::All => { diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 193a31bbf3..5d247f65a1 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -2,8 +2,9 @@ use crate::block_tree::DomainBlock; use crate::domain_registry::{DomainConfig, DomainObject}; use crate::{ self as pallet_domains, BalanceOf, BlockTree, BundleError, Config, ConsensusBlockHash, - DomainBlocks, DomainRegistry, ExecutionInbox, ExecutionReceiptOf, FraudProofError, - FungibleHoldId, HeadReceiptNumber, NextDomainId, Operator, OperatorStatus, Operators, + DomainBlockNumberFor, DomainBlocks, DomainRegistry, ExecutionInbox, ExecutionReceiptOf, + FraudProofError, FungibleHoldId, HeadReceiptNumber, NextDomainId, Operator, OperatorStatus, + Operators, }; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::dispatch::RawOrigin; @@ -16,8 +17,8 @@ use sp_core::crypto::Pair; use sp_core::storage::{StateVersion, StorageKey}; use sp_core::{Get, H256, U256}; use sp_domains::fraud_proof::{ - ExtrinsicDigest, FraudProof, InvalidExtrinsicsRootProof, InvalidTotalRewardsProof, - ValidBundleDigest, + ExtrinsicDigest, FraudProof, InvalidDomainBlockHashProof, InvalidExtrinsicsRootProof, + InvalidTotalRewardsProof, ValidBundleDigest, }; use sp_domains::merkle_tree::MerkleTree; use sp_domains::storage::RawGenesis; @@ -31,7 +32,7 @@ use sp_domains_fraud_proof::{ FraudProofVerificationInfoResponse, }; use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, Hash as HashT, IdentityLookup, Zero}; -use sp_runtime::{BuildStorage, OpaqueExtrinsic}; +use sp_runtime::{BuildStorage, Digest, OpaqueExtrinsic}; use sp_state_machine::backend::AsTrieBackend; use sp_state_machine::{prove_read, Backend, TrieBackendBuilder}; use sp_std::sync::Arc; @@ -209,9 +210,8 @@ impl pallet_timestamp::Config for Test { impl pallet_domains::Config for Test { type RuntimeEvent = RuntimeEvent; - type DomainNumber = BlockNumber; type DomainHash = sp_core::H256; - type DomainHashing = BlakeTwo256; + type DomainHeader = sp_runtime::generic::Header; type ConfirmationDepthK = ConfirmationDepthK; type DomainRuntimeUpgradeDelay = DomainRuntimeUpgradeDelay; type Currency = Balances; @@ -491,8 +491,10 @@ pub(crate) fn extend_block_tree( #[allow(clippy::type_complexity)] pub(crate) fn get_block_tree_node_at( domain_id: DomainId, - block_number: T::DomainNumber, -) -> Option, T::Hash, T::DomainNumber, T::DomainHash, BalanceOf>> { + block_number: DomainBlockNumberFor, +) -> Option< + DomainBlock, T::Hash, DomainBlockNumberFor, T::DomainHash, BalanceOf>, +> { BlockTree::::get(domain_id, block_number) .first() .and_then(DomainBlocks::::get) @@ -929,6 +931,52 @@ fn generate_invalid_domain_extrinsic_root_fraud_proof::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(); + DomainBlocks::::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::fraud_proof::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 = 0u64; diff --git a/crates/sp-domains-fraud-proof/src/verification.rs b/crates/sp-domains-fraud-proof/src/verification.rs index 6800803b97..0475875ffe 100644 --- a/crates/sp-domains-fraud-proof/src/verification.rs +++ b/crates/sp-domains-fraud-proof/src/verification.rs @@ -1,14 +1,18 @@ +use codec::Decode; use hash_db::Hasher; +use sp_core::storage::StorageKey; use sp_core::H256; use sp_domains::fraud_proof::{ExtrinsicDigest, InvalidExtrinsicsRootProof}; use sp_domains::valued_trie_root::valued_ordered_trie_root; use sp_domains::verification::{ - deduplicate_and_shuffle_extrinsics, extrinsics_shuffling_seed, VerificationError, + deduplicate_and_shuffle_extrinsics, extrinsics_shuffling_seed, StorageProofVerifier, + VerificationError, }; use sp_domains::ExecutionReceipt; -use sp_runtime::traits::{BlakeTwo256, Block, Hash, NumberFor}; +use sp_runtime::generic::Digest; +use sp_runtime::traits::{BlakeTwo256, Block, Hash, Header as HeaderT, NumberFor}; use sp_std::vec::Vec; -use sp_trie::LayoutV1; +use sp_trie::{LayoutV1, StorageProof}; use subspace_core_primitives::Randomness; use trie_db::node::Value; @@ -85,3 +89,45 @@ where Ok(()) } + +pub fn verify_invalid_domain_block_hash_fraud_proof( + bad_receipt: ExecutionReceipt< + NumberFor, + CBlock::Hash, + DomainHeader::Number, + DomainHeader::Hash, + Balance, + >, + digest_storage_proof: StorageProof, + parent_domain_block_hash: DomainHeader::Hash, +) -> Result<(), VerificationError> +where + CBlock: Block, + Balance: PartialEq + Decode, + DomainHeader: HeaderT, + DomainHeader::Hash: From, +{ + let state_root = bad_receipt.final_state_root; + let digest_storage_key = StorageKey(sp_domains::fraud_proof::system_digest_final_key()); + + let digest = StorageProofVerifier::::verify_and_get_value::( + &state_root, + digest_storage_proof, + digest_storage_key, + ) + .map_err(|_| VerificationError::InvalidProof)?; + + let derived_domain_block_hash = sp_domains::derive_domain_block_hash::( + bad_receipt.domain_block_number, + bad_receipt.domain_block_extrinsic_root.into(), + state_root, + parent_domain_block_hash, + digest, + ); + + if bad_receipt.domain_block_hash == derived_domain_block_hash { + return Err(VerificationError::InvalidProof); + } + + Ok(()) +} diff --git a/crates/sp-domains/src/fraud_proof.rs b/crates/sp-domains/src/fraud_proof.rs index 441d7a5387..655509a4b7 100644 --- a/crates/sp-domains/src/fraud_proof.rs +++ b/crates/sp-domains/src/fraud_proof.rs @@ -242,6 +242,7 @@ pub enum FraudProof { ImproperTransactionSortition(ImproperTransactionSortitionProof), InvalidTotalRewards(InvalidTotalRewardsProof), InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof), + InvalidDomainBlockHash(InvalidDomainBlockHashProof), // Dummy fraud proof only used in test and benchmark #[cfg(any(feature = "std", feature = "runtime-benchmarks"))] Dummy { @@ -265,6 +266,7 @@ impl FraudProof { FraudProof::InvalidTotalRewards(proof) => proof.domain_id(), FraudProof::InvalidBundles(proof) => proof.domain_id(), FraudProof::InvalidExtrinsicsRoot(proof) => proof.domain_id, + FraudProof::InvalidDomainBlockHash(proof) => proof.domain_id, } } @@ -285,6 +287,7 @@ impl FraudProof { // TODO: Remove default value when invalid bundle proofs are fully expanded FraudProof::InvalidBundles(_) => Default::default(), FraudProof::InvalidExtrinsicsRoot(proof) => proof.bad_receipt_hash, + FraudProof::InvalidDomainBlockHash(proof) => proof.bad_receipt_hash, } } @@ -417,6 +420,17 @@ pub struct InvalidTotalRewardsProof { 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, + /// Digests storage proof that is used to derive Domain block hash. + pub digest_storage_proof: StorageProof, +} + /// Represents the extrinsic either as full data or hash of the data. #[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] pub enum ExtrinsicDigest { @@ -477,9 +491,17 @@ impl InvalidTotalRewardsProof { } } +//TODO: remove there key generations from here and instead use the fraud proof host function to fetch them + /// This is a representation of actual Block Rewards storage in pallet-operator-rewards. /// Any change in key or value there should be changed here accordingly. pub fn operator_block_rewards_final_key() -> Vec { frame_support::storage::storage_prefix("OperatorRewards".as_ref(), "BlockRewards".as_ref()) .to_vec() } + +/// 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/src/lib.rs b/crates/sp-domains/src/lib.rs index a905f3fead..60b857dab3 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -45,9 +45,9 @@ use sp_core::sr25519::vrf::{VrfOutput, VrfProof}; use sp_core::H256; use sp_runtime::generic::OpaqueDigestItemId; use sp_runtime::traits::{ - BlakeTwo256, Block as BlockT, CheckedAdd, Hash as HashT, NumberFor, Zero, + BlakeTwo256, Block as BlockT, CheckedAdd, Hash as HashT, Header as HeaderT, NumberFor, Zero, }; -use sp_runtime::{DigestItem, OpaqueExtrinsic, Percent}; +use sp_runtime::{Digest, DigestItem, OpaqueExtrinsic, Percent}; use sp_runtime_interface::pass_by; use sp_runtime_interface::pass_by::PassBy; use sp_std::collections::btree_set::BTreeSet; @@ -431,11 +431,15 @@ impl< BlakeTwo256::hash_of(self) } - pub fn genesis(genesis_state_root: DomainHash) -> Self { + pub fn genesis( + genesis_state_root: DomainHash, + genesis_extrinsic_root: H256, + genesis_domain_block_hash: DomainHash, + ) -> Self { ExecutionReceipt { domain_block_number: Zero::zero(), - domain_block_hash: Default::default(), - domain_block_extrinsic_root: Default::default(), + domain_block_hash: genesis_domain_block_hash, + domain_block_extrinsic_root: genesis_extrinsic_root, parent_domain_block_receipt_hash: Default::default(), consensus_block_hash: Default::default(), consensus_block_number: Zero::zero(), @@ -808,6 +812,24 @@ pub const ZERO_OPERATOR_SIGNING_KEY: sr25519::Public = sr25519::Public(hex!( "0000000000000000000000000000000000000000000000000000000000000000" )); +pub fn derive_domain_block_hash( + domain_block_number: DomainHeader::Number, + extrinsics_root: DomainHeader::Hash, + state_root: DomainHeader::Hash, + parent_domain_block_hash: DomainHeader::Hash, + digest: Digest, +) -> DomainHeader::Hash { + let domain_header = DomainHeader::new( + domain_block_number, + extrinsics_root, + state_root, + parent_domain_block_hash, + digest, + ); + + domain_header.hash() +} + sp_api::decl_runtime_apis! { /// API necessary for domains pallet. pub trait DomainsApi { diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index a81aca07b4..268a9e8dbc 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -621,9 +621,8 @@ parameter_types! { impl pallet_domains::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type DomainNumber = DomainNumber; type DomainHash = DomainHash; - type DomainHashing = BlakeTwo256; + type DomainHeader = sp_runtime::generic::Header; type ConfirmationDepthK = ConfirmationDepthK; type DomainRuntimeUpgradeDelay = DomainRuntimeUpgradeDelay; type Currency = Balances; diff --git a/domains/client/domain-operator/src/aux_schema.rs b/domains/client/domain-operator/src/aux_schema.rs index e972c9fc77..095c6ff940 100644 --- a/domains/client/domain-operator/src/aux_schema.rs +++ b/domains/client/domain-operator/src/aux_schema.rs @@ -254,6 +254,9 @@ pub(super) enum ReceiptMismatchInfo { DomainExtrinsicsRoot { consensus_block_hash: CHash, }, + DomainBlockHash { + consensus_block_hash: CHash, + }, Bundles { mismatch_type: BundleMismatchType, bundle_index: u32, @@ -287,6 +290,9 @@ impl ReceiptMismatchInfo { ReceiptMismatchInfo::DomainExtrinsicsRoot { consensus_block_hash, } => consensus_block_hash.clone(), + ReceiptMismatchInfo::DomainBlockHash { + consensus_block_hash, + } => consensus_block_hash.clone(), } } } diff --git a/domains/client/domain-operator/src/domain_block_processor.rs b/domains/client/domain-operator/src/domain_block_processor.rs index 7749030ce7..8ced864172 100644 --- a/domains/client/domain-operator/src/domain_block_processor.rs +++ b/domains/client/domain-operator/src/domain_block_processor.rs @@ -352,7 +352,11 @@ where "Domain block header for #{genesis_hash:?} not found", )) })?; - ExecutionReceipt::genesis(*genesis_header.state_root()) + ExecutionReceipt::genesis( + *genesis_header.state_root(), + (*genesis_header.extrinsics_root()).into(), + genesis_hash, + ) } else { crate::load_execution_receipt_by_domain_hash::( &*self.client, @@ -808,6 +812,16 @@ where }, )); } + + if execution_receipt.domain_block_hash != local_receipt.domain_block_hash { + bad_receipts_to_write.push(( + execution_receipt.consensus_block_number, + execution_receipt.hash(), + ReceiptMismatchInfo::DomainBlockHash { + consensus_block_hash, + }, + )); + } } let bad_receipts_to_delete = fraud_proofs @@ -917,6 +931,18 @@ where "Failed to generate invalid block rewards fraud proof: {err}" ))) })?, + ReceiptMismatchInfo::DomainBlockHash { .. } => self + .fraud_proof_generator + .generate_invalid_domain_block_hash_proof::( + self.domain_id, + &local_receipt, + bad_receipt_hash, + ) + .map_err(|err| { + sp_blockchain::Error::Application(Box::from(format!( + "Failed to generate invalid domain block hash fraud proof: {err}" + ))) + })?, ReceiptMismatchInfo::Bundles { mismatch_type, bundle_index, diff --git a/domains/client/domain-operator/src/domain_bundle_producer.rs b/domains/client/domain-operator/src/domain_bundle_producer.rs index 4881f2d42c..cc211ea07f 100644 --- a/domains/client/domain-operator/src/domain_bundle_producer.rs +++ b/domains/client/domain-operator/src/domain_bundle_producer.rs @@ -9,6 +9,7 @@ use sc_client_api::{AuxStore, BlockBackend}; use sp_api::{NumberFor, ProvideRuntimeApi}; use sp_block_builder::BlockBuilder; use sp_blockchain::{HashAndNumber, HeaderBackend}; +use sp_core::H256; use sp_domains::{ Bundle, BundleProducerElectionApi, DomainId, DomainsApi, OperatorPublicKey, OperatorSignature, SealedBundleHeader, @@ -95,6 +96,7 @@ impl where Block: BlockT, + Block::Hash: Into, CBlock: BlockT, ParentChainBlock: BlockT, NumberFor: Into>, diff --git a/domains/client/domain-operator/src/domain_bundle_proposer.rs b/domains/client/domain-operator/src/domain_bundle_proposer.rs index 7be3c95009..72758b9d88 100644 --- a/domains/client/domain-operator/src/domain_bundle_proposer.rs +++ b/domains/client/domain-operator/src/domain_bundle_proposer.rs @@ -8,6 +8,7 @@ use sc_transaction_pool_api::InPoolTransaction; use sp_api::{HeaderT, NumberFor, ProvideRuntimeApi}; use sp_block_builder::BlockBuilder; use sp_blockchain::HeaderBackend; +use sp_core::H256; use sp_domains::{BundleHeader, ExecutionReceipt, ProofOfElection}; use sp_runtime::traits::{BlakeTwo256, Block as BlockT, Hash as HashT, One, Zero}; use sp_weights::Weight; @@ -53,6 +54,7 @@ impl where Block: BlockT, CBlock: BlockT, + Block::Hash: Into, NumberFor: Into>, Client: HeaderBackend + BlockBackend + AuxStore + ProvideRuntimeApi, Client::Api: BlockBuilder + DomainCoreApi, @@ -194,7 +196,12 @@ where "Domain block header for #{genesis_hash:?} not found", )) })?; - return Ok(ExecutionReceipt::genesis(*genesis_header.state_root())); + + return Ok(ExecutionReceipt::genesis( + *genesis_header.state_root(), + (*genesis_header.extrinsics_root()).into(), + genesis_hash, + )); } // Get the domain block hash corresponding to `receipt_number` in the domain canonical chain diff --git a/domains/client/domain-operator/src/fraud_proof.rs b/domains/client/domain-operator/src/fraud_proof.rs index 99140456c3..0bbb1756b1 100644 --- a/domains/client/domain-operator/src/fraud_proof.rs +++ b/domains/client/domain-operator/src/fraud_proof.rs @@ -12,8 +12,9 @@ use sp_core::traits::CodeExecutor; use sp_core::H256; use sp_domains::fraud_proof::{ ExecutionPhase, ExtrinsicDigest, FraudProof, InvalidBundlesFraudProof, - InvalidExtrinsicsRootProof, InvalidStateTransitionProof, InvalidTotalRewardsProof, - MissingInvalidBundleEntryFraudProof, ValidAsInvalidBundleEntryFraudProof, ValidBundleDigest, + InvalidDomainBlockHashProof, InvalidExtrinsicsRootProof, InvalidStateTransitionProof, + InvalidTotalRewardsProof, MissingInvalidBundleEntryFraudProof, + ValidAsInvalidBundleEntryFraudProof, ValidBundleDigest, }; use sp_domains::{DomainId, DomainsApi}; use sp_runtime::traits::{BlakeTwo256, Block as BlockT, HashingFor, Header as HeaderT, NumberFor}; @@ -122,6 +123,29 @@ where })) } + pub(crate) fn generate_invalid_domain_block_hash_proof( + &self, + domain_id: DomainId, + local_receipt: &ExecutionReceiptFor, + bad_receipt_hash: H256, + ) -> Result, PCB::Hash>, FraudProofError> + where + PCB: BlockT, + { + let block_hash = local_receipt.domain_block_hash; + let digest_key = sp_domains::fraud_proof::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, + }, + )) + } + pub(crate) fn generate_invalid_bundle_field_proof( &self, domain_id: DomainId, diff --git a/domains/client/domain-operator/src/tests.rs b/domains/client/domain-operator/src/tests.rs index 064ce59bcf..063a6100b7 100644 --- a/domains/client/domain-operator/src/tests.rs +++ b/domains/client/domain-operator/src/tests.rs @@ -18,8 +18,8 @@ use sp_core::traits::FetchRuntimeCode; use sp_core::Pair; use sp_domain_digests::AsPredigest; use sp_domains::fraud_proof::{ - ExecutionPhase, FraudProof, InvalidExtrinsicsRootProof, InvalidStateTransitionProof, - InvalidTotalRewardsProof, + ExecutionPhase, FraudProof, InvalidDomainBlockHashProof, InvalidExtrinsicsRootProof, + InvalidStateTransitionProof, InvalidTotalRewardsProof, }; use sp_domains::transaction::InvalidTransactionCode; use sp_domains::{Bundle, DomainId, DomainsApi}; @@ -973,6 +973,120 @@ async fn test_invalid_total_rewards_proof_creation() { ferdie.produce_blocks(1).await.unwrap(); } +#[tokio::test(flavor = "multi_thread")] +#[ignore] +async fn test_invalid_domain_block_hash_proof_creation() { + let directory = TempDir::new().expect("Must be able to create temporary directory"); + + let mut builder = sc_cli::LoggerBuilder::new(""); + builder.with_colors(false); + let _ = builder.init(); + + let tokio_handle = tokio::runtime::Handle::current(); + + // Start Ferdie + let mut ferdie = MockConsensusNode::run( + tokio_handle.clone(), + Ferdie, + BasePath::new(directory.path().join("ferdie")), + ); + // Produce 1 consensus block to initialize genesis domain + ferdie.produce_block_with_slot(1.into()).await.unwrap(); + + // Run Alice (a evm domain authority node) + let mut alice = domain_test_service::DomainNodeBuilder::new( + tokio_handle.clone(), + Alice, + BasePath::new(directory.path().join("alice")), + ) + .build_evm_node(Role::Authority, GENESIS_DOMAIN_ID, &mut ferdie) + .await; + + let bundle_to_tx = |opaque_bundle| { + subspace_test_runtime::UncheckedExtrinsic::new_unsigned( + pallet_domains::Call::submit_bundle { opaque_bundle }.into(), + ) + .into() + }; + + produce_blocks!(ferdie, alice, 5).await.unwrap(); + + alice + .construct_and_send_extrinsic(pallet_balances::Call::transfer_allow_death { + dest: Bob.to_account_id(), + value: 1, + }) + .await + .expect("Failed to send extrinsic"); + + // Produce a bundle that contains the previously sent extrinsic and record that bundle for later use + let (slot, bundle) = ferdie.produce_slot_and_wait_for_bundle_submission().await; + let target_bundle = bundle.unwrap(); + assert_eq!(target_bundle.extrinsics.len(), 1); + produce_block_with!(ferdie.produce_block_with_slot(slot), alice) + .await + .unwrap(); + + // Get a bundle from the txn pool and modify the receipt of the target bundle to an invalid one + let (slot, bundle) = ferdie.produce_slot_and_wait_for_bundle_submission().await; + let original_submit_bundle_tx = bundle_to_tx(bundle.clone().unwrap()); + let bad_submit_bundle_tx = { + let mut opaque_bundle = bundle.unwrap(); + let receipt = &mut opaque_bundle.sealed_header.header.receipt; + receipt.domain_block_hash = Default::default(); + opaque_bundle.sealed_header.signature = Sr25519Keyring::Alice + .pair() + .sign(opaque_bundle.sealed_header.pre_hash().as_ref()) + .into(); + bundle_to_tx(opaque_bundle) + }; + + // Replace `original_submit_bundle_tx` with `bad_submit_bundle_tx` in the tx pool + ferdie + .prune_tx_from_pool(&original_submit_bundle_tx) + .await + .unwrap(); + assert!(ferdie.get_bundle_from_tx_pool(slot.into()).is_none()); + + ferdie + .submit_transaction(bad_submit_bundle_tx) + .await + .unwrap(); + + // Produce a consensus block that contains the `bad_submit_bundle_tx` + let mut import_tx_stream = ferdie.transaction_pool.import_notification_stream(); + produce_block_with!(ferdie.produce_block_with_slot(slot), alice) + .await + .unwrap(); + + // When the domain node operator process the primary block that contains the `bad_submit_bundle_tx`, + // it will generate and submit a fraud proof + while let Some(ready_tx_hash) = import_tx_stream.next().await { + let ready_tx = ferdie + .transaction_pool + .ready_transaction(&ready_tx_hash) + .unwrap(); + let ext = subspace_test_runtime::UncheckedExtrinsic::decode( + &mut ready_tx.data.encode().as_slice(), + ) + .unwrap(); + if let subspace_test_runtime::RuntimeCall::Domains( + pallet_domains::Call::submit_fraud_proof { fraud_proof }, + ) = ext.function + { + if let FraudProof::InvalidDomainBlockHash(InvalidDomainBlockHashProof { .. }) = + *fraud_proof + { + break; + } + } + } + + // Produce a consensus block that contains the fraud proof, the fraud proof wil be verified on + // on the runtime itself + ferdie.produce_blocks(1).await.unwrap(); +} + #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_invalid_domain_extrinsics_root_proof_creation() { diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 46038d7ea0..b6e71d6afc 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -645,9 +645,8 @@ parameter_types! { impl pallet_domains::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type DomainNumber = DomainNumber; type DomainHash = DomainHash; - type DomainHashing = BlakeTwo256; + type DomainHeader = sp_runtime::generic::Header; type ConfirmationDepthK = ConfirmationDepthK; type DomainRuntimeUpgradeDelay = DomainRuntimeUpgradeDelay; type Currency = Balances;