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