domain_client_operator/
fraud_proof.rs

1use crate::ExecutionReceiptFor;
2use crate::aux_schema::BundleMismatchType;
3use domain_block_builder::BlockBuilder;
4use domain_runtime_primitives::CheckExtrinsicsValidityError;
5use domain_runtime_primitives::opaque::AccountId;
6use parity_scale_codec::{Decode, Encode};
7use sc_client_api::{AuxStore, BlockBackend, ExecutorProvider, ProofProvider};
8use sc_domains::FPStorageKeyProvider;
9use sp_api::{ApiExt, ProvideRuntimeApi};
10use sp_blockchain::HeaderBackend;
11use sp_core::H256;
12use sp_core::traits::CodeExecutor;
13use sp_domain_digests::AsPredigest;
14use sp_domains::bundle::{InvalidBundleType, OpaqueBundles};
15use sp_domains::core_api::DomainCoreApi;
16use sp_domains::proof_provider_and_verifier::StorageProofProvider;
17use sp_domains::{DomainId, DomainsApi, ExtrinsicDigest, HeaderHashingFor, RuntimeId};
18use sp_domains_fraud_proof::FraudProofApi;
19use sp_domains_fraud_proof::execution_prover::ExecutionProver;
20use sp_domains_fraud_proof::fraud_proof::{
21    ApplyExtrinsicMismatch, DomainRuntimeCodeAt, ExecutionPhase, FinalizeBlockMismatch, FraudProof,
22    FraudProofVariant, InvalidBlockFeesProof, InvalidBundlesProof, InvalidBundlesProofData,
23    InvalidDomainBlockHashProof, InvalidExtrinsicsRootProof, InvalidStateTransitionProof,
24    InvalidTransfersProof, MmrRootProof, ValidBundleDigest, ValidBundleProof,
25};
26use sp_domains_fraud_proof::storage_proof::{self, *};
27use sp_messenger::MessengerApi;
28use sp_mmr_primitives::MmrApi;
29use sp_runtime::generic::BlockId;
30use sp_runtime::traits::{Block as BlockT, HashingFor, Header as HeaderT, NumberFor, One};
31use sp_runtime::{Digest, DigestItem};
32use sp_trie::LayoutV1;
33use std::marker::PhantomData;
34use std::sync::Arc;
35use subspace_runtime_primitives::{Balance, BlockHashFor};
36
37#[derive(Copy, Clone, PartialEq, Debug)]
38pub enum TraceDiffType {
39    Shorter,
40    Longer,
41    Mismatch,
42}
43
44/// Error type for fraud proof generation.
45#[derive(Debug, thiserror::Error)]
46pub enum FraudProofError {
47    #[error("Out of bounds extrinsic index for creating the fraud proof, got: {index}, max: {max}")]
48    OutOfBoundsExtrinsicIndex { index: usize, max: usize },
49    #[error(transparent)]
50    Blockchain(#[from] sp_blockchain::Error),
51    #[error(transparent)]
52    RuntimeApi(#[from] sp_api::ApiError),
53    #[error("Missing bundle at {bundle_index}")]
54    MissingBundle { bundle_index: usize },
55    #[error("Invalid bundle extrinsic at bundle index[{bundle_index}]")]
56    InvalidBundleExtrinsic { bundle_index: usize },
57    #[error("Fail to generate the proof of inclusion")]
58    FailToGenerateProofOfInclusion,
59    #[error("Missing extrinsics in the Consesnsus block")]
60    MissingConsensusExtrinsics,
61    #[error("Unable to decode opaque bundle's extrinsic at index {extrinsic_index}")]
62    UnableToDecodeOpaqueBundleExtrinsic {
63        extrinsic_index: usize,
64        decoding_error: parity_scale_codec::Error,
65    },
66    #[error(
67        "Invalid extrinsic index for creating illegal tx fraud proof, \
68        expected extrinsic index: {index},\
69        is_good_invalid_fraud_proof: {is_good_invalid_fraud_proof} \
70        and validity response is: {extrinsics_validity_response:?}"
71    )]
72    InvalidIllegalTxFraudProofExtrinsicIndex {
73        index: usize,
74        is_good_invalid_fraud_proof: bool,
75        extrinsics_validity_response: Result<(), CheckExtrinsicsValidityError>,
76    },
77    #[error("Fail to generate the storage proof")]
78    StorageProof(#[from] storage_proof::GenerationError),
79}
80
81pub struct FraudProofGenerator<Block: BlockT, CBlock, Client, CClient, Backend, E> {
82    client: Arc<Client>,
83    consensus_client: Arc<CClient>,
84    backend: Arc<Backend>,
85    code_executor: Arc<E>,
86    storage_key_provider: FPStorageKeyProvider<CBlock, Block::Header, CClient>,
87    _phantom: PhantomData<(Block, CBlock)>,
88}
89
90impl<Block: BlockT, CBlock, Client, CClient, Backend, E> Clone
91    for FraudProofGenerator<Block, CBlock, Client, CClient, Backend, E>
92{
93    fn clone(&self) -> Self {
94        Self {
95            client: self.client.clone(),
96            consensus_client: self.consensus_client.clone(),
97            backend: self.backend.clone(),
98            code_executor: self.code_executor.clone(),
99            storage_key_provider: self.storage_key_provider.clone(),
100            _phantom: self._phantom,
101        }
102    }
103}
104
105pub type FraudProofFor<CBlock, DomainHeader> =
106    FraudProof<NumberFor<CBlock>, BlockHashFor<CBlock>, DomainHeader, H256>;
107
108impl<Block, CBlock, Client, CClient, Backend, E>
109    FraudProofGenerator<Block, CBlock, Client, CClient, Backend, E>
110where
111    Block: BlockT,
112    Block::Hash: Into<H256>,
113    CBlock: BlockT,
114    Client: HeaderBackend<Block>
115        + BlockBackend<Block>
116        + AuxStore
117        + ProvideRuntimeApi<Block>
118        + ProofProvider<Block>
119        + ExecutorProvider<Block>
120        + 'static,
121    Client::Api: sp_block_builder::BlockBuilder<Block>
122        + sp_api::ApiExt<Block>
123        + DomainCoreApi<Block>
124        + MessengerApi<Block, NumberFor<CBlock>, CBlock::Hash>,
125    CClient: HeaderBackend<CBlock>
126        + BlockBackend<CBlock>
127        + ProvideRuntimeApi<CBlock>
128        + ProofProvider<CBlock>
129        + 'static,
130    CClient::Api: DomainsApi<CBlock, Block::Header>
131        + MmrApi<CBlock, H256, NumberFor<CBlock>>
132        + FraudProofApi<CBlock, Block::Header>,
133    Backend: sc_client_api::Backend<Block> + Send + Sync + 'static,
134    E: CodeExecutor,
135{
136    pub fn new(
137        client: Arc<Client>,
138        consensus_client: Arc<CClient>,
139        backend: Arc<Backend>,
140        code_executor: Arc<E>,
141    ) -> Self {
142        Self {
143            client,
144            consensus_client: consensus_client.clone(),
145            backend,
146            code_executor,
147            storage_key_provider: FPStorageKeyProvider::new(consensus_client),
148            _phantom: Default::default(),
149        }
150    }
151
152    #[allow(clippy::type_complexity)]
153    fn maybe_generate_domain_runtime_code_proof_for_receipt(
154        &self,
155        domain_id: DomainId,
156        local_receipt: &ExecutionReceiptFor<Block, CBlock>,
157    ) -> Result<Option<DomainRuntimeCodeAt<NumberFor<CBlock>, CBlock::Hash, H256>>, FraudProofError>
158    {
159        // NOTE: domain runtime code is take affect in the next block, so we need to get
160        // the doamin runtime code of the parent block, which is what used to derived the
161        // ER.
162        let (parent_consensus_number, parent_consensus_hash) = {
163            let consensus_header = self.consensus_header(*local_receipt.consensus_block_hash())?;
164            (
165                *local_receipt.consensus_block_number() - One::one(),
166                *consensus_header.parent_hash(),
167            )
168        };
169
170        let best_hash = self.consensus_client.info().best_hash;
171        let runtime_api = self.consensus_client.runtime_api();
172        let runtime_id = runtime_api
173            .runtime_id(best_hash, domain_id)?
174            .ok_or_else(|| {
175                sp_blockchain::Error::Application(
176                    "Failed to get domain runtime id".to_string().into(),
177                )
178            })?;
179        let is_domain_runtime_upgraded_since = runtime_api
180            .is_domain_runtime_upgraded_since(best_hash, domain_id, parent_consensus_number)?
181            .ok_or_else(|| {
182                sp_blockchain::Error::Application(
183                    "Failed to get domain runtime object".to_string().into(),
184                )
185            })?;
186
187        if is_domain_runtime_upgraded_since {
188            let mmr_proof =
189                sc_domains::generate_mmr_proof(&self.consensus_client, parent_consensus_number)?;
190            let domain_runtime_code_proof = DomainRuntimeCodeProof::generate(
191                self.consensus_client.as_ref(),
192                parent_consensus_hash,
193                runtime_id,
194                &self.storage_key_provider,
195            )?;
196            Ok(Some(DomainRuntimeCodeAt {
197                mmr_proof,
198                domain_runtime_code_proof,
199            }))
200        } else {
201            Ok(None)
202        }
203    }
204
205    pub(crate) fn generate_invalid_state_transition_proof(
206        &self,
207        domain_id: DomainId,
208        execution_phase: ExecutionPhase,
209        local_receipt: &ExecutionReceiptFor<Block, CBlock>,
210        bad_receipt_trace_length: usize,
211        bad_receipt_hash: Block::Hash,
212    ) -> Result<FraudProofFor<CBlock, Block::Header>, FraudProofError> {
213        let block_hash = *local_receipt.domain_block_hash();
214        let block_number = *local_receipt.domain_block_number();
215        let header = self.header(block_hash)?;
216        let parent_header = self.header(*header.parent_hash())?;
217
218        let prover = ExecutionProver::new(self.backend.clone(), self.code_executor.clone());
219
220        let inherent_digests = Digest {
221            logs: vec![DigestItem::consensus_block_info(
222                *local_receipt.consensus_block_hash(),
223            )],
224        };
225
226        let extrinsics = self.block_body(block_hash)?;
227        let max_extrinsic_index = extrinsics.len() - 1;
228        let encoded_extrinsics: Vec<_> = extrinsics.iter().map(Encode::encode).collect();
229
230        let mut block_builder = BlockBuilder::new(
231            self.client.clone(),
232            parent_header.hash(),
233            *parent_header.number(),
234            inherent_digests.clone(),
235            self.backend.clone(),
236            self.code_executor.clone(),
237            extrinsics.into(),
238            // NOTE: the inherent extrinsic is already contained in the above `extrinsics`, which
239            // is getting from the block body, thus it is okay to pass `maybe_inherent_data` as
240            // `None`.
241            None,
242        )?;
243
244        let (storage_changes, call_data) = match &execution_phase {
245            ExecutionPhase::InitializeBlock => (
246                None,
247                Block::Header::new(
248                    block_number,
249                    Default::default(),
250                    Default::default(),
251                    parent_header.hash(),
252                    inherent_digests,
253                )
254                .encode(),
255            ),
256            ExecutionPhase::ApplyExtrinsic { mismatch, .. } => {
257                let extrinsic_index = match mismatch {
258                    ApplyExtrinsicMismatch::StateRoot(trace_mismatch_index) => {
259                        (trace_mismatch_index - 1) as usize
260                    }
261                    ApplyExtrinsicMismatch::Shorter => (bad_receipt_trace_length - 1) - 1,
262                };
263
264                let target_extrinsic = encoded_extrinsics.get(extrinsic_index).ok_or(
265                    FraudProofError::OutOfBoundsExtrinsicIndex {
266                        index: extrinsic_index,
267                        max: max_extrinsic_index,
268                    },
269                )?;
270
271                (
272                    Some(block_builder.prepare_storage_changes_before(extrinsic_index)?),
273                    target_extrinsic.clone(),
274                )
275            }
276            ExecutionPhase::FinalizeBlock { .. } => (
277                Some(block_builder.prepare_storage_changes_before_finalize_block()?),
278                Vec::new(),
279            ),
280        };
281
282        let delta_changes = storage_changes.map(|storage_changes| {
283            (
284                storage_changes.storage_changes.transaction,
285                storage_changes.storage_changes.transaction_storage_root,
286            )
287        });
288
289        let execution_proof = prover.prove_execution(
290            parent_header.hash(),
291            &execution_phase,
292            call_data.as_slice(),
293            delta_changes,
294        )?;
295
296        let maybe_domain_runtime_code_proof =
297            self.maybe_generate_domain_runtime_code_proof_for_receipt(domain_id, local_receipt)?;
298
299        let invalid_state_transition_proof = FraudProof {
300            domain_id,
301            bad_receipt_hash,
302            maybe_mmr_proof: None,
303            maybe_domain_runtime_code_proof,
304            proof: FraudProofVariant::InvalidStateTransition(InvalidStateTransitionProof {
305                execution_proof,
306                execution_phase,
307            }),
308        };
309
310        Ok(invalid_state_transition_proof)
311    }
312
313    fn extract_successful_bundles(
314        &self,
315        consensus_block_hash: CBlock::Hash,
316        domain_id: DomainId,
317        consensus_extrinsics: Vec<CBlock::Extrinsic>,
318    ) -> Result<OpaqueBundles<CBlock, Block::Header, Balance>, FraudProofError> {
319        let runtime_api = self.consensus_client.runtime_api();
320        runtime_api
321            .extract_successful_bundles(consensus_block_hash, domain_id, consensus_extrinsics)
322            .map_err(FraudProofError::RuntimeApi)
323    }
324
325    pub(crate) fn generate_valid_bundle_proof(
326        &self,
327        domain_id: DomainId,
328        local_receipt: &ExecutionReceiptFor<Block, CBlock>,
329        bundle_index: usize,
330        bad_receipt_hash: Block::Hash,
331    ) -> Result<FraudProofFor<CBlock, Block::Header>, FraudProofError> {
332        let consensus_block_hash = *local_receipt.consensus_block_hash();
333        let consensus_block_number = *local_receipt.consensus_block_number();
334        let consensus_extrinsics = self
335            .consensus_client
336            .block_body(consensus_block_hash)?
337            .ok_or(FraudProofError::MissingConsensusExtrinsics)?;
338
339        let mmr_proof =
340            sc_domains::generate_mmr_proof(&self.consensus_client, consensus_block_number)?;
341
342        let maybe_domain_runtime_code_proof =
343            self.maybe_generate_domain_runtime_code_proof_for_receipt(domain_id, local_receipt)?;
344
345        let mut bundles =
346            self.extract_successful_bundles(consensus_block_hash, domain_id, consensus_extrinsics)?;
347
348        if bundle_index >= bundles.len() {
349            return Err(FraudProofError::MissingBundle { bundle_index });
350        }
351        let bundle = bundles.swap_remove(bundle_index);
352
353        let bundle_with_proof = OpaqueBundleWithProof::generate(
354            &self.storage_key_provider,
355            self.consensus_client.as_ref(),
356            domain_id,
357            consensus_block_hash,
358            bundle,
359            bundle_index as u32,
360        )?;
361
362        let valid_bundle_proof = FraudProof {
363            domain_id,
364            bad_receipt_hash,
365            maybe_mmr_proof: Some(mmr_proof),
366            maybe_domain_runtime_code_proof,
367            proof: FraudProofVariant::ValidBundle(ValidBundleProof { bundle_with_proof }),
368        };
369
370        Ok(valid_bundle_proof)
371    }
372
373    pub(crate) fn generate_invalid_domain_extrinsics_root_proof(
374        &self,
375        domain_id: DomainId,
376        local_receipt: &ExecutionReceiptFor<Block, CBlock>,
377        bad_receipt_hash: Block::Hash,
378    ) -> Result<FraudProofFor<CBlock, Block::Header>, FraudProofError> {
379        let consensus_block_hash = *local_receipt.consensus_block_hash();
380        let consensus_block_number = *local_receipt.consensus_block_number();
381
382        let valid_bundle_digests =
383            self.generate_valid_bundle_digest_for_receipt(domain_id, local_receipt)?;
384
385        let mmr_proof =
386            sc_domains::generate_mmr_proof(&self.consensus_client, consensus_block_number)?;
387
388        let maybe_domain_runtime_code_proof =
389            self.maybe_generate_domain_runtime_code_proof_for_receipt(domain_id, local_receipt)?;
390
391        let maybe_runtime_id =
392            self.is_domain_runtime_upgraded_at(domain_id, consensus_block_hash)?;
393
394        let invalid_inherent_extrinsic_proofs = InvalidInherentExtrinsicDataProof::generate(
395            self.consensus_client.as_ref(),
396            consensus_block_hash,
397            (),
398            &self.storage_key_provider,
399        )?;
400
401        let maybe_domain_runtime_upgraded_proof = MaybeDomainRuntimeUpgradedProof::generate(
402            &self.storage_key_provider,
403            self.consensus_client.as_ref(),
404            consensus_block_hash,
405            maybe_runtime_id,
406        )?;
407
408        let domain_chain_allowlist_proof = DomainChainsAllowlistUpdateStorageProof::generate(
409            self.consensus_client.as_ref(),
410            consensus_block_hash,
411            domain_id,
412            &self.storage_key_provider,
413        )?;
414
415        let domain_sudo_call_proof = DomainSudoCallStorageProof::generate(
416            self.consensus_client.as_ref(),
417            consensus_block_hash,
418            domain_id,
419            &self.storage_key_provider,
420        )?;
421
422        let evm_domain_contract_creation_allowed_by_call_proof =
423            EvmDomainContractCreationAllowedByCallStorageProof::generate(
424                self.consensus_client.as_ref(),
425                consensus_block_hash,
426                domain_id,
427                &self.storage_key_provider,
428            )?;
429
430        let invalid_domain_extrinsics_root_proof = FraudProof {
431            domain_id,
432            bad_receipt_hash,
433            maybe_mmr_proof: Some(mmr_proof),
434            maybe_domain_runtime_code_proof,
435            proof: FraudProofVariant::InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof {
436                valid_bundle_digests,
437                invalid_inherent_extrinsic_proofs,
438                maybe_domain_runtime_upgraded_proof,
439                domain_chain_allowlist_proof,
440                domain_sudo_call_proof,
441                evm_domain_contract_creation_allowed_by_call_proof,
442            }),
443        };
444
445        Ok(invalid_domain_extrinsics_root_proof)
446    }
447
448    pub fn is_domain_runtime_upgraded_at(
449        &self,
450        domain_id: DomainId,
451        at: CBlock::Hash,
452    ) -> Result<Option<RuntimeId>, FraudProofError> {
453        let runtime_id = self
454            .consensus_client
455            .runtime_api()
456            .runtime_id(at, domain_id)?
457            .ok_or(sp_blockchain::Error::Application(Box::from(format!(
458                "No RuntimeId found for {domain_id:?}"
459            ))))?;
460        let runtime_upgrades = self.consensus_client.runtime_api().runtime_upgrades(at)?;
461
462        let is_runtime_upgraded = runtime_upgrades.contains(&runtime_id);
463
464        Ok(is_runtime_upgraded.then_some(runtime_id))
465    }
466
467    pub(crate) fn generate_valid_bundle_digest_for_receipt(
468        &self,
469        domain_id: DomainId,
470        local_receipt: &ExecutionReceiptFor<Block, CBlock>,
471    ) -> Result<Vec<ValidBundleDigest>, FraudProofError> {
472        let consensus_block_hash = *local_receipt.consensus_block_hash();
473        let consensus_extrinsics = self
474            .consensus_client
475            .block_body(consensus_block_hash)?
476            .ok_or_else(|| {
477                sp_blockchain::Error::Backend(format!(
478                    "BlockBody of {consensus_block_hash:?} unavailable"
479                ))
480            })?;
481
482        let bundles =
483            self.extract_successful_bundles(consensus_block_hash, domain_id, consensus_extrinsics)?;
484
485        let domain_runtime_api = self.client.runtime_api();
486        let mut valid_bundle_digests = Vec::new();
487        for bundle_index in local_receipt.valid_bundle_indexes() {
488            let extrinsics = bundles
489                .get(bundle_index as usize)
490                .map(|bundle| bundle.extrinsics())
491                .ok_or(FraudProofError::MissingBundle {
492                    bundle_index: bundle_index as usize,
493                })?;
494
495            let mut exts = Vec::with_capacity(extrinsics.len());
496            for opaque_extrinsic in extrinsics {
497                let extrinsic = Block::Extrinsic::decode(&mut opaque_extrinsic.encode().as_slice())
498                    .map_err(|_| FraudProofError::InvalidBundleExtrinsic {
499                        bundle_index: bundle_index as usize,
500                    })?;
501
502                exts.push(extrinsic)
503            }
504
505            let bundle_digest = domain_runtime_api
506                .extract_signer(*local_receipt.domain_block_hash(), exts)?
507                .into_iter()
508                .map(|(signer, ext)| {
509                    (
510                        signer,
511                        ExtrinsicDigest::new::<LayoutV1<HeaderHashingFor<Block::Header>>>(
512                            ext.encode(),
513                        ),
514                    )
515                })
516                .collect::<Vec<(Option<AccountId>, ExtrinsicDigest)>>();
517            valid_bundle_digests.push(ValidBundleDigest {
518                bundle_index,
519                bundle_digest,
520            });
521        }
522
523        Ok(valid_bundle_digests)
524    }
525
526    /// Generates and returns an invalid bundle proof.
527    pub(crate) fn generate_invalid_bundle_proof(
528        &self,
529        domain_id: DomainId,
530        local_receipt: &ExecutionReceiptFor<Block, CBlock>,
531        mismatch_type: BundleMismatchType,
532        bundle_index: u32,
533        bad_receipt_hash: Block::Hash,
534    ) -> Result<FraudProofFor<CBlock, Block::Header>, FraudProofError> {
535        self.generate_invalid_bundle_proof_inner(
536            domain_id,
537            local_receipt,
538            mismatch_type,
539            bundle_index,
540            bad_receipt_hash,
541            false,
542        )
543    }
544
545    /// Test-only function for generating an invalid bundle proof.
546    #[cfg(test)]
547    pub(crate) fn generate_invalid_bundle_proof_for_test(
548        &self,
549        domain_id: DomainId,
550        local_receipt: &ExecutionReceiptFor<Block, CBlock>,
551        mismatch_type: BundleMismatchType,
552        bundle_index: u32,
553        bad_receipt_hash: Block::Hash,
554        allow_invalid_proof_in_tests: bool,
555    ) -> Result<FraudProofFor<CBlock, Block::Header>, FraudProofError> {
556        self.generate_invalid_bundle_proof_inner(
557            domain_id,
558            local_receipt,
559            mismatch_type,
560            bundle_index,
561            bad_receipt_hash,
562            allow_invalid_proof_in_tests,
563        )
564    }
565
566    /// Inner function for generating invalid bundle proofs, with test support code.
567    fn generate_invalid_bundle_proof_inner(
568        &self,
569        domain_id: DomainId,
570        local_receipt: &ExecutionReceiptFor<Block, CBlock>,
571        mismatch_type: BundleMismatchType,
572        bundle_index: u32,
573        bad_receipt_hash: Block::Hash,
574        // Whether to generate an invalid proof against a valid ER,
575        // only used in tests, ignored in production
576        mut allow_invalid_proof_in_tests: bool,
577    ) -> Result<FraudProofFor<CBlock, Block::Header>, FraudProofError> {
578        if cfg!(not(test)) {
579            allow_invalid_proof_in_tests = false;
580        }
581
582        let consensus_block_hash = *local_receipt.consensus_block_hash();
583        let consensus_block_number = *local_receipt.consensus_block_number();
584
585        let bundle = {
586            let consensus_extrinsics = self
587                .consensus_client
588                .block_body(consensus_block_hash)?
589                .ok_or(FraudProofError::MissingConsensusExtrinsics)?;
590
591            let mut bundles = self.extract_successful_bundles(
592                consensus_block_hash,
593                domain_id,
594                consensus_extrinsics,
595            )?;
596            if bundles.len() <= bundle_index as usize {
597                return Err(FraudProofError::MissingBundle {
598                    bundle_index: bundle_index as usize,
599                });
600            }
601
602            bundles.swap_remove(bundle_index as usize)
603        };
604
605        let (invalid_type, is_good_invalid_fraud_proof) = match mismatch_type {
606            BundleMismatchType::GoodInvalid(invalid_type) => (invalid_type, true),
607            BundleMismatchType::BadInvalid(invalid_type) => (invalid_type, false),
608            BundleMismatchType::ValidBundleContents => {
609                return Err(sp_blockchain::Error::Application(
610                    "Unexpected bundle mismatch type, this should not happen"
611                        .to_string()
612                        .into(),
613                )
614                .into());
615            }
616        };
617
618        let proof_data = if invalid_type
619            .extrinsic_index()
620            .is_some_and(|idx| bundle.body_length() as u32 <= idx)
621        {
622            // The bad receipt claims a non-exist extrinsic is invalid, in this case, generate a
623            // `bundle_with_proof` as proof data is enough
624            let bundle_with_proof = OpaqueBundleWithProof::generate(
625                &self.storage_key_provider,
626                self.consensus_client.as_ref(),
627                domain_id,
628                consensus_block_hash,
629                bundle,
630                bundle_index,
631            )?;
632            InvalidBundlesProofData::Bundle(bundle_with_proof)
633        } else {
634            match invalid_type {
635                InvalidBundleType::IllegalTx(expected_extrinsic_index) => {
636                    if expected_extrinsic_index as usize >= bundle.body_length() {
637                        return Err(FraudProofError::OutOfBoundsExtrinsicIndex {
638                            index: expected_extrinsic_index as usize,
639                            max: bundle.body_length(),
640                        });
641                    }
642
643                    let domain_block_parent_hash = *self
644                        .client
645                        .header(*local_receipt.domain_block_hash())?
646                        .ok_or_else(|| {
647                            FraudProofError::Blockchain(sp_blockchain::Error::MissingHeader(
648                                format!("{:?}", local_receipt.domain_block_hash()),
649                            ))
650                        })?
651                        .parent_hash();
652
653                    let domain_block_parent_number = self
654                        .client
655                        .block_number_from_id(&BlockId::Hash(domain_block_parent_hash))?
656                        .ok_or_else(|| {
657                            FraudProofError::Blockchain(sp_blockchain::Error::Backend(format!(
658                                "unable to get block number for domain block:{domain_block_parent_hash:?}"
659                            )))
660                        })?;
661
662                    let mut runtime_api_instance = self.client.runtime_api();
663                    runtime_api_instance.record_proof();
664                    let proof_recorder = runtime_api_instance
665                        .proof_recorder()
666                        .expect("we enabled proof recording just above; qed");
667
668                    let mut block_extrinsics = vec![];
669                    for (extrinsic_index, extrinsic) in bundle
670                        .extrinsics()
671                        .iter()
672                        .enumerate()
673                        .take((expected_extrinsic_index + 1) as usize)
674                    {
675                        let encoded_extrinsic = extrinsic.encode();
676                        block_extrinsics.push(
677                            Block::Extrinsic::decode(&mut encoded_extrinsic.as_slice()).map_err(
678                                |decoding_error| {
679                                    FraudProofError::UnableToDecodeOpaqueBundleExtrinsic {
680                                        extrinsic_index,
681                                        decoding_error,
682                                    }
683                                },
684                            )?,
685                        );
686                    }
687
688                    let validation_response = runtime_api_instance
689                        .check_extrinsics_and_do_pre_dispatch(
690                            domain_block_parent_hash,
691                            block_extrinsics,
692                            domain_block_parent_number,
693                            domain_block_parent_hash,
694                        )?;
695
696                    // Check the validation response and extrinsic indexes:
697                    // If are proving the bundle is actually invalid, the expected validation response is Err.
698                    // If we are proving the invalid bundle proof is wrong, the expected validation response is Ok.
699                    // If we are proving the bundle is actually invalid, the extrinsic indexes also must match.
700                    if !allow_invalid_proof_in_tests
701                        && ((is_good_invalid_fraud_proof == validation_response.is_ok())
702                            || (is_good_invalid_fraud_proof
703                                && validation_response
704                                    .as_ref()
705                                    .is_err_and(|e| e.extrinsic_index != expected_extrinsic_index)))
706                    {
707                        return Err(FraudProofError::InvalidIllegalTxFraudProofExtrinsicIndex {
708                            index: expected_extrinsic_index as usize,
709                            is_good_invalid_fraud_proof,
710                            extrinsics_validity_response: validation_response,
711                        });
712                    }
713
714                    let execution_proof = proof_recorder.drain_storage_proof();
715                    let bundle_with_proof = OpaqueBundleWithProof::generate(
716                        &self.storage_key_provider,
717                        self.consensus_client.as_ref(),
718                        domain_id,
719                        consensus_block_hash,
720                        bundle,
721                        bundle_index,
722                    )?;
723
724                    InvalidBundlesProofData::BundleAndExecution {
725                        bundle_with_proof,
726                        execution_proof,
727                    }
728                }
729                InvalidBundleType::OutOfRangeTx(_) | InvalidBundleType::InvalidBundleWeight => {
730                    let bundle_with_proof = OpaqueBundleWithProof::generate(
731                        &self.storage_key_provider,
732                        self.consensus_client.as_ref(),
733                        domain_id,
734                        consensus_block_hash,
735                        bundle,
736                        bundle_index,
737                    )?;
738
739                    InvalidBundlesProofData::Bundle(bundle_with_proof)
740                }
741                InvalidBundleType::UndecodableTx(extrinsic_index)
742                | InvalidBundleType::InherentExtrinsic(extrinsic_index) => {
743                    let encoded_extrinsics: Vec<_> =
744                        bundle.extrinsics().iter().map(Encode::encode).collect();
745
746                    let extrinsic_proof = StorageProofProvider::<
747                        LayoutV1<HeaderHashingFor<Block::Header>>,
748                    >::generate_enumerated_proof_of_inclusion(
749                        encoded_extrinsics.as_slice(),
750                        extrinsic_index,
751                    )
752                    .ok_or(FraudProofError::FailToGenerateProofOfInclusion)?;
753
754                    InvalidBundlesProofData::Extrinsic(extrinsic_proof)
755                }
756                InvalidBundleType::InvalidXDM(extrinsic_index) => {
757                    let encoded_extrinsic = bundle.extrinsics()[extrinsic_index as usize].encode();
758                    let extrinsic = Block::Extrinsic::decode(&mut encoded_extrinsic.as_slice())
759                        .map_err(|decoding_error| {
760                            FraudProofError::UnableToDecodeOpaqueBundleExtrinsic {
761                                extrinsic_index: extrinsic_index as usize,
762                                decoding_error,
763                            }
764                        })?;
765
766                    let maybe_xdm_mmr_proof = self
767                        .client
768                        .runtime_api()
769                        .extract_xdm_mmr_proof(*local_receipt.domain_block_hash(), &extrinsic)?;
770
771                    let mmr_root_proof = match maybe_xdm_mmr_proof {
772                        // `None` this is not an XDM so not need to generate mmr root proof
773                        None => None,
774                        Some(xdm_mmr_proof) => {
775                            let xdm_mmr_proof_constructed_at_number =
776                                xdm_mmr_proof.consensus_block_number;
777
778                            // The MMR leaf is added to the state at the next block
779                            let mmr_root_state_added_at_number =
780                                xdm_mmr_proof_constructed_at_number + One::one();
781                            let mmr_root_state_added_at_hash = self.consensus_client.hash(mmr_root_state_added_at_number)?.ok_or_else(|| {
782                                sp_blockchain::Error::Backend(format!(
783                                    "Consensus block hash for #{mmr_root_state_added_at_number:?} not found",
784                                ))
785                            })?;
786
787                            let mmr_proof = sc_domains::generate_mmr_proof(
788                                &self.consensus_client,
789                                mmr_root_state_added_at_number,
790                            )?;
791                            let mmr_root_storage_proof = MmrRootStorageProof::generate(
792                                self.consensus_client.as_ref(),
793                                mmr_root_state_added_at_hash,
794                                xdm_mmr_proof_constructed_at_number,
795                                &self.storage_key_provider,
796                            )?;
797                            Some(MmrRootProof {
798                                mmr_proof,
799                                mmr_root_storage_proof,
800                            })
801                        }
802                    };
803
804                    let extrinsic_proof = {
805                        let encoded_extrinsics: Vec<_> =
806                            bundle.extrinsics().iter().map(Encode::encode).collect();
807
808                        StorageProofProvider::<
809                            LayoutV1<HeaderHashingFor<Block::Header>>,
810                        >::generate_enumerated_proof_of_inclusion(
811                            encoded_extrinsics.as_slice(),
812                            extrinsic_index,
813                        )
814                        .ok_or(FraudProofError::FailToGenerateProofOfInclusion)?
815                    };
816
817                    InvalidBundlesProofData::InvalidXDMProofData {
818                        extrinsic_proof,
819                        mmr_root_proof,
820                    }
821                }
822            }
823        };
824
825        let mmr_proof =
826            sc_domains::generate_mmr_proof(&self.consensus_client, consensus_block_number)?;
827
828        let maybe_domain_runtime_code_proof =
829            self.maybe_generate_domain_runtime_code_proof_for_receipt(domain_id, local_receipt)?;
830
831        let proof = FraudProofVariant::InvalidBundles(InvalidBundlesProof {
832            bundle_index,
833            invalid_bundle_type: invalid_type,
834            is_good_invalid_fraud_proof,
835            proof_data,
836        });
837        let invalid_bundle_proof = FraudProof {
838            domain_id,
839            bad_receipt_hash,
840            maybe_mmr_proof: Some(mmr_proof),
841            maybe_domain_runtime_code_proof,
842            proof,
843        };
844
845        Ok(invalid_bundle_proof)
846    }
847
848    pub(crate) fn generate_invalid_block_fees_proof(
849        &self,
850        domain_id: DomainId,
851        local_receipt: &ExecutionReceiptFor<Block, CBlock>,
852        bad_receipt_hash: Block::Hash,
853    ) -> Result<FraudProofFor<CBlock, Block::Header>, FraudProofError> {
854        let block_hash = *local_receipt.domain_block_hash();
855        let runtime_api = self.client.runtime_api();
856
857        let key = runtime_api.block_fees_storage_key(block_hash)?;
858        let storage_proof = self
859            .client
860            .read_proof(block_hash, &mut [key.as_slice()].into_iter())?;
861
862        let maybe_domain_runtime_code_proof =
863            self.maybe_generate_domain_runtime_code_proof_for_receipt(domain_id, local_receipt)?;
864
865        let invalid_block_fees_proof = FraudProof {
866            domain_id,
867            bad_receipt_hash,
868            maybe_mmr_proof: None,
869            maybe_domain_runtime_code_proof,
870            proof: FraudProofVariant::InvalidBlockFees(InvalidBlockFeesProof { storage_proof }),
871        };
872
873        Ok(invalid_block_fees_proof)
874    }
875
876    pub(crate) fn generate_invalid_transfers_proof(
877        &self,
878        domain_id: DomainId,
879        local_receipt: &ExecutionReceiptFor<Block, CBlock>,
880        bad_receipt_hash: Block::Hash,
881    ) -> Result<FraudProofFor<CBlock, Block::Header>, FraudProofError> {
882        let block_hash = *local_receipt.domain_block_hash();
883        let runtime_api = self.client.runtime_api();
884        let key = runtime_api.transfers_storage_key(block_hash)?;
885        let storage_proof = self
886            .client
887            .read_proof(block_hash, &mut [key.as_slice()].into_iter())?;
888
889        let maybe_domain_runtime_code_proof =
890            self.maybe_generate_domain_runtime_code_proof_for_receipt(domain_id, local_receipt)?;
891
892        let invalid_transfers_proof = FraudProof {
893            domain_id,
894            bad_receipt_hash,
895            maybe_mmr_proof: None,
896            maybe_domain_runtime_code_proof,
897            proof: FraudProofVariant::InvalidTransfers(InvalidTransfersProof { storage_proof }),
898        };
899
900        Ok(invalid_transfers_proof)
901    }
902
903    pub(crate) fn generate_invalid_domain_block_hash_proof(
904        &self,
905        domain_id: DomainId,
906        local_receipt: &ExecutionReceiptFor<Block, CBlock>,
907        bad_receipt_hash: Block::Hash,
908    ) -> Result<FraudProofFor<CBlock, Block::Header>, FraudProofError> {
909        let block_hash = *local_receipt.domain_block_hash();
910        let digest_key = sp_domains::system_digest_final_key();
911        let digest_storage_proof = self
912            .client
913            .read_proof(block_hash, &mut [digest_key.as_slice()].into_iter())?;
914
915        let invalid_domain_block_hash = FraudProof {
916            domain_id,
917            bad_receipt_hash,
918            maybe_mmr_proof: None,
919            maybe_domain_runtime_code_proof: None,
920            proof: FraudProofVariant::InvalidDomainBlockHash(InvalidDomainBlockHashProof {
921                digest_storage_proof,
922            }),
923        };
924
925        Ok(invalid_domain_block_hash)
926    }
927
928    fn header(&self, hash: Block::Hash) -> Result<Block::Header, sp_blockchain::Error> {
929        self.client
930            .header(hash)?
931            .ok_or_else(|| sp_blockchain::Error::Backend(format!("Header not found for {hash:?}")))
932    }
933
934    fn consensus_header(&self, hash: CBlock::Hash) -> Result<CBlock::Header, sp_blockchain::Error> {
935        self.consensus_client.header(hash)?.ok_or_else(|| {
936            sp_blockchain::Error::Backend(format!("Consensus header not found for {hash:?}"))
937        })
938    }
939
940    fn block_body(&self, at: Block::Hash) -> Result<Vec<Block::Extrinsic>, sp_blockchain::Error> {
941        self.client.block_body(at)?.ok_or_else(|| {
942            sp_blockchain::Error::Backend(format!("Block body not found for {at:?}"))
943        })
944    }
945
946    fn generate_execution_phase(
947        &self,
948        local_receipt_domain_block_hash: Block::Hash,
949        local_trace_length: usize,
950        mismatch: (TraceDiffType, u32),
951    ) -> Result<ExecutionPhase, FraudProofError> {
952        let extrinsics = self.block_body(local_receipt_domain_block_hash)?;
953        let encoded_extrinsics: Vec<_> = extrinsics.iter().map(Encode::encode).collect();
954
955        match mismatch {
956            (_, 0) => Ok(ExecutionPhase::InitializeBlock),
957            (TraceDiffType::Longer, mismatch_trace_index) => Ok(ExecutionPhase::FinalizeBlock {
958                mismatch: FinalizeBlockMismatch::Longer(mismatch_trace_index),
959            }),
960            (TraceDiffType::Mismatch, mismatch_trace_index)
961                if mismatch_trace_index as usize == local_trace_length - 1 =>
962            {
963                Ok(ExecutionPhase::FinalizeBlock {
964                    mismatch: FinalizeBlockMismatch::StateRoot,
965                })
966            }
967            (TraceDiffType::Mismatch, mismatch_trace_index)
968            | (TraceDiffType::Shorter, mismatch_trace_index) => {
969                // There is a trace root of the `initialize_block` in the head of the trace so we
970                // need to minus one to get the correct `extrinsic_index`
971                let extrinsic_index = mismatch_trace_index as usize - 1;
972
973                if extrinsic_index >= encoded_extrinsics.len() {
974                    return Err(FraudProofError::OutOfBoundsExtrinsicIndex {
975                        index: extrinsic_index,
976                        max: encoded_extrinsics.len(),
977                    });
978                }
979
980                let proof_of_inclusion = StorageProofProvider::<
981                    LayoutV1<HashingFor<Block>>,
982                >::generate_enumerated_proof_of_inclusion(
983                    encoded_extrinsics.as_slice(),
984                    extrinsic_index as u32,
985                )
986                .ok_or(FraudProofError::FailToGenerateProofOfInclusion)?;
987
988                Ok(ExecutionPhase::ApplyExtrinsic {
989                    extrinsic_proof: proof_of_inclusion,
990                    mismatch: if mismatch.0 == TraceDiffType::Mismatch {
991                        ApplyExtrinsicMismatch::StateRoot(mismatch_trace_index)
992                    } else {
993                        ApplyExtrinsicMismatch::Shorter
994                    },
995                })
996            }
997        }
998    }
999
1000    /// Returns first mismatched ExecutionPhase between the receipts `local` and `other` if any.
1001    /// If local trace length > other trace length then we provide storage proof as usual but add a flag in fraud proof
1002    /// indicating that this is length mismatch, so we create a storage proof with ApplyExtrinsic execution phase
1003    /// and prove that this state transition is valid, that means execution should not stop here.
1004    ///
1005    /// if other trace length > local trace length then we create a storage proof with FinalizeBlock execution phase
1006    /// to prove that it is valid, so that means execution should stop here.
1007    pub(crate) fn find_mismatched_execution_phase(
1008        &self,
1009        local_receipt_domain_block_hash: Block::Hash,
1010        local_trace: &[Block::Hash],
1011        other_trace: &[Block::Hash],
1012    ) -> Result<Option<ExecutionPhase>, FraudProofError> {
1013        let state_root_mismatch = local_trace
1014            .iter()
1015            .enumerate()
1016            .zip(other_trace.iter().enumerate())
1017            .find_map(|((local_index, local_root), (_, other_root))| {
1018                if local_root != other_root {
1019                    Some((TraceDiffType::Mismatch, local_index as u32))
1020                } else {
1021                    None
1022                }
1023            });
1024
1025        let mismatch = if let Some(mismatch) = state_root_mismatch {
1026            mismatch
1027        } else if local_trace.len() > other_trace.len() {
1028            (TraceDiffType::Shorter, (other_trace.len() - 1) as u32)
1029        } else if local_trace.len() < other_trace.len() {
1030            (TraceDiffType::Longer, (local_trace.len() - 1) as u32)
1031        } else {
1032            return Ok(None);
1033        };
1034
1035        self.generate_execution_phase(local_receipt_domain_block_hash, local_trace.len(), mismatch)
1036            .map(Some)
1037    }
1038}