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