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