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