1use crate::ExecutionReceiptFor;
2use crate::aux_schema::BundleMismatchType;
3use crate::fraud_proof::{FraudProofFor, FraudProofGenerator};
4use crate::utils::{DomainBlockImportNotification, DomainImportNotificationSinks};
5use domain_block_builder::{BlockBuilder, BuiltBlock, CollectedStorageChanges};
6use domain_block_preprocessor::PreprocessResult;
7use domain_block_preprocessor::inherents::get_inherent_data;
8use parity_scale_codec::Encode;
9use sc_client_api::{AuxStore, BlockBackend, ExecutorProvider, Finalizer, ProofProvider};
10use sc_consensus::{
11 BlockImportParams, BoxBlockImport, ForkChoiceStrategy, ImportResult, StateAction,
12 StorageChanges,
13};
14use sc_executor::RuntimeVersionOf;
15use sc_transaction_pool_api::OffchainTransactionPoolFactory;
16use sp_api::{ApiExt, ProvideRuntimeApi};
17use sp_blockchain::{HashAndNumber, HeaderBackend, HeaderMetadata};
18use sp_consensus::{BlockOrigin, SyncOracle};
19use sp_core::H256;
20use sp_core::traits::CodeExecutor;
21use sp_domains::bundle::BundleValidity;
22use sp_domains::core_api::DomainCoreApi;
23use sp_domains::execution_receipt::execution_receipt_v0::ExecutionReceiptV0;
24use sp_domains::execution_receipt::{ExecutionReceipt, ExecutionReceiptVersion};
25use sp_domains::merkle_tree::MerkleTree;
26use sp_domains::{DomainId, DomainsApi, HeaderHashingFor};
27use sp_domains_fraud_proof::FraudProofApi;
28use sp_messenger::MessengerApi;
29use sp_mmr_primitives::MmrApi;
30use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor, One, Zero};
31use sp_runtime::{Digest, Saturating};
32use std::cmp::Ordering;
33use std::collections::VecDeque;
34use std::sync::Arc;
35
36struct DomainBlockBuildResult<Block>
37where
38 Block: BlockT,
39{
40 header_number: NumberFor<Block>,
41 header_hash: Block::Hash,
42 state_root: Block::Hash,
43 extrinsics_root: Block::Hash,
44 intermediate_roots: Vec<Block::Hash>,
45}
46
47pub(crate) struct DomainBlockResult<Block, CBlock>
48where
49 Block: BlockT,
50 CBlock: BlockT,
51{
52 pub header_hash: Block::Hash,
53 pub header_number: NumberFor<Block>,
54 pub execution_receipt: ExecutionReceiptFor<Block, CBlock>,
55}
56
57pub(crate) struct DomainBlockProcessor<Block, CBlock, Client, CClient, Backend, Executor>
59where
60 Block: BlockT,
61 CBlock: BlockT,
62{
63 pub(crate) domain_id: DomainId,
64 pub(crate) domain_created_at: NumberFor<CBlock>,
65 pub(crate) client: Arc<Client>,
66 pub(crate) consensus_client: Arc<CClient>,
67 pub(crate) backend: Arc<Backend>,
68 pub(crate) block_import: Arc<BoxBlockImport<Block>>,
69 pub(crate) import_notification_sinks: DomainImportNotificationSinks<Block, CBlock>,
70 pub(crate) domain_sync_oracle: Arc<dyn SyncOracle + Send + Sync>,
71 pub(crate) domain_executor: Arc<Executor>,
72 pub(crate) challenge_period: NumberFor<CBlock>,
73}
74
75impl<Block, CBlock, Client, CClient, Backend, Executor> Clone
76 for DomainBlockProcessor<Block, CBlock, Client, CClient, Backend, Executor>
77where
78 Block: BlockT,
79 CBlock: BlockT,
80{
81 fn clone(&self) -> Self {
82 Self {
83 domain_id: self.domain_id,
84 domain_created_at: self.domain_created_at,
85 client: self.client.clone(),
86 consensus_client: self.consensus_client.clone(),
87 backend: self.backend.clone(),
88 block_import: self.block_import.clone(),
89 import_notification_sinks: self.import_notification_sinks.clone(),
90 domain_sync_oracle: self.domain_sync_oracle.clone(),
91 domain_executor: self.domain_executor.clone(),
92 challenge_period: self.challenge_period,
93 }
94 }
95}
96
97#[derive(Debug)]
104pub(crate) struct PendingConsensusBlocks<Block: BlockT, CBlock: BlockT> {
105 pub initial_parent: (Block::Hash, NumberFor<Block>),
107 pub consensus_imports: Vec<HashAndNumber<CBlock>>,
109}
110
111impl<Block, CBlock, Client, CClient, Backend, Executor>
112 DomainBlockProcessor<Block, CBlock, Client, CClient, Backend, Executor>
113where
114 Block: BlockT,
115 CBlock: BlockT,
116 NumberFor<CBlock>: Into<NumberFor<Block>>,
117 Client: HeaderBackend<Block>
118 + BlockBackend<Block>
119 + AuxStore
120 + ProvideRuntimeApi<Block>
121 + Finalizer<Block, Backend>
122 + ExecutorProvider<Block>
123 + 'static,
124 Client::Api:
125 DomainCoreApi<Block> + sp_block_builder::BlockBuilder<Block> + sp_api::ApiExt<Block>,
126 CClient: HeaderBackend<CBlock>
127 + HeaderMetadata<CBlock, Error = sp_blockchain::Error>
128 + BlockBackend<CBlock>
129 + ProvideRuntimeApi<CBlock>
130 + 'static,
131 CClient::Api: DomainsApi<CBlock, Block::Header>
132 + MessengerApi<CBlock, NumberFor<CBlock>, CBlock::Hash>
133 + 'static,
134 Backend: sc_client_api::Backend<Block> + 'static,
135 Executor: CodeExecutor,
136{
137 pub(crate) fn pending_imported_consensus_blocks(
142 &self,
143 consensus_block_hash: CBlock::Hash,
144 consensus_block_number: NumberFor<CBlock>,
145 ) -> sp_blockchain::Result<Option<PendingConsensusBlocks<Block, CBlock>>> {
146 match consensus_block_number.cmp(&(self.domain_created_at + One::one())) {
147 Ordering::Less => return Ok(None),
150 Ordering::Equal => {
153 return Ok(Some(PendingConsensusBlocks {
154 initial_parent: (self.client.info().genesis_hash, Zero::zero()),
155 consensus_imports: vec![HashAndNumber {
156 hash: consensus_block_hash,
157 number: consensus_block_number,
158 }],
159 }));
160 }
161 Ordering::Greater => {}
164 }
165
166 let best_hash = self.client.info().best_hash;
167 let best_number = self.client.info().best_number;
168
169 let consensus_block_hash_for_best_domain_hash =
173 match crate::aux_schema::latest_consensus_block_hash_for(&*self.backend, &best_hash)? {
174 None if best_number.is_zero() => self
183 .consensus_client
184 .hash(self.domain_created_at)?
185 .ok_or_else(|| {
186 sp_blockchain::Error::Backend(format!(
187 "Consensus hash for block #{} not found",
188 self.domain_created_at
189 ))
190 })?,
191 None => {
192 return Err(sp_blockchain::Error::Backend(format!(
193 "Consensus hash for domain hash #{best_number},{best_hash} not found"
194 )));
195 }
196 Some(b) => b,
197 };
198
199 let consensus_from = consensus_block_hash_for_best_domain_hash;
200 let consensus_to = consensus_block_hash;
201
202 if consensus_from == consensus_to {
203 tracing::debug!("Consensus block {consensus_block_hash:?} has already been processed");
204 return Ok(None);
205 }
206
207 let route =
208 sp_blockchain::tree_route(&*self.consensus_client, consensus_from, consensus_to)?;
209
210 let retracted = route.retracted();
211 let enacted = route.enacted();
212
213 tracing::trace!(
214 ?retracted,
215 ?enacted,
216 common_block = ?route.common_block(),
217 "Calculating PendingConsensusBlocks on #{best_number},{best_hash:?}"
218 );
219
220 match (retracted.is_empty(), enacted.is_empty()) {
221 (true, false) => {
222 Ok(Some(PendingConsensusBlocks {
224 initial_parent: (best_hash, best_number),
225 consensus_imports: enacted.to_vec(),
226 }))
227 }
228 (false, true) => {
229 tracing::debug!("Consensus blocks {retracted:?} have been already processed");
230 Ok(None)
231 }
232 (true, true) => {
233 unreachable!(
234 "Tree route is not empty as `consensus_from` and `consensus_to` in tree_route() \
235 are checked above to be not the same; qed",
236 );
237 }
238 (false, false) => {
239 let (common_block_number, common_block_hash) =
240 (route.common_block().number, route.common_block().hash);
241
242 if common_block_number <= self.domain_created_at {
247 let consensus_imports = enacted
248 .iter()
249 .skip_while(|block| block.number <= self.domain_created_at)
250 .cloned()
251 .collect();
252 return Ok(Some(PendingConsensusBlocks {
253 initial_parent: (self.client.info().genesis_hash, Zero::zero()),
254 consensus_imports,
255 }));
256 }
257
258 let domain_block_hash: Block::Hash = crate::aux_schema::best_domain_hash_for(
261 &*self.client,
262 &common_block_hash,
263 )?
264 .ok_or_else(
265 || {
266 sp_blockchain::Error::Backend(format!(
267 "Hash of domain block derived from consensus block #{common_block_number},{common_block_hash} not found"
268 ))
269 },
270 )?;
271 let parent_header = self.client.header(domain_block_hash)?.ok_or_else(|| {
272 sp_blockchain::Error::Backend(format!(
273 "Domain block header for #{domain_block_hash:?} not found",
274 ))
275 })?;
276
277 Ok(Some(PendingConsensusBlocks {
278 initial_parent: (parent_header.hash(), *parent_header.number()),
279 consensus_imports: enacted.to_vec(),
280 }))
281 }
282 }
283 }
284
285 pub(crate) async fn process_domain_block(
286 &self,
287 (consensus_block_hash, consensus_block_number): (CBlock::Hash, NumberFor<CBlock>),
288 (parent_hash, parent_number): (Block::Hash, NumberFor<Block>),
289 preprocess_result: PreprocessResult<Block>,
290 inherent_digests: Digest,
291 ) -> Result<DomainBlockResult<Block, CBlock>, sp_blockchain::Error> {
292 let PreprocessResult {
293 extrinsics,
294 bundles,
295 } = preprocess_result;
296
297 let fork_choice = ForkChoiceStrategy::Custom(false);
302 let inherent_data = get_inherent_data::<_, _, Block>(
303 self.consensus_client.clone(),
304 consensus_block_hash,
305 parent_hash,
306 self.domain_id,
307 )
308 .await?;
309
310 let DomainBlockBuildResult {
311 extrinsics_root,
312 state_root,
313 header_number,
314 header_hash,
315 intermediate_roots,
316 } = self
317 .build_and_import_block(
318 parent_hash,
319 parent_number,
320 extrinsics,
321 fork_choice,
322 inherent_digests,
323 inherent_data,
324 )
325 .await?;
326
327 tracing::debug!(
328 "Built new domain block #{header_number},{header_hash} from \
329 consensus block #{consensus_block_number},{consensus_block_hash} \
330 on top of parent domain block #{parent_number},{parent_hash}"
331 );
332
333 let roots: Vec<[u8; 32]> = intermediate_roots
334 .iter()
335 .map(|v| {
336 v.encode().try_into().expect(
337 "State root uses the same Block hash type which must fit into [u8; 32]; qed",
338 )
339 })
340 .collect();
341 let trace_root = MerkleTree::from_leaves(&roots).root().ok_or_else(|| {
342 sp_blockchain::Error::Application(Box::from("Failed to get merkle root of trace"))
343 })?;
344
345 tracing::trace!(
346 ?intermediate_roots,
347 ?trace_root,
348 "Trace root calculated for #{header_number},{header_hash}"
349 );
350
351 let runtime_api = self.consensus_client.runtime_api();
352
353 let execution_receipt_version = runtime_api
354 .current_bundle_and_execution_receipt_version(consensus_block_hash)?
355 .execution_receipt_version;
356
357 let parent_receipt = if parent_number.is_zero() {
358 runtime_api
359 .genesis_execution_receipt(consensus_block_hash, self.domain_id)?
360 .ok_or_else(|| {
361 sp_blockchain::Error::Backend("Genesis Execution receipt not found".to_string())
362 })?
363 } else {
364 crate::load_execution_receipt_by_domain_hash::<Block, CBlock, _>(
365 &*self.client,
366 parent_hash,
367 parent_number,
368 )?
369 };
370
371 let runtime_api = self.client.runtime_api();
374 let block_fees = runtime_api.block_fees(header_hash)?;
375 let transfers = runtime_api.transfers(header_hash)?;
376
377 let execution_receipt = match execution_receipt_version {
378 ExecutionReceiptVersion::V0 => ExecutionReceipt::V0(ExecutionReceiptV0 {
379 domain_block_number: header_number,
380 domain_block_hash: header_hash,
381 domain_block_extrinsic_root: extrinsics_root,
382 parent_domain_block_receipt_hash: parent_receipt
383 .hash::<HeaderHashingFor<Block::Header>>(),
384 consensus_block_number,
385 consensus_block_hash,
386 inboxed_bundles: bundles,
387 final_state_root: state_root,
388 execution_trace: intermediate_roots,
389 execution_trace_root: sp_core::H256(trace_root),
390 block_fees,
391 transfers,
392 }),
393 };
394
395 Ok(DomainBlockResult {
396 header_hash,
397 header_number,
398 execution_receipt,
399 })
400 }
401
402 async fn build_and_import_block(
403 &self,
404 parent_hash: Block::Hash,
405 parent_number: NumberFor<Block>,
406 extrinsics: VecDeque<Block::Extrinsic>,
407 fork_choice: ForkChoiceStrategy,
408 inherent_digests: Digest,
409 inherent_data: sp_inherents::InherentData,
410 ) -> Result<DomainBlockBuildResult<Block>, sp_blockchain::Error> {
411 let block_builder = BlockBuilder::new(
412 self.client.clone(),
413 parent_hash,
414 parent_number,
415 inherent_digests,
416 self.backend.clone(),
417 self.domain_executor.clone(),
418 extrinsics,
419 Some(inherent_data),
420 )?;
421
422 let BuiltBlock {
423 block,
424 storage_changes,
425 } = block_builder.build()?;
426
427 let CollectedStorageChanges {
428 storage_changes,
429 intermediate_roots,
430 } = storage_changes;
431
432 let (header, body) = block.deconstruct();
433 let state_root = *header.state_root();
434 let extrinsics_root = *header.extrinsics_root();
435 let header_hash = header.hash();
436 let header_number = *header.number();
437
438 let block_import_params = {
439 let block_origin = if self.domain_sync_oracle.is_major_syncing() {
440 BlockOrigin::NetworkInitialSync
443 } else {
444 BlockOrigin::Own
445 };
446 let mut import_block = BlockImportParams::new(block_origin, header);
447 import_block.body = Some(body);
448 import_block.state_action =
449 StateAction::ApplyChanges(StorageChanges::Changes(storage_changes));
450 import_block.fork_choice = Some(fork_choice);
451 import_block
452 };
453 self.import_domain_block(block_import_params).await?;
454
455 Ok(DomainBlockBuildResult {
456 header_hash,
457 header_number,
458 state_root,
459 extrinsics_root,
460 intermediate_roots,
461 })
462 }
463
464 pub(crate) async fn import_domain_block(
465 &self,
466 block_import_params: BlockImportParams<Block>,
467 ) -> Result<(), sp_blockchain::Error> {
468 let (header_number, header_hash, parent_hash) = (
469 *block_import_params.header.number(),
470 block_import_params.header.hash(),
471 *block_import_params.header.parent_hash(),
472 );
473
474 let import_result = self.block_import.import_block(block_import_params).await?;
475
476 match import_result {
477 ImportResult::Imported(..) => {}
478 ImportResult::AlreadyInChain => {
479 tracing::debug!("Block #{header_number},{header_hash:?} is already in chain");
480 }
481 ImportResult::KnownBad => {
482 return Err(sp_consensus::Error::ClientImport(format!(
483 "Bad block #{header_number}({header_hash:?})"
484 ))
485 .into());
486 }
487 ImportResult::UnknownParent => {
488 return Err(sp_consensus::Error::ClientImport(format!(
489 "Block #{header_number}({header_hash:?}) has an unknown parent: {parent_hash:?}"
490 ))
491 .into());
492 }
493 ImportResult::MissingState => {
494 return Err(sp_consensus::Error::ClientImport(format!(
495 "Parent state of block #{header_number}({header_hash:?}) is missing, parent: {parent_hash:?}"
496 ))
497 .into());
498 }
499 }
500
501 Ok(())
502 }
503
504 pub(crate) fn on_consensus_block_processed(
505 &self,
506 consensus_block_hash: CBlock::Hash,
507 domain_block_result: Option<DomainBlockResult<Block, CBlock>>,
508 ) -> sp_blockchain::Result<()> {
509 let cleanup = domain_block_result.is_some();
511
512 let domain_hash = match domain_block_result {
513 Some(DomainBlockResult {
514 header_hash,
515 header_number: _,
516 execution_receipt,
517 }) => {
518 let oldest_unconfirmed_receipt_number = self
519 .consensus_client
520 .runtime_api()
521 .oldest_unconfirmed_receipt_number(consensus_block_hash, self.domain_id)?;
522 crate::aux_schema::write_execution_receipt::<_, Block, CBlock>(
523 &*self.client,
524 oldest_unconfirmed_receipt_number,
525 &execution_receipt,
526 self.challenge_period,
527 )?;
528
529 let domain_import_notification = DomainBlockImportNotification {
531 domain_block_hash: header_hash,
532 consensus_block_hash,
533 };
534
535 self.import_notification_sinks.lock().retain(|sink| {
536 sink.unbounded_send(domain_import_notification.clone())
537 .is_ok()
538 });
539
540 header_hash
541 }
542 None => {
543 let consensus_header = self
546 .consensus_client
547 .header(consensus_block_hash)?
548 .ok_or_else(|| {
549 sp_blockchain::Error::Backend(format!(
550 "Header for consensus block {consensus_block_hash:?} not found"
551 ))
552 })?;
553 if *consensus_header.number() > self.domain_created_at + One::one() {
554 let consensus_parent_hash = consensus_header.parent_hash();
555 crate::aux_schema::best_domain_hash_for(&*self.client, consensus_parent_hash)?
556 .ok_or_else(|| {
557 sp_blockchain::Error::Backend(format!(
558 "Domain hash for consensus block {consensus_parent_hash:?} not found",
559 ))
560 })?
561 } else {
562 self.client.info().genesis_hash
563 }
564 }
565 };
566
567 crate::aux_schema::track_domain_hash_and_consensus_hash::<_, Block, CBlock>(
568 &self.client,
569 domain_hash,
570 consensus_block_hash,
571 cleanup,
572 )?;
573
574 Ok(())
575 }
576}
577
578#[derive(Debug, PartialEq)]
579pub(crate) struct InboxedBundleMismatchInfo {
580 bundle_index: u32,
581 mismatch_type: BundleMismatchType,
582}
583
584pub(crate) fn find_inboxed_bundles_mismatch<Block, CBlock>(
586 local_receipt: &ExecutionReceiptFor<Block, CBlock>,
587 external_receipt: &ExecutionReceiptFor<Block, CBlock>,
588) -> Result<Option<InboxedBundleMismatchInfo>, sp_blockchain::Error>
589where
590 Block: BlockT,
591 CBlock: BlockT,
592{
593 if local_receipt.inboxed_bundles() == external_receipt.inboxed_bundles() {
594 return Ok(None);
595 }
596
597 if local_receipt.bundles_extrinsics_roots() != external_receipt.bundles_extrinsics_roots() {
603 return Err(sp_blockchain::Error::Application(format!(
604 "Found mismatch of `ER::bundles_extrinsics_roots`, this should not happen, local: {:?}, external: {:?}",
605 local_receipt.bundles_extrinsics_roots(),
606 external_receipt.bundles_extrinsics_roots(),
607 ).into()));
608 }
609
610 let (bundle_index, (local_bundle, external_bundle)) = local_receipt
612 .inboxed_bundles()
613 .iter()
614 .zip(external_receipt.inboxed_bundles().iter())
615 .enumerate()
616 .find(|(_, (local_bundle, external_bundle))| local_bundle != external_bundle)
617 .expect(
618 "The `local_receipt.inboxed_bundles` and `external_receipt.inboxed_bundles` are checked to have the \
619 same length and being non-equal; qed",
620 );
621
622 let mismatch_type = match (local_bundle.bundle.clone(), external_bundle.bundle.clone()) {
625 (
626 BundleValidity::Invalid(local_invalid_type),
627 BundleValidity::Invalid(external_invalid_type),
628 ) => {
629 match local_invalid_type
630 .checking_order()
631 .cmp(&external_invalid_type.checking_order())
632 {
633 Ordering::Less => BundleMismatchType::GoodInvalid(local_invalid_type),
636 Ordering::Greater => BundleMismatchType::BadInvalid(external_invalid_type),
639 Ordering::Equal => unreachable!(
640 "bundle validity must be different as the local/external bundle are checked to be different \
641 and they have the same `extrinsic_root`"
642 ),
643 }
644 }
645 (BundleValidity::Valid(_), BundleValidity::Valid(_)) => {
646 BundleMismatchType::ValidBundleContents
647 }
648 (BundleValidity::Valid(_), BundleValidity::Invalid(invalid_type)) => {
649 BundleMismatchType::BadInvalid(invalid_type)
650 }
651 (BundleValidity::Invalid(invalid_type), BundleValidity::Valid(_)) => {
652 BundleMismatchType::GoodInvalid(invalid_type)
653 }
654 };
655
656 Ok(Some(InboxedBundleMismatchInfo {
657 mismatch_type,
658 bundle_index: bundle_index as u32,
659 }))
660}
661
662pub(crate) struct ReceiptsChecker<Block, Client, CBlock, CClient, Backend, E>
663where
664 Block: BlockT,
665 CBlock: BlockT,
666{
667 pub(crate) domain_id: DomainId,
668 pub(crate) client: Arc<Client>,
669 pub(crate) consensus_client: Arc<CClient>,
670 pub(crate) domain_sync_oracle: Arc<dyn SyncOracle + Send + Sync>,
671 pub(crate) fraud_proof_generator:
672 FraudProofGenerator<Block, CBlock, Client, CClient, Backend, E>,
673 pub(crate) consensus_offchain_tx_pool_factory: OffchainTransactionPoolFactory<CBlock>,
674}
675
676impl<Block, CBlock, Client, CClient, Backend, E> Clone
677 for ReceiptsChecker<Block, Client, CBlock, CClient, Backend, E>
678where
679 Block: BlockT,
680 CBlock: BlockT,
681{
682 fn clone(&self) -> Self {
683 Self {
684 domain_id: self.domain_id,
685 client: self.client.clone(),
686 consensus_client: self.consensus_client.clone(),
687 domain_sync_oracle: self.domain_sync_oracle.clone(),
688 fraud_proof_generator: self.fraud_proof_generator.clone(),
689 consensus_offchain_tx_pool_factory: self.consensus_offchain_tx_pool_factory.clone(),
690 }
691 }
692}
693
694pub struct MismatchedReceipts<Block, CBlock>
695where
696 Block: BlockT,
697 CBlock: BlockT,
698{
699 local_receipt: ExecutionReceiptFor<Block, CBlock>,
700 bad_receipt: ExecutionReceiptFor<Block, CBlock>,
701}
702
703impl<Block, Client, CBlock, CClient, Backend, E>
704 ReceiptsChecker<Block, Client, CBlock, CClient, Backend, E>
705where
706 Block: BlockT,
707 Block::Hash: Into<H256>,
708 CBlock: BlockT,
709 NumberFor<CBlock>: Into<NumberFor<Block>>,
710 Client: HeaderBackend<Block>
711 + BlockBackend<Block>
712 + ProofProvider<Block>
713 + AuxStore
714 + ExecutorProvider<Block>
715 + ProvideRuntimeApi<Block>
716 + 'static,
717 Client::Api: DomainCoreApi<Block>
718 + sp_block_builder::BlockBuilder<Block>
719 + sp_api::ApiExt<Block>
720 + MessengerApi<Block, NumberFor<CBlock>, CBlock::Hash>,
721 CClient: HeaderBackend<CBlock>
722 + BlockBackend<CBlock>
723 + ProofProvider<CBlock>
724 + ProvideRuntimeApi<CBlock>
725 + 'static,
726 CClient::Api: DomainsApi<CBlock, Block::Header>
727 + FraudProofApi<CBlock, Block::Header>
728 + MmrApi<CBlock, H256, NumberFor<CBlock>>,
729 Backend: sc_client_api::Backend<Block> + 'static,
730 E: CodeExecutor + RuntimeVersionOf,
731{
732 pub(crate) fn maybe_submit_fraud_proof(
733 &self,
734 consensus_block_hash: CBlock::Hash,
735 ) -> sp_blockchain::Result<()> {
736 if self.domain_sync_oracle.is_major_syncing() {
737 tracing::debug!(
738 "Skip reporting unconfirmed bad receipt as the consensus node is still major syncing..."
739 );
740 return Ok(());
741 }
742
743 if let Some(mismatched_receipts) = self.find_mismatch_receipt(consensus_block_hash)? {
744 let fraud_proof = self.generate_fraud_proof(mismatched_receipts)?;
745 tracing::info!("Submit fraud proof: {fraud_proof:?}");
746
747 let consensus_best_hash = self.consensus_client.info().best_hash;
748 let mut consensus_runtime_api = self.consensus_client.runtime_api();
749 consensus_runtime_api.register_extension(
750 self.consensus_offchain_tx_pool_factory
751 .offchain_transaction_pool(consensus_best_hash),
752 );
753 consensus_runtime_api.submit_fraud_proof_unsigned(consensus_best_hash, fraud_proof)?;
754 }
755
756 Ok(())
757 }
758
759 pub fn find_mismatch_receipt(
760 &self,
761 consensus_block_hash: CBlock::Hash,
762 ) -> sp_blockchain::Result<Option<MismatchedReceipts<Block, CBlock>>> {
763 let mut oldest_mismatch = None;
764 let mut to_check = self
765 .consensus_client
766 .runtime_api()
767 .head_receipt_number(consensus_block_hash, self.domain_id)?;
768 let oldest_unconfirmed_receipt_number = match self
769 .consensus_client
770 .runtime_api()
771 .oldest_unconfirmed_receipt_number(consensus_block_hash, self.domain_id)?
772 {
773 Some(er_number) => er_number,
774 None => return Ok(None),
776 };
777
778 while !to_check.is_zero() && oldest_unconfirmed_receipt_number <= to_check {
779 let onchain_receipt_hash = self
780 .consensus_client
781 .runtime_api()
782 .receipt_hash(consensus_block_hash, self.domain_id, to_check)?
783 .ok_or_else(|| {
784 sp_blockchain::Error::Application(
785 format!("Receipt hash for #{to_check:?} not found").into(),
786 )
787 })?;
788 let local_receipt = {
789 let domain_hash = self.client.hash(to_check)?.ok_or_else(|| {
792 sp_blockchain::Error::Backend(format!(
793 "Domain block hash for #{to_check:?} not found"
794 ))
795 })?;
796 crate::load_execution_receipt_by_domain_hash::<Block, CBlock, _>(
797 &*self.client,
798 domain_hash,
799 to_check,
800 )?
801 };
802 if local_receipt.hash::<HeaderHashingFor<Block::Header>>() != onchain_receipt_hash {
803 oldest_mismatch.replace((local_receipt, onchain_receipt_hash));
804 to_check = to_check.saturating_sub(One::one());
805 } else {
806 break;
807 }
808 }
809
810 match oldest_mismatch {
811 None => Ok(None),
812 Some((local_receipt, bad_receipt_hash)) => {
813 let bad_receipt = self
814 .consensus_client
815 .runtime_api()
816 .execution_receipt(consensus_block_hash, bad_receipt_hash)?
817 .ok_or_else(|| {
818 sp_blockchain::Error::Application(
819 format!("Receipt for #{bad_receipt_hash:?} not found").into(),
820 )
821 })?;
822 debug_assert_eq!(
823 local_receipt.consensus_block_hash(),
824 bad_receipt.consensus_block_hash(),
825 );
826 Ok(Some(MismatchedReceipts {
827 local_receipt,
828 bad_receipt,
829 }))
830 }
831 }
832 }
833
834 #[allow(clippy::type_complexity)]
835 pub fn generate_fraud_proof(
836 &self,
837 mismatched_receipts: MismatchedReceipts<Block, CBlock>,
838 ) -> sp_blockchain::Result<FraudProofFor<CBlock, Block::Header>> {
839 let MismatchedReceipts {
840 local_receipt,
841 bad_receipt,
842 } = mismatched_receipts;
843
844 let bad_receipt_hash = bad_receipt.hash::<HeaderHashingFor<Block::Header>>();
845
846 if let Some(InboxedBundleMismatchInfo {
849 bundle_index,
850 mismatch_type,
851 }) = find_inboxed_bundles_mismatch::<Block, CBlock>(&local_receipt, &bad_receipt)?
852 {
853 return match mismatch_type {
854 BundleMismatchType::ValidBundleContents => self
855 .fraud_proof_generator
856 .generate_valid_bundle_proof(
857 self.domain_id,
858 &local_receipt,
859 bundle_index as usize,
860 bad_receipt_hash,
861 )
862 .map_err(|err| {
863 sp_blockchain::Error::Application(Box::from(format!(
864 "Failed to generate valid bundles fraud proof: {err}"
865 )))
866 }),
867 _ => self
868 .fraud_proof_generator
869 .generate_invalid_bundle_proof(
870 self.domain_id,
871 &local_receipt,
872 mismatch_type,
873 bundle_index,
874 bad_receipt_hash,
875 )
876 .map_err(|err| {
877 sp_blockchain::Error::Application(Box::from(format!(
878 "Failed to generate invalid bundles fraud proof: {err}"
879 )))
880 }),
881 };
882 }
883
884 if bad_receipt.domain_block_extrinsics_root()
885 != local_receipt.domain_block_extrinsics_root()
886 {
887 return self
888 .fraud_proof_generator
889 .generate_invalid_domain_extrinsics_root_proof(
890 self.domain_id,
891 &local_receipt,
892 bad_receipt_hash,
893 )
894 .map_err(|err| {
895 sp_blockchain::Error::Application(Box::from(format!(
896 "Failed to generate invalid domain extrinsics root fraud proof: {err}"
897 )))
898 });
899 }
900
901 if let Some(execution_phase) = self
902 .fraud_proof_generator
903 .find_mismatched_execution_phase(
904 *local_receipt.domain_block_hash(),
905 local_receipt.execution_traces(),
906 bad_receipt.execution_traces(),
907 )
908 .map_err(|err| {
909 sp_blockchain::Error::Application(Box::from(format!(
910 "Failed to find mismatched execution phase: {err}"
911 )))
912 })?
913 {
914 return self
915 .fraud_proof_generator
916 .generate_invalid_state_transition_proof(
917 self.domain_id,
918 execution_phase,
919 &local_receipt,
920 bad_receipt.execution_traces().len(),
921 bad_receipt_hash,
922 )
923 .map_err(|err| {
924 sp_blockchain::Error::Application(Box::from(format!(
925 "Failed to generate invalid state transition fraud proof: {err}"
926 )))
927 });
928 }
929
930 if bad_receipt.block_fees() != local_receipt.block_fees() {
931 return self
932 .fraud_proof_generator
933 .generate_invalid_block_fees_proof(self.domain_id, &local_receipt, bad_receipt_hash)
934 .map_err(|err| {
935 sp_blockchain::Error::Application(Box::from(format!(
936 "Failed to generate invalid block rewards fraud proof: {err}"
937 )))
938 });
939 }
940
941 if bad_receipt.transfers() != local_receipt.transfers() {
942 return self
943 .fraud_proof_generator
944 .generate_invalid_transfers_proof(self.domain_id, &local_receipt, bad_receipt_hash)
945 .map_err(|err| {
946 sp_blockchain::Error::Application(Box::from(format!(
947 "Failed to generate invalid transfers fraud proof: {err}"
948 )))
949 });
950 }
951
952 if bad_receipt.domain_block_hash() != local_receipt.domain_block_hash() {
953 return self
954 .fraud_proof_generator
955 .generate_invalid_domain_block_hash_proof(
956 self.domain_id,
957 &local_receipt,
958 bad_receipt_hash,
959 )
960 .map_err(|err| {
961 sp_blockchain::Error::Application(Box::from(format!(
962 "Failed to generate invalid domain block hash fraud proof: {err}"
963 )))
964 });
965 }
966
967 Err(sp_blockchain::Error::Application(Box::from(format!(
968 "No fraudulent field found for the mismatched ER, this should not happen, \
969 local_receipt {local_receipt:?}, bad_receipt {bad_receipt:?}"
970 ))))
971 }
972}
973
974#[cfg(test)]
975mod tests {
976 use super::*;
977 use domain_test_service::evm_domain_test_runtime::Block;
978 use sp_domains::bundle::{InboxedBundle, InvalidBundleType};
979 use subspace_runtime_primitives::BlockHashFor;
980 use subspace_test_runtime::Block as CBlock;
981
982 fn create_test_execution_receipt(
983 inboxed_bundles: Vec<InboxedBundle<BlockHashFor<Block>>>,
984 ) -> ExecutionReceiptFor<Block, CBlock>
985 where
986 Block: BlockT,
987 CBlock: BlockT,
988 {
989 ExecutionReceipt::V0(ExecutionReceiptV0 {
990 domain_block_number: Zero::zero(),
991 domain_block_hash: Default::default(),
992 domain_block_extrinsic_root: Default::default(),
993 parent_domain_block_receipt_hash: Default::default(),
994 consensus_block_hash: Default::default(),
995 consensus_block_number: Zero::zero(),
996 inboxed_bundles,
997 final_state_root: Default::default(),
998 execution_trace: vec![],
999 execution_trace_root: Default::default(),
1000 block_fees: Default::default(),
1001 transfers: Default::default(),
1002 })
1003 }
1004
1005 #[test]
1006 fn er_bundles_mismatch_detection() {
1007 assert_eq!(
1009 find_inboxed_bundles_mismatch::<Block, CBlock>(
1010 &create_test_execution_receipt(vec![]),
1011 &create_test_execution_receipt(vec![]),
1012 )
1013 .unwrap(),
1014 None
1015 );
1016
1017 assert_eq!(
1018 find_inboxed_bundles_mismatch::<Block, CBlock>(
1019 &create_test_execution_receipt(vec![InboxedBundle::invalid(
1020 InvalidBundleType::UndecodableTx(0),
1021 Default::default(),
1022 )]),
1023 &create_test_execution_receipt(vec![InboxedBundle::invalid(
1024 InvalidBundleType::UndecodableTx(0),
1025 Default::default(),
1026 )]),
1027 )
1028 .unwrap(),
1029 None
1030 );
1031
1032 assert_eq!(
1034 find_inboxed_bundles_mismatch::<Block, CBlock>(
1035 &create_test_execution_receipt(vec![
1036 InboxedBundle::invalid(InvalidBundleType::UndecodableTx(0), Default::default()),
1037 InboxedBundle::valid(H256::random(), Default::default()),
1038 ]),
1039 &create_test_execution_receipt(vec![
1040 InboxedBundle::invalid(InvalidBundleType::UndecodableTx(0), Default::default()),
1041 InboxedBundle::valid(H256::random(), Default::default()),
1042 ]),
1043 )
1044 .unwrap(),
1045 Some(InboxedBundleMismatchInfo {
1046 mismatch_type: BundleMismatchType::ValidBundleContents,
1047 bundle_index: 1,
1048 })
1049 );
1050
1051 assert_eq!(
1053 find_inboxed_bundles_mismatch::<Block, CBlock>(
1054 &create_test_execution_receipt(vec![
1055 InboxedBundle::valid(Default::default(), Default::default()),
1056 InboxedBundle::invalid(InvalidBundleType::UndecodableTx(1), Default::default()),
1057 ]),
1058 &create_test_execution_receipt(vec![
1059 InboxedBundle::valid(Default::default(), Default::default()),
1060 InboxedBundle::invalid(InvalidBundleType::UndecodableTx(2), Default::default()),
1061 ]),
1062 )
1063 .unwrap(),
1064 Some(InboxedBundleMismatchInfo {
1065 mismatch_type: BundleMismatchType::GoodInvalid(InvalidBundleType::UndecodableTx(1)),
1066 bundle_index: 1,
1067 })
1068 );
1069 assert_eq!(
1070 find_inboxed_bundles_mismatch::<Block, CBlock>(
1071 &create_test_execution_receipt(vec![
1072 InboxedBundle::valid(Default::default(), Default::default()),
1073 InboxedBundle::invalid(InvalidBundleType::UndecodableTx(4), Default::default()),
1074 ]),
1075 &create_test_execution_receipt(vec![
1076 InboxedBundle::valid(Default::default(), Default::default()),
1077 InboxedBundle::invalid(InvalidBundleType::UndecodableTx(3), Default::default()),
1078 ]),
1079 )
1080 .unwrap(),
1081 Some(InboxedBundleMismatchInfo {
1082 mismatch_type: BundleMismatchType::BadInvalid(InvalidBundleType::UndecodableTx(3)),
1083 bundle_index: 1,
1084 })
1085 );
1086 assert_eq!(
1088 find_inboxed_bundles_mismatch::<Block, CBlock>(
1089 &create_test_execution_receipt(vec![
1090 InboxedBundle::valid(Default::default(), Default::default()),
1091 InboxedBundle::invalid(InvalidBundleType::UndecodableTx(4), Default::default()),
1092 ]),
1093 &create_test_execution_receipt(vec![
1094 InboxedBundle::valid(Default::default(), Default::default()),
1095 InboxedBundle::invalid(InvalidBundleType::IllegalTx(3), Default::default()),
1096 ]),
1097 )
1098 .unwrap(),
1099 Some(InboxedBundleMismatchInfo {
1100 mismatch_type: BundleMismatchType::BadInvalid(InvalidBundleType::IllegalTx(3)),
1101 bundle_index: 1,
1102 })
1103 );
1104
1105 assert_eq!(
1107 find_inboxed_bundles_mismatch::<Block, CBlock>(
1108 &create_test_execution_receipt(vec![
1109 InboxedBundle::valid(Default::default(), Default::default()),
1110 InboxedBundle::invalid(InvalidBundleType::IllegalTx(3), Default::default()),
1111 ]),
1112 &create_test_execution_receipt(vec![
1113 InboxedBundle::valid(Default::default(), Default::default()),
1114 InboxedBundle::invalid(
1115 InvalidBundleType::InherentExtrinsic(3),
1116 Default::default()
1117 ),
1118 ]),
1119 )
1120 .unwrap(),
1121 Some(InboxedBundleMismatchInfo {
1122 mismatch_type: BundleMismatchType::BadInvalid(
1123 InvalidBundleType::InherentExtrinsic(3)
1124 ),
1125 bundle_index: 1,
1126 })
1127 );
1128
1129 assert_eq!(
1130 find_inboxed_bundles_mismatch::<Block, CBlock>(
1131 &create_test_execution_receipt(vec![
1132 InboxedBundle::valid(Default::default(), Default::default()),
1133 InboxedBundle::invalid(
1134 InvalidBundleType::InherentExtrinsic(3),
1135 Default::default()
1136 ),
1137 ]),
1138 &create_test_execution_receipt(vec![
1139 InboxedBundle::valid(Default::default(), Default::default()),
1140 InboxedBundle::invalid(InvalidBundleType::IllegalTx(3), Default::default()),
1141 ]),
1142 )
1143 .unwrap(),
1144 Some(InboxedBundleMismatchInfo {
1145 mismatch_type: BundleMismatchType::GoodInvalid(
1146 InvalidBundleType::InherentExtrinsic(3)
1147 ),
1148 bundle_index: 1,
1149 })
1150 );
1151
1152 assert_eq!(
1154 find_inboxed_bundles_mismatch::<Block, CBlock>(
1155 &create_test_execution_receipt(vec![
1156 InboxedBundle::valid(H256::random(), Default::default()),
1157 InboxedBundle::invalid(
1158 InvalidBundleType::InherentExtrinsic(3),
1159 Default::default()
1160 ),
1161 ]),
1162 &create_test_execution_receipt(vec![
1163 InboxedBundle::valid(H256::random(), Default::default()),
1164 InboxedBundle::invalid(InvalidBundleType::IllegalTx(3), Default::default()),
1165 ]),
1166 )
1167 .unwrap(),
1168 Some(InboxedBundleMismatchInfo {
1169 mismatch_type: BundleMismatchType::ValidBundleContents,
1170 bundle_index: 0,
1171 })
1172 );
1173
1174 assert_eq!(
1176 find_inboxed_bundles_mismatch::<Block, CBlock>(
1177 &create_test_execution_receipt(vec![InboxedBundle::valid(
1178 H256::random(),
1179 Default::default(),
1180 ),]),
1181 &create_test_execution_receipt(vec![InboxedBundle::invalid(
1182 InvalidBundleType::IllegalTx(3),
1183 Default::default(),
1184 ),]),
1185 )
1186 .unwrap(),
1187 Some(InboxedBundleMismatchInfo {
1188 mismatch_type: BundleMismatchType::BadInvalid(InvalidBundleType::IllegalTx(3)),
1189 bundle_index: 0,
1190 })
1191 );
1192
1193 assert_eq!(
1195 find_inboxed_bundles_mismatch::<Block, CBlock>(
1196 &create_test_execution_receipt(vec![InboxedBundle::invalid(
1197 InvalidBundleType::IllegalTx(3),
1198 Default::default(),
1199 ),]),
1200 &create_test_execution_receipt(vec![InboxedBundle::valid(
1201 H256::random(),
1202 Default::default(),
1203 ),]),
1204 )
1205 .unwrap(),
1206 Some(InboxedBundleMismatchInfo {
1207 mismatch_type: BundleMismatchType::GoodInvalid(InvalidBundleType::IllegalTx(3)),
1208 bundle_index: 0,
1209 })
1210 );
1211 }
1212}