diff --git a/crates/sp-domains/src/fraud_proof.rs b/crates/sp-domains/src/fraud_proof.rs index 54507a448b..ea23021e98 100644 --- a/crates/sp-domains/src/fraud_proof.rs +++ b/crates/sp-domains/src/fraud_proof.rs @@ -11,6 +11,10 @@ use subspace_core_primitives::BlockNumber; use subspace_runtime_primitives::{AccountId, Balance}; use trie_db::TrieLayout; +/// Maximum encoded size of storage proof. +/// TODO: fill the right size. +pub const MAX_STORAGE_PROOF_SIZE: usize = 64_000_000; + /// A phase of a block's execution, carrying necessary information needed for verifying the /// invalid state transition proof. #[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] @@ -231,6 +235,38 @@ impl InvalidBundlesFraudProof { } } +/// State of the storage witness needed for verifying this proof. +#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] +pub enum StorageWitness { + /// The storage proof included in the fraud proof. + Proof(StorageProof), + + /// The storage proof size exceeded the max limit. + /// So instead of the storage proof, the summary is + /// included in the fraud proof. + SizeExceeded(StorageProofSummary), +} + +/// Summary of the storage proof +#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] +pub struct StorageProofSummary { + /// Encoded size of the storage proof. + proof_size: u64, + + /// Hash of the storage proof. + proof_hash: H256, +} + +impl From<&StorageProof> for StorageProofSummary { + fn from(proof: &StorageProof) -> Self { + let encoded = proof.encode(); + Self { + proof_size: encoded.len() as u64, + proof_hash: BlakeTwo256::hash_of(&encoded), + } + } +} + /// Fraud proof. // TODO: Revisit when fraud proof v2 is implemented. #[allow(clippy::large_enum_variant)] @@ -329,7 +365,7 @@ pub struct InvalidStateTransitionProof { /// State root after the fraudulent transaction. pub post_state_root: H256, /// Proof recorded during the computation. - pub proof: StorageProof, + pub proof: StorageWitness, /// Execution phase. pub execution_phase: ExecutionPhase, } @@ -345,7 +381,7 @@ pub fn dummy_invalid_state_transition_proof( consensus_parent_hash: H256::default(), pre_state_root: H256::default(), post_state_root: H256::default(), - proof: StorageProof::empty(), + proof: StorageWitness::Proof(StorageProof::empty()), execution_phase: ExecutionPhase::ApplyExtrinsic(0), } } diff --git a/crates/subspace-fraud-proof/src/invalid_state_transition_proof.rs b/crates/subspace-fraud-proof/src/invalid_state_transition_proof.rs index 037ae9f398..5d69192e4b 100644 --- a/crates/subspace-fraud-proof/src/invalid_state_transition_proof.rs +++ b/crates/subspace-fraud-proof/src/invalid_state_transition_proof.rs @@ -13,7 +13,9 @@ use sc_client_api::backend; use sp_api::{ProvideRuntimeApi, StorageProof}; use sp_core::traits::{CodeExecutor, RuntimeCode}; use sp_core::H256; -use sp_domains::fraud_proof::{ExecutionPhase, InvalidStateTransitionProof, VerificationError}; +use sp_domains::fraud_proof::{ + ExecutionPhase, InvalidStateTransitionProof, StorageWitness, VerificationError, +}; use sp_domains::DomainsApi; use sp_runtime::traits::{BlakeTwo256, Block as BlockT, HashingFor, Header as HeaderT, NumberFor}; use sp_runtime::Digest; @@ -285,6 +287,17 @@ where ExecutionPhase::FinalizeBlock { .. } => Vec::new(), }; + let proof = match proof { + StorageWitness::Proof(proof) => proof, + StorageWitness::SizeExceeded(_) => { + // TODO: idea is to recreate the storage proof locally, + // and verify that the proof size indeed exceeds the threshold + // and matches the summary. To be determined: if all the + // info is available to recreate the storage proof locally. + return Ok(()); + } + }; + let execution_result = sp_state_machine::execution_proof_check::( *pre_state_root, proof.clone(), diff --git a/crates/subspace-fraud-proof/src/tests.rs b/crates/subspace-fraud-proof/src/tests.rs index 3359f716d4..eaa3989801 100644 --- a/crates/subspace-fraud-proof/src/tests.rs +++ b/crates/subspace-fraud-proof/src/tests.rs @@ -16,7 +16,7 @@ use sp_api::ProvideRuntimeApi; use sp_core::H256; use sp_domain_digests::AsPredigest; use sp_domains::fraud_proof::{ - ExecutionPhase, FraudProof, InvalidStateTransitionProof, VerificationError, + ExecutionPhase, FraudProof, InvalidStateTransitionProof, StorageWitness, VerificationError, }; use sp_domains::DomainId; use sp_runtime::generic::{Digest, DigestItem}; @@ -283,7 +283,7 @@ async fn execution_proof_creation_and_verification_should_work() { consensus_parent_hash, pre_state_root: *parent_header.state_root(), post_state_root: intermediate_roots[0].into(), - proof: storage_proof, + proof: StorageWitness::Proof(storage_proof), execution_phase, }; let fraud_proof = FraudProof::InvalidStateTransition(invalid_state_transition_proof); @@ -340,7 +340,7 @@ async fn execution_proof_creation_and_verification_should_work() { consensus_parent_hash, pre_state_root: intermediate_roots[target_extrinsic_index].into(), post_state_root: intermediate_roots[target_extrinsic_index + 1].into(), - proof: storage_proof, + proof: StorageWitness::Proof(storage_proof), execution_phase, }; let fraud_proof = FraudProof::InvalidStateTransition(invalid_state_transition_proof); @@ -393,7 +393,7 @@ async fn execution_proof_creation_and_verification_should_work() { consensus_parent_hash, pre_state_root: intermediate_roots.last().unwrap().into(), post_state_root: post_execution_root, - proof: storage_proof, + proof: StorageWitness::Proof(storage_proof), execution_phase, }; let fraud_proof = FraudProof::InvalidStateTransition(invalid_state_transition_proof); @@ -570,7 +570,7 @@ async fn invalid_execution_proof_should_not_work() { consensus_parent_hash, pre_state_root: post_delta_root0, post_state_root: post_delta_root1, - proof: proof1, + proof: StorageWitness::Proof(proof1), execution_phase: execution_phase0.clone(), }; let fraud_proof = FraudProof::InvalidStateTransition(invalid_state_transition_proof); @@ -583,7 +583,7 @@ async fn invalid_execution_proof_should_not_work() { consensus_parent_hash, pre_state_root: post_delta_root0, post_state_root: post_delta_root1, - proof: proof0.clone(), + proof: StorageWitness::Proof(proof0.clone()), execution_phase: execution_phase1, }; let fraud_proof = FraudProof::InvalidStateTransition(invalid_state_transition_proof); @@ -596,7 +596,7 @@ async fn invalid_execution_proof_should_not_work() { consensus_parent_hash, pre_state_root: post_delta_root0, post_state_root: post_delta_root1, - proof: proof0, + proof: StorageWitness::Proof(proof0), execution_phase: execution_phase0, }; let fraud_proof = FraudProof::InvalidStateTransition(invalid_state_transition_proof); diff --git a/domains/client/domain-operator/src/fraud_proof.rs b/domains/client/domain-operator/src/fraud_proof.rs index 99140456c3..1b33f3ca61 100644 --- a/domains/client/domain-operator/src/fraud_proof.rs +++ b/domains/client/domain-operator/src/fraud_proof.rs @@ -13,7 +13,8 @@ use sp_core::H256; use sp_domains::fraud_proof::{ ExecutionPhase, ExtrinsicDigest, FraudProof, InvalidBundlesFraudProof, InvalidExtrinsicsRootProof, InvalidStateTransitionProof, InvalidTotalRewardsProof, - MissingInvalidBundleEntryFraudProof, ValidAsInvalidBundleEntryFraudProof, ValidBundleDigest, + MissingInvalidBundleEntryFraudProof, StorageWitness, ValidAsInvalidBundleEntryFraudProof, + ValidBundleDigest, MAX_STORAGE_PROOF_SIZE, }; use sp_domains::{DomainId, DomainsApi}; use sp_runtime::traits::{BlakeTwo256, Block as BlockT, HashingFor, Header as HeaderT, NumberFor}; @@ -310,7 +311,7 @@ where consensus_parent_hash, pre_state_root, post_state_root, - proof, + proof: storage_witness(proof), execution_phase, } } else if local_trace_index as usize == local_receipt.execution_trace.len() - 1 { @@ -352,7 +353,7 @@ where consensus_parent_hash, pre_state_root, post_state_root, - proof, + proof: storage_witness(proof), execution_phase, } } else { @@ -377,7 +378,7 @@ where consensus_parent_hash, pre_state_root, post_state_root, - proof, + proof: storage_witness(proof), execution_phase, } }; @@ -469,3 +470,12 @@ pub(crate) fn find_trace_mismatch( } }) } + +/// Builds the storage witness from the proof. +fn storage_witness(proof: StorageProof) -> StorageWitness { + if proof.encoded_size() <= MAX_STORAGE_PROOF_SIZE { + StorageWitness::Proof(proof) + } else { + StorageWitness::SizeExceeded((&proof).into()) + } +} diff --git a/domains/client/domain-operator/src/tests.rs b/domains/client/domain-operator/src/tests.rs index 064ce59bcf..2c23dbced5 100644 --- a/domains/client/domain-operator/src/tests.rs +++ b/domains/client/domain-operator/src/tests.rs @@ -19,7 +19,7 @@ use sp_core::Pair; use sp_domain_digests::AsPredigest; use sp_domains::fraud_proof::{ ExecutionPhase, FraudProof, InvalidExtrinsicsRootProof, InvalidStateTransitionProof, - InvalidTotalRewardsProof, + InvalidTotalRewardsProof, StorageWitness, }; use sp_domains::transaction::InvalidTransactionCode; use sp_domains::{Bundle, DomainId, DomainsApi}; @@ -1212,7 +1212,7 @@ async fn fraud_proof_verification_in_tx_pool_should_work() { consensus_parent_hash: parent_hash_ferdie, pre_state_root: *parent_header.state_root(), post_state_root: intermediate_roots[0].into(), - proof: storage_proof, + proof: StorageWitness::Proof(storage_proof), execution_phase, }; let valid_fraud_proof =