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