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::{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 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 create_dummy_bundle_with_receipts, create_dummy_receipt, extend_block_tree,
633 extend_block_tree_from_zero, get_block_tree_node_at, new_test_ext_with_extensions,
634 register_genesis_domain, run_to_block, BlockTreePruningDepth, Domains, Test,
635 };
636 use crate::{FrozenDomains, RawOrigin as DomainOrigin};
637 use frame_support::dispatch::RawOrigin;
638 use frame_support::{assert_err, assert_ok};
639 use frame_system::Origin;
640 use sp_core::H256;
641 use sp_domains::{BundleDigest, InboxedBundle, InvalidBundleType};
642
643 #[test]
644 fn test_genesis_receipt() {
645 let mut ext = new_test_ext_with_extensions();
646 ext.execute_with(|| {
647 let domain_id = register_genesis_domain(0u128, vec![0u64]);
648
649 let block_tree_node_at_0 = BlockTree::<Test>::get(domain_id, 0).unwrap();
651
652 let genesis_node = BlockTreeNodes::<Test>::get(block_tree_node_at_0).unwrap();
653 assert!(genesis_node.operator_ids.is_empty());
654 assert_eq!(HeadReceiptNumber::<Test>::get(domain_id), 0);
655
656 let genesis_receipt = genesis_node.execution_receipt;
658 let invalid_genesis_receipt = {
659 let mut receipt = genesis_receipt.clone();
660 receipt.final_state_root = H256::random();
661 receipt
662 };
663 assert_ok!(verify_execution_receipt::<Test>(
664 domain_id,
665 &genesis_receipt
666 ));
667 assert_err!(
670 verify_execution_receipt::<Test>(domain_id, &invalid_genesis_receipt),
671 Error::NewBranchReceipt
672 );
673 });
674 }
675
676 #[test]
677 fn test_new_head_receipt() {
678 let creator = 0u128;
679 let operator_id = 1u64;
680 let block_tree_pruning_depth = <Test as Config>::BlockTreePruningDepth::get();
681
682 let mut ext = new_test_ext_with_extensions();
683 ext.execute_with(|| {
684 let domain_id = register_genesis_domain(creator, vec![operator_id]);
685
686 let genesis_node = get_block_tree_node_at::<Test>(domain_id, 0).unwrap();
688 let mut receipt = genesis_node.execution_receipt;
689 assert_eq!(
690 receipt.consensus_block_number,
691 frame_system::Pallet::<Test>::current_block_number()
692 );
693 let mut receipt_of_block_1 = None;
694 let mut bundle_header_hash_of_block_1 = None;
695 for block_number in 1..=(block_tree_pruning_depth as u64 + 3) {
696 run_to_block::<Test>(block_number, receipt.consensus_block_hash);
698
699 if block_number != 1 {
700 assert_eq!(
702 ConsensusBlockHash::<Test>::get(domain_id, block_number - 1),
703 Some(frame_system::Pallet::<Test>::block_hash(block_number - 1))
704 );
705 assert_eq!(
707 execution_receipt_type::<Test>(domain_id, &receipt),
708 ReceiptType::Accepted(AcceptedReceiptType::NewHead)
709 );
710 assert_ok!(verify_execution_receipt::<Test>(domain_id, &receipt));
711 }
712
713 let bundle_extrinsics_root = H256::random();
715 let bundle = create_dummy_bundle_with_receipts(
716 domain_id,
717 operator_id,
718 bundle_extrinsics_root,
719 receipt,
720 );
721 let bundle_header_hash = bundle.sealed_header.pre_hash();
722 let bundle_size = bundle.size();
723 assert_ok!(crate::Pallet::<Test>::submit_bundle(
724 DomainOrigin::ValidatedUnsigned.into(),
725 bundle,
726 ));
727 assert_eq!(
729 ExecutionInbox::<Test>::get((domain_id, block_number as u32, block_number)),
730 vec![BundleDigest {
731 header_hash: bundle_header_hash,
732 extrinsics_root: bundle_extrinsics_root,
733 size: bundle_size,
734 }]
735 );
736 assert!(InboxedBundleAuthor::<Test>::contains_key(
737 bundle_header_hash
738 ));
739
740 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
742 assert_eq!(head_receipt_number, block_number as u32 - 1);
743
744 let parent_domain_block_receipt =
746 BlockTree::<Test>::get(domain_id, head_receipt_number).unwrap();
747
748 let parent_node = BlockTreeNodes::<Test>::get(parent_domain_block_receipt).unwrap();
750 assert_eq!(parent_node.operator_ids.len(), 1);
751 assert_eq!(parent_node.operator_ids[0], operator_id);
752
753 receipt = create_dummy_receipt(
756 block_number,
757 H256::random(),
758 parent_domain_block_receipt,
759 vec![bundle_extrinsics_root],
760 );
761
762 if block_number == 1 {
764 receipt_of_block_1.replace(receipt.clone());
765 bundle_header_hash_of_block_1.replace(bundle_header_hash);
766 }
767 }
768
769 let pruned_receipt = receipt_of_block_1.unwrap();
772 let pruned_bundle = bundle_header_hash_of_block_1.unwrap();
773 assert!(BlockTree::<Test>::get(domain_id, 1).is_none());
774 assert!(ExecutionInbox::<Test>::get((domain_id, 1, 1)).is_empty());
775 assert!(!InboxedBundleAuthor::<Test>::contains_key(pruned_bundle));
776 assert_eq!(
777 execution_receipt_type::<Test>(domain_id, &pruned_receipt),
778 ReceiptType::Rejected(RejectedReceiptType::Pruned)
779 );
780 assert_err!(
781 verify_execution_receipt::<Test>(domain_id, &pruned_receipt),
782 Error::PrunedReceipt
783 );
784 assert!(ConsensusBlockHash::<Test>::get(
785 domain_id,
786 pruned_receipt.consensus_block_number,
787 )
788 .is_none());
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 u64 + 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![invalid_execution_trace_receipt
1095 .execution_trace
1096 .first()
1097 .cloned()
1098 .expect("First element should be there; qed")];
1099 assert_err!(
1100 verify_execution_receipt::<Test>(domain_id, &invalid_execution_trace_receipt),
1101 Error::InvalidExecutionTrace
1102 );
1103
1104 invalid_execution_trace_receipt.execution_trace = vec![];
1106 assert_err!(
1107 verify_execution_receipt::<Test>(domain_id, &invalid_execution_trace_receipt),
1108 Error::InvalidExecutionTrace
1109 );
1110 });
1111 }
1112
1113 #[test]
1114 fn test_invalid_receipt_with_head_receipt_already_extended() {
1115 let creator = 0u128;
1116 let operator_id = 1u64;
1117 let mut ext = new_test_ext_with_extensions();
1118 ext.execute_with(|| {
1119 let domain_id = register_genesis_domain(creator, vec![operator_id]);
1120 let next_receipt = extend_block_tree_from_zero(domain_id, operator_id, 3);
1121 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
1122
1123 assert!(NewAddedHeadReceipt::<Test>::get(domain_id).is_none());
1125 NewAddedHeadReceipt::<Test>::set(domain_id, Some(H256::random()));
1126
1127 let mut future_receipt = next_receipt.clone();
1129 future_receipt.domain_block_number = head_receipt_number + 1;
1130 future_receipt.consensus_block_number = head_receipt_number as u64 + 1;
1131
1132 ExecutionInbox::<Test>::insert(
1133 (
1134 domain_id,
1135 future_receipt.domain_block_number,
1136 future_receipt.consensus_block_number,
1137 ),
1138 future_receipt
1139 .inboxed_bundles
1140 .clone()
1141 .into_iter()
1142 .map(|b| BundleDigest {
1143 header_hash: H256::random(),
1144 extrinsics_root: b.extrinsics_root,
1145 size: 0,
1146 })
1147 .collect::<Vec<_>>(),
1148 );
1149 assert_eq!(
1150 execution_receipt_type::<Test>(domain_id, &future_receipt),
1151 ReceiptType::Rejected(RejectedReceiptType::InFuture)
1152 );
1153 assert_err!(
1154 verify_execution_receipt::<Test>(domain_id, &future_receipt),
1155 Error::InFutureReceipt
1156 );
1157 });
1158 }
1159
1160 #[test]
1161 fn test_invalid_trace_root_receipt() {
1162 let creator = 0u128;
1163 let operator_id1 = 1u64;
1164 let operator_id2 = 2u64;
1165 let mut ext = new_test_ext_with_extensions();
1166 ext.execute_with(|| {
1167 let domain_id = register_genesis_domain(creator, vec![operator_id1, operator_id2]);
1168 let mut next_receipt = extend_block_tree_from_zero(domain_id, operator_id1, 3);
1169 next_receipt.execution_trace.push(H256::random());
1170 next_receipt.final_state_root = *next_receipt.execution_trace.last().unwrap();
1171
1172 let mut trace = Vec::with_capacity(next_receipt.execution_trace.len());
1173 for root in &next_receipt.execution_trace {
1174 trace.push(
1175 root.encode()
1176 .try_into()
1177 .map_err(|_| Error::InvalidTraceRoot)
1178 .expect("H256 to Blake3Hash should be successful; qed"),
1179 );
1180 }
1181 let new_execution_trace_root = MerkleTree::from_leaves(trace.as_slice())
1182 .root()
1183 .expect("Compute merkle root of trace should success")
1184 .into();
1185 next_receipt.execution_trace_root = new_execution_trace_root;
1186 assert_ok!(verify_execution_receipt::<Test>(domain_id, &next_receipt));
1187
1188 let mut invalid_receipt = next_receipt.clone();
1190 invalid_receipt.execution_trace_root = H256::random();
1191 assert_err!(
1192 verify_execution_receipt::<Test>(domain_id, &invalid_receipt),
1193 Error::InvalidTraceRoot
1194 );
1195
1196 let mut invalid_receipt = next_receipt.clone();
1198 invalid_receipt.execution_trace[0] = H256::random();
1199 assert_err!(
1200 verify_execution_receipt::<Test>(domain_id, &invalid_receipt),
1201 Error::InvalidTraceRoot
1202 );
1203
1204 let mut invalid_receipt = next_receipt.clone();
1206 invalid_receipt.execution_trace.push(H256::random());
1207 assert_err!(
1208 verify_execution_receipt::<Test>(domain_id, &invalid_receipt),
1209 Error::InvalidTraceRoot
1210 );
1211
1212 let mut invalid_receipt = next_receipt;
1214 invalid_receipt.execution_trace.pop();
1215 assert_err!(
1216 verify_execution_receipt::<Test>(domain_id, &invalid_receipt),
1217 Error::InvalidTraceRoot
1218 );
1219 });
1220 }
1221
1222 #[test]
1223 fn test_collect_invalid_bundle_author() {
1224 let creator = 0u128;
1225 let challenge_period = BlockTreePruningDepth::get() as u64;
1226 let operator_set: Vec<_> = (1..15).collect();
1227 let mut ext = new_test_ext_with_extensions();
1228 ext.execute_with(|| {
1229 let domain_id = register_genesis_domain(creator, operator_set.clone());
1230 let next_receipt = extend_block_tree_from_zero(domain_id, operator_set[0], 3);
1231
1232 for operator_id in operator_set.iter() {
1234 let bundle = create_dummy_bundle_with_receipts(
1235 domain_id,
1236 *operator_id,
1237 H256::random(),
1238 next_receipt.clone(),
1239 );
1240 assert_ok!(crate::Pallet::<Test>::submit_bundle(
1241 DomainOrigin::ValidatedUnsigned.into(),
1242 bundle,
1243 ));
1244 }
1245 let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
1246 let head_node = get_block_tree_node_at::<Test>(domain_id, head_receipt_number).unwrap();
1247 assert_eq!(head_node.operator_ids, operator_set);
1248
1249 let current_block_number = frame_system::Pallet::<Test>::current_block_number();
1251 let execution_inbox = ExecutionInbox::<Test>::get((
1252 domain_id,
1253 current_block_number as u32,
1254 current_block_number,
1255 ));
1256 let bundles_extrinsics_roots: Vec<_> = execution_inbox
1257 .into_iter()
1258 .map(|b| b.extrinsics_root)
1259 .collect();
1260 assert_eq!(bundles_extrinsics_roots.len(), operator_set.len());
1261
1262 let mut bundles = vec![];
1264 let mut invalid_bundle_authors = vec![];
1265 for (i, (operator, extrinsics_root)) in operator_set
1266 .iter()
1267 .zip(bundles_extrinsics_roots)
1268 .enumerate()
1269 {
1270 if i % 2 == 0 {
1271 invalid_bundle_authors.push(*operator);
1272 bundles.push(InboxedBundle::invalid(
1273 InvalidBundleType::OutOfRangeTx(0),
1274 extrinsics_root,
1275 ));
1276 } else {
1277 bundles.push(InboxedBundle::valid(H256::random(), extrinsics_root));
1278 }
1279 }
1280 let mut target_receipt = create_dummy_receipt(
1281 current_block_number,
1282 H256::random(),
1283 next_receipt.hash::<DomainHashingFor<Test>>(),
1284 vec![],
1285 );
1286 target_receipt.inboxed_bundles = bundles;
1287
1288 let next_receipt = extend_block_tree(
1290 domain_id,
1291 operator_set[0],
1292 (current_block_number + challenge_period) as u32 + 1,
1293 target_receipt,
1294 );
1295 let confirmed_domain_block = process_execution_receipt::<Test>(
1297 domain_id,
1298 operator_set[0],
1299 next_receipt,
1300 AcceptedReceiptType::NewHead,
1301 )
1302 .unwrap()
1303 .unwrap();
1304
1305 assert_eq!(
1307 confirmed_domain_block.invalid_bundle_authors,
1308 invalid_bundle_authors
1309 );
1310 });
1311 }
1312}