sp_domains_fraud_proof/
verification.rs

1#[cfg(not(feature = "std"))]
2extern crate alloc;
3
4use crate::fraud_proof::{
5    InvalidBundlesProof, InvalidBundlesProofData, InvalidExtrinsicsRootProof,
6    InvalidStateTransitionProof, MmrRootProof, ValidBundleProof, VerificationError,
7};
8use crate::storage_proof::{self, *};
9use crate::{
10    DomainInherentExtrinsic, DomainInherentExtrinsicData, DomainStorageKeyRequest,
11    StatelessDomainRuntimeCall, fraud_proof_runtime_interface,
12};
13#[cfg(not(feature = "std"))]
14use alloc::vec::Vec;
15use domain_runtime_primitives::BlockNumber;
16use hash_db::Hasher;
17use parity_scale_codec::{Decode, Encode};
18use sp_core::H256;
19use sp_core::storage::StorageKey;
20use sp_domains::bundle::{BundleValidity, InboxedBundle, InvalidBundleType};
21use sp_domains::execution_receipt::{BlockFees, ExecutionReceipt, Transfers};
22use sp_domains::extrinsics::deduplicate_and_shuffle_extrinsics;
23use sp_domains::proof_provider_and_verifier::StorageProofVerifier;
24use sp_domains::valued_trie::valued_ordered_trie_root;
25use sp_domains::{
26    DomainId, ExtrinsicDigest, HeaderHashFor, HeaderHashingFor, HeaderNumberFor,
27    INITIAL_DOMAIN_TX_RANGE, RuntimeId,
28};
29use sp_runtime::generic::Digest;
30use sp_runtime::traits::{
31    Block as BlockT, Hash, Header as HeaderT, NumberFor, UniqueSaturatedInto, Zero,
32};
33use sp_runtime::{OpaqueExtrinsic, SaturatedConversion};
34use sp_subspace_mmr::{ConsensusChainMmrLeafProof, MmrProofVerifier};
35use sp_trie::{LayoutV1, StorageProof};
36use subspace_core_primitives::U256;
37use trie_db::node::Value;
38
39/// Verifies invalid domain extrinsic root fraud proof.
40pub fn verify_invalid_domain_extrinsics_root_fraud_proof<
41    CBlock,
42    Balance,
43    DomainHeader,
44    Hashing,
45    SKP,
46>(
47    bad_receipt: ExecutionReceipt<
48        NumberFor<CBlock>,
49        CBlock::Hash,
50        HeaderNumberFor<DomainHeader>,
51        HeaderHashFor<DomainHeader>,
52        Balance,
53    >,
54    fraud_proof: &InvalidExtrinsicsRootProof,
55    domain_id: DomainId,
56    runtime_id: RuntimeId,
57    state_root: CBlock::Hash,
58    domain_runtime_code: Vec<u8>,
59) -> Result<(), VerificationError<DomainHeader::Hash>>
60where
61    CBlock: BlockT,
62    DomainHeader: HeaderT,
63    DomainHeader::Hash: Into<H256> + PartialEq + Copy,
64    Hashing: Hasher<Out = CBlock::Hash>,
65    SKP: FraudProofStorageKeyProvider<NumberFor<CBlock>>,
66    Balance: Encode + Zero + Default,
67{
68    let InvalidExtrinsicsRootProof {
69        valid_bundle_digests,
70        invalid_inherent_extrinsic_proofs,
71        maybe_domain_runtime_upgraded_proof,
72        domain_chain_allowlist_proof,
73        domain_sudo_call_proof,
74        evm_domain_contract_creation_allowed_by_call_proof,
75    } = fraud_proof;
76
77    let invalid_inherent_extrinsic_data =
78        <InvalidInherentExtrinsicDataProof as BasicStorageProof<CBlock>>::verify::<SKP>(
79            invalid_inherent_extrinsic_proofs.clone(),
80            (),
81            &state_root,
82        )?;
83
84    let maybe_domain_runtime_upgrade =
85        maybe_domain_runtime_upgraded_proof.verify::<CBlock, SKP>(runtime_id, &state_root)?;
86
87    let domain_chain_allowlist = <DomainChainsAllowlistUpdateStorageProof as BasicStorageProof<
88        CBlock,
89    >>::verify::<SKP>(
90        domain_chain_allowlist_proof.clone(), domain_id, &state_root
91    )?;
92
93    let domain_sudo_call = <DomainSudoCallStorageProof as BasicStorageProof<CBlock>>::verify::<SKP>(
94        domain_sudo_call_proof.clone(),
95        domain_id,
96        &state_root,
97    )?;
98
99    let evm_domain_contract_creation_allowed_by_call =
100        <EvmDomainContractCreationAllowedByCallStorageProof as BasicStorageProof<CBlock>>::verify::<
101            SKP,
102        >(
103            evm_domain_contract_creation_allowed_by_call_proof.clone(),
104            domain_id,
105            &state_root,
106        )?;
107
108    let shuffling_seed = invalid_inherent_extrinsic_data.extrinsics_shuffling_seed;
109
110    let domain_inherent_extrinsic_data = DomainInherentExtrinsicData {
111        timestamp: invalid_inherent_extrinsic_data.timestamp,
112        maybe_domain_runtime_upgrade,
113        consensus_transaction_byte_fee: invalid_inherent_extrinsic_data
114            .consensus_transaction_byte_fee,
115        domain_chain_allowlist,
116        maybe_sudo_runtime_call: domain_sudo_call.maybe_call,
117        maybe_evm_domain_contract_creation_allowed_by_call:
118            evm_domain_contract_creation_allowed_by_call.maybe_call,
119    };
120
121    let DomainInherentExtrinsic {
122        domain_timestamp_extrinsic,
123        maybe_domain_chain_allowlist_extrinsic,
124        consensus_chain_byte_fee_extrinsic,
125        maybe_domain_set_code_extrinsic,
126        maybe_domain_sudo_call_extrinsic,
127        maybe_evm_domain_contract_creation_allowed_by_call_extrinsic,
128    } = fraud_proof_runtime_interface::construct_domain_inherent_extrinsic(
129        domain_runtime_code,
130        domain_inherent_extrinsic_data,
131    )
132    .ok_or(VerificationError::FailedToDeriveDomainInherentExtrinsic)?;
133
134    let bad_receipt_valid_bundle_digests = bad_receipt.valid_bundle_digests();
135    if valid_bundle_digests.len() != bad_receipt_valid_bundle_digests.len() {
136        return Err(VerificationError::InvalidBundleDigest);
137    }
138
139    let mut bundle_extrinsics_digests = Vec::new();
140    for (bad_receipt_valid_bundle_digest, bundle_digest) in bad_receipt_valid_bundle_digests
141        .into_iter()
142        .zip(valid_bundle_digests)
143    {
144        let bundle_digest_hash =
145            HeaderHashingFor::<DomainHeader>::hash_of(&bundle_digest.bundle_digest);
146        if bundle_digest_hash != bad_receipt_valid_bundle_digest {
147            return Err(VerificationError::InvalidBundleDigest);
148        }
149
150        bundle_extrinsics_digests.extend(bundle_digest.bundle_digest.clone());
151    }
152
153    let mut ordered_extrinsics =
154        deduplicate_and_shuffle_extrinsics(bundle_extrinsics_digests, shuffling_seed);
155
156    // NOTE: the order of the inherent extrinsics MUST be the same as the pallet order defined in
157    // the `construct_runtime` macro for domains.
158    // Currently this is the following order:
159    // - timestamp extrinsic
160    // - executive set_code extrinsic
161    // - messenger update_domain_allowlist extrinsic
162    // - evm_tracker contract_creation_allowed_by extrinsic
163    // - block_fees transaction_byte_fee_extrinsic
164    // - domain_sudo extrinsic
165    // Since we use `push_front`, the extrinsics should be pushed in reverse order.
166    // TODO: this will not be valid once we have a different runtime. To achieve consistency across
167    // domains, we should define a runtime api for each domain, which orders the extrinsics the
168    // same way the inherents are derived while the domain block is being built.
169
170    if let Some(domain_sudo_call_extrinsic) = maybe_domain_sudo_call_extrinsic {
171        let domain_sudo_call_extrinsic = ExtrinsicDigest::new::<
172            LayoutV1<HeaderHashingFor<DomainHeader>>,
173        >(domain_sudo_call_extrinsic);
174        ordered_extrinsics.push_front(domain_sudo_call_extrinsic);
175    }
176
177    let transaction_byte_fee_extrinsic = ExtrinsicDigest::new::<
178        LayoutV1<HeaderHashingFor<DomainHeader>>,
179    >(consensus_chain_byte_fee_extrinsic);
180    ordered_extrinsics.push_front(transaction_byte_fee_extrinsic);
181
182    if let Some(evm_domain_contract_creation_allowed_by_call_extrinsic) =
183        maybe_evm_domain_contract_creation_allowed_by_call_extrinsic
184    {
185        let evm_domain_contract_creation_allowed_by_call_extrinsic =
186            ExtrinsicDigest::new::<LayoutV1<HeaderHashingFor<DomainHeader>>>(
187                evm_domain_contract_creation_allowed_by_call_extrinsic,
188            );
189        ordered_extrinsics.push_front(evm_domain_contract_creation_allowed_by_call_extrinsic);
190    }
191
192    if let Some(domain_chain_allowlist_extrinsic) = maybe_domain_chain_allowlist_extrinsic {
193        let domain_chain_allowlist_extrinsic = ExtrinsicDigest::new::<
194            LayoutV1<HeaderHashingFor<DomainHeader>>,
195        >(domain_chain_allowlist_extrinsic);
196        ordered_extrinsics.push_front(domain_chain_allowlist_extrinsic);
197    }
198
199    if let Some(domain_set_code_extrinsic) = maybe_domain_set_code_extrinsic {
200        let domain_set_code_extrinsic = ExtrinsicDigest::new::<
201            LayoutV1<HeaderHashingFor<DomainHeader>>,
202        >(domain_set_code_extrinsic);
203        ordered_extrinsics.push_front(domain_set_code_extrinsic);
204    }
205
206    let timestamp_extrinsic = ExtrinsicDigest::new::<LayoutV1<HeaderHashingFor<DomainHeader>>>(
207        domain_timestamp_extrinsic,
208    );
209    ordered_extrinsics.push_front(timestamp_extrinsic);
210
211    let ordered_trie_node_values = ordered_extrinsics
212        .iter()
213        .map(|ext_digest| match ext_digest {
214            ExtrinsicDigest::Data(data) => Value::Inline(data),
215            ExtrinsicDigest::Hash(hash) => Value::Node(hash.0.as_slice()),
216        })
217        .collect();
218
219    let extrinsics_root = valued_ordered_trie_root::<LayoutV1<HeaderHashingFor<DomainHeader>>>(
220        ordered_trie_node_values,
221    );
222    if *bad_receipt.domain_block_extrinsics_root() == extrinsics_root {
223        return Err(VerificationError::InvalidProof);
224    }
225
226    Ok(())
227}
228
229/// Verifies valid bundle fraud proof.
230pub fn verify_valid_bundle_fraud_proof<CBlock, DomainHeader, Balance, SKP>(
231    bad_receipt: ExecutionReceipt<
232        NumberFor<CBlock>,
233        CBlock::Hash,
234        HeaderNumberFor<DomainHeader>,
235        HeaderHashFor<DomainHeader>,
236        Balance,
237    >,
238    fraud_proof: &ValidBundleProof<NumberFor<CBlock>, CBlock::Hash, DomainHeader>,
239    domain_id: DomainId,
240    state_root: CBlock::Hash,
241    domain_runtime_code: Vec<u8>,
242) -> Result<(), VerificationError<DomainHeader::Hash>>
243where
244    CBlock: BlockT,
245    CBlock::Hash: Into<H256>,
246    DomainHeader: HeaderT,
247    DomainHeader::Hash: Into<H256> + PartialEq + Copy,
248    SKP: FraudProofStorageKeyProvider<NumberFor<CBlock>>,
249    Balance: Encode + Zero + Default,
250{
251    let ValidBundleProof { bundle_with_proof } = fraud_proof;
252    bundle_with_proof.verify::<CBlock, SKP>(domain_id, &state_root)?;
253    let (extrinsics, bundle_index) = (
254        bundle_with_proof.bundle.extrinsics().to_vec(),
255        bundle_with_proof.bundle_index,
256    );
257
258    let valid_bundle_digest =
259        fraud_proof_runtime_interface::derive_bundle_digest(domain_runtime_code, extrinsics)
260            .ok_or(VerificationError::FailedToDeriveBundleDigest)?;
261
262    let bad_valid_bundle_digest = bad_receipt
263        .valid_bundle_digest_at(bundle_index as usize)
264        .ok_or(VerificationError::TargetValidBundleNotFound)?;
265
266    if bad_valid_bundle_digest.into() == valid_bundle_digest {
267        Err(VerificationError::InvalidProof)
268    } else {
269        Ok(())
270    }
271}
272
273/// Verifies invalid state transition fraud proof.
274pub fn verify_invalid_state_transition_fraud_proof<CBlock, DomainHeader, Balance>(
275    bad_receipt: ExecutionReceipt<
276        NumberFor<CBlock>,
277        CBlock::Hash,
278        DomainHeader::Number,
279        DomainHeader::Hash,
280        Balance,
281    >,
282    bad_receipt_parent: ExecutionReceipt<
283        NumberFor<CBlock>,
284        CBlock::Hash,
285        DomainHeader::Number,
286        DomainHeader::Hash,
287        Balance,
288    >,
289    fraud_proof: &InvalidStateTransitionProof,
290    domain_runtime_code: Vec<u8>,
291) -> Result<(), VerificationError<DomainHeader::Hash>>
292where
293    CBlock: BlockT,
294    CBlock::Hash: Into<H256>,
295    DomainHeader: HeaderT,
296    DomainHeader::Hash: Into<H256> + From<H256>,
297    DomainHeader::Number: UniqueSaturatedInto<BlockNumber> + From<BlockNumber>,
298    Balance: Encode + Zero + Default,
299{
300    let InvalidStateTransitionProof {
301        execution_proof,
302        execution_phase,
303        ..
304    } = fraud_proof;
305
306    let (pre_state_root, post_state_root) = execution_phase
307        .pre_post_state_root::<CBlock, DomainHeader, Balance>(&bad_receipt, &bad_receipt_parent)?;
308
309    let call_data = execution_phase
310        .call_data::<CBlock, DomainHeader, Balance>(&bad_receipt, &bad_receipt_parent)?;
311
312    let execution_result = fraud_proof_runtime_interface::execution_proof_check(
313        (
314            (*bad_receipt_parent.domain_block_number()).saturated_into(),
315            (*bad_receipt_parent.domain_block_hash()).into(),
316        ),
317        pre_state_root,
318        execution_proof.encode(),
319        execution_phase.execution_method(),
320        call_data.as_ref(),
321        domain_runtime_code,
322    )
323    .ok_or(VerificationError::BadExecutionProof)?;
324
325    let valid_post_state_root = execution_phase
326        .decode_execution_result::<DomainHeader>(execution_result)?
327        .into();
328
329    let is_mismatch = valid_post_state_root != post_state_root;
330
331    // If there is mismatch and execution phase indicate state root mismatch then the fraud proof is valid
332    // If there is no mismatch and execution phase indicate non state root mismatch (i.e the trace is either long or short) then
333    // the fraud proof is valid.
334    let is_valid = is_mismatch == execution_phase.is_state_root_mismatch();
335
336    if is_valid {
337        Ok(())
338    } else {
339        Err(VerificationError::InvalidProof)
340    }
341}
342
343/// Verifies invalid domain block hash fraud proof.
344pub fn verify_invalid_domain_block_hash_fraud_proof<CBlock, Balance, DomainHeader>(
345    bad_receipt: ExecutionReceipt<
346        NumberFor<CBlock>,
347        CBlock::Hash,
348        DomainHeader::Number,
349        DomainHeader::Hash,
350        Balance,
351    >,
352    digest_storage_proof: StorageProof,
353    parent_domain_block_hash: DomainHeader::Hash,
354) -> Result<(), VerificationError<DomainHeader::Hash>>
355where
356    CBlock: BlockT,
357    Balance: PartialEq + Decode + Encode + Zero + Default,
358    DomainHeader: HeaderT,
359{
360    let state_root = *bad_receipt.final_state_root();
361    let digest_storage_key = StorageKey(sp_domains::system_digest_final_key());
362
363    let digest = StorageProofVerifier::<DomainHeader::Hashing>::get_decoded_value::<Digest>(
364        &state_root,
365        digest_storage_proof,
366        digest_storage_key,
367    )
368    .map_err(|err| {
369        VerificationError::StorageProof(storage_proof::VerificationError::DigestStorageProof(err))
370    })?;
371
372    let derived_domain_block_hash = sp_domains::derive_domain_block_hash::<DomainHeader>(
373        *bad_receipt.domain_block_number(),
374        *bad_receipt.domain_block_extrinsics_root(),
375        state_root,
376        parent_domain_block_hash,
377        digest,
378    );
379
380    if *bad_receipt.domain_block_hash() == derived_domain_block_hash {
381        return Err(VerificationError::InvalidProof);
382    }
383
384    Ok(())
385}
386
387/// Verifies invalid block fees fraud proof.
388pub fn verify_invalid_block_fees_fraud_proof<
389    CBlock,
390    DomainNumber,
391    DomainHash,
392    Balance,
393    DomainHashing,
394>(
395    bad_receipt: ExecutionReceipt<
396        NumberFor<CBlock>,
397        CBlock::Hash,
398        DomainNumber,
399        DomainHash,
400        Balance,
401    >,
402    storage_proof: &StorageProof,
403    domain_runtime_code: Vec<u8>,
404) -> Result<(), VerificationError<DomainHash>>
405where
406    CBlock: BlockT,
407    Balance: PartialEq + Decode + Encode + Zero + Default,
408    DomainHashing: Hasher<Out = DomainHash>,
409    DomainNumber: Encode + Zero,
410    DomainHash: Clone + Encode + Default + Copy,
411{
412    let storage_key = fraud_proof_runtime_interface::domain_storage_key(
413        domain_runtime_code,
414        DomainStorageKeyRequest::BlockFees,
415    )
416    .ok_or(VerificationError::FailedToGetDomainStorageKey)?;
417
418    let block_fees =
419        StorageProofVerifier::<DomainHashing>::get_decoded_value::<BlockFees<Balance>>(
420            bad_receipt.final_state_root(),
421            storage_proof.clone(),
422            StorageKey(storage_key),
423        )
424        .map_err(|err| {
425            VerificationError::StorageProof(
426                storage_proof::VerificationError::BlockFeesStorageProof(err),
427            )
428        })?;
429
430    // if the rewards matches, then this is an invalid fraud proof since rewards must be different.
431    if bad_receipt.block_fees() == &block_fees {
432        return Err(VerificationError::InvalidProof);
433    }
434
435    Ok(())
436}
437
438/// Verifies invalid transfers fraud proof.
439pub fn verify_invalid_transfers_fraud_proof<
440    CBlock,
441    DomainNumber,
442    DomainHash,
443    Balance,
444    DomainHashing,
445>(
446    bad_receipt: ExecutionReceipt<
447        NumberFor<CBlock>,
448        CBlock::Hash,
449        DomainNumber,
450        DomainHash,
451        Balance,
452    >,
453    storage_proof: &StorageProof,
454    domain_runtime_code: Vec<u8>,
455) -> Result<(), VerificationError<DomainHash>>
456where
457    CBlock: BlockT,
458    Balance: PartialEq + Decode + Encode + Zero + Default,
459    DomainNumber: Encode + Zero,
460    DomainHash: Clone + Encode + Default + Copy,
461    DomainHashing: Hasher<Out = DomainHash>,
462{
463    let storage_key = fraud_proof_runtime_interface::domain_storage_key(
464        domain_runtime_code,
465        DomainStorageKeyRequest::Transfers,
466    )
467    .ok_or(VerificationError::FailedToGetDomainStorageKey)?;
468
469    let transfers = StorageProofVerifier::<DomainHashing>::get_decoded_value::<Transfers<Balance>>(
470        bad_receipt.final_state_root(),
471        storage_proof.clone(),
472        StorageKey(storage_key),
473    )
474    .map_err(|err| {
475        VerificationError::StorageProof(storage_proof::VerificationError::TransfersStorageProof(
476            err,
477        ))
478    })?;
479
480    // if the rewards matches, then this is an invalid fraud proof since rewards must be different.
481    if bad_receipt.transfers() == &transfers {
482        return Err(VerificationError::InvalidProof);
483    }
484
485    Ok(())
486}
487
488/// This function checks if this fraud proof is expected against the inboxed bundle entry it is targeting.
489/// If the entry is expected then it will be returned
490/// In any other cases VerificationError will be returned
491fn check_expected_bundle_entry<CBlock, DomainHeader, Balance>(
492    bad_receipt: &ExecutionReceipt<
493        NumberFor<CBlock>,
494        CBlock::Hash,
495        HeaderNumberFor<DomainHeader>,
496        HeaderHashFor<DomainHeader>,
497        Balance,
498    >,
499    bundle_index: u32,
500    invalid_bundle_type: InvalidBundleType,
501    is_good_invalid_fraud_proof: bool,
502) -> Result<InboxedBundle<HeaderHashFor<DomainHeader>>, VerificationError<DomainHeader::Hash>>
503where
504    CBlock: BlockT,
505    DomainHeader: HeaderT,
506    Balance: Encode + Zero + Default,
507{
508    let targeted_invalid_bundle_entry = bad_receipt
509        .inboxed_bundles()
510        .get(bundle_index as usize)
511        .ok_or(VerificationError::BundleNotFound)?;
512
513    let is_expected = if !is_good_invalid_fraud_proof {
514        // `BadInvalid`
515        // The proof trying to prove `bad_receipt_bundle`'s `invalid_bundle_type` is wrong,
516        // so the proof should contain the same `invalid_bundle_type`
517        targeted_invalid_bundle_entry.bundle == BundleValidity::Invalid(invalid_bundle_type.clone())
518    } else {
519        // `GoodInvalid`
520        match &targeted_invalid_bundle_entry.bundle {
521            // The proof trying to prove the bundle is invalid due to `invalid_type_of_proof` while `bad_receipt_bundle`
522            // thinks it is valid
523            BundleValidity::Valid(_) => true,
524            BundleValidity::Invalid(invalid_type) => {
525                // The proof trying to prove there is a check failed while the `bad_receipt` thinks it passed,
526                // so the proof should point to a check that is performed before the `bad_receipt`'s
527                invalid_bundle_type.checking_order() < invalid_type.checking_order()
528            }
529        }
530    };
531
532    if !is_expected {
533        return Err(VerificationError::UnexpectedTargetedBundleEntry {
534            bundle_index,
535            fraud_proof_invalid_type_of_proof: invalid_bundle_type,
536            targeted_entry_bundle: targeted_invalid_bundle_entry.bundle.clone(),
537        });
538    }
539
540    Ok(targeted_invalid_bundle_entry.clone())
541}
542
543fn get_extrinsic_from_proof<DomainHeader: HeaderT>(
544    extrinsic_index: u32,
545    extrinsics_root: <HeaderHashingFor<DomainHeader> as Hasher>::Out,
546    proof_data: StorageProof,
547) -> Result<OpaqueExtrinsic, VerificationError<DomainHeader::Hash>> {
548    let storage_key =
549        StorageProofVerifier::<HeaderHashingFor<DomainHeader>>::enumerated_storage_key(
550            extrinsic_index,
551        );
552    StorageProofVerifier::<HeaderHashingFor<DomainHeader>>::get_decoded_value(
553        &extrinsics_root,
554        proof_data,
555        storage_key,
556    )
557    .map_err(|err| {
558        VerificationError::StorageProof(storage_proof::VerificationError::ExtrinsicStorageProof(
559            err,
560        ))
561    })
562}
563
564pub fn verify_invalid_bundles_fraud_proof<CBlock, DomainHeader, MmrHash, Balance, SKP, MPV>(
565    bad_receipt: ExecutionReceipt<
566        NumberFor<CBlock>,
567        CBlock::Hash,
568        HeaderNumberFor<DomainHeader>,
569        HeaderHashFor<DomainHeader>,
570        Balance,
571    >,
572    bad_receipt_parent: ExecutionReceipt<
573        NumberFor<CBlock>,
574        CBlock::Hash,
575        HeaderNumberFor<DomainHeader>,
576        HeaderHashFor<DomainHeader>,
577        Balance,
578    >,
579    invalid_bundles_fraud_proof: &InvalidBundlesProof<
580        NumberFor<CBlock>,
581        CBlock::Hash,
582        MmrHash,
583        DomainHeader,
584    >,
585    domain_id: DomainId,
586    state_root: CBlock::Hash,
587    domain_runtime_code: Vec<u8>,
588) -> Result<(), VerificationError<DomainHeader::Hash>>
589where
590    CBlock: BlockT,
591    DomainHeader: HeaderT,
592    CBlock::Hash: Into<H256>,
593    DomainHeader::Hash: Into<H256>,
594    MmrHash: Decode + Clone,
595    SKP: FraudProofStorageKeyProvider<NumberFor<CBlock>>,
596    MPV: MmrProofVerifier<MmrHash, NumberFor<CBlock>, CBlock::Hash>,
597    Balance: Encode + Zero + Default,
598{
599    let InvalidBundlesProof {
600        bundle_index,
601        invalid_bundle_type,
602        is_good_invalid_fraud_proof,
603        proof_data,
604    } = invalid_bundles_fraud_proof;
605
606    let (bundle_index, is_good_invalid_fraud_proof) = (*bundle_index, *is_good_invalid_fraud_proof);
607    let targeted_invalid_bundle_entry = check_expected_bundle_entry::<CBlock, DomainHeader, Balance>(
608        &bad_receipt,
609        bundle_index,
610        invalid_bundle_type.clone(),
611        is_good_invalid_fraud_proof,
612    )?;
613    let bundle_extrinsic_root = targeted_invalid_bundle_entry.extrinsics_root;
614
615    // Verify the bundle proof so in following we can use the bundle directly
616    match proof_data {
617        InvalidBundlesProofData::Bundle(bundle_with_proof)
618        | InvalidBundlesProofData::BundleAndExecution {
619            bundle_with_proof, ..
620        } => {
621            if bundle_with_proof.bundle_index != bundle_index {
622                return Err(VerificationError::UnexpectedInvalidBundleProofData);
623            }
624            bundle_with_proof.verify::<CBlock, SKP>(domain_id, &state_root)?;
625        }
626        InvalidBundlesProofData::Extrinsic(_) => {}
627        InvalidBundlesProofData::InvalidXDMProofData { .. } => {}
628    }
629
630    // Fast path to check if the fraud proof is targeting a bad receipt that claim a non-exist extrinsic
631    // is invalid
632    if let Some(invalid_extrinsic_index) = targeted_invalid_bundle_entry.invalid_extrinsic_index()
633        && let InvalidBundlesProofData::Bundle(bundle_with_proof) = proof_data
634        && bundle_with_proof.bundle.body_length() as u32 <= invalid_extrinsic_index
635    {
636        return Ok(());
637    }
638
639    match &invalid_bundle_type {
640        InvalidBundleType::OutOfRangeTx(extrinsic_index) => {
641            let bundle = match proof_data {
642                InvalidBundlesProofData::Bundle(bundle_with_proof) => {
643                    bundle_with_proof.bundle.clone()
644                }
645                _ => return Err(VerificationError::UnexpectedInvalidBundleProofData),
646            };
647
648            let opaque_extrinsic = bundle
649                .extrinsics()
650                .get(*extrinsic_index as usize)
651                .cloned()
652                .ok_or(VerificationError::ExtrinsicNotFound)?;
653
654            let domain_tx_range = U256::MAX / INITIAL_DOMAIN_TX_RANGE;
655            let bundle_vrf_hash = U256::from_be_bytes(*bundle.proof_of_election().vrf_hash());
656
657            let is_tx_in_range = fraud_proof_runtime_interface::domain_runtime_call(
658                domain_runtime_code,
659                StatelessDomainRuntimeCall::IsTxInRange {
660                    opaque_extrinsic,
661                    domain_tx_range,
662                    bundle_vrf_hash,
663                },
664            )
665            .ok_or(VerificationError::FailedToGetDomainRuntimeCallResponse)?;
666
667            // Proof to be considered valid only if:
668            // if we are proving the bundle is actually invalid, then tx must not be in range, but
669            // if we are proving the invalid bundle is wrong, then tx must be in range.
670            if is_tx_in_range == is_good_invalid_fraud_proof {
671                return Err(VerificationError::InvalidProof);
672            }
673            Ok(())
674        }
675        InvalidBundleType::InherentExtrinsic(extrinsic_index) => {
676            let opaque_extrinsic = {
677                let extrinsic_storage_proof = match proof_data {
678                    InvalidBundlesProofData::Extrinsic(p) => p.clone(),
679                    _ => return Err(VerificationError::UnexpectedInvalidBundleProofData),
680                };
681                get_extrinsic_from_proof::<DomainHeader>(
682                    *extrinsic_index,
683                    bundle_extrinsic_root,
684                    extrinsic_storage_proof,
685                )?
686            };
687            let is_inherent = fraud_proof_runtime_interface::domain_runtime_call(
688                domain_runtime_code,
689                StatelessDomainRuntimeCall::IsInherentExtrinsic(opaque_extrinsic),
690            )
691            .ok_or(VerificationError::FailedToGetDomainRuntimeCallResponse)?;
692
693            // Proof to be considered valid only if:
694            // if we are proving the bundle is actually invalid, then extrinsic must be an inherent, but
695            // if we are proving the invalid bundle is wrong, then the extrinsic must not be an inherent.
696            if is_inherent == is_good_invalid_fraud_proof {
697                Ok(())
698            } else {
699                Err(VerificationError::InvalidProof)
700            }
701        }
702        InvalidBundleType::IllegalTx(extrinsic_index) => {
703            let (bundle, execution_proof) = match proof_data {
704                InvalidBundlesProofData::BundleAndExecution {
705                    bundle_with_proof,
706                    execution_proof,
707                } => (bundle_with_proof.bundle.clone(), execution_proof.clone()),
708                _ => return Err(VerificationError::UnexpectedInvalidBundleProofData),
709            };
710
711            let extrinsics = bundle
712                .into_extrinsics()
713                .drain(..)
714                .take((*extrinsic_index + 1) as usize)
715                .collect();
716
717            // Make host call for check extrinsic in single context
718            let check_extrinsic_result =
719                fraud_proof_runtime_interface::check_extrinsics_in_single_context(
720                    domain_runtime_code,
721                    (
722                        (*bad_receipt_parent.domain_block_number()).saturated_into(),
723                        (*bad_receipt_parent.domain_block_hash()).into(),
724                    ),
725                    (*bad_receipt_parent.final_state_root()).into(),
726                    extrinsics,
727                    execution_proof.encode(),
728                )
729                .ok_or(VerificationError::FailedToCheckExtrinsicsInSingleContext)?;
730
731            let is_extrinsic_invalid = check_extrinsic_result == Some(*extrinsic_index);
732
733            // Proof to be considered valid only if:
734            // if we are proving the bundle is actually invalid, then the extrinsic must be an invalid extrinsic, but
735            // if we are proving the invalid bundle is wrong, then the extrinsic must not be an invalid extrinsic.
736            if is_extrinsic_invalid == is_good_invalid_fraud_proof {
737                Ok(())
738            } else {
739                Err(VerificationError::InvalidProof)
740            }
741        }
742        InvalidBundleType::UndecodableTx(extrinsic_index) => {
743            let opaque_extrinsic = {
744                let extrinsic_storage_proof = match proof_data {
745                    InvalidBundlesProofData::Extrinsic(p) => p.clone(),
746                    _ => return Err(VerificationError::UnexpectedInvalidBundleProofData),
747                };
748                get_extrinsic_from_proof::<DomainHeader>(
749                    *extrinsic_index,
750                    bundle_extrinsic_root,
751                    extrinsic_storage_proof,
752                )?
753            };
754            let is_decodable = fraud_proof_runtime_interface::domain_runtime_call(
755                domain_runtime_code,
756                StatelessDomainRuntimeCall::IsDecodableExtrinsic(opaque_extrinsic),
757            )
758            .ok_or(VerificationError::FailedToGetDomainRuntimeCallResponse)?;
759
760            if is_decodable == is_good_invalid_fraud_proof {
761                return Err(VerificationError::InvalidProof);
762            }
763            Ok(())
764        }
765        InvalidBundleType::InvalidBundleWeight => {
766            let bundle = match proof_data {
767                InvalidBundlesProofData::Bundle(bundle_with_proof) => {
768                    bundle_with_proof.bundle.clone()
769                }
770                _ => return Err(VerificationError::UnexpectedInvalidBundleProofData),
771            };
772            let bundle_header_weight = bundle.estimated_weight();
773            let estimated_bundle_weight = fraud_proof_runtime_interface::bundle_weight(
774                domain_runtime_code,
775                bundle.into_extrinsics(),
776            )
777            .ok_or(VerificationError::FailedToGetBundleWeight)?;
778
779            let is_bundle_weight_correct = estimated_bundle_weight == bundle_header_weight;
780
781            if is_bundle_weight_correct == is_good_invalid_fraud_proof {
782                return Err(VerificationError::InvalidProof);
783            }
784            Ok(())
785        }
786        InvalidBundleType::InvalidXDM(extrinsic_index) => {
787            let (extrinsic_proof, maybe_mmr_root_proof) = match proof_data {
788                InvalidBundlesProofData::InvalidXDMProofData {
789                    extrinsic_proof,
790                    mmr_root_proof,
791                } => (extrinsic_proof.clone(), mmr_root_proof.clone()),
792                _ => return Err(VerificationError::UnexpectedInvalidBundleProofData),
793            };
794
795            let opaque_extrinsic = get_extrinsic_from_proof::<DomainHeader>(
796                *extrinsic_index,
797                bundle_extrinsic_root,
798                extrinsic_proof,
799            )?;
800
801            let maybe_xdm_mmr_proof = fraud_proof_runtime_interface::extract_xdm_mmr_proof(
802                domain_runtime_code,
803                opaque_extrinsic.encode(),
804            )
805            .ok_or(VerificationError::FailedToGetExtractXdmMmrProof)?;
806
807            let (mmr_root_proof, consensus_chain_mmr_leaf_proof) = match maybe_xdm_mmr_proof {
808                Some(encoded_xdm_mmr_proof) => {
809                    let consensus_chain_mmr_leaf_proof: ConsensusChainMmrLeafProof<
810                        NumberFor<CBlock>,
811                        CBlock::Hash,
812                        MmrHash,
813                    > = Decode::decode(&mut encoded_xdm_mmr_proof.as_ref())
814                        .map_err(|_| VerificationError::FailedToDecodeXdmMmrProof)?;
815                    let mmr_root_proof = maybe_mmr_root_proof
816                        .ok_or(VerificationError::UnexpectedInvalidBundleProofData)?;
817                    (mmr_root_proof, consensus_chain_mmr_leaf_proof)
818                }
819                None => {
820                    // `None` means this is not an XDM, so for this fraud proof to be valid:
821                    // it has to prove that the invalid bundle is wrong, and
822                    // the `maybe_mmr_root_proof` must be `None`
823                    return if is_good_invalid_fraud_proof || maybe_mmr_root_proof.is_some() {
824                        Err(VerificationError::InvalidProof)
825                    } else {
826                        Ok(())
827                    };
828                }
829            };
830
831            let mmr_root = {
832                let MmrRootProof {
833                    mmr_proof,
834                    mmr_root_storage_proof,
835                } = mmr_root_proof;
836
837                let leaf_data = MPV::verify_proof_and_extract_leaf(mmr_proof)
838                    .ok_or(VerificationError::BadMmrProof)?;
839
840                <MmrRootStorageProof<MmrHash> as BasicStorageProof<CBlock>>::verify::<SKP>(
841                    mmr_root_storage_proof,
842                    consensus_chain_mmr_leaf_proof.consensus_block_number,
843                    &leaf_data.state_root(),
844                )?
845            };
846
847            // Verify the original XDM mmr proof statelessly to get the original result
848            let is_valid_mmr_proof =
849                MPV::verify_proof_stateless(mmr_root, consensus_chain_mmr_leaf_proof).is_some();
850
851            if is_valid_mmr_proof == is_good_invalid_fraud_proof {
852                return Err(VerificationError::InvalidProof);
853            }
854            Ok(())
855        }
856    }
857}