From 062075523a6c4202619d2b061446bc31b4be5efb Mon Sep 17 00:00:00 2001 From: vedhavyas Date: Sun, 17 Sep 2023 18:55:36 +0200 Subject: [PATCH 1/8] generate fraud proof for invalid domain extrinsics root --- Cargo.lock | 1 + crates/sp-domains/Cargo.toml | 48 +++++---- crates/sp-domains/src/fraud_proof.rs | 29 +++++ .../domain-operator/src/bundle_processor.rs | 1 + .../src/domain_block_processor.rs | 6 +- .../src/domain_worker_starter.rs | 1 + .../client/domain-operator/src/fraud_proof.rs | 102 ++++++++++++++++-- .../client/domain-operator/src/operator.rs | 1 + 8 files changed, 157 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 179fc80a44..80b887c10c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10831,6 +10831,7 @@ name = "sp-domains" version = "0.1.0" dependencies = [ "blake2", + "domain-runtime-primitives", "frame-support", "hash-db 0.16.0", "hexlit", diff --git a/crates/sp-domains/Cargo.toml b/crates/sp-domains/Cargo.toml index dfbe56a604..d65844e2ac 100644 --- a/crates/sp-domains/Cargo.toml +++ b/crates/sp-domains/Cargo.toml @@ -13,6 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] blake2 = { version = "0.10.6", default-features = false } +domain-runtime-primitives = { version = "0.1.0", default-features = false, path = "../../domains/primitives/runtime" } frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } hash-db = { version = "0.16.0", default-features = false } hexlit = "0.5.5" @@ -43,28 +44,29 @@ num-traits = "0.2.16" [features] default = ["std"] std = [ - "blake2/std", - "frame-support/std", - "hash-db/std", - "parity-scale-codec/std", - "rs_merkle/std", - "scale-info/std", - "serde/std", - "sp-api/std", - "sp-blockchain", - "sp-application-crypto/std", - "sp-consensus-slots/std", - "sp-core/std", - "sp-externalities/std", - "sp-keystore", - "sp-runtime/std", - "sp-runtime-interface/std", - "sp-state-machine/std", - "sp-std/std", - "sp-trie/std", - "sp-weights/std", - "subspace-core-primitives/std", - "subspace-runtime-primitives/std", - "thiserror", + "blake2/std", + "domain-runtime-primitives/std", + "frame-support/std", + "hash-db/std", + "parity-scale-codec/std", + "rs_merkle/std", + "scale-info/std", + "serde/std", + "sp-api/std", + "sp-blockchain", + "sp-application-crypto/std", + "sp-consensus-slots/std", + "sp-core/std", + "sp-externalities/std", + "sp-keystore", + "sp-runtime/std", + "sp-runtime-interface/std", + "sp-state-machine/std", + "sp-std/std", + "sp-trie/std", + "sp-weights/std", + "subspace-core-primitives/std", + "subspace-runtime-primitives/std", + "thiserror", ] runtime-benchmarks = [] diff --git a/crates/sp-domains/src/fraud_proof.rs b/crates/sp-domains/src/fraud_proof.rs index 8d462ae690..a76e9d06d1 100644 --- a/crates/sp-domains/src/fraud_proof.rs +++ b/crates/sp-domains/src/fraud_proof.rs @@ -239,6 +239,7 @@ pub enum FraudProof { BundleEquivocation(BundleEquivocationProof), ImproperTransactionSortition(ImproperTransactionSortitionProof), InvalidTotalRewards(InvalidTotalRewardsProof), + InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof), // Dummy fraud proof only used in test and benchmark #[cfg(any(feature = "std", feature = "runtime-benchmarks"))] Dummy { @@ -261,6 +262,7 @@ impl FraudProof { Self::Dummy { domain_id, .. } => *domain_id, FraudProof::InvalidTotalRewards(proof) => proof.domain_id(), FraudProof::InvalidBundles(proof) => proof.domain_id(), + FraudProof::InvalidExtrinsicsRoot(proof) => proof.domain_id, } } @@ -280,6 +282,7 @@ impl FraudProof { FraudProof::InvalidTotalRewards(proof) => proof.bad_receipt_hash(), // TODO: Remove default value when invalid bundle proofs are fully expanded FraudProof::InvalidBundles(_) => Default::default(), + FraudProof::InvalidExtrinsicsRoot(proof) => proof.bad_receipt_hash, } } @@ -411,6 +414,26 @@ pub struct InvalidTotalRewardsProof { pub storage_proof: StorageProof, } +#[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, H256)>, +} + +#[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, + /// Randomness Storage proof + pub randomness_proof: StorageProof, +} + impl InvalidTotalRewardsProof { pub(crate) fn domain_id(&self) -> DomainId { self.domain_id @@ -427,3 +450,9 @@ pub fn operator_block_rewards_final_key() -> Vec { frame_support::storage::storage_prefix("OperatorRewards".as_ref(), "BlockRewards".as_ref()) .to_vec() } + +/// This is a representation of actual Randomness on Consensus chain state. +/// Any change in key or value there should be changed here accordingly. +pub fn block_randomness_final_key() -> Vec { + frame_support::storage::storage_prefix("Subspace".as_ref(), "BlockRandomness".as_ref()).to_vec() +} diff --git a/domains/client/domain-operator/src/bundle_processor.rs b/domains/client/domain-operator/src/bundle_processor.rs index c6e0f55648..4e10ba4901 100644 --- a/domains/client/domain-operator/src/bundle_processor.rs +++ b/domains/client/domain-operator/src/bundle_processor.rs @@ -148,6 +148,7 @@ where CClient: HeaderBackend + HeaderMetadata + BlockBackend + + ProofProvider + ProvideRuntimeApi + 'static, CClient::Api: DomainsApi, Block::Hash> diff --git a/domains/client/domain-operator/src/domain_block_processor.rs b/domains/client/domain-operator/src/domain_block_processor.rs index da55ed2e4c..9aea5ac495 100644 --- a/domains/client/domain-operator/src/domain_block_processor.rs +++ b/domains/client/domain-operator/src/domain_block_processor.rs @@ -686,7 +686,11 @@ where + 'static, Client::Api: DomainCoreApi + sp_block_builder::BlockBuilder + sp_api::ApiExt, - CClient: HeaderBackend + BlockBackend + ProvideRuntimeApi + 'static, + CClient: HeaderBackend + + BlockBackend + + ProofProvider + + ProvideRuntimeApi + + 'static, CClient::Api: DomainsApi, Block::Hash>, Backend: sc_client_api::Backend + 'static, E: CodeExecutor, diff --git a/domains/client/domain-operator/src/domain_worker_starter.rs b/domains/client/domain-operator/src/domain_worker_starter.rs index a77cdf50b7..975fe54865 100644 --- a/domains/client/domain-operator/src/domain_worker_starter.rs +++ b/domains/client/domain-operator/src/domain_worker_starter.rs @@ -88,6 +88,7 @@ pub(super) async fn start_worker< CClient: HeaderBackend + HeaderMetadata + BlockBackend + + ProofProvider + ProvideRuntimeApi + BlockchainEvents + 'static, diff --git a/domains/client/domain-operator/src/fraud_proof.rs b/domains/client/domain-operator/src/fraud_proof.rs index fb6177a60b..661fe2d2ff 100644 --- a/domains/client/domain-operator/src/fraud_proof.rs +++ b/domains/client/domain-operator/src/fraud_proof.rs @@ -3,18 +3,20 @@ use crate::utils::to_number_primitive; use crate::ExecutionReceiptFor; use codec::{Decode, Encode}; use domain_block_builder::{BlockBuilder, RecordProof}; +use domain_runtime_primitives::opaque::AccountId; +use domain_runtime_primitives::DomainCoreApi; use sc_client_api::{AuxStore, BlockBackend, ProofProvider}; -use sp_api::ProvideRuntimeApi; +use sp_api::{HashT, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; use sp_core::traits::CodeExecutor; use sp_core::H256; use sp_domains::fraud_proof::{ - ExecutionPhase, FraudProof, InvalidBundlesFraudProof, InvalidStateTransitionProof, - InvalidTotalRewardsProof, MissingInvalidBundleEntryFraudProof, - ValidAsInvalidBundleEntryFraudProof, + ExecutionPhase, FraudProof, InvalidBundlesFraudProof, InvalidExtrinsicsRootProof, + InvalidStateTransitionProof, InvalidTotalRewardsProof, MissingInvalidBundleEntryFraudProof, + ValidAsInvalidBundleEntryFraudProof, ValidBundleDigest, }; -use sp_domains::DomainId; -use sp_runtime::traits::{Block as BlockT, HashingFor, Header as HeaderT, NumberFor}; +use sp_domains::{DomainId, DomainsApi}; +use sp_runtime::traits::{BlakeTwo256, Block as BlockT, HashingFor, Header as HeaderT, NumberFor}; use sp_runtime::Digest; use sp_trie::StorageProof; use std::marker::PhantomData; @@ -34,6 +36,10 @@ pub enum FraudProofError { Blockchain(#[from] sp_blockchain::Error), #[error(transparent)] RuntimeApi(#[from] sp_api::ApiError), + #[error("Missing bundle at {bundle_index}")] + MissingBundle { bundle_index: usize }, + #[error("Invalid bundle extrinsic at bundle index[{bundle_index}]")] + InvalidBundleExtrinsic { bundle_index: usize }, } pub struct FraudProofGenerator { @@ -69,8 +75,14 @@ where + ProvideRuntimeApi + ProofProvider + 'static, - Client::Api: sp_block_builder::BlockBuilder + sp_api::ApiExt, - CClient: HeaderBackend + 'static, + Client::Api: + sp_block_builder::BlockBuilder + sp_api::ApiExt + DomainCoreApi, + CClient: HeaderBackend + + BlockBackend + + ProvideRuntimeApi + + ProofProvider + + 'static, + CClient::Api: DomainsApi, Block::Hash>, Backend: sc_client_api::Backend + Send + Sync + 'static, E: CodeExecutor, { @@ -138,6 +150,80 @@ where } } + #[allow(dead_code)] + pub(crate) fn generate_invalid_domain_extrinsics_root_proof( + &self, + domain_id: DomainId, + local_receipt: &ExecutionReceiptFor, + bad_receipt_hash: H256, + ) -> Result, PCB::Hash>, FraudProofError> + where + PCB: BlockT, + { + let consensus_block_hash = local_receipt.consensus_block_hash; + let consensus_extrinsics = self + .consensus_client + .block_body(consensus_block_hash)? + .ok_or_else(|| { + sp_blockchain::Error::Backend(format!( + "BlockBody of {consensus_block_hash:?} unavailable" + )) + })?; + + let bundles = self + .consensus_client + .runtime_api() + .extract_successful_bundles(consensus_block_hash, domain_id, consensus_extrinsics)?; + + let mut valid_bundle_digests = Vec::with_capacity(local_receipt.valid_bundles.len()); + for valid_bundle in local_receipt.valid_bundles.iter() { + let bundle_index = valid_bundle.bundle_index; + let bundle = + bundles + .get(bundle_index as usize) + .ok_or(FraudProofError::MissingBundle { + bundle_index: bundle_index as usize, + })?; + + let mut exts = Vec::with_capacity(bundle.extrinsics.len()); + for opaque_extrinsic in &bundle.extrinsics { + let extrinsic = <::Extrinsic>::decode( + &mut opaque_extrinsic.encode().as_slice(), + ) + .map_err(|_| FraudProofError::InvalidBundleExtrinsic { + bundle_index: bundle_index as usize, + })?; + + exts.push(extrinsic) + } + + let domain_runtime_api = self.client.runtime_api(); + let bundle_digest = domain_runtime_api + .extract_signer(local_receipt.domain_block_hash, exts)? + .into_iter() + .map(|(signer, ext)| (signer, BlakeTwo256::hash_of(&ext))) + .collect::, H256)>>(); + valid_bundle_digests.push(ValidBundleDigest { + bundle_index, + bundle_digest, + }); + } + + let key = sp_domains::fraud_proof::block_randomness_final_key(); + let randomness_proof = self + .consensus_client + .read_proof(consensus_block_hash, &mut [key.as_slice()].into_iter())?; + + Ok(FraudProof::InvalidExtrinsicsRoot( + InvalidExtrinsicsRootProof { + domain_id, + bad_receipt_hash, + valid_bundle_digests, + randomness_proof, + }, + )) + } + pub(crate) fn generate_invalid_state_transition_proof( &self, domain_id: DomainId, diff --git a/domains/client/domain-operator/src/operator.rs b/domains/client/domain-operator/src/operator.rs index 84d68886d4..91f71a78b1 100644 --- a/domains/client/domain-operator/src/operator.rs +++ b/domains/client/domain-operator/src/operator.rs @@ -79,6 +79,7 @@ where + HeaderMetadata + BlockBackend + ProvideRuntimeApi + + ProofProvider + BlockchainEvents + Send + Sync From aec3b9589f4dd71b509cec19693b623098f8e7e2 Mon Sep 17 00:00:00 2001 From: vedhavyas Date: Sun, 17 Sep 2023 20:19:26 +0200 Subject: [PATCH 2/8] submit invalid domain extrinsics root proof --- .../client/domain-operator/src/aux_schema.rs | 6 + .../src/domain_block_processor.rs | 26 ++++ domains/client/domain-operator/src/tests.rs | 117 +++++++++++++++++- 3 files changed, 148 insertions(+), 1 deletion(-) diff --git a/domains/client/domain-operator/src/aux_schema.rs b/domains/client/domain-operator/src/aux_schema.rs index 3ed21d60b3..ca143f78bc 100644 --- a/domains/client/domain-operator/src/aux_schema.rs +++ b/domains/client/domain-operator/src/aux_schema.rs @@ -246,6 +246,9 @@ pub(super) enum ReceiptMismatchInfo { trace_index: u32, consensus_block_hash: CHash, }, + DomainExtrinsicsRoot { + consensus_block_hash: CHash, + }, InvalidBundles { mismatch_type: InvalidBundlesMismatchType, bundle_index: u32, @@ -276,6 +279,9 @@ impl ReceiptMismatchInfo { consensus_block_hash, .. } => consensus_block_hash.clone(), + ReceiptMismatchInfo::DomainExtrinsicsRoot { + 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 9aea5ac495..9882ee8d01 100644 --- a/domains/client/domain-operator/src/domain_block_processor.rs +++ b/domains/client/domain-operator/src/domain_block_processor.rs @@ -775,6 +775,20 @@ where execution_receipt.hash(), receipt_mismatch_info, )); + + continue; + } + + if execution_receipt.domain_block_extrinsic_root + != local_receipt.domain_block_extrinsic_root + { + bad_receipts_to_write.push(( + execution_receipt.consensus_block_number, + execution_receipt.hash(), + ReceiptMismatchInfo::DomainExtrinsicsRoot { + consensus_block_hash, + }, + )); continue; } @@ -926,6 +940,18 @@ where "Failed to generate invalid bundles field fraud proof: {err}" ))) })?, + ReceiptMismatchInfo::DomainExtrinsicsRoot { .. } => self + .fraud_proof_generator + .generate_invalid_domain_extrinsics_root_proof::( + self.domain_id, + &local_receipt, + bad_receipt_hash, + ) + .map_err(|err| { + sp_blockchain::Error::Application(Box::from(format!( + "Failed to generate invalid domain extrinsics root fraud proof: {err}" + ))) + })?, }; return Ok(Some(fraud_proof)); diff --git a/domains/client/domain-operator/src/tests.rs b/domains/client/domain-operator/src/tests.rs index 714fb54560..621e5de2ca 100644 --- a/domains/client/domain-operator/src/tests.rs +++ b/domains/client/domain-operator/src/tests.rs @@ -18,7 +18,8 @@ use sp_core::traits::FetchRuntimeCode; use sp_core::Pair; use sp_domain_digests::AsPredigest; use sp_domains::fraud_proof::{ - ExecutionPhase, FraudProof, InvalidStateTransitionProof, InvalidTotalRewardsProof, + ExecutionPhase, FraudProof, InvalidExtrinsicsRootProof, InvalidStateTransitionProof, + InvalidTotalRewardsProof, }; use sp_domains::transaction::InvalidTransactionCode; use sp_domains::{Bundle, DomainId, DomainsApi}; @@ -999,6 +1000,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_extrinsics_root_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_extrinsic_root = 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::InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof { .. }) = + *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 fraud_proof_verification_in_tx_pool_should_work() { From f71847e00ed639b119e802f3e80716abd5c7fb6a Mon Sep 17 00:00:00 2001 From: vedhavyas Date: Sun, 17 Sep 2023 20:36:50 +0200 Subject: [PATCH 3/8] move total rewards fraud proof verification to domain primitives --- crates/pallet-domains/src/lib.rs | 31 +++++------------- crates/sp-domains/src/verification.rs | 47 +++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index df40376b95..bc5d14d7f3 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -41,11 +41,10 @@ use frame_system::offchain::SubmitTransaction; use frame_system::pallet_prelude::*; pub use pallet::*; use scale_info::TypeInfo; -use sp_core::storage::StorageKey; use sp_core::H256; use sp_domains::bundle_producer_election::{is_below_threshold, BundleProducerElectionParams}; use sp_domains::fraud_proof::{FraudProof, InvalidTotalRewardsProof}; -use sp_domains::verification::StorageProofVerifier; +use sp_domains::verification::verify_invalid_total_rewards_fraud_proof; use sp_domains::{ DomainBlockLimit, DomainId, DomainInstanceData, ExecutionReceipt, OpaqueBundle, OperatorId, OperatorPublicKey, ProofOfElection, RuntimeId, EMPTY_EXTRINSIC_ROOT, @@ -580,12 +579,8 @@ mod pallet { ChallengingGenesisReceipt, /// The descendants of the fraudulent ER is not pruned DescendantsOfFraudulentERNotPruned, - /// Proof of total rewards is invalid. - InvalidTotalRewardsProof, /// Invalid fraud proof since total rewards are not mismatched. - InvalidTotalRewardsFraudProof, - /// Invalid state root. - FailedToDecodeDomainBlockHash, + InvalidTotalRewardsFraudProof(sp_domains::verification::VerificationError), } impl From for Error { @@ -1497,22 +1492,14 @@ impl Pallet { if let FraudProof::InvalidTotalRewards(InvalidTotalRewardsProof { storage_proof, .. }) = fraud_proof { - let state_root = bad_receipt.final_state_root.encode(); - let state_root = T::Hash::decode(&mut state_root.as_slice()) - .map_err(|_| FraudProofError::FailedToDecodeDomainBlockHash)?; - let storage_key = - StorageKey(sp_domains::fraud_proof::operator_block_rewards_final_key()); - let storage_proof = storage_proof.clone(); - - let total_rewards = StorageProofVerifier::::verify_and_get_value::< + verify_invalid_total_rewards_fraud_proof::< + T::Block, + T::DomainNumber, + T::DomainHash, BalanceOf, - >(&state_root, storage_proof, storage_key) - .map_err(|_| FraudProofError::InvalidTotalRewardsProof)?; - - // if the rewards matches, then this is an invalid fraud proof since rewards must be different. - if bad_receipt.total_rewards == total_rewards { - return Err(FraudProofError::InvalidTotalRewardsFraudProof); - } + T::Hashing, + >(bad_receipt, storage_proof) + .map_err(FraudProofError::InvalidTotalRewardsFraudProof)?; } Ok(()) diff --git a/crates/sp-domains/src/verification.rs b/crates/sp-domains/src/verification.rs index 6185e72474..c3211fdc4a 100644 --- a/crates/sp-domains/src/verification.rs +++ b/crates/sp-domains/src/verification.rs @@ -1,16 +1,16 @@ +use crate::ExecutionReceipt; use frame_support::PalletError; use hash_db::Hasher; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_core::storage::StorageKey; +use sp_runtime::traits::{Block, NumberFor}; use sp_std::marker::PhantomData; use sp_trie::{read_trie_value, LayoutV1, StorageProof}; /// Verification error. #[derive(Debug, PartialEq, Eq, Encode, Decode, PalletError, TypeInfo)] pub enum VerificationError { - /// Emits when the expected state root doesn't exist - InvalidStateRoot, /// Emits when the given storage proof is invalid. InvalidProof, /// Value doesn't exist in the Db for the given key. @@ -37,3 +37,46 @@ impl StorageProofVerifier { Ok(decoded) } } + +pub fn verify_invalid_total_rewards_fraud_proof< + CBlock, + DomainNumber, + DomainHash, + Balance, + Hashing, +>( + bad_receipt: ExecutionReceipt< + NumberFor, + CBlock::Hash, + DomainNumber, + DomainHash, + Balance, + >, + storage_proof: &StorageProof, +) -> Result<(), VerificationError> +where + CBlock: Block, + Balance: PartialEq + Decode, + Hashing: Hasher, + DomainHash: Encode, +{ + let state_root = bad_receipt.final_state_root.encode(); + let state_root = CBlock::Hash::decode(&mut state_root.as_slice()) + .map_err(|_| VerificationError::FailedToDecode)?; + let storage_key = StorageKey(crate::fraud_proof::operator_block_rewards_final_key()); + let storage_proof = storage_proof.clone(); + + let total_rewards = StorageProofVerifier::::verify_and_get_value::( + &state_root, + storage_proof, + storage_key, + ) + .map_err(|_| VerificationError::InvalidProof)?; + + // if the rewards matches, then this is an invalid fraud proof since rewards must be different. + if bad_receipt.total_rewards == total_rewards { + return Err(VerificationError::InvalidProof); + } + + Ok(()) +} From a0f3ef715a52240899c3477127f0f798499bc87d Mon Sep 17 00:00:00 2001 From: vedhavyas Date: Thu, 21 Sep 2023 16:49:56 +0100 Subject: [PATCH 4/8] add trie root to derive extrinsics root using value and add infra to verify the invalid domain extrinsincs block root. trie root derivation was taken from trie-db with necessary modifications to allow for generating root using just hash of extrinsics. --- Cargo.lock | 3 + crates/pallet-domains/src/lib.rs | 4 +- crates/sp-domains/Cargo.toml | 7 + crates/sp-domains/src/fraud_proof.rs | 34 +- crates/sp-domains/src/lib.rs | 6 +- crates/sp-domains/src/valued_trie_root.rs | 292 ++++++++++++++++++ crates/sp-domains/src/verification.rs | 173 ++++++++++- domains/client/block-preprocessor/src/lib.rs | 89 +----- .../domain-operator/src/bundle_processor.rs | 2 + .../src/domain_block_processor.rs | 4 +- .../src/domain_worker_starter.rs | 2 + .../client/domain-operator/src/fraud_proof.rs | 19 +- .../client/domain-operator/src/operator.rs | 2 + 13 files changed, 539 insertions(+), 98 deletions(-) create mode 100644 crates/sp-domains/src/valued_trie_root.rs diff --git a/Cargo.lock b/Cargo.lock index 80b887c10c..ad4c6c30ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10837,6 +10837,8 @@ dependencies = [ "hexlit", "num-traits", "parity-scale-codec", + "rand 0.8.5", + "rand_chacha 0.3.1", "rs_merkle", "scale-info", "serde", @@ -10856,6 +10858,7 @@ dependencies = [ "subspace-core-primitives", "subspace-runtime-primitives", "thiserror", + "trie-db", ] [[package]] diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index bc5d14d7f3..74569b1764 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -47,7 +47,7 @@ use sp_domains::fraud_proof::{FraudProof, InvalidTotalRewardsProof}; use sp_domains::verification::verify_invalid_total_rewards_fraud_proof; use sp_domains::{ DomainBlockLimit, DomainId, DomainInstanceData, ExecutionReceipt, OpaqueBundle, OperatorId, - OperatorPublicKey, ProofOfElection, RuntimeId, EMPTY_EXTRINSIC_ROOT, + OperatorPublicKey, ProofOfElection, RuntimeId, EMPTY_EXTRINSIC_ROOT, EXTRINSICS_SHUFFLING_SEED, }; use sp_runtime::traits::{BlakeTwo256, CheckedSub, Hash, One, Zero}; use sp_runtime::{RuntimeAppPublic, SaturatedConversion, Saturating}; @@ -1678,7 +1678,7 @@ impl Pallet { } pub fn extrinsics_shuffling_seed() -> T::Hash { - let seed: &[u8] = b"extrinsics-shuffling-seed"; + let seed = EXTRINSICS_SHUFFLING_SEED; let (randomness, _) = T::Randomness::random(seed); randomness } diff --git a/crates/sp-domains/Cargo.toml b/crates/sp-domains/Cargo.toml index d65844e2ac..514ec0e167 100644 --- a/crates/sp-domains/Cargo.toml +++ b/crates/sp-domains/Cargo.toml @@ -18,6 +18,8 @@ frame-support = { version = "4.0.0-dev", default-features = false, git = "https: hash-db = { version = "0.16.0", default-features = false } hexlit = "0.5.5" parity-scale-codec = { version = "3.6.5", default-features = false, features = ["derive"] } +rand = { version = "0.8.5", default-features = false } +rand_chacha = { version = "0.3.1", default-features = false } rs_merkle = { version = "1.4.1", default-features = false } scale-info = { version = "2.7.0", default-features = false, features = ["derive"] } serde = { version = "1.0.183", default-features = false, features = ["alloc", "derive"] } @@ -36,10 +38,12 @@ sp-trie = { version = "22.0.0", default-features = false, git = "https://github. sp-weights = { version = "20.0.0", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } 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" } +trie-db = { version = "0.28.0", default-features = false } thiserror = { version = "1.0.48", optional = true } [dev-dependencies] num-traits = "0.2.16" +rand = { version = "0.8.5", features = ["min_const_gen"] } [features] default = ["std"] @@ -49,6 +53,8 @@ std = [ "frame-support/std", "hash-db/std", "parity-scale-codec/std", + "rand/std", + "rand_chacha/std", "rs_merkle/std", "scale-info/std", "serde/std", @@ -67,6 +73,7 @@ std = [ "sp-weights/std", "subspace-core-primitives/std", "subspace-runtime-primitives/std", + "trie-db/std", "thiserror", ] runtime-benchmarks = [] diff --git a/crates/sp-domains/src/fraud_proof.rs b/crates/sp-domains/src/fraud_proof.rs index a76e9d06d1..d1e7b048eb 100644 --- a/crates/sp-domains/src/fraud_proof.rs +++ b/crates/sp-domains/src/fraud_proof.rs @@ -1,4 +1,5 @@ use crate::{DomainId, ReceiptHash, SealedBundleHeader}; +use hash_db::Hasher; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_consensus_slots::Slot; @@ -8,6 +9,7 @@ use sp_std::vec::Vec; use sp_trie::StorageProof; use subspace_core_primitives::BlockNumber; use subspace_runtime_primitives::{AccountId, Balance}; +use trie_db::TrieLayout; /// A phase of a block's execution, carrying necessary information needed for verifying the /// invalid state transition proof. @@ -304,7 +306,7 @@ where Hash: Encode, { pub fn hash(&self) -> H256 { - BlakeTwo256::hash(&self.encode()) + ::hash(&self.encode()) } } @@ -414,12 +416,40 @@ pub struct InvalidTotalRewardsProof { pub storage_proof: StorageProof, } +#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] +pub enum ExtrinsicDigest { + /// Actual extrinsic data that is inlined since it is less than 33 bytes. + Data(Vec), + /// Extrinsic Hash. + Hash(H256), +} + +impl ExtrinsicDigest { + pub fn new(ext: Vec) -> Self + where + Layout::Hash: Hasher, + { + if let Some(threshold) = Layout::MAX_INLINE_VALUE { + if ext.len() >= threshold as usize { + ExtrinsicDigest::Hash(Layout::Hash::hash(&ext)) + } else { + ExtrinsicDigest::Data(ext) + } + } else { + ExtrinsicDigest::Data(ext) + } + } +} + #[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, H256)>, + pub bundle_digest: Vec<( + Option, + ExtrinsicDigest, + )>, } #[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index 735f6c8ea6..570eb8b173 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -23,6 +23,7 @@ pub mod merkle_tree; #[cfg(test)] mod tests; pub mod transaction; +pub mod valued_trie_root; pub mod verification; use bundle_producer_election::{BundleProducerElectionParams, VrfProofError}; @@ -52,6 +53,9 @@ use subspace_runtime_primitives::{Balance, Moment}; /// Key type for Operator. const KEY_TYPE: KeyTypeId = KeyTypeId(*b"oper"); +/// Extrinsics shuffling seed +pub const EXTRINSICS_SHUFFLING_SEED: &[u8] = b"extrinsics-shuffling-seed"; + mod app { use super::KEY_TYPE; use sp_application_crypto::{app_crypto, sr25519}; @@ -351,7 +355,7 @@ pub struct ExecutionReceipt { /// The block hash corresponding to `domain_block_number`. pub domain_block_hash: DomainHash, /// Extrinsic root field of the header of domain block referenced by this ER. - pub domain_block_extrinsic_root: DomainHash, + pub domain_block_extrinsic_root: H256, /// The hash of the ER for the last domain block. pub parent_domain_block_receipt_hash: ReceiptHash, /// A pointer to the consensus block index which contains all of the bundles that were used to derive and diff --git a/crates/sp-domains/src/valued_trie_root.rs b/crates/sp-domains/src/valued_trie_root.rs new file mode 100644 index 0000000000..be8c7038e5 --- /dev/null +++ b/crates/sp-domains/src/valued_trie_root.rs @@ -0,0 +1,292 @@ +use hash_db::Hasher; +use parity_scale_codec::{Compact, Encode}; +use sp_std::cmp::max; +use sp_std::vec::Vec; +use trie_db::node::Value; +use trie_db::{ + nibble_ops, ChildReference, NibbleSlice, NodeCodec, ProcessEncodedNode, TrieHash, TrieLayout, + TrieRoot, +}; + +macro_rules! exponential_out { + (@3, [$($inpp:expr),*]) => { exponential_out!(@2, [$($inpp,)* $($inpp),*]) }; + (@2, [$($inpp:expr),*]) => { exponential_out!(@1, [$($inpp,)* $($inpp),*]) }; + (@1, [$($inpp:expr),*]) => { [$($inpp,)* $($inpp),*] }; +} + +type CacheNode = Option>; + +#[inline(always)] +fn new_vec_slice_buffer() -> [CacheNode; 16] { + exponential_out!(@3, [None, None]) +} + +type ArrayNode = [CacheNode>; 16]; + +/// This is a modified version of trie root that takes trie node values instead of deriving from +/// the actual data. Taken from `trie-db` as is. +/// Note: This is an opportunity to push this change upstream but I'm not sure how to present these +/// changes yet. Need to be discussed further. +pub fn valued_ordered_trie_root( + input: Vec, +) -> <::Hash as Hasher>::Out +where + Layout: TrieLayout, +{ + let input = input + .into_iter() + .enumerate() + .map(|(i, v)| (Compact(i as u32).encode(), v)) + .collect(); + + let mut cb = TrieRoot::::default(); + trie_visit::(input, &mut cb); + cb.root.unwrap_or_default() +} + +fn trie_visit(input: Vec<(Vec, Value)>, callback: &mut F) +where + T: TrieLayout, + F: ProcessEncodedNode>, +{ + let mut depth_queue = CacheAccum::::new(); + // compare iter ordering + let mut iter_input = input.into_iter(); + if let Some(mut previous_value) = iter_input.next() { + // depth of last item + let mut last_depth = 0; + + let mut single = true; + for (k, v) in iter_input { + single = false; + let common_depth = nibble_ops::biggest_depth(&previous_value.0, &k); + // 0 is a reserved value : could use option + let depth_item = common_depth; + if common_depth == previous_value.0.len() * nibble_ops::NIBBLE_PER_BYTE { + // the new key include the previous one : branch value case + // just stored value at branch depth + depth_queue.set_cache_value(common_depth, Some(previous_value.1)); + } else if depth_item >= last_depth { + // put previous with next (common branch previous value can be flush) + depth_queue.flush_value(callback, depth_item, &previous_value); + } else if depth_item < last_depth { + // do not put with next, previous is last of a branch + depth_queue.flush_value(callback, last_depth, &previous_value); + let ref_branches = previous_value.0; + depth_queue.flush_branch(callback, ref_branches, depth_item, false); + } + + previous_value = (k, v); + last_depth = depth_item; + } + // last pendings + if single { + // one single element corner case + let (k2, v2) = previous_value; + let nkey = NibbleSlice::new_offset(&k2, last_depth); + let pr = + NibbleSlice::new_offset(&k2, k2.len() * nibble_ops::NIBBLE_PER_BYTE - nkey.len()); + + let encoded = T::Codec::leaf_node(nkey.right_iter(), nkey.len(), v2); + callback.process(pr.left(), encoded, true); + } else { + depth_queue.flush_value(callback, last_depth, &previous_value); + let ref_branches = previous_value.0; + depth_queue.flush_branch(callback, ref_branches, 0, true); + } + } else { + // nothing null root corner case + callback.process(hash_db::EMPTY_PREFIX, T::Codec::empty_node().to_vec(), true); + } +} + +struct CacheAccum<'a, T: TrieLayout>(Vec<(ArrayNode, Option>, usize)>); + +/// Initially allocated cache depth. +const INITIAL_DEPTH: usize = 10; + +impl<'a, T> CacheAccum<'a, T> +where + T: TrieLayout, +{ + fn new() -> Self { + let v = Vec::with_capacity(INITIAL_DEPTH); + CacheAccum(v) + } + + #[inline(always)] + fn set_cache_value(&mut self, depth: usize, value: Option>) { + if self.0.is_empty() || self.0[self.0.len() - 1].2 < depth { + self.0.push((new_vec_slice_buffer(), None, depth)); + } + let last = self.0.len() - 1; + debug_assert!(self.0[last].2 <= depth); + self.0[last].1 = value; + } + + #[inline(always)] + fn set_node(&mut self, depth: usize, nibble_index: usize, node: CacheNode>) { + if self.0.is_empty() || self.0[self.0.len() - 1].2 < depth { + self.0.push((new_vec_slice_buffer(), None, depth)); + } + + let last = self.0.len() - 1; + debug_assert!(self.0[last].2 == depth); + + self.0[last].0.as_mut()[nibble_index] = node; + } + + #[inline(always)] + fn last_depth(&self) -> usize { + let ix = self.0.len(); + if ix > 0 { + let last = ix - 1; + self.0[last].2 + } else { + 0 + } + } + + #[inline(always)] + fn last_last_depth(&self) -> usize { + let ix = self.0.len(); + if ix > 1 { + let last = ix - 2; + self.0[last].2 + } else { + 0 + } + } + + #[inline(always)] + fn is_empty(&self) -> bool { + self.0.is_empty() + } + #[inline(always)] + fn is_one(&self) -> bool { + self.0.len() == 1 + } + + fn flush_value( + &mut self, + callback: &mut impl ProcessEncodedNode>, + target_depth: usize, + (k2, v2): &(impl AsRef<[u8]>, Value), + ) { + let nibble_value = nibble_ops::left_nibble_at(k2.as_ref(), target_depth); + // is it a branch value (two candidate same ix) + let nkey = NibbleSlice::new_offset(k2.as_ref(), target_depth + 1); + let pr = NibbleSlice::new_offset( + k2.as_ref(), + k2.as_ref().len() * nibble_ops::NIBBLE_PER_BYTE - nkey.len(), + ); + + let encoded = T::Codec::leaf_node(nkey.right_iter(), nkey.len(), v2.clone()); + let hash = callback.process(pr.left(), encoded, false); + + // insert hash in branch (first level branch only at this point) + self.set_node(target_depth, nibble_value as usize, Some(hash)); + } + + fn flush_branch( + &mut self, + callback: &mut impl ProcessEncodedNode>, + ref_branch: impl AsRef<[u8]> + Ord, + new_depth: usize, + is_last: bool, + ) { + while self.last_depth() > new_depth || is_last && !self.is_empty() { + let lix = self.last_depth(); + let llix = max(self.last_last_depth(), new_depth); + + let (offset, slice_size, is_root) = if llix == 0 && is_last && self.is_one() { + // branch root + (llix, lix - llix, true) + } else { + (llix + 1, lix - llix - 1, false) + }; + let nkey = if slice_size > 0 { + Some((offset, slice_size)) + } else { + None + }; + + let h = self.no_extension(ref_branch.as_ref(), callback, lix, is_root, nkey); + if !is_root { + // put hash in parent + let nibble: u8 = nibble_ops::left_nibble_at(ref_branch.as_ref(), llix); + self.set_node(llix, nibble as usize, Some(h)); + } + } + } + + #[inline(always)] + fn no_extension( + &mut self, + key_branch: &[u8], + callback: &mut impl ProcessEncodedNode>, + branch_d: usize, + is_root: bool, + nkey: Option<(usize, usize)>, + ) -> ChildReference> { + let (children, v, depth) = self.0.pop().expect("checked"); + + debug_assert!(branch_d == depth); + // encode branch + let nkeyix = nkey.unwrap_or((branch_d, 0)); + let pr = NibbleSlice::new_offset(key_branch, nkeyix.0); + let encoded = T::Codec::branch_node_nibbled( + pr.right_range_iter(nkeyix.1), + nkeyix.1, + children.iter(), + v, + ); + callback.process(pr.left(), encoded, is_root) + } +} + +#[cfg(test)] +mod test { + use crate::valued_trie_root::valued_ordered_trie_root; + use rand::rngs::StdRng; + use rand::{Rng, SeedableRng}; + use sp_runtime::traits::{BlakeTwo256, Hash}; + use sp_trie::LayoutV1; + use trie_db::node::Value; + + #[test] + fn test_extrinsics_root() { + let mut rng = StdRng::seed_from_u64(10000); + let exts_length = vec![35, 31, 50, 100, 20, 10, 120]; + let mut exts = Vec::new(); + let mut exts_hashed = Vec::new(); + for ext_length in &exts_length { + let mut ext = vec![0u8; *ext_length]; + rng.fill(ext.as_mut_slice()); + let hashed = if *ext_length <= 32 { + ext.clone() + } else { + BlakeTwo256::hash(&ext).0.to_vec() + }; + exts.push(ext); + exts_hashed.push(hashed); + } + + let exts_values = exts_hashed + .iter() + .zip(exts_length) + .map(|(ext_hashed, ext_length)| { + let value = if ext_length <= 32 { + Value::Inline(ext_hashed) + } else { + Value::Node(ext_hashed) + }; + value + }) + .collect(); + + let root = BlakeTwo256::ordered_trie_root(exts, sp_core::storage::StateVersion::V1); + let got_root = valued_ordered_trie_root::>(exts_values); + assert_eq!(root, got_root) + } +} diff --git a/crates/sp-domains/src/verification.rs b/crates/sp-domains/src/verification.rs index c3211fdc4a..3b1b9caa91 100644 --- a/crates/sp-domains/src/verification.rs +++ b/crates/sp-domains/src/verification.rs @@ -1,12 +1,26 @@ -use crate::ExecutionReceipt; +use crate::fraud_proof::{ExtrinsicDigest, InvalidExtrinsicsRootProof}; +use crate::valued_trie_root::valued_ordered_trie_root; +use crate::{ExecutionReceipt, EXTRINSICS_SHUFFLING_SEED}; +use domain_runtime_primitives::opaque::AccountId; use frame_support::PalletError; use hash_db::Hasher; use parity_scale_codec::{Decode, Encode}; +use rand::seq::SliceRandom; +use rand::SeedableRng; +use rand_chacha::ChaCha8Rng; use scale_info::TypeInfo; use sp_core::storage::StorageKey; -use sp_runtime::traits::{Block, NumberFor}; +use sp_core::H256; +use sp_runtime::traits::{BlakeTwo256, Block, Hash, NumberFor}; +use sp_state_machine::trace; +use sp_std::collections::btree_map::BTreeMap; +use sp_std::collections::vec_deque::VecDeque; +use sp_std::fmt::Debug; use sp_std::marker::PhantomData; +use sp_std::vec::Vec; use sp_trie::{read_trie_value, LayoutV1, StorageProof}; +use subspace_core_primitives::Randomness; +use trie_db::node::Value; /// Verification error. #[derive(Debug, PartialEq, Eq, Encode, Decode, PalletError, TypeInfo)] @@ -17,6 +31,8 @@ pub enum VerificationError { MissingValue, /// Failed to decode value. FailedToDecode, + /// Invalid bundle digest + InvalidBundleDigest, } pub struct StorageProofVerifier(PhantomData); @@ -80,3 +96,156 @@ where Ok(()) } + +pub fn verify_invalid_domain_extrinsics_root_fraud_proof< + CBlock, + DomainNumber, + DomainHash, + Balance, + Hashing, +>( + consensus_state_root: CBlock::Hash, + bad_receipt: ExecutionReceipt< + NumberFor, + CBlock::Hash, + DomainNumber, + DomainHash, + Balance, + >, + fraud_proof: InvalidExtrinsicsRootProof, +) -> Result<(), VerificationError> +where + CBlock: Block, + CBlock::Hash: Into, + Hashing: Hasher, +{ + let InvalidExtrinsicsRootProof { + valid_bundle_digests, + randomness_proof, + .. + } = fraud_proof; + + let mut ext_values = Vec::new(); + for (valid_bundle, bundle_digest) in bad_receipt + .valid_bundles + .into_iter() + .zip(valid_bundle_digests) + { + let bundle_digest_hash = BlakeTwo256::hash_of(&bundle_digest.bundle_digest); + if bundle_digest_hash != valid_bundle.bundle_digest { + return Err(VerificationError::InvalidBundleDigest); + } + + ext_values.extend(bundle_digest.bundle_digest); + } + + let storage_key = StorageKey(crate::fraud_proof::block_randomness_final_key()); + let storage_proof = randomness_proof.clone(); + let block_randomness = StorageProofVerifier::::verify_and_get_value::( + &consensus_state_root, + storage_proof, + storage_key, + ) + .map_err(|_| VerificationError::InvalidProof)?; + + let shuffling_seed = extrinsics_shuffling_seed::(block_randomness) + .into() + .to_fixed_bytes(); + let ordered_extrinsics = + deduplicate_and_shuffle_extrinsics(ext_values, Randomness::from(shuffling_seed)); + let ordered_trie_node_values = ordered_extrinsics + .iter() + .map(|ext_digest| match ext_digest { + ExtrinsicDigest::Data(data) => Value::Inline(data), + ExtrinsicDigest::Hash(hash) => Value::Node(hash.0.as_slice()), + }) + .collect(); + // TODO: include timestamp and domain runtime upgrade extrinsic + // TODO: change Layout version used for deriving extrinsics root in frame system + let extrinsics_root = + valued_ordered_trie_root::>(ordered_trie_node_values); + if bad_receipt.domain_block_extrinsic_root == extrinsics_root { + return Err(VerificationError::InvalidProof); + } + + Ok(()) +} + +fn extrinsics_shuffling_seed(block_randomness: Randomness) -> Hashing::Out +where + Hashing: Hasher, +{ + let mut subject = EXTRINSICS_SHUFFLING_SEED.to_vec(); + subject.extend_from_slice(block_randomness.as_ref()); + Hashing::hash(&subject) +} + +pub fn deduplicate_and_shuffle_extrinsics( + mut extrinsics: Vec<(Option, Extrinsic)>, + shuffling_seed: Randomness, +) -> Vec +where + Extrinsic: Debug + PartialEq + Clone, +{ + let mut seen = Vec::new(); + extrinsics.retain(|(_, uxt)| match seen.contains(uxt) { + true => { + trace!(extrinsic = ?uxt, "Duplicated extrinsic"); + false + } + false => { + seen.push(uxt.clone()); + true + } + }); + drop(seen); + trace!(?extrinsics, "Origin deduplicated extrinsics"); + shuffle_extrinsics::(extrinsics, shuffling_seed) +} + +/// Shuffles the extrinsics in a deterministic way. +/// +/// The extrinsics are grouped by the signer. The extrinsics without a signer, i.e., unsigned +/// extrinsics, are considered as a special group. The items in different groups are cross shuffled, +/// while the order of items inside the same group is still maintained. +pub fn shuffle_extrinsics( + extrinsics: Vec<(Option, Extrinsic)>, + shuffling_seed: Randomness, +) -> Vec { + let mut rng = ChaCha8Rng::from_seed(*shuffling_seed); + + let mut positions = extrinsics + .iter() + .map(|(maybe_signer, _)| maybe_signer) + .cloned() + .collect::>(); + + // Shuffles the positions using Fisher–Yates algorithm. + positions.shuffle(&mut rng); + + let mut grouped_extrinsics: BTreeMap, VecDeque<_>> = extrinsics + .into_iter() + .fold(BTreeMap::new(), |mut groups, (maybe_signer, tx)| { + groups + .entry(maybe_signer) + .or_insert_with(VecDeque::new) + .push_back(tx); + groups + }); + + // The relative ordering for the items in the same group does not change. + let shuffled_extrinsics = positions + .into_iter() + .map(|maybe_signer| { + grouped_extrinsics + .get_mut(&maybe_signer) + .expect("Extrinsics are grouped correctly; qed") + .pop_front() + .expect("Extrinsic definitely exists as it's correctly grouped above; qed") + }) + .collect::>(); + + trace!(?shuffled_extrinsics, "Shuffled extrinsics"); + + shuffled_extrinsics +} diff --git a/domains/client/block-preprocessor/src/lib.rs b/domains/client/block-preprocessor/src/lib.rs index 2b5a886e43..8fd2936d6c 100644 --- a/domains/client/block-preprocessor/src/lib.rs +++ b/domains/client/block-preprocessor/src/lib.rs @@ -22,13 +22,11 @@ use crate::xdm_verifier::is_valid_xdm; use codec::{Decode, Encode}; use domain_runtime_primitives::opaque::AccountId; use domain_runtime_primitives::DomainCoreApi; -use rand::seq::SliceRandom; -use rand::SeedableRng; -use rand_chacha::ChaCha8Rng; use runtime_api::InherentExtrinsicConstructor; use sc_client_api::BlockBackend; use sp_api::{HashT, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; +use sp_domains::verification::deduplicate_and_shuffle_extrinsics; use sp_domains::{ BundleValidity, DomainId, DomainsApi, DomainsDigestItem, ExecutionReceipt, ExtrinsicsRoot, InvalidBundle, InvalidBundleType, OpaqueBundle, OpaqueBundles, ReceiptValidity, ValidBundle, @@ -36,8 +34,6 @@ use sp_domains::{ use sp_messenger::MessengerApi; use sp_runtime::traits::{BlakeTwo256, Block as BlockT, Header as HeaderT, NumberFor}; use std::borrow::Cow; -use std::collections::{BTreeMap, VecDeque}; -use std::fmt::Debug; use std::marker::PhantomData; use std::sync::Arc; use subspace_core_primitives::{Randomness, U256}; @@ -108,81 +104,6 @@ where Ok((extrinsics, shuffling_seed, maybe_new_runtime)) } -fn deduplicate_and_shuffle_extrinsics( - mut extrinsics: Vec<(Option, Block::Extrinsic)>, - shuffling_seed: Randomness, -) -> Result, sp_blockchain::Error> -where - Block: BlockT, -{ - let mut seen = Vec::new(); - extrinsics.retain(|(_, uxt)| match seen.contains(uxt) { - true => { - tracing::trace!(extrinsic = ?uxt, "Duplicated extrinsic"); - false - } - false => { - seen.push(uxt.clone()); - true - } - }); - drop(seen); - - tracing::trace!(?extrinsics, "Origin deduplicated extrinsics"); - - let extrinsics = - shuffle_extrinsics::<::Extrinsic, AccountId>(extrinsics, shuffling_seed); - - Ok(extrinsics) -} - -/// Shuffles the extrinsics in a deterministic way. -/// -/// The extrinsics are grouped by the signer. The extrinsics without a signer, i.e., unsigned -/// extrinsics, are considered as a special group. The items in different groups are cross shuffled, -/// while the order of items inside the same group is still maintained. -fn shuffle_extrinsics( - extrinsics: Vec<(Option, Extrinsic)>, - shuffling_seed: Randomness, -) -> Vec { - let mut rng = ChaCha8Rng::from_seed(*shuffling_seed); - - let mut positions = extrinsics - .iter() - .map(|(maybe_signer, _)| maybe_signer) - .cloned() - .collect::>(); - - // Shuffles the positions using Fisher–Yates algorithm. - positions.shuffle(&mut rng); - - let mut grouped_extrinsics: BTreeMap, VecDeque<_>> = extrinsics - .into_iter() - .fold(BTreeMap::new(), |mut groups, (maybe_signer, tx)| { - groups - .entry(maybe_signer) - .or_insert_with(VecDeque::new) - .push_back(tx); - groups - }); - - // The relative ordering for the items in the same group does not change. - let shuffled_extrinsics = positions - .into_iter() - .map(|maybe_signer| { - grouped_extrinsics - .get_mut(&maybe_signer) - .expect("Extrinsics are grouped correctly; qed") - .pop_front() - .expect("Extrinsic definitely exists as it's correctly grouped above; qed") - }) - .collect::>(); - - tracing::trace!(?shuffled_extrinsics, "Shuffled extrinsics"); - - shuffled_extrinsics -} - pub struct PreprocessResult { pub extrinsics: Vec, pub extrinsics_roots: Vec, @@ -305,8 +226,10 @@ where let (valid_bundles, invalid_bundles, extrinsics) = self.compile_bundles_to_extrinsics(bundles, tx_range, domain_hash)?; - let extrinsics_in_bundle = - deduplicate_and_shuffle_extrinsics::(extrinsics, shuffling_seed)?; + let extrinsics_in_bundle = deduplicate_and_shuffle_extrinsics::<::Extrinsic>( + extrinsics, + shuffling_seed, + ); // Fetch inherent extrinsics let mut extrinsics = construct_inherent_extrinsics( @@ -471,7 +394,7 @@ where #[cfg(test)] mod tests { - use super::shuffle_extrinsics; + use sp_domains::verification::shuffle_extrinsics; use sp_keyring::sr25519::Keyring; use sp_runtime::traits::{BlakeTwo256, Hash as HashT}; use subspace_core_primitives::Randomness; diff --git a/domains/client/domain-operator/src/bundle_processor.rs b/domains/client/domain-operator/src/bundle_processor.rs index 4e10ba4901..5fd67adaa0 100644 --- a/domains/client/domain-operator/src/bundle_processor.rs +++ b/domains/client/domain-operator/src/bundle_processor.rs @@ -11,6 +11,7 @@ use sp_api::{NumberFor, ProvideRuntimeApi}; use sp_blockchain::{HeaderBackend, HeaderMetadata}; use sp_consensus::BlockOrigin; use sp_core::traits::CodeExecutor; +use sp_core::H256; use sp_domain_digests::AsPredigest; use sp_domains::{DomainId, DomainsApi, InvalidReceipt, ReceiptValidity}; use sp_keystore::KeystorePtr; @@ -133,6 +134,7 @@ where CBlock: BlockT, NumberFor: From> + Into>, CBlock::Hash: From, + Block::Hash: Into, Client: HeaderBackend + BlockBackend + AuxStore diff --git a/domains/client/domain-operator/src/domain_block_processor.rs b/domains/client/domain-operator/src/domain_block_processor.rs index 9882ee8d01..1ea2d1c44a 100644 --- a/domains/client/domain-operator/src/domain_block_processor.rs +++ b/domains/client/domain-operator/src/domain_block_processor.rs @@ -16,6 +16,7 @@ use sp_api::{NumberFor, ProvideRuntimeApi}; use sp_blockchain::{HashAndNumber, HeaderBackend, HeaderMetadata}; use sp_consensus::{BlockOrigin, SyncOracle}; use sp_core::traits::CodeExecutor; +use sp_core::H256; use sp_domains::fraud_proof::FraudProof; use sp_domains::merkle_tree::MerkleTree; use sp_domains::{DomainId, DomainsApi, ExecutionReceipt}; @@ -100,6 +101,7 @@ where Block: BlockT, CBlock: BlockT, NumberFor: Into>, + Block::Hash: Into, Client: HeaderBackend + BlockBackend + AuxStore @@ -371,7 +373,7 @@ where let execution_receipt = ExecutionReceipt { domain_block_number: header_number, domain_block_hash: header_hash, - domain_block_extrinsic_root: extrinsics_root, + domain_block_extrinsic_root: extrinsics_root.into(), parent_domain_block_receipt_hash: parent_receipt.hash(), consensus_block_number, consensus_block_hash, diff --git a/domains/client/domain-operator/src/domain_worker_starter.rs b/domains/client/domain-operator/src/domain_worker_starter.rs index 975fe54865..62930c253b 100644 --- a/domains/client/domain-operator/src/domain_worker_starter.rs +++ b/domains/client/domain-operator/src/domain_worker_starter.rs @@ -31,6 +31,7 @@ use sp_api::{BlockT, ProvideRuntimeApi}; use sp_block_builder::BlockBuilder; use sp_blockchain::{HeaderBackend, HeaderMetadata}; use sp_core::traits::{CodeExecutor, SpawnEssentialNamed}; +use sp_core::H256; use sp_domains::{BundleProducerElectionApi, DomainsApi}; use sp_messenger::MessengerApi; use sp_runtime::traits::NumberFor; @@ -70,6 +71,7 @@ pub(super) async fn start_worker< active_leaves: Vec>, ) where Block: BlockT, + Block::Hash: Into, CBlock: BlockT, NumberFor: From> + Into>, CBlock::Hash: From, diff --git a/domains/client/domain-operator/src/fraud_proof.rs b/domains/client/domain-operator/src/fraud_proof.rs index 661fe2d2ff..9436c87d26 100644 --- a/domains/client/domain-operator/src/fraud_proof.rs +++ b/domains/client/domain-operator/src/fraud_proof.rs @@ -6,19 +6,19 @@ use domain_block_builder::{BlockBuilder, RecordProof}; use domain_runtime_primitives::opaque::AccountId; use domain_runtime_primitives::DomainCoreApi; use sc_client_api::{AuxStore, BlockBackend, ProofProvider}; -use sp_api::{HashT, ProvideRuntimeApi}; +use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_core::traits::CodeExecutor; use sp_core::H256; use sp_domains::fraud_proof::{ - ExecutionPhase, FraudProof, InvalidBundlesFraudProof, InvalidExtrinsicsRootProof, - InvalidStateTransitionProof, InvalidTotalRewardsProof, MissingInvalidBundleEntryFraudProof, - ValidAsInvalidBundleEntryFraudProof, ValidBundleDigest, + ExecutionPhase, ExtrinsicDigest, FraudProof, InvalidBundlesFraudProof, + InvalidExtrinsicsRootProof, InvalidStateTransitionProof, InvalidTotalRewardsProof, + MissingInvalidBundleEntryFraudProof, ValidAsInvalidBundleEntryFraudProof, ValidBundleDigest, }; use sp_domains::{DomainId, DomainsApi}; use sp_runtime::traits::{BlakeTwo256, Block as BlockT, HashingFor, Header as HeaderT, NumberFor}; use sp_runtime::Digest; -use sp_trie::StorageProof; +use sp_trie::{LayoutV1, StorageProof}; use std::marker::PhantomData; use std::sync::Arc; use subspace_fraud_proof::invalid_state_transition_proof::ExecutionProver; @@ -201,8 +201,13 @@ where let bundle_digest = domain_runtime_api .extract_signer(local_receipt.domain_block_hash, exts)? .into_iter() - .map(|(signer, ext)| (signer, BlakeTwo256::hash_of(&ext))) - .collect::, H256)>>(); + .map(|(signer, ext)| { + ( + signer, + ExtrinsicDigest::new::>(ext.encode()), + ) + }) + .collect::, ExtrinsicDigest)>>(); valid_bundle_digests.push(ValidBundleDigest { bundle_index, bundle_digest, diff --git a/domains/client/domain-operator/src/operator.rs b/domains/client/domain-operator/src/operator.rs index 91f71a78b1..95c56ae8d6 100644 --- a/domains/client/domain-operator/src/operator.rs +++ b/domains/client/domain-operator/src/operator.rs @@ -16,6 +16,7 @@ use sp_api::ProvideRuntimeApi; use sp_blockchain::{HeaderBackend, HeaderMetadata}; use sp_consensus::SelectChain; use sp_core::traits::{CodeExecutor, SpawnEssentialNamed}; +use sp_core::H256; use sp_domains::{BundleProducerElectionApi, DomainsApi}; use sp_messenger::MessengerApi; use sp_runtime::traits::{Block as BlockT, NumberFor}; @@ -63,6 +64,7 @@ where CBlock: BlockT, NumberFor: From> + Into>, CBlock::Hash: From, + Block::Hash: Into, Client: HeaderBackend + BlockBackend + AuxStore From 95e8f60f344502679870f605e433af6045abcff2 Mon Sep 17 00:00:00 2001 From: vedhavyas Date: Sat, 23 Sep 2023 12:04:11 +0200 Subject: [PATCH 5/8] create state root inherent to pass parent state root in the next block and use StateVersion::V1 for domains --- Cargo.lock | 2 + crates/pallet-domains/src/block_tree.rs | 21 +++--- crates/pallet-domains/src/lib.rs | 88 ++++++++++++++++++++++-- crates/pallet-domains/src/tests.rs | 4 +- crates/pallet-runtime-configs/Cargo.toml | 24 +++---- crates/sp-domains/Cargo.toml | 4 ++ crates/sp-domains/src/inherents.rs | 74 ++++++++++++++++++++ crates/sp-domains/src/lib.rs | 1 + crates/sp-domains/src/verification.rs | 1 - crates/subspace-service/src/lib.rs | 13 ++-- domains/client/block-builder/src/lib.rs | 2 +- domains/runtime/evm/src/lib.rs | 2 +- domains/test/runtime/evm/src/lib.rs | 2 +- test/subspace-test-service/src/lib.rs | 13 +++- 14 files changed, 212 insertions(+), 39 deletions(-) create mode 100644 crates/sp-domains/src/inherents.rs diff --git a/Cargo.lock b/Cargo.lock index ad4c6c30ad..e98f0ed0ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10830,6 +10830,7 @@ dependencies = [ name = "sp-domains" version = "0.1.0" dependencies = [ + "async-trait", "blake2", "domain-runtime-primitives", "frame-support", @@ -10848,6 +10849,7 @@ dependencies = [ "sp-consensus-slots", "sp-core", "sp-externalities", + "sp-inherents", "sp-keystore", "sp-runtime", "sp-runtime-interface", diff --git a/crates/pallet-domains/src/block_tree.rs b/crates/pallet-domains/src/block_tree.rs index 566923ea61..ba9a41d0cb 100644 --- a/crates/pallet-domains/src/block_tree.rs +++ b/crates/pallet-domains/src/block_tree.rs @@ -2,7 +2,7 @@ use crate::pallet::StateRoots; use crate::{ - BalanceOf, BlockTree, Config, ConsensusBlockHash, DomainBlockDescendants, DomainBlocks, + BalanceOf, BlockTree, Config, ConsensusBlockInfo, DomainBlockDescendants, DomainBlocks, ExecutionInbox, ExecutionReceiptOf, HeadReceiptNumber, InboxedBundle, }; use codec::{Decode, Encode}; @@ -169,8 +169,8 @@ pub(crate) fn verify_execution_receipt( ); let excepted_consensus_block_hash = - match ConsensusBlockHash::::get(domain_id, consensus_block_number) { - Some(hash) => hash, + match ConsensusBlockInfo::::get(domain_id, consensus_block_number) { + Some((hash, _)) => hash, // The `initialize_block` of non-system pallets is skipped in the `validate_transaction`, // thus the hash of best block, which is recorded in the this pallet's `on_initialize` hook, // is unavailable at this point. @@ -312,7 +312,7 @@ pub(crate) fn process_execution_receipt( // its receipt's `extrinsics_root` anymore. let _ = ExecutionInbox::::clear_prefix((domain_id, to_prune), u32::MAX, None); - ConsensusBlockHash::::remove( + ConsensusBlockInfo::::remove( domain_id, execution_receipt.consensus_block_number, ); @@ -471,8 +471,11 @@ mod tests { if block_number != 1 { // `ConsensusBlockHash` should be set to `Some` since last consensus block contains bundle assert_eq!( - ConsensusBlockHash::::get(domain_id, block_number - 1), - Some(frame_system::Pallet::::block_hash(block_number - 1)) + ConsensusBlockInfo::::get(domain_id, block_number - 1), + Some(( + frame_system::Pallet::::block_hash(block_number - 1), + H256::default() + )) ); // ER point to last consensus block should have `NewHead` type assert_eq!( @@ -551,7 +554,7 @@ mod tests { verify_execution_receipt::(domain_id, &pruned_receipt), Error::InvalidExtrinsicsRoots ); - assert!(ConsensusBlockHash::::get( + assert!(ConsensusBlockInfo::::get( domain_id, pruned_receipt.consensus_block_number, ) @@ -757,10 +760,10 @@ mod tests { verify_execution_receipt::(domain_id, &future_receipt), Error::UnavailableConsensusBlockHash ); - ConsensusBlockHash::::insert( + ConsensusBlockInfo::::insert( domain_id, future_receipt.consensus_block_number, - future_receipt.consensus_block_hash, + (future_receipt.consensus_block_hash, H256::default()), ); // Return `UnknownParentBlockReceipt` error as its parent receipt is missing from the block tree diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 74569b1764..b344e8583b 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -136,6 +136,7 @@ mod pallet { use frame_system::pallet_prelude::*; use sp_core::H256; use sp_domains::fraud_proof::FraudProof; + use sp_domains::inherents::{InherentError, InherentType, INHERENT_IDENTIFIER}; use sp_domains::transaction::InvalidTransactionCode; use sp_domains::{ BundleDigest, DomainId, EpochIndex, GenesisDomain, OperatorId, ReceiptHash, RuntimeId, @@ -481,16 +482,24 @@ mod pallet { OptionQuery, >; - /// The consensus block hash used to verify ER, only store the consensus block hash for a domain + /// The consensus block hash and state root used to verify ER and storage proofs, + /// only store the consensus block hash for a domain /// if that consensus block contains bundle of the domain, the hash will be pruned when the ER /// that point to the consensus block is pruned. /// /// TODO: this storage is unbounded in some cases, see https://github.com/subspace/subspace/issues/1673 /// for more details, this will be fixed once https://github.com/subspace/subspace/issues/1731 is implemented. #[pallet::storage] - #[pallet::getter(fn consensus_hash)] - pub type ConsensusBlockHash = - StorageDoubleMap<_, Identity, DomainId, Identity, BlockNumberFor, T::Hash, OptionQuery>; + #[pallet::getter(fn consensus_block_info)] + pub type ConsensusBlockInfo = StorageDoubleMap< + _, + Identity, + DomainId, + Identity, + BlockNumberFor, + (T::Hash, T::Hash), + OptionQuery, + >; /// A set of `BundleDigest` from all bundles that successfully submitted to the consensus block, /// these bundles will be used to construct the domain block and `ExecutionInbox` is used to: @@ -1112,6 +1121,27 @@ mod pallet { Ok(()) } + + /// Submit parent state root to the blockchain. + #[pallet::call_index(11)] + #[pallet::weight((Weight::from_all(10_000), DispatchClass::Mandatory, Pays::No))] + pub fn store_parent_state_root( + origin: OriginFor, + parent_state_root: T::Hash, + ) -> DispatchResult { + ensure_none(origin)?; + let block_number = frame_system::Pallet::::block_number(); + let parent_number = block_number - One::one(); + + let domains_ids = DomainRegistry::::iter_keys().collect::>(); + for domain_id in domains_ids { + if let Some(info) = ConsensusBlockInfo::::get(domain_id, parent_number) { + let info = (info.0, parent_state_root); + ConsensusBlockInfo::::insert(domain_id, parent_number, info); + } + } + Ok(()) + } } #[pallet::genesis_config] @@ -1202,7 +1232,11 @@ mod pallet { let parent_number = block_number - One::one(); let parent_hash = frame_system::Pallet::::block_hash(parent_number); for (domain_id, _) in SuccessfulBundles::::drain() { - ConsensusBlockHash::::insert(domain_id, parent_number, parent_hash); + ConsensusBlockInfo::::insert( + domain_id, + parent_number, + (parent_hash, T::Hash::default()), + ); } Weight::zero() @@ -1224,6 +1258,42 @@ mod pallet { .build() } + #[pallet::inherent] + impl ProvideInherent for Pallet { + type Call = Call; + type Error = InherentError; + const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER; + + fn create_inherent(data: &InherentData) -> Option { + let inherent_data = data + .get_data::>(&INHERENT_IDENTIFIER) + .expect("Domains inherent data not correctly encoded") + .expect("Domains inherent data must be provided"); + + let parent_state_root = inherent_data.parent_state_root; + Some(Call::store_parent_state_root { parent_state_root }) + } + + fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> { + if let Call::store_parent_state_root { parent_state_root } = call { + let inherent_data = data + .get_data::>(&INHERENT_IDENTIFIER) + .expect("Domains inherent data not correctly encoded") + .expect("Domains inherent data must be provided"); + + if parent_state_root != &inherent_data.parent_state_root { + return Err(InherentError::IncorrectParentStateRoot); + } + } + + Ok(()) + } + + fn is_inherent(call: &Self::Call) -> bool { + matches!(call, Call::store_parent_state_root { .. }) + } + } + #[pallet::validate_unsigned] impl ValidateUnsigned for Pallet { type Call = Call; @@ -1233,6 +1303,7 @@ mod pallet { .map_err(|_| InvalidTransaction::Call.into()), Call::submit_fraud_proof { fraud_proof } => Self::validate_fraud_proof(fraud_proof) .map_err(|_| InvalidTransaction::Call.into()), + Call::store_parent_state_root { .. } => Ok(()), _ => Err(InvalidTransaction::Call.into()), } } @@ -1273,6 +1344,13 @@ mod pallet { // TODO: proper tag value. unsigned_validity("SubspaceSubmitFraudProof", fraud_proof) } + Call::store_parent_state_root { .. } => { + ValidTransaction::with_tag_prefix("Domain Parent State root Inherent") + .priority(TransactionPriority::MAX) + .longevity(0) + .propagate(false) + .build() + } _ => InvalidTransaction::Call.into(), } diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 40ffbf5263..cbf5cfddf8 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -1,7 +1,7 @@ use crate::block_tree::DomainBlock; use crate::domain_registry::{DomainConfig, DomainObject}; use crate::{ - self as pallet_domains, BalanceOf, BlockTree, BundleError, Config, ConsensusBlockHash, + self as pallet_domains, BalanceOf, BlockTree, BundleError, Config, ConsensusBlockInfo, DomainBlocks, DomainRegistry, ExecutionInbox, ExecutionReceiptOf, FraudProofError, FungibleHoldId, HeadReceiptNumber, NextDomainId, Operator, OperatorStatus, Operators, }; @@ -859,7 +859,7 @@ fn test_basic_fraud_proof_processing() { assert!( !ExecutionInbox::::get((domain_id, block_number, block_number)).is_empty() ); - assert!(ConsensusBlockHash::::get(domain_id, block_number).is_some()); + assert!(ConsensusBlockInfo::::get(domain_id, block_number).is_some()); } // Re-submit the valid ER diff --git a/crates/pallet-runtime-configs/Cargo.toml b/crates/pallet-runtime-configs/Cargo.toml index 34a22e821c..c1ae378bbf 100644 --- a/crates/pallet-runtime-configs/Cargo.toml +++ b/crates/pallet-runtime-configs/Cargo.toml @@ -8,8 +8,8 @@ homepage = "https://subspace.network" repository = "https://github.com/subspace/subspace" description = "Pallet for tweaking the runtime configs for multiple network" include = [ - "/src", - "/Cargo.toml", + "/src", + "/Cargo.toml", ] [package.metadata.docs.rs] @@ -28,17 +28,17 @@ sp-std = { version = "8.0.0", default-features = false, git = "https://github.co [features] default = ["std"] std = [ - "codec/std", - "frame-support/std", - "frame-system/std", - "scale-info/std", - "sp-runtime/std", - "sp-std?/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-runtime/std", + "sp-std?/std", ] try-runtime = ["frame-support/try-runtime"] runtime-benchmarks = [ - "frame-benchmarking", - "frame-benchmarking/runtime-benchmarks", - "sp-core", - "sp-std", + "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", + "sp-core", + "sp-std", ] diff --git a/crates/sp-domains/Cargo.toml b/crates/sp-domains/Cargo.toml index 514ec0e167..4c30be0676 100644 --- a/crates/sp-domains/Cargo.toml +++ b/crates/sp-domains/Cargo.toml @@ -12,6 +12,7 @@ description = "Primitives of domains pallet" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +async-trait = { version = "0.1.73", optional = true } blake2 = { version = "0.10.6", default-features = false } domain-runtime-primitives = { version = "0.1.0", default-features = false, path = "../../domains/primitives/runtime" } frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } @@ -29,6 +30,7 @@ sp-blockchain = { version = "4.0.0-dev", git = "https://github.com/subspace/polk sp-consensus-slots = { version = "0.10.0-dev", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } sp-core = { version = "21.0.0", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } sp-externalities = { version = "0.19.0", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } +sp-inherents = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } sp-keystore = { version = "0.27.0", git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7", optional = true } sp-runtime = { version = "24.0.0", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } sp-runtime-interface = { version = "17.0.0", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } @@ -48,6 +50,7 @@ rand = { version = "0.8.5", features = ["min_const_gen"] } [features] default = ["std"] std = [ + "async-trait", "blake2/std", "domain-runtime-primitives/std", "frame-support/std", @@ -64,6 +67,7 @@ std = [ "sp-consensus-slots/std", "sp-core/std", "sp-externalities/std", + "sp-inherents/std", "sp-keystore", "sp-runtime/std", "sp-runtime-interface/std", diff --git a/crates/sp-domains/src/inherents.rs b/crates/sp-domains/src/inherents.rs new file mode 100644 index 0000000000..fb56a33aca --- /dev/null +++ b/crates/sp-domains/src/inherents.rs @@ -0,0 +1,74 @@ +//! Inherents for domain + +use parity_scale_codec::{Decode, Encode}; +use sp_inherents::{Error, InherentData, InherentIdentifier, IsFatalError}; + +/// The domain inherent identifier. +pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"domains_"; + +/// Errors that can occur while checking provided inherent data. +#[derive(Debug, Encode)] +#[cfg_attr(feature = "std", derive(Decode))] +pub enum InherentError { + /// Incorrect parent state root. + IncorrectParentStateRoot, +} + +impl IsFatalError for InherentError { + fn is_fatal_error(&self) -> bool { + true + } +} + +/// The type of the Domains inherent data. +#[derive(Debug, Encode, Decode)] +pub struct InherentType { + /// Parent state root. + pub parent_state_root: Hash, +} + +/// Provides the parent state root for Domains inherent data +#[cfg(feature = "std")] +pub struct InherentDataProvider { + data: InherentType, +} + +#[cfg(feature = "std")] +impl InherentDataProvider { + /// Create new inherent data provider from the given `data`. + pub fn new(parent_state_root: Hash) -> Self { + Self { + data: InherentType { parent_state_root }, + } + } + + /// Returns the `data` of this inherent data provider. + pub fn data(&self) -> &InherentType { + &self.data + } +} + +#[cfg(feature = "std")] +#[async_trait::async_trait] +impl sp_inherents::InherentDataProvider for InherentDataProvider +where + Hash: Send + Sync + Encode + Decode, +{ + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { + inherent_data.put_data(INHERENT_IDENTIFIER, &self.data) + } + + async fn try_handle_error( + &self, + identifier: &InherentIdentifier, + error: &[u8], + ) -> Option> { + if *identifier != INHERENT_IDENTIFIER { + return None; + } + + let error = InherentError::decode(&mut &*error).ok()?; + + Some(Err(Error::Application(Box::from(format!("{error:?}"))))) + } +} diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index 570eb8b173..2410b051af 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -19,6 +19,7 @@ pub mod bundle_producer_election; pub mod fraud_proof; +pub mod inherents; pub mod merkle_tree; #[cfg(test)] mod tests; diff --git a/crates/sp-domains/src/verification.rs b/crates/sp-domains/src/verification.rs index 3b1b9caa91..431ef7f9cf 100644 --- a/crates/sp-domains/src/verification.rs +++ b/crates/sp-domains/src/verification.rs @@ -161,7 +161,6 @@ where }) .collect(); // TODO: include timestamp and domain runtime upgrade extrinsic - // TODO: change Layout version used for deriving extrinsics root in frame system let extrinsics_root = valued_ordered_trie_root::>(ordered_trie_node_values); if bad_receipt.domain_block_extrinsic_root == extrinsics_root { diff --git a/crates/subspace-service/src/lib.rs b/crates/subspace-service/src/lib.rs index 8d53832bd5..0286265c40 100644 --- a/crates/subspace-service/src/lib.rs +++ b/crates/subspace-service/src/lib.rs @@ -556,10 +556,12 @@ where let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); // TODO: Would be nice if the whole header was passed in here - let parent_block_number = client + let parent_header = client .header(parent_hash)? - .expect("Parent header must always exist when block is created; qed") - .number; + .expect("Parent header must always exist when block is created; qed"); + + let parent_block_number = parent_header.number; + let parent_state_root = parent_header.state_root; let subspace_inherents = sp_consensus_subspace::inherents::InherentDataProvider::from_timestamp_and_slot_duration( @@ -568,7 +570,10 @@ where subspace_link.segment_headers_for_block(parent_block_number + 1), ); - Ok((timestamp, subspace_inherents)) + let domain_inherents = + sp_domains::inherents::InherentDataProvider::new(parent_state_root); + + Ok((timestamp, subspace_inherents, domain_inherents)) } } }, diff --git a/domains/client/block-builder/src/lib.rs b/domains/client/block-builder/src/lib.rs index 06c6c7e43d..cac193e23a 100644 --- a/domains/client/block-builder/src/lib.rs +++ b/domains/client/block-builder/src/lib.rs @@ -266,7 +266,7 @@ where header.extrinsics_root().clone(), HashingFor::::ordered_trie_root( self.extrinsics.iter().map(Encode::encode).collect(), - sp_core::storage::StateVersion::V0 // TODO: switch to V1 once the upstream substrate switches. + sp_core::storage::StateVersion::V1 ), ); diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index fe06d8a2fd..478697f950 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -245,7 +245,7 @@ parameter_types! { }) .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) .build_or_panic(); - pub const ExtrinsicsRootStateVersion: StateVersion = StateVersion::V0; + pub const ExtrinsicsRootStateVersion: StateVersion = StateVersion::V1; } impl frame_system::Config for Runtime { diff --git a/domains/test/runtime/evm/src/lib.rs b/domains/test/runtime/evm/src/lib.rs index 2773fa63c4..199cb47a8a 100644 --- a/domains/test/runtime/evm/src/lib.rs +++ b/domains/test/runtime/evm/src/lib.rs @@ -245,7 +245,7 @@ parameter_types! { }) .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) .build_or_panic(); - pub const ExtrinsicsRootStateVersion: StateVersion = StateVersion::V0; + pub const ExtrinsicsRootStateVersion: StateVersion = StateVersion::V1; } impl frame_system::Config for Runtime { diff --git a/test/subspace-test-service/src/lib.rs b/test/subspace-test-service/src/lib.rs index c15896f858..ed321bd25f 100644 --- a/test/subspace-test-service/src/lib.rs +++ b/test/subspace-test-service/src/lib.rs @@ -574,14 +574,19 @@ impl MockConsensusNode { extrinsics } - async fn mock_inherent_data(slot: Slot) -> Result> { + async fn mock_inherent_data( + slot: Slot, + parent_state_root: ::Hash, + ) -> Result> { let timestamp = sp_timestamp::InherentDataProvider::new(Timestamp::new( >::into(slot) * SLOT_DURATION, )); let subspace_inherents = sp_consensus_subspace::inherents::InherentDataProvider::new(slot, vec![]); - let inherent_data = (subspace_inherents, timestamp) + let domain_inherents = sp_domains::inherents::InherentDataProvider::new(parent_state_root); + + let inherent_data = (subspace_inherents, timestamp, domain_inherents) .create_inherent_data() .await?; @@ -610,7 +615,9 @@ impl MockConsensusNode { extrinsics: Vec<::Extrinsic>, ) -> Result<(Block, StorageChanges), Box> { let digest = self.mock_subspace_digest(slot); - let inherent_data = Self::mock_inherent_data(slot).await?; + let parent_state_root = self.client.header(parent_hash)?.unwrap().state_root; + + let inherent_data = Self::mock_inherent_data(slot, parent_state_root).await?; let mut block_builder = self.client.new_block_at(parent_hash, digest, false)?; From 51cb0e61b1af697a8994833faf858607e84de130 Mon Sep 17 00:00:00 2001 From: vedhavyas Date: Sat, 23 Sep 2023 12:38:27 +0200 Subject: [PATCH 6/8] verify invalid domain extrinsic root fraud proof on runtime --- crates/pallet-domains/src/lib.rs | 47 ++++++++++++----- crates/pallet-domains/src/tests.rs | 74 ++++++++++++++++++++++++++- crates/sp-domains/src/verification.rs | 17 +++--- 3 files changed, 116 insertions(+), 22 deletions(-) diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index b344e8583b..b854da373e 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -44,7 +44,9 @@ 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::verification::verify_invalid_total_rewards_fraud_proof; +use sp_domains::verification::{ + verify_invalid_domain_extrinsics_root_fraud_proof, verify_invalid_total_rewards_fraud_proof, +}; use sp_domains::{ DomainBlockLimit, DomainId, DomainInstanceData, ExecutionReceipt, OpaqueBundle, OperatorId, OperatorPublicKey, ProofOfElection, RuntimeId, EMPTY_EXTRINSIC_ROOT, EXTRINSICS_SHUFFLING_SEED, @@ -590,6 +592,10 @@ mod pallet { DescendantsOfFraudulentERNotPruned, /// Invalid fraud proof since total rewards are not mismatched. InvalidTotalRewardsFraudProof(sp_domains::verification::VerificationError), + /// Missing state root for a given consensus block + MissingConsensusStateRoot, + /// Invalid domain extrinsic fraud proof + InvalidExtrinsicRootFraudProof(sp_domains::verification::VerificationError), } impl From for Error { @@ -1567,17 +1573,34 @@ impl Pallet { FraudProofError::ChallengingGenesisReceipt ); - if let FraudProof::InvalidTotalRewards(InvalidTotalRewardsProof { storage_proof, .. }) = - fraud_proof - { - verify_invalid_total_rewards_fraud_proof::< - T::Block, - T::DomainNumber, - T::DomainHash, - BalanceOf, - T::Hashing, - >(bad_receipt, storage_proof) - .map_err(FraudProofError::InvalidTotalRewardsFraudProof)?; + match fraud_proof { + FraudProof::InvalidTotalRewards(InvalidTotalRewardsProof { storage_proof, .. }) => { + verify_invalid_total_rewards_fraud_proof::< + T::Block, + T::DomainNumber, + T::DomainHash, + BalanceOf, + T::Hashing, + >(bad_receipt, storage_proof) + .map_err(FraudProofError::InvalidTotalRewardsFraudProof)?; + } + FraudProof::InvalidExtrinsicsRoot(proof) => { + let consensus_state_root = ConsensusBlockInfo::::get( + proof.domain_id, + bad_receipt.consensus_block_number, + ) + .ok_or(FraudProofError::MissingConsensusStateRoot)? + .1; + verify_invalid_domain_extrinsics_root_fraud_proof::< + T::Block, + T::DomainNumber, + T::DomainHash, + BalanceOf, + T::Hashing, + >(consensus_state_root, bad_receipt, proof) + .map_err(FraudProofError::InvalidExtrinsicRootFraudProof)?; + } + _ => {} } Ok(()) diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index cbf5cfddf8..4f857be86d 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -15,7 +15,10 @@ use scale_info::TypeInfo; use sp_core::crypto::Pair; use sp_core::storage::{StateVersion, StorageKey}; use sp_core::{Get, H256, U256}; -use sp_domains::fraud_proof::{FraudProof, InvalidTotalRewardsProof}; +use sp_domains::fraud_proof::{ + ExtrinsicDigest, FraudProof, InvalidExtrinsicsRootProof, InvalidTotalRewardsProof, + ValidBundleDigest, +}; use sp_domains::merkle_tree::MerkleTree; use sp_domains::{ BundleHeader, DomainId, DomainInstanceData, DomainsHoldIdentifier, ExecutionReceipt, @@ -31,7 +34,7 @@ use sp_trie::{PrefixedMemoryDB, StorageProof, TrieMut}; use sp_version::RuntimeVersion; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; -use subspace_core_primitives::U256 as P256; +use subspace_core_primitives::{Randomness, U256 as P256}; use subspace_runtime_primitives::SSC; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -820,6 +823,73 @@ fn storage_proof_for_key + AsTrieBackend::get(domain_id), + head_domain_number + ); + + let bad_receipt_at = 8; + let 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_domain_extrinsic_root_fraud_proof::( + domain_id, + bad_receipt_hash, + Randomness::from([1u8; 32]), + ); + let (consensus_block_number, consensus_block_hash) = ( + domain_block.execution_receipt.consensus_block_number, + domain_block.execution_receipt.consensus_block_hash, + ); + ConsensusBlockInfo::::insert( + domain_id, + consensus_block_number, + (consensus_block_hash, root), + ); + DomainBlocks::::insert(bad_receipt_hash, domain_block); + assert_ok!(Domains::validate_fraud_proof(&fraud_proof),); + }); +} + +fn generate_invalid_domain_extrinsic_root_fraud_proof( + domain_id: DomainId, + bad_receipt_hash: ReceiptHash, + randomness: Randomness, +) -> (FraudProof, T::Hash>, T::Hash) { + let storage_key = sp_domains::fraud_proof::block_randomness_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, &randomness.encode()).unwrap(); + }; + + let backend = TrieBackendBuilder::new(mdb, root).build(); + let (root, randomness_proof) = storage_proof_for_key::(backend, StorageKey(storage_key)); + 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, + randomness_proof, + valid_bundle_digests, + }), + root, + ) +} + #[test] fn test_basic_fraud_proof_processing() { let creator = 0u64; diff --git a/crates/sp-domains/src/verification.rs b/crates/sp-domains/src/verification.rs index 431ef7f9cf..b2138c3e88 100644 --- a/crates/sp-domains/src/verification.rs +++ b/crates/sp-domains/src/verification.rs @@ -112,11 +112,10 @@ pub fn verify_invalid_domain_extrinsics_root_fraud_proof< DomainHash, Balance, >, - fraud_proof: InvalidExtrinsicsRootProof, + fraud_proof: &InvalidExtrinsicsRootProof, ) -> Result<(), VerificationError> where CBlock: Block, - CBlock::Hash: Into, Hashing: Hasher, { let InvalidExtrinsicsRootProof { @@ -136,7 +135,7 @@ where return Err(VerificationError::InvalidBundleDigest); } - ext_values.extend(bundle_digest.bundle_digest); + ext_values.extend(bundle_digest.bundle_digest.clone()); } let storage_key = StorageKey(crate::fraud_proof::block_randomness_final_key()); @@ -148,11 +147,13 @@ where ) .map_err(|_| VerificationError::InvalidProof)?; - let shuffling_seed = extrinsics_shuffling_seed::(block_randomness) - .into() - .to_fixed_bytes(); - let ordered_extrinsics = - deduplicate_and_shuffle_extrinsics(ext_values, Randomness::from(shuffling_seed)); + let shuffling_seed = + H256::decode(&mut extrinsics_shuffling_seed::(block_randomness).as_ref()) + .map_err(|_| VerificationError::FailedToDecode)?; + let ordered_extrinsics = deduplicate_and_shuffle_extrinsics( + ext_values, + Randomness::from(shuffling_seed.to_fixed_bytes()), + ); let ordered_trie_node_values = ordered_extrinsics .iter() .map(|ext_digest| match ext_digest { From af30f80176854e7c2fe8d790db690f539ed7f31c Mon Sep 17 00:00:00 2001 From: vedhavyas Date: Tue, 26 Sep 2023 17:35:03 +0200 Subject: [PATCH 7/8] derive storage keys from runtime and update comments --- crates/pallet-domains/src/lib.rs | 6 +++++- crates/pallet-domains/src/tests.rs | 15 ++++++++++++++- crates/pallet-subspace/src/lib.rs | 4 ++-- crates/sp-domains/src/fraud_proof.rs | 16 ++++++++++------ crates/sp-domains/src/lib.rs | 3 +++ crates/sp-domains/src/verification.rs | 6 ++++-- crates/subspace-runtime/src/lib.rs | 17 ++++++++++++++++- .../client/domain-operator/src/fraud_proof.rs | 13 ++++++++----- test/subspace-test-runtime/src/lib.rs | 17 ++++++++++++++++- 9 files changed, 78 insertions(+), 19 deletions(-) diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index b854da373e..a031e13147 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -137,7 +137,7 @@ mod pallet { use frame_support::{Identity, PalletError}; use frame_system::pallet_prelude::*; use sp_core::H256; - use sp_domains::fraud_proof::FraudProof; + use sp_domains::fraud_proof::{FraudProof, StorageKeys}; use sp_domains::inherents::{InherentError, InherentType, INHERENT_IDENTIFIER}; use sp_domains::transaction::InvalidTransactionCode; use sp_domains::{ @@ -283,6 +283,9 @@ mod pallet { /// Randomness source. type Randomness: RandomnessT>; + + /// Trait impl to fetch storage keys. + type StorageKeys: StorageKeys; } #[pallet::pallet] @@ -1597,6 +1600,7 @@ impl Pallet { T::DomainHash, BalanceOf, T::Hashing, + T::StorageKeys, >(consensus_state_root, bad_receipt, proof) .map_err(FraudProofError::InvalidExtrinsicRootFraudProof)?; } diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 4f857be86d..5da7622b31 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -191,6 +191,16 @@ impl frame_support::traits::Randomness for MockRandomness { } } +pub struct StorageKeys; +impl sp_domains::fraud_proof::StorageKeys for StorageKeys { + fn block_randomness_key() -> StorageKey { + StorageKey( + frame_support::storage::storage_prefix("Subspace".as_ref(), "BlockRandomness".as_ref()) + .to_vec(), + ) + } +} + impl pallet_domains::Config for Test { type RuntimeEvent = RuntimeEvent; type DomainNumber = BlockNumber; @@ -216,6 +226,7 @@ impl pallet_domains::Config for Test { type MaxPendingStakingOperation = MaxPendingStakingOperation; type SudoId = (); type Randomness = MockRandomness; + type StorageKeys = StorageKeys; } pub(crate) fn new_test_ext() -> sp_io::TestExternalities { @@ -865,7 +876,9 @@ fn generate_invalid_domain_extrinsic_root_fraud_proof( bad_receipt_hash: ReceiptHash, randomness: Randomness, ) -> (FraudProof, T::Hash>, T::Hash) { - let storage_key = sp_domains::fraud_proof::block_randomness_final_key(); + let storage_key = + frame_support::storage::storage_prefix("Subspace".as_ref(), "BlockRandomness".as_ref()) + .to_vec(); let mut root = T::Hash::default(); let mut mdb = PrefixedMemoryDB::::default(); { diff --git a/crates/pallet-subspace/src/lib.rs b/crates/pallet-subspace/src/lib.rs index 94fb559866..1dbae5ef91 100644 --- a/crates/pallet-subspace/src/lib.rs +++ b/crates/pallet-subspace/src/lib.rs @@ -103,7 +103,7 @@ struct VoteVerificationData { struct AuditChunkOffset(u8); #[frame_support::pallet] -mod pallet { +pub mod pallet { use super::{AuditChunkOffset, EraChangeTrigger, VoteVerificationData}; use crate::equivocation::HandleEquivocation; use crate::weights::WeightInfo; @@ -449,7 +449,7 @@ mod pallet { /// The current block randomness, updated at block initialization. When the proof of time feature /// is enabled it derived from PoT otherwise PoR. #[pallet::storage] - pub(super) type BlockRandomness = StorageValue<_, Randomness>; + pub type BlockRandomness = StorageValue<_, Randomness>; /// Enable storage access for all users. #[pallet::storage] diff --git a/crates/sp-domains/src/fraud_proof.rs b/crates/sp-domains/src/fraud_proof.rs index d1e7b048eb..62d31ada49 100644 --- a/crates/sp-domains/src/fraud_proof.rs +++ b/crates/sp-domains/src/fraud_proof.rs @@ -3,6 +3,7 @@ use hash_db::Hasher; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_consensus_slots::Slot; +use sp_core::storage::StorageKey; use sp_core::H256; use sp_runtime::traits::{BlakeTwo256, Hash as HashT, Header as HeaderT}; use sp_std::vec::Vec; @@ -406,6 +407,7 @@ pub struct ImproperTransactionSortitionProof { pub bad_receipt_hash: ReceiptHash, } +/// Represents an invalid total rewards proof. #[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] pub struct InvalidTotalRewardsProof { /// The id of the domain this fraud proof targeted @@ -416,6 +418,7 @@ pub struct InvalidTotalRewardsProof { pub 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 { /// Actual extrinsic data that is inlined since it is less than 33 bytes. @@ -441,6 +444,7 @@ impl ExtrinsicDigest { } } +/// 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. @@ -452,6 +456,7 @@ pub struct ValidBundleDigest { )>, } +/// 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 @@ -474,15 +479,14 @@ impl InvalidTotalRewardsProof { } } +/// Trait to get Storage keys. +pub trait StorageKeys { + fn block_randomness_key() -> StorageKey; +} + /// 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() } - -/// This is a representation of actual Randomness on Consensus chain state. -/// Any change in key or value there should be changed here accordingly. -pub fn block_randomness_final_key() -> Vec { - frame_support::storage::storage_prefix("Subspace".as_ref(), "BlockRandomness".as_ref()).to_vec() -} diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index 2410b051af..98b8b65043 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -800,6 +800,9 @@ sp_api::decl_runtime_apis! { /// Returns the chain state root at the given block. fn domain_state_root(domain_id: DomainId, number: DomainNumber, hash: DomainHash) -> Option; + + /// Block randomness key. + fn block_randomness_key() -> Vec; } pub trait BundleProducerElectionApi { diff --git a/crates/sp-domains/src/verification.rs b/crates/sp-domains/src/verification.rs index b2138c3e88..0e40cf2b2a 100644 --- a/crates/sp-domains/src/verification.rs +++ b/crates/sp-domains/src/verification.rs @@ -1,4 +1,4 @@ -use crate::fraud_proof::{ExtrinsicDigest, InvalidExtrinsicsRootProof}; +use crate::fraud_proof::{ExtrinsicDigest, InvalidExtrinsicsRootProof, StorageKeys}; use crate::valued_trie_root::valued_ordered_trie_root; use crate::{ExecutionReceipt, EXTRINSICS_SHUFFLING_SEED}; use domain_runtime_primitives::opaque::AccountId; @@ -103,6 +103,7 @@ pub fn verify_invalid_domain_extrinsics_root_fraud_proof< DomainHash, Balance, Hashing, + SK, >( consensus_state_root: CBlock::Hash, bad_receipt: ExecutionReceipt< @@ -117,6 +118,7 @@ pub fn verify_invalid_domain_extrinsics_root_fraud_proof< where CBlock: Block, Hashing: Hasher, + SK: StorageKeys, { let InvalidExtrinsicsRootProof { valid_bundle_digests, @@ -138,7 +140,7 @@ where ext_values.extend(bundle_digest.bundle_digest.clone()); } - let storage_key = StorageKey(crate::fraud_proof::block_randomness_final_key()); + let storage_key = SK::block_randomness_key(); let storage_proof = randomness_proof.clone(); let block_randomness = StorageProofVerifier::::verify_and_get_value::( &consensus_state_root, diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index dbc45e3d16..91bf39ed61 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -41,6 +41,7 @@ use domain_runtime_primitives::{ BlockNumber as DomainNumber, Hash as DomainHash, MultiAccountId, TryConvertBack, }; use frame_support::inherent::ProvideInherent; +use frame_support::storage::generator::StorageValue; use frame_support::traits::{ConstU16, ConstU32, ConstU64, ConstU8, Currency, Everything, Get}; use frame_support::weights::constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND}; use frame_support::weights::{ConstantMultiplier, IdentityFee, Weight}; @@ -58,7 +59,7 @@ use sp_consensus_subspace::{ Vote, }; use sp_core::crypto::{ByteArray, KeyTypeId}; -use sp_core::storage::StateVersion; +use sp_core::storage::{StateVersion, StorageKey}; use sp_core::{OpaqueMetadata, H256}; use sp_domains::bundle_producer_election::BundleProducerElectionParams; use sp_domains::{ @@ -619,6 +620,15 @@ parameter_types! { pub SudoId: AccountId = Sudo::key().expect("Sudo account must exist"); } +pub struct StorageKeys; +impl sp_domains::fraud_proof::StorageKeys for StorageKeys { + fn block_randomness_key() -> StorageKey { + StorageKey( + pallet_subspace::pallet::BlockRandomness::::storage_value_final_key().to_vec(), + ) + } +} + impl pallet_domains::Config for Runtime { type RuntimeEvent = RuntimeEvent; type DomainNumber = DomainNumber; @@ -644,6 +654,7 @@ impl pallet_domains::Config for Runtime { type MaxPendingStakingOperation = MaxPendingStakingOperation; type SudoId = SudoId; type Randomness = Subspace; + type StorageKeys = StorageKeys; } pub struct StakingOnReward; @@ -1089,6 +1100,10 @@ impl_runtime_apis! { fn domain_state_root(domain_id: DomainId, number: DomainNumber, hash: DomainHash) -> Option{ Domains::domain_state_root(domain_id, number, hash) } + + fn block_randomness_key() -> Vec { + pallet_subspace::pallet::BlockRandomness::::storage_value_final_key().to_vec() + } } impl sp_domains::BundleProducerElectionApi for Runtime { diff --git a/domains/client/domain-operator/src/fraud_proof.rs b/domains/client/domain-operator/src/fraud_proof.rs index 9436c87d26..898368ab41 100644 --- a/domains/client/domain-operator/src/fraud_proof.rs +++ b/domains/client/domain-operator/src/fraud_proof.rs @@ -175,6 +175,7 @@ where .runtime_api() .extract_successful_bundles(consensus_block_hash, domain_id, consensus_extrinsics)?; + let domain_runtime_api = self.client.runtime_api(); let mut valid_bundle_digests = Vec::with_capacity(local_receipt.valid_bundles.len()); for valid_bundle in local_receipt.valid_bundles.iter() { let bundle_index = valid_bundle.bundle_index; @@ -197,7 +198,6 @@ where exts.push(extrinsic) } - let domain_runtime_api = self.client.runtime_api(); let bundle_digest = domain_runtime_api .extract_signer(local_receipt.domain_block_hash, exts)? .into_iter() @@ -214,10 +214,13 @@ where }); } - let key = sp_domains::fraud_proof::block_randomness_final_key(); - let randomness_proof = self - .consensus_client - .read_proof(consensus_block_hash, &mut [key.as_slice()].into_iter())?; + let consensus_runtime = self.consensus_client.runtime_api(); + let block_randomness_key = consensus_runtime.block_randomness_key(consensus_block_hash)?; + + let randomness_proof = self.consensus_client.read_proof( + consensus_block_hash, + &mut [block_randomness_key.as_slice()].into_iter(), + )?; Ok(FraudProof::InvalidExtrinsicsRoot( InvalidExtrinsicsRootProof { diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index eb131380a9..2520adfdf9 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -29,6 +29,7 @@ use domain_runtime_primitives::{ BlockNumber as DomainNumber, Hash as DomainHash, MultiAccountId, TryConvertBack, }; use frame_support::inherent::ProvideInherent; +use frame_support::storage::generator::StorageValue; use frame_support::traits::{ ConstU128, ConstU16, ConstU32, ConstU64, ConstU8, Currency, ExistenceRequirement, Get, Imbalance, WithdrawReasons, @@ -51,7 +52,7 @@ use sp_consensus_subspace::{ Vote, }; use sp_core::crypto::{ByteArray, KeyTypeId}; -use sp_core::storage::StateVersion; +use sp_core::storage::{StateVersion, StorageKey}; use sp_core::{Hasher, OpaqueMetadata, H256}; use sp_domains::bundle_producer_election::BundleProducerElectionParams; use sp_domains::fraud_proof::FraudProof; @@ -643,6 +644,15 @@ parameter_types! { pub SudoId: AccountId = Sudo::key().expect("Sudo account must exist"); } +pub struct StorageKeys; +impl sp_domains::fraud_proof::StorageKeys for StorageKeys { + fn block_randomness_key() -> StorageKey { + StorageKey( + pallet_subspace::pallet::BlockRandomness::::storage_value_final_key().to_vec(), + ) + } +} + impl pallet_domains::Config for Runtime { type RuntimeEvent = RuntimeEvent; type DomainNumber = DomainNumber; @@ -668,6 +678,7 @@ impl pallet_domains::Config for Runtime { type MaxPendingStakingOperation = MaxPendingStakingOperation; type SudoId = SudoId; type Randomness = Subspace; + type StorageKeys = StorageKeys; } parameter_types! { @@ -1378,6 +1389,10 @@ impl_runtime_apis! { fn domain_state_root(domain_id: DomainId, number: DomainNumber, hash: DomainHash) -> Option{ Domains::domain_state_root(domain_id, number, hash) } + + fn block_randomness_key() -> Vec { + pallet_subspace::pallet::BlockRandomness::::storage_value_final_key().to_vec() + } } impl sp_domains::BundleProducerElectionApi for Runtime { From b700e164b38e2fdf9ed3b918b27bd501a2db3271 Mon Sep 17 00:00:00 2001 From: vedhavyas Date: Mon, 25 Sep 2023 14:10:45 +0200 Subject: [PATCH 8/8] rename confusing variable and constant names --- crates/pallet-domains/src/lib.rs | 5 +++-- crates/sp-domains/src/lib.rs | 4 ++-- crates/sp-domains/src/verification.rs | 12 ++++++------ domains/client/block-preprocessor/src/lib.rs | 2 +- domains/client/domain-operator/src/tests.rs | 4 ++++ 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index a031e13147..86e01e94e9 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -49,7 +49,8 @@ use sp_domains::verification::{ }; use sp_domains::{ DomainBlockLimit, DomainId, DomainInstanceData, ExecutionReceipt, OpaqueBundle, OperatorId, - OperatorPublicKey, ProofOfElection, RuntimeId, EMPTY_EXTRINSIC_ROOT, EXTRINSICS_SHUFFLING_SEED, + OperatorPublicKey, ProofOfElection, RuntimeId, DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT, + EMPTY_EXTRINSIC_ROOT, }; use sp_runtime::traits::{BlakeTwo256, CheckedSub, Hash, One, Zero}; use sp_runtime::{RuntimeAppPublic, SaturatedConversion, Saturating}; @@ -1783,7 +1784,7 @@ impl Pallet { } pub fn extrinsics_shuffling_seed() -> T::Hash { - let seed = EXTRINSICS_SHUFFLING_SEED; + let seed = DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT; let (randomness, _) = T::Randomness::random(seed); randomness } diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index 98b8b65043..3c001b6ede 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -55,7 +55,7 @@ use subspace_runtime_primitives::{Balance, Moment}; const KEY_TYPE: KeyTypeId = KeyTypeId(*b"oper"); /// Extrinsics shuffling seed -pub const EXTRINSICS_SHUFFLING_SEED: &[u8] = b"extrinsics-shuffling-seed"; +pub const DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT: &[u8] = b"extrinsics-shuffling-seed"; mod app { use super::KEY_TYPE; @@ -733,7 +733,7 @@ pub struct ValidBundle { /// Index of this bundle in the original list of bundles in the consensus block. pub bundle_index: u32, /// Hash of `Vec<(tx_signer, tx_hash)>` of all domain extrinsic being included in the bundle. - pub bundle_digest: H256, + pub bundle_digest_hash: H256, } #[derive(Debug, Decode, Encode, TypeInfo, Clone, PartialEq, Eq)] diff --git a/crates/sp-domains/src/verification.rs b/crates/sp-domains/src/verification.rs index 0e40cf2b2a..f8990df01c 100644 --- a/crates/sp-domains/src/verification.rs +++ b/crates/sp-domains/src/verification.rs @@ -1,6 +1,6 @@ use crate::fraud_proof::{ExtrinsicDigest, InvalidExtrinsicsRootProof, StorageKeys}; use crate::valued_trie_root::valued_ordered_trie_root; -use crate::{ExecutionReceipt, EXTRINSICS_SHUFFLING_SEED}; +use crate::{ExecutionReceipt, DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT}; use domain_runtime_primitives::opaque::AccountId; use frame_support::PalletError; use hash_db::Hasher; @@ -126,18 +126,18 @@ where .. } = fraud_proof; - let mut ext_values = Vec::new(); + let mut bundle_extrinsics_digests = Vec::new(); for (valid_bundle, bundle_digest) in bad_receipt .valid_bundles .into_iter() .zip(valid_bundle_digests) { let bundle_digest_hash = BlakeTwo256::hash_of(&bundle_digest.bundle_digest); - if bundle_digest_hash != valid_bundle.bundle_digest { + if bundle_digest_hash != valid_bundle.bundle_digest_hash { return Err(VerificationError::InvalidBundleDigest); } - ext_values.extend(bundle_digest.bundle_digest.clone()); + bundle_extrinsics_digests.extend(bundle_digest.bundle_digest.clone()); } let storage_key = SK::block_randomness_key(); @@ -153,7 +153,7 @@ where H256::decode(&mut extrinsics_shuffling_seed::(block_randomness).as_ref()) .map_err(|_| VerificationError::FailedToDecode)?; let ordered_extrinsics = deduplicate_and_shuffle_extrinsics( - ext_values, + bundle_extrinsics_digests, Randomness::from(shuffling_seed.to_fixed_bytes()), ); let ordered_trie_node_values = ordered_extrinsics @@ -177,7 +177,7 @@ fn extrinsics_shuffling_seed(block_randomness: Randomness) -> Hashing:: where Hashing: Hasher, { - let mut subject = EXTRINSICS_SHUFFLING_SEED.to_vec(); + let mut subject = DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT.to_vec(); subject.extend_from_slice(block_randomness.as_ref()); Hashing::hash(&subject) } diff --git a/domains/client/block-preprocessor/src/lib.rs b/domains/client/block-preprocessor/src/lib.rs index 8fd2936d6c..6a713bd569 100644 --- a/domains/client/block-preprocessor/src/lib.rs +++ b/domains/client/block-preprocessor/src/lib.rs @@ -294,7 +294,7 @@ where .collect(); valid_bundles.push(ValidBundle { bundle_index: index as u32, - bundle_digest: BlakeTwo256::hash_of(&bundle_digest), + bundle_digest_hash: BlakeTwo256::hash_of(&bundle_digest), }); valid_extrinsics.extend(extrinsics); } diff --git a/domains/client/domain-operator/src/tests.rs b/domains/client/domain-operator/src/tests.rs index 621e5de2ca..6799d7fd31 100644 --- a/domains/client/domain-operator/src/tests.rs +++ b/domains/client/domain-operator/src/tests.rs @@ -1088,6 +1088,7 @@ async fn test_invalid_domain_extrinsics_root_proof_creation() { // When the domain node operator process the primary block that contains the `bad_submit_bundle_tx`, // it will generate and submit a fraud proof + let mut fraud_proof_submitted = false; while let Some(ready_tx_hash) = import_tx_stream.next().await { let ready_tx = ferdie .transaction_pool @@ -1104,11 +1105,14 @@ async fn test_invalid_domain_extrinsics_root_proof_creation() { if let FraudProof::InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof { .. }) = *fraud_proof { + fraud_proof_submitted = true; break; } } } + assert!(fraud_proof_submitted, "Fraud proof must be submitted"); + // 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();