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    #[
238    error(
239    "Failed to decode the storage root from verifying `initialize_block` and `apply_extrinsic`: {0}"
240    )
241    ]
242    StorageRootDecode(parity_scale_codec::Error),
243    /// Failed to decode the header produced by `finalize_block`.
244    #[error("Failed to decode the header from verifying `finalize_block`: {0}")]
245    HeaderDecode(parity_scale_codec::Error),
246    #[error("The receipt's execution_trace have less than 2 traces")]
247    InvalidExecutionTrace,
248    #[error("Invalid ApplyExtrinsic trace index")]
249    InvalidApplyExtrinsicTraceIndex,
250    #[error("Invalid longer mismatch trace index")]
251    InvalidLongerMismatchTraceIndex,
252    #[error("Invalid ApplyExtrinsic call data")]
253    InvalidApplyExtrinsicCallData,
254    /// Invalid bundle digest
255    #[error("Invalid Bundle Digest")]
256    InvalidBundleDigest,
257    /// Bundle with requested index not found in execution receipt
258    #[error("Bundle with requested index not found in execution receipt")]
259    BundleNotFound,
260    /// Invalid bundle entry in bad receipt was expected to be valid but instead found invalid entry
261    #[error(
262        "Unexpected bundle entry at {bundle_index} in bad receipt found: \
263        {targeted_entry_bundle:?} with fraud proof's type of proof: \
264        {fraud_proof_invalid_type_of_proof:?}"
265    )]
266    UnexpectedTargetedBundleEntry {
267        bundle_index: u32,
268        fraud_proof_invalid_type_of_proof: InvalidBundleType,
269        targeted_entry_bundle: BundleValidity<DomainHash>,
270    },
271    /// Failed to derive bundle digest
272    #[error("Failed to derive bundle digest")]
273    FailedToDeriveBundleDigest,
274    /// The target valid bundle not found from the target bad receipt
275    #[error("The target valid bundle not found from the target bad receipt")]
276    TargetValidBundleNotFound,
277    /// Failed to check extrinsics in single context
278    #[error("Failed to check extrinsics in single context")]
279    FailedToCheckExtrinsicsInSingleContext,
280    #[error(
281        "Bad MMR proof, the proof is probably expired or is generated against a different fork"
282    )]
283    BadMmrProof,
284    #[error("Unexpected MMR proof")]
285    UnexpectedMmrProof,
286    #[error("Failed to verify storage proof")]
287    StorageProof(storage_proof::VerificationError),
288    /// Failed to derive domain inherent extrinsic
289    #[error("Failed to derive domain inherent extrinsic")]
290    FailedToDeriveDomainInherentExtrinsic,
291    /// Failed to derive domain storage key
292    #[error("Failed to derive domain storage key")]
293    FailedToGetDomainStorageKey,
294    /// Unexpected invalid bundle proof data
295    #[error("Unexpected invalid bundle proof data")]
296    UnexpectedInvalidBundleProofData,
297    /// Extrinsic with requested index not found in bundle
298    #[error("Extrinsic with requested index not found in bundle")]
299    ExtrinsicNotFound,
300    /// Failed to get domain runtime call response
301    #[error("Failed to get domain runtime call response")]
302    FailedToGetDomainRuntimeCallResponse,
303    /// Failed to get bundle weight
304    #[error("Failed to get bundle weight")]
305    FailedToGetBundleWeight,
306    #[error("Failed to extract xdm mmr proof")]
307    FailedToGetExtractXdmMmrProof,
308    #[error("Failed to decode xdm mmr proof")]
309    FailedToDecodeXdmMmrProof,
310}
311
312impl<DomainHash> From<storage_proof::VerificationError> for VerificationError<DomainHash> {
313    fn from(err: storage_proof::VerificationError) -> Self {
314        Self::StorageProof(err)
315    }
316}
317
318#[derive(Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
319pub struct FraudProof<Number, Hash, DomainHeader: HeaderT, MmrHash> {
320    pub domain_id: DomainId,
321    /// Hash of the bad receipt this fraud proof targeted
322    pub bad_receipt_hash: HeaderHashFor<DomainHeader>,
323    /// The MMR proof for the consensus state root that is used to verify the storage proof
324    ///
325    /// It is set `None` if the specific fraud proof variant doesn't contain a storage proof
326    pub maybe_mmr_proof: Option<ConsensusChainMmrLeafProof<Number, Hash, MmrHash>>,
327    /// The domain runtime code storage proof
328    ///
329    /// It is set `None` if the specific fraud proof variant doesn't require domain runtime code
330    /// or the required domain runtime code is available from the current runtime state.
331    pub maybe_domain_runtime_code_proof: Option<DomainRuntimeCodeAt<Number, Hash, MmrHash>>,
332    /// The specific fraud proof variant
333    pub proof: FraudProofVariant<Number, Hash, MmrHash, DomainHeader>,
334}
335
336#[allow(clippy::large_enum_variant)]
337#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
338pub enum FraudProofVariant<Number, Hash, MmrHash, DomainHeader: HeaderT> {
339    #[codec(index = 0)]
340    InvalidStateTransition(InvalidStateTransitionProof),
341    #[codec(index = 1)]
342    ValidBundle(ValidBundleProof<Number, Hash, DomainHeader>),
343    #[codec(index = 2)]
344    InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof),
345    #[codec(index = 3)]
346    InvalidBundles(InvalidBundlesProof<Number, Hash, MmrHash, DomainHeader>),
347    #[codec(index = 4)]
348    InvalidDomainBlockHash(InvalidDomainBlockHashProof),
349    #[codec(index = 5)]
350    InvalidBlockFees(InvalidBlockFeesProof),
351    #[codec(index = 6)]
352    InvalidTransfers(InvalidTransfersProof),
353    /// Dummy fraud proof only used in tests and benchmarks
354    #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
355    #[codec(index = 100)]
356    Dummy,
357}
358
359impl<Number, Hash, MmrHash, DomainHeader: HeaderT> FraudProof<Number, Hash, DomainHeader, MmrHash> {
360    pub fn domain_id(&self) -> DomainId {
361        self.domain_id
362    }
363
364    pub fn targeted_bad_receipt_hash(&self) -> HeaderHashFor<DomainHeader> {
365        self.bad_receipt_hash
366    }
367
368    pub fn is_unexpected_domain_runtime_code_proof(&self) -> bool {
369        // The invalid domain block hash fraud proof doesn't use the domain runtime code
370        // during its verification so it is unexpected to see `maybe_domain_runtime_code_proof`
371        // set to `Some`
372        self.maybe_domain_runtime_code_proof.is_some()
373            && matches!(self.proof, FraudProofVariant::InvalidDomainBlockHash(_))
374    }
375
376    pub fn is_unexpected_mmr_proof(&self) -> bool {
377        if self.maybe_mmr_proof.is_none() {
378            return false;
379        }
380        // Only the `InvalidExtrinsicsRoot`, `InvalidBundles` and `ValidBundle` fraud proof
381        // are using the MMR proof during verifiction, for other fraud proofs it is unexpected
382        // to see `maybe_mmr_proof` set to `Some`
383        !matches!(
384            self.proof,
385            FraudProofVariant::InvalidExtrinsicsRoot(_)
386                | FraudProofVariant::InvalidBundles(_)
387                | FraudProofVariant::ValidBundle(_)
388        )
389    }
390
391    #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
392    pub fn dummy_fraud_proof(
393        domain_id: DomainId,
394        bad_receipt_hash: HeaderHashFor<DomainHeader>,
395    ) -> FraudProof<Number, Hash, DomainHeader, MmrHash> {
396        Self {
397            domain_id,
398            bad_receipt_hash,
399            maybe_mmr_proof: None,
400            maybe_domain_runtime_code_proof: None,
401            proof: FraudProofVariant::Dummy,
402        }
403    }
404}
405
406impl<Number, Hash, MmrHash, DomainHeader: HeaderT> FraudProof<Number, Hash, DomainHeader, MmrHash>
407where
408    Number: Encode,
409    Hash: Encode,
410    MmrHash: Encode,
411{
412    pub fn hash(&self) -> HeaderHashFor<DomainHeader> {
413        HeaderHashingFor::<DomainHeader>::hash(&self.encode())
414    }
415}
416
417impl<Number, Hash, MmrHash, DomainHeader: HeaderT> fmt::Debug
418    for FraudProof<Number, Hash, DomainHeader, MmrHash>
419{
420    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421        let fp_target =
422            scale_info::prelude::format!("{:?}#{:?}", self.domain_id, self.bad_receipt_hash);
423        match &self.proof {
424            FraudProofVariant::InvalidStateTransition(_) => {
425                write!(f, "InvalidStateTransitionFraudProof({fp_target})")
426            }
427            FraudProofVariant::InvalidExtrinsicsRoot(_) => {
428                write!(f, "InvalidExtrinsicsRootFraudProof({fp_target})")
429            }
430            FraudProofVariant::InvalidBlockFees(_) => {
431                write!(f, "InvalidBlockFeesFraudProof({fp_target})")
432            }
433            FraudProofVariant::ValidBundle(_) => {
434                write!(f, "ValidBundleFraudProof({fp_target})")
435            }
436            FraudProofVariant::InvalidBundles(proof) => {
437                write!(
438                    f,
439                    "InvalidBundlesFraudProof(type: {:?}, target: {fp_target})",
440                    proof.invalid_bundle_type
441                )
442            }
443            FraudProofVariant::InvalidDomainBlockHash(_) => {
444                write!(f, "InvalidDomainBlockHashFraudProof({fp_target})")
445            }
446            FraudProofVariant::InvalidTransfers(_) => {
447                write!(f, "InvalidTransfersFraudProof({fp_target})")
448            }
449            #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
450            FraudProofVariant::Dummy => {
451                write!(f, "DummyFraudProof({fp_target})")
452            }
453        }
454    }
455}
456
457/// Represents a valid bundle index and all the extrinsics within that bundle.
458#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
459pub struct ValidBundleDigest {
460    /// Index of this bundle in the original list of bundles in the consensus block.
461    pub bundle_index: u32,
462    /// `Vec<(tx_signer, tx_hash)>` of all extrinsics
463    pub bundle_digest: Vec<(
464        Option<domain_runtime_primitives::opaque::AccountId>,
465        ExtrinsicDigest,
466    )>,
467}
468
469// Domain runtime code at a specific block
470#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
471pub struct DomainRuntimeCodeAt<Number, Hash, MmrHash> {
472    pub mmr_proof: ConsensusChainMmrLeafProof<Number, Hash, MmrHash>,
473    pub domain_runtime_code_proof: DomainRuntimeCodeProof,
474}
475
476/// Proves an invalid state transition by challenging the trace at specific index in a bad receipt.
477#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
478pub struct InvalidStateTransitionProof {
479    /// Proof recorded during the computation.
480    pub execution_proof: StorageProof,
481    /// Execution phase.
482    pub execution_phase: ExecutionPhase,
483}
484
485/// Fraud proof for the valid bundles in `ExecutionReceipt::inboxed_bundles`
486#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
487pub struct ValidBundleProof<Number, Hash, DomainHeader: HeaderT> {
488    /// The targeted bundle with proof
489    pub bundle_with_proof: OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>,
490}
491
492#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
493pub struct InvalidExtrinsicsRootProof {
494    /// Valid Bundle digests
495    pub valid_bundle_digests: Vec<ValidBundleDigest>,
496
497    /// The combined storage proofs used during verification
498    pub invalid_inherent_extrinsic_proofs: InvalidInherentExtrinsicDataProof,
499
500    /// A single domain runtime code upgrade (or "not upgraded") storage proof
501    pub maybe_domain_runtime_upgraded_proof: MaybeDomainRuntimeUpgradedProof,
502
503    /// Storage proof for a change to the chains that are allowed to open a channel with each domain
504    pub domain_chain_allowlist_proof: DomainChainsAllowlistUpdateStorageProof,
505
506    /// Optional sudo extrinsic call storage proof
507    pub domain_sudo_call_proof: DomainSudoCallStorageProof,
508
509    /// Optional EVM domain "set contract creation allowed by" extrinsic call storage proof
510    pub evm_domain_contract_creation_allowed_by_call_proof:
511        EvmDomainContractCreationAllowedByCallStorageProof,
512}
513
514#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
515pub struct MmrRootProof<Number, Hash, MmrHash> {
516    pub mmr_proof: ConsensusChainMmrLeafProof<Number, Hash, MmrHash>,
517    pub mmr_root_storage_proof: MmrRootStorageProof<MmrHash>,
518}
519
520#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
521pub enum InvalidBundlesProofData<Number, Hash, MmrHash, DomainHeader: HeaderT> {
522    Extrinsic(StorageProof),
523    Bundle(OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>),
524    BundleAndExecution {
525        bundle_with_proof: OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>,
526        execution_proof: StorageProof,
527    },
528    InvalidXDMProofData {
529        extrinsic_proof: StorageProof,
530        mmr_root_proof: Option<MmrRootProof<Number, Hash, MmrHash>>,
531    },
532}
533
534/// A proof about a bundle that was marked invalid (but might or might not actually be invalid).
535#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
536pub struct InvalidBundlesProof<Number, Hash, MmrHash, DomainHeader: HeaderT> {
537    pub bundle_index: u32,
538    /// The invalid bundle type that the bundle was marked with.
539    pub invalid_bundle_type: InvalidBundleType,
540    /// If `true`, the fraud proof must prove the bundle was correctly marked invalid.
541    /// If `false`, it must prove the bundle was marked invalid, but is actually valid.
542    pub is_good_invalid_fraud_proof: bool,
543    /// Proof data of the bundle which was marked invalid.
544    pub proof_data: InvalidBundlesProofData<Number, Hash, MmrHash, DomainHeader>,
545}
546
547/// Represents an invalid block fees proof.
548#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
549pub struct InvalidBlockFeesProof {
550    /// Storage witness needed for verifying this proof.
551    pub storage_proof: StorageProof,
552}
553
554/// Represents an invalid transfers proof.
555#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
556pub struct InvalidTransfersProof {
557    /// Storage witness needed for verifying this proof.
558    pub storage_proof: StorageProof,
559}
560
561/// Represents an invalid domain block hash fraud proof.
562#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
563pub struct InvalidDomainBlockHashProof {
564    /// Digests storage proof that is used to derive Domain block hash.
565    pub digest_storage_proof: StorageProof,
566}