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 log::trace!(
465 target: LOG_TARGET,
466 "The head of parachain {:?} is None. {}",
467 parachain,
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 log::trace!(
479 target: LOG_TARGET,
480 "The read of head of parachain {:?} has failed: {:?}",
481 parachain,
482 e,
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 log::trace!(
494 target: LOG_TARGET,
495 "The submitter has specified invalid parachain {:?} head hash: \
496 {:?} vs {:?}",
497 parachain,
498 parachain_head_hash,
499 actual_parachain_head_hash,
500 );
501 Self::deposit_event(Event::IncorrectParachainHeadHash {
502 parachain,
503 parachain_head_hash,
504 actual_parachain_head_hash,
505 });
506 continue
507 }
508
509 let parachain_head_size = parachain_head.0.len();
511 let parachain_head_data =
512 match T::ParaStoredHeaderDataBuilder::try_build(parachain, ¶chain_head) {
513 Some(parachain_head_data) => parachain_head_data,
514 None => {
515 log::trace!(
516 target: LOG_TARGET,
517 "The head of parachain {:?} has been provided, but it is not tracked by the pallet",
518 parachain,
519 );
520 Self::deposit_event(Event::UntrackedParachainRejected { parachain });
521 continue
522 },
523 };
524
525 let update_result: Result<_, ()> =
526 ParasInfo::<T, I>::try_mutate(parachain, |stored_best_head| {
527 let is_free = parachain_head_size <
528 T::ParaStoredHeaderDataBuilder::max_free_head_size() as usize &&
529 match stored_best_head {
530 Some(ref best_head)
531 if at_relay_block.0.saturating_sub(
532 best_head.best_head_hash.at_relay_block_number,
533 ) >= free_headers_interval =>
534 true,
535 Some(_) => false,
536 None => true,
537 };
538 let artifacts = Pallet::<T, I>::update_parachain_head(
539 parachain,
540 stored_best_head.take(),
541 HeaderId(relay_block_number, relay_block_hash),
542 parachain_head_data,
543 parachain_head_hash,
544 parachain_head,
545 )?;
546
547 if is_free {
548 free_parachain_heads = free_parachain_heads + 1;
549 }
550
551 *stored_best_head = Some(artifacts.best_head);
552 Ok(artifacts.prune_happened)
553 });
554
555 let is_update_happened = update_result.is_ok();
557 if !is_update_happened {
558 actual_weight = actual_weight.saturating_sub(
559 WeightInfoOf::<T, I>::parachain_head_storage_write_weight(
560 T::DbWeight::get(),
561 ),
562 );
563 }
564 let is_prune_happened = matches!(update_result, Ok(true));
565 if !is_prune_happened {
566 actual_weight = actual_weight.saturating_sub(
567 WeightInfoOf::<T, I>::parachain_head_pruning_weight(T::DbWeight::get()),
568 );
569 }
570 }
571
572 storage.ensure_no_unused_keys().map_err(|e| {
578 Error::<T, I>::HeaderChainStorageProof(HeaderChainError::StorageProof(e))
579 })?;
580
581 let is_free = total_parachains == 1
583 && free_parachain_heads == total_parachains
584 && SubmitFinalityProofHelper::<T, T::BridgesGrandpaPalletInstance>::has_free_header_slots();
585 let pays_fee = if is_free {
586 log::trace!(target: LOG_TARGET, "Parachain heads update transaction is free");
587 pallet_bridge_grandpa::on_free_header_imported::<T, T::BridgesGrandpaPalletInstance>(
588 );
589 Pays::No
590 } else {
591 log::trace!(target: LOG_TARGET, "Parachain heads update transaction is paid");
592 Pays::Yes
593 };
594
595 Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee })
596 }
597 }
598
599 impl<T: Config<I>, I: 'static> Pallet<T, I> {
600 pub fn best_parachain_info(parachain: ParaId) -> Option<ParaInfo> {
602 ParasInfo::<T, I>::get(parachain)
603 }
604
605 pub fn best_parachain_head(parachain: ParaId) -> Option<ParaStoredHeaderData> {
607 let best_para_head_hash = ParasInfo::<T, I>::get(parachain)?.best_head_hash.head_hash;
608 ImportedParaHeads::<T, I>::get(parachain, best_para_head_hash).map(|h| h.into_inner())
609 }
610
611 pub fn best_parachain_head_hash(parachain: ParaId) -> Option<ParaHash> {
613 Some(ParasInfo::<T, I>::get(parachain)?.best_head_hash.head_hash)
614 }
615
616 pub fn best_parachain_head_id<C: Chain<Hash = ParaHash> + Parachain>(
618 ) -> Result<Option<HeaderIdOf<C>>, codec::Error> {
619 let parachain = ParaId(C::PARACHAIN_ID);
620 let best_head_hash = match Self::best_parachain_head_hash(parachain) {
621 Some(best_head_hash) => best_head_hash,
622 None => return Ok(None),
623 };
624 let encoded_head = match Self::parachain_head(parachain, best_head_hash) {
625 Some(encoded_head) => encoded_head,
626 None => return Ok(None),
627 };
628 encoded_head
629 .decode_parachain_head_data::<C>()
630 .map(|data| Some(HeaderId(data.number, best_head_hash)))
631 }
632
633 pub fn parachain_head(parachain: ParaId, hash: ParaHash) -> Option<ParaStoredHeaderData> {
635 ImportedParaHeads::<T, I>::get(parachain, hash).map(|h| h.into_inner())
636 }
637
638 pub(super) fn update_parachain_head(
640 parachain: ParaId,
641 stored_best_head: Option<ParaInfo>,
642 new_at_relay_block: HeaderId<RelayBlockHash, RelayBlockNumber>,
643 new_head_data: ParaStoredHeaderData,
644 new_head_hash: ParaHash,
645 new_head: ParaHead,
646 ) -> Result<UpdateParachainHeadArtifacts, ()> {
647 let update = SubmitParachainHeadsInfo {
650 at_relay_block: new_at_relay_block,
651 para_id: parachain,
652 para_head_hash: new_head_hash,
653 is_free_execution_expected: false,
655 };
656 if SubmitParachainHeadsHelper::<T, I>::check_obsolete(&update).is_err() {
657 Self::deposit_event(Event::RejectedObsoleteParachainHead {
658 parachain,
659 parachain_head_hash: new_head_hash,
660 });
661 return Err(())
662 }
663
664 let updated_head_data =
666 match StoredParaHeadDataOf::<T, I>::try_from_inner(new_head_data) {
667 Ok(updated_head_data) => updated_head_data,
668 Err(e) => {
669 log::trace!(
670 target: LOG_TARGET,
671 "The parachain head can't be updated. The parachain head data size \
672 for {:?} is {}. It exceeds maximal configured size {}.",
673 parachain,
674 e.value_size,
675 e.maximal_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 log::trace!(
709 target: LOG_TARGET,
710 "Updated head of parachain {:?} to {} at relay block {}",
711 parachain,
712 new_head_hash,
713 new_at_relay_block.0,
714 );
715
716 T::OnNewHead::on_new_head(parachain, &new_head);
718
719 let prune_happened = head_hash_to_prune.is_ok();
721 if let Ok(head_hash_to_prune) = head_hash_to_prune {
722 log::trace!(
723 target: LOG_TARGET,
724 "Pruning old head of parachain {:?}: {}",
725 parachain,
726 head_hash_to_prune,
727 );
728 ImportedParaHeads::<T, I>::remove(parachain, head_hash_to_prune);
729 }
730 Self::deposit_event(Event::UpdatedParachainHead {
731 parachain,
732 parachain_head_hash: new_head_hash,
733 });
734
735 Ok(UpdateParachainHeadArtifacts { best_head: updated_best_para_head, prune_happened })
736 }
737 }
738
739 #[pallet::genesis_config]
740 #[derive(DefaultNoBound)]
741 pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
742 pub operating_mode: BasicOperatingMode,
744 pub owner: Option<T::AccountId>,
746 #[serde(skip)]
748 pub _phantom: sp_std::marker::PhantomData<I>,
749 }
750
751 #[pallet::genesis_build]
752 impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
753 fn build(&self) {
754 PalletOperatingMode::<T, I>::put(self.operating_mode);
755 if let Some(ref owner) = self.owner {
756 PalletOwner::<T, I>::put(owner);
757 }
758 }
759 }
760
761 pub struct MaybeMaxParachains<T, I>(PhantomData<(T, I)>);
763
764 impl<T: Config<I>, I: 'static> Get<Option<u32>> for MaybeMaxParachains<T, I> {
765 fn get() -> Option<u32> {
766 Some(T::ParaStoredHeaderDataBuilder::supported_parachains())
767 }
768 }
769
770 pub struct MaybeMaxTotalParachainHashes<T, I>(PhantomData<(T, I)>);
772
773 impl<T: Config<I>, I: 'static> Get<Option<u32>> for MaybeMaxTotalParachainHashes<T, I> {
774 fn get() -> Option<u32> {
775 Some(
776 T::ParaStoredHeaderDataBuilder::supported_parachains()
777 .saturating_mul(T::HeadsToKeep::get()),
778 )
779 }
780 }
781}
782
783pub struct ParachainHeaders<T, I, C>(PhantomData<(T, I, C)>);
785
786impl<T: Config<I>, I: 'static, C: Parachain<Hash = ParaHash>> HeaderChain<C>
787 for ParachainHeaders<T, I, C>
788{
789 fn finalized_header_state_root(hash: HashOf<C>) -> Option<HashOf<C>> {
790 Pallet::<T, I>::parachain_head(ParaId(C::PARACHAIN_ID), hash)
791 .and_then(|head| head.decode_parachain_head_data::<C>().ok())
792 .map(|h| h.state_root)
793 }
794}
795
796#[cfg(feature = "runtime-benchmarks")]
798pub fn initialize_for_benchmarks<T: Config<I>, I: 'static, PC: Parachain<Hash = ParaHash>>(
799 header: HeaderOf<PC>,
800) {
801 use bp_polkadot_core::parachains::ParaHead;
802 use bp_runtime::HeaderIdProvider;
803 use sp_runtime::traits::Header;
804
805 let relay_head =
806 pallet_bridge_grandpa::BridgedHeader::<T, T::BridgesGrandpaPalletInstance>::new(
807 0,
808 Default::default(),
809 Default::default(),
810 Default::default(),
811 Default::default(),
812 );
813 let parachain = ParaId(PC::PARACHAIN_ID);
814 let parachain_head = ParaHead(header.encode());
815 let updated_head_data = T::ParaStoredHeaderDataBuilder::try_build(parachain, ¶chain_head)
816 .expect("failed to build stored parachain head in benchmarks");
817 pallet_bridge_grandpa::initialize_for_benchmarks::<T, T::BridgesGrandpaPalletInstance>(
818 relay_head.clone(),
819 );
820 Pallet::<T, I>::update_parachain_head(
821 parachain,
822 None,
823 relay_head.id(),
824 updated_head_data,
825 parachain_head.hash(),
826 parachain_head,
827 )
828 .expect("failed to insert parachain head in benchmarks");
829}
830
831#[cfg(test)]
832pub(crate) mod tests {
833 use super::*;
834 use crate::mock::{
835 run_test, test_relay_header, BigParachain, BigParachainHeader, FreeHeadersInterval,
836 RegularParachainHasher, RegularParachainHeader, RelayBlockHeader,
837 RuntimeEvent as TestEvent, RuntimeOrigin, TestRuntime, UNTRACKED_PARACHAIN_ID,
838 };
839 use bp_test_utils::prepare_parachain_heads_proof;
840 use codec::Encode;
841
842 use bp_header_chain::{justification::GrandpaJustification, StoredHeaderGrandpaInfo};
843 use bp_parachains::{
844 BestParaHeadHash, BridgeParachainCall, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider,
845 };
846 use bp_polkadot_core::parachains::ParaHead;
847 use bp_runtime::{
848 BasicOperatingMode, OwnedBridgeModuleError, StorageDoubleMapKeyProvider,
849 StorageMapKeyProvider, StorageProofError,
850 };
851 use bp_test_utils::{
852 authority_list, generate_owned_bridge_module_tests, make_default_justification,
853 TEST_GRANDPA_SET_ID,
854 };
855 use frame_support::{
856 assert_noop, assert_ok,
857 dispatch::DispatchResultWithPostInfo,
858 pallet_prelude::Pays,
859 storage::generator::{StorageDoubleMap, StorageMap},
860 traits::Get,
861 weights::Weight,
862 };
863 use frame_system::{EventRecord, Pallet as System, Phase};
864 use sp_core::Hasher;
865 use sp_runtime::{traits::Header as HeaderT, DispatchError};
866
867 type BridgesGrandpaPalletInstance = pallet_bridge_grandpa::Instance1;
868 type WeightInfo = <TestRuntime as Config>::WeightInfo;
869 type DbWeight = <TestRuntime as frame_system::Config>::DbWeight;
870
871 pub(crate) fn initialize(state_root: RelayBlockHash) -> RelayBlockHash {
872 pallet_bridge_grandpa::FreeHeadersRemaining::<TestRuntime, BridgesGrandpaPalletInstance>::set(Some(100));
873 pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::initialize(
874 RuntimeOrigin::root(),
875 bp_header_chain::InitializationData {
876 header: Box::new(test_relay_header(0, state_root)),
877 authority_list: authority_list(),
878 set_id: 1,
879 operating_mode: BasicOperatingMode::Normal,
880 },
881 )
882 .unwrap();
883
884 System::<TestRuntime>::set_block_number(1);
885 System::<TestRuntime>::reset_events();
886
887 test_relay_header(0, state_root).hash()
888 }
889
890 fn proceed(
891 num: RelayBlockNumber,
892 state_root: RelayBlockHash,
893 ) -> (ParaHash, GrandpaJustification<RelayBlockHeader>) {
894 let header = test_relay_header(num, state_root);
895 let hash = header.hash();
896 let justification = make_default_justification(&header);
897 assert_ok!(
898 pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::submit_finality_proof_ex(
899 RuntimeOrigin::signed(1),
900 Box::new(header),
901 justification.clone(),
902 TEST_GRANDPA_SET_ID,
903 false,
904 )
905 );
906
907 (hash, justification)
908 }
909
910 fn initial_best_head(parachain: u32) -> ParaInfo {
911 ParaInfo {
912 best_head_hash: BestParaHeadHash {
913 at_relay_block_number: 0,
914 head_hash: head_data(parachain, 0).hash(),
915 },
916 next_imported_hash_position: 1,
917 }
918 }
919
920 pub(crate) fn head_data(parachain: u32, head_number: u32) -> ParaHead {
921 ParaHead(
922 RegularParachainHeader::new(
923 head_number as _,
924 Default::default(),
925 RegularParachainHasher::hash(&(parachain, head_number).encode()),
926 Default::default(),
927 Default::default(),
928 )
929 .encode(),
930 )
931 }
932
933 fn stored_head_data(parachain: u32, head_number: u32) -> ParaStoredHeaderData {
934 ParaStoredHeaderData(
935 (head_number as u64, RegularParachainHasher::hash(&(parachain, head_number).encode()))
936 .encode(),
937 )
938 }
939
940 fn big_head_data(parachain: u32, head_number: u32) -> ParaHead {
941 ParaHead(
942 BigParachainHeader::new(
943 head_number as _,
944 Default::default(),
945 RegularParachainHasher::hash(&(parachain, head_number).encode()),
946 Default::default(),
947 Default::default(),
948 )
949 .encode(),
950 )
951 }
952
953 fn big_stored_head_data(parachain: u32, head_number: u32) -> ParaStoredHeaderData {
954 ParaStoredHeaderData(
955 (head_number as u128, RegularParachainHasher::hash(&(parachain, head_number).encode()))
956 .encode(),
957 )
958 }
959
960 fn head_hash(parachain: u32, head_number: u32) -> ParaHash {
961 head_data(parachain, head_number).hash()
962 }
963
964 fn import_parachain_1_head(
965 relay_chain_block: RelayBlockNumber,
966 relay_state_root: RelayBlockHash,
967 parachains: Vec<(ParaId, ParaHash)>,
968 proof: ParaHeadsProof,
969 ) -> DispatchResultWithPostInfo {
970 Pallet::<TestRuntime>::submit_parachain_heads(
971 RuntimeOrigin::signed(1),
972 (relay_chain_block, test_relay_header(relay_chain_block, relay_state_root).hash()),
973 parachains,
974 proof,
975 )
976 }
977
978 fn weight_of_import_parachain_1_head(proof: &ParaHeadsProof, prune_expected: bool) -> Weight {
979 let db_weight = <TestRuntime as frame_system::Config>::DbWeight::get();
980 WeightInfoOf::<TestRuntime, ()>::submit_parachain_heads_weight(db_weight, proof, 1)
981 .saturating_sub(if prune_expected {
982 Weight::zero()
983 } else {
984 WeightInfoOf::<TestRuntime, ()>::parachain_head_pruning_weight(db_weight)
985 })
986 }
987
988 #[test]
989 fn submit_parachain_heads_checks_operating_mode() {
990 let (state_root, proof, parachains) =
991 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
992
993 run_test(|| {
994 initialize(state_root);
995
996 PalletOperatingMode::<TestRuntime>::put(BasicOperatingMode::Halted);
998 assert_noop!(
999 Pallet::<TestRuntime>::submit_parachain_heads(
1000 RuntimeOrigin::signed(1),
1001 (0, test_relay_header(0, state_root).hash()),
1002 parachains.clone(),
1003 proof.clone(),
1004 ),
1005 Error::<TestRuntime>::BridgeModule(OwnedBridgeModuleError::Halted)
1006 );
1007
1008 PalletOperatingMode::<TestRuntime>::put(BasicOperatingMode::Normal);
1010 assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1011 RuntimeOrigin::signed(1),
1012 (0, test_relay_header(0, state_root).hash()),
1013 parachains,
1014 proof,
1015 ),);
1016 });
1017 }
1018
1019 #[test]
1020 fn imports_initial_parachain_heads() {
1021 let (state_root, proof, parachains) =
1022 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![
1023 (1, head_data(1, 0)),
1024 (3, head_data(3, 10)),
1025 ]);
1026 run_test(|| {
1027 initialize(state_root);
1028
1029 let expected_weight =
1031 WeightInfo::submit_parachain_heads_weight(DbWeight::get(), &proof, 2);
1032 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1033 RuntimeOrigin::signed(1),
1034 (0, test_relay_header(0, state_root).hash()),
1035 parachains,
1036 proof,
1037 );
1038 assert_ok!(result);
1039 assert_eq!(result.expect("checked above").pays_fee, Pays::Yes);
1040 assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight));
1041
1042 assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
1044 assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(2)), None);
1045 assert_eq!(
1046 ParasInfo::<TestRuntime>::get(ParaId(3)),
1047 Some(ParaInfo {
1048 best_head_hash: BestParaHeadHash {
1049 at_relay_block_number: 0,
1050 head_hash: head_data(3, 10).hash()
1051 },
1052 next_imported_hash_position: 1,
1053 })
1054 );
1055
1056 assert_eq!(
1057 ImportedParaHeads::<TestRuntime>::get(
1058 ParaId(1),
1059 initial_best_head(1).best_head_hash.head_hash
1060 )
1061 .map(|h| h.into_inner()),
1062 Some(stored_head_data(1, 0))
1063 );
1064 assert_eq!(
1065 ImportedParaHeads::<TestRuntime>::get(
1066 ParaId(2),
1067 initial_best_head(2).best_head_hash.head_hash
1068 )
1069 .map(|h| h.into_inner()),
1070 None
1071 );
1072 assert_eq!(
1073 ImportedParaHeads::<TestRuntime>::get(ParaId(3), head_hash(3, 10))
1074 .map(|h| h.into_inner()),
1075 Some(stored_head_data(3, 10))
1076 );
1077
1078 assert_eq!(
1079 System::<TestRuntime>::events(),
1080 vec![
1081 EventRecord {
1082 phase: Phase::Initialization,
1083 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1084 parachain: ParaId(1),
1085 parachain_head_hash: initial_best_head(1).best_head_hash.head_hash,
1086 }),
1087 topics: vec![],
1088 },
1089 EventRecord {
1090 phase: Phase::Initialization,
1091 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1092 parachain: ParaId(3),
1093 parachain_head_hash: head_data(3, 10).hash(),
1094 }),
1095 topics: vec![],
1096 }
1097 ],
1098 );
1099 });
1100 }
1101
1102 #[test]
1103 fn imports_parachain_heads_is_able_to_progress() {
1104 let (state_root_5, proof_5, parachains_5) =
1105 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 5))]);
1106 let (state_root_10, proof_10, parachains_10) =
1107 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 10))]);
1108 run_test(|| {
1109 initialize(state_root_5);
1111 let result = import_parachain_1_head(0, state_root_5, parachains_5, proof_5);
1112 assert_eq!(result.unwrap().pays_fee, Pays::No);
1114 assert_eq!(
1115 ParasInfo::<TestRuntime>::get(ParaId(1)),
1116 Some(ParaInfo {
1117 best_head_hash: BestParaHeadHash {
1118 at_relay_block_number: 0,
1119 head_hash: head_data(1, 5).hash()
1120 },
1121 next_imported_hash_position: 1,
1122 })
1123 );
1124 assert_eq!(
1125 ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 5).hash())
1126 .map(|h| h.into_inner()),
1127 Some(stored_head_data(1, 5))
1128 );
1129 assert_eq!(
1130 ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 10).hash())
1131 .map(|h| h.into_inner()),
1132 None
1133 );
1134 assert_eq!(
1135 System::<TestRuntime>::events(),
1136 vec![EventRecord {
1137 phase: Phase::Initialization,
1138 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1139 parachain: ParaId(1),
1140 parachain_head_hash: head_data(1, 5).hash(),
1141 }),
1142 topics: vec![],
1143 }],
1144 );
1145
1146 let (relay_1_hash, justification) = proceed(1, state_root_10);
1148 let result = import_parachain_1_head(1, state_root_10, parachains_10, proof_10);
1149 assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1151 assert_eq!(
1152 ParasInfo::<TestRuntime>::get(ParaId(1)),
1153 Some(ParaInfo {
1154 best_head_hash: BestParaHeadHash {
1155 at_relay_block_number: 1,
1156 head_hash: head_data(1, 10).hash()
1157 },
1158 next_imported_hash_position: 2,
1159 })
1160 );
1161 assert_eq!(
1162 ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 5).hash())
1163 .map(|h| h.into_inner()),
1164 Some(stored_head_data(1, 5))
1165 );
1166 assert_eq!(
1167 ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 10).hash())
1168 .map(|h| h.into_inner()),
1169 Some(stored_head_data(1, 10))
1170 );
1171 assert_eq!(
1172 System::<TestRuntime>::events(),
1173 vec![
1174 EventRecord {
1175 phase: Phase::Initialization,
1176 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1177 parachain: ParaId(1),
1178 parachain_head_hash: head_data(1, 5).hash(),
1179 }),
1180 topics: vec![],
1181 },
1182 EventRecord {
1183 phase: Phase::Initialization,
1184 event: TestEvent::Grandpa1(
1185 pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
1186 number: 1,
1187 hash: relay_1_hash,
1188 grandpa_info: StoredHeaderGrandpaInfo {
1189 finality_proof: justification,
1190 new_verification_context: None,
1191 },
1192 }
1193 ),
1194 topics: vec![],
1195 },
1196 EventRecord {
1197 phase: Phase::Initialization,
1198 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1199 parachain: ParaId(1),
1200 parachain_head_hash: head_data(1, 10).hash(),
1201 }),
1202 topics: vec![],
1203 }
1204 ],
1205 );
1206 });
1207 }
1208
1209 #[test]
1210 fn ignores_untracked_parachain() {
1211 let (state_root, proof, parachains) =
1212 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![
1213 (1, head_data(1, 5)),
1214 (UNTRACKED_PARACHAIN_ID, head_data(1, 5)),
1215 (2, head_data(1, 5)),
1216 ]);
1217 run_test(|| {
1218 let expected_weight =
1221 WeightInfo::submit_parachain_heads_weight(DbWeight::get(), &proof, 3)
1222 .saturating_sub(WeightInfo::parachain_head_storage_write_weight(
1223 DbWeight::get(),
1224 ));
1225 initialize(state_root);
1226 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1227 RuntimeOrigin::signed(1),
1228 (0, test_relay_header(0, state_root).hash()),
1229 parachains,
1230 proof,
1231 );
1232 assert_ok!(result);
1233 assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight));
1234 assert_eq!(
1235 ParasInfo::<TestRuntime>::get(ParaId(1)),
1236 Some(ParaInfo {
1237 best_head_hash: BestParaHeadHash {
1238 at_relay_block_number: 0,
1239 head_hash: head_data(1, 5).hash()
1240 },
1241 next_imported_hash_position: 1,
1242 })
1243 );
1244 assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(UNTRACKED_PARACHAIN_ID)), None,);
1245 assert_eq!(
1246 ParasInfo::<TestRuntime>::get(ParaId(2)),
1247 Some(ParaInfo {
1248 best_head_hash: BestParaHeadHash {
1249 at_relay_block_number: 0,
1250 head_hash: head_data(1, 5).hash()
1251 },
1252 next_imported_hash_position: 1,
1253 })
1254 );
1255 assert_eq!(
1256 System::<TestRuntime>::events(),
1257 vec![
1258 EventRecord {
1259 phase: Phase::Initialization,
1260 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1261 parachain: ParaId(1),
1262 parachain_head_hash: head_data(1, 5).hash(),
1263 }),
1264 topics: vec![],
1265 },
1266 EventRecord {
1267 phase: Phase::Initialization,
1268 event: TestEvent::Parachains(Event::UntrackedParachainRejected {
1269 parachain: ParaId(UNTRACKED_PARACHAIN_ID),
1270 }),
1271 topics: vec![],
1272 },
1273 EventRecord {
1274 phase: Phase::Initialization,
1275 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1276 parachain: ParaId(2),
1277 parachain_head_hash: head_data(1, 5).hash(),
1278 }),
1279 topics: vec![],
1280 }
1281 ],
1282 );
1283 });
1284 }
1285
1286 #[test]
1287 fn does_nothing_when_already_imported_this_head_at_previous_relay_header() {
1288 let (state_root, proof, parachains) =
1289 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
1290 run_test(|| {
1291 initialize(state_root);
1293 assert_ok!(import_parachain_1_head(0, state_root, parachains.clone(), proof.clone()));
1294 assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
1295 assert_eq!(
1296 System::<TestRuntime>::events(),
1297 vec![EventRecord {
1298 phase: Phase::Initialization,
1299 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1300 parachain: ParaId(1),
1301 parachain_head_hash: initial_best_head(1).best_head_hash.head_hash,
1302 }),
1303 topics: vec![],
1304 }],
1305 );
1306
1307 let (relay_1_hash, justification) = proceed(1, state_root);
1310 assert_ok!(import_parachain_1_head(1, state_root, parachains, proof));
1311 assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
1312 assert_eq!(
1313 System::<TestRuntime>::events(),
1314 vec![
1315 EventRecord {
1316 phase: Phase::Initialization,
1317 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1318 parachain: ParaId(1),
1319 parachain_head_hash: initial_best_head(1).best_head_hash.head_hash,
1320 }),
1321 topics: vec![],
1322 },
1323 EventRecord {
1324 phase: Phase::Initialization,
1325 event: TestEvent::Grandpa1(
1326 pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
1327 number: 1,
1328 hash: relay_1_hash,
1329 grandpa_info: StoredHeaderGrandpaInfo {
1330 finality_proof: justification,
1331 new_verification_context: None,
1332 }
1333 }
1334 ),
1335 topics: vec![],
1336 },
1337 EventRecord {
1338 phase: Phase::Initialization,
1339 event: TestEvent::Parachains(Event::RejectedObsoleteParachainHead {
1340 parachain: ParaId(1),
1341 parachain_head_hash: initial_best_head(1).best_head_hash.head_hash,
1342 }),
1343 topics: vec![],
1344 }
1345 ],
1346 );
1347 });
1348 }
1349
1350 #[test]
1351 fn does_nothing_when_already_imported_head_at_better_relay_header() {
1352 let (state_root_5, proof_5, parachains_5) =
1353 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 5))]);
1354 let (state_root_10, proof_10, parachains_10) =
1355 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 10))]);
1356 run_test(|| {
1357 initialize(state_root_5);
1359
1360 let (relay_1_hash, justification) = proceed(1, state_root_10);
1362 assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10));
1363 assert_eq!(
1364 ParasInfo::<TestRuntime>::get(ParaId(1)),
1365 Some(ParaInfo {
1366 best_head_hash: BestParaHeadHash {
1367 at_relay_block_number: 1,
1368 head_hash: head_data(1, 10).hash()
1369 },
1370 next_imported_hash_position: 1,
1371 })
1372 );
1373 assert_eq!(
1374 System::<TestRuntime>::events(),
1375 vec![
1376 EventRecord {
1377 phase: Phase::Initialization,
1378 event: TestEvent::Grandpa1(
1379 pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
1380 number: 1,
1381 hash: relay_1_hash,
1382 grandpa_info: StoredHeaderGrandpaInfo {
1383 finality_proof: justification.clone(),
1384 new_verification_context: None,
1385 }
1386 }
1387 ),
1388 topics: vec![],
1389 },
1390 EventRecord {
1391 phase: Phase::Initialization,
1392 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1393 parachain: ParaId(1),
1394 parachain_head_hash: head_data(1, 10).hash(),
1395 }),
1396 topics: vec![],
1397 }
1398 ],
1399 );
1400
1401 assert_ok!(import_parachain_1_head(0, state_root_5, parachains_5, proof_5));
1404 assert_eq!(
1405 ParasInfo::<TestRuntime>::get(ParaId(1)),
1406 Some(ParaInfo {
1407 best_head_hash: BestParaHeadHash {
1408 at_relay_block_number: 1,
1409 head_hash: head_data(1, 10).hash()
1410 },
1411 next_imported_hash_position: 1,
1412 })
1413 );
1414 assert_eq!(
1415 System::<TestRuntime>::events(),
1416 vec![
1417 EventRecord {
1418 phase: Phase::Initialization,
1419 event: TestEvent::Grandpa1(
1420 pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
1421 number: 1,
1422 hash: relay_1_hash,
1423 grandpa_info: StoredHeaderGrandpaInfo {
1424 finality_proof: justification,
1425 new_verification_context: None,
1426 }
1427 }
1428 ),
1429 topics: vec![],
1430 },
1431 EventRecord {
1432 phase: Phase::Initialization,
1433 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1434 parachain: ParaId(1),
1435 parachain_head_hash: head_data(1, 10).hash(),
1436 }),
1437 topics: vec![],
1438 },
1439 EventRecord {
1440 phase: Phase::Initialization,
1441 event: TestEvent::Parachains(Event::RejectedObsoleteParachainHead {
1442 parachain: ParaId(1),
1443 parachain_head_hash: head_data(1, 5).hash(),
1444 }),
1445 topics: vec![],
1446 }
1447 ],
1448 );
1449 });
1450 }
1451
1452 #[test]
1453 fn does_nothing_when_parachain_head_is_too_large() {
1454 let (state_root, proof, parachains) =
1455 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![
1456 (1, head_data(1, 5)),
1457 (4, big_head_data(1, 5)),
1458 ]);
1459 run_test(|| {
1460 initialize(state_root);
1462 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1463 RuntimeOrigin::signed(1),
1464 (0, test_relay_header(0, state_root).hash()),
1465 parachains,
1466 proof,
1467 );
1468 assert_ok!(result);
1469 assert_eq!(
1470 ParasInfo::<TestRuntime>::get(ParaId(1)),
1471 Some(ParaInfo {
1472 best_head_hash: BestParaHeadHash {
1473 at_relay_block_number: 0,
1474 head_hash: head_data(1, 5).hash()
1475 },
1476 next_imported_hash_position: 1,
1477 })
1478 );
1479 assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(4)), None);
1480 assert_eq!(
1481 System::<TestRuntime>::events(),
1482 vec![
1483 EventRecord {
1484 phase: Phase::Initialization,
1485 event: TestEvent::Parachains(Event::UpdatedParachainHead {
1486 parachain: ParaId(1),
1487 parachain_head_hash: head_data(1, 5).hash(),
1488 }),
1489 topics: vec![],
1490 },
1491 EventRecord {
1492 phase: Phase::Initialization,
1493 event: TestEvent::Parachains(Event::RejectedLargeParachainHead {
1494 parachain: ParaId(4),
1495 parachain_head_hash: big_head_data(1, 5).hash(),
1496 parachain_head_size: big_stored_head_data(1, 5).encoded_size() as u32,
1497 }),
1498 topics: vec![],
1499 },
1500 ],
1501 );
1502 });
1503 }
1504
1505 #[test]
1506 fn prunes_old_heads() {
1507 run_test(|| {
1508 let heads_to_keep = crate::mock::HeadsToKeep::get();
1509
1510 for i in 0..heads_to_keep {
1512 let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1513 RegularParachainHeader,
1514 >(vec![(1, head_data(1, i))]);
1515 if i == 0 {
1516 initialize(state_root);
1517 } else {
1518 proceed(i, state_root);
1519 }
1520
1521 let expected_weight = weight_of_import_parachain_1_head(&proof, false);
1522 let result = import_parachain_1_head(i, state_root, parachains, proof);
1523 assert_ok!(result);
1524 assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight));
1525 }
1526
1527 for i in 0..heads_to_keep {
1529 assert!(ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, i).hash())
1530 .is_some());
1531 }
1532
1533 let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1535 RegularParachainHeader,
1536 >(vec![(1, head_data(1, heads_to_keep))]);
1537 proceed(heads_to_keep, state_root);
1538 let expected_weight = weight_of_import_parachain_1_head(&proof, true);
1539 let result = import_parachain_1_head(heads_to_keep, state_root, parachains, proof);
1540 assert_ok!(result);
1541 assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight));
1542
1543 assert!(
1545 ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 0).hash()).is_none()
1546 );
1547 for i in 1..=heads_to_keep {
1548 assert!(ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, i).hash())
1549 .is_some());
1550 }
1551 });
1552 }
1553
1554 #[test]
1555 fn fails_on_unknown_relay_chain_block() {
1556 let (state_root, proof, parachains) =
1557 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 5))]);
1558 run_test(|| {
1559 initialize(state_root);
1561
1562 assert_noop!(
1564 import_parachain_1_head(1, state_root, parachains, proof),
1565 Error::<TestRuntime>::UnknownRelayChainBlock
1566 );
1567 });
1568 }
1569
1570 #[test]
1571 fn fails_on_invalid_storage_proof() {
1572 let (_state_root, proof, parachains) =
1573 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 5))]);
1574 run_test(|| {
1575 initialize(Default::default());
1577
1578 assert_noop!(
1580 import_parachain_1_head(0, Default::default(), parachains, proof),
1581 Error::<TestRuntime>::HeaderChainStorageProof(HeaderChainError::StorageProof(
1582 StorageProofError::StorageRootMismatch
1583 ))
1584 );
1585 });
1586 }
1587
1588 #[test]
1589 fn is_not_rewriting_existing_head_if_failed_to_read_updated_head() {
1590 let (state_root_5, proof_5, parachains_5) =
1591 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 5))]);
1592 let (state_root_10_at_20, proof_10_at_20, parachains_10_at_20) =
1593 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(2, head_data(2, 10))]);
1594 let (state_root_10_at_30, proof_10_at_30, parachains_10_at_30) =
1595 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 10))]);
1596 run_test(|| {
1597 initialize(state_root_5);
1599 import_parachain_1_head(0, state_root_5, parachains_5, proof_5).expect("ok");
1600 assert_eq!(
1601 Pallet::<TestRuntime>::best_parachain_head(ParaId(1)),
1602 Some(stored_head_data(1, 5))
1603 );
1604
1605 proceed(20, state_root_10_at_20);
1610 assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1611 RuntimeOrigin::signed(1),
1612 (20, test_relay_header(20, state_root_10_at_20).hash()),
1613 parachains_10_at_20,
1614 proof_10_at_20,
1615 ),);
1616 assert_eq!(
1617 Pallet::<TestRuntime>::best_parachain_head(ParaId(1)),
1618 Some(stored_head_data(1, 5))
1619 );
1620
1621 proceed(30, state_root_10_at_30);
1626 assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1627 RuntimeOrigin::signed(1),
1628 (30, test_relay_header(30, state_root_10_at_30).hash()),
1629 parachains_10_at_30,
1630 proof_10_at_30,
1631 ),);
1632 assert_eq!(
1633 Pallet::<TestRuntime>::best_parachain_head(ParaId(1)),
1634 Some(stored_head_data(1, 10))
1635 );
1636 });
1637 }
1638
1639 #[test]
1640 fn storage_keys_computed_properly() {
1641 assert_eq!(
1642 ParasInfo::<TestRuntime>::storage_map_final_key(ParaId(42)).to_vec(),
1643 ParasInfoKeyProvider::final_key("Parachains", &ParaId(42)).0
1644 );
1645
1646 assert_eq!(
1647 ImportedParaHeads::<TestRuntime>::storage_double_map_final_key(
1648 ParaId(42),
1649 ParaHash::from([21u8; 32])
1650 )
1651 .to_vec(),
1652 ImportedParaHeadsKeyProvider::final_key(
1653 "Parachains",
1654 &ParaId(42),
1655 &ParaHash::from([21u8; 32])
1656 )
1657 .0,
1658 );
1659 }
1660
1661 #[test]
1662 fn ignores_parachain_head_if_it_is_missing_from_storage_proof() {
1663 let (state_root, proof, _) =
1664 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![]);
1665 let parachains = vec![(ParaId(2), Default::default())];
1666 run_test(|| {
1667 initialize(state_root);
1668 assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1669 RuntimeOrigin::signed(1),
1670 (0, test_relay_header(0, state_root).hash()),
1671 parachains,
1672 proof,
1673 ));
1674 assert_eq!(
1675 System::<TestRuntime>::events(),
1676 vec![EventRecord {
1677 phase: Phase::Initialization,
1678 event: TestEvent::Parachains(Event::MissingParachainHead {
1679 parachain: ParaId(2),
1680 }),
1681 topics: vec![],
1682 }],
1683 );
1684 });
1685 }
1686
1687 #[test]
1688 fn ignores_parachain_head_if_parachain_head_hash_is_wrong() {
1689 let (state_root, proof, _) =
1690 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
1691 let parachains = vec![(ParaId(1), head_data(1, 10).hash())];
1692 run_test(|| {
1693 initialize(state_root);
1694 assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1695 RuntimeOrigin::signed(1),
1696 (0, test_relay_header(0, state_root).hash()),
1697 parachains,
1698 proof,
1699 ));
1700 assert_eq!(
1701 System::<TestRuntime>::events(),
1702 vec![EventRecord {
1703 phase: Phase::Initialization,
1704 event: TestEvent::Parachains(Event::IncorrectParachainHeadHash {
1705 parachain: ParaId(1),
1706 parachain_head_hash: head_data(1, 10).hash(),
1707 actual_parachain_head_hash: head_data(1, 0).hash(),
1708 }),
1709 topics: vec![],
1710 }],
1711 );
1712 });
1713 }
1714
1715 #[test]
1716 fn test_bridge_parachain_call_is_correctly_defined() {
1717 let (state_root, proof, _) =
1718 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
1719 let parachains = vec![(ParaId(2), Default::default())];
1720 let relay_header_id = (0, test_relay_header(0, state_root).hash());
1721
1722 let direct_submit_parachain_heads_call = Call::<TestRuntime>::submit_parachain_heads {
1723 at_relay_block: relay_header_id,
1724 parachains: parachains.clone(),
1725 parachain_heads_proof: proof.clone(),
1726 };
1727 let indirect_submit_parachain_heads_call = BridgeParachainCall::submit_parachain_heads {
1728 at_relay_block: relay_header_id,
1729 parachains,
1730 parachain_heads_proof: proof,
1731 };
1732 assert_eq!(
1733 direct_submit_parachain_heads_call.encode(),
1734 indirect_submit_parachain_heads_call.encode()
1735 );
1736 }
1737
1738 generate_owned_bridge_module_tests!(BasicOperatingMode::Normal, BasicOperatingMode::Halted);
1739
1740 #[test]
1741 fn maybe_max_parachains_returns_correct_value() {
1742 assert_eq!(MaybeMaxParachains::<TestRuntime, ()>::get(), Some(mock::TOTAL_PARACHAINS));
1743 }
1744
1745 #[test]
1746 fn maybe_max_total_parachain_hashes_returns_correct_value() {
1747 assert_eq!(
1748 MaybeMaxTotalParachainHashes::<TestRuntime, ()>::get(),
1749 Some(mock::TOTAL_PARACHAINS * mock::HeadsToKeep::get()),
1750 );
1751 }
1752
1753 #[test]
1754 fn submit_finality_proof_requires_signed_origin() {
1755 run_test(|| {
1756 let (state_root, proof, parachains) =
1757 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
1758
1759 initialize(state_root);
1760
1761 assert_noop!(
1763 Pallet::<TestRuntime>::submit_parachain_heads(
1764 RuntimeOrigin::root(),
1765 (0, test_relay_header(0, state_root).hash()),
1766 parachains,
1767 proof,
1768 ),
1769 DispatchError::BadOrigin
1770 );
1771 })
1772 }
1773
1774 #[test]
1775 fn may_be_free_for_submitting_filtered_heads() {
1776 run_test(|| {
1777 let (state_root, proof, parachains) =
1778 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(2, head_data(2, 5))]);
1779 initialize(state_root);
1781 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1783 RuntimeOrigin::signed(1),
1784 (0, test_relay_header(0, state_root).hash()),
1785 parachains.clone(),
1786 proof.clone(),
1787 );
1788 assert_eq!(result.unwrap().pays_fee, Pays::No);
1789 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1791 RuntimeOrigin::signed(1),
1792 (0, test_relay_header(0, state_root).hash()),
1793 parachains,
1794 proof,
1795 );
1796 assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1797 let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1799 RegularParachainHeader,
1800 >(vec![(2, head_data(2, 50))]);
1801 let relay_block_number = FreeHeadersInterval::get() - 1;
1802 proceed(relay_block_number, state_root);
1803 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1804 RuntimeOrigin::signed(1),
1805 (relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
1806 parachains,
1807 proof,
1808 );
1809 assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1810 let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1812 RegularParachainHeader,
1813 >(vec![(2, head_data(2, 100))]);
1814 let relay_block_number = relay_block_number + FreeHeadersInterval::get();
1815 proceed(relay_block_number, state_root);
1816 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1817 RuntimeOrigin::signed(1),
1818 (relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
1819 parachains,
1820 proof,
1821 );
1822 assert_eq!(result.unwrap().pays_fee, Pays::No);
1823 let mut large_head = head_data(2, 100);
1826 large_head.0.extend(&[42u8; BigParachain::MAX_HEADER_SIZE as _]);
1827 let (state_root, proof, parachains) =
1828 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(2, large_head)]);
1829 let relay_block_number = relay_block_number + FreeHeadersInterval::get();
1830 proceed(relay_block_number, state_root);
1831 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1832 RuntimeOrigin::signed(1),
1833 (relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
1834 parachains,
1835 proof,
1836 );
1837 assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1838 })
1839 }
1840
1841 #[test]
1842 fn grandpa_and_parachain_pallets_share_free_headers_counter() {
1843 run_test(|| {
1844 initialize(Default::default());
1845 let mut free_headers_remaining = 4;
1847 pallet_bridge_grandpa::FreeHeadersRemaining::<TestRuntime, BridgesGrandpaPalletInstance>::set(
1848 Some(free_headers_remaining),
1849 );
1850 let mut relay_block_number = 0;
1852 for i in 0..2 {
1853 let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1855 RegularParachainHeader,
1856 >(vec![(2, head_data(2, 5 + i))]);
1857 relay_block_number = relay_block_number + FreeHeadersInterval::get();
1858 proceed(relay_block_number, state_root);
1859 assert_eq!(
1860 pallet_bridge_grandpa::FreeHeadersRemaining::<
1861 TestRuntime,
1862 BridgesGrandpaPalletInstance,
1863 >::get(),
1864 Some(free_headers_remaining - 1),
1865 );
1866 free_headers_remaining = free_headers_remaining - 1;
1867 assert_ok!(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!(
1875 pallet_bridge_grandpa::FreeHeadersRemaining::<
1876 TestRuntime,
1877 BridgesGrandpaPalletInstance,
1878 >::get(),
1879 Some(free_headers_remaining - 1),
1880 );
1881 free_headers_remaining = free_headers_remaining - 1;
1882 }
1883 let (state_root, proof, parachains) =
1885 prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(2, head_data(2, 7))]);
1886 relay_block_number = relay_block_number + FreeHeadersInterval::get();
1887 let result = pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::submit_finality_proof_ex(
1888 RuntimeOrigin::signed(1),
1889 Box::new(test_relay_header(relay_block_number, state_root)),
1890 make_default_justification(&test_relay_header(relay_block_number, state_root)),
1891 TEST_GRANDPA_SET_ID,
1892 false,
1893 );
1894 assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1895 let result = Pallet::<TestRuntime>::submit_parachain_heads(
1897 RuntimeOrigin::signed(1),
1898 (relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
1899 parachains,
1900 proof,
1901 );
1902 assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1903 assert_eq!(
1904 pallet_bridge_grandpa::FreeHeadersRemaining::<
1905 TestRuntime,
1906 BridgesGrandpaPalletInstance,
1907 >::get(),
1908 Some(0),
1909 );
1910 });
1911 }
1912}