1#![warn(missing_docs)]
36#![cfg_attr(not(feature = "std"), no_std)]
37
38pub use storage_types::StoredAuthoritySet;
39
40use bp_header_chain::{
41 justification::GrandpaJustification, AuthoritySet, ChainWithGrandpa, GrandpaConsensusLogReader,
42 HeaderChain, InitializationData, StoredHeaderData, StoredHeaderDataBuilder,
43 StoredHeaderGrandpaInfo,
44};
45use bp_runtime::{BlockNumberOf, HashOf, HasherOf, HeaderId, HeaderOf, OwnedBridgeModule};
46use frame_support::{dispatch::PostDispatchInfo, ensure, DefaultNoBound};
47use sp_consensus_grandpa::{AuthorityList, SetId};
48use sp_runtime::{
49 traits::{Header as HeaderT, Zero},
50 SaturatedConversion,
51};
52use sp_std::{boxed::Box, prelude::*};
53
54mod call_ext;
55#[cfg(test)]
56mod mock;
57mod storage_types;
58
59pub mod weights;
61pub mod weights_ext;
62
63#[cfg(feature = "runtime-benchmarks")]
64pub mod benchmarking;
65
66pub use call_ext::*;
68pub use pallet::*;
69pub use weights::WeightInfo;
70pub use weights_ext::WeightInfoExt;
71
72pub const LOG_TARGET: &str = "runtime::bridge-grandpa";
74
75pub type BridgedChain<T, I> = <T as Config<I>>::BridgedChain;
77pub type BridgedBlockNumber<T, I> = BlockNumberOf<<T as Config<I>>::BridgedChain>;
79pub type BridgedBlockHash<T, I> = HashOf<<T as Config<I>>::BridgedChain>;
81pub type BridgedBlockId<T, I> = HeaderId<BridgedBlockHash<T, I>, BridgedBlockNumber<T, I>>;
83pub type BridgedBlockHasher<T, I> = HasherOf<<T as Config<I>>::BridgedChain>;
85pub type BridgedHeader<T, I> = HeaderOf<<T as Config<I>>::BridgedChain>;
87pub type BridgedStoredHeaderData<T, I> =
89 StoredHeaderData<BridgedBlockNumber<T, I>, BridgedBlockHash<T, I>>;
90
91#[frame_support::pallet]
92pub mod pallet {
93 use super::*;
94 use bp_runtime::BasicOperatingMode;
95 use frame_support::pallet_prelude::*;
96 use frame_system::pallet_prelude::*;
97
98 #[pallet::config]
99 pub trait Config<I: 'static = ()>: frame_system::Config {
100 #[allow(deprecated)]
102 type RuntimeEvent: From<Event<Self, I>>
103 + IsType<<Self as frame_system::Config>::RuntimeEvent>;
104
105 type BridgedChain: ChainWithGrandpa;
107
108 #[pallet::constant]
124 type MaxFreeHeadersPerBlock: Get<u32>;
125
126 #[pallet::constant]
132 type FreeHeadersInterval: Get<Option<u32>>;
133
134 #[pallet::constant]
142 type HeadersToKeep: Get<u32>;
143
144 type WeightInfo: WeightInfoExt;
146 }
147
148 #[pallet::pallet]
149 pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
150
151 #[pallet::hooks]
152 impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
153 fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
154 FreeHeadersRemaining::<T, I>::put(T::MaxFreeHeadersPerBlock::get());
155 Weight::zero()
156 }
157
158 fn on_finalize(_n: BlockNumberFor<T>) {
159 FreeHeadersRemaining::<T, I>::kill();
160 }
161 }
162
163 impl<T: Config<I>, I: 'static> OwnedBridgeModule<T> for Pallet<T, I> {
164 const LOG_TARGET: &'static str = LOG_TARGET;
165 type OwnerStorage = PalletOwner<T, I>;
166 type OperatingMode = BasicOperatingMode;
167 type OperatingModeStorage = PalletOperatingMode<T, I>;
168 }
169
170 #[pallet::call]
171 impl<T: Config<I>, I: 'static> Pallet<T, I> {
172 #[pallet::call_index(0)]
176 #[pallet::weight(T::WeightInfo::submit_finality_proof_weight(
177 justification.commit.precommits.len().saturated_into(),
178 justification.votes_ancestries.len().saturated_into(),
179 ))]
180 #[allow(deprecated)]
181 #[deprecated(
182 note = "`submit_finality_proof` will be removed in May 2024. Use `submit_finality_proof_ex` instead."
183 )]
184 pub fn submit_finality_proof(
185 origin: OriginFor<T>,
186 finality_target: Box<BridgedHeader<T, I>>,
187 justification: GrandpaJustification<BridgedHeader<T, I>>,
188 ) -> DispatchResultWithPostInfo {
189 Self::submit_finality_proof_ex(
190 origin,
191 finality_target,
192 justification,
193 <CurrentAuthoritySet<T, I>>::get().set_id,
196 false,
198 )
199 }
200
201 #[pallet::call_index(1)]
211 #[pallet::weight((T::DbWeight::get().reads_writes(2, 5), DispatchClass::Operational))]
212 pub fn initialize(
213 origin: OriginFor<T>,
214 init_data: super::InitializationData<BridgedHeader<T, I>>,
215 ) -> DispatchResultWithPostInfo {
216 Self::ensure_owner_or_root(origin)?;
217
218 let init_allowed = !<BestFinalized<T, I>>::exists();
219 ensure!(init_allowed, <Error<T, I>>::AlreadyInitialized);
220 initialize_bridge::<T, I>(init_data.clone())?;
221
222 tracing::info!(
223 target: LOG_TARGET,
224 parameters=?init_data,
225 "Pallet has been initialized"
226 );
227
228 Ok(().into())
229 }
230
231 #[pallet::call_index(2)]
235 #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
236 pub fn set_owner(origin: OriginFor<T>, new_owner: Option<T::AccountId>) -> DispatchResult {
237 <Self as OwnedBridgeModule<_>>::set_owner(origin, new_owner)
238 }
239
240 #[pallet::call_index(3)]
244 #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
245 pub fn set_operating_mode(
246 origin: OriginFor<T>,
247 operating_mode: BasicOperatingMode,
248 ) -> DispatchResult {
249 <Self as OwnedBridgeModule<_>>::set_operating_mode(origin, operating_mode)
250 }
251
252 #[pallet::call_index(4)]
280 #[pallet::weight(T::WeightInfo::submit_finality_proof_weight(
281 justification.commit.precommits.len().saturated_into(),
282 justification.votes_ancestries.len().saturated_into(),
283 ))]
284 pub fn submit_finality_proof_ex(
285 origin: OriginFor<T>,
286 finality_target: Box<BridgedHeader<T, I>>,
287 justification: GrandpaJustification<BridgedHeader<T, I>>,
288 current_set_id: sp_consensus_grandpa::SetId,
289 _is_free_execution_expected: bool,
290 ) -> DispatchResultWithPostInfo {
291 Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
292 ensure_signed(origin)?;
293
294 let (hash, number) = (finality_target.hash(), *finality_target.number());
295 tracing::trace!(
296 target: LOG_TARGET,
297 header=?finality_target,
298 "Going to try and finalize header"
299 );
300
301 let improved_by =
304 SubmitFinalityProofHelper::<T, I>::check_obsolete(number, Some(current_set_id))?;
305
306 let authority_set = <CurrentAuthoritySet<T, I>>::get();
307 let unused_proof_size = authority_set.unused_proof_size();
308 let set_id = authority_set.set_id;
309 let authority_set: AuthoritySet = authority_set.into();
310 verify_justification::<T, I>(&justification, hash, number, authority_set)?;
311
312 let maybe_new_authority_set =
313 try_enact_authority_change::<T, I>(&finality_target, set_id)?;
314 let may_refund_call_fee = may_refund_call_fee::<T, I>(
315 &finality_target,
316 &justification,
317 current_set_id,
318 improved_by,
319 );
320 if may_refund_call_fee {
321 on_free_header_imported::<T, I>();
322 }
323 insert_header::<T, I>(*finality_target, hash);
324
325 let pays_fee = if may_refund_call_fee { Pays::No } else { Pays::Yes };
334
335 tracing::info!(
336 target: LOG_TARGET,
337 ?hash,
338 ?pays_fee,
339 "Successfully imported finalized header!"
340 );
341
342 let pre_dispatch_weight = T::WeightInfo::submit_finality_proof(
347 justification.commit.precommits.len().saturated_into(),
348 justification.votes_ancestries.len().saturated_into(),
349 );
350 let actual_weight = pre_dispatch_weight
351 .set_proof_size(pre_dispatch_weight.proof_size().saturating_sub(unused_proof_size));
352
353 Self::deposit_event(Event::UpdatedBestFinalizedHeader {
354 number,
355 hash,
356 grandpa_info: StoredHeaderGrandpaInfo {
357 finality_proof: justification,
358 new_verification_context: maybe_new_authority_set,
359 },
360 });
361
362 Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee })
363 }
364
365 #[pallet::call_index(5)]
375 #[pallet::weight(T::WeightInfo::force_set_pallet_state())]
376 pub fn force_set_pallet_state(
377 origin: OriginFor<T>,
378 new_current_set_id: SetId,
379 new_authorities: AuthorityList,
380 new_best_header: Box<BridgedHeader<T, I>>,
381 ) -> DispatchResult {
382 Self::ensure_owner_or_root(origin)?;
383
384 save_authorities_set::<T, I>(
387 CurrentAuthoritySet::<T, I>::get().set_id,
388 new_current_set_id,
389 new_authorities,
390 )?;
391
392 let new_best_header_hash = new_best_header.hash();
396 insert_header::<T, I>(*new_best_header, new_best_header_hash);
397
398 Ok(())
399 }
400 }
401
402 #[pallet::storage]
411 #[pallet::whitelist_storage]
412 pub type FreeHeadersRemaining<T: Config<I>, I: 'static = ()> =
413 StorageValue<_, u32, OptionQuery>;
414
415 #[pallet::storage]
417 pub(super) type InitialHash<T: Config<I>, I: 'static = ()> =
418 StorageValue<_, BridgedBlockHash<T, I>, ValueQuery>;
419
420 #[pallet::storage]
422 pub type BestFinalized<T: Config<I>, I: 'static = ()> =
423 StorageValue<_, BridgedBlockId<T, I>, OptionQuery>;
424
425 #[pallet::storage]
427 pub(super) type ImportedHashes<T: Config<I>, I: 'static = ()> = StorageMap<
428 Hasher = Identity,
429 Key = u32,
430 Value = BridgedBlockHash<T, I>,
431 QueryKind = OptionQuery,
432 OnEmpty = GetDefault,
433 MaxValues = MaybeHeadersToKeep<T, I>,
434 >;
435
436 #[pallet::storage]
438 pub(super) type ImportedHashesPointer<T: Config<I>, I: 'static = ()> =
439 StorageValue<_, u32, ValueQuery>;
440
441 #[pallet::storage]
443 pub type ImportedHeaders<T: Config<I>, I: 'static = ()> = StorageMap<
444 Hasher = Identity,
445 Key = BridgedBlockHash<T, I>,
446 Value = BridgedStoredHeaderData<T, I>,
447 QueryKind = OptionQuery,
448 OnEmpty = GetDefault,
449 MaxValues = MaybeHeadersToKeep<T, I>,
450 >;
451
452 #[pallet::storage]
454 pub type CurrentAuthoritySet<T: Config<I>, I: 'static = ()> =
455 StorageValue<_, StoredAuthoritySet<T, I>, ValueQuery>;
456
457 #[pallet::storage]
464 pub type PalletOwner<T: Config<I>, I: 'static = ()> =
465 StorageValue<_, T::AccountId, OptionQuery>;
466
467 #[pallet::storage]
471 pub type PalletOperatingMode<T: Config<I>, I: 'static = ()> =
472 StorageValue<_, BasicOperatingMode, ValueQuery>;
473
474 #[pallet::genesis_config]
475 #[derive(DefaultNoBound)]
476 pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
477 pub owner: Option<T::AccountId>,
479 pub init_data: Option<super::InitializationData<BridgedHeader<T, I>>>,
481 }
482
483 #[pallet::genesis_build]
484 impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
485 fn build(&self) {
486 if let Some(ref owner) = self.owner {
487 <PalletOwner<T, I>>::put(owner);
488 }
489
490 if let Some(init_data) = self.init_data.clone() {
491 initialize_bridge::<T, I>(init_data).expect("genesis config is correct; qed");
492 } else {
493 <PalletOperatingMode<T, I>>::put(BasicOperatingMode::Halted);
496 }
497 }
498 }
499
500 #[pallet::event]
501 #[pallet::generate_deposit(pub(super) fn deposit_event)]
502 pub enum Event<T: Config<I>, I: 'static = ()> {
503 UpdatedBestFinalizedHeader {
505 number: BridgedBlockNumber<T, I>,
507 hash: BridgedBlockHash<T, I>,
509 grandpa_info: StoredHeaderGrandpaInfo<BridgedHeader<T, I>>,
511 },
512 }
513
514 #[pallet::error]
515 pub enum Error<T, I = ()> {
516 InvalidJustification,
518 InvalidAuthoritySet,
520 OldHeader,
522 UnsupportedScheduledChange,
526 NotInitialized,
528 AlreadyInitialized,
530 TooManyAuthoritiesInSet,
532 BridgeModule(bp_runtime::OwnedBridgeModuleError),
534 InvalidAuthoritySetId,
537 FreeHeadersLimitExceded,
540 BelowFreeHeaderInterval,
543 HeaderOverflowLimits,
546 }
547
548 pub fn on_free_header_imported<T: Config<I>, I: 'static>() {
550 FreeHeadersRemaining::<T, I>::mutate(|count| {
551 *count = match *count {
552 None => None,
553 Some(count) => Some(count.saturating_sub(1)),
557 }
558 });
559 }
560
561 fn may_refund_call_fee<T: Config<I>, I: 'static>(
564 finality_target: &BridgedHeader<T, I>,
565 justification: &GrandpaJustification<BridgedHeader<T, I>>,
566 current_set_id: SetId,
567 improved_by: BridgedBlockNumber<T, I>,
568 ) -> bool {
569 if FreeHeadersRemaining::<T, I>::get().unwrap_or(0) == 0 {
571 return false;
572 }
573
574 let call_info = submit_finality_proof_info_from_args::<T, I>(
576 &finality_target,
577 &justification,
578 Some(current_set_id),
579 false,
583 );
584 if !call_info.fits_limits() {
585 return false;
586 }
587
588 if call_info.is_mandatory {
590 return true;
591 }
592
593 if let Some(free_headers_interval) = T::FreeHeadersInterval::get() {
596 if improved_by >= free_headers_interval.into() {
597 return true;
598 }
599 }
600
601 false
602 }
603
604 pub(crate) fn try_enact_authority_change<T: Config<I>, I: 'static>(
612 header: &BridgedHeader<T, I>,
613 current_set_id: sp_consensus_grandpa::SetId,
614 ) -> Result<Option<AuthoritySet>, DispatchError> {
615 ensure!(
617 GrandpaConsensusLogReader::<BridgedBlockNumber<T, I>>::find_forced_change(
618 header.digest()
619 )
620 .is_none(),
621 <Error<T, I>>::UnsupportedScheduledChange
622 );
623
624 if let Some(change) =
625 GrandpaConsensusLogReader::<BridgedBlockNumber<T, I>>::find_scheduled_change(
626 header.digest(),
627 ) {
628 ensure!(change.delay == Zero::zero(), <Error<T, I>>::UnsupportedScheduledChange);
630
631 return save_authorities_set::<T, I>(
635 current_set_id,
636 current_set_id + 1,
637 change.next_authorities,
638 );
639 };
640
641 Ok(None)
642 }
643
644 pub(crate) fn save_authorities_set<T: Config<I>, I: 'static>(
646 old_current_set_id: SetId,
647 new_current_set_id: SetId,
648 new_authorities: AuthorityList,
649 ) -> Result<Option<AuthoritySet>, DispatchError> {
650 let next_authorities = StoredAuthoritySet::<T, I> {
651 authorities: new_authorities
652 .try_into()
653 .map_err(|_| Error::<T, I>::TooManyAuthoritiesInSet)?,
654 set_id: new_current_set_id,
655 };
656
657 <CurrentAuthoritySet<T, I>>::put(&next_authorities);
658
659 tracing::info!(
660 target: LOG_TARGET,
661 %old_current_set_id,
662 %new_current_set_id,
663 ?next_authorities,
664 "Transitioned from authority set!"
665 );
666
667 Ok(Some(next_authorities.into()))
668 }
669
670 pub(crate) fn verify_justification<T: Config<I>, I: 'static>(
677 justification: &GrandpaJustification<BridgedHeader<T, I>>,
678 hash: BridgedBlockHash<T, I>,
679 number: BridgedBlockNumber<T, I>,
680 authority_set: bp_header_chain::AuthoritySet,
681 ) -> Result<(), sp_runtime::DispatchError> {
682 use bp_header_chain::justification::verify_justification;
683
684 Ok(verify_justification::<BridgedHeader<T, I>>(
685 (hash, number),
686 &authority_set.try_into().map_err(|_| <Error<T, I>>::InvalidAuthoritySet)?,
687 justification,
688 )
689 .map_err(|e| {
690 tracing::error!(
691 target: LOG_TARGET,
692 error=?e,
693 ?hash,
694 "Received invalid justification"
695 );
696 <Error<T, I>>::InvalidJustification
697 })?)
698 }
699
700 pub(crate) fn insert_header<T: Config<I>, I: 'static>(
705 header: BridgedHeader<T, I>,
706 hash: BridgedBlockHash<T, I>,
707 ) {
708 let index = <ImportedHashesPointer<T, I>>::get();
709 let pruning = <ImportedHashes<T, I>>::try_get(index);
710 <BestFinalized<T, I>>::put(HeaderId(*header.number(), hash));
711 <ImportedHeaders<T, I>>::insert(hash, header.build());
712 <ImportedHashes<T, I>>::insert(index, hash);
713
714 <ImportedHashesPointer<T, I>>::put((index + 1) % T::HeadersToKeep::get());
716 if let Ok(hash) = pruning {
717 tracing::debug!(target: LOG_TARGET, ?hash, "Pruning old header.");
718 <ImportedHeaders<T, I>>::remove(hash);
719 }
720 }
721
722 pub(crate) fn initialize_bridge<T: Config<I>, I: 'static>(
725 init_params: super::InitializationData<BridgedHeader<T, I>>,
726 ) -> Result<(), Error<T, I>> {
727 let super::InitializationData { header, authority_list, set_id, operating_mode } =
728 init_params;
729 let authority_set_length = authority_list.len();
730 let authority_set = StoredAuthoritySet::<T, I>::try_new(authority_list, set_id)
731 .inspect_err(|_| {
732 tracing::error!(
733 target: LOG_TARGET,
734 %authority_set_length,
735 max_count=%T::BridgedChain::MAX_AUTHORITIES_COUNT,
736 "Failed to initialize bridge. Number of authorities in the set is larger than the configured value"
737 );
738 })?;
739 let initial_hash = header.hash();
740
741 <InitialHash<T, I>>::put(initial_hash);
742 <ImportedHashesPointer<T, I>>::put(0);
743 insert_header::<T, I>(*header, initial_hash);
744
745 <CurrentAuthoritySet<T, I>>::put(authority_set);
746
747 <PalletOperatingMode<T, I>>::put(operating_mode);
748
749 Ok(())
750 }
751
752 pub struct MaybeHeadersToKeep<T, I>(PhantomData<(T, I)>);
754
755 impl<T: Config<I>, I: 'static> Get<Option<u32>> for MaybeHeadersToKeep<T, I> {
757 fn get() -> Option<u32> {
758 Some(T::HeadersToKeep::get())
759 }
760 }
761
762 #[cfg(feature = "runtime-benchmarks")]
768 pub(crate) fn bootstrap_bridge<T: Config<I>, I: 'static>(
769 init_params: super::InitializationData<BridgedHeader<T, I>>,
770 ) -> BridgedHeader<T, I> {
771 let start_header = init_params.header.clone();
772 initialize_bridge::<T, I>(init_params).expect("benchmarks are correct");
773
774 assert_eq!(ImportedHashesPointer::<T, I>::get(), 1);
778 ImportedHashesPointer::<T, I>::put(0);
779
780 *start_header
781 }
782}
783
784impl<T: Config<I>, I: 'static> Pallet<T, I>
785where
786 <T as frame_system::Config>::RuntimeEvent: TryInto<Event<T, I>>,
787{
788 pub fn synced_headers_grandpa_info() -> Vec<StoredHeaderGrandpaInfo<BridgedHeader<T, I>>> {
790 frame_system::Pallet::<T>::read_events_no_consensus()
791 .filter_map(|event| {
792 let Event::<T, I>::UpdatedBestFinalizedHeader { grandpa_info, .. } =
793 event.event.try_into().ok()?;
794 Some(grandpa_info)
795 })
796 .collect()
797 }
798}
799
800pub type GrandpaChainHeaders<T, I> = Pallet<T, I>;
802
803impl<T: Config<I>, I: 'static> HeaderChain<BridgedChain<T, I>> for GrandpaChainHeaders<T, I> {
804 fn finalized_header_state_root(
805 header_hash: HashOf<BridgedChain<T, I>>,
806 ) -> Option<HashOf<BridgedChain<T, I>>> {
807 ImportedHeaders::<T, I>::get(header_hash).map(|h| h.state_root)
808 }
809}
810
811#[cfg(feature = "runtime-benchmarks")]
813pub fn initialize_for_benchmarks<T: Config<I>, I: 'static>(header: BridgedHeader<T, I>) {
814 initialize_bridge::<T, I>(InitializationData {
815 header: Box::new(header),
816 authority_list: sp_std::vec::Vec::new(), set_id: 0,
819 operating_mode: bp_runtime::BasicOperatingMode::Normal,
820 })
821 .expect("only used from benchmarks; benchmarks are correct; qed");
822}
823
824impl<T: Config<I>, I: 'static> Pallet<T, I> {
825 pub fn best_finalized() -> Option<BridgedBlockId<T, I>> {
827 BestFinalized::<T, I>::get()
828 }
829}
830
831#[cfg(test)]
832mod tests {
833 use super::*;
834 use crate::mock::{
835 run_test, test_header, FreeHeadersInterval, RuntimeEvent as TestEvent, RuntimeOrigin,
836 System, TestBridgedChain, TestHeader, TestNumber, TestRuntime, MAX_BRIDGED_AUTHORITIES,
837 };
838 use bp_header_chain::BridgeGrandpaCall;
839 use bp_runtime::BasicOperatingMode;
840 use bp_test_utils::{
841 authority_list, generate_owned_bridge_module_tests, make_default_justification,
842 make_justification_for_header, JustificationGeneratorParams, ALICE, BOB,
843 TEST_GRANDPA_SET_ID,
844 };
845 use codec::Encode;
846 use frame_support::{
847 assert_err, assert_noop, assert_ok,
848 dispatch::{Pays, PostDispatchInfo},
849 storage::generator::StorageValue,
850 };
851 use frame_system::{EventRecord, Phase};
852 use sp_consensus_grandpa::{ConsensusLog, GRANDPA_ENGINE_ID};
853 use sp_core::Get;
854 use sp_runtime::{Digest, DigestItem, DispatchError};
855
856 fn initialize_substrate_bridge() {
857 System::set_block_number(1);
858 System::reset_events();
859
860 assert_ok!(init_with_origin(RuntimeOrigin::root()));
861 }
862
863 fn init_with_origin(
864 origin: RuntimeOrigin,
865 ) -> Result<
866 InitializationData<TestHeader>,
867 sp_runtime::DispatchErrorWithPostInfo<PostDispatchInfo>,
868 > {
869 let genesis = test_header(0);
870
871 let init_data = InitializationData {
872 header: Box::new(genesis),
873 authority_list: authority_list(),
874 set_id: TEST_GRANDPA_SET_ID,
875 operating_mode: BasicOperatingMode::Normal,
876 };
877
878 Pallet::<TestRuntime>::initialize(origin, init_data.clone()).map(|_| init_data)
879 }
880
881 fn submit_finality_proof(header: u8) -> frame_support::dispatch::DispatchResultWithPostInfo {
882 let header = test_header(header.into());
883 let justification = make_default_justification(&header);
884
885 Pallet::<TestRuntime>::submit_finality_proof_ex(
886 RuntimeOrigin::signed(1),
887 Box::new(header),
888 justification,
889 TEST_GRANDPA_SET_ID,
890 false,
891 )
892 }
893
894 fn submit_finality_proof_with_set_id(
895 header: u8,
896 set_id: u64,
897 ) -> frame_support::dispatch::DispatchResultWithPostInfo {
898 let header = test_header(header.into());
899 let justification = make_justification_for_header(JustificationGeneratorParams {
900 header: header.clone(),
901 set_id,
902 ..Default::default()
903 });
904
905 Pallet::<TestRuntime>::submit_finality_proof_ex(
906 RuntimeOrigin::signed(1),
907 Box::new(header),
908 justification,
909 set_id,
910 false,
911 )
912 }
913
914 fn submit_mandatory_finality_proof(
915 number: u8,
916 set_id: u64,
917 ) -> frame_support::dispatch::DispatchResultWithPostInfo {
918 let mut header = test_header(number.into());
919 let consensus_log =
922 ConsensusLog::<TestNumber>::ScheduledChange(sp_consensus_grandpa::ScheduledChange {
923 next_authorities: authority_list(),
924 delay: 0,
925 });
926 header.digest =
927 Digest { logs: vec![DigestItem::Consensus(GRANDPA_ENGINE_ID, consensus_log.encode())] };
928 let justification = make_justification_for_header(JustificationGeneratorParams {
929 header: header.clone(),
930 set_id,
931 ..Default::default()
932 });
933
934 Pallet::<TestRuntime>::submit_finality_proof_ex(
935 RuntimeOrigin::signed(1),
936 Box::new(header),
937 justification,
938 set_id,
939 false,
940 )
941 }
942
943 fn next_block() {
944 use frame_support::traits::OnInitialize;
945
946 let current_number = frame_system::Pallet::<TestRuntime>::block_number();
947 frame_system::Pallet::<TestRuntime>::set_block_number(current_number + 1);
948 let _ = Pallet::<TestRuntime>::on_initialize(current_number);
949 }
950
951 fn change_log(delay: u64) -> Digest {
952 let consensus_log =
953 ConsensusLog::<TestNumber>::ScheduledChange(sp_consensus_grandpa::ScheduledChange {
954 next_authorities: vec![(ALICE.into(), 1), (BOB.into(), 1)],
955 delay,
956 });
957
958 Digest { logs: vec![DigestItem::Consensus(GRANDPA_ENGINE_ID, consensus_log.encode())] }
959 }
960
961 fn forced_change_log(delay: u64) -> Digest {
962 let consensus_log = ConsensusLog::<TestNumber>::ForcedChange(
963 delay,
964 sp_consensus_grandpa::ScheduledChange {
965 next_authorities: vec![(ALICE.into(), 1), (BOB.into(), 1)],
966 delay,
967 },
968 );
969
970 Digest { logs: vec![DigestItem::Consensus(GRANDPA_ENGINE_ID, consensus_log.encode())] }
971 }
972
973 fn many_authorities_log() -> Digest {
974 let consensus_log =
975 ConsensusLog::<TestNumber>::ScheduledChange(sp_consensus_grandpa::ScheduledChange {
976 next_authorities: std::iter::repeat((ALICE.into(), 1))
977 .take(MAX_BRIDGED_AUTHORITIES as usize + 1)
978 .collect(),
979 delay: 0,
980 });
981
982 Digest { logs: vec![DigestItem::Consensus(GRANDPA_ENGINE_ID, consensus_log.encode())] }
983 }
984
985 #[test]
986 fn init_root_or_owner_origin_can_initialize_pallet() {
987 run_test(|| {
988 assert_noop!(init_with_origin(RuntimeOrigin::signed(1)), DispatchError::BadOrigin);
989 assert_ok!(init_with_origin(RuntimeOrigin::root()));
990
991 BestFinalized::<TestRuntime>::kill();
993 PalletOwner::<TestRuntime>::put(2);
994 assert_ok!(init_with_origin(RuntimeOrigin::signed(2)));
995 })
996 }
997
998 #[test]
999 fn init_storage_entries_are_correctly_initialized() {
1000 run_test(|| {
1001 assert_eq!(BestFinalized::<TestRuntime>::get(), None,);
1002 assert_eq!(Pallet::<TestRuntime>::best_finalized(), None);
1003 assert_eq!(PalletOperatingMode::<TestRuntime>::try_get(), Err(()));
1004
1005 let init_data = init_with_origin(RuntimeOrigin::root()).unwrap();
1006
1007 assert!(<ImportedHeaders<TestRuntime>>::contains_key(init_data.header.hash()));
1008 assert_eq!(BestFinalized::<TestRuntime>::get().unwrap().1, init_data.header.hash());
1009 assert_eq!(
1010 CurrentAuthoritySet::<TestRuntime>::get().authorities,
1011 init_data.authority_list
1012 );
1013 assert_eq!(
1014 PalletOperatingMode::<TestRuntime>::try_get(),
1015 Ok(BasicOperatingMode::Normal)
1016 );
1017 })
1018 }
1019
1020 #[test]
1021 fn init_can_only_initialize_pallet_once() {
1022 run_test(|| {
1023 initialize_substrate_bridge();
1024 assert_noop!(
1025 init_with_origin(RuntimeOrigin::root()),
1026 <Error<TestRuntime>>::AlreadyInitialized
1027 );
1028 })
1029 }
1030
1031 #[test]
1032 fn init_fails_if_there_are_too_many_authorities_in_the_set() {
1033 run_test(|| {
1034 let genesis = test_header(0);
1035 let init_data = InitializationData {
1036 header: Box::new(genesis),
1037 authority_list: std::iter::repeat(authority_list().remove(0))
1038 .take(MAX_BRIDGED_AUTHORITIES as usize + 1)
1039 .collect(),
1040 set_id: 1,
1041 operating_mode: BasicOperatingMode::Normal,
1042 };
1043
1044 assert_noop!(
1045 Pallet::<TestRuntime>::initialize(RuntimeOrigin::root(), init_data),
1046 Error::<TestRuntime>::TooManyAuthoritiesInSet,
1047 );
1048 });
1049 }
1050
1051 #[test]
1052 fn pallet_rejects_transactions_if_halted() {
1053 run_test(|| {
1054 initialize_substrate_bridge();
1055
1056 assert_ok!(Pallet::<TestRuntime>::set_operating_mode(
1057 RuntimeOrigin::root(),
1058 BasicOperatingMode::Halted
1059 ));
1060 assert_noop!(
1061 submit_finality_proof(1),
1062 Error::<TestRuntime>::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted)
1063 );
1064
1065 assert_ok!(Pallet::<TestRuntime>::set_operating_mode(
1066 RuntimeOrigin::root(),
1067 BasicOperatingMode::Normal
1068 ));
1069 assert_ok!(submit_finality_proof(1));
1070 })
1071 }
1072
1073 #[test]
1074 fn pallet_rejects_header_if_not_initialized_yet() {
1075 run_test(|| {
1076 assert_noop!(submit_finality_proof(1), Error::<TestRuntime>::NotInitialized);
1077 });
1078 }
1079
1080 #[test]
1081 fn successfully_imports_header_with_valid_finality() {
1082 run_test(|| {
1083 initialize_substrate_bridge();
1084
1085 let header_number = 1;
1086 let header = test_header(header_number.into());
1087 let justification = make_default_justification(&header);
1088
1089 let pre_dispatch_weight = <TestRuntime as Config>::WeightInfo::submit_finality_proof(
1090 justification.commit.precommits.len().try_into().unwrap_or(u32::MAX),
1091 justification.votes_ancestries.len().try_into().unwrap_or(u32::MAX),
1092 );
1093
1094 let result = submit_finality_proof(header_number);
1095 assert_ok!(result);
1096 assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes);
1097 let pre_dispatch_proof_size = pre_dispatch_weight.proof_size();
1099 let actual_proof_size = result.unwrap().actual_weight.unwrap().proof_size();
1100 assert!(actual_proof_size > 0);
1101 assert!(
1102 actual_proof_size < pre_dispatch_proof_size,
1103 "Actual proof size {actual_proof_size} must be less than the pre-dispatch {pre_dispatch_proof_size}",
1104 );
1105
1106 let header = test_header(1);
1107 assert_eq!(<BestFinalized<TestRuntime>>::get().unwrap().1, header.hash());
1108 assert!(<ImportedHeaders<TestRuntime>>::contains_key(header.hash()));
1109
1110 assert_eq!(
1111 System::events(),
1112 vec![EventRecord {
1113 phase: Phase::Initialization,
1114 event: TestEvent::Grandpa(Event::UpdatedBestFinalizedHeader {
1115 number: *header.number(),
1116 hash: header.hash(),
1117 grandpa_info: StoredHeaderGrandpaInfo {
1118 finality_proof: justification.clone(),
1119 new_verification_context: None,
1120 },
1121 }),
1122 topics: vec![],
1123 }],
1124 );
1125 assert_eq!(
1126 Pallet::<TestRuntime>::synced_headers_grandpa_info(),
1127 vec![StoredHeaderGrandpaInfo {
1128 finality_proof: justification,
1129 new_verification_context: None
1130 }]
1131 );
1132 })
1133 }
1134
1135 #[test]
1136 fn rejects_justification_that_skips_authority_set_transition() {
1137 run_test(|| {
1138 initialize_substrate_bridge();
1139
1140 let header = test_header(1);
1141
1142 let next_set_id = 2;
1143 let params = JustificationGeneratorParams::<TestHeader> {
1144 set_id: next_set_id,
1145 ..Default::default()
1146 };
1147 let justification = make_justification_for_header(params);
1148
1149 assert_err!(
1150 Pallet::<TestRuntime>::submit_finality_proof_ex(
1151 RuntimeOrigin::signed(1),
1152 Box::new(header.clone()),
1153 justification.clone(),
1154 TEST_GRANDPA_SET_ID,
1155 false,
1156 ),
1157 <Error<TestRuntime>>::InvalidJustification
1158 );
1159 assert_err!(
1160 Pallet::<TestRuntime>::submit_finality_proof_ex(
1161 RuntimeOrigin::signed(1),
1162 Box::new(header),
1163 justification,
1164 next_set_id,
1165 false,
1166 ),
1167 <Error<TestRuntime>>::InvalidAuthoritySetId
1168 );
1169 })
1170 }
1171
1172 #[test]
1173 fn does_not_import_header_with_invalid_finality_proof() {
1174 run_test(|| {
1175 initialize_substrate_bridge();
1176
1177 let header = test_header(1);
1178 let mut justification = make_default_justification(&header);
1179 justification.round = 42;
1180
1181 assert_err!(
1182 Pallet::<TestRuntime>::submit_finality_proof_ex(
1183 RuntimeOrigin::signed(1),
1184 Box::new(header),
1185 justification,
1186 TEST_GRANDPA_SET_ID,
1187 false,
1188 ),
1189 <Error<TestRuntime>>::InvalidJustification
1190 );
1191 })
1192 }
1193
1194 #[test]
1195 fn disallows_invalid_authority_set() {
1196 run_test(|| {
1197 let genesis = test_header(0);
1198
1199 let invalid_authority_list = vec![(ALICE.into(), u64::MAX), (BOB.into(), u64::MAX)];
1200 let init_data = InitializationData {
1201 header: Box::new(genesis),
1202 authority_list: invalid_authority_list,
1203 set_id: 1,
1204 operating_mode: BasicOperatingMode::Normal,
1205 };
1206
1207 assert_ok!(Pallet::<TestRuntime>::initialize(RuntimeOrigin::root(), init_data));
1208
1209 let header = test_header(1);
1210 let justification = make_default_justification(&header);
1211
1212 assert_err!(
1213 Pallet::<TestRuntime>::submit_finality_proof_ex(
1214 RuntimeOrigin::signed(1),
1215 Box::new(header),
1216 justification,
1217 TEST_GRANDPA_SET_ID,
1218 false,
1219 ),
1220 <Error<TestRuntime>>::InvalidAuthoritySet
1221 );
1222 })
1223 }
1224
1225 #[test]
1226 fn importing_header_ensures_that_chain_is_extended() {
1227 run_test(|| {
1228 initialize_substrate_bridge();
1229
1230 assert_ok!(submit_finality_proof(4));
1231 assert_err!(submit_finality_proof(3), Error::<TestRuntime>::OldHeader);
1232 assert_ok!(submit_finality_proof(5));
1233 })
1234 }
1235
1236 #[test]
1237 fn importing_header_enacts_new_authority_set() {
1238 run_test(|| {
1239 initialize_substrate_bridge();
1240
1241 let next_set_id = 2;
1242 let next_authorities = vec![(ALICE.into(), 1), (BOB.into(), 1)];
1243
1244 let mut header = test_header(2);
1247 header.digest = change_log(0);
1248
1249 let justification = make_default_justification(&header);
1251
1252 let result = Pallet::<TestRuntime>::submit_finality_proof_ex(
1254 RuntimeOrigin::signed(1),
1255 Box::new(header.clone()),
1256 justification.clone(),
1257 TEST_GRANDPA_SET_ID,
1258 false,
1259 );
1260 assert_ok!(result);
1261 assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::No);
1262
1263 assert_eq!(<BestFinalized<TestRuntime>>::get().unwrap().1, header.hash());
1265 assert!(<ImportedHeaders<TestRuntime>>::contains_key(header.hash()));
1266
1267 assert_eq!(
1269 <CurrentAuthoritySet<TestRuntime>>::get(),
1270 StoredAuthoritySet::<TestRuntime, ()>::try_new(next_authorities, next_set_id)
1271 .unwrap(),
1272 );
1273
1274 assert_eq!(
1276 System::events(),
1277 vec![EventRecord {
1278 phase: Phase::Initialization,
1279 event: TestEvent::Grandpa(Event::UpdatedBestFinalizedHeader {
1280 number: *header.number(),
1281 hash: header.hash(),
1282 grandpa_info: StoredHeaderGrandpaInfo {
1283 finality_proof: justification.clone(),
1284 new_verification_context: Some(
1285 <CurrentAuthoritySet<TestRuntime>>::get().into()
1286 ),
1287 },
1288 }),
1289 topics: vec![],
1290 }],
1291 );
1292 assert_eq!(
1293 Pallet::<TestRuntime>::synced_headers_grandpa_info(),
1294 vec![StoredHeaderGrandpaInfo {
1295 finality_proof: justification,
1296 new_verification_context: Some(
1297 <CurrentAuthoritySet<TestRuntime>>::get().into()
1298 ),
1299 }]
1300 );
1301 })
1302 }
1303
1304 #[test]
1305 fn relayer_pays_tx_fee_when_submitting_huge_mandatory_header() {
1306 run_test(|| {
1307 initialize_substrate_bridge();
1308
1309 let mut header = test_header(2);
1311 header.digest = change_log(0);
1312 header.digest.push(DigestItem::Other(vec![42u8; 1024 * 1024]));
1313 let justification = make_default_justification(&header);
1314
1315 let result = Pallet::<TestRuntime>::submit_finality_proof_ex(
1318 RuntimeOrigin::signed(1),
1319 Box::new(header.clone()),
1320 justification,
1321 TEST_GRANDPA_SET_ID,
1322 false,
1323 );
1324 assert_ok!(result);
1325 assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes);
1326
1327 assert_eq!(<BestFinalized<TestRuntime>>::get().unwrap().1, header.hash());
1329 assert!(<ImportedHeaders<TestRuntime>>::contains_key(header.hash()));
1330 })
1331 }
1332
1333 #[test]
1334 fn relayer_pays_tx_fee_when_submitting_justification_with_long_ancestry_votes() {
1335 run_test(|| {
1336 initialize_substrate_bridge();
1337
1338 let mut header = test_header(2);
1341 header.digest = change_log(0);
1342 let justification = make_justification_for_header(JustificationGeneratorParams {
1343 header: header.clone(),
1344 ancestors: TestBridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY + 1,
1345 ..Default::default()
1346 });
1347
1348 let result = Pallet::<TestRuntime>::submit_finality_proof_ex(
1351 RuntimeOrigin::signed(1),
1352 Box::new(header.clone()),
1353 justification,
1354 TEST_GRANDPA_SET_ID,
1355 false,
1356 );
1357 assert_ok!(result);
1358 assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes);
1359
1360 assert_eq!(<BestFinalized<TestRuntime>>::get().unwrap().1, header.hash());
1362 assert!(<ImportedHeaders<TestRuntime>>::contains_key(header.hash()));
1363 })
1364 }
1365
1366 #[test]
1367 fn importing_header_rejects_header_with_scheduled_change_delay() {
1368 run_test(|| {
1369 initialize_substrate_bridge();
1370
1371 let mut header = test_header(2);
1374 header.digest = change_log(1);
1375
1376 let justification = make_default_justification(&header);
1378
1379 assert_err!(
1381 Pallet::<TestRuntime>::submit_finality_proof_ex(
1382 RuntimeOrigin::signed(1),
1383 Box::new(header),
1384 justification,
1385 TEST_GRANDPA_SET_ID,
1386 false,
1387 ),
1388 <Error<TestRuntime>>::UnsupportedScheduledChange
1389 );
1390 })
1391 }
1392
1393 #[test]
1394 fn importing_header_rejects_header_with_forced_changes() {
1395 run_test(|| {
1396 initialize_substrate_bridge();
1397
1398 let mut header = test_header(2);
1401 header.digest = forced_change_log(0);
1402
1403 let justification = make_default_justification(&header);
1405
1406 assert_err!(
1408 Pallet::<TestRuntime>::submit_finality_proof_ex(
1409 RuntimeOrigin::signed(1),
1410 Box::new(header),
1411 justification,
1412 TEST_GRANDPA_SET_ID,
1413 false,
1414 ),
1415 <Error<TestRuntime>>::UnsupportedScheduledChange
1416 );
1417 })
1418 }
1419
1420 #[test]
1421 fn importing_header_rejects_header_with_too_many_authorities() {
1422 run_test(|| {
1423 initialize_substrate_bridge();
1424
1425 let mut header = test_header(2);
1428 header.digest = many_authorities_log();
1429
1430 let justification = make_default_justification(&header);
1432
1433 assert_err!(
1435 Pallet::<TestRuntime>::submit_finality_proof_ex(
1436 RuntimeOrigin::signed(1),
1437 Box::new(header),
1438 justification,
1439 TEST_GRANDPA_SET_ID,
1440 false,
1441 ),
1442 <Error<TestRuntime>>::TooManyAuthoritiesInSet
1443 );
1444 });
1445 }
1446
1447 #[test]
1448 fn verify_storage_proof_rejects_unknown_header() {
1449 run_test(|| {
1450 assert_noop!(
1451 Pallet::<TestRuntime>::verify_storage_proof(
1452 Default::default(),
1453 Default::default(),
1454 )
1455 .map(|_| ()),
1456 bp_header_chain::HeaderChainError::UnknownHeader,
1457 );
1458 });
1459 }
1460
1461 #[test]
1462 fn parse_finalized_storage_accepts_valid_proof() {
1463 run_test(|| {
1464 let (state_root, storage_proof) = bp_runtime::craft_valid_storage_proof();
1465
1466 let mut header = test_header(2);
1467 header.set_state_root(state_root);
1468
1469 let hash = header.hash();
1470 <BestFinalized<TestRuntime>>::put(HeaderId(2, hash));
1471 <ImportedHeaders<TestRuntime>>::insert(hash, header.build());
1472
1473 assert_ok!(Pallet::<TestRuntime>::verify_storage_proof(hash, storage_proof).map(|_| ()));
1474 });
1475 }
1476
1477 #[test]
1478 fn rate_limiter_disallows_free_imports_once_limit_is_hit_in_single_block() {
1479 run_test(|| {
1480 initialize_substrate_bridge();
1481
1482 let result = submit_mandatory_finality_proof(1, 1);
1483 assert_eq!(result.expect("call failed").pays_fee, Pays::No);
1484
1485 let result = submit_mandatory_finality_proof(2, 2);
1486 assert_eq!(result.expect("call failed").pays_fee, Pays::No);
1487
1488 let result = submit_mandatory_finality_proof(3, 3);
1489 assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
1490 })
1491 }
1492
1493 #[test]
1494 fn rate_limiter_invalid_requests_do_not_count_towards_request_count() {
1495 run_test(|| {
1496 let submit_invalid_request = || {
1497 let mut header = test_header(1);
1498 header.digest = change_log(0);
1499 let mut invalid_justification = make_default_justification(&header);
1500 invalid_justification.round = 42;
1501
1502 Pallet::<TestRuntime>::submit_finality_proof_ex(
1503 RuntimeOrigin::signed(1),
1504 Box::new(header),
1505 invalid_justification,
1506 TEST_GRANDPA_SET_ID,
1507 false,
1508 )
1509 };
1510
1511 initialize_substrate_bridge();
1512
1513 for _ in 0..<TestRuntime as Config>::MaxFreeHeadersPerBlock::get() + 1 {
1514 assert_err!(submit_invalid_request(), <Error<TestRuntime>>::InvalidJustification);
1515 }
1516
1517 let result = submit_mandatory_finality_proof(1, 1);
1519 assert_eq!(result.expect("call failed").pays_fee, Pays::No);
1520
1521 let result = submit_mandatory_finality_proof(2, 2);
1522 assert_eq!(result.expect("call failed").pays_fee, Pays::No);
1523
1524 let result = submit_mandatory_finality_proof(3, 3);
1525 assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
1526 })
1527 }
1528
1529 #[test]
1530 fn rate_limiter_allows_request_after_new_block_has_started() {
1531 run_test(|| {
1532 initialize_substrate_bridge();
1533
1534 let result = submit_mandatory_finality_proof(1, 1);
1535 assert_eq!(result.expect("call failed").pays_fee, Pays::No);
1536
1537 let result = submit_mandatory_finality_proof(2, 2);
1538 assert_eq!(result.expect("call failed").pays_fee, Pays::No);
1539
1540 let result = submit_mandatory_finality_proof(3, 3);
1541 assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
1542
1543 next_block();
1544
1545 let result = submit_mandatory_finality_proof(4, 4);
1546 assert_eq!(result.expect("call failed").pays_fee, Pays::No);
1547
1548 let result = submit_mandatory_finality_proof(5, 5);
1549 assert_eq!(result.expect("call failed").pays_fee, Pays::No);
1550
1551 let result = submit_mandatory_finality_proof(6, 6);
1552 assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
1553 })
1554 }
1555
1556 #[test]
1557 fn rate_limiter_ignores_non_mandatory_headers() {
1558 run_test(|| {
1559 initialize_substrate_bridge();
1560
1561 let result = submit_finality_proof(1);
1562 assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
1563
1564 let result = submit_mandatory_finality_proof(2, 1);
1565 assert_eq!(result.expect("call failed").pays_fee, Pays::No);
1566
1567 let result = submit_finality_proof_with_set_id(3, 2);
1568 assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
1569
1570 let result = submit_mandatory_finality_proof(4, 2);
1571 assert_eq!(result.expect("call failed").pays_fee, Pays::No);
1572
1573 let result = submit_finality_proof_with_set_id(5, 3);
1574 assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
1575
1576 let result = submit_mandatory_finality_proof(6, 3);
1577 assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
1578 })
1579 }
1580
1581 #[test]
1582 fn may_import_non_mandatory_header_for_free() {
1583 run_test(|| {
1584 initialize_substrate_bridge();
1585
1586 const BEST: u8 = 12;
1588 fn reset_best() {
1589 BestFinalized::<TestRuntime, ()>::set(Some(HeaderId(
1590 BEST as _,
1591 Default::default(),
1592 )));
1593 }
1594
1595 reset_best();
1597 let non_free_header_number = BEST + FreeHeadersInterval::get() as u8 - 1;
1598 let result = submit_finality_proof(non_free_header_number);
1599 assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1600
1601 reset_best();
1603 let free_header_number = BEST + FreeHeadersInterval::get() as u8;
1604 let result = submit_finality_proof(free_header_number);
1605 assert_eq!(result.unwrap().pays_fee, Pays::No);
1606
1607 let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 2;
1609 let result = submit_finality_proof(free_header_number);
1610 assert_eq!(result.unwrap().pays_fee, Pays::No);
1611
1612 let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 3;
1614 let result = submit_finality_proof(free_header_number);
1615 assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1616
1617 next_block();
1620 reset_best();
1621 let free_header_number = FreeHeadersInterval::get() as u8 + 42;
1622 let result = submit_finality_proof(free_header_number);
1623 assert_eq!(result.unwrap().pays_fee, Pays::No);
1624
1625 next_block();
1628 reset_best();
1629 let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 4;
1630 let result = submit_finality_proof(free_header_number);
1631 assert_eq!(result.unwrap().pays_fee, Pays::No);
1632 let result = submit_mandatory_finality_proof(free_header_number + 1, 1);
1633 assert_eq!(result.expect("call failed").pays_fee, Pays::No);
1634 let result = submit_mandatory_finality_proof(free_header_number + 2, 2);
1635 assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
1636 });
1637 }
1638
1639 #[test]
1640 fn should_prune_headers_over_headers_to_keep_parameter() {
1641 run_test(|| {
1642 initialize_substrate_bridge();
1643 assert_ok!(submit_finality_proof(1));
1644 let first_header_hash = Pallet::<TestRuntime>::best_finalized().unwrap().hash();
1645 next_block();
1646
1647 assert_ok!(submit_finality_proof(2));
1648 next_block();
1649 assert_ok!(submit_finality_proof(3));
1650 next_block();
1651 assert_ok!(submit_finality_proof(4));
1652 next_block();
1653 assert_ok!(submit_finality_proof(5));
1654 next_block();
1655
1656 assert_ok!(submit_finality_proof(6));
1657
1658 assert!(
1659 !ImportedHeaders::<TestRuntime, ()>::contains_key(first_header_hash),
1660 "First header should be pruned.",
1661 );
1662 })
1663 }
1664
1665 #[test]
1666 fn storage_keys_computed_properly() {
1667 assert_eq!(
1668 PalletOperatingMode::<TestRuntime>::storage_value_final_key().to_vec(),
1669 bp_header_chain::storage_keys::pallet_operating_mode_key("Grandpa").0,
1670 );
1671
1672 assert_eq!(
1673 CurrentAuthoritySet::<TestRuntime>::storage_value_final_key().to_vec(),
1674 bp_header_chain::storage_keys::current_authority_set_key("Grandpa").0,
1675 );
1676
1677 assert_eq!(
1678 BestFinalized::<TestRuntime>::storage_value_final_key().to_vec(),
1679 bp_header_chain::storage_keys::best_finalized_key("Grandpa").0,
1680 );
1681 }
1682
1683 #[test]
1684 fn test_bridge_grandpa_call_is_correctly_defined() {
1685 let header = test_header(0);
1686 let init_data = InitializationData {
1687 header: Box::new(header.clone()),
1688 authority_list: authority_list(),
1689 set_id: 1,
1690 operating_mode: BasicOperatingMode::Normal,
1691 };
1692 let justification = make_default_justification(&header);
1693
1694 let direct_initialize_call =
1695 Call::<TestRuntime>::initialize { init_data: init_data.clone() };
1696 let indirect_initialize_call = BridgeGrandpaCall::<TestHeader>::initialize { init_data };
1697 assert_eq!(direct_initialize_call.encode(), indirect_initialize_call.encode());
1698
1699 let direct_submit_finality_proof_call = Call::<TestRuntime>::submit_finality_proof {
1700 finality_target: Box::new(header.clone()),
1701 justification: justification.clone(),
1702 };
1703 let indirect_submit_finality_proof_call =
1704 BridgeGrandpaCall::<TestHeader>::submit_finality_proof {
1705 finality_target: Box::new(header),
1706 justification,
1707 };
1708 assert_eq!(
1709 direct_submit_finality_proof_call.encode(),
1710 indirect_submit_finality_proof_call.encode()
1711 );
1712 }
1713
1714 generate_owned_bridge_module_tests!(BasicOperatingMode::Normal, BasicOperatingMode::Halted);
1715
1716 #[test]
1717 fn maybe_headers_to_keep_returns_correct_value() {
1718 assert_eq!(MaybeHeadersToKeep::<TestRuntime, ()>::get(), Some(mock::HeadersToKeep::get()));
1719 }
1720
1721 #[test]
1722 fn submit_finality_proof_requires_signed_origin() {
1723 run_test(|| {
1724 initialize_substrate_bridge();
1725
1726 let header = test_header(1);
1727 let justification = make_default_justification(&header);
1728
1729 assert_noop!(
1730 Pallet::<TestRuntime>::submit_finality_proof_ex(
1731 RuntimeOrigin::root(),
1732 Box::new(header),
1733 justification,
1734 TEST_GRANDPA_SET_ID,
1735 false,
1736 ),
1737 DispatchError::BadOrigin,
1738 );
1739 })
1740 }
1741
1742 #[test]
1743 fn on_free_header_imported_never_sets_to_none() {
1744 run_test(|| {
1745 FreeHeadersRemaining::<TestRuntime, ()>::set(Some(2));
1746 on_free_header_imported::<TestRuntime, ()>();
1747 assert_eq!(FreeHeadersRemaining::<TestRuntime, ()>::get(), Some(1));
1748 on_free_header_imported::<TestRuntime, ()>();
1749 assert_eq!(FreeHeadersRemaining::<TestRuntime, ()>::get(), Some(0));
1750 on_free_header_imported::<TestRuntime, ()>();
1751 assert_eq!(FreeHeadersRemaining::<TestRuntime, ()>::get(), Some(0));
1752 })
1753 }
1754
1755 #[test]
1756 fn force_set_pallet_state_works() {
1757 run_test(|| {
1758 let header25 = test_header(25);
1759 let header50 = test_header(50);
1760 let ok_new_set_id = 100;
1761 let ok_new_authorities = authority_list();
1762 let bad_new_set_id = 100;
1763 let bad_new_authorities: Vec<_> = std::iter::repeat((ALICE.into(), 1))
1764 .take(MAX_BRIDGED_AUTHORITIES as usize + 1)
1765 .collect();
1766
1767 initialize_substrate_bridge();
1769 assert_ok!(submit_finality_proof(30));
1770
1771 assert_noop!(
1773 Pallet::<TestRuntime>::force_set_pallet_state(
1774 RuntimeOrigin::signed(1),
1775 ok_new_set_id,
1776 ok_new_authorities.clone(),
1777 Box::new(header50.clone()),
1778 ),
1779 DispatchError::BadOrigin,
1780 );
1781
1782 assert_noop!(
1784 Pallet::<TestRuntime>::force_set_pallet_state(
1785 RuntimeOrigin::root(),
1786 bad_new_set_id,
1787 bad_new_authorities.clone(),
1788 Box::new(header50.clone()),
1789 ),
1790 Error::<TestRuntime>::TooManyAuthoritiesInSet,
1791 );
1792
1793 assert_ok!(Pallet::<TestRuntime>::force_set_pallet_state(
1795 RuntimeOrigin::root(),
1796 ok_new_set_id,
1797 ok_new_authorities.clone(),
1798 Box::new(header50.clone()),
1799 ),);
1800
1801 assert_ok!(Pallet::<TestRuntime>::force_set_pallet_state(
1803 RuntimeOrigin::root(),
1804 ok_new_set_id,
1805 ok_new_authorities.clone(),
1806 Box::new(header25.clone()),
1807 ),);
1808
1809 assert_noop!(submit_finality_proof(20), Error::<TestRuntime>::OldHeader);
1811 assert_ok!(submit_finality_proof_with_set_id(26, ok_new_set_id));
1812
1813 assert_ok!(submit_finality_proof_with_set_id(50, ok_new_set_id));
1816
1817 assert!(GrandpaChainHeaders::<TestRuntime, ()>::finalized_header_state_root(
1820 test_header(30).hash()
1821 )
1822 .is_some());
1823 assert!(GrandpaChainHeaders::<TestRuntime, ()>::finalized_header_state_root(
1824 test_header(50).hash()
1825 )
1826 .is_some());
1827 assert!(GrandpaChainHeaders::<TestRuntime, ()>::finalized_header_state_root(
1828 test_header(25).hash()
1829 )
1830 .is_some());
1831 assert!(GrandpaChainHeaders::<TestRuntime, ()>::finalized_header_state_root(
1832 test_header(26).hash()
1833 )
1834 .is_some());
1835
1836 assert_ok!(submit_finality_proof_with_set_id(70, ok_new_set_id));
1838 assert_ok!(submit_finality_proof_with_set_id(80, ok_new_set_id));
1840 assert_ok!(submit_finality_proof_with_set_id(90, ok_new_set_id));
1842 assert_ok!(submit_finality_proof_with_set_id(100, ok_new_set_id));
1844 assert_ok!(submit_finality_proof_with_set_id(110, ok_new_set_id));
1846 });
1847 }
1848}