From d63a75697cf2360dd13c8434830c300eed5c359a Mon Sep 17 00:00:00 2001
From: Svyatoslav Nikolsky <svyatonik@gmail.com>
Date: Mon, 12 Dec 2022 09:15:36 +0300
Subject: [PATCH] Only store header state root (pallet-bridge-parachains)
 (#1701)

* store block number ++ state root in parachains pallet

* fixed parachains finality APIs

* (test commit)

* removed test code

* deduplicated code a bit

* removed commented code

* spelling

* Update modules/parachains/src/lib.rs

Co-authored-by: Adrian Catangiu <adrian@parity.io>

* Update modules/parachains/src/lib.rs

Co-authored-by: Adrian Catangiu <adrian@parity.io>

* Update modules/parachains/src/mock.rs

Co-authored-by: Adrian Catangiu <adrian@parity.io>

* added comment

Co-authored-by: Adrian Catangiu <adrian@parity.io>
---
 bridges/bin/millau/runtime/Cargo.toml         |   2 +
 bridges/bin/millau/runtime/src/lib.rs         |  34 +--
 bridges/modules/parachains/Cargo.toml         |   1 +
 bridges/modules/parachains/src/lib.rs         | 233 +++++++++++-------
 bridges/modules/parachains/src/mock.rs        | 120 ++++++++-
 bridges/primitives/chain-rialto/src/lib.rs    |   8 +-
 bridges/primitives/chain-rococo/src/lib.rs    |  10 +-
 bridges/primitives/chain-westend/Cargo.toml   |   2 +
 bridges/primitives/chain-westend/src/lib.rs   |  40 ++-
 bridges/primitives/chain-wococo/src/lib.rs    |   2 +-
 bridges/primitives/parachains/Cargo.toml      |   7 +
 bridges/primitives/parachains/src/lib.rs      |  66 ++++-
 .../polkadot-core/src/parachains.rs           |   4 +-
 bridges/primitives/runtime/src/chain.rs       |   5 +-
 bridges/primitives/runtime/src/lib.rs         |  13 +-
 bridges/relays/client-substrate/src/lib.rs    |   7 +-
 .../relays/finality/src/sync_loop_metrics.rs  |  19 +-
 .../src/parachains/target.rs                  |  20 +-
 bridges/relays/messages/src/metrics.rs        |  14 +-
 .../parachains/src/parachains_loop_metrics.rs |  13 +-
 bridges/relays/utils/Cargo.toml               |   1 +
 bridges/relays/utils/src/lib.rs               |   5 +-
 22 files changed, 448 insertions(+), 178 deletions(-)

diff --git a/bridges/bin/millau/runtime/Cargo.toml b/bridges/bin/millau/runtime/Cargo.toml
index 7605a642cdc..d3aabdcba39 100644
--- a/bridges/bin/millau/runtime/Cargo.toml
+++ b/bridges/bin/millau/runtime/Cargo.toml
@@ -16,6 +16,7 @@ scale-info = { version = "2.1.1", default-features = false, features = ["derive"
 
 bp-messages = { path = "../../../primitives/messages", default-features = false }
 bp-millau = { path = "../../../primitives/chain-millau", default-features = false }
+bp-parachains = { path = "../../../primitives/parachains", default-features = false }
 bp-polkadot-core = { path = "../../../primitives/polkadot-core", default-features = false }
 bp-relayers = { path = "../../../primitives/relayers", default-features = false }
 bp-rialto = { path = "../../../primitives/chain-rialto", default-features = false }
@@ -84,6 +85,7 @@ std = [
 	"beefy-primitives/std",
 	"bp-messages/std",
 	"bp-millau/std",
+	"bp-parachains/std",
 	"bp-polkadot-core/std",
 	"bp-relayers/std",
 	"bp-rialto/std",
diff --git a/bridges/bin/millau/runtime/src/lib.rs b/bridges/bin/millau/runtime/src/lib.rs
index dcb76fc32eb..19a8770430a 100644
--- a/bridges/bin/millau/runtime/src/lib.rs
+++ b/bridges/bin/millau/runtime/src/lib.rs
@@ -33,8 +33,8 @@ pub mod rialto_parachain_messages;
 pub mod xcm_config;
 
 use beefy_primitives::{crypto::AuthorityId as BeefyId, mmr::MmrLeafVersion, ValidatorSet};
-use bp_runtime::{HeaderId, HeaderIdProvider};
-use codec::Decode;
+use bp_parachains::SingleParaStoredHeaderDataBuilder;
+use bp_runtime::HeaderId;
 use pallet_grandpa::{
 	fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList,
 };
@@ -522,8 +522,8 @@ parameter_types! {
 	pub const RialtoParachainId: u32 = bp_rialto_parachain::RIALTO_PARACHAIN_ID;
 	pub const RialtoParasPalletName: &'static str = bp_rialto::PARAS_PALLET_NAME;
 	pub const WestendParasPalletName: &'static str = bp_westend::PARAS_PALLET_NAME;
-	pub const MaxRialtoParaHeadSize: u32 = bp_rialto::MAX_NESTED_PARACHAIN_HEAD_SIZE;
-	pub const MaxWestendParaHeadSize: u32 = bp_westend::MAX_NESTED_PARACHAIN_HEAD_SIZE;
+	pub const MaxRialtoParaHeadDataSize: u32 = bp_rialto::MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE;
+	pub const MaxWestendParaHeadDataSize: u32 = bp_westend::MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE;
 }
 
 /// Instance of the with-Rialto parachains pallet.
@@ -534,9 +534,10 @@ impl pallet_bridge_parachains::Config<WithRialtoParachainsInstance> for Runtime
 	type WeightInfo = pallet_bridge_parachains::weights::BridgeWeight<Runtime>;
 	type BridgesGrandpaPalletInstance = RialtoGrandpaInstance;
 	type ParasPalletName = RialtoParasPalletName;
-	type TrackedParachains = frame_support::traits::Everything;
+	type ParaStoredHeaderDataBuilder =
+		SingleParaStoredHeaderDataBuilder<bp_rialto_parachain::RialtoParachain>;
 	type HeadsToKeep = HeadersToKeep;
-	type MaxParaHeadSize = MaxRialtoParaHeadSize;
+	type MaxParaHeadDataSize = MaxRialtoParaHeadDataSize;
 }
 
 /// Instance of the with-Westend parachains pallet.
@@ -547,9 +548,9 @@ impl pallet_bridge_parachains::Config<WithWestendParachainsInstance> for Runtime
 	type WeightInfo = pallet_bridge_parachains::weights::BridgeWeight<Runtime>;
 	type BridgesGrandpaPalletInstance = WestendGrandpaInstance;
 	type ParasPalletName = WestendParasPalletName;
-	type TrackedParachains = frame_support::traits::Everything;
+	type ParaStoredHeaderDataBuilder = SingleParaStoredHeaderDataBuilder<bp_westend::Westmint>;
 	type HeadsToKeep = HeadersToKeep;
-	type MaxParaHeadSize = MaxWestendParaHeadSize;
+	type MaxParaHeadDataSize = MaxWestendParaHeadDataSize;
 }
 
 impl pallet_utility::Config for Runtime {
@@ -902,28 +903,19 @@ impl_runtime_apis! {
 
 	impl bp_westend::WestmintFinalityApi<Block> for Runtime {
 		fn best_finalized() -> Option<HeaderId<bp_westend::Hash, bp_westend::BlockNumber>> {
-			// the parachains finality pallet is never decoding parachain heads, so it is
-			// only done in the integration code
-			use bp_westend::WESTMINT_PARACHAIN_ID;
-			let encoded_head = pallet_bridge_parachains::Pallet::<
+			pallet_bridge_parachains::Pallet::<
 				Runtime,
 				WithWestendParachainsInstance,
-			>::best_parachain_head(WESTMINT_PARACHAIN_ID.into())?;
-			let head = bp_westend::Header::decode(&mut &encoded_head.0[..]).ok()?;
-			Some(head.id())
+			>::best_parachain_head_id::<bp_westend::Westmint>().unwrap_or(None)
 		}
 	}
 
 	impl bp_rialto_parachain::RialtoParachainFinalityApi<Block> for Runtime {
 		fn best_finalized() -> Option<HeaderId<bp_rialto::Hash, bp_rialto::BlockNumber>> {
-			// the parachains finality pallet is never decoding parachain heads, so it is
-			// only done in the integration code
-			let encoded_head = pallet_bridge_parachains::Pallet::<
+			pallet_bridge_parachains::Pallet::<
 				Runtime,
 				WithRialtoParachainsInstance,
-			>::best_parachain_head(bp_rialto_parachain::RIALTO_PARACHAIN_ID.into())?;
-			let head = bp_rialto_parachain::Header::decode(&mut &encoded_head.0[..]).ok()?;
-			Some(head.id())
+			>::best_parachain_head_id::<bp_rialto_parachain::RialtoParachain>().unwrap_or(None)
 		}
 	}
 
diff --git a/bridges/modules/parachains/Cargo.toml b/bridges/modules/parachains/Cargo.toml
index ce674459eac..3c3ad95fe34 100644
--- a/bridges/modules/parachains/Cargo.toml
+++ b/bridges/modules/parachains/Cargo.toml
@@ -30,6 +30,7 @@ sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master",
 [dev-dependencies]
 bp-header-chain = { path = "../../primitives/header-chain" }
 bp-test-utils = { path = "../../primitives/test-utils" }
+sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
 sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
 
 [features]
diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs
index 1b1b4de5250..bf2ab8dcd64 100644
--- a/bridges/modules/parachains/src/lib.rs
+++ b/bridges/modules/parachains/src/lib.rs
@@ -27,12 +27,10 @@ pub use weights::WeightInfo;
 pub use weights_ext::WeightInfoExt;
 
 use bp_header_chain::HeaderChain;
-use bp_parachains::{parachain_head_storage_key_at_source, ParaInfo};
+use bp_parachains::{parachain_head_storage_key_at_source, ParaInfo, ParaStoredHeaderData};
 use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId};
-use bp_runtime::{HashOf, HeaderOf, Parachain, StorageProofError};
-use codec::Decode;
-use frame_support::{dispatch::PostDispatchInfo, traits::Contains};
-use sp_runtime::traits::Header as HeaderT;
+use bp_runtime::{Chain, HashOf, HeaderId, HeaderIdOf, Parachain, StorageProofError};
+use frame_support::dispatch::PostDispatchInfo;
 use sp_std::{marker::PhantomData, vec::Vec};
 
 // Re-export in crate namespace for `construct_runtime!`.
@@ -69,7 +67,10 @@ struct UpdateParachainHeadArtifacts {
 #[frame_support::pallet]
 pub mod pallet {
 	use super::*;
-	use bp_parachains::{BestParaHeadHash, ImportedParaHeadsKeyProvider, ParasInfoKeyProvider};
+	use bp_parachains::{
+		BestParaHeadHash, ImportedParaHeadsKeyProvider, ParaStoredHeaderDataBuilder,
+		ParasInfoKeyProvider,
+	};
 	use bp_runtime::{
 		BasicOperatingMode, BoundedStorageValue, OwnedBridgeModule, StorageDoubleMapKeyProvider,
 		StorageMapKeyProvider,
@@ -77,9 +78,9 @@ pub mod pallet {
 	use frame_support::pallet_prelude::*;
 	use frame_system::pallet_prelude::*;
 
-	/// Stored parachain head of given parachains pallet.
-	pub type StoredParaHeadOf<T, I> =
-		BoundedStorageValue<<T as Config<I>>::MaxParaHeadSize, ParaHead>;
+	/// Stored parachain head data of given parachains pallet.
+	pub type StoredParaHeadDataOf<T, I> =
+		BoundedStorageValue<<T as Config<I>>::MaxParaHeadDataSize, ParaStoredHeaderData>;
 	/// Weight info of the given parachains pallet.
 	pub type WeightInfoOf<T, I> = <T as Config<I>>::WeightInfo;
 
@@ -153,12 +154,18 @@ pub mod pallet {
 		#[pallet::constant]
 		type ParasPalletName: Get<&'static str>;
 
-		/// Set of parachains that are tracked by this pallet.
+		/// Parachain head data builder.
+		///
+		/// We never store parachain heads here, since they may be too big (e.g. because of large
+		/// digest items). Instead we're using the same approach as `pallet-bridge-grandpa`
+		/// pallet - we are only storing `bp_messages::StoredHeaderData` (number and state root),
+		/// which is enough for our applications. However, we work with different parachains here
+		/// and they can use different primitives (for block numbers and hash). So we can't store
+		/// it directly. Instead, we're storing `bp_messages::StoredHeaderData` in SCALE-encoded
+		/// form, wrapping it into `bp_parachains::ParaStoredHeaderData`.
 		///
-		/// The set may be extended easily, without requiring any runtime upgrades. Removing tracked
-		/// parachain requires special handling - pruning existing heads and cleaning related data
-		/// structures.
-		type TrackedParachains: Contains<ParaId>;
+		/// This builder helps to convert from `HeadData` to `bp_parachains::ParaStoredHeaderData`.
+		type ParaStoredHeaderDataBuilder: ParaStoredHeaderDataBuilder;
 
 		/// Maximal number of single parachain heads to keep in the storage.
 		///
@@ -170,16 +177,17 @@ pub mod pallet {
 		#[pallet::constant]
 		type HeadsToKeep: Get<u32>;
 
-		/// Maximal size (in bytes) of the SCALE-encoded parachain head.
+		/// Maximal size (in bytes) of the SCALE-encoded parachain head data
+		/// (`bp_parachains::ParaStoredHeaderData`).
 		///
-		/// Keep in mind that the size of any tracked parachain header must not exceed this value.
-		/// So if you're going to track multiple parachains, one of which is storing large digests
-		/// in its headers, you shall choose this maximal value.
+		/// Keep in mind that the size of any tracked parachain header data must not exceed this
+		/// value. So if you're going to track multiple parachains, one of which is using large
+		/// hashes, you shall choose this maximal value.
 		///
 		/// There's no mandatory headers in this pallet, so it can't stall if there's some header
 		/// that exceeds this bound.
 		#[pallet::constant]
-		type MaxParaHeadSize: Get<u32>;
+		type MaxParaHeadDataSize: Get<u32>;
 	}
 
 	/// Optional pallet owner.
@@ -212,7 +220,7 @@ pub mod pallet {
 		<ParasInfoKeyProvider as StorageMapKeyProvider>::Value,
 	>;
 
-	/// Parachain heads which have been imported into the pallet.
+	/// State roots of parachain heads which have been imported into the pallet.
 	#[pallet::storage]
 	pub type ImportedParaHeads<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
 		_,
@@ -220,7 +228,7 @@ pub mod pallet {
 		<ImportedParaHeadsKeyProvider as StorageDoubleMapKeyProvider>::Key1,
 		<ImportedParaHeadsKeyProvider as StorageDoubleMapKeyProvider>::Hasher2,
 		<ImportedParaHeadsKeyProvider as StorageDoubleMapKeyProvider>::Key2,
-		StoredParaHeadOf<T, I>,
+		StoredParaHeadDataOf<T, I>,
 	>;
 
 	/// A ring buffer of imported parachain head hashes. Ordered by the insertion time.
@@ -291,17 +299,6 @@ pub mod pallet {
 				sp_trie::StorageProof::new(parachain_heads_proof.0),
 				move |storage| {
 					for (parachain, parachain_head_hash) in parachains {
-						// if we're not tracking this parachain, we'll just ignore its head proof here
-						if !T::TrackedParachains::contains(&parachain) {
-							log::trace!(
-								target: LOG_TARGET,
-								"The head of parachain {:?} has been provided, but it is not tracked by the pallet",
-								parachain,
-							);
-							Self::deposit_event(Event::UntrackedParachainRejected { parachain });
-							continue;
-						}
-
 						let parachain_head = match Pallet::<T, I>::read_parachain_head(&storage, parachain) {
 							Ok(Some(parachain_head)) => parachain_head,
 							Ok(None) => {
@@ -349,12 +346,26 @@ pub mod pallet {
 							continue;
 						}
 
+						// convert from parachain head into stored parachain head data
+						let parachain_head_data = match T::ParaStoredHeaderDataBuilder::try_build(parachain, &parachain_head) {
+							Some(parachain_head_data) => parachain_head_data,
+							None => {
+								log::trace!(
+									target: LOG_TARGET,
+									"The head of parachain {:?} has been provided, but it is not tracked by the pallet",
+									parachain,
+								);
+								Self::deposit_event(Event::UntrackedParachainRejected { parachain });
+								continue;
+							},
+						};
+
 						let update_result: Result<_, ()> = ParasInfo::<T, I>::try_mutate(parachain, |stored_best_head| {
 							let artifacts = Pallet::<T, I>::update_parachain_head(
 								parachain,
 								stored_best_head.take(),
 								relay_block_number,
-								parachain_head,
+								parachain_head_data,
 								parachain_head_hash,
 							)?;
 							*stored_best_head = Some(artifacts.best_head);
@@ -406,14 +417,36 @@ pub mod pallet {
 			ParasInfo::<T, I>::get(parachain)
 		}
 
-		/// Get best finalized header of the given parachain.
-		pub fn best_parachain_head(parachain: ParaId) -> Option<ParaHead> {
+		/// Get best finalized head data of the given parachain.
+		pub fn best_parachain_head(parachain: ParaId) -> Option<ParaStoredHeaderData> {
 			let best_para_head_hash = ParasInfo::<T, I>::get(parachain)?.best_head_hash.head_hash;
 			ImportedParaHeads::<T, I>::get(parachain, best_para_head_hash).map(|h| h.into_inner())
 		}
 
-		/// Get parachain head with given hash.
-		pub fn parachain_head(parachain: ParaId, hash: ParaHash) -> Option<ParaHead> {
+		/// Get best finalized head hash of the given parachain.
+		pub fn best_parachain_head_hash(parachain: ParaId) -> Option<ParaHash> {
+			Some(ParasInfo::<T, I>::get(parachain)?.best_head_hash.head_hash)
+		}
+
+		/// Get best finalized head id of the given parachain.
+		pub fn best_parachain_head_id<C: Chain<Hash = ParaHash> + Parachain>(
+		) -> Result<Option<HeaderIdOf<C>>, codec::Error> {
+			let parachain = ParaId(C::PARACHAIN_ID);
+			let best_head_hash = match Self::best_parachain_head_hash(parachain) {
+				Some(best_head_hash) => best_head_hash,
+				None => return Ok(None),
+			};
+			let encoded_head = match Self::parachain_head(parachain, best_head_hash) {
+				Some(encoded_head) => encoded_head,
+				None => return Ok(None),
+			};
+			encoded_head
+				.decode_parachain_head_data::<C>()
+				.map(|data| Some(HeaderId(data.number, best_head_hash)))
+		}
+
+		/// Get parachain head data with given hash.
+		pub fn parachain_head(parachain: ParaId, hash: ParaHash) -> Option<ParaStoredHeaderData> {
 			ImportedParaHeads::<T, I>::get(parachain, hash).map(|h| h.into_inner())
 		}
 
@@ -480,7 +513,7 @@ pub mod pallet {
 			parachain: ParaId,
 			stored_best_head: Option<ParaInfo>,
 			updated_at_relay_block_number: RelayBlockNumber,
-			updated_head: ParaHead,
+			updated_head_data: ParaStoredHeaderData,
 			updated_head_hash: ParaHash,
 		) -> Result<UpdateParachainHeadArtifacts, ()> {
 			// check if head has been already updated at better relay chain block. Without this
@@ -501,28 +534,29 @@ pub mod pallet {
 				return Err(())
 			}
 
-			// verify that the parachain head size is <= `MaxParaHeadSize`
-			let updated_head = match StoredParaHeadOf::<T, I>::try_from_inner(updated_head) {
-				Ok(updated_head) => updated_head,
-				Err(e) => {
-					log::trace!(
-						target: LOG_TARGET,
-						"{}. The parachain head size for {:?} is {}. It exceeds maximal configured size {}.",
-						err_log_prefix,
-						parachain,
-						e.value_size,
-						e.maximal_size,
-					);
-
-					Self::deposit_event(Event::RejectedLargeParachainHead {
-						parachain,
-						parachain_head_hash: updated_head_hash,
-						parachain_head_size: e.value_size as _,
-					});
-
-					return Err(())
-				},
-			};
+			// verify that the parachain head data size is <= `MaxParaHeadDataSize`
+			let updated_head_data =
+				match StoredParaHeadDataOf::<T, I>::try_from_inner(updated_head_data) {
+					Ok(updated_head_data) => updated_head_data,
+					Err(e) => {
+						log::trace!(
+							target: LOG_TARGET,
+							"{}. The parachain head data size for {:?} is {}. It exceeds maximal configured size {}.",
+							err_log_prefix,
+							parachain,
+							e.value_size,
+							e.maximal_size,
+						);
+
+						Self::deposit_event(Event::RejectedLargeParachainHead {
+							parachain,
+							parachain_head_hash: updated_head_hash,
+							parachain_head_size: e.value_size as _,
+						});
+
+						return Err(())
+					},
+				};
 
 			let next_imported_hash_position = stored_best_head
 				.map_or(0, |stored_best_head| stored_best_head.next_imported_hash_position);
@@ -543,7 +577,7 @@ pub mod pallet {
 				next_imported_hash_position,
 				updated_head_hash,
 			);
-			ImportedParaHeads::<T, I>::insert(parachain, updated_head_hash, updated_head);
+			ImportedParaHeads::<T, I>::insert(parachain, updated_head_hash, updated_head_data);
 			log::trace!(
 				target: LOG_TARGET,
 				"Updated head of parachain {:?} to {}",
@@ -611,8 +645,8 @@ impl<T: Config<I>, I: 'static, C: Parachain<Hash = ParaHash>> HeaderChain<C>
 {
 	fn finalized_header_state_root(hash: HashOf<C>) -> Option<HashOf<C>> {
 		Pallet::<T, I>::parachain_head(ParaId(C::PARACHAIN_ID), hash)
-			.and_then(|head| HeaderOf::<C>::decode(&mut &head.0[..]).ok())
-			.map(|h| *h.state_root())
+			.and_then(|head| head.decode_parachain_head_data::<C>().ok())
+			.map(|h| h.state_root)
 	}
 }
 
@@ -620,8 +654,9 @@ impl<T: Config<I>, I: 'static, C: Parachain<Hash = ParaHash>> HeaderChain<C>
 mod tests {
 	use super::*;
 	use crate::mock::{
-		run_test, test_relay_header, RuntimeEvent as TestEvent, RuntimeOrigin, TestRuntime,
-		MAXIMAL_PARACHAIN_HEAD_SIZE, PARAS_PALLET_NAME, UNTRACKED_PARACHAIN_ID,
+		run_test, test_relay_header, BigParachainHeader, RegularParachainHasher,
+		RegularParachainHeader, RuntimeEvent as TestEvent, RuntimeOrigin, TestRuntime,
+		PARAS_PALLET_NAME, UNTRACKED_PARACHAIN_ID,
 	};
 	use codec::Encode;
 
@@ -641,7 +676,8 @@ mod tests {
 		weights::Weight,
 	};
 	use frame_system::{EventRecord, Pallet as System, Phase};
-	use sp_runtime::DispatchError;
+	use sp_core::Hasher;
+	use sp_runtime::{traits::Header as HeaderT, DispatchError};
 	use sp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, Recorder, TrieMut};
 
 	type BridgesGrandpaPalletInstance = pallet_bridge_grandpa::Instance1;
@@ -716,12 +752,42 @@ mod tests {
 	}
 
 	fn head_data(parachain: u32, head_number: u32) -> ParaHead {
-		ParaHead((parachain, head_number).encode())
+		ParaHead(
+			RegularParachainHeader::new(
+				head_number as _,
+				Default::default(),
+				RegularParachainHasher::hash(&(parachain, head_number).encode()),
+				Default::default(),
+				Default::default(),
+			)
+			.encode(),
+		)
 	}
 
-	fn large_head_data(parachain: u32, head_number: u32) -> ParaHead {
+	fn stored_head_data(parachain: u32, head_number: u32) -> ParaStoredHeaderData {
+		ParaStoredHeaderData(
+			(head_number as u64, RegularParachainHasher::hash(&(parachain, head_number).encode()))
+				.encode(),
+		)
+	}
+
+	fn big_head_data(parachain: u32, head_number: u32) -> ParaHead {
 		ParaHead(
-			(parachain, head_number, vec![42u8; MAXIMAL_PARACHAIN_HEAD_SIZE as usize]).encode(),
+			BigParachainHeader::new(
+				head_number as _,
+				Default::default(),
+				RegularParachainHasher::hash(&(parachain, head_number).encode()),
+				Default::default(),
+				Default::default(),
+			)
+			.encode(),
+		)
+	}
+
+	fn big_stored_head_data(parachain: u32, head_number: u32) -> ParaStoredHeaderData {
+		ParaStoredHeaderData(
+			(head_number as u128, RegularParachainHasher::hash(&(parachain, head_number).encode()))
+				.encode(),
 		)
 	}
 
@@ -823,7 +889,7 @@ mod tests {
 					initial_best_head(1).best_head_hash.head_hash
 				)
 				.map(|h| h.into_inner()),
-				Some(head_data(1, 0))
+				Some(stored_head_data(1, 0))
 			);
 			assert_eq!(
 				ImportedParaHeads::<TestRuntime>::get(
@@ -836,7 +902,7 @@ mod tests {
 			assert_eq!(
 				ImportedParaHeads::<TestRuntime>::get(ParaId(3), head_hash(3, 10))
 					.map(|h| h.into_inner()),
-				Some(head_data(3, 10))
+				Some(stored_head_data(3, 10))
 			);
 
 			assert_eq!(
@@ -886,7 +952,7 @@ mod tests {
 			assert_eq!(
 				ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 5).hash())
 					.map(|h| h.into_inner()),
-				Some(head_data(1, 5))
+				Some(stored_head_data(1, 5))
 			);
 			assert_eq!(
 				ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 10).hash())
@@ -921,12 +987,12 @@ mod tests {
 			assert_eq!(
 				ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 5).hash())
 					.map(|h| h.into_inner()),
-				Some(head_data(1, 5))
+				Some(stored_head_data(1, 5))
 			);
 			assert_eq!(
 				ImportedParaHeads::<TestRuntime>::get(ParaId(1), head_data(1, 10).hash())
 					.map(|h| h.into_inner()),
-				Some(head_data(1, 10))
+				Some(stored_head_data(1, 10))
 			);
 			assert_eq!(
 				System::<TestRuntime>::events(),
@@ -1153,10 +1219,9 @@ mod tests {
 	#[test]
 	fn does_nothing_when_parachain_head_is_too_large() {
 		let (state_root, proof, parachains) =
-			prepare_parachain_heads_proof(vec![(1, head_data(1, 5)), (2, large_head_data(1, 5))]);
+			prepare_parachain_heads_proof(vec![(1, head_data(1, 5)), (4, big_head_data(1, 5))]);
 		run_test(|| {
-			// start with relay block #0 and try to import head#5 of parachain#1 and untracked
-			// parachain
+			// start with relay block #0 and try to import head#5 of parachain#1 and big parachain
 			initialize(state_root);
 			let result = Pallet::<TestRuntime>::submit_parachain_heads(
 				RuntimeOrigin::signed(1),
@@ -1175,7 +1240,7 @@ mod tests {
 					next_imported_hash_position: 1,
 				})
 			);
-			assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(2)), None);
+			assert_eq!(ParasInfo::<TestRuntime>::get(ParaId(4)), None);
 			assert_eq!(
 				System::<TestRuntime>::events(),
 				vec![
@@ -1190,9 +1255,9 @@ mod tests {
 					EventRecord {
 						phase: Phase::Initialization,
 						event: TestEvent::Parachains(Event::RejectedLargeParachainHead {
-							parachain: ParaId(2),
-							parachain_head_hash: large_head_data(1, 5).hash(),
-							parachain_head_size: large_head_data(1, 5).encoded_size() as u32,
+							parachain: ParaId(4),
+							parachain_head_hash: big_head_data(1, 5).hash(),
+							parachain_head_size: big_stored_head_data(1, 5).encoded_size() as u32,
 						}),
 						topics: vec![],
 					},
@@ -1294,7 +1359,7 @@ mod tests {
 			import_parachain_1_head(0, state_root_5, parachains_5, proof_5).expect("ok");
 			assert_eq!(
 				Pallet::<TestRuntime>::best_parachain_head(ParaId(1)),
-				Some(head_data(1, 5))
+				Some(stored_head_data(1, 5))
 			);
 
 			// then if someone is pretending to provide updated head#10 of parachain#1 at relay
@@ -1310,7 +1375,7 @@ mod tests {
 			),);
 			assert_eq!(
 				Pallet::<TestRuntime>::best_parachain_head(ParaId(1)),
-				Some(head_data(1, 5))
+				Some(stored_head_data(1, 5))
 			);
 
 			// then if someone is pretending to provide updated head#10 of parachain#1 at relay
@@ -1326,7 +1391,7 @@ mod tests {
 			),);
 			assert_eq!(
 				Pallet::<TestRuntime>::best_parachain_head(ParaId(1)),
-				Some(head_data(1, 10))
+				Some(stored_head_data(1, 10))
 			);
 		});
 	}
diff --git a/bridges/modules/parachains/src/mock.rs b/bridges/modules/parachains/src/mock.rs
index 6e8df019fff..118a9b1d315 100644
--- a/bridges/modules/parachains/src/mock.rs
+++ b/bridges/modules/parachains/src/mock.rs
@@ -15,16 +15,12 @@
 // along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
 
 use bp_polkadot_core::parachains::ParaId;
-use bp_runtime::Chain;
-use frame_support::{
-	construct_runtime, parameter_types,
-	traits::{ConstU32, IsInVec},
-	weights::Weight,
-};
+use bp_runtime::{Chain, Parachain};
+use frame_support::{construct_runtime, parameter_types, traits::ConstU32, weights::Weight};
 use sp_runtime::{
 	testing::{Header, H256},
 	traits::{BlakeTwo256, Header as HeaderT, IdentityLookup},
-	Perbill,
+	MultiSignature, Perbill,
 };
 
 use crate as pallet_bridge_parachains;
@@ -40,7 +36,109 @@ type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRunt
 
 pub const PARAS_PALLET_NAME: &str = "Paras";
 pub const UNTRACKED_PARACHAIN_ID: u32 = 10;
-pub const MAXIMAL_PARACHAIN_HEAD_SIZE: u32 = 512;
+// use exact expected encoded size: `vec_len_size + header_number_size + state_root_hash_size`
+pub const MAXIMAL_PARACHAIN_HEAD_DATA_SIZE: u32 = 1 + 8 + 32;
+
+pub type RegularParachainHeader = sp_runtime::testing::Header;
+pub type RegularParachainHasher = BlakeTwo256;
+pub type BigParachainHeader = sp_runtime::generic::Header<u128, BlakeTwo256>;
+
+pub struct Parachain1;
+
+impl Chain for Parachain1 {
+	type BlockNumber = u64;
+	type Hash = H256;
+	type Hasher = RegularParachainHasher;
+	type Header = RegularParachainHeader;
+	type AccountId = u64;
+	type Balance = u64;
+	type Index = u64;
+	type Signature = MultiSignature;
+
+	fn max_extrinsic_size() -> u32 {
+		0
+	}
+	fn max_extrinsic_weight() -> Weight {
+		Weight::zero()
+	}
+}
+
+impl Parachain for Parachain1 {
+	const PARACHAIN_ID: u32 = 1;
+}
+
+pub struct Parachain2;
+
+impl Chain for Parachain2 {
+	type BlockNumber = u64;
+	type Hash = H256;
+	type Hasher = RegularParachainHasher;
+	type Header = RegularParachainHeader;
+	type AccountId = u64;
+	type Balance = u64;
+	type Index = u64;
+	type Signature = MultiSignature;
+
+	fn max_extrinsic_size() -> u32 {
+		0
+	}
+	fn max_extrinsic_weight() -> Weight {
+		Weight::zero()
+	}
+}
+
+impl Parachain for Parachain2 {
+	const PARACHAIN_ID: u32 = 2;
+}
+
+pub struct Parachain3;
+
+impl Chain for Parachain3 {
+	type BlockNumber = u64;
+	type Hash = H256;
+	type Hasher = RegularParachainHasher;
+	type Header = RegularParachainHeader;
+	type AccountId = u64;
+	type Balance = u64;
+	type Index = u64;
+	type Signature = MultiSignature;
+
+	fn max_extrinsic_size() -> u32 {
+		0
+	}
+	fn max_extrinsic_weight() -> Weight {
+		Weight::zero()
+	}
+}
+
+impl Parachain for Parachain3 {
+	const PARACHAIN_ID: u32 = 3;
+}
+
+// this parachain is using u128 as block number and stored head data size exceeds limit
+pub struct BigParachain;
+
+impl Chain for BigParachain {
+	type BlockNumber = u128;
+	type Hash = H256;
+	type Hasher = RegularParachainHasher;
+	type Header = BigParachainHeader;
+	type AccountId = u64;
+	type Balance = u64;
+	type Index = u64;
+	type Signature = MultiSignature;
+
+	fn max_extrinsic_size() -> u32 {
+		0
+	}
+	fn max_extrinsic_weight() -> Weight {
+		Weight::zero()
+	}
+}
+
+impl Parachain for BigParachain {
+	const PARACHAIN_ID: u32 = 4;
+}
 
 construct_runtime! {
 	pub enum TestRuntime where
@@ -68,7 +166,7 @@ impl frame_system::Config for TestRuntime {
 	type RuntimeCall = RuntimeCall;
 	type BlockNumber = TestNumber;
 	type Hash = H256;
-	type Hashing = BlakeTwo256;
+	type Hashing = RegularParachainHasher;
 	type AccountId = AccountId;
 	type Lookup = IdentityLookup<Self::AccountId>;
 	type Header = Header;
@@ -122,9 +220,9 @@ impl pallet_bridge_parachains::Config for TestRuntime {
 	type WeightInfo = ();
 	type BridgesGrandpaPalletInstance = pallet_bridge_grandpa::Instance1;
 	type ParasPalletName = ParasPalletName;
-	type TrackedParachains = IsInVec<GetTenFirstParachains>;
+	type ParaStoredHeaderDataBuilder = (Parachain1, Parachain2, Parachain3, BigParachain);
 	type HeadsToKeep = HeadsToKeep;
-	type MaxParaHeadSize = frame_support::traits::ConstU32<MAXIMAL_PARACHAIN_HEAD_SIZE>;
+	type MaxParaHeadDataSize = frame_support::traits::ConstU32<MAXIMAL_PARACHAIN_HEAD_DATA_SIZE>;
 }
 
 #[derive(Debug)]
diff --git a/bridges/primitives/chain-rialto/src/lib.rs b/bridges/primitives/chain-rialto/src/lib.rs
index 4aea107777a..0b718ec5215 100644
--- a/bridges/primitives/chain-rialto/src/lib.rs
+++ b/bridges/primitives/chain-rialto/src/lib.rs
@@ -70,8 +70,12 @@ pub const SESSION_LENGTH: BlockNumber = 4;
 /// Maximal number of GRANDPA authorities at Rialto.
 pub const MAX_AUTHORITIES_COUNT: u32 = 5;
 
-/// Maximal SCALE-encoded size of parachains headers that are stored at Rialto `Paras` pallet.
-pub const MAX_NESTED_PARACHAIN_HEAD_SIZE: u32 = 1024;
+/// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Rialto
+/// parachains.
+///
+/// It includes the block number and state root, so it shall be near 40 bytes, but let's have some
+/// reserve.
+pub const MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE: u32 = 128;
 
 /// Re-export `time_units` to make usage easier.
 pub use time_units::*;
diff --git a/bridges/primitives/chain-rococo/src/lib.rs b/bridges/primitives/chain-rococo/src/lib.rs
index 32e4e30ca4a..57a47211e4e 100644
--- a/bridges/primitives/chain-rococo/src/lib.rs
+++ b/bridges/primitives/chain-rococo/src/lib.rs
@@ -35,12 +35,12 @@ pub const PARAS_PALLET_NAME: &str = "Paras";
 /// Name of the With-Rococo GRANDPA pallet instance that is deployed at bridged chains.
 pub const WITH_ROCOCO_GRANDPA_PALLET_NAME: &str = "BridgeRococoGrandpa";
 
-/// Maximal SCALE-encoded size of parachains headers that are stored at Rococo `Paras` pallet.
+/// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Rococo
+/// parachains.
 ///
-/// Let's assume that the largest header is header that enacts new authorities set with
-/// `MAX_AUTHORITES_COUNT`. Every authority means 32-byte key and 8-byte weight. Let's also have
-/// some fixed reserve for other things (digest, block hash and number, ...) as well.
-pub const MAX_NESTED_PARACHAIN_HEAD_SIZE: u32 = 4096 + MAX_AUTHORITIES_COUNT * 40;
+/// It includes the block number and state root, so it shall be near 40 bytes, but let's have some
+/// reserve.
+pub const MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE: u32 = 128;
 
 /// Maximal number of GRANDPA authorities at Rococo.
 ///
diff --git a/bridges/primitives/chain-westend/Cargo.toml b/bridges/primitives/chain-westend/Cargo.toml
index 7d74362b09a..75f6727d764 100644
--- a/bridges/primitives/chain-westend/Cargo.toml
+++ b/bridges/primitives/chain-westend/Cargo.toml
@@ -15,6 +15,7 @@ bp-runtime = { path = "../runtime", default-features = false }
 
 # Substrate Based Dependencies
 
+frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
 sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
 
 [features]
@@ -22,5 +23,6 @@ default = ["std"]
 std = [
 	"bp-polkadot-core/std",
 	"bp-runtime/std",
+	"frame-support/std",
 	"sp-api/std",
 ]
diff --git a/bridges/primitives/chain-westend/src/lib.rs b/bridges/primitives/chain-westend/src/lib.rs
index 6df4d5ff6d5..8a3794b418e 100644
--- a/bridges/primitives/chain-westend/src/lib.rs
+++ b/bridges/primitives/chain-westend/src/lib.rs
@@ -19,11 +19,42 @@
 #![allow(clippy::too_many_arguments)]
 
 pub use bp_polkadot_core::*;
-use bp_runtime::decl_bridge_finality_runtime_apis;
+use bp_runtime::{decl_bridge_finality_runtime_apis, Chain, Parachain};
+use frame_support::weights::Weight;
 
 /// Westend Chain
 pub type Westend = PolkadotLike;
 
+/// Westmint parachain definition
+#[derive(Debug, Clone, Copy)]
+pub struct Westmint;
+
+// Westmint seems to use the same configuration as all Polkadot-like chains, so we'll use Westend
+// primitives here.
+impl Chain for Westmint {
+	type BlockNumber = BlockNumber;
+	type Hash = Hash;
+	type Hasher = Hasher;
+	type Header = Header;
+
+	type AccountId = AccountId;
+	type Balance = Balance;
+	type Index = Nonce;
+	type Signature = Signature;
+
+	fn max_extrinsic_size() -> u32 {
+		Westend::max_extrinsic_size()
+	}
+
+	fn max_extrinsic_weight() -> Weight {
+		Westend::max_extrinsic_weight()
+	}
+}
+
+impl Parachain for Westmint {
+	const PARACHAIN_ID: u32 = WESTMINT_PARACHAIN_ID;
+}
+
 /// Name of the parachains pallet at the Westend runtime.
 pub const PARAS_PALLET_NAME: &str = "Paras";
 
@@ -39,10 +70,9 @@ pub const MAX_AUTHORITIES_COUNT: u32 = 100_000;
 
 /// Maximal SCALE-encoded size of parachains headers that are stored at Westend `Paras` pallet.
 ///
-/// Let's assume that the largest header is header that enacts new authorities set with
-/// `MAX_AUTHORITES_COUNT`. Every authority means 32-byte key and 8-byte weight. Let's also have
-/// some fixed reserve for other things (digest, block hash and number, ...) as well.
-pub const MAX_NESTED_PARACHAIN_HEAD_SIZE: u32 = 4096 + MAX_AUTHORITIES_COUNT * 40;
+/// It includes the block number and state root, so it shall be near 40 bytes, but let's have some
+/// reserve.
+pub const MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE: u32 = 128;
 
 /// Identifier of Westmint parachain at the Westend relay chain.
 pub const WESTMINT_PARACHAIN_ID: u32 = 2000;
diff --git a/bridges/primitives/chain-wococo/src/lib.rs b/bridges/primitives/chain-wococo/src/lib.rs
index c4dc6da13f0..1cf666c7f96 100644
--- a/bridges/primitives/chain-wococo/src/lib.rs
+++ b/bridges/primitives/chain-wococo/src/lib.rs
@@ -20,7 +20,7 @@
 
 pub use bp_polkadot_core::*;
 pub use bp_rococo::{
-	SS58Prefix, MAX_AUTHORITIES_COUNT, MAX_NESTED_PARACHAIN_HEAD_SIZE, PARAS_PALLET_NAME,
+	SS58Prefix, MAX_AUTHORITIES_COUNT, MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE, PARAS_PALLET_NAME,
 };
 use bp_runtime::decl_bridge_finality_runtime_apis;
 
diff --git a/bridges/primitives/parachains/Cargo.toml b/bridges/primitives/parachains/Cargo.toml
index 0aa2adfead1..333f7ad647a 100644
--- a/bridges/primitives/parachains/Cargo.toml
+++ b/bridges/primitives/parachains/Cargo.toml
@@ -8,10 +8,12 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
 
 [dependencies]
 codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] }
+impl-trait-for-tuples = "0.2"
 scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
 
 # Bridge dependencies
 
+bp-header-chain = { path = "../header-chain", default-features = false }
 bp-polkadot-core = { path = "../polkadot-core", default-features = false }
 bp-runtime = { path = "../runtime", default-features = false }
 
@@ -19,14 +21,19 @@ bp-runtime = { path = "../runtime", default-features = false }
 
 frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
 sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
+sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
+sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
 
 [features]
 default = ["std"]
 std = [
+	"bp-header-chain/std",
 	"bp-polkadot-core/std",
 	"bp-runtime/std",
 	"codec/std",
 	"frame-support/std",
 	"scale-info/std",
 	"sp-core/std",
+	"sp-runtime/std",
+	"sp-std/std",
 ]
diff --git a/bridges/primitives/parachains/src/lib.rs b/bridges/primitives/parachains/src/lib.rs
index f2edebf8a22..cafe40da056 100644
--- a/bridges/primitives/parachains/src/lib.rs
+++ b/bridges/primitives/parachains/src/lib.rs
@@ -18,15 +18,22 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
+pub use bp_header_chain::StoredHeaderData;
+
 use bp_polkadot_core::{
 	parachains::{ParaHash, ParaHead, ParaId},
 	BlockNumber as RelayBlockNumber,
 };
-use bp_runtime::{StorageDoubleMapKeyProvider, StorageMapKeyProvider};
+use bp_runtime::{
+	BlockNumberOf, Chain, HashOf, HeaderOf, Parachain, StorageDoubleMapKeyProvider,
+	StorageMapKeyProvider,
+};
 use codec::{Decode, Encode, MaxEncodedLen};
 use frame_support::{Blake2_128Concat, RuntimeDebug, Twox64Concat};
 use scale_info::TypeInfo;
 use sp_core::storage::StorageKey;
+use sp_runtime::traits::Header as HeaderT;
+use sp_std::{marker::PhantomData, prelude::*};
 
 /// Best known parachain head hash.
 #[derive(Clone, Decode, Encode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)]
@@ -86,5 +93,60 @@ impl StorageDoubleMapKeyProvider for ImportedParaHeadsKeyProvider {
 	type Key1 = ParaId;
 	type Hasher2 = Blake2_128Concat;
 	type Key2 = ParaHash;
-	type Value = ParaHead;
+	type Value = ParaStoredHeaderData;
+}
+
+/// Stored data of the parachain head. It is encoded version of the
+/// `bp_runtime::StoredHeaderData` structure.
+///
+/// We do not know exact structure of the parachain head, so we always store encoded version
+/// of the `bp_runtime::StoredHeaderData`. It is only decoded when we talk about specific parachain.
+#[derive(Clone, Decode, Encode, PartialEq, RuntimeDebug, TypeInfo)]
+pub struct ParaStoredHeaderData(pub Vec<u8>);
+
+impl ParaStoredHeaderData {
+	/// Decode stored parachain head data.
+	pub fn decode_parachain_head_data<C: Chain>(
+		&self,
+	) -> Result<StoredHeaderData<BlockNumberOf<C>, HashOf<C>>, codec::Error> {
+		StoredHeaderData::<BlockNumberOf<C>, HashOf<C>>::decode(&mut &self.0[..])
+	}
+}
+
+/// Stored parachain head data builder.
+pub trait ParaStoredHeaderDataBuilder {
+	/// Try to build head data from self.
+	fn try_build(para_id: ParaId, para_head: &ParaHead) -> Option<ParaStoredHeaderData>;
+}
+
+/// Helper for using single parachain as `ParaStoredHeaderDataBuilder`.
+pub struct SingleParaStoredHeaderDataBuilder<C: Parachain>(PhantomData<C>);
+
+impl<C: Parachain> ParaStoredHeaderDataBuilder for SingleParaStoredHeaderDataBuilder<C> {
+	fn try_build(para_id: ParaId, para_head: &ParaHead) -> Option<ParaStoredHeaderData> {
+		if para_id == ParaId(C::PARACHAIN_ID) {
+			let header = HeaderOf::<C>::decode(&mut &para_head.0[..]).ok()?;
+			return Some(ParaStoredHeaderData(
+				StoredHeaderData { number: *header.number(), state_root: *header.state_root() }
+					.encode(),
+			))
+		}
+		None
+	}
+}
+
+// Tries to build header data from each tuple member, short-circuiting on first successful one.
+#[impl_trait_for_tuples::impl_for_tuples(1, 30)]
+#[tuple_types_custom_trait_bound(Parachain)]
+impl ParaStoredHeaderDataBuilder for C {
+	fn try_build(para_id: ParaId, para_head: &ParaHead) -> Option<ParaStoredHeaderData> {
+		for_tuples!( #(
+			let maybe_para_head = SingleParaStoredHeaderDataBuilder::<C>::try_build(para_id, para_head);
+			if let Some(maybe_para_head) = maybe_para_head {
+				return Some(maybe_para_head);
+			}
+		)* );
+
+		None
+	}
 }
diff --git a/bridges/primitives/polkadot-core/src/parachains.rs b/bridges/primitives/polkadot-core/src/parachains.rs
index 51fcd59cae1..e8f68dd2a9a 100644
--- a/bridges/primitives/polkadot-core/src/parachains.rs
+++ b/bridges/primitives/polkadot-core/src/parachains.rs
@@ -66,9 +66,7 @@ impl From<u32> for ParaId {
 ///
 /// This is an equivalent of the `polkadot_parachain::HeadData`.
 ///
-/// The parachain head means (at least in Cumulus) a SCALE-encoded parachain header. Keep in mind
-/// that in Polkadot it is twice-encoded (so `header.encode().encode()`). We'll also do it to keep
-/// it binary-compatible (implies hash-compatibility) with other parachain pallets.
+/// The parachain head means (at least in Cumulus) a SCALE-encoded parachain header.
 #[derive(
 	PartialEq, Eq, Clone, PartialOrd, Ord, Encode, Decode, RuntimeDebug, TypeInfo, Default,
 )]
diff --git a/bridges/primitives/runtime/src/chain.rs b/bridges/primitives/runtime/src/chain.rs
index cb5cbad434c..78e63be405b 100644
--- a/bridges/primitives/runtime/src/chain.rs
+++ b/bridges/primitives/runtime/src/chain.rs
@@ -111,10 +111,7 @@ pub trait Chain: Send + Sync + 'static {
 		+ AsPrimitive<usize>
 		+ Default
 		+ Saturating
-		+ MaxEncodedLen
-		// original `sp_runtime::traits::Header::BlockNumber` doesn't have this trait, but
-		// `sp_runtime::generic::Era` requires block number -> `u64` conversion.
-		+ Into<u64>;
+		+ MaxEncodedLen;
 
 	/// A type that fulfills the abstract idea of what a Substrate hash is.
 	// Constraits come from the associated Hash type of `sp_runtime::traits::Header`
diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs
index 34f521a7273..5db54b87f7f 100644
--- a/bridges/primitives/runtime/src/lib.rs
+++ b/bridges/primitives/runtime/src/lib.rs
@@ -27,7 +27,7 @@ use frame_system::RawOrigin;
 use scale_info::TypeInfo;
 use sp_core::{hash::H256, storage::StorageKey};
 use sp_io::hashing::blake2_256;
-use sp_runtime::traits::{BadOrigin, Header as HeaderT};
+use sp_runtime::traits::{BadOrigin, Header as HeaderT, UniqueSaturatedInto};
 use sp_std::{convert::TryFrom, fmt::Debug, vec, vec::Vec};
 
 pub use chain::{
@@ -124,6 +124,9 @@ impl<Hash: Copy, Number: Copy> HeaderId<Hash, Number> {
 	}
 }
 
+/// Header id used by the chain.
+pub type HeaderIdOf<C> = HeaderId<HashOf<C>, BlockNumberOf<C>>;
+
 /// Generic header id provider.
 pub trait HeaderIdProvider<Header: HeaderT> {
 	// Get the header id.
@@ -225,7 +228,9 @@ pub enum TransactionEra<BlockNumber, BlockHash> {
 	Mortal(HeaderId<BlockHash, BlockNumber>, u32),
 }
 
-impl<BlockNumber: Copy + Into<u64>, BlockHash: Copy> TransactionEra<BlockNumber, BlockHash> {
+impl<BlockNumber: Copy + UniqueSaturatedInto<u64>, BlockHash: Copy>
+	TransactionEra<BlockNumber, BlockHash>
+{
 	/// Prepare transaction era, based on mortality period and current best block number.
 	pub fn new(
 		best_block_id: HeaderId<BlockHash, BlockNumber>,
@@ -253,8 +258,10 @@ impl<BlockNumber: Copy + Into<u64>, BlockHash: Copy> TransactionEra<BlockNumber,
 	pub fn frame_era(&self) -> sp_runtime::generic::Era {
 		match *self {
 			TransactionEra::Immortal => sp_runtime::generic::Era::immortal(),
+			// `unique_saturated_into` is fine here - mortality `u64::MAX` is not something we
+			// expect to see on any chain
 			TransactionEra::Mortal(header_id, period) =>
-				sp_runtime::generic::Era::mortal(period as _, header_id.0.into()),
+				sp_runtime::generic::Era::mortal(period as _, header_id.0.unique_saturated_into()),
 		}
 	}
 
diff --git a/bridges/relays/client-substrate/src/lib.rs b/bridges/relays/client-substrate/src/lib.rs
index 99ff0fbe394..a5e73d78b4b 100644
--- a/bridges/relays/client-substrate/src/lib.rs
+++ b/bridges/relays/client-substrate/src/lib.rs
@@ -44,13 +44,10 @@ pub use crate::{
 	transaction_tracker::TransactionTracker,
 };
 pub use bp_runtime::{
-	AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, Chain as ChainBase, HashOf, HeaderOf,
-	IndexOf, SignatureOf, TransactionEra, TransactionEraOf,
+	AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, Chain as ChainBase, HashOf, HeaderIdOf,
+	HeaderOf, IndexOf, SignatureOf, TransactionEra, TransactionEraOf,
 };
 
-/// Header id used by the chain.
-pub type HeaderIdOf<C> = relay_utils::HeaderId<HashOf<C>, BlockNumberOf<C>>;
-
 /// Substrate-over-websocket connection params.
 #[derive(Debug, Clone)]
 pub struct ConnectionParams {
diff --git a/bridges/relays/finality/src/sync_loop_metrics.rs b/bridges/relays/finality/src/sync_loop_metrics.rs
index ae73bbedc4f..4da1df811f6 100644
--- a/bridges/relays/finality/src/sync_loop_metrics.rs
+++ b/bridges/relays/finality/src/sync_loop_metrics.rs
@@ -16,7 +16,10 @@
 
 //! Metrics for headers synchronization relay loop.
 
-use relay_utils::metrics::{metric_name, register, IntGauge, Metric, PrometheusError, Registry};
+use relay_utils::{
+	metrics::{metric_name, register, IntGauge, Metric, PrometheusError, Registry},
+	UniqueSaturatedInto,
+};
 
 /// Headers sync metrics.
 #[derive(Clone)]
@@ -61,13 +64,19 @@ impl SyncLoopMetrics {
 	}
 
 	/// Update best block number at source.
-	pub fn update_best_block_at_source<Number: Into<u64>>(&self, source_best_number: Number) {
-		self.best_source_block_number.set(source_best_number.into());
+	pub fn update_best_block_at_source<Number: UniqueSaturatedInto<u64>>(
+		&self,
+		source_best_number: Number,
+	) {
+		self.best_source_block_number.set(source_best_number.unique_saturated_into());
 	}
 
 	/// Update best block number at target.
-	pub fn update_best_block_at_target<Number: Into<u64>>(&self, target_best_number: Number) {
-		self.best_target_block_number.set(target_best_number.into());
+	pub fn update_best_block_at_target<Number: UniqueSaturatedInto<u64>>(
+		&self,
+		target_best_number: Number,
+	) {
+		self.best_target_block_number.set(target_best_number.unique_saturated_into());
 	}
 
 	/// Update using-same-fork flag.
diff --git a/bridges/relays/lib-substrate-relay/src/parachains/target.rs b/bridges/relays/lib-substrate-relay/src/parachains/target.rs
index 68fd72765aa..027a6b75222 100644
--- a/bridges/relays/lib-substrate-relay/src/parachains/target.rs
+++ b/bridges/relays/lib-substrate-relay/src/parachains/target.rs
@@ -33,12 +33,10 @@ use parachains_relay::{
 };
 use relay_substrate_client::{
 	AccountIdOf, AccountKeyPairOf, BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf,
-	HeaderIdOf, HeaderOf, RelayChain, SignParam, TransactionEra, TransactionTracker,
-	UnsignedTransaction,
+	HeaderIdOf, RelayChain, SignParam, TransactionEra, TransactionTracker, UnsignedTransaction,
 };
 use relay_utils::{relay_loop::Client as RelayClient, HeaderId};
 use sp_core::{Bytes, Pair};
-use sp_runtime::traits::Header as HeaderT;
 
 /// Substrate client as parachain heads source.
 pub struct ParachainsTarget<P: SubstrateParachainsPipeline> {
@@ -132,7 +130,7 @@ where
 			.map(|para_info| para_info.best_head_hash);
 
 		if let (Some(metrics), Some(best_para_head_hash)) = (metrics, &best_para_head_hash) {
-			let imported_para_head = self
+			let imported_para_head_number = self
 				.client
 				.storage_double_map_value::<ImportedParaHeadsKeyProvider>(
 					P::SourceRelayChain::PARACHAINS_FINALITY_PALLET_NAME,
@@ -142,10 +140,11 @@ where
 				)
 				.await
 				.and_then(|maybe_encoded_head| match maybe_encoded_head {
-					Some(encoded_head) =>
-						HeaderOf::<P::SourceParachain>::decode(&mut &encoded_head.0[..])
-							.map(Some)
-							.map_err(Self::Error::ResponseParseFailed),
+					Some(encoded_head) => encoded_head
+						.decode_parachain_head_data::<P::SourceParachain>()
+						.map(|head| head.number)
+						.map(Some)
+						.map_err(Self::Error::ResponseParseFailed),
 					None => Ok(None),
 				})
 				.map_err(|e| {
@@ -159,9 +158,8 @@ where
 					e
 				})
 				.unwrap_or(None);
-			if let Some(imported_para_head) = imported_para_head {
-				metrics
-					.update_best_parachain_block_at_target(para_id, *imported_para_head.number());
+			if let Some(imported_para_head_number) = imported_para_head_number {
+				metrics.update_best_parachain_block_at_target(para_id, imported_para_head_number);
 			}
 		}
 
diff --git a/bridges/relays/messages/src/metrics.rs b/bridges/relays/messages/src/metrics.rs
index 4decb7e092e..ace4264cacc 100644
--- a/bridges/relays/messages/src/metrics.rs
+++ b/bridges/relays/messages/src/metrics.rs
@@ -65,10 +65,9 @@ impl MessageLaneLoopMetrics {
 	/// Update source client state metrics.
 	pub fn update_source_state<P: MessageLane>(&self, source_client_state: SourceClientState<P>) {
 		self.source_to_target_finality_metrics
-			.update_best_block_at_source(source_client_state.best_self.0.into());
-		self.target_to_source_finality_metrics.update_best_block_at_target(
-			source_client_state.best_finalized_peer_at_best_self.0.into(),
-		);
+			.update_best_block_at_source(source_client_state.best_self.0);
+		self.target_to_source_finality_metrics
+			.update_best_block_at_target(source_client_state.best_finalized_peer_at_best_self.0);
 		self.target_to_source_finality_metrics.update_using_same_fork(
 			source_client_state.best_finalized_peer_at_best_self.1 ==
 				source_client_state.actual_best_finalized_peer_at_best_self.1,
@@ -78,10 +77,9 @@ impl MessageLaneLoopMetrics {
 	/// Update target client state metrics.
 	pub fn update_target_state<P: MessageLane>(&self, target_client_state: TargetClientState<P>) {
 		self.target_to_source_finality_metrics
-			.update_best_block_at_source(target_client_state.best_self.0.into());
-		self.source_to_target_finality_metrics.update_best_block_at_target(
-			target_client_state.best_finalized_peer_at_best_self.0.into(),
-		);
+			.update_best_block_at_source(target_client_state.best_self.0);
+		self.source_to_target_finality_metrics
+			.update_best_block_at_target(target_client_state.best_finalized_peer_at_best_self.0);
 		self.source_to_target_finality_metrics.update_using_same_fork(
 			target_client_state.best_finalized_peer_at_best_self.1 ==
 				target_client_state.actual_best_finalized_peer_at_best_self.1,
diff --git a/bridges/relays/parachains/src/parachains_loop_metrics.rs b/bridges/relays/parachains/src/parachains_loop_metrics.rs
index ff8bace2744..5df996b4ddd 100644
--- a/bridges/relays/parachains/src/parachains_loop_metrics.rs
+++ b/bridges/relays/parachains/src/parachains_loop_metrics.rs
@@ -15,8 +15,9 @@
 // along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
 
 use bp_polkadot_core::parachains::ParaId;
-use relay_utils::metrics::{
-	metric_name, register, GaugeVec, Metric, Opts, PrometheusError, Registry, U64,
+use relay_utils::{
+	metrics::{metric_name, register, GaugeVec, Metric, Opts, PrometheusError, Registry, U64},
+	UniqueSaturatedInto,
 };
 
 /// Parachains sync metrics.
@@ -50,12 +51,12 @@ impl ParachainsLoopMetrics {
 	}
 
 	/// Update best block number at source.
-	pub fn update_best_parachain_block_at_source<Number: Into<u64>>(
+	pub fn update_best_parachain_block_at_source<Number: UniqueSaturatedInto<u64>>(
 		&self,
 		parachain: ParaId,
 		block_number: Number,
 	) {
-		let block_number = block_number.into();
+		let block_number = block_number.unique_saturated_into();
 		let label = parachain_label(&parachain);
 		log::trace!(
 			target: "bridge-metrics",
@@ -67,12 +68,12 @@ impl ParachainsLoopMetrics {
 	}
 
 	/// Update best block number at target.
-	pub fn update_best_parachain_block_at_target<Number: Into<u64>>(
+	pub fn update_best_parachain_block_at_target<Number: UniqueSaturatedInto<u64>>(
 		&self,
 		parachain: ParaId,
 		block_number: Number,
 	) {
-		let block_number = block_number.into();
+		let block_number = block_number.unique_saturated_into();
 		let label = parachain_label(&parachain);
 		log::trace!(
 			target: "bridge-metrics",
diff --git a/bridges/relays/utils/Cargo.toml b/bridges/relays/utils/Cargo.toml
index ebbfa74c6ff..09cec370900 100644
--- a/bridges/relays/utils/Cargo.toml
+++ b/bridges/relays/utils/Cargo.toml
@@ -29,4 +29,5 @@ bp-runtime = { path = "../../primitives/runtime" }
 
 # Substrate dependencies
 
+sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
 substrate-prometheus-endpoint = { git = "https://github.com/paritytech/substrate", branch = "master" }
diff --git a/bridges/relays/utils/src/lib.rs b/bridges/relays/utils/src/lib.rs
index 42bf86ebf5b..16e1a18bab7 100644
--- a/bridges/relays/utils/src/lib.rs
+++ b/bridges/relays/utils/src/lib.rs
@@ -19,6 +19,7 @@
 pub use bp_runtime::HeaderId;
 pub use error::Error;
 pub use relay_loop::{relay_loop, relay_metrics};
+pub use sp_runtime::traits::UniqueSaturatedInto;
 
 use async_trait::async_trait;
 use backoff::{backoff::Backoff, ExponentialBackoff};
@@ -51,7 +52,7 @@ pub mod relay_loop;
 pub trait BlockNumberBase:
 	'static
 	+ From<u32>
-	+ Into<u64>
+	+ UniqueSaturatedInto<u64>
 	+ Ord
 	+ Clone
 	+ Copy
@@ -73,7 +74,7 @@ pub trait BlockNumberBase:
 impl<T> BlockNumberBase for T where
 	T: 'static
 		+ From<u32>
-		+ Into<u64>
+		+ UniqueSaturatedInto<u64>
 		+ Ord
 		+ Clone
 		+ Copy
-- 
GitLab