1#[cfg(not(feature = "std"))]
4extern crate alloc;
5
6use crate::{
7 BalanceOf, BlockTree, BlockTreeNodeFor, BlockTreeNodes, Config, ConsensusBlockHash,
8 DomainBlockNumberFor, DomainHashingFor, DomainRuntimeUpgradeRecords, ExecutionInbox,
9 ExecutionReceiptOf, HeadDomainNumber, HeadReceiptNumber, InboxedBundleAuthor,
10 LatestConfirmedDomainExecutionReceipt, LatestSubmittedER, NewAddedHeadReceipt, Pallet,
11 ReceiptHashFor, SkipBalanceChecks,
12};
13#[cfg(not(feature = "std"))]
14use alloc::vec::Vec;
15use frame_support::{ensure, PalletError};
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
587 let block_tree_node = BlockTreeNode {
588 execution_receipt: genesis_receipt,
589 operator_ids: sp_std::vec![],
590 };
591 BlockTree::<T>::insert(domain_id, domain_block_number, er_hash);
593 BlockTreeNodes::<T>::insert(er_hash, block_tree_node);
594}
595
596pub(crate) fn prune_receipt<T: Config>(
597 domain_id: DomainId,
598 receipt_number: DomainBlockNumberFor<T>,
599) -> Result<Option<BlockTreeNodeFor<T>>, Error> {
600 let receipt_hash = match BlockTree::<T>::take(domain_id, receipt_number) {
601 Some(er_hash) => er_hash,
602 None => return Ok(None),
603 };
604 let block_tree_node =
605 BlockTreeNodes::<T>::take(receipt_hash).ok_or(Error::MissingDomainBlock)?;
606
607 for operator_id in block_tree_node.operator_ids.iter() {
617 let key = (domain_id, operator_id);
618 let latest_submitted_er = Pallet::<T>::latest_submitted_er(key);
619 if block_tree_node.execution_receipt.domain_block_number == latest_submitted_er {
620 LatestSubmittedER::<T>::remove(key);
621 }
622 }
623
624 Ok(Some(block_tree_node))
625}
626
627#[cfg(test)]
628mod tests {
629 use super::*;
630 use crate::tests::{
631 create_dummy_bundle_with_receipts, create_dummy_receipt, extend_block_tree,
632 extend_block_tree_from_zero, get_block_tree_node_at, new_test_ext_with_extensions,
633 register_genesis_domain, run_to_block, BlockTreePruningDepth, Domains, Test,
634 };
635 use crate::{FrozenDomains, RawOrigin as DomainOrigin};
636 use frame_support::dispatch::RawOrigin;
637 use frame_support::{assert_err, assert_ok};
638 use frame_system::Origin;
639 use sp_core::H256;
640 use sp_domains::{BundleDigest, InboxedBundle, InvalidBundleType};
641
642 #[test]
643 fn test_genesis_receipt() {
644 let mut ext = new_test_ext_with_extensions();
645 ext.execute_with(|| {
646 let domain_id = register_genesis_domain(0u128, vec![0u64]);
647
648 let block_tree_node_at_0 = BlockTree::<Test>::get(domain_id, 0).unwrap();
650
651 let genesis_node = BlockTreeNodes::<Test>::get(block_tree_node_at_0).unwrap();
652 assert!(genesis_node.operator_ids.is_empty());
653 assert_eq!(HeadReceiptNumber::<Test>::get(domain_id), 0);
654
655 let genesis_receipt = genesis_node.execution_receipt;
657 let invalid_genesis_receipt = {
658 let mut receipt = genesis_receipt.clone();
659 receipt.final_state_root = H256::random();
660 receipt
661 };
662 assert_ok!(verify_execution_receipt::<Test>(
663 domain_id,
664 &genesis_receipt
665 ));
666 assert_err!(
669 verify_execution_receipt::<Test>(domain_id, &invalid_genesis_receipt),
670 Error::NewBranchReceipt
671 );
672 });
673 }
674
675 #[test]
676 fn test_new_head_receipt() {
677 let creator = 0u128;
678 let operator_id = 1u64;
679 let block_tree_pruning_depth = <Test as Config>::BlockTreePruningDepth::get();
680
681 let mut ext = new_test_ext_with_extensions();
682 ext.execute_with(|| {
683 let domain_id = register_genesis_domain(creator, vec![operator_id]);
684
685 let genesis_node = get_block_tree_node_at::<Test>(domain_id, 0).unwrap();
687 let mut receipt = genesis_node.execution_receipt;
688 assert_eq!(
689 receipt.consensus_block_number,
690 frame_system::Pallet::<Test>::current_block_number()
691 );
692 let mut receipt_of_block_1 = None;
693 let mut bundle_header_hash_of_block_1 = None;
694 for block_number in 1..=(block_tree_pruning_depth as u64 + 3) {
695 run_to_block::<Test>(block_number, receipt.consensus_block_hash);
697
698 if block_number != 1 {
699 assert_eq!(
701 ConsensusBlockHash::<Test>::get(domain_id, block_number - 1),
702 Some(frame_system::Pallet::<Test>::block_hash(block_number - 1))
703 );
704 assert_eq!(
706 execution_receipt_type::<Test>(domain_id, &receipt),
707 ReceiptType::Accepted(AcceptedReceiptType::NewHead)
708 );
709 assert_ok!(verify_execution_receipt::<Test>(domain_id, &receipt));
710 }
711
712 let bundle_extrinsics_root = H256::random();
714 let bundle = create_dummy_bundle_with_receipts(
715 domain_id,
716 operator_id,
717 bundle_extrinsics_root,
718 receipt,
719 );
720 let bundle_header_hash = bundle.sealed_header.pre_hash();
721 let bundle_size = bundle.size();
722 assert_ok!(crate::Pallet::<Test>::submit_bundle(
723 DomainOrigin::ValidatedUnsigned.into(),
724 bundle,
725 ));
726 assert_eq!(
728 ExecutionInbox::<Test>::get((domain_id, block_number as u32, block_number)),
729 vec![BundleDigest {
730 header_hash: bundle_header_hash,
731 extrinsics_root: bundle_extrinsics_root,
732 size: bundle_size,
733 }]
734 );
735 assert!(InboxedBundleAuthor::<Test>::contains_key(
736 bundle_header_hash
737 ));
738
739 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
741 assert_eq!(head_receipt_number, block_number as u32 - 1);
742
743 let parent_domain_block_receipt =
745 BlockTree::<Test>::get(domain_id, head_receipt_number).unwrap();
746
747 let parent_node = BlockTreeNodes::<Test>::get(parent_domain_block_receipt).unwrap();
749 assert_eq!(parent_node.operator_ids.len(), 1);
750 assert_eq!(parent_node.operator_ids[0], operator_id);
751
752 receipt = create_dummy_receipt(
755 block_number,
756 H256::random(),
757 parent_domain_block_receipt,
758 vec![bundle_extrinsics_root],
759 );
760
761 if block_number == 1 {
763 receipt_of_block_1.replace(receipt.clone());
764 bundle_header_hash_of_block_1.replace(bundle_header_hash);
765 }
766 }
767
768 let pruned_receipt = receipt_of_block_1.unwrap();
771 let pruned_bundle = bundle_header_hash_of_block_1.unwrap();
772 assert!(BlockTree::<Test>::get(domain_id, 1).is_none());
773 assert!(ExecutionInbox::<Test>::get((domain_id, 1, 1)).is_empty());
774 assert!(!InboxedBundleAuthor::<Test>::contains_key(pruned_bundle));
775 assert_eq!(
776 execution_receipt_type::<Test>(domain_id, &pruned_receipt),
777 ReceiptType::Rejected(RejectedReceiptType::Pruned)
778 );
779 assert_err!(
780 verify_execution_receipt::<Test>(domain_id, &pruned_receipt),
781 Error::PrunedReceipt
782 );
783 assert!(ConsensusBlockHash::<Test>::get(
784 domain_id,
785 pruned_receipt.consensus_block_number,
786 )
787 .is_none());
788 });
789 }
790
791 #[test]
792 fn test_confirm_current_head_receipt() {
793 let creator = 0u128;
794 let operator_id1 = 1u64;
795 let operator_id2 = 2u64;
796 let mut ext = new_test_ext_with_extensions();
797 ext.execute_with(|| {
798 let domain_id = register_genesis_domain(creator, vec![operator_id1, operator_id2]);
799 let next_head_receipt = extend_block_tree_from_zero(domain_id, operator_id1, 3);
800
801 assert_eq!(
803 execution_receipt_type::<Test>(domain_id, &next_head_receipt),
804 ReceiptType::Accepted(AcceptedReceiptType::NewHead)
805 );
806 assert_ok!(verify_execution_receipt::<Test>(
807 domain_id,
808 &next_head_receipt
809 ));
810 let bundle = create_dummy_bundle_with_receipts(
811 domain_id,
812 operator_id1,
813 H256::random(),
814 next_head_receipt.clone(),
815 );
816 assert_ok!(crate::Pallet::<Test>::submit_bundle(
817 DomainOrigin::ValidatedUnsigned.into(),
818 bundle,
819 ));
820
821 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
822 let current_head_receipt =
823 get_block_tree_node_at::<Test>(domain_id, head_receipt_number)
824 .unwrap()
825 .execution_receipt;
826
827 assert_eq!(next_head_receipt, current_head_receipt);
829
830 assert_eq!(
832 execution_receipt_type::<Test>(domain_id, ¤t_head_receipt),
833 ReceiptType::Accepted(AcceptedReceiptType::CurrentHead)
834 );
835 assert_ok!(verify_execution_receipt::<Test>(
836 domain_id,
837 ¤t_head_receipt
838 ));
839
840 let bundle = create_dummy_bundle_with_receipts(
842 domain_id,
843 operator_id2,
844 H256::random(),
845 current_head_receipt,
846 );
847 assert_ok!(crate::Pallet::<Test>::submit_bundle(
848 DomainOrigin::ValidatedUnsigned.into(),
849 bundle,
850 ));
851
852 let head_node = get_block_tree_node_at::<Test>(domain_id, head_receipt_number).unwrap();
853 assert_eq!(head_node.operator_ids, vec![operator_id1, operator_id2]);
854 });
855 }
856
857 #[test]
858 fn test_non_head_receipt() {
859 let creator = 0u128;
860 let operator_id1 = 1u64;
861 let operator_id2 = 2u64;
862 let mut ext = new_test_ext_with_extensions();
863 ext.execute_with(|| {
864 let domain_id = register_genesis_domain(creator, vec![operator_id1, operator_id2]);
865 extend_block_tree_from_zero(domain_id, operator_id1, 3);
866
867 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
869 let stale_receipt = get_block_tree_node_at::<Test>(domain_id, head_receipt_number - 1)
870 .unwrap()
871 .execution_receipt;
872 let stale_receipt_hash = stale_receipt.hash::<DomainHashingFor<Test>>();
873
874 assert_eq!(
876 execution_receipt_type::<Test>(domain_id, &stale_receipt),
877 ReceiptType::Rejected(RejectedReceiptType::Stale)
878 );
879 assert_err!(
880 verify_execution_receipt::<Test>(domain_id, &stale_receipt),
881 Error::StaleReceipt
882 );
883
884 let bundle = create_dummy_bundle_with_receipts(
886 domain_id,
887 operator_id2,
888 H256::random(),
889 stale_receipt,
890 );
891 assert!(crate::Pallet::<Test>::submit_bundle(RawOrigin::None.into(), bundle).is_err());
892
893 assert_eq!(
894 BlockTreeNodes::<Test>::get(stale_receipt_hash)
895 .unwrap()
896 .operator_ids,
897 vec![operator_id1]
898 );
899 });
900 }
901
902 #[test]
903 fn test_previous_head_receipt() {
904 let creator = 0u128;
905 let operator_id1 = 1u64;
906 let operator_id2 = 2u64;
907 let mut ext = new_test_ext_with_extensions();
908 ext.execute_with(|| {
909 let domain_id = register_genesis_domain(creator, vec![operator_id1, operator_id2]);
910 extend_block_tree_from_zero(domain_id, operator_id1, 3);
911
912 assert!(NewAddedHeadReceipt::<Test>::get(domain_id).is_none());
914
915 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
917 let previous_head_receipt =
918 get_block_tree_node_at::<Test>(domain_id, head_receipt_number)
919 .unwrap()
920 .execution_receipt;
921
922 assert_eq!(
924 execution_receipt_type::<Test>(domain_id, &previous_head_receipt),
925 ReceiptType::Rejected(RejectedReceiptType::Stale)
926 );
927 assert_err!(
928 verify_execution_receipt::<Test>(domain_id, &previous_head_receipt),
929 Error::StaleReceipt
930 );
931
932 let bundle = create_dummy_bundle_with_receipts(
934 domain_id,
935 operator_id2,
936 H256::random(),
937 previous_head_receipt,
938 );
939 assert!(crate::Pallet::<Test>::submit_bundle(RawOrigin::None.into(), bundle).is_err());
940 });
941 }
942
943 #[test]
944 fn test_new_branch_receipt() {
945 let creator = 0u128;
946 let operator_id1 = 1u64;
947 let operator_id2 = 2u64;
948 let mut ext = new_test_ext_with_extensions();
949 ext.execute_with(|| {
950 let domain_id = register_genesis_domain(creator, vec![operator_id1, operator_id2]);
951 extend_block_tree_from_zero(domain_id, operator_id1, 3);
952
953 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
954 assert!(BlockTree::<Test>::get(domain_id, head_receipt_number).is_some());
955
956 let new_branch_receipt = {
959 let mut head_receipt =
960 get_block_tree_node_at::<Test>(domain_id, head_receipt_number)
961 .unwrap()
962 .execution_receipt;
963 head_receipt.final_state_root = H256::random();
964 head_receipt
965 };
966 let new_branch_receipt_hash = new_branch_receipt.hash::<DomainHashingFor<Test>>();
967
968 assert_eq!(
970 execution_receipt_type::<Test>(domain_id, &new_branch_receipt),
971 ReceiptType::Rejected(RejectedReceiptType::NewBranch)
972 );
973 assert_err!(
974 verify_execution_receipt::<Test>(domain_id, &new_branch_receipt),
975 Error::NewBranchReceipt
976 );
977
978 let bundle = create_dummy_bundle_with_receipts(
980 domain_id,
981 operator_id2,
982 H256::random(),
983 new_branch_receipt,
984 );
985 assert!(crate::Pallet::<Test>::submit_bundle(RawOrigin::None.into(), bundle).is_err());
986 assert!(BlockTreeNodes::<Test>::get(new_branch_receipt_hash).is_none());
987 });
988 }
989
990 #[test]
991 fn test_prune_domain_execution_receipt() {
992 let creator = 0u128;
993 let operator_id = 1u64;
994 let mut ext = new_test_ext_with_extensions();
995 ext.execute_with(|| {
996 let domain_id = register_genesis_domain(creator, vec![operator_id]);
997 let _next_receipt = extend_block_tree_from_zero(domain_id, operator_id, 3);
998 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
999
1000 assert!(!FrozenDomains::<Test>::get().contains(&domain_id));
1002 Domains::freeze_domain(Origin::<Test>::Root.into(), domain_id).unwrap();
1003 assert!(FrozenDomains::<Test>::get().contains(&domain_id));
1004
1005 let head_receipt_hash = BlockTree::<Test>::get(domain_id, head_receipt_number).unwrap();
1007 Domains::prune_domain_execution_receipt(
1008 Origin::<Test>::Root.into(),
1009 domain_id,
1010 head_receipt_hash,
1011 )
1012 .unwrap();
1013 assert_eq!(
1014 HeadReceiptNumber::<Test>::get(domain_id),
1015 head_receipt_number - 1
1016 );
1017
1018 Domains::unfreeze_domain(Origin::<Test>::Root.into(), domain_id).unwrap();
1020 assert!(!FrozenDomains::<Test>::get().contains(&domain_id));
1021 })
1022 }
1023
1024 #[test]
1025 fn test_invalid_receipt() {
1026 let creator = 0u128;
1027 let operator_id = 1u64;
1028 let mut ext = new_test_ext_with_extensions();
1029 ext.execute_with(|| {
1030 let domain_id = register_genesis_domain(creator, vec![operator_id]);
1031 let next_receipt = extend_block_tree_from_zero(domain_id, operator_id, 3);
1032 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
1033
1034 let mut future_receipt = next_receipt.clone();
1036 future_receipt.domain_block_number = head_receipt_number + 2;
1037 future_receipt.consensus_block_number = head_receipt_number as u64 + 2;
1038 ExecutionInbox::<Test>::insert(
1039 (
1040 domain_id,
1041 future_receipt.domain_block_number,
1042 future_receipt.consensus_block_number,
1043 ),
1044 future_receipt
1045 .inboxed_bundles
1046 .clone()
1047 .into_iter()
1048 .map(|b| BundleDigest {
1049 header_hash: H256::random(),
1050 extrinsics_root: b.extrinsics_root,
1051 size: 0,
1052 })
1053 .collect::<Vec<_>>(),
1054 );
1055 assert_eq!(
1056 execution_receipt_type::<Test>(domain_id, &future_receipt),
1057 ReceiptType::Rejected(RejectedReceiptType::InFuture)
1058 );
1059 assert_err!(
1060 verify_execution_receipt::<Test>(domain_id, &future_receipt),
1061 Error::InFutureReceipt
1062 );
1063
1064 let mut unknown_extrinsics_roots_receipt = next_receipt.clone();
1066 unknown_extrinsics_roots_receipt.inboxed_bundles =
1067 vec![InboxedBundle::valid(H256::random(), H256::random())];
1068 assert_err!(
1069 verify_execution_receipt::<Test>(domain_id, &unknown_extrinsics_roots_receipt),
1070 Error::InvalidExtrinsicsRoots
1071 );
1072
1073 let mut unknown_consensus_block_receipt = next_receipt.clone();
1075 unknown_consensus_block_receipt.consensus_block_hash = H256::random();
1076 assert_err!(
1077 verify_execution_receipt::<Test>(domain_id, &unknown_consensus_block_receipt),
1078 Error::BuiltOnUnknownConsensusBlock
1079 );
1080
1081 let mut unknown_parent_receipt = next_receipt.clone();
1083 unknown_parent_receipt.parent_domain_block_receipt_hash = H256::random();
1084 assert_err!(
1085 verify_execution_receipt::<Test>(domain_id, &unknown_parent_receipt),
1086 Error::UnknownParentBlockReceipt
1087 );
1088
1089 let mut invalid_execution_trace_receipt = next_receipt;
1091
1092 invalid_execution_trace_receipt.execution_trace = vec![invalid_execution_trace_receipt
1094 .execution_trace
1095 .first()
1096 .cloned()
1097 .expect("First element should be there; qed")];
1098 assert_err!(
1099 verify_execution_receipt::<Test>(domain_id, &invalid_execution_trace_receipt),
1100 Error::InvalidExecutionTrace
1101 );
1102
1103 invalid_execution_trace_receipt.execution_trace = vec![];
1105 assert_err!(
1106 verify_execution_receipt::<Test>(domain_id, &invalid_execution_trace_receipt),
1107 Error::InvalidExecutionTrace
1108 );
1109 });
1110 }
1111
1112 #[test]
1113 fn test_invalid_receipt_with_head_receipt_already_extended() {
1114 let creator = 0u128;
1115 let operator_id = 1u64;
1116 let mut ext = new_test_ext_with_extensions();
1117 ext.execute_with(|| {
1118 let domain_id = register_genesis_domain(creator, vec![operator_id]);
1119 let next_receipt = extend_block_tree_from_zero(domain_id, operator_id, 3);
1120 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
1121
1122 assert!(NewAddedHeadReceipt::<Test>::get(domain_id).is_none());
1124 NewAddedHeadReceipt::<Test>::set(domain_id, Some(H256::random()));
1125
1126 let mut future_receipt = next_receipt.clone();
1128 future_receipt.domain_block_number = head_receipt_number + 1;
1129 future_receipt.consensus_block_number = head_receipt_number as u64 + 1;
1130
1131 ExecutionInbox::<Test>::insert(
1132 (
1133 domain_id,
1134 future_receipt.domain_block_number,
1135 future_receipt.consensus_block_number,
1136 ),
1137 future_receipt
1138 .inboxed_bundles
1139 .clone()
1140 .into_iter()
1141 .map(|b| BundleDigest {
1142 header_hash: H256::random(),
1143 extrinsics_root: b.extrinsics_root,
1144 size: 0,
1145 })
1146 .collect::<Vec<_>>(),
1147 );
1148 assert_eq!(
1149 execution_receipt_type::<Test>(domain_id, &future_receipt),
1150 ReceiptType::Rejected(RejectedReceiptType::InFuture)
1151 );
1152 assert_err!(
1153 verify_execution_receipt::<Test>(domain_id, &future_receipt),
1154 Error::InFutureReceipt
1155 );
1156 });
1157 }
1158
1159 #[test]
1160 fn test_invalid_trace_root_receipt() {
1161 let creator = 0u128;
1162 let operator_id1 = 1u64;
1163 let operator_id2 = 2u64;
1164 let mut ext = new_test_ext_with_extensions();
1165 ext.execute_with(|| {
1166 let domain_id = register_genesis_domain(creator, vec![operator_id1, operator_id2]);
1167 let mut next_receipt = extend_block_tree_from_zero(domain_id, operator_id1, 3);
1168 next_receipt.execution_trace.push(H256::random());
1169 next_receipt.final_state_root = *next_receipt.execution_trace.last().unwrap();
1170
1171 let mut trace = Vec::with_capacity(next_receipt.execution_trace.len());
1172 for root in &next_receipt.execution_trace {
1173 trace.push(
1174 root.encode()
1175 .try_into()
1176 .map_err(|_| Error::InvalidTraceRoot)
1177 .expect("H256 to Blake3Hash should be successful; qed"),
1178 );
1179 }
1180 let new_execution_trace_root = MerkleTree::from_leaves(trace.as_slice())
1181 .root()
1182 .expect("Compute merkle root of trace should success")
1183 .into();
1184 next_receipt.execution_trace_root = new_execution_trace_root;
1185 assert_ok!(verify_execution_receipt::<Test>(domain_id, &next_receipt));
1186
1187 let mut invalid_receipt = next_receipt.clone();
1189 invalid_receipt.execution_trace_root = H256::random();
1190 assert_err!(
1191 verify_execution_receipt::<Test>(domain_id, &invalid_receipt),
1192 Error::InvalidTraceRoot
1193 );
1194
1195 let mut invalid_receipt = next_receipt.clone();
1197 invalid_receipt.execution_trace[0] = H256::random();
1198 assert_err!(
1199 verify_execution_receipt::<Test>(domain_id, &invalid_receipt),
1200 Error::InvalidTraceRoot
1201 );
1202
1203 let mut invalid_receipt = next_receipt.clone();
1205 invalid_receipt.execution_trace.push(H256::random());
1206 assert_err!(
1207 verify_execution_receipt::<Test>(domain_id, &invalid_receipt),
1208 Error::InvalidTraceRoot
1209 );
1210
1211 let mut invalid_receipt = next_receipt;
1213 invalid_receipt.execution_trace.pop();
1214 assert_err!(
1215 verify_execution_receipt::<Test>(domain_id, &invalid_receipt),
1216 Error::InvalidTraceRoot
1217 );
1218 });
1219 }
1220
1221 #[test]
1222 fn test_collect_invalid_bundle_author() {
1223 let creator = 0u128;
1224 let challenge_period = BlockTreePruningDepth::get() as u64;
1225 let operator_set: Vec<_> = (1..15).collect();
1226 let mut ext = new_test_ext_with_extensions();
1227 ext.execute_with(|| {
1228 let domain_id = register_genesis_domain(creator, operator_set.clone());
1229 let next_receipt = extend_block_tree_from_zero(domain_id, operator_set[0], 3);
1230
1231 for operator_id in operator_set.iter() {
1233 let bundle = create_dummy_bundle_with_receipts(
1234 domain_id,
1235 *operator_id,
1236 H256::random(),
1237 next_receipt.clone(),
1238 );
1239 assert_ok!(crate::Pallet::<Test>::submit_bundle(
1240 DomainOrigin::ValidatedUnsigned.into(),
1241 bundle,
1242 ));
1243 }
1244 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
1245 let head_node = get_block_tree_node_at::<Test>(domain_id, head_receipt_number).unwrap();
1246 assert_eq!(head_node.operator_ids, operator_set);
1247
1248 let current_block_number = frame_system::Pallet::<Test>::current_block_number();
1250 let execution_inbox = ExecutionInbox::<Test>::get((
1251 domain_id,
1252 current_block_number as u32,
1253 current_block_number,
1254 ));
1255 let bundles_extrinsics_roots: Vec<_> = execution_inbox
1256 .into_iter()
1257 .map(|b| b.extrinsics_root)
1258 .collect();
1259 assert_eq!(bundles_extrinsics_roots.len(), operator_set.len());
1260
1261 let mut bundles = vec![];
1263 let mut invalid_bundle_authors = vec![];
1264 for (i, (operator, extrinsics_root)) in operator_set
1265 .iter()
1266 .zip(bundles_extrinsics_roots)
1267 .enumerate()
1268 {
1269 if i % 2 == 0 {
1270 invalid_bundle_authors.push(*operator);
1271 bundles.push(InboxedBundle::invalid(
1272 InvalidBundleType::OutOfRangeTx(0),
1273 extrinsics_root,
1274 ));
1275 } else {
1276 bundles.push(InboxedBundle::valid(H256::random(), extrinsics_root));
1277 }
1278 }
1279 let mut target_receipt = create_dummy_receipt(
1280 current_block_number,
1281 H256::random(),
1282 next_receipt.hash::<DomainHashingFor<Test>>(),
1283 vec![],
1284 );
1285 target_receipt.inboxed_bundles = bundles;
1286
1287 let next_receipt = extend_block_tree(
1289 domain_id,
1290 operator_set[0],
1291 (current_block_number + challenge_period) as u32 + 1,
1292 target_receipt,
1293 );
1294 let confirmed_domain_block = process_execution_receipt::<Test>(
1296 domain_id,
1297 operator_set[0],
1298 next_receipt,
1299 AcceptedReceiptType::NewHead,
1300 )
1301 .unwrap()
1302 .unwrap();
1303
1304 assert_eq!(
1306 confirmed_domain_block.invalid_bundle_authors,
1307 invalid_bundle_authors
1308 );
1309 });
1310 }
1311}