From ae57b31e26256ff5f900363ca8c176586fa4fd14 Mon Sep 17 00:00:00 2001 From: Rahul Subramaniyam Date: Thu, 12 Oct 2023 22:04:02 -0700 Subject: [PATCH] Handle large storage proofs. 1. When the storage proof in InvalidStateTransitionProof exceeds a threshold, a summary of the storage proof is included in the fraud proof instead of the full storage proof. 2. The verifier is expected to recreate the storage proof and verify that the summary is correct. --- crates/sp-domains/src/fraud_proof.rs | 40 ++++++++++++++++++- .../src/invalid_state_transition_proof.rs | 15 ++++++- crates/subspace-fraud-proof/src/tests.rs | 14 +++---- .../client/domain-operator/src/fraud_proof.rs | 18 +++++++-- domains/client/domain-operator/src/tests.rs | 4 +- 5 files changed, 75 insertions(+), 16 deletions(-) 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 =