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