sp_domains_fraud_proof/
fraud_proof.rs

1#[cfg(not(feature = "std"))]
2extern crate alloc;
3
4use crate::storage_proof::{self, *};
5#[cfg(not(feature = "std"))]
6use alloc::vec::Vec;
7use core::fmt;
8use parity_scale_codec::{Decode, Encode};
9use scale_info::TypeInfo;
10use sp_core::H256;
11use sp_domain_digests::AsPredigest;
12use sp_domains::proof_provider_and_verifier::StorageProofVerifier;
13use sp_domains::{
14    BundleValidity, DomainId, ExecutionReceiptFor, ExtrinsicDigest, HeaderHashFor,
15    HeaderHashingFor, InvalidBundleType,
16};
17use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT};
18use sp_runtime::{Digest, DigestItem};
19use sp_subspace_mmr::ConsensusChainMmrLeafProof;
20use sp_trie::StorageProof;
21use subspace_runtime_primitives::Balance;
22
23/// Mismatch type possible for ApplyExtrinsic execution phase
24#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
25pub enum ApplyExtrinsicMismatch {
26    StateRoot(u32),
27    Shorter,
28}
29
30/// Mismatch type possible for FinalizBlock execution phase
31#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
32pub enum FinalizeBlockMismatch {
33    StateRoot,
34    Longer(u32),
35}
36
37/// A phase of a block's execution, carrying necessary information needed for verifying the
38/// invalid state transition proof.
39#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
40pub enum ExecutionPhase {
41    /// Executes the `initialize_block` hook.
42    InitializeBlock,
43    /// Executes some extrinsic.
44    ApplyExtrinsic {
45        extrinsic_proof: StorageProof,
46        mismatch: ApplyExtrinsicMismatch,
47    },
48    /// Executes the `finalize_block` hook.
49    FinalizeBlock { mismatch: FinalizeBlockMismatch },
50}
51
52impl ExecutionPhase {
53    /// Returns the method for generating the proof.
54    pub fn execution_method(&self) -> &'static str {
55        match self {
56            // TODO: Replace `DomainCoreApi_initialize_block_with_post_state_root` with `Core_initalize_block`
57            // Should be a same issue with https://github.com/paritytech/substrate/pull/10922#issuecomment-1068997467
58            Self::InitializeBlock => "DomainCoreApi_initialize_block_with_post_state_root",
59            Self::ApplyExtrinsic { .. } => "DomainCoreApi_apply_extrinsic_with_post_state_root",
60            Self::FinalizeBlock { .. } => "BlockBuilder_finalize_block",
61        }
62    }
63
64    /// Returns true if execution phase refers to mismatch between state roots
65    /// false otherwise.
66    pub fn is_state_root_mismatch(&self) -> bool {
67        matches!(
68            self,
69            ExecutionPhase::InitializeBlock
70                | ExecutionPhase::ApplyExtrinsic {
71                    mismatch: ApplyExtrinsicMismatch::StateRoot(_),
72                    extrinsic_proof: _,
73                }
74                | ExecutionPhase::FinalizeBlock {
75                    mismatch: FinalizeBlockMismatch::StateRoot,
76                }
77        )
78    }
79    /// Returns the post state root for the given execution result.
80    pub fn decode_execution_result<Header: HeaderT>(
81        &self,
82        execution_result: Vec<u8>,
83    ) -> Result<Header::Hash, VerificationError<Header::Hash>> {
84        match self {
85            Self::InitializeBlock | Self::ApplyExtrinsic { .. } => {
86                let encoded_storage_root = Vec::<u8>::decode(&mut execution_result.as_slice())
87                    .map_err(VerificationError::InitializeBlockOrApplyExtrinsicDecode)?;
88                Header::Hash::decode(&mut encoded_storage_root.as_slice())
89                    .map_err(VerificationError::StorageRootDecode)
90            }
91            Self::FinalizeBlock { .. } => {
92                let new_header = Header::decode(&mut execution_result.as_slice())
93                    .map_err(VerificationError::HeaderDecode)?;
94                Ok(*new_header.state_root())
95            }
96        }
97    }
98
99    pub fn pre_post_state_root<CBlock, DomainHeader, Balance>(
100        &self,
101        bad_receipt: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
102        bad_receipt_parent: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
103    ) -> Result<(H256, H256), VerificationError<DomainHeader::Hash>>
104    where
105        CBlock: BlockT,
106        DomainHeader: HeaderT,
107        DomainHeader::Hash: Into<H256>,
108    {
109        if bad_receipt.execution_trace.len() < 2 {
110            return Err(VerificationError::InvalidExecutionTrace);
111        }
112        let (pre, post) = match self {
113            ExecutionPhase::InitializeBlock => (
114                bad_receipt_parent.final_state_root,
115                bad_receipt.execution_trace[0],
116            ),
117            ExecutionPhase::ApplyExtrinsic {
118                mismatch: ApplyExtrinsicMismatch::StateRoot(mismatch_index),
119                ..
120            } => {
121                if *mismatch_index == 0
122                    || *mismatch_index >= bad_receipt.execution_trace.len() as u32 - 1
123                {
124                    return Err(VerificationError::InvalidApplyExtrinsicTraceIndex);
125                }
126                (
127                    bad_receipt.execution_trace[*mismatch_index as usize - 1],
128                    bad_receipt.execution_trace[*mismatch_index as usize],
129                )
130            }
131            ExecutionPhase::ApplyExtrinsic {
132                mismatch: ApplyExtrinsicMismatch::Shorter,
133                ..
134            } => {
135                let mismatch_index = bad_receipt.execution_trace.len() - 1;
136                (
137                    bad_receipt.execution_trace[mismatch_index - 1],
138                    bad_receipt.execution_trace[mismatch_index],
139                )
140            }
141            ExecutionPhase::FinalizeBlock {
142                mismatch: FinalizeBlockMismatch::StateRoot,
143            } => {
144                let mismatch_index = bad_receipt.execution_trace.len() - 1;
145                (
146                    bad_receipt.execution_trace[mismatch_index - 1],
147                    bad_receipt.execution_trace[mismatch_index],
148                )
149            }
150            ExecutionPhase::FinalizeBlock {
151                mismatch: FinalizeBlockMismatch::Longer(mismatch_index),
152            } => {
153                if *mismatch_index == 0
154                    || *mismatch_index >= bad_receipt.execution_trace.len() as u32 - 1
155                {
156                    return Err(VerificationError::InvalidLongerMismatchTraceIndex);
157                }
158                (
159                    bad_receipt.execution_trace[(*mismatch_index - 1) as usize],
160                    bad_receipt.execution_trace[*mismatch_index as usize],
161                )
162            }
163        };
164        Ok((pre.into(), post.into()))
165    }
166
167    pub fn call_data<CBlock, DomainHeader, Balance>(
168        &self,
169        bad_receipt: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
170        bad_receipt_parent: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
171    ) -> Result<Vec<u8>, VerificationError<DomainHeader::Hash>>
172    where
173        CBlock: BlockT,
174        DomainHeader: HeaderT,
175    {
176        Ok(match self {
177            ExecutionPhase::InitializeBlock => {
178                let inherent_digests = Digest {
179                    logs: sp_std::vec![DigestItem::consensus_block_info(
180                        bad_receipt.consensus_block_hash,
181                    )],
182                };
183
184                let new_header = DomainHeader::new(
185                    bad_receipt.domain_block_number,
186                    Default::default(),
187                    Default::default(),
188                    bad_receipt_parent.domain_block_hash,
189                    inherent_digests,
190                );
191                new_header.encode()
192            }
193            ExecutionPhase::ApplyExtrinsic {
194                extrinsic_proof: proof_of_inclusion,
195                mismatch,
196            } => {
197                let mismatch_index = match mismatch {
198                    ApplyExtrinsicMismatch::StateRoot(mismatch_index) => *mismatch_index,
199                    ApplyExtrinsicMismatch::Shorter => {
200                        (bad_receipt.execution_trace.len() - 1) as u32
201                    }
202                };
203                // There is a trace root of the `initialize_block` in the head of the trace so we
204                // need to minus one to get the correct `extrinsic_index`
205                let extrinsic_index: u32 = mismatch_index - 1;
206
207                let storage_key =
208                    StorageProofVerifier::<DomainHeader::Hashing>::enumerated_storage_key(
209                        extrinsic_index,
210                    );
211
212                StorageProofVerifier::<DomainHeader::Hashing>::get_bare_value(
213                    &bad_receipt.domain_block_extrinsic_root,
214                    proof_of_inclusion.clone(),
215                    storage_key,
216                )
217                .map_err(|_| VerificationError::InvalidApplyExtrinsicCallData)?
218            }
219            ExecutionPhase::FinalizeBlock { .. } => Vec::new(),
220        })
221    }
222}
223
224/// Error type of fraud proof verification on consensus node.
225#[derive(Debug, thiserror::Error)]
226pub enum VerificationError<DomainHash> {
227    /// Failed to pass the execution proof check.
228    #[error("Failed to pass the execution proof check")]
229    BadExecutionProof,
230    /// The fraud proof prove nothing invalid
231    #[error("The fraud proof prove nothing invalid")]
232    InvalidProof,
233    /// Failed to decode the return value of `initialize_block` and `apply_extrinsic`.
234    #[error("Failed to decode the return value of `initialize_block` and `apply_extrinsic`: {0}")]
235    InitializeBlockOrApplyExtrinsicDecode(parity_scale_codec::Error),
236    /// Failed to decode the storage root produced by verifying `initialize_block` or `apply_extrinsic`.
237    #[error(
238        "Failed to decode the storage root from verifying `initialize_block` and `apply_extrinsic`: {0}"
239    )]
240    StorageRootDecode(parity_scale_codec::Error),
241    /// Failed to decode the header produced by `finalize_block`.
242    #[error("Failed to decode the header from verifying `finalize_block`: {0}")]
243    HeaderDecode(parity_scale_codec::Error),
244    #[error("The receipt's execution_trace have less than 2 traces")]
245    InvalidExecutionTrace,
246    #[error("Invalid ApplyExtrinsic trace index")]
247    InvalidApplyExtrinsicTraceIndex,
248    #[error("Invalid longer mismatch trace index")]
249    InvalidLongerMismatchTraceIndex,
250    #[error("Invalid ApplyExtrinsic call data")]
251    InvalidApplyExtrinsicCallData,
252    /// Invalid bundle digest
253    #[error("Invalid Bundle Digest")]
254    InvalidBundleDigest,
255    /// Bundle with requested index not found in execution receipt
256    #[error("Bundle with requested index not found in execution receipt")]
257    BundleNotFound,
258    /// Invalid bundle entry in bad receipt was expected to be valid but instead found invalid entry
259    #[error(
260        "Unexpected bundle entry at {bundle_index} in bad receipt found: \
261        {targeted_entry_bundle:?} with fraud proof's type of proof: \
262        {fraud_proof_invalid_type_of_proof:?}"
263    )]
264    UnexpectedTargetedBundleEntry {
265        bundle_index: u32,
266        fraud_proof_invalid_type_of_proof: InvalidBundleType,
267        targeted_entry_bundle: BundleValidity<DomainHash>,
268    },
269    /// Failed to derive bundle digest
270    #[error("Failed to derive bundle digest")]
271    FailedToDeriveBundleDigest,
272    /// The target valid bundle not found from the target bad receipt
273    #[error("The target valid bundle not found from the target bad receipt")]
274    TargetValidBundleNotFound,
275    /// Failed to check extrinsics in single context
276    #[error("Failed to check extrinsics in single context")]
277    FailedToCheckExtrinsicsInSingleContext,
278    #[error(
279        "Bad MMR proof, the proof is probably expired or is generated against a different fork"
280    )]
281    BadMmrProof,
282    #[error("Unexpected MMR proof")]
283    UnexpectedMmrProof,
284    #[error("Failed to verify storage proof")]
285    StorageProof(storage_proof::VerificationError),
286    /// Failed to derive domain inherent extrinsic
287    #[error("Failed to derive domain inherent extrinsic")]
288    FailedToDeriveDomainInherentExtrinsic,
289    /// Failed to derive domain storage key
290    #[error("Failed to derive domain storage key")]
291    FailedToGetDomainStorageKey,
292    /// Unexpected invalid bundle proof data
293    #[error("Unexpected invalid bundle proof data")]
294    UnexpectedInvalidBundleProofData,
295    /// Extrinsic with requested index not found in bundle
296    #[error("Extrinsic with requested index not found in bundle")]
297    ExtrinsicNotFound,
298    /// Failed to get domain runtime call response
299    #[error("Failed to get domain runtime call response")]
300    FailedToGetDomainRuntimeCallResponse,
301    /// Failed to get bundle weight
302    #[error("Failed to get bundle weight")]
303    FailedToGetBundleWeight,
304    #[error("Failed to extract xdm mmr proof")]
305    FailedToGetExtractXdmMmrProof,
306    #[error("Failed to decode xdm mmr proof")]
307    FailedToDecodeXdmMmrProof,
308}
309
310impl<DomainHash> From<storage_proof::VerificationError> for VerificationError<DomainHash> {
311    fn from(err: storage_proof::VerificationError) -> Self {
312        Self::StorageProof(err)
313    }
314}
315
316#[derive(Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
317pub struct FraudProof<Number, Hash, DomainHeader: HeaderT, MmrHash> {
318    pub domain_id: DomainId,
319    /// Hash of the bad receipt this fraud proof targeted
320    pub bad_receipt_hash: HeaderHashFor<DomainHeader>,
321    /// The MMR proof for the consensus state root that is used to verify the storage proof
322    ///
323    /// It is set `None` if the specific fraud proof variant doesn't contain a storage proof
324    pub maybe_mmr_proof: Option<ConsensusChainMmrLeafProof<Number, Hash, MmrHash>>,
325    /// The domain runtime code storage proof
326    ///
327    /// It is set `None` if the specific fraud proof variant doesn't require domain runtime code
328    /// or the required domain runtime code is available from the current runtime state.
329    pub maybe_domain_runtime_code_proof: Option<DomainRuntimeCodeAt<Number, Hash, MmrHash>>,
330    /// The specific fraud proof variant
331    pub proof: FraudProofVariant<Number, Hash, MmrHash, DomainHeader>,
332}
333
334#[allow(clippy::large_enum_variant)]
335#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
336pub enum FraudProofVariant<Number, Hash, MmrHash, DomainHeader: HeaderT> {
337    #[codec(index = 0)]
338    InvalidStateTransition(InvalidStateTransitionProof),
339    #[codec(index = 1)]
340    ValidBundle(ValidBundleProof<Number, Hash, DomainHeader>),
341    #[codec(index = 2)]
342    InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof),
343    #[codec(index = 3)]
344    InvalidBundles(InvalidBundlesProof<Number, Hash, MmrHash, DomainHeader>),
345    #[codec(index = 4)]
346    InvalidDomainBlockHash(InvalidDomainBlockHashProof),
347    #[codec(index = 5)]
348    InvalidBlockFees(InvalidBlockFeesProof),
349    #[codec(index = 6)]
350    InvalidTransfers(InvalidTransfersProof),
351    /// Dummy fraud proof only used in tests and benchmarks
352    #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
353    #[codec(index = 100)]
354    Dummy,
355}
356
357impl<Number, Hash, MmrHash, DomainHeader: HeaderT> FraudProof<Number, Hash, DomainHeader, MmrHash> {
358    pub fn domain_id(&self) -> DomainId {
359        self.domain_id
360    }
361
362    pub fn targeted_bad_receipt_hash(&self) -> HeaderHashFor<DomainHeader> {
363        self.bad_receipt_hash
364    }
365
366    pub fn is_unexpected_domain_runtime_code_proof(&self) -> bool {
367        // The invalid domain block hash fraud proof doesn't use the domain runtime code
368        // during its verification so it is unexpected to see `maybe_domain_runtime_code_proof`
369        // set to `Some`
370        self.maybe_domain_runtime_code_proof.is_some()
371            && matches!(self.proof, FraudProofVariant::InvalidDomainBlockHash(_))
372    }
373
374    pub fn is_unexpected_mmr_proof(&self) -> bool {
375        if self.maybe_mmr_proof.is_none() {
376            return false;
377        }
378        // Only the `InvalidExtrinsicsRoot`, `InvalidBundles` and `ValidBundle` fraud proof
379        // are using the MMR proof during verifiction, for other fraud proofs it is unexpected
380        // to see `maybe_mmr_proof` set to `Some`
381        !matches!(
382            self.proof,
383            FraudProofVariant::InvalidExtrinsicsRoot(_)
384                | FraudProofVariant::InvalidBundles(_)
385                | FraudProofVariant::ValidBundle(_)
386        )
387    }
388
389    #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
390    pub fn dummy_fraud_proof(
391        domain_id: DomainId,
392        bad_receipt_hash: HeaderHashFor<DomainHeader>,
393    ) -> FraudProof<Number, Hash, DomainHeader, MmrHash> {
394        Self {
395            domain_id,
396            bad_receipt_hash,
397            maybe_mmr_proof: None,
398            maybe_domain_runtime_code_proof: None,
399            proof: FraudProofVariant::Dummy,
400        }
401    }
402}
403
404impl<Number, Hash, MmrHash, DomainHeader: HeaderT> FraudProof<Number, Hash, DomainHeader, MmrHash>
405where
406    Number: Encode,
407    Hash: Encode,
408    MmrHash: Encode,
409{
410    pub fn hash(&self) -> HeaderHashFor<DomainHeader> {
411        HeaderHashingFor::<DomainHeader>::hash(&self.encode())
412    }
413}
414
415impl<Number, Hash, MmrHash, DomainHeader: HeaderT> fmt::Debug
416    for FraudProof<Number, Hash, DomainHeader, MmrHash>
417{
418    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419        let fp_target =
420            scale_info::prelude::format!("{:?}#{:?}", self.domain_id, self.bad_receipt_hash);
421        match &self.proof {
422            FraudProofVariant::InvalidStateTransition(_) => {
423                write!(f, "InvalidStateTransitionFraudProof({fp_target})")
424            }
425            FraudProofVariant::InvalidExtrinsicsRoot(_) => {
426                write!(f, "InvalidExtrinsicsRootFraudProof({fp_target})")
427            }
428            FraudProofVariant::InvalidBlockFees(_) => {
429                write!(f, "InvalidBlockFeesFraudProof({fp_target})")
430            }
431            FraudProofVariant::ValidBundle(_) => {
432                write!(f, "ValidBundleFraudProof({fp_target})")
433            }
434            FraudProofVariant::InvalidBundles(proof) => {
435                write!(
436                    f,
437                    "InvalidBundlesFraudProof(type: {:?}, target: {fp_target})",
438                    proof.invalid_bundle_type
439                )
440            }
441            FraudProofVariant::InvalidDomainBlockHash(_) => {
442                write!(f, "InvalidDomainBlockHashFraudProof({fp_target})")
443            }
444            FraudProofVariant::InvalidTransfers(_) => {
445                write!(f, "InvalidTransfersFraudProof({fp_target})")
446            }
447            #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
448            FraudProofVariant::Dummy => {
449                write!(f, "DummyFraudProof({fp_target})")
450            }
451        }
452    }
453}
454
455/// Represents a valid bundle index and all the extrinsics within that bundle.
456#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
457pub struct ValidBundleDigest {
458    /// Index of this bundle in the original list of bundles in the consensus block.
459    pub bundle_index: u32,
460    /// `Vec<(tx_signer, tx_hash)>` of all extrinsics
461    pub bundle_digest: Vec<(
462        Option<domain_runtime_primitives::opaque::AccountId>,
463        ExtrinsicDigest,
464    )>,
465}
466
467// Domain runtime code at a specific block
468#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
469pub struct DomainRuntimeCodeAt<Number, Hash, MmrHash> {
470    pub mmr_proof: ConsensusChainMmrLeafProof<Number, Hash, MmrHash>,
471    pub domain_runtime_code_proof: DomainRuntimeCodeProof,
472}
473
474/// Proves an invalid state transition by challenging the trace at specific index in a bad receipt.
475#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
476pub struct InvalidStateTransitionProof {
477    /// Proof recorded during the computation.
478    pub execution_proof: StorageProof,
479    /// Execution phase.
480    pub execution_phase: ExecutionPhase,
481}
482
483/// Fraud proof for the valid bundles in `ExecutionReceipt::inboxed_bundles`
484#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
485pub struct ValidBundleProof<Number, Hash, DomainHeader: HeaderT> {
486    /// The targeted bundle with proof
487    pub bundle_with_proof: OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>,
488}
489
490#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
491pub struct InvalidExtrinsicsRootProof {
492    /// Valid Bundle digests
493    pub valid_bundle_digests: Vec<ValidBundleDigest>,
494
495    /// The combined storage proofs used during verification
496    pub invalid_inherent_extrinsic_proofs: InvalidInherentExtrinsicDataProof,
497
498    /// A single domain runtime code upgrade (or "not upgraded") storage proof
499    pub maybe_domain_runtime_upgraded_proof: MaybeDomainRuntimeUpgradedProof,
500
501    /// Storage proof for a change to the chains that are allowed to open a channel with each domain
502    pub domain_chain_allowlist_proof: DomainChainsAllowlistUpdateStorageProof,
503
504    /// Optional sudo extrinsic call storage proof
505    pub domain_sudo_call_proof: DomainSudoCallStorageProof,
506
507    /// Optional EVM domain "set contract creation allowed by" extrinsic call storage proof
508    pub evm_domain_contract_creation_allowed_by_call_proof:
509        EvmDomainContractCreationAllowedByCallStorageProof,
510}
511
512#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
513pub struct MmrRootProof<Number, Hash, MmrHash> {
514    pub mmr_proof: ConsensusChainMmrLeafProof<Number, Hash, MmrHash>,
515    pub mmr_root_storage_proof: MmrRootStorageProof<MmrHash>,
516}
517
518#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
519pub enum InvalidBundlesProofData<Number, Hash, MmrHash, DomainHeader: HeaderT> {
520    Extrinsic(StorageProof),
521    Bundle(OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>),
522    BundleAndExecution {
523        bundle_with_proof: OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>,
524        execution_proof: StorageProof,
525    },
526    InvalidXDMProofData {
527        extrinsic_proof: StorageProof,
528        mmr_root_proof: Option<MmrRootProof<Number, Hash, MmrHash>>,
529    },
530}
531
532/// A proof about a bundle that was marked invalid (but might or might not actually be invalid).
533#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
534pub struct InvalidBundlesProof<Number, Hash, MmrHash, DomainHeader: HeaderT> {
535    pub bundle_index: u32,
536    /// The invalid bundle type that the bundle was marked with.
537    pub invalid_bundle_type: InvalidBundleType,
538    /// If `true`, the fraud proof must prove the bundle was correctly marked invalid.
539    /// If `false`, it must prove the bundle was marked invalid, but is actually valid.
540    pub is_good_invalid_fraud_proof: bool,
541    /// Proof data of the bundle which was marked invalid.
542    pub proof_data: InvalidBundlesProofData<Number, Hash, MmrHash, DomainHeader>,
543}
544
545/// Represents an invalid block fees proof.
546#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
547pub struct InvalidBlockFeesProof {
548    /// Storage witness needed for verifying this proof.
549    pub storage_proof: StorageProof,
550}
551
552/// Represents an invalid transfers proof.
553#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
554pub struct InvalidTransfersProof {
555    /// Storage witness needed for verifying this proof.
556    pub storage_proof: StorageProof,
557}
558
559/// Represents an invalid domain block hash fraud proof.
560#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
561pub struct InvalidDomainBlockHashProof {
562    /// Digests storage proof that is used to derive Domain block hash.
563    pub digest_storage_proof: StorageProof,
564}