1#[cfg(not(feature = "std"))]
4extern crate alloc;
5
6use crate::{
7 BalanceOf, BlockTree, BlockTreeNodeFor, BlockTreeNodes, Config, ConsensusBlockHash,
8 DomainBlockNumberFor, DomainGenesisBlockExecutionReceipt, DomainHashingFor,
9 DomainRuntimeUpgradeRecords, ExecutionInbox, ExecutionReceiptOf, HeadDomainNumber,
10 HeadReceiptNumber, InboxedBundleAuthor, LatestConfirmedDomainExecutionReceipt,
11 LatestSubmittedER, NewAddedHeadReceipt, Pallet, ReceiptHashFor, SkipBalanceChecks,
12};
13#[cfg(not(feature = "std"))]
14use alloc::vec::Vec;
15use frame_support::{PalletError, ensure};
16use frame_system::pallet_prelude::BlockNumberFor;
17use parity_scale_codec::{Decode, Encode};
18use scale_info::TypeInfo;
19use sp_core::Get;
20use sp_domains::merkle_tree::MerkleTree;
21use sp_domains::{
22 ChainId, DomainId, DomainsTransfersTracker, ExecutionReceipt, OnChainRewards, OperatorId,
23 Transfers,
24};
25use sp_runtime::traits::{BlockNumberProvider, CheckedSub, One, Saturating, Zero};
26use sp_std::cmp::Ordering;
27use sp_std::collections::btree_map::BTreeMap;
28
29#[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
31pub enum Error {
32 InvalidExtrinsicsRoots,
33 UnknownParentBlockReceipt,
34 BuiltOnUnknownConsensusBlock,
35 InFutureReceipt,
36 PrunedReceipt,
37 StaleReceipt,
38 NewBranchReceipt,
39 BadGenesisReceipt,
40 UnexpectedReceiptType,
41 MaxHeadDomainNumber,
42 MissingDomainBlock,
43 InvalidTraceRoot,
44 InvalidExecutionTrace,
45 UnavailableConsensusBlockHash,
46 InvalidStateRoot,
47 BalanceOverflow,
48 DomainTransfersTracking,
49 InvalidDomainTransfers,
50 OverwritingER,
51 RuntimeNotFound,
52 LastBlockNotFound,
53 UnmatchedNewHeadReceipt,
54}
55
56#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
57pub struct BlockTreeNode<Number, Hash, DomainNumber, DomainHash, Balance> {
58 pub execution_receipt: ExecutionReceipt<Number, Hash, DomainNumber, DomainHash, Balance>,
60 pub operator_ids: Vec<OperatorId>,
66}
67
68#[derive(Debug, PartialEq, Eq)]
69pub(crate) enum AcceptedReceiptType {
70 NewHead,
72 CurrentHead,
74}
75
76#[derive(Debug, PartialEq, Eq)]
77pub(crate) enum RejectedReceiptType {
78 InFuture,
80 Pruned,
82 Stale,
84 NewBranch,
89}
90
91impl From<RejectedReceiptType> for Error {
92 fn from(rejected_receipt: RejectedReceiptType) -> Error {
93 match rejected_receipt {
94 RejectedReceiptType::InFuture => Error::InFutureReceipt,
95 RejectedReceiptType::Pruned => Error::PrunedReceipt,
96 RejectedReceiptType::Stale => Error::StaleReceipt,
97 RejectedReceiptType::NewBranch => Error::NewBranchReceipt,
98 }
99 }
100}
101
102#[derive(Debug, PartialEq, Eq)]
104pub(crate) enum ReceiptType {
105 Accepted(AcceptedReceiptType),
106 Rejected(RejectedReceiptType),
107}
108
109pub(crate) fn does_receipt_exists<T: Config>(
110 domain_id: DomainId,
111 domain_number: DomainBlockNumberFor<T>,
112 receipt_hash: ReceiptHashFor<T>,
113) -> bool {
114 BlockTree::<T>::get(domain_id, domain_number)
115 .map(|h| h == receipt_hash)
116 .unwrap_or(false)
117}
118
119pub(crate) fn execution_receipt_type<T: Config>(
121 domain_id: DomainId,
122 execution_receipt: &ExecutionReceiptOf<T>,
123) -> ReceiptType {
124 let receipt_number = execution_receipt.domain_block_number;
125 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
126 let head_receipt_extended = NewAddedHeadReceipt::<T>::get(domain_id).is_some();
127 let next_receipt_number = head_receipt_number.saturating_add(One::one());
128 let latest_confirmed_domain_block_number =
129 Pallet::<T>::latest_confirmed_domain_block_number(domain_id);
130
131 match receipt_number.cmp(&next_receipt_number) {
132 Ordering::Greater => ReceiptType::Rejected(RejectedReceiptType::InFuture),
133 Ordering::Equal => {
134 if head_receipt_extended {
138 ReceiptType::Rejected(RejectedReceiptType::InFuture)
139 } else {
140 ReceiptType::Accepted(AcceptedReceiptType::NewHead)
141 }
142 }
143 Ordering::Less => {
144 if !latest_confirmed_domain_block_number.is_zero()
146 && receipt_number <= latest_confirmed_domain_block_number
147 {
148 return ReceiptType::Rejected(RejectedReceiptType::Pruned);
149 }
150
151 let already_exist = does_receipt_exists::<T>(
153 domain_id,
154 receipt_number,
155 execution_receipt.hash::<DomainHashingFor<T>>(),
156 );
157 if !already_exist {
158 return ReceiptType::Rejected(RejectedReceiptType::NewBranch);
159 }
160
161 let is_first_genesis_receipt =
164 receipt_number.is_zero() && HeadDomainNumber::<T>::get(domain_id).is_zero();
165 if receipt_number == head_receipt_number
166 && (head_receipt_extended || is_first_genesis_receipt)
167 {
168 return ReceiptType::Accepted(AcceptedReceiptType::CurrentHead);
169 }
170
171 ReceiptType::Rejected(RejectedReceiptType::Stale)
173 }
174 }
175}
176
177pub(crate) fn verify_execution_receipt<T: Config>(
179 domain_id: DomainId,
180 execution_receipt: &ExecutionReceiptOf<T>,
181) -> Result<(), Error> {
182 let ExecutionReceipt {
183 consensus_block_number,
184 consensus_block_hash,
185 domain_block_number,
186 inboxed_bundles,
187 parent_domain_block_receipt_hash,
188 execution_trace,
189 execution_trace_root,
190 final_state_root,
191 ..
192 } = execution_receipt;
193
194 if let ReceiptType::Rejected(rejected_receipt_type) =
196 execution_receipt_type::<T>(domain_id, execution_receipt)
197 {
198 return Err(rejected_receipt_type.into());
199 }
200
201 if let Some(new_added_head_receipt) = NewAddedHeadReceipt::<T>::get(domain_id) {
207 ensure!(
208 new_added_head_receipt == execution_receipt.hash::<DomainHashingFor<T>>(),
209 Error::UnmatchedNewHeadReceipt,
210 );
211 return Ok(());
212 }
213
214 if domain_block_number.is_zero() {
218 ensure!(
219 does_receipt_exists::<T>(
220 domain_id,
221 *domain_block_number,
222 execution_receipt.hash::<DomainHashingFor<T>>(),
223 ),
224 Error::BadGenesisReceipt
225 );
226 return Ok(());
227 }
228
229 if execution_trace.len() < 2 {
231 return Err(Error::InvalidExecutionTrace);
232 }
233
234 let maybe_domain_runtime_upgraded_at = {
235 let runtime_id = Pallet::<T>::runtime_id(domain_id).ok_or(Error::RuntimeNotFound)?;
236 DomainRuntimeUpgradeRecords::<T>::get(runtime_id).remove(consensus_block_number)
237 };
238
239 let excepted_consensus_block_hash =
241 match ConsensusBlockHash::<T>::get(domain_id, consensus_block_number) {
242 Some(hash) => hash,
243 None => {
244 let parent_block_number =
248 frame_system::Pallet::<T>::current_block_number() - One::one();
249 if *consensus_block_number == parent_block_number {
250 frame_system::Pallet::<T>::parent_hash()
251
252 } else if let Some(ref upgrade_entry) = maybe_domain_runtime_upgraded_at {
256 upgrade_entry.at_hash
257 } else {
258 return Err(Error::UnavailableConsensusBlockHash);
259 }
260 }
261 };
262 ensure!(
263 *consensus_block_hash == excepted_consensus_block_hash,
264 Error::BuiltOnUnknownConsensusBlock
265 );
266
267 let bundles_extrinsics_roots: Vec<_> =
269 inboxed_bundles.iter().map(|b| b.extrinsics_root).collect();
270 let execution_inbox =
271 ExecutionInbox::<T>::get((domain_id, domain_block_number, consensus_block_number));
272 let expected_extrinsics_roots: Vec<_> =
273 execution_inbox.iter().map(|b| b.extrinsics_root).collect();
274 ensure!(
275 (!bundles_extrinsics_roots.is_empty() || maybe_domain_runtime_upgraded_at.is_some())
276 && bundles_extrinsics_roots == expected_extrinsics_roots,
277 Error::InvalidExtrinsicsRoots
278 );
279
280 let mut trace = Vec::with_capacity(execution_trace.len());
282 for root in execution_trace {
283 trace.push(
284 root.encode()
285 .try_into()
286 .map_err(|_| Error::InvalidTraceRoot)?,
287 );
288 }
289 let expected_execution_trace_root: sp_core::H256 = MerkleTree::from_leaves(trace.as_slice())
290 .root()
291 .ok_or(Error::InvalidTraceRoot)?
292 .into();
293 ensure!(
294 expected_execution_trace_root == *execution_trace_root,
295 Error::InvalidTraceRoot
296 );
297
298 if let Some(expected_final_state_root) = execution_trace.last() {
300 ensure!(
301 final_state_root == expected_final_state_root,
302 Error::InvalidStateRoot
303 );
304 }
305
306 if let Some(parent_block_number) = domain_block_number.checked_sub(&One::one()) {
308 let parent_block_exist = does_receipt_exists::<T>(
309 domain_id,
310 parent_block_number,
311 *parent_domain_block_receipt_hash,
312 );
313 ensure!(parent_block_exist, Error::UnknownParentBlockReceipt);
314 }
315
316 Ok(())
317}
318
319#[derive(Debug, PartialEq)]
321pub(crate) struct ConfirmedDomainBlockInfo<ConsensusNumber, DomainNumber, Balance> {
322 pub consensus_block_number: ConsensusNumber,
323 pub domain_block_number: DomainNumber,
324 pub operator_ids: Vec<OperatorId>,
325 pub rewards: Balance,
326 pub invalid_bundle_authors: Vec<OperatorId>,
327 pub total_storage_fee: Balance,
328 pub paid_bundle_storage_fees: BTreeMap<OperatorId, u32>,
329}
330
331pub(crate) type ProcessExecutionReceiptResult<T> = Result<
332 Option<ConfirmedDomainBlockInfo<BlockNumberFor<T>, DomainBlockNumberFor<T>, BalanceOf<T>>>,
333 Error,
334>;
335
336pub(crate) fn process_execution_receipt<T: Config>(
339 domain_id: DomainId,
340 submitter: OperatorId,
341 execution_receipt: ExecutionReceiptOf<T>,
342 receipt_type: AcceptedReceiptType,
343) -> ProcessExecutionReceiptResult<T> {
344 let er_hash = execution_receipt.hash::<DomainHashingFor<T>>();
345 let receipt_block_number = execution_receipt.domain_block_number;
346 match receipt_type {
347 AcceptedReceiptType::NewHead => {
348 add_new_receipt_to_block_tree::<T>(domain_id, submitter, execution_receipt)?;
349
350 HeadReceiptNumber::<T>::insert(domain_id, receipt_block_number);
352 NewAddedHeadReceipt::<T>::insert(domain_id, er_hash);
353
354 if let Some(to_prune) =
356 receipt_block_number.checked_sub(&T::BlockTreePruningDepth::get())
357 {
358 let BlockTreeNode {
359 execution_receipt,
360 operator_ids,
361 } = match prune_receipt::<T>(domain_id, to_prune)? {
362 Some(n) => n,
363 None => return Ok(None),
366 };
367
368 let mut paid_bundle_storage_fees = BTreeMap::new();
370 let mut invalid_bundle_authors = Vec::new();
371 let bundle_digests = ExecutionInbox::<T>::get((
372 domain_id,
373 to_prune,
374 execution_receipt.consensus_block_number,
375 ));
376 for (index, bd) in bundle_digests.into_iter().enumerate() {
377 if let Some(bundle_author) = InboxedBundleAuthor::<T>::take(bd.header_hash) {
378 if execution_receipt.inboxed_bundles[index].is_invalid() {
381 invalid_bundle_authors.push(bundle_author);
382 } else {
383 paid_bundle_storage_fees
384 .entry(bundle_author)
385 .and_modify(|s| *s += bd.size)
386 .or_insert(bd.size);
387 }
388 }
389 }
390
391 let _ = ExecutionInbox::<T>::clear_prefix((domain_id, to_prune), u32::MAX, None);
394
395 LatestConfirmedDomainExecutionReceipt::<T>::insert(
396 domain_id,
397 execution_receipt.clone(),
398 );
399
400 ConsensusBlockHash::<T>::remove(
401 domain_id,
402 execution_receipt.consensus_block_number,
403 );
404
405 let block_fees = execution_receipt
406 .block_fees
407 .total_fees()
408 .ok_or(Error::BalanceOverflow)?;
409
410 ensure!(
411 execution_receipt
412 .transfers
413 .is_valid(ChainId::Domain(domain_id)),
414 Error::InvalidDomainTransfers
415 );
416
417 if !SkipBalanceChecks::<T>::get().contains(&domain_id) {
421 update_domain_transfers::<T>(
422 domain_id,
423 &execution_receipt.transfers,
424 block_fees,
425 )
426 .map_err(|_| Error::DomainTransfersTracking)?;
427 }
428
429 update_domain_runtime_upgrade_records::<T>(
430 domain_id,
431 execution_receipt.consensus_block_number,
432 )?;
433
434 execution_receipt
436 .block_fees
437 .chain_rewards
438 .into_iter()
439 .for_each(|(chain_id, reward)| {
440 T::OnChainRewards::on_chain_rewards(chain_id, reward)
441 });
442
443 return Ok(Some(ConfirmedDomainBlockInfo {
444 consensus_block_number: execution_receipt.consensus_block_number,
445 domain_block_number: to_prune,
446 operator_ids,
447 rewards: execution_receipt.block_fees.domain_execution_fee,
448 invalid_bundle_authors,
449 total_storage_fee: execution_receipt.block_fees.consensus_storage_fee,
450 paid_bundle_storage_fees,
451 }));
452 }
453 }
454 AcceptedReceiptType::CurrentHead => {
455 BlockTreeNodes::<T>::mutate(er_hash, |maybe_node| {
457 let node = maybe_node.as_mut().expect(
458 "The domain block of `CurrentHead` receipt is checked to be exist in `execution_receipt_type`; qed"
459 );
460 node.operator_ids.push(submitter);
461 });
462 }
463 }
464
465 let key = (domain_id, submitter);
467 if receipt_block_number > Pallet::<T>::latest_submitted_er(key) {
468 LatestSubmittedER::<T>::insert(key, receipt_block_number)
469 }
470
471 Ok(None)
472}
473
474type TransferTrackerError<T> =
475 <<T as Config>::DomainsTransfersTracker as DomainsTransfersTracker<BalanceOf<T>>>::Error;
476
477fn update_domain_transfers<T: Config>(
483 domain_id: DomainId,
484 transfers: &Transfers<BalanceOf<T>>,
485 block_fees: BalanceOf<T>,
486) -> Result<(), TransferTrackerError<T>> {
487 let Transfers {
488 transfers_in,
489 transfers_out,
490 transfers_rejected,
491 rejected_transfers_claimed,
492 } = transfers;
493
494 let er_chain_id = ChainId::Domain(domain_id);
496 transfers_in
497 .iter()
498 .try_for_each(|(from_chain_id, amount)| {
499 T::DomainsTransfersTracker::confirm_transfer(*from_chain_id, er_chain_id, *amount)
500 })?;
501
502 transfers_out.iter().try_for_each(|(to_chain_id, amount)| {
504 T::DomainsTransfersTracker::note_transfer(er_chain_id, *to_chain_id, *amount)
505 })?;
506
507 transfers_rejected
509 .iter()
510 .try_for_each(|(from_chain_id, amount)| {
511 T::DomainsTransfersTracker::reject_transfer(*from_chain_id, er_chain_id, *amount)
512 })?;
513
514 rejected_transfers_claimed
516 .iter()
517 .try_for_each(|(to_chain_id, amount)| {
518 T::DomainsTransfersTracker::claim_rejected_transfer(er_chain_id, *to_chain_id, *amount)
519 })?;
520
521 T::DomainsTransfersTracker::reduce_domain_balance(domain_id, block_fees)?;
523
524 Ok(())
525}
526
527fn update_domain_runtime_upgrade_records<T: Config>(
529 domain_id: DomainId,
530 consensus_number: BlockNumberFor<T>,
531) -> Result<(), Error> {
532 let runtime_id = Pallet::<T>::runtime_id(domain_id).ok_or(Error::RuntimeNotFound)?;
533 let mut domain_runtime_upgrade_records = DomainRuntimeUpgradeRecords::<T>::get(runtime_id);
534
535 if let Some(upgrade_entry) = domain_runtime_upgrade_records.get_mut(&consensus_number) {
536 if upgrade_entry.reference_count > One::one() {
538 upgrade_entry.reference_count =
539 upgrade_entry.reference_count.saturating_sub(One::one());
540 } else {
541 domain_runtime_upgrade_records.remove(&consensus_number);
542 }
543
544 if !domain_runtime_upgrade_records.is_empty() {
545 DomainRuntimeUpgradeRecords::<T>::set(runtime_id, domain_runtime_upgrade_records);
546 } else {
547 DomainRuntimeUpgradeRecords::<T>::remove(runtime_id);
548 }
549 }
550 Ok(())
551}
552
553fn add_new_receipt_to_block_tree<T: Config>(
554 domain_id: DomainId,
555 submitter: OperatorId,
556 execution_receipt: ExecutionReceiptOf<T>,
557) -> Result<(), Error> {
558 let er_hash = execution_receipt.hash::<DomainHashingFor<T>>();
560 let domain_block_number = execution_receipt.domain_block_number;
561
562 ensure!(
563 !BlockTree::<T>::contains_key(domain_id, domain_block_number),
564 Error::OverwritingER,
565 );
566
567 BlockTree::<T>::insert(domain_id, domain_block_number, er_hash);
568 let block_tree_node = BlockTreeNode {
569 execution_receipt,
570 operator_ids: sp_std::vec![submitter],
571 };
572 BlockTreeNodes::<T>::insert(er_hash, block_tree_node);
573
574 Ok(())
575}
576
577pub(crate) fn import_genesis_receipt<T: Config>(
579 domain_id: DomainId,
580 genesis_receipt: ExecutionReceiptOf<T>,
581) {
582 let er_hash = genesis_receipt.hash::<DomainHashingFor<T>>();
583 let domain_block_number = genesis_receipt.domain_block_number;
584
585 LatestConfirmedDomainExecutionReceipt::<T>::insert(domain_id, genesis_receipt.clone());
586 DomainGenesisBlockExecutionReceipt::<T>::insert(domain_id, genesis_receipt.clone());
587
588 let block_tree_node = BlockTreeNode {
589 execution_receipt: genesis_receipt,
590 operator_ids: sp_std::vec![],
591 };
592 BlockTree::<T>::insert(domain_id, domain_block_number, er_hash);
594 BlockTreeNodes::<T>::insert(er_hash, block_tree_node);
595}
596
597pub(crate) fn prune_receipt<T: Config>(
598 domain_id: DomainId,
599 receipt_number: DomainBlockNumberFor<T>,
600) -> Result<Option<BlockTreeNodeFor<T>>, Error> {
601 let receipt_hash = match BlockTree::<T>::take(domain_id, receipt_number) {
602 Some(er_hash) => er_hash,
603 None => return Ok(None),
604 };
605 let block_tree_node =
606 BlockTreeNodes::<T>::take(receipt_hash).ok_or(Error::MissingDomainBlock)?;
607
608 for operator_id in block_tree_node.operator_ids.iter() {
618 let key = (domain_id, operator_id);
619 let latest_submitted_er = Pallet::<T>::latest_submitted_er(key);
620 if block_tree_node.execution_receipt.domain_block_number == latest_submitted_er {
621 LatestSubmittedER::<T>::remove(key);
622 }
623 }
624
625 Ok(Some(block_tree_node))
626}
627
628#[cfg(test)]
629mod tests {
630 use super::*;
631 use crate::tests::{
632 BlockTreePruningDepth, Domains, Test, create_dummy_bundle_with_receipts,
633 create_dummy_receipt, extend_block_tree, extend_block_tree_from_zero,
634 get_block_tree_node_at, new_test_ext_with_extensions, register_genesis_domain,
635 run_to_block,
636 };
637 use crate::{FrozenDomains, RawOrigin as DomainOrigin};
638 use frame_support::dispatch::RawOrigin;
639 use frame_support::{assert_err, assert_ok};
640 use frame_system::Origin;
641 use sp_core::H256;
642 use sp_domains::{BundleDigest, InboxedBundle, InvalidBundleType};
643
644 #[test]
645 fn test_genesis_receipt() {
646 let mut ext = new_test_ext_with_extensions();
647 ext.execute_with(|| {
648 let domain_id = register_genesis_domain(0u128, vec![0u64]);
649
650 let block_tree_node_at_0 = BlockTree::<Test>::get(domain_id, 0).unwrap();
652
653 let genesis_node = BlockTreeNodes::<Test>::get(block_tree_node_at_0).unwrap();
654 assert!(genesis_node.operator_ids.is_empty());
655 assert_eq!(HeadReceiptNumber::<Test>::get(domain_id), 0);
656
657 let genesis_receipt = genesis_node.execution_receipt;
659 let invalid_genesis_receipt = {
660 let mut receipt = genesis_receipt.clone();
661 receipt.final_state_root = H256::random();
662 receipt
663 };
664 assert_ok!(verify_execution_receipt::<Test>(
665 domain_id,
666 &genesis_receipt
667 ));
668 assert_err!(
671 verify_execution_receipt::<Test>(domain_id, &invalid_genesis_receipt),
672 Error::NewBranchReceipt
673 );
674 });
675 }
676
677 #[test]
678 fn test_new_head_receipt() {
679 let creator = 0u128;
680 let operator_id = 1u64;
681 let block_tree_pruning_depth = <Test as Config>::BlockTreePruningDepth::get();
682
683 let mut ext = new_test_ext_with_extensions();
684 ext.execute_with(|| {
685 let domain_id = register_genesis_domain(creator, vec![operator_id]);
686
687 let genesis_node = get_block_tree_node_at::<Test>(domain_id, 0).unwrap();
689 let mut receipt = genesis_node.execution_receipt;
690 assert_eq!(
691 receipt.consensus_block_number,
692 frame_system::Pallet::<Test>::current_block_number()
693 );
694 let mut receipt_of_block_1 = None;
695 let mut bundle_header_hash_of_block_1 = None;
696 for block_number in 1..=(block_tree_pruning_depth + 3) {
697 run_to_block::<Test>(block_number, receipt.consensus_block_hash);
699
700 if block_number != 1 {
701 assert_eq!(
703 ConsensusBlockHash::<Test>::get(domain_id, block_number - 1),
704 Some(frame_system::Pallet::<Test>::block_hash(block_number - 1))
705 );
706 assert_eq!(
708 execution_receipt_type::<Test>(domain_id, &receipt),
709 ReceiptType::Accepted(AcceptedReceiptType::NewHead)
710 );
711 assert_ok!(verify_execution_receipt::<Test>(domain_id, &receipt));
712 }
713
714 let bundle_extrinsics_root = H256::random();
716 let bundle = create_dummy_bundle_with_receipts(
717 domain_id,
718 operator_id,
719 bundle_extrinsics_root,
720 receipt,
721 );
722 let bundle_header_hash = bundle.sealed_header.pre_hash();
723 let bundle_size = bundle.size();
724 assert_ok!(crate::Pallet::<Test>::submit_bundle(
725 DomainOrigin::ValidatedUnsigned.into(),
726 bundle,
727 ));
728 assert_eq!(
730 ExecutionInbox::<Test>::get((domain_id, block_number, block_number)),
731 vec![BundleDigest {
732 header_hash: bundle_header_hash,
733 extrinsics_root: bundle_extrinsics_root,
734 size: bundle_size,
735 }]
736 );
737 assert!(InboxedBundleAuthor::<Test>::contains_key(
738 bundle_header_hash
739 ));
740
741 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
743 assert_eq!(head_receipt_number, block_number - 1);
744
745 let parent_domain_block_receipt =
747 BlockTree::<Test>::get(domain_id, head_receipt_number).unwrap();
748
749 let parent_node = BlockTreeNodes::<Test>::get(parent_domain_block_receipt).unwrap();
751 assert_eq!(parent_node.operator_ids.len(), 1);
752 assert_eq!(parent_node.operator_ids[0], operator_id);
753
754 receipt = create_dummy_receipt(
757 block_number,
758 H256::random(),
759 parent_domain_block_receipt,
760 vec![bundle_extrinsics_root],
761 );
762
763 if block_number == 1 {
765 receipt_of_block_1.replace(receipt.clone());
766 bundle_header_hash_of_block_1.replace(bundle_header_hash);
767 }
768 }
769
770 let pruned_receipt = receipt_of_block_1.unwrap();
773 let pruned_bundle = bundle_header_hash_of_block_1.unwrap();
774 assert!(BlockTree::<Test>::get(domain_id, 1).is_none());
775 assert!(ExecutionInbox::<Test>::get((domain_id, 1, 1)).is_empty());
776 assert!(!InboxedBundleAuthor::<Test>::contains_key(pruned_bundle));
777 assert_eq!(
778 execution_receipt_type::<Test>(domain_id, &pruned_receipt),
779 ReceiptType::Rejected(RejectedReceiptType::Pruned)
780 );
781 assert_err!(
782 verify_execution_receipt::<Test>(domain_id, &pruned_receipt),
783 Error::PrunedReceipt
784 );
785 assert!(
786 ConsensusBlockHash::<Test>::get(domain_id, pruned_receipt.consensus_block_number,)
787 .is_none()
788 );
789 });
790 }
791
792 #[test]
793 fn test_confirm_current_head_receipt() {
794 let creator = 0u128;
795 let operator_id1 = 1u64;
796 let operator_id2 = 2u64;
797 let mut ext = new_test_ext_with_extensions();
798 ext.execute_with(|| {
799 let domain_id = register_genesis_domain(creator, vec![operator_id1, operator_id2]);
800 let next_head_receipt = extend_block_tree_from_zero(domain_id, operator_id1, 3);
801
802 assert_eq!(
804 execution_receipt_type::<Test>(domain_id, &next_head_receipt),
805 ReceiptType::Accepted(AcceptedReceiptType::NewHead)
806 );
807 assert_ok!(verify_execution_receipt::<Test>(
808 domain_id,
809 &next_head_receipt
810 ));
811 let bundle = create_dummy_bundle_with_receipts(
812 domain_id,
813 operator_id1,
814 H256::random(),
815 next_head_receipt.clone(),
816 );
817 assert_ok!(crate::Pallet::<Test>::submit_bundle(
818 DomainOrigin::ValidatedUnsigned.into(),
819 bundle,
820 ));
821
822 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
823 let current_head_receipt =
824 get_block_tree_node_at::<Test>(domain_id, head_receipt_number)
825 .unwrap()
826 .execution_receipt;
827
828 assert_eq!(next_head_receipt, current_head_receipt);
830
831 assert_eq!(
833 execution_receipt_type::<Test>(domain_id, ¤t_head_receipt),
834 ReceiptType::Accepted(AcceptedReceiptType::CurrentHead)
835 );
836 assert_ok!(verify_execution_receipt::<Test>(
837 domain_id,
838 ¤t_head_receipt
839 ));
840
841 let bundle = create_dummy_bundle_with_receipts(
843 domain_id,
844 operator_id2,
845 H256::random(),
846 current_head_receipt,
847 );
848 assert_ok!(crate::Pallet::<Test>::submit_bundle(
849 DomainOrigin::ValidatedUnsigned.into(),
850 bundle,
851 ));
852
853 let head_node = get_block_tree_node_at::<Test>(domain_id, head_receipt_number).unwrap();
854 assert_eq!(head_node.operator_ids, vec![operator_id1, operator_id2]);
855 });
856 }
857
858 #[test]
859 fn test_non_head_receipt() {
860 let creator = 0u128;
861 let operator_id1 = 1u64;
862 let operator_id2 = 2u64;
863 let mut ext = new_test_ext_with_extensions();
864 ext.execute_with(|| {
865 let domain_id = register_genesis_domain(creator, vec![operator_id1, operator_id2]);
866 extend_block_tree_from_zero(domain_id, operator_id1, 3);
867
868 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
870 let stale_receipt = get_block_tree_node_at::<Test>(domain_id, head_receipt_number - 1)
871 .unwrap()
872 .execution_receipt;
873 let stale_receipt_hash = stale_receipt.hash::<DomainHashingFor<Test>>();
874
875 assert_eq!(
877 execution_receipt_type::<Test>(domain_id, &stale_receipt),
878 ReceiptType::Rejected(RejectedReceiptType::Stale)
879 );
880 assert_err!(
881 verify_execution_receipt::<Test>(domain_id, &stale_receipt),
882 Error::StaleReceipt
883 );
884
885 let bundle = create_dummy_bundle_with_receipts(
887 domain_id,
888 operator_id2,
889 H256::random(),
890 stale_receipt,
891 );
892 assert!(crate::Pallet::<Test>::submit_bundle(RawOrigin::None.into(), bundle).is_err());
893
894 assert_eq!(
895 BlockTreeNodes::<Test>::get(stale_receipt_hash)
896 .unwrap()
897 .operator_ids,
898 vec![operator_id1]
899 );
900 });
901 }
902
903 #[test]
904 fn test_previous_head_receipt() {
905 let creator = 0u128;
906 let operator_id1 = 1u64;
907 let operator_id2 = 2u64;
908 let mut ext = new_test_ext_with_extensions();
909 ext.execute_with(|| {
910 let domain_id = register_genesis_domain(creator, vec![operator_id1, operator_id2]);
911 extend_block_tree_from_zero(domain_id, operator_id1, 3);
912
913 assert!(NewAddedHeadReceipt::<Test>::get(domain_id).is_none());
915
916 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
918 let previous_head_receipt =
919 get_block_tree_node_at::<Test>(domain_id, head_receipt_number)
920 .unwrap()
921 .execution_receipt;
922
923 assert_eq!(
925 execution_receipt_type::<Test>(domain_id, &previous_head_receipt),
926 ReceiptType::Rejected(RejectedReceiptType::Stale)
927 );
928 assert_err!(
929 verify_execution_receipt::<Test>(domain_id, &previous_head_receipt),
930 Error::StaleReceipt
931 );
932
933 let bundle = create_dummy_bundle_with_receipts(
935 domain_id,
936 operator_id2,
937 H256::random(),
938 previous_head_receipt,
939 );
940 assert!(crate::Pallet::<Test>::submit_bundle(RawOrigin::None.into(), bundle).is_err());
941 });
942 }
943
944 #[test]
945 fn test_new_branch_receipt() {
946 let creator = 0u128;
947 let operator_id1 = 1u64;
948 let operator_id2 = 2u64;
949 let mut ext = new_test_ext_with_extensions();
950 ext.execute_with(|| {
951 let domain_id = register_genesis_domain(creator, vec![operator_id1, operator_id2]);
952 extend_block_tree_from_zero(domain_id, operator_id1, 3);
953
954 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
955 assert!(BlockTree::<Test>::get(domain_id, head_receipt_number).is_some());
956
957 let new_branch_receipt = {
960 let mut head_receipt =
961 get_block_tree_node_at::<Test>(domain_id, head_receipt_number)
962 .unwrap()
963 .execution_receipt;
964 head_receipt.final_state_root = H256::random();
965 head_receipt
966 };
967 let new_branch_receipt_hash = new_branch_receipt.hash::<DomainHashingFor<Test>>();
968
969 assert_eq!(
971 execution_receipt_type::<Test>(domain_id, &new_branch_receipt),
972 ReceiptType::Rejected(RejectedReceiptType::NewBranch)
973 );
974 assert_err!(
975 verify_execution_receipt::<Test>(domain_id, &new_branch_receipt),
976 Error::NewBranchReceipt
977 );
978
979 let bundle = create_dummy_bundle_with_receipts(
981 domain_id,
982 operator_id2,
983 H256::random(),
984 new_branch_receipt,
985 );
986 assert!(crate::Pallet::<Test>::submit_bundle(RawOrigin::None.into(), bundle).is_err());
987 assert!(BlockTreeNodes::<Test>::get(new_branch_receipt_hash).is_none());
988 });
989 }
990
991 #[test]
992 fn test_prune_domain_execution_receipt() {
993 let creator = 0u128;
994 let operator_id = 1u64;
995 let mut ext = new_test_ext_with_extensions();
996 ext.execute_with(|| {
997 let domain_id = register_genesis_domain(creator, vec![operator_id]);
998 let _next_receipt = extend_block_tree_from_zero(domain_id, operator_id, 3);
999 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
1000
1001 assert!(!FrozenDomains::<Test>::get().contains(&domain_id));
1003 Domains::freeze_domain(Origin::<Test>::Root.into(), domain_id).unwrap();
1004 assert!(FrozenDomains::<Test>::get().contains(&domain_id));
1005
1006 let head_receipt_hash = BlockTree::<Test>::get(domain_id, head_receipt_number).unwrap();
1008 Domains::prune_domain_execution_receipt(
1009 Origin::<Test>::Root.into(),
1010 domain_id,
1011 head_receipt_hash,
1012 )
1013 .unwrap();
1014 assert_eq!(
1015 HeadReceiptNumber::<Test>::get(domain_id),
1016 head_receipt_number - 1
1017 );
1018
1019 Domains::unfreeze_domain(Origin::<Test>::Root.into(), domain_id).unwrap();
1021 assert!(!FrozenDomains::<Test>::get().contains(&domain_id));
1022 })
1023 }
1024
1025 #[test]
1026 fn test_invalid_receipt() {
1027 let creator = 0u128;
1028 let operator_id = 1u64;
1029 let mut ext = new_test_ext_with_extensions();
1030 ext.execute_with(|| {
1031 let domain_id = register_genesis_domain(creator, vec![operator_id]);
1032 let next_receipt = extend_block_tree_from_zero(domain_id, operator_id, 3);
1033 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
1034
1035 let mut future_receipt = next_receipt.clone();
1037 future_receipt.domain_block_number = head_receipt_number + 2;
1038 future_receipt.consensus_block_number = head_receipt_number as u32 + 2;
1039 ExecutionInbox::<Test>::insert(
1040 (
1041 domain_id,
1042 future_receipt.domain_block_number,
1043 future_receipt.consensus_block_number,
1044 ),
1045 future_receipt
1046 .inboxed_bundles
1047 .clone()
1048 .into_iter()
1049 .map(|b| BundleDigest {
1050 header_hash: H256::random(),
1051 extrinsics_root: b.extrinsics_root,
1052 size: 0,
1053 })
1054 .collect::<Vec<_>>(),
1055 );
1056 assert_eq!(
1057 execution_receipt_type::<Test>(domain_id, &future_receipt),
1058 ReceiptType::Rejected(RejectedReceiptType::InFuture)
1059 );
1060 assert_err!(
1061 verify_execution_receipt::<Test>(domain_id, &future_receipt),
1062 Error::InFutureReceipt
1063 );
1064
1065 let mut unknown_extrinsics_roots_receipt = next_receipt.clone();
1067 unknown_extrinsics_roots_receipt.inboxed_bundles =
1068 vec![InboxedBundle::valid(H256::random(), H256::random())];
1069 assert_err!(
1070 verify_execution_receipt::<Test>(domain_id, &unknown_extrinsics_roots_receipt),
1071 Error::InvalidExtrinsicsRoots
1072 );
1073
1074 let mut unknown_consensus_block_receipt = next_receipt.clone();
1076 unknown_consensus_block_receipt.consensus_block_hash = H256::random();
1077 assert_err!(
1078 verify_execution_receipt::<Test>(domain_id, &unknown_consensus_block_receipt),
1079 Error::BuiltOnUnknownConsensusBlock
1080 );
1081
1082 let mut unknown_parent_receipt = next_receipt.clone();
1084 unknown_parent_receipt.parent_domain_block_receipt_hash = H256::random();
1085 assert_err!(
1086 verify_execution_receipt::<Test>(domain_id, &unknown_parent_receipt),
1087 Error::UnknownParentBlockReceipt
1088 );
1089
1090 let mut invalid_execution_trace_receipt = next_receipt;
1092
1093 invalid_execution_trace_receipt.execution_trace = vec![
1095 invalid_execution_trace_receipt
1096 .execution_trace
1097 .first()
1098 .cloned()
1099 .expect("First element should be there; qed"),
1100 ];
1101 assert_err!(
1102 verify_execution_receipt::<Test>(domain_id, &invalid_execution_trace_receipt),
1103 Error::InvalidExecutionTrace
1104 );
1105
1106 invalid_execution_trace_receipt.execution_trace = vec![];
1108 assert_err!(
1109 verify_execution_receipt::<Test>(domain_id, &invalid_execution_trace_receipt),
1110 Error::InvalidExecutionTrace
1111 );
1112 });
1113 }
1114
1115 #[test]
1116 fn test_invalid_receipt_with_head_receipt_already_extended() {
1117 let creator = 0u128;
1118 let operator_id = 1u64;
1119 let mut ext = new_test_ext_with_extensions();
1120 ext.execute_with(|| {
1121 let domain_id = register_genesis_domain(creator, vec![operator_id]);
1122 let next_receipt = extend_block_tree_from_zero(domain_id, operator_id, 3);
1123 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
1124
1125 assert!(NewAddedHeadReceipt::<Test>::get(domain_id).is_none());
1127 NewAddedHeadReceipt::<Test>::set(domain_id, Some(H256::random()));
1128
1129 let mut future_receipt = next_receipt.clone();
1131 future_receipt.domain_block_number = head_receipt_number + 1;
1132 future_receipt.consensus_block_number = head_receipt_number as u32 + 1;
1133
1134 ExecutionInbox::<Test>::insert(
1135 (
1136 domain_id,
1137 future_receipt.domain_block_number,
1138 future_receipt.consensus_block_number,
1139 ),
1140 future_receipt
1141 .inboxed_bundles
1142 .clone()
1143 .into_iter()
1144 .map(|b| BundleDigest {
1145 header_hash: H256::random(),
1146 extrinsics_root: b.extrinsics_root,
1147 size: 0,
1148 })
1149 .collect::<Vec<_>>(),
1150 );
1151 assert_eq!(
1152 execution_receipt_type::<Test>(domain_id, &future_receipt),
1153 ReceiptType::Rejected(RejectedReceiptType::InFuture)
1154 );
1155 assert_err!(
1156 verify_execution_receipt::<Test>(domain_id, &future_receipt),
1157 Error::InFutureReceipt
1158 );
1159 });
1160 }
1161
1162 #[test]
1163 fn test_invalid_trace_root_receipt() {
1164 let creator = 0u128;
1165 let operator_id1 = 1u64;
1166 let operator_id2 = 2u64;
1167 let mut ext = new_test_ext_with_extensions();
1168 ext.execute_with(|| {
1169 let domain_id = register_genesis_domain(creator, vec![operator_id1, operator_id2]);
1170 let mut next_receipt = extend_block_tree_from_zero(domain_id, operator_id1, 3);
1171 next_receipt.execution_trace.push(H256::random());
1172 next_receipt.final_state_root = *next_receipt.execution_trace.last().unwrap();
1173
1174 let mut trace = Vec::with_capacity(next_receipt.execution_trace.len());
1175 for root in &next_receipt.execution_trace {
1176 trace.push(
1177 root.encode()
1178 .try_into()
1179 .map_err(|_| Error::InvalidTraceRoot)
1180 .expect("H256 to Blake3Hash should be successful; qed"),
1181 );
1182 }
1183 let new_execution_trace_root = MerkleTree::from_leaves(trace.as_slice())
1184 .root()
1185 .expect("Compute merkle root of trace should success")
1186 .into();
1187 next_receipt.execution_trace_root = new_execution_trace_root;
1188 assert_ok!(verify_execution_receipt::<Test>(domain_id, &next_receipt));
1189
1190 let mut invalid_receipt = next_receipt.clone();
1192 invalid_receipt.execution_trace_root = H256::random();
1193 assert_err!(
1194 verify_execution_receipt::<Test>(domain_id, &invalid_receipt),
1195 Error::InvalidTraceRoot
1196 );
1197
1198 let mut invalid_receipt = next_receipt.clone();
1200 invalid_receipt.execution_trace[0] = H256::random();
1201 assert_err!(
1202 verify_execution_receipt::<Test>(domain_id, &invalid_receipt),
1203 Error::InvalidTraceRoot
1204 );
1205
1206 let mut invalid_receipt = next_receipt.clone();
1208 invalid_receipt.execution_trace.push(H256::random());
1209 assert_err!(
1210 verify_execution_receipt::<Test>(domain_id, &invalid_receipt),
1211 Error::InvalidTraceRoot
1212 );
1213
1214 let mut invalid_receipt = next_receipt;
1216 invalid_receipt.execution_trace.pop();
1217 assert_err!(
1218 verify_execution_receipt::<Test>(domain_id, &invalid_receipt),
1219 Error::InvalidTraceRoot
1220 );
1221 });
1222 }
1223
1224 #[test]
1225 fn test_collect_invalid_bundle_author() {
1226 let creator = 0u128;
1227 let challenge_period = BlockTreePruningDepth::get();
1228 let operator_set: Vec<_> = (1..15).collect();
1229 let mut ext = new_test_ext_with_extensions();
1230 ext.execute_with(|| {
1231 let domain_id = register_genesis_domain(creator, operator_set.clone());
1232 let next_receipt = extend_block_tree_from_zero(domain_id, operator_set[0], 3);
1233
1234 for operator_id in operator_set.iter() {
1236 let bundle = create_dummy_bundle_with_receipts(
1237 domain_id,
1238 *operator_id,
1239 H256::random(),
1240 next_receipt.clone(),
1241 );
1242 assert_ok!(crate::Pallet::<Test>::submit_bundle(
1243 DomainOrigin::ValidatedUnsigned.into(),
1244 bundle,
1245 ));
1246 }
1247 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
1248 let head_node = get_block_tree_node_at::<Test>(domain_id, head_receipt_number).unwrap();
1249 assert_eq!(head_node.operator_ids, operator_set);
1250
1251 let current_block_number = frame_system::Pallet::<Test>::current_block_number();
1253 let execution_inbox = ExecutionInbox::<Test>::get((
1254 domain_id,
1255 current_block_number,
1256 current_block_number,
1257 ));
1258 let bundles_extrinsics_roots: Vec<_> = execution_inbox
1259 .into_iter()
1260 .map(|b| b.extrinsics_root)
1261 .collect();
1262 assert_eq!(bundles_extrinsics_roots.len(), operator_set.len());
1263
1264 let mut bundles = vec![];
1266 let mut invalid_bundle_authors = vec![];
1267 for (i, (operator, extrinsics_root)) in operator_set
1268 .iter()
1269 .zip(bundles_extrinsics_roots)
1270 .enumerate()
1271 {
1272 if i % 2 == 0 {
1273 invalid_bundle_authors.push(*operator);
1274 bundles.push(InboxedBundle::invalid(
1275 InvalidBundleType::OutOfRangeTx(0),
1276 extrinsics_root,
1277 ));
1278 } else {
1279 bundles.push(InboxedBundle::valid(H256::random(), extrinsics_root));
1280 }
1281 }
1282 let mut target_receipt = create_dummy_receipt(
1283 current_block_number,
1284 H256::random(),
1285 next_receipt.hash::<DomainHashingFor<Test>>(),
1286 vec![],
1287 );
1288 target_receipt.inboxed_bundles = bundles;
1289
1290 let next_receipt = extend_block_tree(
1292 domain_id,
1293 operator_set[0],
1294 current_block_number + challenge_period + 1u32,
1295 target_receipt,
1296 );
1297 let confirmed_domain_block = process_execution_receipt::<Test>(
1299 domain_id,
1300 operator_set[0],
1301 next_receipt,
1302 AcceptedReceiptType::NewHead,
1303 )
1304 .unwrap()
1305 .unwrap();
1306
1307 assert_eq!(
1309 confirmed_domain_block.invalid_bundle_authors,
1310 invalid_bundle_authors
1311 );
1312 });
1313 }
1314}