domain_client_operator/
fraud_proof.rs

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