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