referrerpolicy=no-referrer-when-downgrade

pallet_bridge_parachains/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Parity Bridges Common.
3
4// Parity Bridges Common is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Parity Bridges Common is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Parachains finality module.
18//!
19//! This module needs to be deployed with GRANDPA module, which is syncing relay
20//! chain blocks. The main entry point of this module is `submit_parachain_heads`, which
21//! accepts storage proof of some parachain `Heads` entries from bridged relay chain.
22//! It requires corresponding relay headers to be already synced.
23
24#![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
49// Re-export in crate namespace for `construct_runtime!`.
50pub 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
64/// The target that will be used when publishing logs related to this pallet.
65pub const LOG_TARGET: &str = "runtime::bridge-parachains";
66
67/// Artifacts of the parachains head update.
68struct UpdateParachainHeadArtifacts {
69	/// New best head of the parachain.
70	pub best_head: ParaInfo,
71	/// If `true`, some old parachain head has been pruned during update.
72	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	/// Stored parachain head data of given parachains pallet.
90	pub type StoredParaHeadDataOf<T, I> =
91		BoundedStorageValue<<T as Config<I>>::MaxParaHeadDataSize, ParaStoredHeaderData>;
92	/// Weight info of the given parachains pallet.
93	pub type WeightInfoOf<T, I> = <T as Config<I>>::WeightInfo;
94	/// Bridge GRANDPA pallet that is used to verify parachain proofs.
95	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		/// The caller has provided head of parachain that the pallet is not configured to track.
102		UntrackedParachainRejected {
103			/// Identifier of the parachain that is not tracked by the pallet.
104			parachain: ParaId,
105		},
106		/// The caller has declared that he has provided given parachain head, but it is missing
107		/// from the storage proof.
108		MissingParachainHead {
109			/// Identifier of the parachain with missing head.
110			parachain: ParaId,
111		},
112		/// The caller has provided parachain head hash that is not matching the hash read from the
113		/// storage proof.
114		IncorrectParachainHeadHash {
115			/// Identifier of the parachain with incorrect head hast.
116			parachain: ParaId,
117			/// Specified parachain head hash.
118			parachain_head_hash: ParaHash,
119			/// Actual parachain head hash.
120			actual_parachain_head_hash: ParaHash,
121		},
122		/// The caller has provided obsolete parachain head, which is already known to the pallet.
123		RejectedObsoleteParachainHead {
124			/// Identifier of the parachain with obsolete head.
125			parachain: ParaId,
126			/// Obsolete parachain head hash.
127			parachain_head_hash: ParaHash,
128		},
129		/// The caller has provided parachain head that exceeds the maximal configured head size.
130		RejectedLargeParachainHead {
131			/// Identifier of the parachain with rejected head.
132			parachain: ParaId,
133			/// Parachain head hash.
134			parachain_head_hash: ParaHash,
135			/// Parachain head size.
136			parachain_head_size: u32,
137		},
138		/// Parachain head has been updated.
139		UpdatedParachainHead {
140			/// Identifier of the parachain that has been updated.
141			parachain: ParaId,
142			/// Parachain head hash.
143			parachain_head_hash: ParaHash,
144		},
145	}
146
147	#[pallet::error]
148	pub enum Error<T, I = ()> {
149		/// Relay chain block hash is unknown to us.
150		UnknownRelayChainBlock,
151		/// The number of stored relay block is different from what the relayer has provided.
152		InvalidRelayChainBlockNumber,
153		/// Parachain heads storage proof is invalid.
154		HeaderChainStorageProof(HeaderChainError),
155		/// Error generated by the `OwnedBridgeModule` trait.
156		BridgeModule(bp_runtime::OwnedBridgeModuleError),
157	}
158
159	/// Convenience trait for defining `BridgedChain` bounds.
160	pub trait BoundedBridgeGrandpaConfig<I: 'static>:
161		pallet_bridge_grandpa::Config<I, BridgedChain = Self::BridgedRelayChain>
162	{
163		/// Type of the bridged relay chain.
164		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		/// The overarching event type.
186		#[allow(deprecated)]
187		type RuntimeEvent: From<Event<Self, I>>
188			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
189		/// Benchmarks results from runtime we're plugged into.
190		type WeightInfo: WeightInfoExt;
191
192		/// Instance of bridges GRANDPA pallet (within this runtime) that this pallet is linked to.
193		///
194		/// The GRANDPA pallet instance must be configured to import headers of relay chain that
195		/// we're interested in.
196		///
197		/// The associated GRANDPA pallet is also used to configure free parachain heads
198		/// submissions. The parachain head submission will be free if:
199		///
200		/// 1) the submission contains exactly one parachain head update that succeeds;
201		///
202		/// 2) the difference between relay chain block numbers, used to prove new parachain head
203		///    and previous best parachain head is larger than the `FreeHeadersInterval`, configured
204		///    at the associated GRANDPA pallet;
205		///
206		/// 3) there are slots for free submissions, remaining at the block. This is also configured
207		///    at the associated GRANDPA pallet using `MaxFreeHeadersPerBlock` parameter.
208		///
209		/// First parachain head submission is also free for the submitted, if free submissions
210		/// are yet accepted to this block.
211		type BridgesGrandpaPalletInstance: 'static;
212
213		/// Name of the original `paras` pallet in the `construct_runtime!()` call at the bridged
214		/// chain.
215		///
216		/// Please keep in mind that this should be the name of the `runtime_parachains::paras`
217		/// pallet from polkadot repository, not the `pallet-bridge-parachains`.
218		#[pallet::constant]
219		type ParasPalletName: Get<&'static str>;
220
221		/// Parachain head data builder.
222		///
223		/// We never store parachain heads here, since they may be too big (e.g. because of large
224		/// digest items). Instead we're using the same approach as `pallet-bridge-grandpa`
225		/// pallet - we are only storing `bp_messages::StoredHeaderData` (number and state root),
226		/// which is enough for our applications. However, we work with different parachains here
227		/// and they can use different primitives (for block numbers and hash). So we can't store
228		/// it directly. Instead, we're storing `bp_messages::StoredHeaderData` in SCALE-encoded
229		/// form, wrapping it into `bp_parachains::ParaStoredHeaderData`.
230		///
231		/// This builder helps to convert from `HeadData` to `bp_parachains::ParaStoredHeaderData`.
232		type ParaStoredHeaderDataBuilder: ParaStoredHeaderDataBuilder;
233
234		/// Maximal number of single parachain heads to keep in the storage.
235		///
236		/// The setting is there to prevent growing the on-chain state indefinitely. Note
237		/// the setting does not relate to parachain block numbers - we will simply keep as much
238		/// items in the storage, so it doesn't guarantee any fixed timeframe for heads.
239		///
240		/// Incautious change of this constant may lead to orphan entries in the runtime storage.
241		#[pallet::constant]
242		type HeadsToKeep: Get<u32>;
243
244		/// Maximal size (in bytes) of the SCALE-encoded parachain head data
245		/// (`bp_parachains::ParaStoredHeaderData`).
246		///
247		/// Keep in mind that the size of any tracked parachain header data must not exceed this
248		/// value. So if you're going to track multiple parachains, one of which is using large
249		/// hashes, you shall choose this maximal value.
250		///
251		/// There's no mandatory headers in this pallet, so it can't stall if there's some header
252		/// that exceeds this bound.
253		#[pallet::constant]
254		type MaxParaHeadDataSize: Get<u32>;
255
256		/// Runtime hook for when a parachain head is updated.
257		type OnNewHead: OnNewHead;
258	}
259
260	/// Optional pallet owner.
261	///
262	/// Pallet owner has a right to halt all pallet operations and then resume them. If it is
263	/// `None`, then there are no direct ways to halt/resume pallet operations, but other
264	/// runtime methods may still be used to do that (i.e. democracy::referendum to update halt
265	/// flag directly or call the `set_operating_mode`).
266	#[pallet::storage]
267	pub type PalletOwner<T: Config<I>, I: 'static = ()> =
268		StorageValue<_, T::AccountId, OptionQuery>;
269
270	/// The current operating mode of the pallet.
271	///
272	/// Depending on the mode either all, or no transactions will be allowed.
273	#[pallet::storage]
274	pub type PalletOperatingMode<T: Config<I>, I: 'static = ()> =
275		StorageValue<_, BasicOperatingMode, ValueQuery>;
276
277	/// Parachains info.
278	///
279	/// Contains the following info:
280	/// - best parachain head hash
281	/// - the head of the `ImportedParaHashes` ring buffer
282	#[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	/// State roots of parachain heads which have been imported into the pallet.
293	#[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	/// A ring buffer of imported parachain head hashes. Ordered by the insertion time.
306	#[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		/// Submit proof of one or several parachain heads.
331		///
332		/// The proof is supposed to be proof of some `Heads` entries from the
333		/// `polkadot-runtime-parachains::paras` pallet instance, deployed at the bridged chain.
334		/// The proof is supposed to be crafted at the `relay_header_hash` that must already be
335		/// imported by corresponding GRANDPA pallet at this chain.
336		///
337		/// The call fails if:
338		///
339		/// - the pallet is halted;
340		///
341		/// - the relay chain block `at_relay_block` is not imported by the associated bridge
342		///   GRANDPA pallet.
343		///
344		/// The call may succeed, but some heads may not be updated e.g. because pallet knows
345		/// better head or it isn't tracked by the pallet.
346		#[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		/// Change `PalletOwner`.
368		///
369		/// May only be called either by root, or by `PalletOwner`.
370		#[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		/// Halt or resume all pallet operations.
377		///
378		/// May only be called either by root, or by `PalletOwner`.
379		#[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		/// Submit proof of one or several parachain heads.
389		///
390		/// The proof is supposed to be proof of some `Heads` entries from the
391		/// `polkadot-runtime-parachains::paras` pallet instance, deployed at the bridged chain.
392		/// The proof is supposed to be crafted at the `relay_header_hash` that must already be
393		/// imported by corresponding GRANDPA pallet at this chain.
394		///
395		/// The call fails if:
396		///
397		/// - the pallet is halted;
398		///
399		/// - the relay chain block `at_relay_block` is not imported by the associated bridge
400		///   GRANDPA pallet.
401		///
402		/// The call may succeed, but some heads may not be updated e.g. because pallet knows
403		/// better head or it isn't tracked by the pallet.
404		///
405		/// The `is_free_execution_expected` parameter is not really used inside the call. It is
406		/// used by the transaction extension, which should be registered at the runtime level. If
407		/// this parameter is `true`, the transaction will be treated as invalid, if the call won't
408		/// be executed for free. If transaction extension is not used by the runtime, this
409		/// parameter is not used at all.
410		#[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			// the pallet allows two kind of free submissions
430			// 1) if distance between all parachain heads is gte than the [`T::FreeHeadersInterval`]
431			// 2) if all heads are the first heads of their parachains
432			let mut free_parachain_heads = 0;
433
434			// we'll need relay chain header to verify that parachains heads are always increasing.
435			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			// now parse storage proof and read parachain heads
447			let mut actual_weight = WeightInfoOf::<T, I>::submit_parachain_heads_weight(
448				T::DbWeight::get(),
449				&parachain_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				// if relayer has specified invalid parachain head hash, ignore the head
490				// (this isn't strictly necessary, but better safe than sorry)
491				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				// convert from parachain head into stored parachain head data
509				let parachain_head_size = parachain_head.0.len();
510				let parachain_head_data =
511					match T::ParaStoredHeaderDataBuilder::try_build(parachain, &parachain_head) {
512						Some(parachain_head_data) => parachain_head_data,
513						None => {
514							tracing::trace!(
515								target: LOG_TARGET,
516								?parachain,
517								"The head of parachain has been provided, but it is not tracked by the pallet"
518							);
519							Self::deposit_event(Event::UntrackedParachainRejected { parachain });
520							continue;
521						},
522					};
523
524				let update_result: Result<_, ()> =
525					ParasInfo::<T, I>::try_mutate(parachain, |stored_best_head| {
526						let is_free = parachain_head_size <
527							T::ParaStoredHeaderDataBuilder::max_free_head_size() as usize &&
528							match stored_best_head {
529								Some(ref best_head)
530									if at_relay_block.0.saturating_sub(
531										best_head.best_head_hash.at_relay_block_number,
532									) >= free_headers_interval =>
533								{
534									true
535								},
536								Some(_) => false,
537								None => true,
538							};
539						let artifacts = Pallet::<T, I>::update_parachain_head(
540							parachain,
541							stored_best_head.take(),
542							HeaderId(relay_block_number, relay_block_hash),
543							parachain_head_data,
544							parachain_head_hash,
545							parachain_head,
546						)?;
547
548						if is_free {
549							free_parachain_heads = free_parachain_heads + 1;
550						}
551
552						*stored_best_head = Some(artifacts.best_head);
553						Ok(artifacts.prune_happened)
554					});
555
556				// we're refunding weight if update has not happened and if pruning has not happened
557				let is_update_happened = update_result.is_ok();
558				if !is_update_happened {
559					actual_weight = actual_weight.saturating_sub(
560						WeightInfoOf::<T, I>::parachain_head_storage_write_weight(
561							T::DbWeight::get(),
562						),
563					);
564				}
565				let is_prune_happened = matches!(update_result, Ok(true));
566				if !is_prune_happened {
567					actual_weight = actual_weight.saturating_sub(
568						WeightInfoOf::<T, I>::parachain_head_pruning_weight(T::DbWeight::get()),
569					);
570				}
571			}
572
573			// even though we may have accepted some parachain heads, we can't allow relayers to
574			// submit proof with unused trie nodes
575			// => treat this as an error
576			//
577			// (we can throw error here, because now all our calls are transactional)
578			storage.ensure_no_unused_keys().map_err(|e| {
579				Error::<T, I>::HeaderChainStorageProof(HeaderChainError::StorageProof(e))
580			})?;
581
582			// check if we allow this submission for free
583			let is_free = total_parachains == 1
584				&& free_parachain_heads == total_parachains
585				&& SubmitFinalityProofHelper::<T, T::BridgesGrandpaPalletInstance>::has_free_header_slots();
586			let pays_fee = if is_free {
587				tracing::trace!(target: LOG_TARGET, "Parachain heads update transaction is free");
588				pallet_bridge_grandpa::on_free_header_imported::<T, T::BridgesGrandpaPalletInstance>(
589				);
590				Pays::No
591			} else {
592				tracing::trace!(target: LOG_TARGET, "Parachain heads update transaction is paid");
593				Pays::Yes
594			};
595
596			Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee })
597		}
598	}
599
600	impl<T: Config<I>, I: 'static> Pallet<T, I> {
601		/// Get stored parachain info.
602		pub fn best_parachain_info(parachain: ParaId) -> Option<ParaInfo> {
603			ParasInfo::<T, I>::get(parachain)
604		}
605
606		/// Get best finalized head data of the given parachain.
607		pub fn best_parachain_head(parachain: ParaId) -> Option<ParaStoredHeaderData> {
608			let best_para_head_hash = ParasInfo::<T, I>::get(parachain)?.best_head_hash.head_hash;
609			ImportedParaHeads::<T, I>::get(parachain, best_para_head_hash).map(|h| h.into_inner())
610		}
611
612		/// Get best finalized head hash of the given parachain.
613		pub fn best_parachain_head_hash(parachain: ParaId) -> Option<ParaHash> {
614			Some(ParasInfo::<T, I>::get(parachain)?.best_head_hash.head_hash)
615		}
616
617		/// Get best finalized head id of the given parachain.
618		pub fn best_parachain_head_id<C: Chain<Hash = ParaHash> + Parachain>(
619		) -> Result<Option<HeaderIdOf<C>>, codec::Error> {
620			let parachain = ParaId(C::PARACHAIN_ID);
621			let best_head_hash = match Self::best_parachain_head_hash(parachain) {
622				Some(best_head_hash) => best_head_hash,
623				None => return Ok(None),
624			};
625			let encoded_head = match Self::parachain_head(parachain, best_head_hash) {
626				Some(encoded_head) => encoded_head,
627				None => return Ok(None),
628			};
629			encoded_head
630				.decode_parachain_head_data::<C>()
631				.map(|data| Some(HeaderId(data.number, best_head_hash)))
632		}
633
634		/// Get parachain head data with given hash.
635		pub fn parachain_head(parachain: ParaId, hash: ParaHash) -> Option<ParaStoredHeaderData> {
636			ImportedParaHeads::<T, I>::get(parachain, hash).map(|h| h.into_inner())
637		}
638
639		/// Try to update parachain head.
640		pub(super) fn update_parachain_head(
641			parachain: ParaId,
642			stored_best_head: Option<ParaInfo>,
643			new_at_relay_block: HeaderId<RelayBlockHash, RelayBlockNumber>,
644			new_head_data: ParaStoredHeaderData,
645			new_head_hash: ParaHash,
646			new_head: ParaHead,
647		) -> Result<UpdateParachainHeadArtifacts, ()> {
648			// check if head has been already updated at better relay chain block. Without this
649			// check, we may import heads in random order
650			let update = SubmitParachainHeadsInfo {
651				at_relay_block: new_at_relay_block,
652				para_id: parachain,
653				para_head_hash: new_head_hash,
654				// doesn't actually matter here
655				is_free_execution_expected: false,
656			};
657			if SubmitParachainHeadsHelper::<T, I>::check_obsolete(&update).is_err() {
658				Self::deposit_event(Event::RejectedObsoleteParachainHead {
659					parachain,
660					parachain_head_hash: new_head_hash,
661				});
662				return Err(());
663			}
664
665			// verify that the parachain head data size is <= `MaxParaHeadDataSize`
666			let updated_head_data = match StoredParaHeadDataOf::<T, I>::try_from_inner(
667				new_head_data,
668			) {
669				Ok(updated_head_data) => updated_head_data,
670				Err(e) => {
671					tracing::trace!(
672						target: LOG_TARGET,
673						error=?e,
674						?parachain,
675						"The parachain head can't be updated. The parachain head data size exceeds maximal configured size."
676					);
677
678					Self::deposit_event(Event::RejectedLargeParachainHead {
679						parachain,
680						parachain_head_hash: new_head_hash,
681						parachain_head_size: e.value_size as _,
682					});
683
684					return Err(());
685				},
686			};
687
688			let next_imported_hash_position = stored_best_head
689				.map_or(0, |stored_best_head| stored_best_head.next_imported_hash_position);
690
691			// insert updated best parachain head
692			let head_hash_to_prune =
693				ImportedParaHashes::<T, I>::try_get(parachain, next_imported_hash_position);
694			let updated_best_para_head = ParaInfo {
695				best_head_hash: BestParaHeadHash {
696					at_relay_block_number: new_at_relay_block.0,
697					head_hash: new_head_hash,
698				},
699				next_imported_hash_position: (next_imported_hash_position + 1) %
700					T::HeadsToKeep::get(),
701			};
702			ImportedParaHashes::<T, I>::insert(
703				parachain,
704				next_imported_hash_position,
705				new_head_hash,
706			);
707			ImportedParaHeads::<T, I>::insert(parachain, new_head_hash, &updated_head_data);
708			tracing::trace!(
709				target: LOG_TARGET,
710				?parachain,
711				%new_head_hash,
712				at_relay_block=%new_at_relay_block.0,
713				"Updated head of parachain"
714			);
715
716			// trigger callback
717			T::OnNewHead::on_new_head(parachain, &new_head);
718
719			// remove old head
720			let prune_happened = head_hash_to_prune
721				.as_ref()
722				.is_ok_and(|head_hash_to_prune| head_hash_to_prune != &new_head_hash);
723			if let Some(head_hash_to_prune) = head_hash_to_prune.ok().filter(|_| prune_happened) {
724				tracing::trace!(
725					target: LOG_TARGET,
726					?parachain,
727					%head_hash_to_prune,
728					"Pruning old head of parachain"
729				);
730				ImportedParaHeads::<T, I>::remove(parachain, head_hash_to_prune);
731			}
732			Self::deposit_event(Event::UpdatedParachainHead {
733				parachain,
734				parachain_head_hash: new_head_hash,
735			});
736
737			Ok(UpdateParachainHeadArtifacts { best_head: updated_best_para_head, prune_happened })
738		}
739	}
740
741	#[pallet::genesis_config]
742	#[derive(DefaultNoBound)]
743	pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
744		/// Initial pallet operating mode.
745		pub operating_mode: BasicOperatingMode,
746		/// Initial pallet owner.
747		pub owner: Option<T::AccountId>,
748		/// Dummy marker.
749		#[serde(skip)]
750		pub _phantom: sp_std::marker::PhantomData<I>,
751	}
752
753	#[pallet::genesis_build]
754	impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
755		fn build(&self) {
756			PalletOperatingMode::<T, I>::put(self.operating_mode);
757			if let Some(ref owner) = self.owner {
758				PalletOwner::<T, I>::put(owner);
759			}
760		}
761	}
762
763	/// Returns maximal number of parachains, supported by the pallet.
764	pub struct MaybeMaxParachains<T, I>(PhantomData<(T, I)>);
765
766	impl<T: Config<I>, I: 'static> Get<Option<u32>> for MaybeMaxParachains<T, I> {
767		fn get() -> Option<u32> {
768			Some(T::ParaStoredHeaderDataBuilder::supported_parachains())
769		}
770	}
771
772	/// Returns total number of all parachains hashes/heads, stored by the pallet.
773	pub struct MaybeMaxTotalParachainHashes<T, I>(PhantomData<(T, I)>);
774
775	impl<T: Config<I>, I: 'static> Get<Option<u32>> for MaybeMaxTotalParachainHashes<T, I> {
776		fn get() -> Option<u32> {
777			Some(
778				T::ParaStoredHeaderDataBuilder::supported_parachains()
779					.saturating_mul(T::HeadsToKeep::get()),
780			)
781		}
782	}
783}
784
785/// Single parachain header chain adapter.
786pub struct ParachainHeaders<T, I, C>(PhantomData<(T, I, C)>);
787
788impl<T: Config<I>, I: 'static, C: Parachain<Hash = ParaHash>> HeaderChain<C>
789	for ParachainHeaders<T, I, C>
790{
791	fn finalized_header_state_root(hash: HashOf<C>) -> Option<HashOf<C>> {
792		Pallet::<T, I>::parachain_head(ParaId(C::PARACHAIN_ID), hash)
793			.and_then(|head| head.decode_parachain_head_data::<C>().ok())
794			.map(|h| h.state_root)
795	}
796}
797
798/// (Re)initialize pallet with given header for using it in `pallet-bridge-messages` benchmarks.
799#[cfg(feature = "runtime-benchmarks")]
800pub fn initialize_for_benchmarks<T: Config<I>, I: 'static, PC: Parachain<Hash = ParaHash>>(
801	header: HeaderOf<PC>,
802) {
803	use bp_polkadot_core::parachains::ParaHead;
804	use bp_runtime::HeaderIdProvider;
805	use sp_runtime::traits::Header;
806
807	let relay_head =
808		pallet_bridge_grandpa::BridgedHeader::<T, T::BridgesGrandpaPalletInstance>::new(
809			0,
810			Default::default(),
811			Default::default(),
812			Default::default(),
813			Default::default(),
814		);
815	let parachain = ParaId(PC::PARACHAIN_ID);
816	let parachain_head = ParaHead(header.encode());
817	let updated_head_data = T::ParaStoredHeaderDataBuilder::try_build(parachain, &parachain_head)
818		.expect("failed to build stored parachain head in benchmarks");
819	pallet_bridge_grandpa::initialize_for_benchmarks::<T, T::BridgesGrandpaPalletInstance>(
820		relay_head.clone(),
821	);
822	Pallet::<T, I>::update_parachain_head(
823		parachain,
824		None,
825		relay_head.id(),
826		updated_head_data,
827		parachain_head.hash(),
828		parachain_head,
829	)
830	.expect("failed to insert parachain head in benchmarks");
831}
832
833#[cfg(test)]
834pub(crate) mod tests {
835	use super::*;
836	use crate::mock::{
837		run_test, test_relay_header, BigParachain, BigParachainHeader, FreeHeadersInterval,
838		RegularParachainHasher, RegularParachainHeader, RelayBlockHeader,
839		RuntimeEvent as TestEvent, RuntimeOrigin, TestRuntime, UNTRACKED_PARACHAIN_ID,
840	};
841	use bp_test_utils::prepare_parachain_heads_proof;
842	use codec::Encode;
843
844	use bp_header_chain::{justification::GrandpaJustification, StoredHeaderGrandpaInfo};
845	use bp_parachains::{
846		BestParaHeadHash, BridgeParachainCall, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider,
847	};
848	use bp_polkadot_core::parachains::ParaHead;
849	use bp_runtime::{
850		BasicOperatingMode, OwnedBridgeModuleError, StorageDoubleMapKeyProvider,
851		StorageMapKeyProvider, StorageProofError,
852	};
853	use bp_test_utils::{
854		authority_list, generate_owned_bridge_module_tests, make_default_justification,
855		TEST_GRANDPA_SET_ID,
856	};
857	use frame_support::{
858		assert_noop, assert_ok,
859		dispatch::DispatchResultWithPostInfo,
860		pallet_prelude::Pays,
861		storage::generator::{StorageDoubleMap, StorageMap},
862		traits::Get,
863		weights::Weight,
864	};
865	use frame_system::{EventRecord, Pallet as System, Phase};
866	use sp_core::Hasher;
867	use sp_runtime::{traits::Header as HeaderT, DispatchError};
868
869	type BridgesGrandpaPalletInstance = pallet_bridge_grandpa::Instance1;
870	type WeightInfo = <TestRuntime as Config>::WeightInfo;
871	type DbWeight = <TestRuntime as frame_system::Config>::DbWeight;
872
873	pub(crate) fn initialize(state_root: RelayBlockHash) -> RelayBlockHash {
874		pallet_bridge_grandpa::FreeHeadersRemaining::<TestRuntime, BridgesGrandpaPalletInstance>::set(Some(100));
875		pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::initialize(
876			RuntimeOrigin::root(),
877			bp_header_chain::InitializationData {
878				header: Box::new(test_relay_header(0, state_root)),
879				authority_list: authority_list(),
880				set_id: 1,
881				operating_mode: BasicOperatingMode::Normal,
882			},
883		)
884		.unwrap();
885
886		System::<TestRuntime>::set_block_number(1);
887		System::<TestRuntime>::reset_events();
888
889		test_relay_header(0, state_root).hash()
890	}
891
892	fn proceed(
893		num: RelayBlockNumber,
894		state_root: RelayBlockHash,
895	) -> (ParaHash, GrandpaJustification<RelayBlockHeader>) {
896		let header = test_relay_header(num, state_root);
897		let hash = header.hash();
898		let justification = make_default_justification(&header);
899		assert_ok!(
900			pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::submit_finality_proof_ex(
901				RuntimeOrigin::signed(1),
902				Box::new(header),
903				justification.clone(),
904				TEST_GRANDPA_SET_ID,
905				false,
906			)
907		);
908
909		(hash, justification)
910	}
911
912	fn initial_best_head(parachain: u32) -> ParaInfo {
913		ParaInfo {
914			best_head_hash: BestParaHeadHash {
915				at_relay_block_number: 0,
916				head_hash: head_data(parachain, 0).hash(),
917			},
918			next_imported_hash_position: 1,
919		}
920	}
921
922	pub(crate) fn head_data(parachain: u32, head_number: u32) -> ParaHead {
923		ParaHead(
924			RegularParachainHeader::new(
925				head_number as _,
926				Default::default(),
927				RegularParachainHasher::hash(&(parachain, head_number).encode()),
928				Default::default(),
929				Default::default(),
930			)
931			.encode(),
932		)
933	}
934
935	fn stored_head_data(parachain: u32, head_number: u32) -> ParaStoredHeaderData {
936		ParaStoredHeaderData(
937			(head_number as u64, RegularParachainHasher::hash(&(parachain, head_number).encode()))
938				.encode(),
939		)
940	}
941
942	fn big_head_data(parachain: u32, head_number: u32) -> ParaHead {
943		ParaHead(
944			BigParachainHeader::new(
945				head_number as _,
946				Default::default(),
947				RegularParachainHasher::hash(&(parachain, head_number).encode()),
948				Default::default(),
949				Default::default(),
950			)
951			.encode(),
952		)
953	}
954
955	fn big_stored_head_data(parachain: u32, head_number: u32) -> ParaStoredHeaderData {
956		ParaStoredHeaderData(
957			(head_number as u128, RegularParachainHasher::hash(&(parachain, head_number).encode()))
958				.encode(),
959		)
960	}
961
962	fn head_hash(parachain: u32, head_number: u32) -> ParaHash {
963		head_data(parachain, head_number).hash()
964	}
965
966	fn import_parachain_1_head(
967		relay_chain_block: RelayBlockNumber,
968		relay_state_root: RelayBlockHash,
969		parachains: Vec<(ParaId, ParaHash)>,
970		proof: ParaHeadsProof,
971	) -> DispatchResultWithPostInfo {
972		Pallet::<TestRuntime>::submit_parachain_heads(
973			RuntimeOrigin::signed(1),
974			(relay_chain_block, test_relay_header(relay_chain_block, relay_state_root).hash()),
975			parachains,
976			proof,
977		)
978	}
979
980	fn weight_of_import_parachain_1_head(proof: &ParaHeadsProof, prune_expected: bool) -> Weight {
981		let db_weight = <TestRuntime as frame_system::Config>::DbWeight::get();
982		WeightInfoOf::<TestRuntime, ()>::submit_parachain_heads_weight(db_weight, proof, 1)
983			.saturating_sub(if prune_expected {
984				Weight::zero()
985			} else {
986				WeightInfoOf::<TestRuntime, ()>::parachain_head_pruning_weight(db_weight)
987			})
988	}
989
990	#[test]
991	fn submit_parachain_heads_checks_operating_mode() {
992		let (state_root, proof, parachains) =
993			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
994
995		run_test(|| {
996			initialize(state_root);
997
998			// `submit_parachain_heads()` should fail when the pallet is halted.
999			PalletOperatingMode::<TestRuntime>::put(BasicOperatingMode::Halted);
1000			assert_noop!(
1001				Pallet::<TestRuntime>::submit_parachain_heads(
1002					RuntimeOrigin::signed(1),
1003					(0, test_relay_header(0, state_root).hash()),
1004					parachains.clone(),
1005					proof.clone(),
1006				),
1007				Error::<TestRuntime>::BridgeModule(OwnedBridgeModuleError::Halted)
1008			);
1009
1010			// `submit_parachain_heads()` should succeed now that the pallet is resumed.
1011			PalletOperatingMode::<TestRuntime>::put(BasicOperatingMode::Normal);
1012			assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1013				RuntimeOrigin::signed(1),
1014				(0, test_relay_header(0, state_root).hash()),
1015				parachains,
1016				proof,
1017			),);
1018		});
1019	}
1020
1021	#[test]
1022	fn imports_initial_parachain_heads() {
1023		let (state_root, proof, parachains) =
1024			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![
1025				(1, head_data(1, 0)),
1026				(3, head_data(3, 10)),
1027			]);
1028		run_test(|| {
1029			initialize(state_root);
1030
1031			// we're trying to update heads of parachains 1 and 3
1032			let expected_weight =
1033				WeightInfo::submit_parachain_heads_weight(DbWeight::get(), &proof, 2);
1034			let result = Pallet::<TestRuntime>::submit_parachain_heads(
1035				RuntimeOrigin::signed(1),
1036				(0, test_relay_header(0, state_root).hash()),
1037				parachains,
1038				proof,
1039			);
1040			assert_ok!(result);
1041			assert_eq!(result.expect("checked above").pays_fee, Pays::Yes);
1042			assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight));
1043
1044			// 1 and 3 are updated, because proof is missing head of parachain#2
1045			assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
1046			assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(2)), None);
1047			assert_eq!(
1048				ParasInfo::<TestRuntime>::get(ParaId(3)),
1049				Some(ParaInfo {
1050					best_head_hash: BestParaHeadHash {
1051						at_relay_block_number: 0,
1052						head_hash: head_data(3, 10).hash()
1053					},
1054					next_imported_hash_position: 1,
1055				})
1056			);
1057
1058			assert_eq!(
1059				ImportedParaHeads::<TestRuntime>::get(
1060					ParaId(1),
1061					initial_best_head(1).best_head_hash.head_hash
1062				)
1063				.map(|h| h.into_inner()),
1064				Some(stored_head_data(1, 0))
1065			);
1066			assert_eq!(
1067				ImportedParaHeads::<TestRuntime>::get(
1068					ParaId(2),
1069					initial_best_head(2).best_head_hash.head_hash
1070				)
1071				.map(|h| h.into_inner()),
1072				None
1073			);
1074			assert_eq!(
1075				ImportedParaHeads::<TestRuntime>::get(ParaId(3), head_hash(3, 10))
1076					.map(|h| h.into_inner()),
1077				Some(stored_head_data(3, 10))
1078			);
1079
1080			assert_eq!(
1081				System::<TestRuntime>::events(),
1082				vec![
1083					EventRecord {
1084						phase: Phase::Initialization,
1085						event: TestEvent::Parachains(Event::UpdatedParachainHead {
1086							parachain: ParaId(1),
1087							parachain_head_hash: initial_best_head(1).best_head_hash.head_hash,
1088						}),
1089						topics: vec![],
1090					},
1091					EventRecord {
1092						phase: Phase::Initialization,
1093						event: TestEvent::Parachains(Event::UpdatedParachainHead {
1094							parachain: ParaId(3),
1095							parachain_head_hash: head_data(3, 10).hash(),
1096						}),
1097						topics: vec![],
1098					}
1099				],
1100			);
1101		});
1102	}
1103
1104	#[test]
1105	fn imports_parachain_heads_is_able_to_progress() {
1106		let (state_root_5, proof_5, parachains_5) =
1107			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 5))]);
1108		let (state_root_10, proof_10, parachains_10) =
1109			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 10))]);
1110		run_test(|| {
1111			// start with relay block #0 and import head#5 of parachain#1
1112			initialize(state_root_5);
1113			let result = import_parachain_1_head(0, state_root_5, parachains_5, proof_5);
1114			// first parachain head is imported for free
1115			assert_eq!(result.unwrap().pays_fee, Pays::No);
1116			assert_eq!(
1117				ParasInfo::<TestRuntime>::get(ParaId(1)),
1118				Some(ParaInfo {
1119					best_head_hash: BestParaHeadHash {
1120						at_relay_block_number: 0,
1121						head_hash: head_data(1, 5).hash()
1122					},
1123					next_imported_hash_position: 1,
1124				})
1125			);
1126			assert_eq!(
1127				ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 5).hash())
1128					.map(|h| h.into_inner()),
1129				Some(stored_head_data(1, 5))
1130			);
1131			assert_eq!(
1132				ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 10).hash())
1133					.map(|h| h.into_inner()),
1134				None
1135			);
1136			assert_eq!(
1137				System::<TestRuntime>::events(),
1138				vec![EventRecord {
1139					phase: Phase::Initialization,
1140					event: TestEvent::Parachains(Event::UpdatedParachainHead {
1141						parachain: ParaId(1),
1142						parachain_head_hash: head_data(1, 5).hash(),
1143					}),
1144					topics: vec![],
1145				}],
1146			);
1147
1148			// import head#10 of parachain#1 at relay block #1
1149			let (relay_1_hash, justification) = proceed(1, state_root_10);
1150			let result = import_parachain_1_head(1, state_root_10, parachains_10, proof_10);
1151			// second parachain head is imported for fee
1152			assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1153			assert_eq!(
1154				ParasInfo::<TestRuntime>::get(ParaId(1)),
1155				Some(ParaInfo {
1156					best_head_hash: BestParaHeadHash {
1157						at_relay_block_number: 1,
1158						head_hash: head_data(1, 10).hash()
1159					},
1160					next_imported_hash_position: 2,
1161				})
1162			);
1163			assert_eq!(
1164				ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 5).hash())
1165					.map(|h| h.into_inner()),
1166				Some(stored_head_data(1, 5))
1167			);
1168			assert_eq!(
1169				ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 10).hash())
1170					.map(|h| h.into_inner()),
1171				Some(stored_head_data(1, 10))
1172			);
1173			assert_eq!(
1174				System::<TestRuntime>::events(),
1175				vec![
1176					EventRecord {
1177						phase: Phase::Initialization,
1178						event: TestEvent::Parachains(Event::UpdatedParachainHead {
1179							parachain: ParaId(1),
1180							parachain_head_hash: head_data(1, 5).hash(),
1181						}),
1182						topics: vec![],
1183					},
1184					EventRecord {
1185						phase: Phase::Initialization,
1186						event: TestEvent::Grandpa1(
1187							pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
1188								number: 1,
1189								hash: relay_1_hash,
1190								grandpa_info: StoredHeaderGrandpaInfo {
1191									finality_proof: justification,
1192									new_verification_context: None,
1193								},
1194							}
1195						),
1196						topics: vec![],
1197					},
1198					EventRecord {
1199						phase: Phase::Initialization,
1200						event: TestEvent::Parachains(Event::UpdatedParachainHead {
1201							parachain: ParaId(1),
1202							parachain_head_hash: head_data(1, 10).hash(),
1203						}),
1204						topics: vec![],
1205					}
1206				],
1207			);
1208		});
1209	}
1210
1211	#[test]
1212	fn ignores_untracked_parachain() {
1213		let (state_root, proof, parachains) =
1214			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![
1215				(1, head_data(1, 5)),
1216				(UNTRACKED_PARACHAIN_ID, head_data(1, 5)),
1217				(2, head_data(1, 5)),
1218			]);
1219		run_test(|| {
1220			// start with relay block #0 and try to import head#5 of parachain#1 and untracked
1221			// parachain
1222			let expected_weight =
1223				WeightInfo::submit_parachain_heads_weight(DbWeight::get(), &proof, 3)
1224					.saturating_sub(WeightInfo::parachain_head_storage_write_weight(
1225						DbWeight::get(),
1226					));
1227			initialize(state_root);
1228			let result = Pallet::<TestRuntime>::submit_parachain_heads(
1229				RuntimeOrigin::signed(1),
1230				(0, test_relay_header(0, state_root).hash()),
1231				parachains,
1232				proof,
1233			);
1234			assert_ok!(result);
1235			assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight));
1236			assert_eq!(
1237				ParasInfo::<TestRuntime>::get(ParaId(1)),
1238				Some(ParaInfo {
1239					best_head_hash: BestParaHeadHash {
1240						at_relay_block_number: 0,
1241						head_hash: head_data(1, 5).hash()
1242					},
1243					next_imported_hash_position: 1,
1244				})
1245			);
1246			assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(UNTRACKED_PARACHAIN_ID)), None,);
1247			assert_eq!(
1248				ParasInfo::<TestRuntime>::get(ParaId(2)),
1249				Some(ParaInfo {
1250					best_head_hash: BestParaHeadHash {
1251						at_relay_block_number: 0,
1252						head_hash: head_data(1, 5).hash()
1253					},
1254					next_imported_hash_position: 1,
1255				})
1256			);
1257			assert_eq!(
1258				System::<TestRuntime>::events(),
1259				vec![
1260					EventRecord {
1261						phase: Phase::Initialization,
1262						event: TestEvent::Parachains(Event::UpdatedParachainHead {
1263							parachain: ParaId(1),
1264							parachain_head_hash: head_data(1, 5).hash(),
1265						}),
1266						topics: vec![],
1267					},
1268					EventRecord {
1269						phase: Phase::Initialization,
1270						event: TestEvent::Parachains(Event::UntrackedParachainRejected {
1271							parachain: ParaId(UNTRACKED_PARACHAIN_ID),
1272						}),
1273						topics: vec![],
1274					},
1275					EventRecord {
1276						phase: Phase::Initialization,
1277						event: TestEvent::Parachains(Event::UpdatedParachainHead {
1278							parachain: ParaId(2),
1279							parachain_head_hash: head_data(1, 5).hash(),
1280						}),
1281						topics: vec![],
1282					}
1283				],
1284			);
1285		});
1286	}
1287
1288	#[test]
1289	fn does_nothing_when_already_imported_this_head_at_previous_relay_header() {
1290		let (state_root, proof, parachains) =
1291			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
1292		run_test(|| {
1293			// import head#0 of parachain#1 at relay block#0
1294			initialize(state_root);
1295			assert_ok!(import_parachain_1_head(0, state_root, parachains.clone(), proof.clone()));
1296			assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
1297			assert_eq!(
1298				System::<TestRuntime>::events(),
1299				vec![EventRecord {
1300					phase: Phase::Initialization,
1301					event: TestEvent::Parachains(Event::UpdatedParachainHead {
1302						parachain: ParaId(1),
1303						parachain_head_hash: initial_best_head(1).best_head_hash.head_hash,
1304					}),
1305					topics: vec![],
1306				}],
1307			);
1308
1309			// try to import head#0 of parachain#1 at relay block#1
1310			// => call succeeds, but nothing is changed
1311			let (relay_1_hash, justification) = proceed(1, state_root);
1312			assert_ok!(import_parachain_1_head(1, state_root, parachains, proof));
1313			assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(1)), Some(initial_best_head(1)));
1314			assert_eq!(
1315				System::<TestRuntime>::events(),
1316				vec![
1317					EventRecord {
1318						phase: Phase::Initialization,
1319						event: TestEvent::Parachains(Event::UpdatedParachainHead {
1320							parachain: ParaId(1),
1321							parachain_head_hash: initial_best_head(1).best_head_hash.head_hash,
1322						}),
1323						topics: vec![],
1324					},
1325					EventRecord {
1326						phase: Phase::Initialization,
1327						event: TestEvent::Grandpa1(
1328							pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
1329								number: 1,
1330								hash: relay_1_hash,
1331								grandpa_info: StoredHeaderGrandpaInfo {
1332									finality_proof: justification,
1333									new_verification_context: None,
1334								}
1335							}
1336						),
1337						topics: vec![],
1338					},
1339					EventRecord {
1340						phase: Phase::Initialization,
1341						event: TestEvent::Parachains(Event::RejectedObsoleteParachainHead {
1342							parachain: ParaId(1),
1343							parachain_head_hash: initial_best_head(1).best_head_hash.head_hash,
1344						}),
1345						topics: vec![],
1346					}
1347				],
1348			);
1349		});
1350	}
1351
1352	#[test]
1353	fn does_nothing_when_already_imported_head_at_better_relay_header() {
1354		let (state_root_5, proof_5, parachains_5) =
1355			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 5))]);
1356		let (state_root_10, proof_10, parachains_10) =
1357			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 10))]);
1358		run_test(|| {
1359			// start with relay block #0
1360			initialize(state_root_5);
1361
1362			// head#10 of parachain#1 at relay block#1
1363			let (relay_1_hash, justification) = proceed(1, state_root_10);
1364			assert_ok!(import_parachain_1_head(1, state_root_10, parachains_10, proof_10));
1365			assert_eq!(
1366				ParasInfo::<TestRuntime>::get(ParaId(1)),
1367				Some(ParaInfo {
1368					best_head_hash: BestParaHeadHash {
1369						at_relay_block_number: 1,
1370						head_hash: head_data(1, 10).hash()
1371					},
1372					next_imported_hash_position: 1,
1373				})
1374			);
1375			assert_eq!(
1376				System::<TestRuntime>::events(),
1377				vec![
1378					EventRecord {
1379						phase: Phase::Initialization,
1380						event: TestEvent::Grandpa1(
1381							pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
1382								number: 1,
1383								hash: relay_1_hash,
1384								grandpa_info: StoredHeaderGrandpaInfo {
1385									finality_proof: justification.clone(),
1386									new_verification_context: None,
1387								}
1388							}
1389						),
1390						topics: vec![],
1391					},
1392					EventRecord {
1393						phase: Phase::Initialization,
1394						event: TestEvent::Parachains(Event::UpdatedParachainHead {
1395							parachain: ParaId(1),
1396							parachain_head_hash: head_data(1, 10).hash(),
1397						}),
1398						topics: vec![],
1399					}
1400				],
1401			);
1402
1403			// now try to import head#5 at relay block#0
1404			// => nothing is changed, because better head has already been imported
1405			assert_ok!(import_parachain_1_head(0, state_root_5, parachains_5, proof_5));
1406			assert_eq!(
1407				ParasInfo::<TestRuntime>::get(ParaId(1)),
1408				Some(ParaInfo {
1409					best_head_hash: BestParaHeadHash {
1410						at_relay_block_number: 1,
1411						head_hash: head_data(1, 10).hash()
1412					},
1413					next_imported_hash_position: 1,
1414				})
1415			);
1416			assert_eq!(
1417				System::<TestRuntime>::events(),
1418				vec![
1419					EventRecord {
1420						phase: Phase::Initialization,
1421						event: TestEvent::Grandpa1(
1422							pallet_bridge_grandpa::Event::UpdatedBestFinalizedHeader {
1423								number: 1,
1424								hash: relay_1_hash,
1425								grandpa_info: StoredHeaderGrandpaInfo {
1426									finality_proof: justification,
1427									new_verification_context: None,
1428								}
1429							}
1430						),
1431						topics: vec![],
1432					},
1433					EventRecord {
1434						phase: Phase::Initialization,
1435						event: TestEvent::Parachains(Event::UpdatedParachainHead {
1436							parachain: ParaId(1),
1437							parachain_head_hash: head_data(1, 10).hash(),
1438						}),
1439						topics: vec![],
1440					},
1441					EventRecord {
1442						phase: Phase::Initialization,
1443						event: TestEvent::Parachains(Event::RejectedObsoleteParachainHead {
1444							parachain: ParaId(1),
1445							parachain_head_hash: head_data(1, 5).hash(),
1446						}),
1447						topics: vec![],
1448					}
1449				],
1450			);
1451		});
1452	}
1453
1454	#[test]
1455	fn does_nothing_when_parachain_head_is_too_large() {
1456		let (state_root, proof, parachains) =
1457			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![
1458				(1, head_data(1, 5)),
1459				(4, big_head_data(1, 5)),
1460			]);
1461		run_test(|| {
1462			// start with relay block #0 and try to import head#5 of parachain#1 and big parachain
1463			initialize(state_root);
1464			let result = Pallet::<TestRuntime>::submit_parachain_heads(
1465				RuntimeOrigin::signed(1),
1466				(0, test_relay_header(0, state_root).hash()),
1467				parachains,
1468				proof,
1469			);
1470			assert_ok!(result);
1471			assert_eq!(
1472				ParasInfo::<TestRuntime>::get(ParaId(1)),
1473				Some(ParaInfo {
1474					best_head_hash: BestParaHeadHash {
1475						at_relay_block_number: 0,
1476						head_hash: head_data(1, 5).hash()
1477					},
1478					next_imported_hash_position: 1,
1479				})
1480			);
1481			assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(4)), None);
1482			assert_eq!(
1483				System::<TestRuntime>::events(),
1484				vec![
1485					EventRecord {
1486						phase: Phase::Initialization,
1487						event: TestEvent::Parachains(Event::UpdatedParachainHead {
1488							parachain: ParaId(1),
1489							parachain_head_hash: head_data(1, 5).hash(),
1490						}),
1491						topics: vec![],
1492					},
1493					EventRecord {
1494						phase: Phase::Initialization,
1495						event: TestEvent::Parachains(Event::RejectedLargeParachainHead {
1496							parachain: ParaId(4),
1497							parachain_head_hash: big_head_data(1, 5).hash(),
1498							parachain_head_size: big_stored_head_data(1, 5).encoded_size() as u32,
1499						}),
1500						topics: vec![],
1501					},
1502				],
1503			);
1504		});
1505	}
1506
1507	#[test]
1508	fn prunes_old_heads() {
1509		run_test(|| {
1510			let heads_to_keep = crate::mock::HeadsToKeep::get();
1511
1512			// import exactly `HeadsToKeep` headers
1513			for i in 0..heads_to_keep {
1514				let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1515					RegularParachainHeader,
1516				>(vec![(1, head_data(1, i))]);
1517				if i == 0 {
1518					initialize(state_root);
1519				} else {
1520					proceed(i, state_root);
1521				}
1522
1523				let expected_weight = weight_of_import_parachain_1_head(&proof, false);
1524				let result = import_parachain_1_head(i, state_root, parachains, proof);
1525				assert_ok!(result);
1526				assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight));
1527			}
1528
1529			// nothing is pruned yet
1530			for i in 0..heads_to_keep {
1531				assert!(ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, i).hash())
1532					.is_some());
1533			}
1534
1535			// import next relay chain header and next parachain head
1536			let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1537				RegularParachainHeader,
1538			>(vec![(1, head_data(1, heads_to_keep))]);
1539			proceed(heads_to_keep, state_root);
1540			let expected_weight = weight_of_import_parachain_1_head(&proof, true);
1541			let result = import_parachain_1_head(heads_to_keep, state_root, parachains, proof);
1542			assert_ok!(result);
1543			assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight));
1544
1545			// and the head#0 is pruned
1546			assert!(
1547				ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 0).hash()).is_none()
1548			);
1549			for i in 1..=heads_to_keep {
1550				assert!(ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, i).hash())
1551					.is_some());
1552			}
1553		});
1554	}
1555
1556	#[test]
1557	fn keeps_reimported_head_when_ring_wraps() {
1558		run_test(|| {
1559			let heads_to_keep = crate::mock::HeadsToKeep::get();
1560
1561			// Fill every ring slot while retaining the first head in storage.
1562			for i in 0..heads_to_keep {
1563				let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1564					RegularParachainHeader,
1565				>(vec![(1, head_data(1, i))]);
1566				if i == 0 {
1567					initialize(state_root);
1568				} else {
1569					proceed(i, state_root);
1570				}
1571
1572				assert_ok!(import_parachain_1_head(i, state_root, parachains, proof));
1573			}
1574
1575			// Re-importing the first head targets the slot containing the same hash. The new
1576			// storage entry must not be deleted by the pruning step.
1577			let (state_root, proof, parachains) =
1578				prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
1579			proceed(heads_to_keep, state_root);
1580			let expected_weight = weight_of_import_parachain_1_head(&proof, false);
1581			let result = import_parachain_1_head(heads_to_keep, state_root, parachains, proof);
1582			assert_ok!(result);
1583			assert_eq!(result.expect("checked above").actual_weight, Some(expected_weight));
1584			assert_eq!(
1585				Pallet::<TestRuntime>::parachain_head(ParaId(1), head_data(1, 0).hash()),
1586				Some(stored_head_data(1, 0)),
1587			);
1588		});
1589	}
1590
1591	#[test]
1592	fn fails_on_unknown_relay_chain_block() {
1593		let (state_root, proof, parachains) =
1594			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 5))]);
1595		run_test(|| {
1596			// start with relay block #0
1597			initialize(state_root);
1598
1599			// try to import head#5 of parachain#1 at unknown relay chain block #1
1600			assert_noop!(
1601				import_parachain_1_head(1, state_root, parachains, proof),
1602				Error::<TestRuntime>::UnknownRelayChainBlock
1603			);
1604		});
1605	}
1606
1607	#[test]
1608	fn fails_on_invalid_storage_proof() {
1609		let (_state_root, proof, parachains) =
1610			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 5))]);
1611		run_test(|| {
1612			// start with relay block #0
1613			initialize(Default::default());
1614
1615			// try to import head#5 of parachain#1 at relay chain block #0
1616			assert_noop!(
1617				import_parachain_1_head(0, Default::default(), parachains, proof),
1618				Error::<TestRuntime>::HeaderChainStorageProof(HeaderChainError::StorageProof(
1619					StorageProofError::StorageRootMismatch
1620				))
1621			);
1622		});
1623	}
1624
1625	#[test]
1626	fn is_not_rewriting_existing_head_if_failed_to_read_updated_head() {
1627		let (state_root_5, proof_5, parachains_5) =
1628			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 5))]);
1629		let (state_root_10_at_20, proof_10_at_20, parachains_10_at_20) =
1630			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(2, head_data(2, 10))]);
1631		let (state_root_10_at_30, proof_10_at_30, parachains_10_at_30) =
1632			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 10))]);
1633		run_test(|| {
1634			// we've already imported head#5 of parachain#1 at relay block#10
1635			initialize(state_root_5);
1636			import_parachain_1_head(0, state_root_5, parachains_5, proof_5).expect("ok");
1637			assert_eq!(
1638				Pallet::<TestRuntime>::best_parachain_head(ParaId(1)),
1639				Some(stored_head_data(1, 5))
1640			);
1641
1642			// then if someone is pretending to provide updated head#10 of parachain#1 at relay
1643			// block#20, but fails to do that
1644			//
1645			// => we'll leave previous value
1646			proceed(20, state_root_10_at_20);
1647			assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1648				RuntimeOrigin::signed(1),
1649				(20, test_relay_header(20, state_root_10_at_20).hash()),
1650				parachains_10_at_20,
1651				proof_10_at_20,
1652			),);
1653			assert_eq!(
1654				Pallet::<TestRuntime>::best_parachain_head(ParaId(1)),
1655				Some(stored_head_data(1, 5))
1656			);
1657
1658			// then if someone is pretending to provide updated head#10 of parachain#1 at relay
1659			// block#30, and actually provides it
1660			//
1661			// => we'll update value
1662			proceed(30, state_root_10_at_30);
1663			assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1664				RuntimeOrigin::signed(1),
1665				(30, test_relay_header(30, state_root_10_at_30).hash()),
1666				parachains_10_at_30,
1667				proof_10_at_30,
1668			),);
1669			assert_eq!(
1670				Pallet::<TestRuntime>::best_parachain_head(ParaId(1)),
1671				Some(stored_head_data(1, 10))
1672			);
1673		});
1674	}
1675
1676	#[test]
1677	fn storage_keys_computed_properly() {
1678		assert_eq!(
1679			ParasInfo::<TestRuntime>::storage_map_final_key(ParaId(42)).to_vec(),
1680			ParasInfoKeyProvider::final_key("Parachains", &ParaId(42)).0
1681		);
1682
1683		assert_eq!(
1684			ImportedParaHeads::<TestRuntime>::storage_double_map_final_key(
1685				ParaId(42),
1686				ParaHash::from([21u8; 32])
1687			)
1688			.to_vec(),
1689			ImportedParaHeadsKeyProvider::final_key(
1690				"Parachains",
1691				&ParaId(42),
1692				&ParaHash::from([21u8; 32])
1693			)
1694			.0,
1695		);
1696	}
1697
1698	#[test]
1699	fn ignores_parachain_head_if_it_is_missing_from_storage_proof() {
1700		let (state_root, proof, _) =
1701			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![]);
1702		let parachains = vec![(ParaId(2), Default::default())];
1703		run_test(|| {
1704			initialize(state_root);
1705			assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1706				RuntimeOrigin::signed(1),
1707				(0, test_relay_header(0, state_root).hash()),
1708				parachains,
1709				proof,
1710			));
1711			assert_eq!(
1712				System::<TestRuntime>::events(),
1713				vec![EventRecord {
1714					phase: Phase::Initialization,
1715					event: TestEvent::Parachains(Event::MissingParachainHead {
1716						parachain: ParaId(2),
1717					}),
1718					topics: vec![],
1719				}],
1720			);
1721		});
1722	}
1723
1724	#[test]
1725	fn ignores_parachain_head_if_parachain_head_hash_is_wrong() {
1726		let (state_root, proof, _) =
1727			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
1728		let parachains = vec![(ParaId(1), head_data(1, 10).hash())];
1729		run_test(|| {
1730			initialize(state_root);
1731			assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1732				RuntimeOrigin::signed(1),
1733				(0, test_relay_header(0, state_root).hash()),
1734				parachains,
1735				proof,
1736			));
1737			assert_eq!(
1738				System::<TestRuntime>::events(),
1739				vec![EventRecord {
1740					phase: Phase::Initialization,
1741					event: TestEvent::Parachains(Event::IncorrectParachainHeadHash {
1742						parachain: ParaId(1),
1743						parachain_head_hash: head_data(1, 10).hash(),
1744						actual_parachain_head_hash: head_data(1, 0).hash(),
1745					}),
1746					topics: vec![],
1747				}],
1748			);
1749		});
1750	}
1751
1752	#[test]
1753	fn test_bridge_parachain_call_is_correctly_defined() {
1754		let (state_root, proof, _) =
1755			prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
1756		let parachains = vec![(ParaId(2), Default::default())];
1757		let relay_header_id = (0, test_relay_header(0, state_root).hash());
1758
1759		let direct_submit_parachain_heads_call = Call::<TestRuntime>::submit_parachain_heads {
1760			at_relay_block: relay_header_id,
1761			parachains: parachains.clone(),
1762			parachain_heads_proof: proof.clone(),
1763		};
1764		let indirect_submit_parachain_heads_call = BridgeParachainCall::submit_parachain_heads {
1765			at_relay_block: relay_header_id,
1766			parachains,
1767			parachain_heads_proof: proof,
1768		};
1769		assert_eq!(
1770			direct_submit_parachain_heads_call.encode(),
1771			indirect_submit_parachain_heads_call.encode()
1772		);
1773	}
1774
1775	generate_owned_bridge_module_tests!(BasicOperatingMode::Normal, BasicOperatingMode::Halted);
1776
1777	#[test]
1778	fn maybe_max_parachains_returns_correct_value() {
1779		assert_eq!(MaybeMaxParachains::<TestRuntime, ()>::get(), Some(mock::TOTAL_PARACHAINS));
1780	}
1781
1782	#[test]
1783	fn maybe_max_total_parachain_hashes_returns_correct_value() {
1784		assert_eq!(
1785			MaybeMaxTotalParachainHashes::<TestRuntime, ()>::get(),
1786			Some(mock::TOTAL_PARACHAINS * mock::HeadsToKeep::get()),
1787		);
1788	}
1789
1790	#[test]
1791	fn submit_finality_proof_requires_signed_origin() {
1792		run_test(|| {
1793			let (state_root, proof, parachains) =
1794				prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(1, head_data(1, 0))]);
1795
1796			initialize(state_root);
1797
1798			// `submit_parachain_heads()` should fail when the pallet is halted.
1799			assert_noop!(
1800				Pallet::<TestRuntime>::submit_parachain_heads(
1801					RuntimeOrigin::root(),
1802					(0, test_relay_header(0, state_root).hash()),
1803					parachains,
1804					proof,
1805				),
1806				DispatchError::BadOrigin
1807			);
1808		})
1809	}
1810
1811	#[test]
1812	fn may_be_free_for_submitting_filtered_heads() {
1813		run_test(|| {
1814			let (state_root, proof, parachains) =
1815				prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(2, head_data(2, 5))]);
1816			// start with relay block #0 and import head#5 of parachain#2
1817			initialize(state_root);
1818			// first submission is free
1819			let result = Pallet::<TestRuntime>::submit_parachain_heads(
1820				RuntimeOrigin::signed(1),
1821				(0, test_relay_header(0, state_root).hash()),
1822				parachains.clone(),
1823				proof.clone(),
1824			);
1825			assert_eq!(result.unwrap().pays_fee, Pays::No);
1826			// next submission is NOT free, because we haven't updated anything
1827			let result = Pallet::<TestRuntime>::submit_parachain_heads(
1828				RuntimeOrigin::signed(1),
1829				(0, test_relay_header(0, state_root).hash()),
1830				parachains,
1831				proof,
1832			);
1833			assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1834			// then we submit new head, proved at relay block `FreeHeadersInterval - 1` => Pays::Yes
1835			let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1836				RegularParachainHeader,
1837			>(vec![(2, head_data(2, 50))]);
1838			let relay_block_number = FreeHeadersInterval::get() - 1;
1839			proceed(relay_block_number, state_root);
1840			let result = Pallet::<TestRuntime>::submit_parachain_heads(
1841				RuntimeOrigin::signed(1),
1842				(relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
1843				parachains,
1844				proof,
1845			);
1846			assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1847			// then we submit new head, proved after `FreeHeadersInterval` => Pays::No
1848			let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1849				RegularParachainHeader,
1850			>(vec![(2, head_data(2, 100))]);
1851			let relay_block_number = relay_block_number + FreeHeadersInterval::get();
1852			proceed(relay_block_number, state_root);
1853			let result = Pallet::<TestRuntime>::submit_parachain_heads(
1854				RuntimeOrigin::signed(1),
1855				(relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
1856				parachains,
1857				proof,
1858			);
1859			assert_eq!(result.unwrap().pays_fee, Pays::No);
1860			// then we submit new BIG head, proved after `FreeHeadersInterval` => Pays::Yes
1861			// then we submit new head, proved after `FreeHeadersInterval` => Pays::No
1862			let mut large_head = head_data(2, 100);
1863			large_head.0.extend(&[42u8; BigParachain::MAX_HEADER_SIZE as _]);
1864			let (state_root, proof, parachains) =
1865				prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(2, large_head)]);
1866			let relay_block_number = relay_block_number + FreeHeadersInterval::get();
1867			proceed(relay_block_number, state_root);
1868			let result = Pallet::<TestRuntime>::submit_parachain_heads(
1869				RuntimeOrigin::signed(1),
1870				(relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
1871				parachains,
1872				proof,
1873			);
1874			assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1875		})
1876	}
1877
1878	#[test]
1879	fn grandpa_and_parachain_pallets_share_free_headers_counter() {
1880		run_test(|| {
1881			initialize(Default::default());
1882			// set free headers limit to `4`
1883			let mut free_headers_remaining = 4;
1884			pallet_bridge_grandpa::FreeHeadersRemaining::<TestRuntime, BridgesGrandpaPalletInstance>::set(
1885				Some(free_headers_remaining),
1886			);
1887			// import free GRANDPA and parachain headers
1888			let mut relay_block_number = 0;
1889			for i in 0..2 {
1890				// import free GRANDPA header
1891				let (state_root, proof, parachains) = prepare_parachain_heads_proof::<
1892					RegularParachainHeader,
1893				>(vec![(2, head_data(2, 5 + i))]);
1894				relay_block_number = relay_block_number + FreeHeadersInterval::get();
1895				proceed(relay_block_number, state_root);
1896				assert_eq!(
1897					pallet_bridge_grandpa::FreeHeadersRemaining::<
1898						TestRuntime,
1899						BridgesGrandpaPalletInstance,
1900					>::get(),
1901					Some(free_headers_remaining - 1),
1902				);
1903				free_headers_remaining = free_headers_remaining - 1;
1904				// import free parachain header
1905				assert_ok!(Pallet::<TestRuntime>::submit_parachain_heads(
1906					RuntimeOrigin::signed(1),
1907					(relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
1908					parachains,
1909					proof,
1910				),);
1911				assert_eq!(
1912					pallet_bridge_grandpa::FreeHeadersRemaining::<
1913						TestRuntime,
1914						BridgesGrandpaPalletInstance,
1915					>::get(),
1916					Some(free_headers_remaining - 1),
1917				);
1918				free_headers_remaining = free_headers_remaining - 1;
1919			}
1920			// try to import free GRANDPA header => non-free execution
1921			let (state_root, proof, parachains) =
1922				prepare_parachain_heads_proof::<RegularParachainHeader>(vec![(2, head_data(2, 7))]);
1923			relay_block_number = relay_block_number + FreeHeadersInterval::get();
1924			let result = pallet_bridge_grandpa::Pallet::<TestRuntime, BridgesGrandpaPalletInstance>::submit_finality_proof_ex(
1925				RuntimeOrigin::signed(1),
1926				Box::new(test_relay_header(relay_block_number, state_root)),
1927				make_default_justification(&test_relay_header(relay_block_number, state_root)),
1928				TEST_GRANDPA_SET_ID,
1929				false,
1930			);
1931			assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1932			// try to import free parachain header => non-free execution
1933			let result = Pallet::<TestRuntime>::submit_parachain_heads(
1934				RuntimeOrigin::signed(1),
1935				(relay_block_number, test_relay_header(relay_block_number, state_root).hash()),
1936				parachains,
1937				proof,
1938			);
1939			assert_eq!(result.unwrap().pays_fee, Pays::Yes);
1940			assert_eq!(
1941				pallet_bridge_grandpa::FreeHeadersRemaining::<
1942					TestRuntime,
1943					BridgesGrandpaPalletInstance,
1944				>::get(),
1945				Some(0),
1946			);
1947		});
1948	}
1949}