1#![warn(missing_docs)]
25#![cfg_attr(not(feature = "std"), no_std)]
26
27pub use weights::WeightInfo;
28pub use weights_ext::WeightInfoExt;
29
30use bp_header_chain::{HeaderChain, HeaderChainError};
31use bp_parachains::{
32 ParaInfo, ParaStoredHeaderData, RelayBlockHash, RelayBlockHasher, RelayBlockNumber,
33 SubmitParachainHeadsInfo,
34};
35use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId};
36use bp_runtime::{Chain, HashOf, HeaderId, HeaderIdOf, Parachain};
37use frame_support::{dispatch::PostDispatchInfo, DefaultNoBound};
38use pallet_bridge_grandpa::SubmitFinalityProofHelper;
39use proofs::{ParachainsStorageProofAdapter, StorageProofAdapter};
40use sp_std::{marker::PhantomData, vec::Vec};
41
42#[cfg(feature = "runtime-benchmarks")]
43use bp_parachains::ParaStoredHeaderDataBuilder;
44#[cfg(feature = "runtime-benchmarks")]
45use bp_runtime::HeaderOf;
46#[cfg(feature = "runtime-benchmarks")]
47use codec::Encode;
48
49pub use call_ext::*;
51pub use pallet::*;
52
53pub mod weights;
54pub mod weights_ext;
55
56#[cfg(feature = "runtime-benchmarks")]
57pub mod benchmarking;
58
59mod call_ext;
60#[cfg(test)]
61mod mock;
62mod proofs;
63
64pub const LOG_TARGET: &str = "runtime::bridge-parachains";
66
67struct UpdateParachainHeadArtifacts {
69 pub best_head: ParaInfo,
71 pub prune_happened: bool,
73}
74
75#[frame_support::pallet]
76pub mod pallet {
77 use super::*;
78 use bp_parachains::{
79 BestParaHeadHash, ImportedParaHeadsKeyProvider, ParaStoredHeaderDataBuilder,
80 ParasInfoKeyProvider,
81 };
82 use bp_runtime::{
83 BasicOperatingMode, BoundedStorageValue, OwnedBridgeModule, StorageDoubleMapKeyProvider,
84 StorageMapKeyProvider,
85 };
86 use frame_support::pallet_prelude::*;
87 use frame_system::pallet_prelude::*;
88
89 pub type StoredParaHeadDataOf<T, I> =
91 BoundedStorageValue<<T as Config<I>>::MaxParaHeadDataSize, ParaStoredHeaderData>;
92 pub type WeightInfoOf<T, I> = <T as Config<I>>::WeightInfo;
94 pub type GrandpaPalletOf<T, I> =
96 pallet_bridge_grandpa::Pallet<T, <T as Config<I>>::BridgesGrandpaPalletInstance>;
97
98 #[pallet::event]
99 #[pallet::generate_deposit(pub(super) fn deposit_event)]
100 pub enum Event<T: Config<I>, I: 'static = ()> {
101 UntrackedParachainRejected {
103 parachain: ParaId,
105 },
106 MissingParachainHead {
109 parachain: ParaId,
111 },
112 IncorrectParachainHeadHash {
115 parachain: ParaId,
117 parachain_head_hash: ParaHash,
119 actual_parachain_head_hash: ParaHash,
121 },
122 RejectedObsoleteParachainHead {
124 parachain: ParaId,
126 parachain_head_hash: ParaHash,
128 },
129 RejectedLargeParachainHead {
131 parachain: ParaId,
133 parachain_head_hash: ParaHash,
135 parachain_head_size: u32,
137 },
138 UpdatedParachainHead {
140 parachain: ParaId,
142 parachain_head_hash: ParaHash,
144 },
145 }
146
147 #[pallet::error]
148 pub enum Error<T, I = ()> {
149 UnknownRelayChainBlock,
151 InvalidRelayChainBlockNumber,
153 HeaderChainStorageProof(HeaderChainError),
155 BridgeModule(bp_runtime::OwnedBridgeModuleError),
157 }
158
159 pub trait BoundedBridgeGrandpaConfig<I: 'static>:
161 pallet_bridge_grandpa::Config<I, BridgedChain = Self::BridgedRelayChain>
162 {
163 type BridgedRelayChain: Chain<
165 BlockNumber = RelayBlockNumber,
166 Hash = RelayBlockHash,
167 Hasher = RelayBlockHasher,
168 >;
169 }
170
171 impl<T, I: 'static> BoundedBridgeGrandpaConfig<I> for T
172 where
173 T: pallet_bridge_grandpa::Config<I>,
174 T::BridgedChain:
175 Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash, Hasher = RelayBlockHasher>,
176 {
177 type BridgedRelayChain = T::BridgedChain;
178 }
179
180 #[pallet::config]
181 #[pallet::disable_frame_system_supertrait_check]
182 pub trait Config<I: 'static = ()>:
183 BoundedBridgeGrandpaConfig<Self::BridgesGrandpaPalletInstance>
184 {
185 type RuntimeEvent: From<Event<Self, I>>
187 + IsType<<Self as frame_system::Config>::RuntimeEvent>;
188 type WeightInfo: WeightInfoExt;
190
191 type BridgesGrandpaPalletInstance: 'static;
211
212 #[pallet::constant]
218 type ParasPalletName: Get<&'static str>;
219
220 type ParaStoredHeaderDataBuilder: ParaStoredHeaderDataBuilder;
232
233 #[pallet::constant]
241 type HeadsToKeep: Get<u32>;
242
243 #[pallet::constant]
253 type MaxParaHeadDataSize: Get<u32>;
254 }
255
256 #[pallet::storage]
263 pub type PalletOwner<T: Config<I>, I: 'static = ()> =
264 StorageValue<_, T::AccountId, OptionQuery>;
265
266 #[pallet::storage]
270 pub type PalletOperatingMode<T: Config<I>, I: 'static = ()> =
271 StorageValue<_, BasicOperatingMode, ValueQuery>;
272
273 #[pallet::storage]
279 pub type ParasInfo<T: Config<I>, I: 'static = ()> = StorageMap<
280 Hasher = <ParasInfoKeyProvider as StorageMapKeyProvider>::Hasher,
281 Key = <ParasInfoKeyProvider as StorageMapKeyProvider>::Key,
282 Value = <ParasInfoKeyProvider as StorageMapKeyProvider>::Value,
283 QueryKind = OptionQuery,
284 OnEmpty = GetDefault,
285 MaxValues = MaybeMaxParachains<T, I>,
286 >;
287
288 #[pallet::storage]
290 pub type ImportedParaHeads<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
291 Hasher1 = <ImportedParaHeadsKeyProvider as StorageDoubleMapKeyProvider>::Hasher1,
292 Key1 = <ImportedParaHeadsKeyProvider as StorageDoubleMapKeyProvider>::Key1,
293 Hasher2 = <ImportedParaHeadsKeyProvider as StorageDoubleMapKeyProvider>::Hasher2,
294 Key2 = <ImportedParaHeadsKeyProvider as StorageDoubleMapKeyProvider>::Key2,
295 Value = StoredParaHeadDataOf<T, I>,
296 QueryKind = OptionQuery,
297 OnEmpty = GetDefault,
298 MaxValues = MaybeMaxTotalParachainHashes<T, I>,
299 >;
300
301 #[pallet::storage]
303 pub(super) type ImportedParaHashes<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
304 Hasher1 = Blake2_128Concat,
305 Key1 = ParaId,
306 Hasher2 = Twox64Concat,
307 Key2 = u32,
308 Value = ParaHash,
309 QueryKind = OptionQuery,
310 OnEmpty = GetDefault,
311 MaxValues = MaybeMaxTotalParachainHashes<T, I>,
312 >;
313
314 #[pallet::pallet]
315 pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
316
317 impl<T: Config<I>, I: 'static> OwnedBridgeModule<T> for Pallet<T, I> {
318 const LOG_TARGET: &'static str = LOG_TARGET;
319 type OwnerStorage = PalletOwner<T, I>;
320 type OperatingMode = BasicOperatingMode;
321 type OperatingModeStorage = PalletOperatingMode<T, I>;
322 }
323
324 #[pallet::call]
325 impl<T: Config<I>, I: 'static> Pallet<T, I> {
326 #[pallet::call_index(0)]
343 #[pallet::weight(WeightInfoOf::<T, I>::submit_parachain_heads_weight(
344 T::DbWeight::get(),
345 parachain_heads_proof,
346 parachains.len() as _,
347 ))]
348 pub fn submit_parachain_heads(
349 origin: OriginFor<T>,
350 at_relay_block: (RelayBlockNumber, RelayBlockHash),
351 parachains: Vec<(ParaId, ParaHash)>,
352 parachain_heads_proof: ParaHeadsProof,
353 ) -> DispatchResultWithPostInfo {
354 Self::submit_parachain_heads_ex(
355 origin,
356 at_relay_block,
357 parachains,
358 parachain_heads_proof,
359 false,
360 )
361 }
362
363 #[pallet::call_index(1)]
367 #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
368 pub fn set_owner(origin: OriginFor<T>, new_owner: Option<T::AccountId>) -> DispatchResult {
369 <Self as OwnedBridgeModule<_>>::set_owner(origin, new_owner)
370 }
371
372 #[pallet::call_index(2)]
376 #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
377 pub fn set_operating_mode(
378 origin: OriginFor<T>,
379 operating_mode: BasicOperatingMode,
380 ) -> DispatchResult {
381 <Self as OwnedBridgeModule<_>>::set_operating_mode(origin, operating_mode)
382 }
383
384 #[pallet::call_index(3)]
407 #[pallet::weight(WeightInfoOf::<T, I>::submit_parachain_heads_weight(
408 T::DbWeight::get(),
409 parachain_heads_proof,
410 parachains.len() as _,
411 ))]
412 pub fn submit_parachain_heads_ex(
413 origin: OriginFor<T>,
414 at_relay_block: (RelayBlockNumber, RelayBlockHash),
415 parachains: Vec<(ParaId, ParaHash)>,
416 parachain_heads_proof: ParaHeadsProof,
417 _is_free_execution_expected: bool,
418 ) -> DispatchResultWithPostInfo {
419 Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
420 ensure_signed(origin)?;
421
422 let total_parachains = parachains.len();
423 let free_headers_interval =
424 T::FreeHeadersInterval::get().unwrap_or(RelayBlockNumber::MAX);
425 let mut free_parachain_heads = 0;
429
430 let (relay_block_number, relay_block_hash) = at_relay_block;
432 let relay_block = pallet_bridge_grandpa::ImportedHeaders::<
433 T,
434 T::BridgesGrandpaPalletInstance,
435 >::get(relay_block_hash)
436 .ok_or(Error::<T, I>::UnknownRelayChainBlock)?;
437 ensure!(
438 relay_block.number == relay_block_number,
439 Error::<T, I>::InvalidRelayChainBlockNumber,
440 );
441
442 let mut actual_weight = WeightInfoOf::<T, I>::submit_parachain_heads_weight(
444 T::DbWeight::get(),
445 ¶chain_heads_proof,
446 parachains.len() as _,
447 );
448
449 let mut storage: ParachainsStorageProofAdapter<T, I> =
450 ParachainsStorageProofAdapter::try_new_with_verified_storage_proof(
451 relay_block_hash,
452 parachain_heads_proof.storage_proof,
453 )
454 .map_err(Error::<T, I>::HeaderChainStorageProof)?;
455
456 for (parachain, parachain_head_hash) in parachains {
457 let parachain_head = match storage.read_parachain_head(parachain) {
458 Ok(Some(parachain_head)) => parachain_head,
459 Ok(None) => {
460 log::trace!(
461 target: LOG_TARGET,
462 "The head of parachain {:?} is None. {}",
463 parachain,
464 if ParasInfo::<T, I>::contains_key(parachain) {
465 "Looks like it is not yet registered at the source relay chain"
466 } else {
467 "Looks like it has been deregistered from the source relay chain"
468 },
469 );
470 Self::deposit_event(Event::MissingParachainHead { parachain });
471 continue
472 },
473 Err(e) => {
474 log::trace!(
475 target: LOG_TARGET,
476 "The read of head of parachain {:?} has failed: {:?}",
477 parachain,
478 e,
479 );
480 Self::deposit_event(Event::MissingParachainHead { parachain });
481 continue
482 },
483 };
484
485 let actual_parachain_head_hash = parachain_head.hash();
488 if parachain_head_hash != actual_parachain_head_hash {
489 log::trace!(
490 target: LOG_TARGET,
491 "The submitter has specified invalid parachain {:?} head hash: \
492 {:?} vs {:?}",
493 parachain,
494 parachain_head_hash,
495 actual_parachain_head_hash,
496 );
497 Self::deposit_event(Event::IncorrectParachainHeadHash {
498 parachain,
499 parachain_head_hash,
500 actual_parachain_head_hash,
501 });
502 continue
503 }
504
505 let parachain_head_size = parachain_head.0.len();
507 let parachain_head_data =
508 match T::ParaStoredHeaderDataBuilder::try_build(parachain, ¶chain_head) {
509 Some(parachain_head_data) => parachain_head_data,
510 None => {
511 log::trace!(
512 target: LOG_TARGET,
513 "The head of parachain {:?} has been provided, but it is not tracked by the pallet",
514 parachain,
515 );
516 Self::deposit_event(Event::UntrackedParachainRejected { parachain });
517 continue
518 },
519 };
520
521 let update_result: Result<_, ()> =
522 ParasInfo::<T, I>::try_mutate(parachain, |stored_best_head| {
523 let is_free = parachain_head_size <
524 T::ParaStoredHeaderDataBuilder::max_free_head_size() as usize &&
525 match stored_best_head {
526 Some(ref best_head)
527 if at_relay_block.0.saturating_sub(
528 best_head.best_head_hash.at_relay_block_number,
529 ) >= free_headers_interval =>
530 true,
531 Some(_) => false,
532 None => true,
533 };
534 let artifacts = Pallet::<T, I>::update_parachain_head(
535 parachain,
536 stored_best_head.take(),
537 HeaderId(relay_block_number, relay_block_hash),
538 parachain_head_data,
539 parachain_head_hash,
540 )?;
541
542 if is_free {
543 free_parachain_heads = free_parachain_heads + 1;
544 }
545
546 *stored_best_head = Some(artifacts.best_head);
547 Ok(artifacts.prune_happened)
548 });
549
550 let is_update_happened = update_result.is_ok();
552 if !is_update_happened {
553 actual_weight = actual_weight.saturating_sub(
554 WeightInfoOf::<T, I>::parachain_head_storage_write_weight(
555 T::DbWeight::get(),
556 ),
557 );
558 }
559 let is_prune_happened = matches!(update_result, Ok(true));
560 if !is_prune_happened {
561 actual_weight = actual_weight.saturating_sub(
562 WeightInfoOf::<T, I>::parachain_head_pruning_weight(T::DbWeight::get()),
563 );
564 }
565 }
566
567 storage.ensure_no_unused_keys().map_err(|e| {
573 Error::<T, I>::HeaderChainStorageProof(HeaderChainError::StorageProof(e))
574 })?;
575
576 let is_free = total_parachains == 1
578 && free_parachain_heads == total_parachains
579 && SubmitFinalityProofHelper::<T, T::BridgesGrandpaPalletInstance>::has_free_header_slots();
580 let pays_fee = if is_free {
581 log::trace!(target: LOG_TARGET, "Parachain heads update transaction is free");
582 pallet_bridge_grandpa::on_free_header_imported::<T, T::BridgesGrandpaPalletInstance>(
583 );
584 Pays::No
585 } else {
586 log::trace!(target: LOG_TARGET, "Parachain heads update transaction is paid");
587 Pays::Yes
588 };
589
590 Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee })
591 }
592 }
593
594 impl<T: Config<I>, I: 'static> Pallet<T, I> {
595 pub fn best_parachain_info(parachain: ParaId) -> Option<ParaInfo> {
597 ParasInfo::<T, I>::get(parachain)
598 }
599
600 pub fn best_parachain_head(parachain: ParaId) -> Option<ParaStoredHeaderData> {
602 let best_para_head_hash = ParasInfo::<T, I>::get(parachain)?.best_head_hash.head_hash;
603 ImportedParaHeads::<T, I>::get(parachain, best_para_head_hash).map(|h| h.into_inner())
604 }
605
606 pub fn best_parachain_head_hash(parachain: ParaId) -> Option<ParaHash> {
608 Some(ParasInfo::<T, I>::get(parachain)?.best_head_hash.head_hash)
609 }
610
611 pub fn best_parachain_head_id<C: Chain<Hash = ParaHash> + Parachain>(
613 ) -> Result<Option<HeaderIdOf<C>>, codec::Error> {
614 let parachain = ParaId(C::PARACHAIN_ID);
615 let best_head_hash = match Self::best_parachain_head_hash(parachain) {
616 Some(best_head_hash) => best_head_hash,
617 None => return Ok(None),
618 };
619 let encoded_head = match Self::parachain_head(parachain, best_head_hash) {
620 Some(encoded_head) => encoded_head,
621 None => return Ok(None),
622 };
623 encoded_head
624 .decode_parachain_head_data::<C>()
625 .map(|data| Some(HeaderId(data.number, best_head_hash)))
626 }
627
628 pub fn parachain_head(parachain: ParaId, hash: ParaHash) -> Option<ParaStoredHeaderData> {
630 ImportedParaHeads::<T, I>::get(parachain, hash).map(|h| h.into_inner())
631 }
632
633 pub(super) fn update_parachain_head(
635 parachain: ParaId,
636 stored_best_head: Option<ParaInfo>,
637 new_at_relay_block: HeaderId<RelayBlockHash, RelayBlockNumber>,
638 new_head_data: ParaStoredHeaderData,
639 new_head_hash: ParaHash,
640 ) -> Result<UpdateParachainHeadArtifacts, ()> {
641 let update = SubmitParachainHeadsInfo {
644 at_relay_block: new_at_relay_block,
645 para_id: parachain,
646 para_head_hash: new_head_hash,
647 is_free_execution_expected: false,
649 };
650 if SubmitParachainHeadsHelper::<T, I>::check_obsolete(&update).is_err() {
651 Self::deposit_event(Event::RejectedObsoleteParachainHead {
652 parachain,
653 parachain_head_hash: new_head_hash,
654 });
655 return Err(())
656 }
657
658 let updated_head_data =
660 match StoredParaHeadDataOf::<T, I>::try_from_inner(new_head_data) {
661 Ok(updated_head_data) => updated_head_data,
662 Err(e) => {
663 log::trace!(
664 target: LOG_TARGET,
665 "The parachain head can't be updated. The parachain head data size \
666 for {:?} is {}. It exceeds maximal configured size {}.",
667 parachain,
668 e.value_size,
669 e.maximal_size,
670 );
671
672 Self::deposit_event(Event::RejectedLargeParachainHead {
673 parachain,
674 parachain_head_hash: new_head_hash,
675 parachain_head_size: e.value_size as _,
676 });
677
678 return Err(())
679 },
680 };
681
682 let next_imported_hash_position = stored_best_head
683 .map_or(0, |stored_best_head| stored_best_head.next_imported_hash_position);
684
685 let head_hash_to_prune =
687 ImportedParaHashes::<T, I>::try_get(parachain, next_imported_hash_position);
688 let updated_best_para_head = ParaInfo {
689 best_head_hash: BestParaHeadHash {
690 at_relay_block_number: new_at_relay_block.0,
691 head_hash: new_head_hash,
692 },
693 next_imported_hash_position: (next_imported_hash_position + 1) %
694 T::HeadsToKeep::get(),
695 };
696 ImportedParaHashes::<T, I>::insert(
697 parachain,
698 next_imported_hash_position,
699 new_head_hash,
700 );
701 ImportedParaHeads::<T, I>::insert(parachain, new_head_hash, updated_head_data);
702 log::trace!(
703 target: LOG_TARGET,
704 "Updated head of parachain {:?} to {} at relay block {}",
705 parachain,
706 new_head_hash,
707 new_at_relay_block.0,
708 );
709
710 let prune_happened = head_hash_to_prune.is_ok();
712 if let Ok(head_hash_to_prune) = head_hash_to_prune {
713 log::trace!(
714 target: LOG_TARGET,
715 "Pruning old head of parachain {:?}: {}",
716 parachain,
717 head_hash_to_prune,
718 );
719 ImportedParaHeads::<T, I>::remove(parachain, head_hash_to_prune);
720 }
721 Self::deposit_event(Event::UpdatedParachainHead {
722 parachain,
723 parachain_head_hash: new_head_hash,
724 });
725
726 Ok(UpdateParachainHeadArtifacts { best_head: updated_best_para_head, prune_happened })
727 }
728 }
729
730 #[pallet::genesis_config]
731 #[derive(DefaultNoBound)]
732 pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
733 pub operating_mode: BasicOperatingMode,
735 pub owner: Option<T::AccountId>,
737 #[serde(skip)]
739 pub _phantom: sp_std::marker::PhantomData<I>,
740 }
741
742 #[pallet::genesis_build]
743 impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
744 fn build(&self) {
745 PalletOperatingMode::<T, I>::put(self.operating_mode);
746 if let Some(ref owner) = self.owner {
747 PalletOwner::<T, I>::put(owner);
748 }
749 }
750 }
751
752 pub struct MaybeMaxParachains<T, I>(PhantomData<(T, I)>);
754
755 impl<T: Config<I>, I: 'static> Get<Option<u32>> for MaybeMaxParachains<T, I> {
756 fn get() -> Option<u32> {
757 Some(T::ParaStoredHeaderDataBuilder::supported_parachains())
758 }
759 }
760
761 pub struct MaybeMaxTotalParachainHashes<T, I>(PhantomData<(T, I)>);
763
764 impl<T: Config<I>, I: 'static> Get<Option<u32>> for MaybeMaxTotalParachainHashes<T, I> {
765 fn get() -> Option<u32> {
766 Some(
767 T::ParaStoredHeaderDataBuilder::supported_parachains()
768 .saturating_mul(T::HeadsToKeep::get()),
769 )
770 }
771 }
772}
773
774pub struct ParachainHeaders<T, I, C>(PhantomData<(T, I, C)>);
776
777impl<T: Config<I>, I: 'static, C: Parachain<Hash = ParaHash>> HeaderChain<C>
778 for ParachainHeaders<T, I, C>
779{
780 fn finalized_header_state_root(hash: HashOf<C>) -> Option<HashOf<C>> {
781 Pallet::<T, I>::parachain_head(ParaId(C::PARACHAIN_ID), hash)
782 .and_then(|head| head.decode_parachain_head_data::<C>().ok())
783 .map(|h| h.state_root)
784 }
785}
786
787#[cfg(feature = "runtime-benchmarks")]
789pub fn initialize_for_benchmarks<T: Config<I>, I: 'static, PC: Parachain<Hash = ParaHash>>(
790 header: HeaderOf<PC>,
791) {
792 use bp_polkadot_core::parachains::ParaHead;
793 use bp_runtime::HeaderIdProvider;
794 use sp_runtime::traits::Header;
795
796 let relay_head =
797 pallet_bridge_grandpa::BridgedHeader::<T, T::BridgesGrandpaPalletInstance>::new(
798 0,
799 Default::default(),
800 Default::default(),
801 Default::default(),
802 Default::default(),
803 );
804 let parachain = ParaId(PC::PARACHAIN_ID);
805 let parachain_head = ParaHead(header.encode());
806 let updated_head_data = T::ParaStoredHeaderDataBuilder::try_build(parachain, ¶chain_head)
807 .expect("failed to build stored parachain head in benchmarks");
808 pallet_bridge_grandpa::initialize_for_benchmarks::<T, T::BridgesGrandpaPalletInstance>(
809 relay_head.clone(),
810 );
811 Pallet::<T, I>::update_parachain_head(
812 parachain,
813 None,
814 relay_head.id(),
815 updated_head_data,
816 parachain_head.hash(),
817 )
818 .expect("failed to insert parachain head in benchmarks");
819}
820
821#[cfg(test)]
822pub(crate) mod tests {
823 use super::*;
824 use crate::mock::{
825 run_test, test_relay_header, BigParachain, BigParachainHeader, FreeHeadersInterval,
826 RegularParachainHasher, RegularParachainHeader, RelayBlockHeader,
827 RuntimeEvent as TestEvent, RuntimeOrigin, TestRuntime, UNTRACKED_PARACHAIN_ID,
828 };
829 use bp_test_utils::prepare_parachain_heads_proof;
830 use codec::Encode;
831
832 use bp_header_chain::{justification::GrandpaJustification, StoredHeaderGrandpaInfo};
833 use bp_parachains::{
834 BestParaHeadHash, BridgeParachainCall, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider,
835 };
836 use bp_polkadot_core::parachains::ParaHead;
837 use bp_runtime::{
838 BasicOperatingMode, OwnedBridgeModuleError, StorageDoubleMapKeyProvider,
839 StorageMapKeyProvider, StorageProofError,
840 };
841 use bp_test_utils::{
842 authority_list, generate_owned_bridge_module_tests, make_default_justification,
843 TEST_GRANDPA_SET_ID,
844 };
845 use frame_support::{
846 assert_noop, assert_ok,
847 dispatch::DispatchResultWithPostInfo,
848 pallet_prelude::Pays,
849 storage::generator::{StorageDoubleMap, StorageMap},
850 traits::Get,
851 weights::Weight,
852 };
853 use frame_system::{EventRecord, Pallet as System, Phase};
854 use sp_core::Hasher;
855 use sp_runtime::{traits::Header as HeaderT, DispatchError};
856
857 type BridgesGrandpaPalletInstance = pallet_bridge_grandpa::Instance1;
858 type WeightInfo = <TestRuntime as Config>::WeightInfo;
859 type DbWeight = <TestRuntime as frame_system::Config>::DbWeight;
860
861 pub(crate) fn initialize(state_root: RelayBlockHash) -> RelayBlockHash {
862 pallet_bridge_grandpa::FreeHeadersRemaining::<TestRuntime, BridgesGrandpaPalletInstance>::set(Some(100));
863 pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::initialize(
864 RuntimeOrigin::root(),
865 bp_header_chain::InitializationData {
866 header: Box::new(test_relay_header(0, state_root)),
867 authority_list: authority_list(),
868 set_id: 1,
869 operating_mode: BasicOperatingMode::Normal,
870 },
871 )
872 .unwrap();
873
874 System::<TestRuntime>::set_block_number(1);
875 System::<TestRuntime>::reset_events();
876
877 test_relay_header(0, state_root).hash()
878 }
879
880 fn proceed(
881 num: RelayBlockNumber,
882 state_root: RelayBlockHash,
883 ) -> (ParaHash, GrandpaJustification<RelayBlockHeader>) {
884 let header = test_relay_header(num, state_root);
885 let hash = header.hash();
886 let justification = make_default_justification(&header);
887 assert_ok!(
888 pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::submit_finality_proof_ex(
889 RuntimeOrigin::signed(1),
890 Box::new(header),
891 justification.clone(),
892 TEST_GRANDPA_SET_ID,
893 false,
894 )
895 );
896
897 (hash, justification)
898 }
899
900 fn initial_best_head(parachain: u32) -> ParaInfo {
901 ParaInfo {
902 best_head_hash: BestParaHeadHash {
903 at_relay_block_number: 0,
904 head_hash: head_data(parachain, 0).hash(),
905 },
906 next_imported_hash_position: 1,
907 }
908 }
909
910 pub(crate) fn head_data(parachain: u32, head_number: u32) -> ParaHead {
911 ParaHead(
912 RegularParachainHeader::new(
913 head_number as _,
914 Default::default(),
915 RegularParachainHasher::hash(&(parachain, head_number).encode()),
916 Default::default(),
917 Default::default(),
918 )
919 .encode(),
920 )
921 }
922
923 fn stored_head_data(parachain: u32, head_number: u32) -> ParaStoredHeaderData {
924 ParaStoredHeaderData(
925 (head_number as u64, RegularParachainHasher::hash(&(parachain, head_number).encode()))
926 .encode(),
927 )
928 }
929
930 fn big_head_data(parachain: u32, head_number: u32) -> ParaHead {
931 ParaHead(
932 BigParachainHeader::new(
933 head_number as _,
934 Default::default(),
935 RegularParachainHasher::hash(&(parachain, head_number).encode()),
936 Default::default(),
937 Default::default(),
938 )
939 .encode(),
940 )
941 }
942
943 fn big_stored_head_data(parachain: u32, head_number: u32) -> ParaStoredHeaderData {
944 ParaStoredHeaderData(
945 (head_number as u128, RegularParachainHasher::hash(&(parachain, head_number).encode()))
946 .encode(),
947 )
948 }
949
950 fn head_hash(parachain: u32, head_number: u32) -> ParaHash {
951 head_data(parachain, head_number).hash()
952 }
953
954 fn import_parachain_1_head(
955 relay_chain_block: RelayBlockNumber,
956 relay_state_root: RelayBlockHash,
957 parachains: Vec<(ParaId, ParaHash)>,
958 proof: ParaHeadsProof,
959 ) -> DispatchResultWithPostInfo {
960 Pallet::<TestRuntime>::submit_parachain_heads(
961 RuntimeOrigin::signed(1),
962 (relay_chain_block, test_relay_header(relay_chain_block, relay_state_root).hash()),
963 parachains,
964 proof,
965 )
966 }
967
968 fn weight_of_import_parachain_1_head(proof: &ParaHeadsProof, prune_expected: bool) -> Weight {
969 let db_weight = <TestRuntime as frame_system::Config>::DbWeight::get();
970 WeightInfoOf::<TestRuntime, ()>::submit_parachain_heads_weight(db_weight, proof, 1)
971 .saturating_sub(if prune_expected {
972 Weight::zero()
973 } else {
974 WeightInfoOf::<TestRuntime, ()>::parachain_head_pruning_weight(db_weight)
975 })
976 }
977
978 #[test]
979 fn submit_parachain_heads_checks_operating_mode() {
980 let (state_root, proof, parachains) =
981 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
982
983 run_test(|| {
984 initialize(state_root);
985
986 PalletOperatingMode::<TestRuntime>::put(BasicOperatingMode::Halted);
988 assert_noop!(
989 Pallet::<TestRuntime>::submit_parachain_heads(
990 RuntimeOrigin::signed(1),
991 (0, test_relay_header(0, state_root).hash()),
992 parachains.clone(),
993 proof.clone(),
994 ),
995 Error::<TestRuntime>::BridgeModule(OwnedBridgeModuleError::Halted)
996 );
997
998 PalletOperatingMode::<TestRuntime>::put(BasicOperatingMode::Normal);
1000 assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1001 RuntimeOrigin::signed(1),
1002 (0, test_relay_header(0, state_root).hash()),
1003 parachains,
1004 proof,
1005 ),);
1006 });
1007 }
1008
1009 #[test]
1010 fn imports_initial_parachain_heads() {
1011 let (state_root, proof, parachains) =
1012 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![
1013 (1, head_data(1, 0)),
1014 (3, head_data(3, 10)),
1015 ]);
1016 run_test(|| {
1017 initialize(state_root);
1018
1019 let expected_weight =
1021 WeightInfo::submit_parachain_heads_weight(DbWeight::get(), &proof, 2);
1022 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1023 RuntimeOrigin::signed(1),
1024 (0, test_relay_header(0, state_root).hash()),
1025 parachains,
1026 proof,
1027 );
1028 assert_ok!(result);
1029 assert_eq!(result.expect("checked above").pays_fee, Pays::Yes);
1030 assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight));
1031
1032 assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
1034 assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(2)), None);
1035 assert_eq!(
1036 ParasInfo::<TestRuntime>::get(ParaId(3)),
1037 Some(ParaInfo {
1038 best_head_hash: BestParaHeadHash {
1039 at_relay_block_number: 0,
1040 head_hash: head_data(3, 10).hash()
1041 },
1042 next_imported_hash_position: 1,
1043 })
1044 );
1045
1046 assert_eq!(
1047 ImportedParaHeads::<TestRuntime>::get(
1048 ParaId(1),
1049 initial_best_head(1).best_head_hash.head_hash
1050 )
1051 .map(|h| h.into_inner()),
1052 Some(stored_head_data(1, 0))
1053 );
1054 assert_eq!(
1055 ImportedParaHeads::<TestRuntime>::get(
1056 ParaId(2),
1057 initial_best_head(2).best_head_hash.head_hash
1058 )
1059 .map(|h| h.into_inner()),
1060 None
1061 );
1062 assert_eq!(
1063 ImportedParaHeads::<TestRuntime>::get(ParaId(3), head_hash(3, 10))
1064 .map(|h| h.into_inner()),
1065 Some(stored_head_data(3, 10))
1066 );
1067
1068 assert_eq!(
1069 System::<TestRuntime>::events(),
1070 vec![
1071 EventRecord {
1072 phase: Phase::Initialization,
1073 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1074 parachain: ParaId(1),
1075 parachain_head_hash: initial_best_head(1).best_head_hash.head_hash,
1076 }),
1077 topics: vec![],
1078 },
1079 EventRecord {
1080 phase: Phase::Initialization,
1081 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1082 parachain: ParaId(3),
1083 parachain_head_hash: head_data(3, 10).hash(),
1084 }),
1085 topics: vec![],
1086 }
1087 ],
1088 );
1089 });
1090 }
1091
1092 #[test]
1093 fn imports_parachain_heads_is_able_to_progress() {
1094 let (state_root_5, proof_5, parachains_5) =
1095 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 5))]);
1096 let (state_root_10, proof_10, parachains_10) =
1097 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 10))]);
1098 run_test(|| {
1099 initialize(state_root_5);
1101 let result = import_parachain_1_head(0, state_root_5, parachains_5, proof_5);
1102 assert_eq!(result.unwrap().pays_fee, Pays::No);
1104 assert_eq!(
1105 ParasInfo::<TestRuntime>::get(ParaId(1)),
1106 Some(ParaInfo {
1107 best_head_hash: BestParaHeadHash {
1108 at_relay_block_number: 0,
1109 head_hash: head_data(1, 5).hash()
1110 },
1111 next_imported_hash_position: 1,
1112 })
1113 );
1114 assert_eq!(
1115 ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 5).hash())
1116 .map(|h| h.into_inner()),
1117 Some(stored_head_data(1, 5))
1118 );
1119 assert_eq!(
1120 ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 10).hash())
1121 .map(|h| h.into_inner()),
1122 None
1123 );
1124 assert_eq!(
1125 System::<TestRuntime>::events(),
1126 vec![EventRecord {
1127 phase: Phase::Initialization,
1128 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1129 parachain: ParaId(1),
1130 parachain_head_hash: head_data(1, 5).hash(),
1131 }),
1132 topics: vec![],
1133 }],
1134 );
1135
1136 let (relay_1_hash, justification) = proceed(1, state_root_10);
1138 let result = import_parachain_1_head(1, state_root_10, parachains_10, proof_10);
1139 assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1141 assert_eq!(
1142 ParasInfo::<TestRuntime>::get(ParaId(1)),
1143 Some(ParaInfo {
1144 best_head_hash: BestParaHeadHash {
1145 at_relay_block_number: 1,
1146 head_hash: head_data(1, 10).hash()
1147 },
1148 next_imported_hash_position: 2,
1149 })
1150 );
1151 assert_eq!(
1152 ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 5).hash())
1153 .map(|h| h.into_inner()),
1154 Some(stored_head_data(1, 5))
1155 );
1156 assert_eq!(
1157 ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 10).hash())
1158 .map(|h| h.into_inner()),
1159 Some(stored_head_data(1, 10))
1160 );
1161 assert_eq!(
1162 System::<TestRuntime>::events(),
1163 vec![
1164 EventRecord {
1165 phase: Phase::Initialization,
1166 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1167 parachain: ParaId(1),
1168 parachain_head_hash: head_data(1, 5).hash(),
1169 }),
1170 topics: vec![],
1171 },
1172 EventRecord {
1173 phase: Phase::Initialization,
1174 event: TestEvent::Grandpa1(
1175 pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
1176 number: 1,
1177 hash: relay_1_hash,
1178 grandpa_info: StoredHeaderGrandpaInfo {
1179 finality_proof: justification,
1180 new_verification_context: None,
1181 },
1182 }
1183 ),
1184 topics: vec![],
1185 },
1186 EventRecord {
1187 phase: Phase::Initialization,
1188 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1189 parachain: ParaId(1),
1190 parachain_head_hash: head_data(1, 10).hash(),
1191 }),
1192 topics: vec![],
1193 }
1194 ],
1195 );
1196 });
1197 }
1198
1199 #[test]
1200 fn ignores_untracked_parachain() {
1201 let (state_root, proof, parachains) =
1202 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![
1203 (1, head_data(1, 5)),
1204 (UNTRACKED_PARACHAIN_ID, head_data(1, 5)),
1205 (2, head_data(1, 5)),
1206 ]);
1207 run_test(|| {
1208 let expected_weight =
1211 WeightInfo::submit_parachain_heads_weight(DbWeight::get(), &proof, 3)
1212 .saturating_sub(WeightInfo::parachain_head_storage_write_weight(
1213 DbWeight::get(),
1214 ));
1215 initialize(state_root);
1216 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1217 RuntimeOrigin::signed(1),
1218 (0, test_relay_header(0, state_root).hash()),
1219 parachains,
1220 proof,
1221 );
1222 assert_ok!(result);
1223 assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight));
1224 assert_eq!(
1225 ParasInfo::<TestRuntime>::get(ParaId(1)),
1226 Some(ParaInfo {
1227 best_head_hash: BestParaHeadHash {
1228 at_relay_block_number: 0,
1229 head_hash: head_data(1, 5).hash()
1230 },
1231 next_imported_hash_position: 1,
1232 })
1233 );
1234 assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(UNTRACKED_PARACHAIN_ID)), None,);
1235 assert_eq!(
1236 ParasInfo::<TestRuntime>::get(ParaId(2)),
1237 Some(ParaInfo {
1238 best_head_hash: BestParaHeadHash {
1239 at_relay_block_number: 0,
1240 head_hash: head_data(1, 5).hash()
1241 },
1242 next_imported_hash_position: 1,
1243 })
1244 );
1245 assert_eq!(
1246 System::<TestRuntime>::events(),
1247 vec![
1248 EventRecord {
1249 phase: Phase::Initialization,
1250 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1251 parachain: ParaId(1),
1252 parachain_head_hash: head_data(1, 5).hash(),
1253 }),
1254 topics: vec![],
1255 },
1256 EventRecord {
1257 phase: Phase::Initialization,
1258 event: TestEvent::Parachains(Event::UntrackedParachainRejected {
1259 parachain: ParaId(UNTRACKED_PARACHAIN_ID),
1260 }),
1261 topics: vec![],
1262 },
1263 EventRecord {
1264 phase: Phase::Initialization,
1265 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1266 parachain: ParaId(2),
1267 parachain_head_hash: head_data(1, 5).hash(),
1268 }),
1269 topics: vec![],
1270 }
1271 ],
1272 );
1273 });
1274 }
1275
1276 #[test]
1277 fn does_nothing_when_already_imported_this_head_at_previous_relay_header() {
1278 let (state_root, proof, parachains) =
1279 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
1280 run_test(|| {
1281 initialize(state_root);
1283 assert_ok!(import_parachain_1_head(0, state_root, parachains.clone(), proof.clone()));
1284 assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
1285 assert_eq!(
1286 System::<TestRuntime>::events(),
1287 vec![EventRecord {
1288 phase: Phase::Initialization,
1289 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1290 parachain: ParaId(1),
1291 parachain_head_hash: initial_best_head(1).best_head_hash.head_hash,
1292 }),
1293 topics: vec![],
1294 }],
1295 );
1296
1297 let (relay_1_hash, justification) = proceed(1, state_root);
1300 assert_ok!(import_parachain_1_head(1, state_root, parachains, proof));
1301 assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
1302 assert_eq!(
1303 System::<TestRuntime>::events(),
1304 vec![
1305 EventRecord {
1306 phase: Phase::Initialization,
1307 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1308 parachain: ParaId(1),
1309 parachain_head_hash: initial_best_head(1).best_head_hash.head_hash,
1310 }),
1311 topics: vec![],
1312 },
1313 EventRecord {
1314 phase: Phase::Initialization,
1315 event: TestEvent::Grandpa1(
1316 pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
1317 number: 1,
1318 hash: relay_1_hash,
1319 grandpa_info: StoredHeaderGrandpaInfo {
1320 finality_proof: justification,
1321 new_verification_context: None,
1322 }
1323 }
1324 ),
1325 topics: vec![],
1326 },
1327 EventRecord {
1328 phase: Phase::Initialization,
1329 event: TestEvent::Parachains(Event::RejectedObsoleteParachainHead {
1330 parachain: ParaId(1),
1331 parachain_head_hash: initial_best_head(1).best_head_hash.head_hash,
1332 }),
1333 topics: vec![],
1334 }
1335 ],
1336 );
1337 });
1338 }
1339
1340 #[test]
1341 fn does_nothing_when_already_imported_head_at_better_relay_header() {
1342 let (state_root_5, proof_5, parachains_5) =
1343 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 5))]);
1344 let (state_root_10, proof_10, parachains_10) =
1345 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 10))]);
1346 run_test(|| {
1347 initialize(state_root_5);
1349
1350 let (relay_1_hash, justification) = proceed(1, state_root_10);
1352 assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10));
1353 assert_eq!(
1354 ParasInfo::<TestRuntime>::get(ParaId(1)),
1355 Some(ParaInfo {
1356 best_head_hash: BestParaHeadHash {
1357 at_relay_block_number: 1,
1358 head_hash: head_data(1, 10).hash()
1359 },
1360 next_imported_hash_position: 1,
1361 })
1362 );
1363 assert_eq!(
1364 System::<TestRuntime>::events(),
1365 vec![
1366 EventRecord {
1367 phase: Phase::Initialization,
1368 event: TestEvent::Grandpa1(
1369 pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
1370 number: 1,
1371 hash: relay_1_hash,
1372 grandpa_info: StoredHeaderGrandpaInfo {
1373 finality_proof: justification.clone(),
1374 new_verification_context: None,
1375 }
1376 }
1377 ),
1378 topics: vec![],
1379 },
1380 EventRecord {
1381 phase: Phase::Initialization,
1382 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1383 parachain: ParaId(1),
1384 parachain_head_hash: head_data(1, 10).hash(),
1385 }),
1386 topics: vec![],
1387 }
1388 ],
1389 );
1390
1391 assert_ok!(import_parachain_1_head(0, state_root_5, parachains_5, proof_5));
1394 assert_eq!(
1395 ParasInfo::<TestRuntime>::get(ParaId(1)),
1396 Some(ParaInfo {
1397 best_head_hash: BestParaHeadHash {
1398 at_relay_block_number: 1,
1399 head_hash: head_data(1, 10).hash()
1400 },
1401 next_imported_hash_position: 1,
1402 })
1403 );
1404 assert_eq!(
1405 System::<TestRuntime>::events(),
1406 vec![
1407 EventRecord {
1408 phase: Phase::Initialization,
1409 event: TestEvent::Grandpa1(
1410 pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
1411 number: 1,
1412 hash: relay_1_hash,
1413 grandpa_info: StoredHeaderGrandpaInfo {
1414 finality_proof: justification,
1415 new_verification_context: None,
1416 }
1417 }
1418 ),
1419 topics: vec![],
1420 },
1421 EventRecord {
1422 phase: Phase::Initialization,
1423 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1424 parachain: ParaId(1),
1425 parachain_head_hash: head_data(1, 10).hash(),
1426 }),
1427 topics: vec![],
1428 },
1429 EventRecord {
1430 phase: Phase::Initialization,
1431 event: TestEvent::Parachains(Event::RejectedObsoleteParachainHead {
1432 parachain: ParaId(1),
1433 parachain_head_hash: head_data(1, 5).hash(),
1434 }),
1435 topics: vec![],
1436 }
1437 ],
1438 );
1439 });
1440 }
1441
1442 #[test]
1443 fn does_nothing_when_parachain_head_is_too_large() {
1444 let (state_root, proof, parachains) =
1445 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![
1446 (1, head_data(1, 5)),
1447 (4, big_head_data(1, 5)),
1448 ]);
1449 run_test(|| {
1450 initialize(state_root);
1452 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1453 RuntimeOrigin::signed(1),
1454 (0, test_relay_header(0, state_root).hash()),
1455 parachains,
1456 proof,
1457 );
1458 assert_ok!(result);
1459 assert_eq!(
1460 ParasInfo::<TestRuntime>::get(ParaId(1)),
1461 Some(ParaInfo {
1462 best_head_hash: BestParaHeadHash {
1463 at_relay_block_number: 0,
1464 head_hash: head_data(1, 5).hash()
1465 },
1466 next_imported_hash_position: 1,
1467 })
1468 );
1469 assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(4)), None);
1470 assert_eq!(
1471 System::<TestRuntime>::events(),
1472 vec![
1473 EventRecord {
1474 phase: Phase::Initialization,
1475 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1476 parachain: ParaId(1),
1477 parachain_head_hash: head_data(1, 5).hash(),
1478 }),
1479 topics: vec![],
1480 },
1481 EventRecord {
1482 phase: Phase::Initialization,
1483 event: TestEvent::Parachains(Event::RejectedLargeParachainHead {
1484 parachain: ParaId(4),
1485 parachain_head_hash: big_head_data(1, 5).hash(),
1486 parachain_head_size: big_stored_head_data(1, 5).encoded_size() as u32,
1487 }),
1488 topics: vec![],
1489 },
1490 ],
1491 );
1492 });
1493 }
1494
1495 #[test]
1496 fn prunes_old_heads() {
1497 run_test(|| {
1498 let heads_to_keep = crate::mock::HeadsToKeep::get();
1499
1500 for i in 0..heads_to_keep {
1502 let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1503 RegularParachainHeader,
1504 >(vec![(1, head_data(1, i))]);
1505 if i == 0 {
1506 initialize(state_root);
1507 } else {
1508 proceed(i, state_root);
1509 }
1510
1511 let expected_weight = weight_of_import_parachain_1_head(&proof, false);
1512 let result = import_parachain_1_head(i, state_root, parachains, proof);
1513 assert_ok!(result);
1514 assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight));
1515 }
1516
1517 for i in 0..heads_to_keep {
1519 assert!(ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, i).hash())
1520 .is_some());
1521 }
1522
1523 let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1525 RegularParachainHeader,
1526 >(vec![(1, head_data(1, heads_to_keep))]);
1527 proceed(heads_to_keep, state_root);
1528 let expected_weight = weight_of_import_parachain_1_head(&proof, true);
1529 let result = import_parachain_1_head(heads_to_keep, state_root, parachains, proof);
1530 assert_ok!(result);
1531 assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight));
1532
1533 assert!(
1535 ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 0).hash()).is_none()
1536 );
1537 for i in 1..=heads_to_keep {
1538 assert!(ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, i).hash())
1539 .is_some());
1540 }
1541 });
1542 }
1543
1544 #[test]
1545 fn fails_on_unknown_relay_chain_block() {
1546 let (state_root, proof, parachains) =
1547 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 5))]);
1548 run_test(|| {
1549 initialize(state_root);
1551
1552 assert_noop!(
1554 import_parachain_1_head(1, state_root, parachains, proof),
1555 Error::<TestRuntime>::UnknownRelayChainBlock
1556 );
1557 });
1558 }
1559
1560 #[test]
1561 fn fails_on_invalid_storage_proof() {
1562 let (_state_root, proof, parachains) =
1563 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 5))]);
1564 run_test(|| {
1565 initialize(Default::default());
1567
1568 assert_noop!(
1570 import_parachain_1_head(0, Default::default(), parachains, proof),
1571 Error::<TestRuntime>::HeaderChainStorageProof(HeaderChainError::StorageProof(
1572 StorageProofError::StorageRootMismatch
1573 ))
1574 );
1575 });
1576 }
1577
1578 #[test]
1579 fn is_not_rewriting_existing_head_if_failed_to_read_updated_head() {
1580 let (state_root_5, proof_5, parachains_5) =
1581 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 5))]);
1582 let (state_root_10_at_20, proof_10_at_20, parachains_10_at_20) =
1583 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(2, head_data(2, 10))]);
1584 let (state_root_10_at_30, proof_10_at_30, parachains_10_at_30) =
1585 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 10))]);
1586 run_test(|| {
1587 initialize(state_root_5);
1589 import_parachain_1_head(0, state_root_5, parachains_5, proof_5).expect("ok");
1590 assert_eq!(
1591 Pallet::<TestRuntime>::best_parachain_head(ParaId(1)),
1592 Some(stored_head_data(1, 5))
1593 );
1594
1595 proceed(20, state_root_10_at_20);
1600 assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1601 RuntimeOrigin::signed(1),
1602 (20, test_relay_header(20, state_root_10_at_20).hash()),
1603 parachains_10_at_20,
1604 proof_10_at_20,
1605 ),);
1606 assert_eq!(
1607 Pallet::<TestRuntime>::best_parachain_head(ParaId(1)),
1608 Some(stored_head_data(1, 5))
1609 );
1610
1611 proceed(30, state_root_10_at_30);
1616 assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1617 RuntimeOrigin::signed(1),
1618 (30, test_relay_header(30, state_root_10_at_30).hash()),
1619 parachains_10_at_30,
1620 proof_10_at_30,
1621 ),);
1622 assert_eq!(
1623 Pallet::<TestRuntime>::best_parachain_head(ParaId(1)),
1624 Some(stored_head_data(1, 10))
1625 );
1626 });
1627 }
1628
1629 #[test]
1630 fn storage_keys_computed_properly() {
1631 assert_eq!(
1632 ParasInfo::<TestRuntime>::storage_map_final_key(ParaId(42)).to_vec(),
1633 ParasInfoKeyProvider::final_key("Parachains", &ParaId(42)).0
1634 );
1635
1636 assert_eq!(
1637 ImportedParaHeads::<TestRuntime>::storage_double_map_final_key(
1638 ParaId(42),
1639 ParaHash::from([21u8; 32])
1640 )
1641 .to_vec(),
1642 ImportedParaHeadsKeyProvider::final_key(
1643 "Parachains",
1644 &ParaId(42),
1645 &ParaHash::from([21u8; 32])
1646 )
1647 .0,
1648 );
1649 }
1650
1651 #[test]
1652 fn ignores_parachain_head_if_it_is_missing_from_storage_proof() {
1653 let (state_root, proof, _) =
1654 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![]);
1655 let parachains = vec![(ParaId(2), Default::default())];
1656 run_test(|| {
1657 initialize(state_root);
1658 assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1659 RuntimeOrigin::signed(1),
1660 (0, test_relay_header(0, state_root).hash()),
1661 parachains,
1662 proof,
1663 ));
1664 assert_eq!(
1665 System::<TestRuntime>::events(),
1666 vec![EventRecord {
1667 phase: Phase::Initialization,
1668 event: TestEvent::Parachains(Event::MissingParachainHead {
1669 parachain: ParaId(2),
1670 }),
1671 topics: vec![],
1672 }],
1673 );
1674 });
1675 }
1676
1677 #[test]
1678 fn ignores_parachain_head_if_parachain_head_hash_is_wrong() {
1679 let (state_root, proof, _) =
1680 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
1681 let parachains = vec![(ParaId(1), head_data(1, 10).hash())];
1682 run_test(|| {
1683 initialize(state_root);
1684 assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1685 RuntimeOrigin::signed(1),
1686 (0, test_relay_header(0, state_root).hash()),
1687 parachains,
1688 proof,
1689 ));
1690 assert_eq!(
1691 System::<TestRuntime>::events(),
1692 vec![EventRecord {
1693 phase: Phase::Initialization,
1694 event: TestEvent::Parachains(Event::IncorrectParachainHeadHash {
1695 parachain: ParaId(1),
1696 parachain_head_hash: head_data(1, 10).hash(),
1697 actual_parachain_head_hash: head_data(1, 0).hash(),
1698 }),
1699 topics: vec![],
1700 }],
1701 );
1702 });
1703 }
1704
1705 #[test]
1706 fn test_bridge_parachain_call_is_correctly_defined() {
1707 let (state_root, proof, _) =
1708 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
1709 let parachains = vec![(ParaId(2), Default::default())];
1710 let relay_header_id = (0, test_relay_header(0, state_root).hash());
1711
1712 let direct_submit_parachain_heads_call = Call::<TestRuntime>::submit_parachain_heads {
1713 at_relay_block: relay_header_id,
1714 parachains: parachains.clone(),
1715 parachain_heads_proof: proof.clone(),
1716 };
1717 let indirect_submit_parachain_heads_call = BridgeParachainCall::submit_parachain_heads {
1718 at_relay_block: relay_header_id,
1719 parachains,
1720 parachain_heads_proof: proof,
1721 };
1722 assert_eq!(
1723 direct_submit_parachain_heads_call.encode(),
1724 indirect_submit_parachain_heads_call.encode()
1725 );
1726 }
1727
1728 generate_owned_bridge_module_tests!(BasicOperatingMode::Normal, BasicOperatingMode::Halted);
1729
1730 #[test]
1731 fn maybe_max_parachains_returns_correct_value() {
1732 assert_eq!(MaybeMaxParachains::<TestRuntime, ()>::get(), Some(mock::TOTAL_PARACHAINS));
1733 }
1734
1735 #[test]
1736 fn maybe_max_total_parachain_hashes_returns_correct_value() {
1737 assert_eq!(
1738 MaybeMaxTotalParachainHashes::<TestRuntime, ()>::get(),
1739 Some(mock::TOTAL_PARACHAINS * mock::HeadsToKeep::get()),
1740 );
1741 }
1742
1743 #[test]
1744 fn submit_finality_proof_requires_signed_origin() {
1745 run_test(|| {
1746 let (state_root, proof, parachains) =
1747 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
1748
1749 initialize(state_root);
1750
1751 assert_noop!(
1753 Pallet::<TestRuntime>::submit_parachain_heads(
1754 RuntimeOrigin::root(),
1755 (0, test_relay_header(0, state_root).hash()),
1756 parachains,
1757 proof,
1758 ),
1759 DispatchError::BadOrigin
1760 );
1761 })
1762 }
1763
1764 #[test]
1765 fn may_be_free_for_submitting_filtered_heads() {
1766 run_test(|| {
1767 let (state_root, proof, parachains) =
1768 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(2, head_data(2, 5))]);
1769 initialize(state_root);
1771 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1773 RuntimeOrigin::signed(1),
1774 (0, test_relay_header(0, state_root).hash()),
1775 parachains.clone(),
1776 proof.clone(),
1777 );
1778 assert_eq!(result.unwrap().pays_fee, Pays::No);
1779 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1781 RuntimeOrigin::signed(1),
1782 (0, test_relay_header(0, state_root).hash()),
1783 parachains,
1784 proof,
1785 );
1786 assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1787 let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1789 RegularParachainHeader,
1790 >(vec![(2, head_data(2, 50))]);
1791 let relay_block_number = FreeHeadersInterval::get() - 1;
1792 proceed(relay_block_number, state_root);
1793 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1794 RuntimeOrigin::signed(1),
1795 (relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
1796 parachains,
1797 proof,
1798 );
1799 assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1800 let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1802 RegularParachainHeader,
1803 >(vec![(2, head_data(2, 100))]);
1804 let relay_block_number = relay_block_number + FreeHeadersInterval::get();
1805 proceed(relay_block_number, state_root);
1806 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1807 RuntimeOrigin::signed(1),
1808 (relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
1809 parachains,
1810 proof,
1811 );
1812 assert_eq!(result.unwrap().pays_fee, Pays::No);
1813 let mut large_head = head_data(2, 100);
1816 large_head.0.extend(&[42u8; BigParachain::MAX_HEADER_SIZE as _]);
1817 let (state_root, proof, parachains) =
1818 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(2, large_head)]);
1819 let relay_block_number = relay_block_number + FreeHeadersInterval::get();
1820 proceed(relay_block_number, state_root);
1821 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1822 RuntimeOrigin::signed(1),
1823 (relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
1824 parachains,
1825 proof,
1826 );
1827 assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1828 })
1829 }
1830
1831 #[test]
1832 fn grandpa_and_parachain_pallets_share_free_headers_counter() {
1833 run_test(|| {
1834 initialize(Default::default());
1835 let mut free_headers_remaining = 4;
1837 pallet_bridge_grandpa::FreeHeadersRemaining::<TestRuntime, BridgesGrandpaPalletInstance>::set(
1838 Some(free_headers_remaining),
1839 );
1840 let mut relay_block_number = 0;
1842 for i in 0..2 {
1843 let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1845 RegularParachainHeader,
1846 >(vec![(2, head_data(2, 5 + i))]);
1847 relay_block_number = relay_block_number + FreeHeadersInterval::get();
1848 proceed(relay_block_number, state_root);
1849 assert_eq!(
1850 pallet_bridge_grandpa::FreeHeadersRemaining::<
1851 TestRuntime,
1852 BridgesGrandpaPalletInstance,
1853 >::get(),
1854 Some(free_headers_remaining - 1),
1855 );
1856 free_headers_remaining = free_headers_remaining - 1;
1857 assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1859 RuntimeOrigin::signed(1),
1860 (relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
1861 parachains,
1862 proof,
1863 ),);
1864 assert_eq!(
1865 pallet_bridge_grandpa::FreeHeadersRemaining::<
1866 TestRuntime,
1867 BridgesGrandpaPalletInstance,
1868 >::get(),
1869 Some(free_headers_remaining - 1),
1870 );
1871 free_headers_remaining = free_headers_remaining - 1;
1872 }
1873 let (state_root, proof, parachains) =
1875 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(2, head_data(2, 7))]);
1876 relay_block_number = relay_block_number + FreeHeadersInterval::get();
1877 let result = pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::submit_finality_proof_ex(
1878 RuntimeOrigin::signed(1),
1879 Box::new(test_relay_header(relay_block_number, state_root)),
1880 make_default_justification(&test_relay_header(relay_block_number, state_root)),
1881 TEST_GRANDPA_SET_ID,
1882 false,
1883 );
1884 assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1885 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1887 RuntimeOrigin::signed(1),
1888 (relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
1889 parachains,
1890 proof,
1891 );
1892 assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1893 assert_eq!(
1894 pallet_bridge_grandpa::FreeHeadersRemaining::<
1895 TestRuntime,
1896 BridgesGrandpaPalletInstance,
1897 >::get(),
1898 Some(0),
1899 );
1900 });
1901 }
1902}