From 4e19d9ff91df7edd5de9c7ab974baf737cf26de0 Mon Sep 17 00:00:00 2001 From: vedhavyas Date: Thu, 26 Oct 2023 14:14:03 +0200 Subject: [PATCH 1/5] remove unnecessary type conversions and unneeded new types for invalid bundle verification --- .../sp-domains-fraud-proof/src/fraud_proof.rs | 35 ++---- .../src/verification.rs | 119 +++++++----------- .../client/domain-operator/src/fraud_proof.rs | 16 ++- 3 files changed, 62 insertions(+), 108 deletions(-) diff --git a/crates/sp-domains-fraud-proof/src/fraud_proof.rs b/crates/sp-domains-fraud-proof/src/fraud_proof.rs index 5f814f4a48..a7a82ae16a 100644 --- a/crates/sp-domains-fraud-proof/src/fraud_proof.rs +++ b/crates/sp-domains-fraud-proof/src/fraud_proof.rs @@ -68,7 +68,7 @@ impl ExecutionPhase { pub fn decode_execution_result( &self, execution_result: Vec, - ) -> Result { + ) -> Result> { match self { Self::InitializeBlock | Self::ApplyExtrinsic { .. } => { let encoded_storage_root = Vec::::decode(&mut execution_result.as_slice()) @@ -88,7 +88,7 @@ impl ExecutionPhase { &self, bad_receipt: &ExecutionReceiptFor, bad_receipt_parent: &ExecutionReceiptFor, - ) -> Result<(H256, H256), VerificationError> + ) -> Result<(H256, H256), VerificationError> where CBlock: BlockT, DomainHeader: HeaderT, @@ -128,7 +128,7 @@ impl ExecutionPhase { &self, bad_receipt: &ExecutionReceiptFor, bad_receipt_parent: &ExecutionReceiptFor, - ) -> Result, VerificationError> + ) -> Result, VerificationError> where CBlock: BlockT, DomainHeader: HeaderT, @@ -177,7 +177,7 @@ impl ExecutionPhase { /// Error type of fraud proof verification on consensus node. #[derive(Debug)] #[cfg_attr(feature = "thiserror", derive(thiserror::Error))] -pub enum VerificationError { +pub enum VerificationError { /// Hash of the consensus block being challenged not found. #[cfg_attr(feature = "thiserror", error("consensus block hash not found"))] ConsensusBlockHashNotFound, @@ -301,7 +301,7 @@ pub enum VerificationError { UnexpectedTargetedBundleEntry { bundle_index: u32, fraud_proof_invalid_type_of_proof: InvalidBundleType, - targeted_entry_bundle: BundleValidity, + targeted_entry_bundle: BundleValidity, }, /// Tx range host function did not return response (returned None) #[cfg_attr( @@ -309,12 +309,6 @@ pub enum VerificationError { error("Tx range host function did not return a response (returned None)") )] FailedToGetResponseFromTxRangeHostFn, - /// Unable to receive tx range from host function - #[cfg_attr( - feature = "thiserror", - error("Received invalid information from tx range host function") - )] - ReceivedInvalidInfoFromTxRangeHostFn, /// Failed to get the bundle body #[cfg_attr(feature = "thiserror", error("Failed to get the bundle body"))] FailedToGetDomainBundleBody, @@ -329,21 +323,14 @@ pub enum VerificationError { TargetValidBundleNotFound, } -/// Proof data specific to each *expected* invalid bundle type -#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] -pub enum ProofDataPerInvalidBundleType { - OutOfRangeTx, -} - #[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] pub struct InvalidBundlesFraudProof { pub bad_receipt_hash: ReceiptHash, pub domain_id: DomainId, pub bundle_index: u32, - pub mismatched_extrinsic_index: u32, - pub extrinsic_inclusion_proof: Vec>, + pub invalid_bundle_type: InvalidBundleType, + pub extrinsic_inclusion_proof: StorageProof, pub is_true_invalid_fraud_proof: bool, - pub proof_data: ProofDataPerInvalidBundleType, } impl InvalidBundlesFraudProof { @@ -351,19 +338,17 @@ impl InvalidBundlesFraudProof { bad_receipt_hash: ReceiptHash, domain_id: DomainId, bundle_index: u32, - mismatched_extrinsic_index: u32, - extrinsic_inclusion_proof: Vec>, + invalid_bundle_type: InvalidBundleType, + extrinsic_inclusion_proof: StorageProof, is_true_invalid_fraud_proof: bool, - proof_data: ProofDataPerInvalidBundleType, ) -> Self { Self { bad_receipt_hash, domain_id, bundle_index, - mismatched_extrinsic_index, + invalid_bundle_type, extrinsic_inclusion_proof, is_true_invalid_fraud_proof, - proof_data, } } } diff --git a/crates/sp-domains-fraud-proof/src/verification.rs b/crates/sp-domains-fraud-proof/src/verification.rs index 0f1ff781e6..319f93fe10 100644 --- a/crates/sp-domains-fraud-proof/src/verification.rs +++ b/crates/sp-domains-fraud-proof/src/verification.rs @@ -1,7 +1,6 @@ use crate::fraud_proof::{ ExtrinsicDigest, InvalidBundlesFraudProof, InvalidExtrinsicsRootProof, - InvalidStateTransitionProof, ProofDataPerInvalidBundleType, ValidBundleProof, - VerificationError, + InvalidStateTransitionProof, ValidBundleProof, VerificationError, }; use crate::fraud_proof_runtime_interface::get_fraud_proof_verification_info; use crate::{ @@ -20,7 +19,7 @@ use sp_domains::{ InboxedBundle, InvalidBundleType, }; use sp_runtime::generic::Digest; -use sp_runtime::traits::{BlakeTwo256, Block as BlockT, Hash, Header as HeaderT, NumberFor}; +use sp_runtime::traits::{Block as BlockT, Hash, Header as HeaderT, NumberFor}; use sp_std::vec::Vec; use sp_trie::{LayoutV1, StorageProof}; use subspace_core_primitives::Randomness; @@ -36,7 +35,7 @@ pub fn verify_invalid_domain_extrinsics_root_fraud_proof, fraud_proof: &InvalidExtrinsicsRootProof>, -) -> Result<(), VerificationError> +) -> Result<(), VerificationError> where CBlock: BlockT, Hashing: Hasher, @@ -136,7 +135,7 @@ pub fn verify_valid_bundle_fraud_proof, fraud_proof: &ValidBundleProof, -) -> Result<(), VerificationError> +) -> Result<(), VerificationError> where CBlock: BlockT, CBlock::Hash: Into, @@ -148,7 +147,7 @@ where .. } = fraud_proof; - let bundle_body = fraud_proof_runtime_interface::get_fraud_proof_verification_info( + let bundle_body = get_fraud_proof_verification_info( bad_receipt.consensus_block_hash.into(), FraudProofVerificationInfoRequest::DomainBundleBody { domain_id: *domain_id, @@ -193,7 +192,7 @@ pub fn verify_invalid_state_transition_fraud_proof, fraud_proof: &InvalidStateTransitionProof>, -) -> Result<(), VerificationError> +) -> Result<(), VerificationError> where CBlock: BlockT, CBlock::Hash: Into, @@ -251,7 +250,7 @@ pub fn verify_invalid_domain_block_hash_fraud_proof, digest_storage_proof: StorageProof, parent_domain_block_hash: DomainHeader::Hash, -) -> Result<(), VerificationError> +) -> Result<(), VerificationError> where CBlock: BlockT, Balance: PartialEq + Decode, @@ -298,7 +297,7 @@ pub fn verify_invalid_total_rewards_fraud_proof< Balance, >, storage_proof: &StorageProof, -) -> Result<(), VerificationError> +) -> Result<(), VerificationError> where CBlock: BlockT, Balance: PartialEq + Decode, @@ -322,54 +321,34 @@ where Ok(()) } -fn convert_bundle_validity_to_generic( - bundle_validity: BundleValidity, -) -> BundleValidity -where - DomainHash: Into + Clone, -{ - match bundle_validity { - BundleValidity::Invalid(invalid_bundle_type) => { - BundleValidity::Invalid(invalid_bundle_type.clone()) - } - BundleValidity::Valid(hash) => BundleValidity::Valid(hash.clone().into()), - } -} - /// This function checks if this fraud proof is expected against the inboxed bundle entry it is targeting. /// If the entry is expected then it will be returned /// In any other cases VerificationError will be returned -fn check_expected_bundle_entry( +fn check_expected_bundle_entry( bad_receipt: &ExecutionReceipt< NumberFor, CBlock::Hash, - DomainNumber, - DomainHash, + HeaderNumberFor, + HeaderHashFor, Balance, >, - invalid_bundle_fraud_proof: &InvalidBundlesFraudProof, -) -> Result, VerificationError> + invalid_bundle_fraud_proof: &InvalidBundlesFraudProof, +) -> Result>, VerificationError> where CBlock: BlockT, - DomainHash: Into + PartialEq + Clone, + DomainHeader: HeaderT, { let targeted_invalid_bundle_entry = bad_receipt .inboxed_bundles .get(invalid_bundle_fraud_proof.bundle_index as usize) .ok_or(VerificationError::BundleNotFound)?; - let invalid_type_of_proof = match invalid_bundle_fraud_proof.proof_data { - ProofDataPerInvalidBundleType::OutOfRangeTx => { - InvalidBundleType::OutOfRangeTx(invalid_bundle_fraud_proof.mismatched_extrinsic_index) - } - }; - + let invalid_bundle_type = invalid_bundle_fraud_proof.invalid_bundle_type.clone(); let is_expected = if !invalid_bundle_fraud_proof.is_true_invalid_fraud_proof { // `FalseInvalid` // The proof trying to prove `bad_receipt_bundle`'s `invalid_bundle_type` is wrong, // so the proof should contains the same `invalid_bundle_type` - targeted_invalid_bundle_entry.bundle - == BundleValidity::Invalid(invalid_type_of_proof.clone()) + targeted_invalid_bundle_entry.bundle == BundleValidity::Invalid(invalid_bundle_type.clone()) } else { // `TrueInvalid` match &targeted_invalid_bundle_entry.bundle { @@ -379,11 +358,11 @@ where BundleValidity::Invalid(invalid_type) => { // The proof trying to prove there is an invalid extrinsic that the `bad_receipt_bundle` think is valid, // so the proof should point to an extrinsic that in front of the `bad_receipt_bundle`'s - invalid_type_of_proof.extrinsic_index() < invalid_type.extrinsic_index() || + invalid_bundle_type.extrinsic_index() < invalid_type.extrinsic_index() || // The proof trying to prove the invalid extrinsic can not pass a check that the `bad_receipt_bundle` think it can, // so the proof should point to the same extrinsic and a check that perform before the `bad_receipt_bundle`'s - (invalid_type_of_proof.extrinsic_index() == invalid_type.extrinsic_index() - && invalid_type_of_proof.checking_order() < invalid_type.checking_order()) + (invalid_bundle_type.extrinsic_index() == invalid_type.extrinsic_index() + && invalid_bundle_type.checking_order() < invalid_type.checking_order()) } } }; @@ -391,10 +370,8 @@ where if !is_expected { return Err(VerificationError::UnexpectedTargetedBundleEntry { bundle_index: invalid_bundle_fraud_proof.bundle_index, - fraud_proof_invalid_type_of_proof: invalid_type_of_proof, - targeted_entry_bundle: convert_bundle_validity_to_generic( - targeted_invalid_bundle_entry.bundle.clone(), - ), + fraud_proof_invalid_type_of_proof: invalid_bundle_type, + targeted_entry_bundle: targeted_invalid_bundle_entry.bundle.clone(), }); } @@ -410,41 +387,34 @@ pub fn verify_invalid_bundles_fraud_proof( Balance, >, invalid_bundles_fraud_proof: &InvalidBundlesFraudProof>, -) -> Result<(), VerificationError> +) -> Result<(), VerificationError> where CBlock: BlockT, - CBlock::Hash: Into, DomainHeader: HeaderT, - DomainHeader::Hash: Into + From + PartialEq + Clone, { - let invalid_bundle_entry = check_expected_bundle_entry::< - CBlock, - ::Number, - ::Hash, - Balance, - ::Hash, - >(&bad_receipt, invalid_bundles_fraud_proof)?; + let invalid_bundle_entry = check_expected_bundle_entry::( + &bad_receipt, + invalid_bundles_fraud_proof, + )?; - let extrinsic = StorageProofVerifier::::get_decoded_value( - &invalid_bundle_entry.extrinsics_root.into(), - StorageProof::new( - invalid_bundles_fraud_proof - .extrinsic_inclusion_proof - .clone() - .drain(..), - ), - StorageKey( + let storage_key = + StorageProofVerifier::>::enumerated_storage_key( invalid_bundles_fraud_proof - .mismatched_extrinsic_index - .to_be_bytes() - .to_vec(), - ), + .invalid_bundle_type + .extrinsic_index(), + ); + let extrinsic = StorageProofVerifier::>::get_decoded_value( + &invalid_bundle_entry.extrinsics_root, + invalid_bundles_fraud_proof + .extrinsic_inclusion_proof + .clone(), + storage_key, ) .map_err(|_e| VerificationError::InvalidProof)?; - match invalid_bundles_fraud_proof.proof_data { - ProofDataPerInvalidBundleType::OutOfRangeTx => { - let is_tx_in_range = fraud_proof_runtime_interface::get_fraud_proof_verification_info( + match invalid_bundles_fraud_proof.invalid_bundle_type { + InvalidBundleType::OutOfRangeTx(_) => { + let is_tx_in_range = get_fraud_proof_verification_info( H256::from_slice(bad_receipt.consensus_block_hash.as_ref()), FraudProofVerificationInfoRequest::TxRangeCheck { domain_id: invalid_bundles_fraud_proof.domain_id, @@ -452,10 +422,8 @@ where opaque_extrinsic: extrinsic, }, ) - // TODO: In future if we start getting errors from host function, change below errors to include the underlying error - .ok_or(VerificationError::FailedToGetResponseFromTxRangeHostFn)? - .into_tx_range_check() - .ok_or(VerificationError::ReceivedInvalidInfoFromTxRangeHostFn)?; + .and_then(FraudProofVerificationInfoResponse::into_tx_range_check) + .ok_or(VerificationError::FailedToGetResponseFromTxRangeHostFn)?; // If it is true invalid fraud proof then tx must not be in range and // if it is false invalid fraud proof then tx must be in range for fraud @@ -465,5 +433,8 @@ where } Ok(()) } + + // TODO: implement the other invalid bundle types + _ => Err(VerificationError::InvalidProof), } } diff --git a/domains/client/domain-operator/src/fraud_proof.rs b/domains/client/domain-operator/src/fraud_proof.rs index b2c1d49554..58f1aca491 100644 --- a/domains/client/domain-operator/src/fraud_proof.rs +++ b/domains/client/domain-operator/src/fraud_proof.rs @@ -15,7 +15,7 @@ use sp_domains_fraud_proof::execution_prover::ExecutionProver; use sp_domains_fraud_proof::fraud_proof::{ ExecutionPhase, ExtrinsicDigest, FraudProof, InvalidBundlesFraudProof, InvalidDomainBlockHashProof, InvalidExtrinsicsRootProof, InvalidStateTransitionProof, - InvalidTotalRewardsProof, ProofDataPerInvalidBundleType, ValidBundleDigest, + InvalidTotalRewardsProof, ValidBundleDigest, }; use sp_runtime::traits::{BlakeTwo256, Block as BlockT, HashingFor, Header as HeaderT, NumberFor}; use sp_runtime::{Digest, DigestItem}; @@ -159,27 +159,25 @@ where { match mismatch_type { // TODO: Generate a proper proof once fields are in place - BundleMismatchType::TrueInvalid(_invalid_type) => { + BundleMismatchType::TrueInvalid(invalid_type) => { Ok(FraudProof::InvalidBundles(InvalidBundlesFraudProof::new( bad_receipt_hash, domain_id, bundle_index, - 0, - vec![], + invalid_type, + StorageProof::empty(), true, - ProofDataPerInvalidBundleType::OutOfRangeTx, ))) } // TODO: Generate a proper proof once fields are in place - BundleMismatchType::FalseInvalid(_invalid_type) => { + BundleMismatchType::FalseInvalid(invalid_type) => { Ok(FraudProof::InvalidBundles(InvalidBundlesFraudProof::new( bad_receipt_hash, domain_id, bundle_index, - 0, - vec![], + invalid_type, + StorageProof::empty(), false, - ProofDataPerInvalidBundleType::OutOfRangeTx, ))) } BundleMismatchType::Valid => Err(sp_blockchain::Error::Application( From 426e085c2aa110cd98cfa2e79139d13a5c26ba3d Mon Sep 17 00:00:00 2001 From: vedhavyas Date: Thu, 26 Oct 2023 14:39:17 +0200 Subject: [PATCH 2/5] generate fraud proof for all invalid, true and false, bundles --- .../client/domain-operator/src/fraud_proof.rs | 81 ++++++++++++------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/domains/client/domain-operator/src/fraud_proof.rs b/domains/client/domain-operator/src/fraud_proof.rs index 58f1aca491..bf281013cf 100644 --- a/domains/client/domain-operator/src/fraud_proof.rs +++ b/domains/client/domain-operator/src/fraud_proof.rs @@ -10,7 +10,7 @@ use sp_blockchain::HeaderBackend; use sp_core::traits::CodeExecutor; use sp_domain_digests::AsPredigest; use sp_domains::proof_provider_and_verifier::StorageProofProvider; -use sp_domains::{DomainId, DomainsApi}; +use sp_domains::{DomainId, DomainsApi, HeaderHashingFor}; use sp_domains_fraud_proof::execution_prover::ExecutionProver; use sp_domains_fraud_proof::fraud_proof::{ ExecutionPhase, ExtrinsicDigest, FraudProof, InvalidBundlesFraudProof, @@ -38,6 +38,8 @@ pub enum FraudProofError { InvalidBundleExtrinsic { bundle_index: usize }, #[error("Fail to generate the proof of inclusion")] FailToGenerateProofOfInclusion, + #[error("Missing extrinsics in the Consesnsus block")] + MissingConsensusExtrinsics, } pub struct FraudProofGenerator { @@ -149,7 +151,7 @@ where pub(crate) fn generate_invalid_bundle_field_proof( &self, domain_id: DomainId, - _local_receipt: &ExecutionReceiptFor, + local_receipt: &ExecutionReceiptFor, mismatch_type: BundleMismatchType, bundle_index: u32, bad_receipt_hash: Block::Hash, @@ -157,36 +159,53 @@ where where PCB: BlockT, { - match mismatch_type { - // TODO: Generate a proper proof once fields are in place - BundleMismatchType::TrueInvalid(invalid_type) => { - Ok(FraudProof::InvalidBundles(InvalidBundlesFraudProof::new( - bad_receipt_hash, - domain_id, - bundle_index, - invalid_type, - StorageProof::empty(), - true, - ))) - } - // TODO: Generate a proper proof once fields are in place - BundleMismatchType::FalseInvalid(invalid_type) => { - Ok(FraudProof::InvalidBundles(InvalidBundlesFraudProof::new( - bad_receipt_hash, - domain_id, - bundle_index, - invalid_type, - StorageProof::empty(), - false, - ))) + let consensus_block_hash = local_receipt.consensus_block_hash; + let consensus_extrinsics = self + .consensus_client + .block_body(consensus_block_hash)? + .ok_or(FraudProofError::MissingConsensusExtrinsics)?; + + let bundles = self + .consensus_client + .runtime_api() + .extract_successful_bundles(consensus_block_hash, domain_id, consensus_extrinsics)?; + + let bundle = bundles + .get(bundle_index as usize) + .ok_or(FraudProofError::MissingBundle { + bundle_index: bundle_index as usize, + })?; + + let (invalid_type, is_true_invalid) = match mismatch_type { + BundleMismatchType::TrueInvalid(invalid_type) => (invalid_type, true), + BundleMismatchType::FalseInvalid(invalid_type) => (invalid_type, false), + BundleMismatchType::Valid => { + return Err(sp_blockchain::Error::Application( + "Unexpected bundle mismatch type, this should not happen" + .to_string() + .into(), + ) + .into()) } - BundleMismatchType::Valid => Err(sp_blockchain::Error::Application( - "Unexpected bundle mismatch type, this should not happen" - .to_string() - .into(), - ) - .into()), - } + }; + + let extrinsic_index = invalid_type.extrinsic_index(); + let encoded_extrinsics: Vec<_> = bundle.extrinsics.iter().map(Encode::encode).collect(); + let extrinsic_inclusion_proof = StorageProofProvider::< + LayoutV1>, + >::generate_enumerated_proof_of_inclusion( + encoded_extrinsics.as_slice(), extrinsic_index + ) + .ok_or(FraudProofError::FailToGenerateProofOfInclusion)?; + + Ok(FraudProof::InvalidBundles(InvalidBundlesFraudProof::new( + bad_receipt_hash, + domain_id, + bundle_index, + invalid_type, + extrinsic_inclusion_proof, + is_true_invalid, + ))) } pub(crate) fn generate_invalid_domain_extrinsics_root_proof( From f3bff92a760047464fef4d7b023ce423f301976e Mon Sep 17 00:00:00 2001 From: vedhavyas Date: Thu, 26 Oct 2023 17:17:41 +0200 Subject: [PATCH 3/5] verify true and false invalid bundle for inherent extrinsic --- crates/pallet-domains/src/tests.rs | 164 +++++++++++++++++- .../sp-domains-fraud-proof/src/fraud_proof.rs | 6 + .../src/host_functions.rs | 32 ++++ crates/sp-domains-fraud-proof/src/lib.rs | 19 +- .../src/verification.rs | 20 +++ crates/sp-domains/src/lib.rs | 2 + 6 files changed, 236 insertions(+), 7 deletions(-) diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 4c8c97c20f..27fc71a819 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -19,15 +19,16 @@ use sp_core::crypto::Pair; use sp_core::storage::{StateVersion, StorageKey}; use sp_core::{Get, H256, U256}; use sp_domains::merkle_tree::MerkleTree; +use sp_domains::proof_provider_and_verifier::StorageProofProvider; use sp_domains::storage::RawGenesis; use sp_domains::{ - BundleHeader, DomainId, DomainsHoldIdentifier, ExecutionReceipt, InboxedBundle, OpaqueBundle, - OperatorAllowList, OperatorId, OperatorPair, ProofOfElection, RuntimeType, SealedBundleHeader, - StakingHoldIdentifier, + BundleHeader, DomainId, DomainsHoldIdentifier, ExecutionReceipt, InboxedBundle, + InvalidBundleType, OpaqueBundle, OperatorAllowList, OperatorId, OperatorPair, ProofOfElection, + RuntimeType, SealedBundleHeader, StakingHoldIdentifier, }; use sp_domains_fraud_proof::fraud_proof::{ - ExtrinsicDigest, FraudProof, InvalidDomainBlockHashProof, InvalidExtrinsicsRootProof, - InvalidTotalRewardsProof, ValidBundleDigest, + ExtrinsicDigest, FraudProof, InvalidBundlesFraudProof, InvalidDomainBlockHashProof, + InvalidExtrinsicsRootProof, InvalidTotalRewardsProof, ValidBundleDigest, }; use sp_domains_fraud_proof::{ FraudProofExtension, FraudProofHostFunctions, FraudProofVerificationInfoRequest, @@ -39,7 +40,7 @@ use sp_state_machine::backend::AsTrieBackend; use sp_state_machine::{prove_read, Backend, TrieBackendBuilder}; use sp_std::sync::Arc; use sp_trie::trie_types::TrieDBMutBuilderV1; -use sp_trie::{PrefixedMemoryDB, StorageProof, TrieMut}; +use sp_trie::{LayoutV1, PrefixedMemoryDB, StorageProof, TrieMut}; use sp_version::RuntimeVersion; use std::sync::atomic::{AtomicU64, Ordering}; use subspace_core_primitives::{Randomness, U256 as P256}; @@ -256,6 +257,7 @@ pub(crate) struct MockDomainFraudProofExtension { timestamp: Moment, runtime_code: Vec, tx_range: bool, + is_inherent: bool, } impl FraudProofHostFunctions for MockDomainFraudProofExtension { @@ -301,6 +303,9 @@ impl FraudProofHostFunctions for MockDomainFraudProofExtension { FraudProofVerificationInfoRequest::TxRangeCheck { .. } => { FraudProofVerificationInfoResponse::TxRangeCheck(self.tx_range) } + FraudProofVerificationInfoRequest::InherentExtrinsicCheck { .. } => { + FraudProofVerificationInfoResponse::InherentExtrinsicCheck(self.is_inherent) + } }; Some(response) @@ -968,6 +973,7 @@ fn test_invalid_domain_extrinsic_root_proof() { timestamp: 1000, runtime_code: vec![1, 2, 3, 4], tx_range: true, + is_inherent: true, })); ext.register_extension(fraud_proof_ext); @@ -992,6 +998,152 @@ fn generate_invalid_domain_extrinsic_root_fraud_proof::get(domain_id), + head_domain_number + ); + + let inherent_extrinsic = vec![1, 2, 3].encode(); + let extrinsics = vec![inherent_extrinsic]; + let bundle_extrinsic_root = + BlakeTwo256::ordered_trie_root(extrinsics.clone(), StateVersion::V1); + + let bad_receipt_at = 8; + let mut domain_block = get_block_tree_node_at::(domain_id, bad_receipt_at).unwrap(); + let bad_receipt = &mut domain_block.execution_receipt; + // bad receipt marks this particular bundle as valid even though bundle contains inherent extrinsic + bad_receipt.inboxed_bundles = + vec![InboxedBundle::valid(H256::random(), bundle_extrinsic_root)]; + bad_receipt.domain_block_extrinsic_root = H256::random(); + + let bad_receipt_hash = bad_receipt.hash::>(); + let fraud_proof = generate_invalid_bundle_inherent_extrinsic_fraud_proof::( + domain_id, + bad_receipt_hash, + 0, + 0, + extrinsics, + true, + ); + let (consensus_block_number, consensus_block_hash) = ( + bad_receipt.consensus_block_number, + bad_receipt.consensus_block_hash, + ); + ConsensusBlockHash::::insert(domain_id, consensus_block_number, consensus_block_hash); + BlockTreeNodes::::insert(bad_receipt_hash, domain_block); + fraud_proof + }); + + let fraud_proof_ext = FraudProofExtension::new(Arc::new(MockDomainFraudProofExtension { + block_randomness: Randomness::from([1u8; 32]), + timestamp: 1000, + runtime_code: vec![1, 2, 3, 4], + tx_range: true, + // return `true` indicating this is an inherent extrinsic + is_inherent: true, + })); + ext.register_extension(fraud_proof_ext); + + ext.execute_with(|| { + assert_ok!(Domains::validate_fraud_proof(&fraud_proof),); + }) +} + +#[test] +fn test_false_invalid_bundles_inherent_extrinsic_proof() { + let creator = 0u64; + let operator_id = 1u64; + let head_domain_number = 10; + let mut ext = new_test_ext_with_extensions(); + let fraud_proof = ext.execute_with(|| { + let domain_id = register_genesis_domain(creator, vec![operator_id]); + extend_block_tree(domain_id, operator_id, head_domain_number + 1); + assert_eq!( + HeadReceiptNumber::::get(domain_id), + head_domain_number + ); + + let non_inherent_extrinsic = vec![1, 2, 3].encode(); + let extrinsics = vec![non_inherent_extrinsic]; + let bundle_extrinsic_root = + BlakeTwo256::ordered_trie_root(extrinsics.clone(), StateVersion::V1); + + let bad_receipt_at = 8; + let mut domain_block = get_block_tree_node_at::(domain_id, bad_receipt_at).unwrap(); + let bad_receipt = &mut domain_block.execution_receipt; + // bad receipt marks this bundle as invalid even though bundle do not contain inherent extrinsic. + bad_receipt.inboxed_bundles = vec![InboxedBundle::invalid( + InvalidBundleType::InherentExtrinsic(0), + bundle_extrinsic_root, + )]; + bad_receipt.domain_block_extrinsic_root = H256::random(); + + let bad_receipt_hash = bad_receipt.hash::>(); + let fraud_proof = generate_invalid_bundle_inherent_extrinsic_fraud_proof::( + domain_id, + bad_receipt_hash, + 0, + 0, + extrinsics, + false, + ); + let (consensus_block_number, consensus_block_hash) = ( + bad_receipt.consensus_block_number, + bad_receipt.consensus_block_hash, + ); + ConsensusBlockHash::::insert(domain_id, consensus_block_number, consensus_block_hash); + BlockTreeNodes::::insert(bad_receipt_hash, domain_block); + fraud_proof + }); + + let fraud_proof_ext = FraudProofExtension::new(Arc::new(MockDomainFraudProofExtension { + block_randomness: Randomness::from([1u8; 32]), + timestamp: 1000, + runtime_code: vec![1, 2, 3, 4], + tx_range: true, + // return `false` indicating this is not an inherent extrinsic + is_inherent: false, + })); + ext.register_extension(fraud_proof_ext); + + ext.execute_with(|| { + assert_ok!(Domains::validate_fraud_proof(&fraud_proof),); + }) +} + +fn generate_invalid_bundle_inherent_extrinsic_fraud_proof( + domain_id: DomainId, + bad_receipt_hash: ReceiptHashFor, + bundle_index: u32, + bundle_extrinsic_index: u32, + bundle_extrinsics: Vec>, + is_true_invalid_fraud_proof: bool, +) -> FraudProof, T::Hash, T::DomainHeader> { + let extrinsic_inclusion_proof = + StorageProofProvider::>::generate_enumerated_proof_of_inclusion( + bundle_extrinsics.as_slice(), + bundle_extrinsic_index, + ) + .unwrap(); + FraudProof::InvalidBundles(InvalidBundlesFraudProof { + domain_id, + bad_receipt_hash, + bundle_index, + invalid_bundle_type: InvalidBundleType::InherentExtrinsic(bundle_extrinsic_index), + extrinsic_inclusion_proof, + is_true_invalid_fraud_proof, + }) +} + #[test] fn test_invalid_domain_block_hash_fraud_proof() { let creator = 0u64; diff --git a/crates/sp-domains-fraud-proof/src/fraud_proof.rs b/crates/sp-domains-fraud-proof/src/fraud_proof.rs index a7a82ae16a..d2f7be3abd 100644 --- a/crates/sp-domains-fraud-proof/src/fraud_proof.rs +++ b/crates/sp-domains-fraud-proof/src/fraud_proof.rs @@ -321,6 +321,12 @@ pub enum VerificationError { error("The target valid bundle not found from the target bad receipt") )] TargetValidBundleNotFound, + /// Failed to check if a given extrinsic is inherent or not. + #[cfg_attr( + feature = "thiserror", + error("Failed to check if a given extrinsic is inherent or not") + )] + FailedToCheckInherentExtrinsic, } #[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] diff --git a/crates/sp-domains-fraud-proof/src/host_functions.rs b/crates/sp-domains-fraud-proof/src/host_functions.rs index bedfb1e9dd..bef967f70c 100644 --- a/crates/sp-domains-fraud-proof/src/host_functions.rs +++ b/crates/sp-domains-fraud-proof/src/host_functions.rs @@ -253,6 +253,30 @@ where &domain_tx_range, ).ok() } + + fn is_inherent_extrinsic( + &self, + consensus_block_hash: H256, + domain_id: DomainId, + opaque_extrinsic: OpaqueExtrinsic, + ) -> Option { + let runtime_code = self.get_domain_runtime_code(consensus_block_hash, domain_id)?; + + let domain_runtime_api_light = + RuntimeApiLight::new(self.executor.clone(), runtime_code.into()); + + let encoded_extrinsic = opaque_extrinsic.encode(); + let extrinsic = + ::Extrinsic::decode(&mut encoded_extrinsic.as_slice()).ok()?; + + as domain_runtime_primitives::DomainCoreApi< + DomainBlock, + >>::is_inherent_extrinsic( + &domain_runtime_api_light, + Default::default(), // Doesn't matter for RuntimeApiLight + &extrinsic + ).ok() + } } impl FraudProofHostFunctions @@ -318,6 +342,14 @@ where .map(|is_tx_in_range| { FraudProofVerificationInfoResponse::TxRangeCheck(is_tx_in_range) }), + FraudProofVerificationInfoRequest::InherentExtrinsicCheck { + domain_id, + opaque_extrinsic, + } => self + .is_inherent_extrinsic(consensus_block_hash, domain_id, opaque_extrinsic) + .map(|is_inherent| { + FraudProofVerificationInfoResponse::InherentExtrinsicCheck(is_inherent) + }), } } diff --git a/crates/sp-domains-fraud-proof/src/lib.rs b/crates/sp-domains-fraud-proof/src/lib.rs index ed89113adc..676c1dbc23 100644 --- a/crates/sp-domains-fraud-proof/src/lib.rs +++ b/crates/sp-domains-fraud-proof/src/lib.rs @@ -92,6 +92,12 @@ pub enum FraudProofVerificationInfoRequest { /// Extrinsic for which we need to check the range opaque_extrinsic: OpaqueExtrinsic, }, + /// Request to check if particular extrinsic is an inherent extrinsic + InherentExtrinsicCheck { + domain_id: DomainId, + /// Extrinsic for which we need to if it is inherent or not. + opaque_extrinsic: OpaqueExtrinsic, + }, } impl PassBy for FraudProofVerificationInfoRequest { @@ -120,8 +126,10 @@ pub enum FraudProofVerificationInfoResponse { DomainRuntimeCode(Vec), /// Encoded domain set_code extrinsic if there is a runtime upgrade at given consensus block hash. DomainSetCodeExtrinsic(SetCodeExtrinsic), - /// if particular extrinsic is in range for (domain, bundle) pair at given domain block + /// If particular extrinsic is in range for (domain, bundle) pair at given domain block TxRangeCheck(bool), + /// If the particular extrinsic provided is either inherent or not. + InherentExtrinsicCheck(bool), } impl FraudProofVerificationInfoResponse { @@ -170,4 +178,13 @@ impl FraudProofVerificationInfoResponse { _ => None, } } + + pub fn into_inherent_extrinsic_check(self) -> Option { + match self { + FraudProofVerificationInfoResponse::InherentExtrinsicCheck(is_inherent) => { + Some(is_inherent) + } + _ => None, + } + } } diff --git a/crates/sp-domains-fraud-proof/src/verification.rs b/crates/sp-domains-fraud-proof/src/verification.rs index 319f93fe10..2be2b41122 100644 --- a/crates/sp-domains-fraud-proof/src/verification.rs +++ b/crates/sp-domains-fraud-proof/src/verification.rs @@ -433,6 +433,26 @@ where } Ok(()) } + InvalidBundleType::InherentExtrinsic(_) => { + let is_inherent = get_fraud_proof_verification_info( + H256::from_slice(bad_receipt.consensus_block_hash.as_ref()), + FraudProofVerificationInfoRequest::InherentExtrinsicCheck { + domain_id: invalid_bundles_fraud_proof.domain_id, + opaque_extrinsic: extrinsic, + }, + ) + .and_then(FraudProofVerificationInfoResponse::into_inherent_extrinsic_check) + .ok_or(VerificationError::FailedToCheckInherentExtrinsic)?; + + // Proof to be considered valid only, + // If it is true invalid fraud proof then extrinsic must be an inherent and + // If it is false invalid fraud proof then extrinsic must not be an inherent + if is_inherent == invalid_bundles_fraud_proof.is_true_invalid_fraud_proof { + Ok(()) + } else { + Err(VerificationError::InvalidProof) + } + } // TODO: implement the other invalid bundle types _ => Err(VerificationError::InvalidProof), diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index 118612fd0f..6476f5019d 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -805,6 +805,7 @@ pub enum BundleValidity { Invalid(InvalidBundleType), // The valid bundle's hash of `Vec<(tx_signer, tx_hash)>` of all domain extrinsic being // included in the bundle. + // TODO remove this and use host function to fetch above mentioned data Valid(Hash), } @@ -812,6 +813,7 @@ pub enum BundleValidity { #[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] pub struct InboxedBundle { pub bundle: BundleValidity, + // TODO remove this as the root is already present in the `ExecutionInbox` storage pub extrinsics_root: Hash, } From a56ff0bb164b12d105b35e633d7171adf8dff280 Mon Sep 17 00:00:00 2001 From: vedhavyas Date: Mon, 30 Oct 2023 10:48:19 +0100 Subject: [PATCH 4/5] add integration tests for true and false invalid bundles for inherent extrinsics --- Cargo.lock | 1 + crates/pallet-domains/src/lib.rs | 2 +- domains/client/domain-operator/Cargo.toml | 1 + domains/client/domain-operator/src/tests.rs | 307 +++++++++++++++++++- 4 files changed, 308 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 528d436a1e..db95820a13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2583,6 +2583,7 @@ dependencies = [ "futures-timer", "pallet-balances", "pallet-domains", + "pallet-timestamp", "parity-scale-codec", "parking_lot 0.12.1", "sc-cli", diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index d0ec9b19df..88104e19a1 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -1287,7 +1287,7 @@ mod pallet { match call { Call::submit_bundle { opaque_bundle } => { if let Err(e) = Self::validate_bundle(opaque_bundle) { - log::debug!( + log::info!( target: "runtime::domains", "Bad bundle {:?}, error: {e:?}", opaque_bundle.domain_id(), ); diff --git a/domains/client/domain-operator/Cargo.toml b/domains/client/domain-operator/Cargo.toml index 9013aa8580..c8d730fd36 100644 --- a/domains/client/domain-operator/Cargo.toml +++ b/domains/client/domain-operator/Cargo.toml @@ -46,6 +46,7 @@ evm-domain-test-runtime = { version = "0.1.0", path = "../../test/runtime/evm" } frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "892bf8e938c6bd2b893d3827d1093cd81baa59a1" } pallet-balances = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "892bf8e938c6bd2b893d3827d1093cd81baa59a1" } pallet-domains = { version = "0.1.0", path = "../../../crates/pallet-domains" } +pallet-timestamp = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "892bf8e938c6bd2b893d3827d1093cd81baa59a1" } sc-cli = { version = "0.10.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "892bf8e938c6bd2b893d3827d1093cd81baa59a1", default-features = false } sc-service = { version = "0.10.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "892bf8e938c6bd2b893d3827d1093cd81baa59a1", default-features = false } sc-transaction-pool = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "892bf8e938c6bd2b893d3827d1093cd81baa59a1" } diff --git a/domains/client/domain-operator/src/tests.rs b/domains/client/domain-operator/src/tests.rs index f1b6eebea7..07a8620288 100644 --- a/domains/client/domain-operator/src/tests.rs +++ b/domains/client/domain-operator/src/tests.rs @@ -12,12 +12,16 @@ use sc_consensus::SharedBlockImport; use sc_service::{BasePath, Role}; use sc_transaction_pool_api::error::Error as TxPoolError; use sc_transaction_pool_api::TransactionPool; -use sp_api::{AsTrieBackend, ProvideRuntimeApi}; +use sp_api::{AsTrieBackend, HashT, ProvideRuntimeApi}; use sp_consensus::SyncOracle; +use sp_core::storage::StateVersion; use sp_core::traits::FetchRuntimeCode; use sp_core::{Pair, H256}; use sp_domain_digests::AsPredigest; -use sp_domains::{Bundle, BundleValidity, DomainId, DomainsApi, HeaderHashingFor}; +use sp_domains::{ + Bundle, BundleValidity, DomainId, DomainsApi, HeaderHashingFor, InboxedBundle, + InvalidBundleType, +}; use sp_domains_fraud_proof::execution_prover::ExecutionProver; use sp_domains_fraud_proof::fraud_proof::{ ExecutionPhase, FraudProof, InvalidDomainBlockHashProof, InvalidExtrinsicsRootProof, @@ -859,6 +863,305 @@ async fn test_invalid_state_transition_proof_creation_and_verification( ferdie.produce_blocks(1).await.unwrap(); } +#[tokio::test(flavor = "multi_thread")] +#[ignore] +async fn test_true_invalid_bundles_inherent_extrinsic_proof_creation_and_verification() { + 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")), + ); + + // 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() + }; + + let inherent_extrinsic = || { + let now = 1234; + subspace_test_runtime::UncheckedExtrinsic::new_unsigned( + pallet_timestamp::Call::set { now }.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 extrinsics: Vec>; + let bundle_extrinsic_root; + let bad_submit_bundle_tx = { + let mut opaque_bundle = bundle.unwrap(); + opaque_bundle.extrinsics.push(inherent_extrinsic()); + extrinsics = opaque_bundle + .extrinsics + .clone() + .into_iter() + .map(|ext| ext.encode()) + .collect(); + bundle_extrinsic_root = + BlakeTwo256::ordered_trie_root(extrinsics.clone(), StateVersion::V1); + opaque_bundle.sealed_header.header.bundle_extrinsics_root = bundle_extrinsic_root; + 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_block_with!(ferdie.produce_block_with_slot(slot), alice) + .await + .unwrap(); + + // produce another bundle that marks the previous extrinsic as invalid. + 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 bad_receipt = &mut opaque_bundle.sealed_header.header.receipt; + // bad receipt marks this particular bundle as valid even though bundle contains inherent extrinsic + bad_receipt.inboxed_bundles = + vec![InboxedBundle::valid(H256::random(), bundle_extrinsic_root)]; + + 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_block_with!(ferdie.produce_block_with_slot(slot), alice) + .await + .unwrap(); + + // Produce a consensus block that contains the fraud proof for true invalid bundle. + let mut import_tx_stream = ferdie.transaction_pool.import_notification_stream(); + + 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::InvalidBundles(proof) = *fraud_proof { + match proof.invalid_bundle_type { + InvalidBundleType::InherentExtrinsic(_) => { + assert!(proof.is_true_invalid_fraud_proof); + break; + } + + _ => continue, + } + } + } + } + + // Produce a consensus block that contains the fraud proof, the fraud proof wil be verified + // in the block import pipeline + ferdie.produce_blocks(1).await.unwrap(); +} + +#[tokio::test(flavor = "multi_thread")] +#[ignore] +async fn test_false_invalid_bundles_inherent_extrinsic_proof_creation_and_verification() { + 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")), + ); + + // 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); + let extrinsics: Vec> = target_bundle + .extrinsics + .clone() + .into_iter() + .map(|ext| ext.encode()) + .collect(); + let bundle_extrinsic_root = + BlakeTwo256::ordered_trie_root(extrinsics.clone(), StateVersion::V1); + produce_block_with!(ferdie.produce_block_with_slot(slot), alice) + .await + .unwrap(); + + // produce another bundle that marks the previous valid extrinsic as invalid. + 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 bad_receipt = &mut opaque_bundle.sealed_header.header.receipt; + // bad receipt marks this particular bundle as invalid even though bundle does not contain + // inherent extrinsic + bad_receipt.inboxed_bundles = vec![InboxedBundle::invalid( + InvalidBundleType::InherentExtrinsic(0), + bundle_extrinsic_root, + )]; + + 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_block_with!(ferdie.produce_block_with_slot(slot), alice) + .await + .unwrap(); + + // Produce a consensus block that contains the fraud proof for true invalid bundle. + let mut import_tx_stream = ferdie.transaction_pool.import_notification_stream(); + + 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::InvalidBundles(proof) = *fraud_proof { + match proof.invalid_bundle_type { + InvalidBundleType::InherentExtrinsic(_) => { + assert!(!proof.is_true_invalid_fraud_proof); + break; + } + + _ => continue, + } + } + } + } + + // Produce a consensus block that contains the fraud proof, the fraud proof wil be verified + // in the block import pipeline + ferdie.produce_blocks(1).await.unwrap(); +} + #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_invalid_total_rewards_proof_creation() { From 2ecb9c8b113f47fa1b2320a9415e6e2a2de84164 Mon Sep 17 00:00:00 2001 From: vedhavyas Date: Mon, 30 Oct 2023 11:57:02 +0100 Subject: [PATCH 5/5] log errors for iinvalid fraud proofs and bundles --- crates/pallet-domains/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 88104e19a1..f8083beba3 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -1287,7 +1287,7 @@ mod pallet { match call { Call::submit_bundle { opaque_bundle } => { if let Err(e) = Self::validate_bundle(opaque_bundle) { - log::info!( + log::error!( target: "runtime::domains", "Bad bundle {:?}, error: {e:?}", opaque_bundle.domain_id(), ); @@ -1309,7 +1309,7 @@ mod pallet { } Call::submit_fraud_proof { fraud_proof } => { if let Err(e) = Self::validate_fraud_proof(fraud_proof) { - log::debug!( + log::error!( target: "runtime::domains", "Bad fraud proof {:?}, error: {e:?}", fraud_proof.domain_id(), );