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#[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 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 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 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 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 #[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 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 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 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 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 => None,
775 Some(xdm_mmr_proof) => {
776 let xdm_mmr_proof_constructed_at_number =
777 xdm_mmr_proof.consensus_block_number;
778
779 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 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 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}