From 6dcecf4425824cb387429f9416086651dd7be073 Mon Sep 17 00:00:00 2001
From: Svyatoslav Nikolsky <svyatonik@gmail.com>
Date: Fri, 11 Nov 2022 15:31:01 +0300
Subject: [PATCH] Reintroduce header chain trait (#1622)

* reintroduce header chain trait

* renive BridgedChainWithMessages::maximal_extrinsic_size
---
 .../bin/millau/runtime/src/rialto_messages.rs |  39 +-
 .../runtime/src/rialto_parachain_messages.rs  |  46 +-
 .../runtime/src/millau_messages.rs            |  37 +-
 .../bin/rialto/runtime/src/millau_messages.rs |  37 +-
 bridges/bin/runtime-common/Cargo.toml         |   2 +
 bridges/bin/runtime-common/src/integrity.rs   |  31 -
 bridges/bin/runtime-common/src/lib.rs         |   2 +
 bridges/bin/runtime-common/src/messages.rs    | 861 +++++++-----------
 .../src/messages_benchmarking.rs              | 131 +--
 .../runtime-common/src/messages_generation.rs | 154 ++++
 .../src/parachains_benchmarking.rs            |   4 +-
 bridges/modules/grandpa/src/lib.rs            |  34 +-
 bridges/modules/parachains/Cargo.toml         |   2 +
 bridges/modules/parachains/src/lib.rs         |  41 +-
 .../chain-rialto-parachain/src/lib.rs         |   6 +-
 bridges/primitives/header-chain/Cargo.toml    |   2 +
 bridges/primitives/header-chain/src/lib.rs    |  42 +-
 bridges/primitives/runtime/src/chain.rs       |   6 +
 bridges/primitives/runtime/src/lib.rs         |   2 +-
 19 files changed, 648 insertions(+), 831 deletions(-)
 create mode 100644 bridges/bin/runtime-common/src/messages_generation.rs

diff --git a/bridges/bin/millau/runtime/src/rialto_messages.rs b/bridges/bin/millau/runtime/src/rialto_messages.rs
index cf2bd4badaf..c989de67b17 100644
--- a/bridges/bin/millau/runtime/src/rialto_messages.rs
+++ b/bridges/bin/millau/runtime/src/rialto_messages.rs
@@ -16,14 +16,14 @@
 
 //! Everything required to serve Millau <-> Rialto messages.
 
-use crate::{OriginCaller, Runtime, RuntimeCall, RuntimeOrigin};
+use crate::{OriginCaller, RialtoGrandpaInstance, Runtime, RuntimeCall, RuntimeOrigin};
 
 use bp_messages::{
 	source_chain::TargetHeaderChain,
 	target_chain::{ProvedMessages, SourceHeaderChain},
 	InboundLaneData, LaneId, Message, MessageNonce, Parameter as MessagesParameter,
 };
-use bp_runtime::{Chain, ChainId, MILLAU_CHAIN_ID, RIALTO_CHAIN_ID};
+use bp_runtime::{ChainId, MILLAU_CHAIN_ID, RIALTO_CHAIN_ID};
 use bridge_runtime_common::messages::{
 	self, BasicConfirmationTransactionEstimation, MessageBridge, MessageTransaction,
 };
@@ -98,6 +98,8 @@ impl MessageBridge for WithRialtoMessageBridge {
 
 	type ThisChain = Millau;
 	type BridgedChain = Rialto;
+	type BridgedHeaderChain =
+		pallet_bridge_grandpa::GrandpaChainHeaders<Runtime, RialtoGrandpaInstance>;
 
 	fn bridged_balance_to_this_balance(
 		bridged_balance: bp_rialto::Balance,
@@ -115,18 +117,14 @@ impl MessageBridge for WithRialtoMessageBridge {
 pub struct Millau;
 
 impl messages::ChainWithMessages for Millau {
-	type Hash = bp_millau::Hash;
-	type AccountId = bp_millau::AccountId;
-	type Signer = bp_millau::AccountSigner;
-	type Signature = bp_millau::Signature;
-	type Balance = bp_millau::Balance;
+	type Chain = bp_millau::Millau;
 }
 
 impl messages::ThisChainWithMessages for Millau {
 	type RuntimeOrigin = RuntimeOrigin;
 	type RuntimeCall = RuntimeCall;
 	type ConfirmationTransactionEstimation = BasicConfirmationTransactionEstimation<
-		Self::AccountId,
+		bp_millau::AccountId,
 		{ bp_millau::MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT.ref_time() },
 		{ bp_rialto::EXTRA_STORAGE_PROOF_SIZE },
 		{ bp_millau::TX_EXTRA_BYTES },
@@ -177,18 +175,10 @@ impl messages::ThisChainWithMessages for Millau {
 pub struct Rialto;
 
 impl messages::ChainWithMessages for Rialto {
-	type Hash = bp_rialto::Hash;
-	type AccountId = bp_rialto::AccountId;
-	type Signer = bp_rialto::AccountSigner;
-	type Signature = bp_rialto::Signature;
-	type Balance = bp_rialto::Balance;
+	type Chain = bp_rialto::Rialto;
 }
 
 impl messages::BridgedChainWithMessages for Rialto {
-	fn maximal_extrinsic_size() -> u32 {
-		bp_rialto::Rialto::max_extrinsic_size()
-	}
-
 	fn verify_dispatch_weight(_message_payload: &[u8]) -> bool {
 		true
 	}
@@ -248,11 +238,7 @@ impl TargetHeaderChain<ToRialtoMessagePayload, bp_millau::AccountId> for Rialto
 	fn verify_messages_delivery_proof(
 		proof: Self::MessagesDeliveryProof,
 	) -> Result<(LaneId, InboundLaneData<bp_millau::AccountId>), Self::Error> {
-		messages::source::verify_messages_delivery_proof::<
-			WithRialtoMessageBridge,
-			Runtime,
-			crate::RialtoGrandpaInstance,
-		>(proof)
+		messages::source::verify_messages_delivery_proof::<WithRialtoMessageBridge>(proof)
 	}
 }
 
@@ -269,11 +255,8 @@ impl SourceHeaderChain<bp_rialto::Balance> for Rialto {
 		proof: Self::MessagesProof,
 		messages_count: u32,
 	) -> Result<ProvedMessages<Message<bp_rialto::Balance>>, Self::Error> {
-		messages::target::verify_messages_proof::<
-			WithRialtoMessageBridge,
-			Runtime,
-			crate::RialtoGrandpaInstance,
-		>(proof, messages_count)
+		messages::target::verify_messages_proof::<WithRialtoMessageBridge>(proof, messages_count)
+			.map_err(Into::into)
 	}
 }
 
@@ -296,7 +279,7 @@ impl MessagesParameter for MillauToRialtoMessagesParameter {
 #[cfg(test)]
 mod tests {
 	use super::*;
-	use crate::{DbWeight, RialtoGrandpaInstance, Runtime, WithRialtoMessagesInstance};
+	use crate::{DbWeight, Runtime, WithRialtoMessagesInstance};
 
 	use bp_runtime::Chain;
 	use bridge_runtime_common::{
diff --git a/bridges/bin/millau/runtime/src/rialto_parachain_messages.rs b/bridges/bin/millau/runtime/src/rialto_parachain_messages.rs
index cce7aa79bad..70e8db89d3a 100644
--- a/bridges/bin/millau/runtime/src/rialto_parachain_messages.rs
+++ b/bridges/bin/millau/runtime/src/rialto_parachain_messages.rs
@@ -16,15 +16,14 @@
 
 //! Everything required to serve Millau <-> RialtoParachain messages.
 
-use crate::{Runtime, RuntimeCall, RuntimeOrigin};
+use crate::{Runtime, RuntimeCall, RuntimeOrigin, WithRialtoParachainsInstance};
 
 use bp_messages::{
 	source_chain::TargetHeaderChain,
 	target_chain::{ProvedMessages, SourceHeaderChain},
 	InboundLaneData, LaneId, Message, MessageNonce, Parameter as MessagesParameter,
 };
-use bp_polkadot_core::parachains::ParaId;
-use bp_runtime::{Chain, ChainId, MILLAU_CHAIN_ID, RIALTO_PARACHAIN_CHAIN_ID};
+use bp_runtime::{ChainId, MILLAU_CHAIN_ID, RIALTO_PARACHAIN_CHAIN_ID};
 use bridge_runtime_common::messages::{
 	self, BasicConfirmationTransactionEstimation, MessageBridge, MessageTransaction,
 };
@@ -103,6 +102,11 @@ impl MessageBridge for WithRialtoParachainMessageBridge {
 
 	type ThisChain = Millau;
 	type BridgedChain = RialtoParachain;
+	type BridgedHeaderChain = pallet_bridge_parachains::ParachainHeaders<
+		Runtime,
+		WithRialtoParachainsInstance,
+		bp_rialto_parachain::RialtoParachain,
+	>;
 
 	fn bridged_balance_to_this_balance(
 		bridged_balance: bp_rialto_parachain::Balance,
@@ -120,18 +124,14 @@ impl MessageBridge for WithRialtoParachainMessageBridge {
 pub struct Millau;
 
 impl messages::ChainWithMessages for Millau {
-	type Hash = bp_millau::Hash;
-	type AccountId = bp_millau::AccountId;
-	type Signer = bp_millau::AccountSigner;
-	type Signature = bp_millau::Signature;
-	type Balance = bp_millau::Balance;
+	type Chain = bp_millau::Millau;
 }
 
 impl messages::ThisChainWithMessages for Millau {
 	type RuntimeCall = RuntimeCall;
 	type RuntimeOrigin = RuntimeOrigin;
 	type ConfirmationTransactionEstimation = BasicConfirmationTransactionEstimation<
-		Self::AccountId,
+		bp_rialto_parachain::AccountId,
 		{ bp_millau::MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT.ref_time() },
 		{ bp_rialto_parachain::EXTRA_STORAGE_PROOF_SIZE },
 		{ bp_millau::TX_EXTRA_BYTES },
@@ -166,18 +166,10 @@ impl messages::ThisChainWithMessages for Millau {
 pub struct RialtoParachain;
 
 impl messages::ChainWithMessages for RialtoParachain {
-	type Hash = bp_rialto_parachain::Hash;
-	type AccountId = bp_rialto_parachain::AccountId;
-	type Signer = bp_rialto_parachain::AccountSigner;
-	type Signature = bp_rialto_parachain::Signature;
-	type Balance = bp_rialto_parachain::Balance;
+	type Chain = bp_rialto_parachain::RialtoParachain;
 }
 
 impl messages::BridgedChainWithMessages for RialtoParachain {
-	fn maximal_extrinsic_size() -> u32 {
-		bp_rialto_parachain::RialtoParachain::max_extrinsic_size()
-	}
-
 	fn verify_dispatch_weight(_message_payload: &[u8]) -> bool {
 		true
 	}
@@ -241,12 +233,7 @@ impl TargetHeaderChain<ToRialtoParachainMessagePayload, bp_millau::AccountId> fo
 	fn verify_messages_delivery_proof(
 		proof: Self::MessagesDeliveryProof,
 	) -> Result<(LaneId, InboundLaneData<bp_millau::AccountId>), Self::Error> {
-		messages::source::verify_messages_delivery_proof_from_parachain::<
-			WithRialtoParachainMessageBridge,
-			bp_rialto_parachain::Header,
-			Runtime,
-			crate::WithRialtoParachainsInstance,
-		>(ParaId(bp_rialto_parachain::RIALTO_PARACHAIN_ID), proof)
+		messages::source::verify_messages_delivery_proof::<WithRialtoParachainMessageBridge>(proof)
 	}
 }
 
@@ -263,12 +250,11 @@ impl SourceHeaderChain<bp_rialto_parachain::Balance> for RialtoParachain {
 		proof: Self::MessagesProof,
 		messages_count: u32,
 	) -> Result<ProvedMessages<Message<bp_rialto_parachain::Balance>>, Self::Error> {
-		messages::target::verify_messages_proof_from_parachain::<
-			WithRialtoParachainMessageBridge,
-			bp_rialto_parachain::Header,
-			Runtime,
-			crate::WithRialtoParachainsInstance,
-		>(ParaId(bp_rialto_parachain::RIALTO_PARACHAIN_ID), proof, messages_count)
+		messages::target::verify_messages_proof::<WithRialtoParachainMessageBridge>(
+			proof,
+			messages_count,
+		)
+		.map_err(Into::into)
 	}
 }
 
diff --git a/bridges/bin/rialto-parachain/runtime/src/millau_messages.rs b/bridges/bin/rialto-parachain/runtime/src/millau_messages.rs
index 4688f89a708..a13a17a1ea2 100644
--- a/bridges/bin/rialto-parachain/runtime/src/millau_messages.rs
+++ b/bridges/bin/rialto-parachain/runtime/src/millau_messages.rs
@@ -19,14 +19,14 @@
 // TODO: this is almost exact copy of `millau_messages.rs` from Rialto runtime.
 // Should be extracted to a separate crate and reused here.
 
-use crate::{OriginCaller, Runtime, RuntimeCall, RuntimeOrigin};
+use crate::{MillauGrandpaInstance, OriginCaller, Runtime, RuntimeCall, RuntimeOrigin};
 
 use bp_messages::{
 	source_chain::TargetHeaderChain,
 	target_chain::{ProvedMessages, SourceHeaderChain},
 	InboundLaneData, LaneId, Message, MessageNonce, Parameter as MessagesParameter,
 };
-use bp_runtime::{Chain, ChainId, MILLAU_CHAIN_ID, RIALTO_PARACHAIN_CHAIN_ID};
+use bp_runtime::{ChainId, MILLAU_CHAIN_ID, RIALTO_PARACHAIN_CHAIN_ID};
 use bridge_runtime_common::messages::{
 	self, BasicConfirmationTransactionEstimation, MessageBridge, MessageTransaction,
 };
@@ -102,6 +102,8 @@ impl MessageBridge for WithMillauMessageBridge {
 
 	type ThisChain = RialtoParachain;
 	type BridgedChain = Millau;
+	type BridgedHeaderChain =
+		pallet_bridge_grandpa::GrandpaChainHeaders<Runtime, MillauGrandpaInstance>;
 
 	fn bridged_balance_to_this_balance(
 		bridged_balance: bp_millau::Balance,
@@ -119,18 +121,14 @@ impl MessageBridge for WithMillauMessageBridge {
 pub struct RialtoParachain;
 
 impl messages::ChainWithMessages for RialtoParachain {
-	type Hash = bp_rialto_parachain::Hash;
-	type AccountId = bp_rialto_parachain::AccountId;
-	type Signer = bp_rialto_parachain::AccountSigner;
-	type Signature = bp_rialto_parachain::Signature;
-	type Balance = bp_rialto_parachain::Balance;
+	type Chain = bp_rialto_parachain::RialtoParachain;
 }
 
 impl messages::ThisChainWithMessages for RialtoParachain {
 	type RuntimeCall = RuntimeCall;
 	type RuntimeOrigin = RuntimeOrigin;
 	type ConfirmationTransactionEstimation = BasicConfirmationTransactionEstimation<
-		Self::AccountId,
+		bp_rialto_parachain::AccountId,
 		{ bp_rialto_parachain::MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT.ref_time() },
 		{ bp_millau::EXTRA_STORAGE_PROOF_SIZE },
 		{ bp_rialto_parachain::TX_EXTRA_BYTES },
@@ -184,18 +182,10 @@ impl messages::ThisChainWithMessages for RialtoParachain {
 pub struct Millau;
 
 impl messages::ChainWithMessages for Millau {
-	type Hash = bp_millau::Hash;
-	type AccountId = bp_millau::AccountId;
-	type Signer = bp_millau::AccountSigner;
-	type Signature = bp_millau::Signature;
-	type Balance = bp_millau::Balance;
+	type Chain = bp_millau::Millau;
 }
 
 impl messages::BridgedChainWithMessages for Millau {
-	fn maximal_extrinsic_size() -> u32 {
-		bp_millau::Millau::max_extrinsic_size()
-	}
-
 	fn verify_dispatch_weight(_message_payload: &[u8]) -> bool {
 		true
 	}
@@ -255,11 +245,7 @@ impl TargetHeaderChain<ToMillauMessagePayload, bp_rialto_parachain::AccountId> f
 	fn verify_messages_delivery_proof(
 		proof: Self::MessagesDeliveryProof,
 	) -> Result<(LaneId, InboundLaneData<bp_rialto_parachain::AccountId>), Self::Error> {
-		messages::source::verify_messages_delivery_proof::<
-			WithMillauMessageBridge,
-			Runtime,
-			crate::MillauGrandpaInstance,
-		>(proof)
+		messages::source::verify_messages_delivery_proof::<WithMillauMessageBridge>(proof)
 	}
 }
 
@@ -276,11 +262,8 @@ impl SourceHeaderChain<bp_millau::Balance> for Millau {
 		proof: Self::MessagesProof,
 		messages_count: u32,
 	) -> Result<ProvedMessages<Message<bp_millau::Balance>>, Self::Error> {
-		messages::target::verify_messages_proof::<
-			WithMillauMessageBridge,
-			Runtime,
-			crate::MillauGrandpaInstance,
-		>(proof, messages_count)
+		messages::target::verify_messages_proof::<WithMillauMessageBridge>(proof, messages_count)
+			.map_err(Into::into)
 	}
 }
 
diff --git a/bridges/bin/rialto/runtime/src/millau_messages.rs b/bridges/bin/rialto/runtime/src/millau_messages.rs
index 43c95744165..b27cce40074 100644
--- a/bridges/bin/rialto/runtime/src/millau_messages.rs
+++ b/bridges/bin/rialto/runtime/src/millau_messages.rs
@@ -16,14 +16,14 @@
 
 //! Everything required to serve Millau <-> Rialto messages.
 
-use crate::{OriginCaller, Runtime, RuntimeCall, RuntimeOrigin};
+use crate::{MillauGrandpaInstance, OriginCaller, Runtime, RuntimeCall, RuntimeOrigin};
 
 use bp_messages::{
 	source_chain::TargetHeaderChain,
 	target_chain::{ProvedMessages, SourceHeaderChain},
 	InboundLaneData, LaneId, Message, MessageNonce, Parameter as MessagesParameter,
 };
-use bp_runtime::{Chain, ChainId, MILLAU_CHAIN_ID, RIALTO_CHAIN_ID};
+use bp_runtime::{ChainId, MILLAU_CHAIN_ID, RIALTO_CHAIN_ID};
 use bridge_runtime_common::messages::{
 	self, BasicConfirmationTransactionEstimation, MessageBridge, MessageTransaction,
 };
@@ -96,6 +96,8 @@ impl MessageBridge for WithMillauMessageBridge {
 
 	type ThisChain = Rialto;
 	type BridgedChain = Millau;
+	type BridgedHeaderChain =
+		pallet_bridge_grandpa::GrandpaChainHeaders<Runtime, MillauGrandpaInstance>;
 
 	fn bridged_balance_to_this_balance(
 		bridged_balance: bp_millau::Balance,
@@ -113,18 +115,14 @@ impl MessageBridge for WithMillauMessageBridge {
 pub struct Rialto;
 
 impl messages::ChainWithMessages for Rialto {
-	type Hash = bp_rialto::Hash;
-	type AccountId = bp_rialto::AccountId;
-	type Signer = bp_rialto::AccountSigner;
-	type Signature = bp_rialto::Signature;
-	type Balance = bp_rialto::Balance;
+	type Chain = bp_rialto::Rialto;
 }
 
 impl messages::ThisChainWithMessages for Rialto {
 	type RuntimeOrigin = RuntimeOrigin;
 	type RuntimeCall = RuntimeCall;
 	type ConfirmationTransactionEstimation = BasicConfirmationTransactionEstimation<
-		Self::AccountId,
+		bp_rialto::AccountId,
 		{ bp_rialto::MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT.ref_time() },
 		{ bp_millau::EXTRA_STORAGE_PROOF_SIZE },
 		{ bp_rialto::TX_EXTRA_BYTES },
@@ -175,18 +173,10 @@ impl messages::ThisChainWithMessages for Rialto {
 pub struct Millau;
 
 impl messages::ChainWithMessages for Millau {
-	type Hash = bp_millau::Hash;
-	type AccountId = bp_millau::AccountId;
-	type Signer = bp_millau::AccountSigner;
-	type Signature = bp_millau::Signature;
-	type Balance = bp_millau::Balance;
+	type Chain = bp_millau::Millau;
 }
 
 impl messages::BridgedChainWithMessages for Millau {
-	fn maximal_extrinsic_size() -> u32 {
-		bp_millau::Millau::max_extrinsic_size()
-	}
-
 	fn verify_dispatch_weight(_message_payload: &[u8]) -> bool {
 		true
 	}
@@ -246,11 +236,7 @@ impl TargetHeaderChain<ToMillauMessagePayload, bp_rialto::AccountId> for Millau
 	fn verify_messages_delivery_proof(
 		proof: Self::MessagesDeliveryProof,
 	) -> Result<(LaneId, InboundLaneData<bp_rialto::AccountId>), Self::Error> {
-		messages::source::verify_messages_delivery_proof::<
-			WithMillauMessageBridge,
-			Runtime,
-			crate::MillauGrandpaInstance,
-		>(proof)
+		messages::source::verify_messages_delivery_proof::<WithMillauMessageBridge>(proof)
 	}
 }
 
@@ -267,11 +253,8 @@ impl SourceHeaderChain<bp_millau::Balance> for Millau {
 		proof: Self::MessagesProof,
 		messages_count: u32,
 	) -> Result<ProvedMessages<Message<bp_millau::Balance>>, Self::Error> {
-		messages::target::verify_messages_proof::<
-			WithMillauMessageBridge,
-			Runtime,
-			crate::MillauGrandpaInstance,
-		>(proof, messages_count)
+		messages::target::verify_messages_proof::<WithMillauMessageBridge>(proof, messages_count)
+			.map_err(Into::into)
 	}
 }
 
diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml
index 2fb64a5b733..4f8f7e34c40 100644
--- a/bridges/bin/runtime-common/Cargo.toml
+++ b/bridges/bin/runtime-common/Cargo.toml
@@ -15,6 +15,7 @@ static_assertions = { version = "1.1", optional = true }
 
 # Bridge dependencies
 
+bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
 bp-messages = { path = "../../primitives/messages", default-features = false }
 bp-parachains = { path = "../../primitives/parachains", default-features = false }
 bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false }
@@ -47,6 +48,7 @@ millau-runtime = { path = "../millau/runtime" }
 [features]
 default = ["std"]
 std = [
+	"bp-header-chain/std",
 	"bp-messages/std",
 	"bp-parachains/std",
 	"bp-polkadot-core/std",
diff --git a/bridges/bin/runtime-common/src/integrity.rs b/bridges/bin/runtime-common/src/integrity.rs
index 90f048cb9cc..3b64d1e8afe 100644
--- a/bridges/bin/runtime-common/src/integrity.rs
+++ b/bridges/bin/runtime-common/src/integrity.rs
@@ -49,36 +49,6 @@ macro_rules! assert_chain_types(
 	}
 );
 
-/// Macro that ensures that the bridge configuration and chain primitives crates are sharing
-/// the same types (hash, account id, ...).
-#[macro_export]
-macro_rules! assert_bridge_types(
-	( bridge: $bridge:path, this_chain: $this:path, bridged_chain: $bridged:path ) => {
-		{
-			// if one of this asserts fail, then all chains, bridged with this chain and bridge relays are now broken
-			//
-			// `frame_support::weights::Weight` is used here directly, because all chains we know are using this
-			// primitive (may be changed in the future)
-			use $crate::messages::{
-				AccountIdOf, BalanceOf, BridgedChain, HashOf, SignatureOf, SignerOf, ThisChain,
-			};
-			use static_assertions::assert_type_eq_all;
-
-			assert_type_eq_all!(HashOf<ThisChain<$bridge>>, bp_runtime::HashOf<$this>);
-			assert_type_eq_all!(AccountIdOf<ThisChain<$bridge>>, bp_runtime::AccountIdOf<$this>);
-			assert_type_eq_all!(SignerOf<ThisChain<$bridge>>, bp_runtime::AccountPublicOf<$this>);
-			assert_type_eq_all!(SignatureOf<ThisChain<$bridge>>, bp_runtime::SignatureOf<$this>);
-			assert_type_eq_all!(BalanceOf<ThisChain<$bridge>>, bp_runtime::BalanceOf<$this>);
-
-			assert_type_eq_all!(HashOf<BridgedChain<$bridge>>, bp_runtime::HashOf<$bridged>);
-			assert_type_eq_all!(AccountIdOf<BridgedChain<$bridge>>, bp_runtime::AccountIdOf<$bridged>);
-			assert_type_eq_all!(SignerOf<BridgedChain<$bridge>>, bp_runtime::AccountPublicOf<$bridged>);
-			assert_type_eq_all!(SignatureOf<BridgedChain<$bridge>>, bp_runtime::SignatureOf<$bridged>);
-			assert_type_eq_all!(BalanceOf<BridgedChain<$bridge>>, bp_runtime::BalanceOf<$bridged>);
-		}
-	}
-);
-
 /// Macro that ensures that the bridge GRANDPA pallet is configured properly to bridge with given
 /// chain.
 #[macro_export]
@@ -145,7 +115,6 @@ macro_rules! assert_complete_bridge_types(
 		bridged_chain: $bridged:path,
 	) => {
 		$crate::assert_chain_types!(runtime: $r, this_chain: $this);
-		$crate::assert_bridge_types!(bridge: $bridge, this_chain: $this, bridged_chain: $bridged);
 		$crate::assert_bridge_grandpa_pallet_types!(
 			runtime: $r,
 			with_bridged_chain_grandpa_instance: $gi,
diff --git a/bridges/bin/runtime-common/src/lib.rs b/bridges/bin/runtime-common/src/lib.rs
index e56b887885a..ca8f2268404 100644
--- a/bridges/bin/runtime-common/src/lib.rs
+++ b/bridges/bin/runtime-common/src/lib.rs
@@ -28,6 +28,8 @@ pub mod messages_benchmarking;
 pub mod messages_extension;
 pub mod parachains_benchmarking;
 
+mod messages_generation;
+
 #[cfg(feature = "integrity-test")]
 pub mod integrity;
 
diff --git a/bridges/bin/runtime-common/src/messages.rs b/bridges/bin/runtime-common/src/messages.rs
index 3744d7153ee..75c3ec6e57f 100644
--- a/bridges/bin/runtime-common/src/messages.rs
+++ b/bridges/bin/runtime-common/src/messages.rs
@@ -20,22 +20,22 @@
 //! pallet is used to dispatch incoming messages. Message identified by a tuple
 //! of to elements - message lane id and message nonce.
 
+use bp_header_chain::{HeaderChain, HeaderChainError};
 use bp_messages::{
 	source_chain::LaneMessageVerifier,
 	target_chain::{DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages},
 	InboundLaneData, LaneId, Message, MessageData, MessageKey, MessageNonce, OutboundLaneData,
 };
-use bp_polkadot_core::parachains::{ParaHash, ParaHasher, ParaId};
-use bp_runtime::{messages::MessageDispatchResult, ChainId, Size, StorageProofChecker};
+use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, Size, StorageProofChecker};
 use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen};
 use frame_support::{traits::Get, weights::Weight, RuntimeDebug};
 use hash_db::Hasher;
 use scale_info::TypeInfo;
 use sp_runtime::{
-	traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedDiv, CheckedMul, Header as HeaderT},
+	traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedDiv, CheckedMul},
 	FixedPointNumber, FixedPointOperand, FixedU128,
 };
-use sp_std::{cmp::PartialOrd, convert::TryFrom, fmt::Debug, marker::PhantomData, vec::Vec};
+use sp_std::{convert::TryFrom, fmt::Debug, marker::PhantomData, vec::Vec};
 use sp_trie::StorageProof;
 use xcm::latest::prelude::*;
 
@@ -57,6 +57,8 @@ pub trait MessageBridge {
 	type ThisChain: ThisChainWithMessages;
 	/// Bridged chain in context of message bridge.
 	type BridgedChain: BridgedChainWithMessages;
+	/// Bridged header chain.
+	type BridgedHeaderChain: HeaderChain<<Self::BridgedChain as ChainWithMessages>::Chain>;
 
 	/// Convert Bridged chain balance into This chain balance.
 	fn bridged_balance_to_this_balance(
@@ -65,27 +67,6 @@ pub trait MessageBridge {
 	) -> BalanceOf<ThisChain<Self>>;
 }
 
-/// Chain that has `pallet-bridge-messages` and `dispatch` modules.
-pub trait ChainWithMessages {
-	/// Hash used in the chain.
-	type Hash: Decode;
-	/// Accound id on the chain.
-	type AccountId: Encode + Decode + MaxEncodedLen;
-	/// Public key of the chain account that may be used to verify signatures.
-	type Signer: Encode + Decode;
-	/// Signature type used on the chain.
-	type Signature: Encode + Decode;
-	/// Type of balances that is used on the chain.
-	type Balance: Encode
-		+ Decode
-		+ CheckedAdd
-		+ CheckedDiv
-		+ CheckedMul
-		+ PartialOrd
-		+ From<u32>
-		+ Copy;
-}
-
 /// Message related transaction parameters estimation.
 #[derive(RuntimeDebug)]
 pub struct MessageTransaction<Weight> {
@@ -134,7 +115,13 @@ impl<
 	}
 }
 
-/// This chain that has `pallet-bridge-messages` and `dispatch` modules.
+/// Chain that has `pallet-bridge-messages` module.
+pub trait ChainWithMessages {
+	/// Underlying chain type.
+	type Chain: Chain;
+}
+
+/// This chain that has `pallet-bridge-messages` module.
 pub trait ThisChainWithMessages: ChainWithMessages {
 	/// Call origin on the chain.
 	type RuntimeOrigin;
@@ -161,11 +148,8 @@ pub trait ThisChainWithMessages: ChainWithMessages {
 	fn transaction_payment(transaction: MessageTransaction<Weight>) -> BalanceOf<Self>;
 }
 
-/// Bridged chain that has `pallet-bridge-messages` and `dispatch` modules.
+/// Bridged chain that has `pallet-bridge-messages` module.
 pub trait BridgedChainWithMessages: ChainWithMessages {
-	/// Maximal extrinsic size at Bridged chain.
-	fn maximal_extrinsic_size() -> u32;
-
 	/// Returns `true` if message dispatch weight is withing expected limits. `false` means
 	/// that the message is too heavy to be sent over the bridge and shall be rejected.
 	fn verify_dispatch_weight(message_payload: &[u8]) -> bool;
@@ -186,16 +170,16 @@ pub trait BridgedChainWithMessages: ChainWithMessages {
 pub type ThisChain<B> = <B as MessageBridge>::ThisChain;
 /// Bridged chain in context of message bridge.
 pub type BridgedChain<B> = <B as MessageBridge>::BridgedChain;
+/// Underlying chain type.
+pub type UnderlyingChainOf<C> = <C as ChainWithMessages>::Chain;
 /// Hash used on the chain.
-pub type HashOf<C> = <C as ChainWithMessages>::Hash;
+pub type HashOf<C> = bp_runtime::HashOf<<C as ChainWithMessages>::Chain>;
+/// Hasher used on the chain.
+pub type HasherOf<C> = bp_runtime::HasherOf<<C as ChainWithMessages>::Chain>;
 /// Account id used on the chain.
-pub type AccountIdOf<C> = <C as ChainWithMessages>::AccountId;
-/// Public key of the chain account that may be used to verify signature.
-pub type SignerOf<C> = <C as ChainWithMessages>::Signer;
-/// Signature type used on the chain.
-pub type SignatureOf<C> = <C as ChainWithMessages>::Signature;
+pub type AccountIdOf<C> = bp_runtime::AccountIdOf<<C as ChainWithMessages>::Chain>;
 /// Type of balances that is used on the chain.
-pub type BalanceOf<C> = <C as ChainWithMessages>::Balance;
+pub type BalanceOf<C> = bp_runtime::BalanceOf<<C as ChainWithMessages>::Chain>;
 /// Type of origin that is used on the chain.
 pub type OriginOf<C> = <C as ThisChainWithMessages>::RuntimeOrigin;
 /// Type of call that is used on this chain.
@@ -358,7 +342,9 @@ pub mod source {
 
 	/// Return maximal message size of This -> Bridged chain message.
 	pub fn maximal_message_size<B: MessageBridge>() -> u32 {
-		super::target::maximal_incoming_message_size(BridgedChain::<B>::maximal_extrinsic_size())
+		super::target::maximal_incoming_message_size(
+			UnderlyingChainOf::<BridgedChain<B>>::max_extrinsic_size(),
+		)
 	}
 
 	/// Do basic Bridged-chain specific verification of This -> Bridged chain message.
@@ -440,87 +426,35 @@ pub mod source {
 	///
 	/// This function is used when Bridged chain is directly using GRANDPA finality. For Bridged
 	/// parachains, please use the `verify_messages_delivery_proof_from_parachain`.
-	pub fn verify_messages_delivery_proof<B: MessageBridge, ThisRuntime, GrandpaInstance: 'static>(
-		proof: FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>,
-	) -> Result<ParsedMessagesDeliveryProofFromBridgedChain<B>, &'static str>
-	where
-		ThisRuntime: pallet_bridge_grandpa::Config<GrandpaInstance>,
-		HashOf<BridgedChain<B>>: Into<
-			bp_runtime::HashOf<
-				<ThisRuntime as pallet_bridge_grandpa::Config<GrandpaInstance>>::BridgedChain,
-			>,
-		>,
-	{
-		let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } =
-			proof;
-		pallet_bridge_grandpa::Pallet::<ThisRuntime, GrandpaInstance>::parse_finalized_storage_proof(
-			bridged_header_hash.into(),
-			StorageProof::new(storage_proof),
-			|storage| do_verify_messages_delivery_proof::<
-				B,
-				bp_runtime::HasherOf<
-					<ThisRuntime as pallet_bridge_grandpa::Config<GrandpaInstance>>::BridgedChain,
-				>,
-			>(lane, storage),
-		)
-		.map_err(<&'static str>::from)?
-	}
-
-	/// Verify proof of This -> Bridged chain messages delivery.
-	///
-	/// This function is used when Bridged chain is using parachain finality. For Bridged
-	/// chains with direct GRANDPA finality, please use the `verify_messages_delivery_proof`.
-	///
-	/// This function currently only supports parachains, which are using header type that
-	/// implements `sp_runtime::traits::Header` trait.
-	pub fn verify_messages_delivery_proof_from_parachain<
-		B,
-		BridgedHeader,
-		ThisRuntime,
-		ParachainsInstance: 'static,
-	>(
-		bridged_parachain: ParaId,
+	pub fn verify_messages_delivery_proof<B: MessageBridge>(
 		proof: FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>,
-	) -> Result<ParsedMessagesDeliveryProofFromBridgedChain<B>, &'static str>
-	where
-		B: MessageBridge,
-		B::BridgedChain: ChainWithMessages<Hash = ParaHash>,
-		BridgedHeader: HeaderT<Hash = HashOf<BridgedChain<B>>>,
-		ThisRuntime: pallet_bridge_parachains::Config<ParachainsInstance>,
-	{
+	) -> Result<ParsedMessagesDeliveryProofFromBridgedChain<B>, &'static str> {
 		let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } =
 			proof;
-		pallet_bridge_parachains::Pallet::<ThisRuntime, ParachainsInstance>::parse_finalized_storage_proof(
-			bridged_parachain,
+		B::BridgedHeaderChain::parse_finalized_storage_proof(
 			bridged_header_hash,
 			StorageProof::new(storage_proof),
-			|para_head| BridgedHeader::decode(&mut &para_head.0[..]).ok().map(|h| *h.state_root()),
-			|storage| do_verify_messages_delivery_proof::<B, ParaHasher>(lane, storage),
+			|storage| {
+				// Messages delivery proof is just proof of single storage key read => any error
+				// is fatal.
+				let storage_inbound_lane_data_key =
+					bp_messages::storage_keys::inbound_lane_data_key(
+						B::BRIDGED_MESSAGES_PALLET_NAME,
+						&lane,
+					);
+				let raw_inbound_lane_data = storage
+					.read_value(storage_inbound_lane_data_key.0.as_ref())
+					.map_err(|_| "Failed to read inbound lane state from storage proof")?
+					.ok_or("Inbound lane state is missing from the messages proof")?;
+				let inbound_lane_data = InboundLaneData::decode(&mut &raw_inbound_lane_data[..])
+					.map_err(|_| "Failed to decode inbound lane state from the proof")?;
+
+				Ok((lane, inbound_lane_data))
+			},
 		)
 		.map_err(<&'static str>::from)?
 	}
 
-	/// The essense of This -> Bridged chain messages delivery proof verification.
-	fn do_verify_messages_delivery_proof<B: MessageBridge, H: Hasher>(
-		lane: LaneId,
-		storage: bp_runtime::StorageProofChecker<H>,
-	) -> Result<ParsedMessagesDeliveryProofFromBridgedChain<B>, &'static str> {
-		// Messages delivery proof is just proof of single storage key read => any error
-		// is fatal.
-		let storage_inbound_lane_data_key = bp_messages::storage_keys::inbound_lane_data_key(
-			B::BRIDGED_MESSAGES_PALLET_NAME,
-			&lane,
-		);
-		let raw_inbound_lane_data = storage
-			.read_value(storage_inbound_lane_data_key.0.as_ref())
-			.map_err(|_| "Failed to read inbound lane state from storage proof")?
-			.ok_or("Inbound lane state is missing from the messages proof")?;
-		let inbound_lane_data = InboundLaneData::decode(&mut &raw_inbound_lane_data[..])
-			.map_err(|_| "Failed to decode inbound lane state from the proof")?;
-
-		Ok((lane, inbound_lane_data))
-	}
-
 	/// XCM bridge.
 	pub trait XcmBridge {
 		/// Runtime message bridge configuration.
@@ -801,98 +735,106 @@ pub mod target {
 	/// The `messages_count` argument verification (sane limits) is supposed to be made
 	/// outside of this function. This function only verifies that the proof declares exactly
 	/// `messages_count` messages.
-	pub fn verify_messages_proof<B: MessageBridge, ThisRuntime, GrandpaInstance: 'static>(
+	pub fn verify_messages_proof<B: MessageBridge>(
 		proof: FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>,
 		messages_count: u32,
-	) -> Result<ProvedMessages<Message<BalanceOf<BridgedChain<B>>>>, &'static str>
-	where
-		ThisRuntime: pallet_bridge_grandpa::Config<GrandpaInstance>,
-		HashOf<BridgedChain<B>>: Into<
-			bp_runtime::HashOf<
-				<ThisRuntime as pallet_bridge_grandpa::Config<GrandpaInstance>>::BridgedChain,
-			>,
-		>,
-	{
-		verify_messages_proof_with_parser::<B, _, _>(
-			proof,
-			messages_count,
-			|bridged_header_hash, bridged_storage_proof| {
-				pallet_bridge_grandpa::Pallet::<ThisRuntime, GrandpaInstance>::parse_finalized_storage_proof(
-					bridged_header_hash.into(),
-					StorageProof::new(bridged_storage_proof),
-					|storage_adapter| storage_adapter,
-				)
-				.map(|storage| StorageProofCheckerAdapter::<_, B> {
-					storage,
-					_dummy: Default::default(),
-				})
-				.map_err(|err| MessageProofError::Custom(err.into()))
-			},
-		)
-		.map_err(Into::into)
-	}
+	) -> Result<ProvedMessages<Message<BalanceOf<BridgedChain<B>>>>, MessageProofError> {
+		let FromBridgedChainMessagesProof {
+			bridged_header_hash,
+			storage_proof,
+			lane,
+			nonces_start,
+			nonces_end,
+		} = proof;
 
-	/// Verify proof of Bridged -> This chain messages.
-	///
-	/// This function is used when Bridged chain is using parachain finality. For Bridged
-	/// chains with direct GRANDPA finality, please use the `verify_messages_proof`.
-	///
-	/// The `messages_count` argument verification (sane limits) is supposed to be made
-	/// outside of this function. This function only verifies that the proof declares exactly
-	/// `messages_count` messages.
-	///
-	/// This function currently only supports parachains, which are using header type that
-	/// implements `sp_runtime::traits::Header` trait.
-	pub fn verify_messages_proof_from_parachain<
-		B,
-		BridgedHeader,
-		ThisRuntime,
-		ParachainsInstance: 'static,
-	>(
-		bridged_parachain: ParaId,
-		proof: FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>,
-		messages_count: u32,
-	) -> Result<ProvedMessages<Message<BalanceOf<BridgedChain<B>>>>, &'static str>
-	where
-		B: MessageBridge,
-		B::BridgedChain: ChainWithMessages<Hash = ParaHash>,
-		BridgedHeader: HeaderT<Hash = HashOf<BridgedChain<B>>>,
-		ThisRuntime: pallet_bridge_parachains::Config<ParachainsInstance>,
-	{
-		verify_messages_proof_with_parser::<B, _, _>(
-			proof,
-			messages_count,
-			|bridged_header_hash, bridged_storage_proof| {
-				pallet_bridge_parachains::Pallet::<ThisRuntime, ParachainsInstance>::parse_finalized_storage_proof(
-					bridged_parachain,
-					bridged_header_hash,
-					StorageProof::new(bridged_storage_proof),
-					|para_head| BridgedHeader::decode(&mut &para_head.0[..]).ok().map(|h| *h.state_root()),
-					|storage_adapter| storage_adapter,
-				)
-				.map(|storage| StorageProofCheckerAdapter::<_, B> {
-					storage,
-					_dummy: Default::default(),
-				})
-				.map_err(|err| MessageProofError::Custom(err.into()))
+		B::BridgedHeaderChain::parse_finalized_storage_proof(
+			bridged_header_hash,
+			StorageProof::new(storage_proof),
+			|storage| {
+				let parser =
+					StorageProofCheckerAdapter::<_, B> { storage, _dummy: Default::default() };
+
+				// receiving proofs where end < begin is ok (if proof includes outbound lane state)
+				let messages_in_the_proof =
+					if let Some(nonces_difference) = nonces_end.checked_sub(nonces_start) {
+						// let's check that the user (relayer) has passed correct `messages_count`
+						// (this bounds maximal capacity of messages vec below)
+						let messages_in_the_proof = nonces_difference.saturating_add(1);
+						if messages_in_the_proof != MessageNonce::from(messages_count) {
+							return Err(MessageProofError::MessagesCountMismatch)
+						}
+
+						messages_in_the_proof
+					} else {
+						0
+					};
+
+				// Read messages first. All messages that are claimed to be in the proof must
+				// be in the proof. So any error in `read_value`, or even missing value is fatal.
+				//
+				// Mind that we allow proofs with no messages if outbound lane state is proved.
+				let mut messages = Vec::with_capacity(messages_in_the_proof as _);
+				for nonce in nonces_start..=nonces_end {
+					let message_key = MessageKey { lane_id: lane, nonce };
+					let raw_message_data = parser
+						.read_raw_message(&message_key)
+						.ok_or(MessageProofError::MissingRequiredMessage)?;
+					let message_data = MessageData::<BalanceOf<BridgedChain<B>>>::decode(
+						&mut &raw_message_data[..],
+					)
+					.map_err(|_| MessageProofError::FailedToDecodeMessage)?;
+					messages.push(Message { key: message_key, data: message_data });
+				}
+
+				// Now let's check if proof contains outbound lane state proof. It is optional, so
+				// we simply ignore `read_value` errors and missing value.
+				let mut proved_lane_messages = ProvedLaneMessages { lane_state: None, messages };
+				let raw_outbound_lane_data = parser.read_raw_outbound_lane_data(&lane);
+				if let Some(raw_outbound_lane_data) = raw_outbound_lane_data {
+					proved_lane_messages.lane_state = Some(
+						OutboundLaneData::decode(&mut &raw_outbound_lane_data[..])
+							.map_err(|_| MessageProofError::FailedToDecodeOutboundLaneState)?,
+					);
+				}
+
+				// Now we may actually check if the proof is empty or not.
+				if proved_lane_messages.lane_state.is_none() &&
+					proved_lane_messages.messages.is_empty()
+				{
+					return Err(MessageProofError::Empty)
+				}
+
+				// We only support single lane messages in this generated_schema
+				let mut proved_messages = ProvedMessages::new();
+				proved_messages.insert(lane, proved_lane_messages);
+
+				Ok(proved_messages)
 			},
 		)
-		.map_err(Into::into)
+		.map_err(MessageProofError::HeaderChain)?
 	}
 
+	/// Error that happens during message proof verification.
 	#[derive(Debug, PartialEq, Eq)]
-	pub(crate) enum MessageProofError {
+	pub enum MessageProofError {
+		/// Error returned by the bridged header chain.
+		HeaderChain(HeaderChainError),
+		/// The message proof is empty.
 		Empty,
+		/// Declared messages count doesn't match actual value.
 		MessagesCountMismatch,
+		/// Message is missing from the proof.
 		MissingRequiredMessage,
+		/// Failed to decode message from the proof.
 		FailedToDecodeMessage,
+		/// Failed to decode outbound lane data from the proof.
 		FailedToDecodeOutboundLaneState,
-		Custom(&'static str),
 	}
 
 	impl From<MessageProofError> for &'static str {
 		fn from(err: MessageProofError) -> &'static str {
 			match err {
+				MessageProofError::HeaderChain(err) => err.into(),
 				MessageProofError::Empty => "Messages proof is empty",
 				MessageProofError::MessagesCountMismatch =>
 					"Declared messages count doesn't match actual value",
@@ -901,26 +843,16 @@ pub mod target {
 					"Failed to decode message from the proof",
 				MessageProofError::FailedToDecodeOutboundLaneState =>
 					"Failed to decode outbound lane data from the proof",
-				MessageProofError::Custom(err) => err,
 			}
 		}
 	}
 
-	pub(crate) trait MessageProofParser {
-		fn read_raw_outbound_lane_data(&self, lane_id: &LaneId) -> Option<Vec<u8>>;
-		fn read_raw_message(&self, message_key: &MessageKey) -> Option<Vec<u8>>;
-	}
-
 	struct StorageProofCheckerAdapter<H: Hasher, B> {
 		storage: StorageProofChecker<H>,
 		_dummy: sp_std::marker::PhantomData<B>,
 	}
 
-	impl<H, B> MessageProofParser for StorageProofCheckerAdapter<H, B>
-	where
-		H: Hasher,
-		B: MessageBridge,
-	{
+	impl<H: Hasher, B: MessageBridge> StorageProofCheckerAdapter<H, B> {
 		fn read_raw_outbound_lane_data(&self, lane_id: &LaneId) -> Option<Vec<u8>> {
 			let storage_outbound_lane_data_key = bp_messages::storage_keys::outbound_lane_data_key(
 				B::BRIDGED_MESSAGES_PALLET_NAME,
@@ -938,89 +870,20 @@ pub mod target {
 			self.storage.read_value(storage_message_key.0.as_ref()).ok()?
 		}
 	}
-
-	/// Verify proof of Bridged -> This chain messages using given message proof parser.
-	pub(crate) fn verify_messages_proof_with_parser<B: MessageBridge, BuildParser, Parser>(
-		proof: FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>,
-		messages_count: u32,
-		build_parser: BuildParser,
-	) -> Result<ProvedMessages<Message<BalanceOf<BridgedChain<B>>>>, MessageProofError>
-	where
-		BuildParser:
-			FnOnce(HashOf<BridgedChain<B>>, RawStorageProof) -> Result<Parser, MessageProofError>,
-		Parser: MessageProofParser,
-	{
-		let FromBridgedChainMessagesProof {
-			bridged_header_hash,
-			storage_proof,
-			lane,
-			nonces_start,
-			nonces_end,
-		} = proof;
-
-		// receiving proofs where end < begin is ok (if proof includes outbound lane state)
-		let messages_in_the_proof =
-			if let Some(nonces_difference) = nonces_end.checked_sub(nonces_start) {
-				// let's check that the user (relayer) has passed correct `messages_count`
-				// (this bounds maximal capacity of messages vec below)
-				let messages_in_the_proof = nonces_difference.saturating_add(1);
-				if messages_in_the_proof != MessageNonce::from(messages_count) {
-					return Err(MessageProofError::MessagesCountMismatch)
-				}
-
-				messages_in_the_proof
-			} else {
-				0
-			};
-
-		let parser = build_parser(bridged_header_hash, storage_proof)?;
-
-		// Read messages first. All messages that are claimed to be in the proof must
-		// be in the proof. So any error in `read_value`, or even missing value is fatal.
-		//
-		// Mind that we allow proofs with no messages if outbound lane state is proved.
-		let mut messages = Vec::with_capacity(messages_in_the_proof as _);
-		for nonce in nonces_start..=nonces_end {
-			let message_key = MessageKey { lane_id: lane, nonce };
-			let raw_message_data = parser
-				.read_raw_message(&message_key)
-				.ok_or(MessageProofError::MissingRequiredMessage)?;
-			let message_data =
-				MessageData::<BalanceOf<BridgedChain<B>>>::decode(&mut &raw_message_data[..])
-					.map_err(|_| MessageProofError::FailedToDecodeMessage)?;
-			messages.push(Message { key: message_key, data: message_data });
-		}
-
-		// Now let's check if proof contains outbound lane state proof. It is optional, so we
-		// simply ignore `read_value` errors and missing value.
-		let mut proved_lane_messages = ProvedLaneMessages { lane_state: None, messages };
-		let raw_outbound_lane_data = parser.read_raw_outbound_lane_data(&lane);
-		if let Some(raw_outbound_lane_data) = raw_outbound_lane_data {
-			proved_lane_messages.lane_state = Some(
-				OutboundLaneData::decode(&mut &raw_outbound_lane_data[..])
-					.map_err(|_| MessageProofError::FailedToDecodeOutboundLaneState)?,
-			);
-		}
-
-		// Now we may actually check if the proof is empty or not.
-		if proved_lane_messages.lane_state.is_none() && proved_lane_messages.messages.is_empty() {
-			return Err(MessageProofError::Empty)
-		}
-
-		// We only support single lane messages in this generated_schema
-		let mut proved_messages = ProvedMessages::new();
-		proved_messages.insert(lane, proved_lane_messages);
-
-		Ok(proved_messages)
-	}
 }
 
 #[cfg(test)]
 mod tests {
 	use super::*;
+	use crate::messages_generation::{
+		encode_all_messages, encode_lane_data, prepare_messages_storage_proof,
+	};
+	use bp_runtime::HeaderOf;
 	use codec::{Decode, Encode};
 	use frame_support::weights::Weight;
-	use std::ops::RangeInclusive;
+	use sp_core::H256;
+	use sp_runtime::traits::{BlakeTwo256, Header as _};
+	use std::cell::RefCell;
 
 	const DELIVERY_TRANSACTION_WEIGHT: Weight = Weight::from_ref_time(100);
 	const DELIVERY_CONFIRMATION_TRANSACTION_WEIGHT: u64 = 100;
@@ -1032,7 +895,7 @@ mod tests {
 	const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024;
 
 	/// Bridge that is deployed on ThisChain and allows sending/receiving messages to/from
-	/// BridgedChain;
+	/// BridgedChain.
 	#[derive(Debug, PartialEq, Eq)]
 	struct OnThisChainBridge;
 
@@ -1044,6 +907,7 @@ mod tests {
 
 		type ThisChain = ThisChain;
 		type BridgedChain = BridgedChain;
+		type BridgedHeaderChain = BridgedHeaderChain;
 
 		fn bridged_balance_to_this_balance(
 			bridged_balance: BridgedChainBalance,
@@ -1052,7 +916,7 @@ mod tests {
 			let conversion_rate = bridged_to_this_conversion_rate_override
 				.map(|r| r.to_float() as u32)
 				.unwrap_or(BRIDGED_CHAIN_TO_THIS_CHAIN_BALANCE_RATE);
-			ThisChainBalance(bridged_balance.0 * conversion_rate)
+			bridged_balance as ThisChainBalance * conversion_rate as ThisChainBalance
 		}
 	}
 
@@ -1069,6 +933,7 @@ mod tests {
 
 		type ThisChain = BridgedChain;
 		type BridgedChain = ThisChain;
+		type BridgedHeaderChain = ThisHeaderChain;
 
 		fn bridged_balance_to_this_balance(
 			_this_balance: ThisChainBalance,
@@ -1078,19 +943,6 @@ mod tests {
 		}
 	}
 
-	#[derive(Debug, PartialEq, Eq, Decode, Encode, Clone, MaxEncodedLen)]
-	struct ThisChainAccountId(u32);
-	#[derive(Debug, PartialEq, Eq, Decode, Encode)]
-	struct ThisChainSigner(u32);
-	#[derive(Debug, PartialEq, Eq, Decode, Encode)]
-	struct ThisChainSignature(u32);
-	#[derive(Debug, PartialEq, Eq, Decode, Encode)]
-	enum ThisChainCall {
-		#[codec(index = 42)]
-		Transfer,
-		#[codec(index = 84)]
-		Mint,
-	}
 	#[derive(Clone, Debug)]
 	struct ThisChainOrigin(Result<frame_system::RawOrigin<ThisChainAccountId>, ()>);
 
@@ -1104,14 +956,6 @@ mod tests {
 		}
 	}
 
-	#[derive(Debug, PartialEq, Eq, Decode, Encode, MaxEncodedLen)]
-	struct BridgedChainAccountId(u32);
-	#[derive(Debug, PartialEq, Eq, Decode, Encode)]
-	struct BridgedChainSigner(u32);
-	#[derive(Debug, PartialEq, Eq, Decode, Encode)]
-	struct BridgedChainSignature(u32);
-	#[derive(Debug, PartialEq, Eq, Decode, Encode)]
-	enum BridgedChainCall {}
 	#[derive(Clone, Debug)]
 	struct BridgedChainOrigin;
 
@@ -1125,85 +969,42 @@ mod tests {
 		}
 	}
 
-	macro_rules! impl_wrapped_balance {
-		($name:ident) => {
-			#[derive(Debug, PartialEq, Eq, Decode, Encode, Clone, Copy)]
-			struct $name(u32);
-
-			impl From<u32> for $name {
-				fn from(balance: u32) -> Self {
-					Self(balance)
-				}
-			}
-
-			impl sp_std::ops::Add for $name {
-				type Output = $name;
-
-				fn add(self, other: Self) -> Self {
-					Self(self.0 + other.0)
-				}
-			}
-
-			impl sp_std::ops::Div for $name {
-				type Output = $name;
-
-				fn div(self, other: Self) -> Self {
-					Self(self.0 / other.0)
-				}
-			}
-
-			impl sp_std::ops::Mul for $name {
-				type Output = $name;
+	struct ThisUnderlyingChain;
+	type ThisChainHeader = sp_runtime::generic::Header<u64, BlakeTwo256>;
+	type ThisChainAccountId = u32;
+	type ThisChainBalance = u32;
+	#[derive(Decode, Encode)]
+	struct ThisChainCall;
 
-				fn mul(self, other: Self) -> Self {
-					Self(self.0 * other.0)
-				}
-			}
-
-			impl sp_std::cmp::PartialOrd for $name {
-				fn partial_cmp(&self, other: &Self) -> Option<sp_std::cmp::Ordering> {
-					self.0.partial_cmp(&other.0)
-				}
-			}
-
-			impl CheckedAdd for $name {
-				fn checked_add(&self, other: &Self) -> Option<Self> {
-					self.0.checked_add(other.0).map(Self)
-				}
-			}
-
-			impl CheckedDiv for $name {
-				fn checked_div(&self, other: &Self) -> Option<Self> {
-					self.0.checked_div(other.0).map(Self)
-				}
-			}
+	impl Chain for ThisUnderlyingChain {
+		type BlockNumber = u64;
+		type Hash = H256;
+		type Hasher = BlakeTwo256;
+		type Header = ThisChainHeader;
+		type AccountId = ThisChainAccountId;
+		type Balance = ThisChainBalance;
+		type Index = u32;
+		type Signature = sp_runtime::MultiSignature;
 
-			impl CheckedMul for $name {
-				fn checked_mul(&self, other: &Self) -> Option<Self> {
-					self.0.checked_mul(other.0).map(Self)
-				}
-			}
-		};
+		fn max_extrinsic_size() -> u32 {
+			BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
+		}
+		fn max_extrinsic_weight() -> Weight {
+			Weight::zero()
+		}
 	}
 
-	impl_wrapped_balance!(ThisChainBalance);
-	impl_wrapped_balance!(BridgedChainBalance);
-
 	struct ThisChain;
 
 	impl ChainWithMessages for ThisChain {
-		type Hash = ();
-		type AccountId = ThisChainAccountId;
-		type Signer = ThisChainSigner;
-		type Signature = ThisChainSignature;
-		type Balance = ThisChainBalance;
+		type Chain = ThisUnderlyingChain;
 	}
 
 	impl ThisChainWithMessages for ThisChain {
 		type RuntimeOrigin = ThisChainOrigin;
 		type RuntimeCall = ThisChainCall;
 		type ConfirmationTransactionEstimation = BasicConfirmationTransactionEstimation<
-			<ThisChain as ChainWithMessages>::AccountId,
+			ThisChainAccountId,
 			{ DELIVERY_CONFIRMATION_TRANSACTION_WEIGHT },
 			0,
 			0,
@@ -1218,20 +1019,14 @@ mod tests {
 		}
 
 		fn transaction_payment(transaction: MessageTransaction<Weight>) -> BalanceOf<Self> {
-			ThisChainBalance(
-				transaction
-					.dispatch_weight
-					.saturating_mul(THIS_CHAIN_WEIGHT_TO_BALANCE_RATE as u64)
-					.ref_time() as _,
-			)
+			transaction
+				.dispatch_weight
+				.saturating_mul(THIS_CHAIN_WEIGHT_TO_BALANCE_RATE as u64)
+				.ref_time() as _
 		}
 	}
 
 	impl BridgedChainWithMessages for ThisChain {
-		fn maximal_extrinsic_size() -> u32 {
-			unreachable!()
-		}
-
 		fn verify_dispatch_weight(_message_payload: &[u8]) -> bool {
 			unreachable!()
 		}
@@ -1249,25 +1044,42 @@ mod tests {
 		}
 	}
 
-	struct BridgedChain;
+	struct BridgedUnderlyingChain;
+	type BridgedChainHeader = sp_runtime::generic::Header<u64, BlakeTwo256>;
+	type BridgedChainAccountId = u128;
+	type BridgedChainBalance = u128;
+	#[derive(Decode, Encode)]
+	struct BridgedChainCall;
 
-	impl ChainWithMessages for BridgedChain {
-		type Hash = ();
+	impl Chain for BridgedUnderlyingChain {
+		type BlockNumber = u64;
+		type Hash = H256;
+		type Hasher = BlakeTwo256;
+		type Header = BridgedChainHeader;
 		type AccountId = BridgedChainAccountId;
-		type Signer = BridgedChainSigner;
-		type Signature = BridgedChainSignature;
 		type Balance = BridgedChainBalance;
+		type Index = u32;
+		type Signature = sp_runtime::MultiSignature;
+
+		fn max_extrinsic_size() -> u32 {
+			BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
+		}
+		fn max_extrinsic_weight() -> Weight {
+			Weight::zero()
+		}
+	}
+
+	struct BridgedChain;
+
+	impl ChainWithMessages for BridgedChain {
+		type Chain = BridgedUnderlyingChain;
 	}
 
 	impl ThisChainWithMessages for BridgedChain {
 		type RuntimeOrigin = BridgedChainOrigin;
 		type RuntimeCall = BridgedChainCall;
-		type ConfirmationTransactionEstimation = BasicConfirmationTransactionEstimation<
-			<BridgedChain as ChainWithMessages>::AccountId,
-			0,
-			0,
-			0,
-		>;
+		type ConfirmationTransactionEstimation =
+			BasicConfirmationTransactionEstimation<BridgedChainAccountId, 0, 0, 0>;
 
 		fn is_message_accepted(_send_origin: &Self::RuntimeOrigin, _lane: &LaneId) -> bool {
 			unreachable!()
@@ -1283,10 +1095,6 @@ mod tests {
 	}
 
 	impl BridgedChainWithMessages for BridgedChain {
-		fn maximal_extrinsic_size() -> u32 {
-			BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
-		}
-
 		fn verify_dispatch_weight(message_payload: &[u8]) -> bool {
 			message_payload.len() >= BRIDGED_CHAIN_MIN_EXTRINSIC_WEIGHT &&
 				message_payload.len() <= BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT
@@ -1304,12 +1112,32 @@ mod tests {
 		}
 
 		fn transaction_payment(transaction: MessageTransaction<Weight>) -> BalanceOf<Self> {
-			BridgedChainBalance(
-				transaction
-					.dispatch_weight
-					.saturating_mul(BRIDGED_CHAIN_WEIGHT_TO_BALANCE_RATE as u64)
-					.ref_time() as _,
-			)
+			transaction
+				.dispatch_weight
+				.saturating_mul(BRIDGED_CHAIN_WEIGHT_TO_BALANCE_RATE as u64)
+				.ref_time() as _
+		}
+	}
+
+	thread_local! {
+		static TEST_BRIDGED_HEADER: RefCell<Option<BridgedChainHeader>> = RefCell::new(None);
+	}
+
+	struct BridgedHeaderChain;
+
+	impl HeaderChain<BridgedUnderlyingChain> for BridgedHeaderChain {
+		fn finalized_header(
+			_hash: HashOf<BridgedChain>,
+		) -> Option<HeaderOf<BridgedUnderlyingChain>> {
+			TEST_BRIDGED_HEADER.with(|h| h.borrow().clone())
+		}
+	}
+
+	struct ThisHeaderChain;
+
+	impl HeaderChain<ThisUnderlyingChain> for ThisHeaderChain {
+		fn finalized_header(_hash: HashOf<ThisChain>) -> Option<HeaderOf<ThisUnderlyingChain>> {
+			unreachable!()
 		}
 	}
 
@@ -1338,14 +1166,14 @@ mod tests {
 				OnThisChainBridge::RELAYER_FEE_PERCENT,
 				None,
 			),
-			Ok(ThisChainBalance(EXPECTED_MINIMAL_FEE)),
+			Ok(EXPECTED_MINIMAL_FEE),
 		);
 
 		// and now check that the verifier checks the fee
 		assert_eq!(
 			source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
 				&ThisChainOrigin(Ok(frame_system::RawOrigin::Root)),
-				&ThisChainBalance(1),
+				&1,
 				TEST_LANE_ID,
 				&test_lane_outbound_data(),
 				&payload,
@@ -1354,7 +1182,7 @@ mod tests {
 		);
 		assert!(source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
 			&ThisChainOrigin(Ok(frame_system::RawOrigin::Root)),
-			&ThisChainBalance(1_000_000),
+			&1_000_000,
 			TEST_LANE_ID,
 			&test_lane_outbound_data(),
 			&payload,
@@ -1367,7 +1195,7 @@ mod tests {
 		assert_eq!(
 			source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
 				&ThisChainOrigin(Ok(frame_system::RawOrigin::Root)),
-				&ThisChainBalance(1_000_000),
+				&1_000_000,
 				b"dsbl",
 				&test_lane_outbound_data(),
 				&regular_outbound_message_payload(),
@@ -1381,7 +1209,7 @@ mod tests {
 		assert_eq!(
 			source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
 				&ThisChainOrigin(Ok(frame_system::RawOrigin::Root)),
-				&ThisChainBalance(1_000_000),
+				&1_000_000,
 				TEST_LANE_ID,
 				&OutboundLaneData {
 					latest_received_nonce: 100,
@@ -1436,62 +1264,48 @@ mod tests {
 		);
 	}
 
-	#[derive(Debug)]
-	struct TestMessageProofParser {
-		failing: bool,
-		messages: RangeInclusive<MessageNonce>,
+	fn using_messages_proof<R>(
+		nonces_end: MessageNonce,
 		outbound_lane_data: Option<OutboundLaneData>,
-	}
-
-	impl target::MessageProofParser for TestMessageProofParser {
-		fn read_raw_outbound_lane_data(&self, _lane_id: &LaneId) -> Option<Vec<u8>> {
-			if self.failing {
-				Some(vec![])
-			} else {
-				self.outbound_lane_data.clone().map(|data| data.encode())
-			}
-		}
-
-		fn read_raw_message(&self, message_key: &MessageKey) -> Option<Vec<u8>> {
-			if self.failing {
-				Some(vec![])
-			} else if self.messages.contains(&message_key.nonce) {
-				Some(
-					MessageData::<BridgedChainBalance> {
-						payload: message_key.nonce.encode(),
-						fee: BridgedChainBalance(0),
-					}
-					.encode(),
-				)
-			} else {
-				None
-			}
-		}
-	}
+		encode_message: impl Fn(MessageNonce, &MessageData<BridgedChainBalance>) -> Option<Vec<u8>>,
+		encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec<u8>,
+		test: impl Fn(target::FromBridgedChainMessagesProof<H256>) -> R,
+	) -> R {
+		let (state_root, storage_proof) = prepare_messages_storage_proof::<OnThisChainBridge>(
+			*TEST_LANE_ID,
+			1..=nonces_end,
+			outbound_lane_data,
+			bp_runtime::StorageProofSize::Minimal(0),
+			vec![42],
+			encode_message,
+			encode_outbound_lane_data,
+		);
 
-	#[allow(clippy::reversed_empty_ranges)]
-	fn no_messages_range() -> RangeInclusive<MessageNonce> {
-		1..=0
-	}
+		TEST_BRIDGED_HEADER.with(|h| {
+			*h.borrow_mut() = Some(BridgedChainHeader::new(
+				0,
+				Default::default(),
+				state_root,
+				Default::default(),
+				Default::default(),
+			))
+		});
 
-	fn messages_proof(nonces_end: MessageNonce) -> target::FromBridgedChainMessagesProof<()> {
-		target::FromBridgedChainMessagesProof {
-			bridged_header_hash: (),
-			storage_proof: vec![],
-			lane: Default::default(),
+		test(target::FromBridgedChainMessagesProof {
+			bridged_header_hash: Default::default(),
+			storage_proof,
+			lane: *TEST_LANE_ID,
 			nonces_start: 1,
 			nonces_end,
-		}
+		})
 	}
 
 	#[test]
 	fn messages_proof_is_rejected_if_declared_less_than_actual_number_of_messages() {
 		assert_eq!(
-			target::verify_messages_proof_with_parser::<OnThisChainBridge, _, TestMessageProofParser>(
-				messages_proof(10),
-				5,
-				|_, _| unreachable!(),
-			),
+			using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
+				target::verify_messages_proof::<OnThisChainBridge>(proof, 5)
+			}),
 			Err(target::MessageProofError::MessagesCountMismatch),
 		);
 	}
@@ -1499,38 +1313,45 @@ mod tests {
 	#[test]
 	fn messages_proof_is_rejected_if_declared_more_than_actual_number_of_messages() {
 		assert_eq!(
-			target::verify_messages_proof_with_parser::<OnThisChainBridge, _, TestMessageProofParser>(
-				messages_proof(10),
-				15,
-				|_, _| unreachable!(),
-			),
+			using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
+				target::verify_messages_proof::<OnThisChainBridge>(proof, 15)
+			}),
 			Err(target::MessageProofError::MessagesCountMismatch),
 		);
 	}
 
 	#[test]
-	fn message_proof_is_rejected_if_build_parser_fails() {
+	fn message_proof_is_rejected_if_header_is_missing_from_the_chain() {
 		assert_eq!(
-			target::verify_messages_proof_with_parser::<OnThisChainBridge, _, TestMessageProofParser>(
-				messages_proof(10),
-				10,
-				|_, _| Err(target::MessageProofError::Custom("test")),
-			),
-			Err(target::MessageProofError::Custom("test")),
+			using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
+				TEST_BRIDGED_HEADER.with(|h| *h.borrow_mut() = None);
+				target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
+			}),
+			Err(target::MessageProofError::HeaderChain(HeaderChainError::UnknownHeader)),
+		);
+	}
+
+	#[test]
+	fn message_proof_is_rejected_if_header_state_root_mismatches() {
+		assert_eq!(
+			using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
+				TEST_BRIDGED_HEADER
+					.with(|h| h.borrow_mut().as_mut().unwrap().state_root = Default::default());
+				target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
+			}),
+			Err(target::MessageProofError::HeaderChain(HeaderChainError::StorageRootMismatch)),
 		);
 	}
 
 	#[test]
 	fn message_proof_is_rejected_if_required_message_is_missing() {
 		assert_eq!(
-			target::verify_messages_proof_with_parser::<OnThisChainBridge, _, _>(
-				messages_proof(10),
+			using_messages_proof(
 				10,
-				|_, _| Ok(TestMessageProofParser {
-					failing: false,
-					messages: 1..=5,
-					outbound_lane_data: None,
-				}),
+				None,
+				|n, m| if n != 5 { Some(m.encode()) } else { None },
+				encode_lane_data,
+				|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
 			),
 			Err(target::MessageProofError::MissingRequiredMessage),
 		);
@@ -1539,14 +1360,19 @@ mod tests {
 	#[test]
 	fn message_proof_is_rejected_if_message_decode_fails() {
 		assert_eq!(
-			target::verify_messages_proof_with_parser::<OnThisChainBridge, _, _>(
-				messages_proof(10),
+			using_messages_proof(
 				10,
-				|_, _| Ok(TestMessageProofParser {
-					failing: true,
-					messages: 1..=10,
-					outbound_lane_data: None,
-				}),
+				None,
+				|n, m| {
+					let mut m = m.encode();
+					if n == 5 {
+						m = MessageData { fee: 0, payload: vec![0u8; 42] }.encode();
+						m.truncate(2);
+					}
+					Some(m.encode())
+				},
+				encode_lane_data,
+				|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 10),
 			),
 			Err(target::MessageProofError::FailedToDecodeMessage),
 		);
@@ -1555,18 +1381,20 @@ mod tests {
 	#[test]
 	fn message_proof_is_rejected_if_outbound_lane_state_decode_fails() {
 		assert_eq!(
-			target::verify_messages_proof_with_parser::<OnThisChainBridge, _, _>(
-				messages_proof(0),
-				0,
-				|_, _| Ok(TestMessageProofParser {
-					failing: true,
-					messages: no_messages_range(),
-					outbound_lane_data: Some(OutboundLaneData {
-						oldest_unpruned_nonce: 1,
-						latest_received_nonce: 1,
-						latest_generated_nonce: 1,
-					}),
+			using_messages_proof(
+				10,
+				Some(OutboundLaneData {
+					oldest_unpruned_nonce: 1,
+					latest_received_nonce: 1,
+					latest_generated_nonce: 1,
 				}),
+				encode_all_messages,
+				|d| {
+					let mut d = d.encode();
+					d.truncate(1);
+					d
+				},
+				|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 10),
 			),
 			Err(target::MessageProofError::FailedToDecodeOutboundLaneState),
 		);
@@ -1575,15 +1403,9 @@ mod tests {
 	#[test]
 	fn message_proof_is_rejected_if_it_is_empty() {
 		assert_eq!(
-			target::verify_messages_proof_with_parser::<OnThisChainBridge, _, _>(
-				messages_proof(0),
-				0,
-				|_, _| Ok(TestMessageProofParser {
-					failing: false,
-					messages: no_messages_range(),
-					outbound_lane_data: None,
-				}),
-			),
+			using_messages_proof(0, None, encode_all_messages, encode_lane_data, |proof| {
+				target::verify_messages_proof::<OnThisChainBridge>(proof, 0)
+			},),
 			Err(target::MessageProofError::Empty),
 		);
 	}
@@ -1591,21 +1413,19 @@ mod tests {
 	#[test]
 	fn non_empty_message_proof_without_messages_is_accepted() {
 		assert_eq!(
-			target::verify_messages_proof_with_parser::<OnThisChainBridge, _, _>(
-				messages_proof(0),
+			using_messages_proof(
 				0,
-				|_, _| Ok(TestMessageProofParser {
-					failing: false,
-					messages: no_messages_range(),
-					outbound_lane_data: Some(OutboundLaneData {
-						oldest_unpruned_nonce: 1,
-						latest_received_nonce: 1,
-						latest_generated_nonce: 1,
-					}),
+				Some(OutboundLaneData {
+					oldest_unpruned_nonce: 1,
+					latest_received_nonce: 1,
+					latest_generated_nonce: 1,
 				}),
+				encode_all_messages,
+				encode_lane_data,
+				|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 0),
 			),
 			Ok(vec![(
-				Default::default(),
+				*TEST_LANE_ID,
 				ProvedLaneMessages {
 					lane_state: Some(OutboundLaneData {
 						oldest_unpruned_nonce: 1,
@@ -1623,21 +1443,19 @@ mod tests {
 	#[test]
 	fn non_empty_message_proof_is_accepted() {
 		assert_eq!(
-			target::verify_messages_proof_with_parser::<OnThisChainBridge, _, _>(
-				messages_proof(1),
+			using_messages_proof(
 				1,
-				|_, _| Ok(TestMessageProofParser {
-					failing: false,
-					messages: 1..=1,
-					outbound_lane_data: Some(OutboundLaneData {
-						oldest_unpruned_nonce: 1,
-						latest_received_nonce: 1,
-						latest_generated_nonce: 1,
-					}),
+				Some(OutboundLaneData {
+					oldest_unpruned_nonce: 1,
+					latest_received_nonce: 1,
+					latest_generated_nonce: 1,
 				}),
+				encode_all_messages,
+				encode_lane_data,
+				|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 1),
 			),
 			Ok(vec![(
-				Default::default(),
+				*TEST_LANE_ID,
 				ProvedLaneMessages {
 					lane_state: Some(OutboundLaneData {
 						oldest_unpruned_nonce: 1,
@@ -1645,8 +1463,8 @@ mod tests {
 						latest_generated_nonce: 1,
 					}),
 					messages: vec![Message {
-						key: MessageKey { lane_id: Default::default(), nonce: 1 },
-						data: MessageData { payload: 1u64.encode(), fee: BridgedChainBalance(0) },
+						key: MessageKey { lane_id: *TEST_LANE_ID, nonce: 1 },
+						data: MessageData { payload: vec![42], fee: 0 },
 					}],
 				},
 			)]
@@ -1656,21 +1474,12 @@ mod tests {
 	}
 
 	#[test]
-	fn verify_messages_proof_with_parser_does_not_panic_if_messages_count_mismatches() {
+	fn verify_messages_proof_does_not_panic_if_messages_count_mismatches() {
 		assert_eq!(
-			target::verify_messages_proof_with_parser::<OnThisChainBridge, _, _>(
-				messages_proof(u64::MAX),
-				0,
-				|_, _| Ok(TestMessageProofParser {
-					failing: false,
-					messages: 0..=u64::MAX,
-					outbound_lane_data: Some(OutboundLaneData {
-						oldest_unpruned_nonce: 1,
-						latest_received_nonce: 1,
-						latest_generated_nonce: 1,
-					}),
-				}),
-			),
+			using_messages_proof(1, None, encode_all_messages, encode_lane_data, |mut proof| {
+				proof.nonces_end = u64::MAX;
+				target::verify_messages_proof::<OnThisChainBridge>(proof, u32::MAX)
+			},),
 			Err(target::MessageProofError::MessagesCountMismatch),
 		);
 	}
diff --git a/bridges/bin/runtime-common/src/messages_benchmarking.rs b/bridges/bin/runtime-common/src/messages_benchmarking.rs
index 71eed881a06..24df69dc90a 100644
--- a/bridges/bin/runtime-common/src/messages_benchmarking.rs
+++ b/bridges/bin/runtime-common/src/messages_benchmarking.rs
@@ -19,14 +19,18 @@
 
 #![cfg(feature = "runtime-benchmarks")]
 
-use crate::messages::{
-	source::{FromBridgedChainMessagesDeliveryProof, FromThisChainMessagePayload},
-	target::FromBridgedChainMessagesProof,
-	AccountIdOf, BalanceOf, BridgedChain, CallOf, HashOf, MessageBridge, RawStorageProof,
-	SignatureOf, SignerOf, ThisChain,
+use crate::{
+	messages::{
+		source::{FromBridgedChainMessagesDeliveryProof, FromThisChainMessagePayload},
+		target::FromBridgedChainMessagesProof,
+		AccountIdOf, BalanceOf, BridgedChain, CallOf, HashOf, MessageBridge, ThisChain,
+	},
+	messages_generation::{
+		encode_all_messages, encode_lane_data, grow_trie, prepare_messages_storage_proof,
+	},
 };
 
-use bp_messages::{storage_keys, MessageData, MessageKey, MessagePayload};
+use bp_messages::storage_keys;
 use bp_runtime::{record_all_trie_keys, StorageProofSize};
 use codec::Encode;
 use frame_support::{dispatch::GetDispatchInfo, weights::Weight};
@@ -34,7 +38,7 @@ use pallet_bridge_messages::benchmarking::{
 	MessageDeliveryProofParams, MessageParams, MessageProofParams,
 };
 use sp_core::Hasher;
-use sp_runtime::traits::{Header, IdentifyAccount, MaybeSerializeDeserialize, Zero};
+use sp_runtime::traits::{Header, MaybeSerializeDeserialize, Zero};
 use sp_std::{fmt::Debug, prelude::*};
 use sp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, Recorder, TrieMut};
 
@@ -71,10 +75,6 @@ where
 	BalanceOf<ThisChain<B>>: Debug + MaybeSerializeDeserialize,
 	CallOf<ThisChain<B>>: From<frame_system::Call<R>> + GetDispatchInfo,
 	HashOf<BridgedChain<B>>: Copy + Default,
-	SignatureOf<ThisChain<B>>: From<sp_core::ed25519::Signature>,
-	SignerOf<ThisChain<B>>: Clone
-		+ From<sp_core::ed25519::Public>
-		+ IdentifyAccount<AccountId = AccountIdOf<ThisChain<B>>>,
 {
 	let message_payload = match params.size {
 		StorageProofSize::Minimal(ref size) => vec![0u8; *size as _],
@@ -82,8 +82,15 @@ where
 	};
 
 	// finally - prepare storage proof and update environment
-	let (state_root, storage_proof) =
-		prepare_messages_storage_proof::<B, BHH>(&params, message_payload);
+	let (state_root, storage_proof) = prepare_messages_storage_proof::<B>(
+		params.lane,
+		params.message_nonces.clone(),
+		params.outbound_lane_data,
+		params.size,
+		message_payload,
+		encode_all_messages,
+		encode_lane_data,
+	);
 	let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::<R, FI>(state_root);
 
 	(
@@ -141,69 +148,6 @@ where
 	}
 }
 
-/// Prepare storage proof of given messages.
-///
-/// Returns state trie root and nodes with prepared messages.
-fn prepare_messages_storage_proof<B, BHH>(
-	params: &MessageProofParams,
-	message_payload: MessagePayload,
-) -> (HashOf<BridgedChain<B>>, RawStorageProof)
-where
-	B: MessageBridge,
-	BHH: Hasher<Out = HashOf<BridgedChain<B>>>,
-	HashOf<BridgedChain<B>>: Copy + Default,
-{
-	// prepare Bridged chain storage with messages and (optionally) outbound lane state
-	let message_count =
-		params.message_nonces.end().saturating_sub(*params.message_nonces.start()) + 1;
-	let mut storage_keys = Vec::with_capacity(message_count as usize + 1);
-	let mut root = Default::default();
-	let mut mdb = MemoryDB::default();
-	{
-		let mut trie = TrieDBMutBuilderV1::<BHH>::new(&mut mdb, &mut root).build();
-
-		// insert messages
-		for nonce in params.message_nonces.clone() {
-			let message_key = MessageKey { lane_id: params.lane, nonce };
-			let message_data = MessageData {
-				fee: BalanceOf::<BridgedChain<B>>::from(0),
-				payload: message_payload.clone(),
-			};
-			let storage_key = storage_keys::message_key(
-				B::BRIDGED_MESSAGES_PALLET_NAME,
-				&message_key.lane_id,
-				message_key.nonce,
-			)
-			.0;
-			trie.insert(&storage_key, &message_data.encode())
-				.map_err(|_| "TrieMut::insert has failed")
-				.expect("TrieMut::insert should not fail in benchmarks");
-			storage_keys.push(storage_key);
-		}
-
-		// insert outbound lane state
-		if let Some(ref outbound_lane_data) = params.outbound_lane_data {
-			let storage_key =
-				storage_keys::outbound_lane_data_key(B::BRIDGED_MESSAGES_PALLET_NAME, &params.lane)
-					.0;
-			trie.insert(&storage_key, &outbound_lane_data.encode())
-				.map_err(|_| "TrieMut::insert has failed")
-				.expect("TrieMut::insert should not fail in benchmarks");
-			storage_keys.push(storage_key);
-		}
-	}
-	root = grow_trie(root, &mut mdb, params.size);
-
-	// generate storage proof to be delivered to This chain
-	let mut proof_recorder = Recorder::<LayoutV1<BHH>>::new();
-	record_all_trie_keys::<LayoutV1<BHH>, _>(&mdb, &root, &mut proof_recorder)
-		.map_err(|_| "record_all_trie_keys has failed")
-		.expect("record_all_trie_keys should not fail in benchmarks");
-	let storage_proof = proof_recorder.drain().into_iter().map(|n| n.data.to_vec()).collect();
-
-	(root, storage_proof)
-}
-
 /// Insert header to the bridge GRANDPA pallet.
 pub(crate) fn insert_header_to_grandpa_pallet<R, GI>(
 	state_root: bp_runtime::HashOf<R::BridgedChain>,
@@ -225,38 +169,3 @@ where
 	pallet_bridge_grandpa::initialize_for_benchmarks::<R, GI>(bridged_header);
 	(bridged_block_number, bridged_header_hash)
 }
-
-/// Populate trie with dummy keys+values until trie has at least given size.
-pub fn grow_trie<H: Hasher>(
-	mut root: H::Out,
-	mdb: &mut MemoryDB<H>,
-	trie_size: StorageProofSize,
-) -> H::Out {
-	let (iterations, leaf_size, minimal_trie_size) = match trie_size {
-		StorageProofSize::Minimal(_) => return root,
-		StorageProofSize::HasLargeLeaf(size) => (1, size, size),
-		StorageProofSize::HasExtraNodes(size) => (8, 1, size),
-	};
-
-	let mut key_index = 0;
-	loop {
-		// generate storage proof to be delivered to This chain
-		let mut proof_recorder = Recorder::<LayoutV1<H>>::new();
-		record_all_trie_keys::<LayoutV1<H>, _>(mdb, &root, &mut proof_recorder)
-			.map_err(|_| "record_all_trie_keys has failed")
-			.expect("record_all_trie_keys should not fail in benchmarks");
-		let size: usize = proof_recorder.drain().into_iter().map(|n| n.data.len()).sum();
-		if size > minimal_trie_size as _ {
-			return root
-		}
-
-		let mut trie = TrieDBMutBuilderV1::<H>::from_existing(mdb, &mut root).build();
-		for _ in 0..iterations {
-			trie.insert(&key_index.encode(), &vec![42u8; leaf_size as _])
-				.map_err(|_| "TrieMut::insert has failed")
-				.expect("TrieMut::insert should not fail in benchmarks");
-			key_index += 1;
-		}
-		trie.commit();
-	}
-}
diff --git a/bridges/bin/runtime-common/src/messages_generation.rs b/bridges/bin/runtime-common/src/messages_generation.rs
new file mode 100644
index 00000000000..822b635cc9b
--- /dev/null
+++ b/bridges/bin/runtime-common/src/messages_generation.rs
@@ -0,0 +1,154 @@
+// Copyright 2019-2022 Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity Bridges Common is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Helpers for generating message storage proofs, that are used by tests and by benchmarks.
+
+#![cfg(any(feature = "runtime-benchmarks", test))]
+
+use crate::messages::{BalanceOf, BridgedChain, HashOf, HasherOf, MessageBridge, RawStorageProof};
+
+use bp_messages::{
+	storage_keys, LaneId, MessageData, MessageKey, MessageNonce, MessagePayload, OutboundLaneData,
+};
+use bp_runtime::{record_all_trie_keys, StorageProofSize};
+use codec::Encode;
+use sp_core::Hasher;
+use sp_runtime::traits::Zero;
+use sp_std::{ops::RangeInclusive, prelude::*};
+use sp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, Recorder, TrieMut};
+
+/// Simple and correct message data encode function.
+pub(crate) fn encode_all_messages<B: Encode>(
+	_: MessageNonce,
+	m: &MessageData<B>,
+) -> Option<Vec<u8>> {
+	Some(m.encode())
+}
+
+/// Simple and correct outbound lane data encode function.
+pub(crate) fn encode_lane_data(d: &OutboundLaneData) -> Vec<u8> {
+	d.encode()
+}
+
+/// Prepare storage proof of given messages.
+///
+/// Returns state trie root and nodes with prepared messages.
+pub(crate) fn prepare_messages_storage_proof<B>(
+	lane: LaneId,
+	message_nonces: RangeInclusive<MessageNonce>,
+	outbound_lane_data: Option<OutboundLaneData>,
+	size: StorageProofSize,
+	message_payload: MessagePayload,
+	encode_message: impl Fn(MessageNonce, &MessageData<BalanceOf<BridgedChain<B>>>) -> Option<Vec<u8>>,
+	encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec<u8>,
+) -> (HashOf<BridgedChain<B>>, RawStorageProof)
+where
+	B: MessageBridge,
+	HashOf<BridgedChain<B>>: Copy + Default,
+{
+	// prepare Bridged chain storage with messages and (optionally) outbound lane state
+	let message_count = message_nonces.end().saturating_sub(*message_nonces.start()) + 1;
+	let mut storage_keys = Vec::with_capacity(message_count as usize + 1);
+	let mut root = Default::default();
+	let mut mdb = MemoryDB::default();
+	{
+		let mut trie =
+			TrieDBMutBuilderV1::<HasherOf<BridgedChain<B>>>::new(&mut mdb, &mut root).build();
+
+		// insert messages
+		for nonce in message_nonces {
+			let message_key = MessageKey { lane_id: lane, nonce };
+			let message_data = MessageData {
+				fee: BalanceOf::<BridgedChain<B>>::zero(),
+				payload: message_payload.clone(),
+			};
+			let message_data = match encode_message(nonce, &message_data) {
+				Some(message_data) => message_data,
+				None => continue,
+			};
+			let storage_key = storage_keys::message_key(
+				B::BRIDGED_MESSAGES_PALLET_NAME,
+				&message_key.lane_id,
+				message_key.nonce,
+			)
+			.0;
+			trie.insert(&storage_key, &message_data)
+				.map_err(|_| "TrieMut::insert has failed")
+				.expect("TrieMut::insert should not fail in benchmarks");
+			storage_keys.push(storage_key);
+		}
+
+		// insert outbound lane state
+		if let Some(outbound_lane_data) = outbound_lane_data.as_ref().map(encode_outbound_lane_data)
+		{
+			let storage_key =
+				storage_keys::outbound_lane_data_key(B::BRIDGED_MESSAGES_PALLET_NAME, &lane).0;
+			trie.insert(&storage_key, &outbound_lane_data)
+				.map_err(|_| "TrieMut::insert has failed")
+				.expect("TrieMut::insert should not fail in benchmarks");
+			storage_keys.push(storage_key);
+		}
+	}
+	root = grow_trie(root, &mut mdb, size);
+
+	// generate storage proof to be delivered to This chain
+	let mut proof_recorder = Recorder::<LayoutV1<HasherOf<BridgedChain<B>>>>::new();
+	record_all_trie_keys::<LayoutV1<HasherOf<BridgedChain<B>>>, _>(
+		&mdb,
+		&root,
+		&mut proof_recorder,
+	)
+	.map_err(|_| "record_all_trie_keys has failed")
+	.expect("record_all_trie_keys should not fail in benchmarks");
+	let storage_proof = proof_recorder.drain().into_iter().map(|n| n.data.to_vec()).collect();
+
+	(root, storage_proof)
+}
+
+/// Populate trie with dummy keys+values until trie has at least given size.
+pub fn grow_trie<H: Hasher>(
+	mut root: H::Out,
+	mdb: &mut MemoryDB<H>,
+	trie_size: StorageProofSize,
+) -> H::Out {
+	let (iterations, leaf_size, minimal_trie_size) = match trie_size {
+		StorageProofSize::Minimal(_) => return root,
+		StorageProofSize::HasLargeLeaf(size) => (1, size, size),
+		StorageProofSize::HasExtraNodes(size) => (8, 1, size),
+	};
+
+	let mut key_index = 0;
+	loop {
+		// generate storage proof to be delivered to This chain
+		let mut proof_recorder = Recorder::<LayoutV1<H>>::new();
+		record_all_trie_keys::<LayoutV1<H>, _>(mdb, &root, &mut proof_recorder)
+			.map_err(|_| "record_all_trie_keys has failed")
+			.expect("record_all_trie_keys should not fail in benchmarks");
+		let size: usize = proof_recorder.drain().into_iter().map(|n| n.data.len()).sum();
+		if size > minimal_trie_size as _ {
+			return root
+		}
+
+		let mut trie = TrieDBMutBuilderV1::<H>::from_existing(mdb, &mut root).build();
+		for _ in 0..iterations {
+			trie.insert(&key_index.encode(), &vec![42u8; leaf_size as _])
+				.map_err(|_| "TrieMut::insert has failed")
+				.expect("TrieMut::insert should not fail in benchmarks");
+			key_index += 1;
+		}
+		trie.commit();
+	}
+}
diff --git a/bridges/bin/runtime-common/src/parachains_benchmarking.rs b/bridges/bin/runtime-common/src/parachains_benchmarking.rs
index 59e2ef6962c..fcd32ea28b8 100644
--- a/bridges/bin/runtime-common/src/parachains_benchmarking.rs
+++ b/bridges/bin/runtime-common/src/parachains_benchmarking.rs
@@ -18,7 +18,9 @@
 
 #![cfg(feature = "runtime-benchmarks")]
 
-use crate::messages_benchmarking::{grow_trie, insert_header_to_grandpa_pallet};
+use crate::{
+	messages_benchmarking::insert_header_to_grandpa_pallet, messages_generation::grow_trie,
+};
 
 use bp_parachains::parachain_head_storage_key_at_source;
 use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId};
diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs
index e061b702a11..2402b2512e2 100644
--- a/bridges/modules/grandpa/src/lib.rs
+++ b/bridges/modules/grandpa/src/lib.rs
@@ -38,7 +38,7 @@
 
 use storage_types::StoredAuthoritySet;
 
-use bp_header_chain::{justification::GrandpaJustification, InitializationData};
+use bp_header_chain::{justification::GrandpaJustification, HeaderChain, InitializationData};
 use bp_runtime::{
 	BlockNumberOf, BoundedStorageValue, Chain, HashOf, HasherOf, HeaderOf, OwnedBridgeModule,
 };
@@ -67,6 +67,8 @@ pub use weights::WeightInfo;
 /// The target that will be used when publishing logs related to this pallet.
 pub const LOG_TARGET: &str = "runtime::bridge-grandpa";
 
+/// Bridged chain from the pallet configuration.
+pub type BridgedChain<T, I> = <T as Config<I>>::BridgedChain;
 /// Block number of the bridged chain.
 pub type BridgedBlockNumber<T, I> = BlockNumberOf<<T as Config<I>>::BridgedChain>;
 /// Block hash of the bridged chain.
@@ -382,8 +384,6 @@ pub mod pallet {
 		TooManyRequests,
 		/// The header being imported is older than the best finalized header known to the pallet.
 		OldHeader,
-		/// The header is unknown to the pallet.
-		UnknownHeader,
 		/// The scheduled authority set change found in the header is unsupported by the pallet.
 		///
 		/// This is the case for non-standard (e.g forced) authority set changes.
@@ -392,8 +392,6 @@ pub mod pallet {
 		NotInitialized,
 		/// The pallet has already been initialized.
 		AlreadyInitialized,
-		/// The storage proof doesn't contains storage root. So it is invalid for given header.
-		StorageRootMismatch,
 		/// Too many authorities in the set.
 		TooManyAuthoritiesInSet,
 		/// Too large header.
@@ -581,9 +579,6 @@ pub mod pallet {
 
 impl<T: Config<I>, I: 'static> Pallet<T, I> {
 	/// Get the best finalized header the pallet knows of.
-	///
-	/// Returns a dummy header if there is no best header. This can only happen
-	/// if the pallet has not been initialized yet.
 	pub fn best_finalized() -> Option<BridgedHeader<T, I>> {
 		let (_, hash) = <BestFinalized<T, I>>::get()?;
 		<ImportedHeaders<T, I>>::get(hash).map(|h| h.into_inner())
@@ -593,21 +588,14 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
 	pub fn is_known_header(hash: BridgedBlockHash<T, I>) -> bool {
 		<ImportedHeaders<T, I>>::contains_key(hash)
 	}
+}
 
-	/// Verify that the passed storage proof is valid, given it is crafted using
-	/// known finalized header. If the proof is valid, then the `parse` callback
-	/// is called and the function returns its result.
-	pub fn parse_finalized_storage_proof<R>(
-		hash: BridgedBlockHash<T, I>,
-		storage_proof: sp_trie::StorageProof,
-		parse: impl FnOnce(bp_runtime::StorageProofChecker<BridgedBlockHasher<T, I>>) -> R,
-	) -> Result<R, sp_runtime::DispatchError> {
-		let header = <ImportedHeaders<T, I>>::get(hash).ok_or(Error::<T, I>::UnknownHeader)?;
-		let storage_proof_checker =
-			bp_runtime::StorageProofChecker::new(*header.state_root(), storage_proof)
-				.map_err(|_| Error::<T, I>::StorageRootMismatch)?;
-
-		Ok(parse(storage_proof_checker))
+/// Bridge GRANDPA pallet as header chain.
+pub type GrandpaChainHeaders<T, I> = Pallet<T, I>;
+
+impl<T: Config<I>, I: 'static> HeaderChain<BridgedChain<T, I>> for GrandpaChainHeaders<T, I> {
+	fn finalized_header(hash: HashOf<BridgedChain<T, I>>) -> Option<HeaderOf<BridgedChain<T, I>>> {
+		ImportedHeaders::<T, I>::get(hash).map(|h| h.into_inner())
 	}
 }
 
@@ -1119,7 +1107,7 @@ mod tests {
 					sp_trie::StorageProof::new(vec![]),
 					|_| (),
 				),
-				Error::<TestRuntime>::UnknownHeader,
+				bp_header_chain::HeaderChainError::UnknownHeader,
 			);
 		});
 	}
diff --git a/bridges/modules/parachains/Cargo.toml b/bridges/modules/parachains/Cargo.toml
index bccd16f3d59..ce674459eac 100644
--- a/bridges/modules/parachains/Cargo.toml
+++ b/bridges/modules/parachains/Cargo.toml
@@ -12,6 +12,7 @@ scale-info = { version = "2.1.1", default-features = false, features = ["derive"
 
 # Bridge Dependencies
 
+bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
 bp-parachains = { path = "../../primitives/parachains", default-features = false }
 bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false }
 bp-runtime = { path = "../../primitives/runtime", default-features = false }
@@ -34,6 +35,7 @@ sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
 [features]
 default = ["std"]
 std = [
+	"bp-header-chain/std",
 	"bp-parachains/std",
 	"bp-polkadot-core/std",
 	"bp-runtime/std",
diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs
index 4b2be1b7170..e6625476a3d 100644
--- a/bridges/modules/parachains/src/lib.rs
+++ b/bridges/modules/parachains/src/lib.rs
@@ -26,12 +26,14 @@
 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_polkadot_core::parachains::{ParaHash, ParaHasher, ParaHead, ParaHeadsProof, ParaId};
-use bp_runtime::StorageProofError;
+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 sp_std::vec::Vec;
+use sp_std::{marker::PhantomData, vec::Vec};
 
 // Re-export in crate namespace for `construct_runtime!`.
 pub use pallet::*;
@@ -410,27 +412,6 @@ pub mod pallet {
 			ImportedParaHeads::<T, I>::get(parachain, hash).map(|h| h.into_inner())
 		}
 
-		/// Verify that the passed storage proof is valid, given it is crafted using
-		/// known finalized header. If the proof is valid, then the `parse` callback
-		/// is called and the function returns its result.
-		pub fn parse_finalized_storage_proof<R>(
-			parachain: ParaId,
-			hash: ParaHash,
-			storage_proof: sp_trie::StorageProof,
-			decode_state_root: impl FnOnce(ParaHead) -> Option<ParaHash>,
-			parse: impl FnOnce(bp_runtime::StorageProofChecker<ParaHasher>) -> R,
-		) -> Result<R, sp_runtime::DispatchError> {
-			let para_head =
-				Self::parachain_head(parachain, hash).ok_or(Error::<T, I>::UnknownParaHead)?;
-			let state_root =
-				decode_state_root(para_head).ok_or(Error::<T, I>::FailedToExtractStateRoot)?;
-			let storage_proof_checker =
-				bp_runtime::StorageProofChecker::new(state_root, storage_proof)
-					.map_err(|_| Error::<T, I>::StorageRootMismatch)?;
-
-			Ok(parse(storage_proof_checker))
-		}
-
 		/// Read parachain head from storage proof.
 		fn read_parachain_head(
 			storage: &bp_runtime::StorageProofChecker<RelayBlockHasher>,
@@ -617,6 +598,18 @@ pub mod pallet {
 	}
 }
 
+/// Single parachain header chain adapter.
+pub struct ParachainHeaders<T, I, C>(PhantomData<(T, I, C)>);
+
+impl<T: Config<I>, I: 'static, C: Parachain<Hash = ParaHash>> HeaderChain<C>
+	for ParachainHeaders<T, I, C>
+{
+	fn finalized_header(hash: HashOf<C>) -> Option<HeaderOf<C>> {
+		Pallet::<T, I>::parachain_head(ParaId(C::PARACHAIN_ID), hash)
+			.and_then(|head| Decode::decode(&mut &head.0[..]).ok())
+	}
+}
+
 #[cfg(test)]
 mod tests {
 	use super::*;
diff --git a/bridges/primitives/chain-rialto-parachain/src/lib.rs b/bridges/primitives/chain-rialto-parachain/src/lib.rs
index 3a54a2fb548..584882425b4 100644
--- a/bridges/primitives/chain-rialto-parachain/src/lib.rs
+++ b/bridges/primitives/chain-rialto-parachain/src/lib.rs
@@ -21,7 +21,7 @@
 use bp_messages::{
 	InboundMessageDetails, LaneId, MessageNonce, MessagePayload, OutboundMessageDetails,
 };
-use bp_runtime::{decl_bridge_runtime_apis, Chain};
+use bp_runtime::{decl_bridge_runtime_apis, Chain, Parachain};
 use frame_support::{
 	dispatch::DispatchClass,
 	weights::{constants::WEIGHT_PER_SECOND, IdentityFee, Weight},
@@ -160,6 +160,10 @@ impl Chain for RialtoParachain {
 	}
 }
 
+impl Parachain for RialtoParachain {
+	const PARACHAIN_ID: u32 = RIALTO_PARACHAIN_ID;
+}
+
 frame_support::parameter_types! {
 	pub BlockLength: limits::BlockLength =
 		limits::BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO);
diff --git a/bridges/primitives/header-chain/Cargo.toml b/bridges/primitives/header-chain/Cargo.toml
index be87cce460e..3b7ea58cb96 100644
--- a/bridges/primitives/header-chain/Cargo.toml
+++ b/bridges/primitives/header-chain/Cargo.toml
@@ -23,6 +23,7 @@ sp-core = { git = "https://github.com/paritytech/substrate", branch = "master",
 sp-finality-grandpa = { 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 }
+sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
 
 [dev-dependencies]
 bp-test-utils = { path = "../test-utils" }
@@ -42,4 +43,5 @@ std = [
 	"sp-finality-grandpa/std",
 	"sp-runtime/std",
 	"sp-std/std",
+	"sp-trie/std",
 ]
diff --git a/bridges/primitives/header-chain/src/lib.rs b/bridges/primitives/header-chain/src/lib.rs
index 21a42874b71..2b4e0802a5a 100644
--- a/bridges/primitives/header-chain/src/lib.rs
+++ b/bridges/primitives/header-chain/src/lib.rs
@@ -19,19 +19,58 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
-use bp_runtime::BasicOperatingMode;
+use bp_runtime::{BasicOperatingMode, Chain, HashOf, HasherOf, HeaderOf, StorageProofChecker};
 use codec::{Codec, Decode, Encode, EncodeLike};
 use core::{clone::Clone, cmp::Eq, default::Default, fmt::Debug};
+use frame_support::PalletError;
 use scale_info::TypeInfo;
 #[cfg(feature = "std")]
 use serde::{Deserialize, Serialize};
 use sp_finality_grandpa::{AuthorityList, ConsensusLog, SetId, GRANDPA_ENGINE_ID};
 use sp_runtime::{traits::Header as HeaderT, Digest, RuntimeDebug};
 use sp_std::boxed::Box;
+use sp_trie::StorageProof;
 
 pub mod justification;
 pub mod storage_keys;
 
+/// Header chain error.
+#[derive(Clone, Copy, Decode, Encode, Eq, PalletError, PartialEq, RuntimeDebug, TypeInfo)]
+pub enum HeaderChainError {
+	/// Header with given hash is missing from the chain.
+	UnknownHeader,
+	/// The storage proof doesn't contains storage root.
+	StorageRootMismatch,
+}
+
+impl From<HeaderChainError> for &'static str {
+	fn from(err: HeaderChainError) -> &'static str {
+		match err {
+			HeaderChainError::UnknownHeader => "UnknownHeader",
+			HeaderChainError::StorageRootMismatch => "StorageRootMismatch",
+		}
+	}
+}
+
+/// Substrate header chain, abstracted from the way it is stored.
+pub trait HeaderChain<C: Chain> {
+	/// Returns finalized header by its hash.
+	fn finalized_header(hash: HashOf<C>) -> Option<HeaderOf<C>>;
+	/// Parse storage proof using finalized header.
+	fn parse_finalized_storage_proof<R>(
+		hash: HashOf<C>,
+		storage_proof: StorageProof,
+		parse: impl FnOnce(StorageProofChecker<HasherOf<C>>) -> R,
+	) -> Result<R, HeaderChainError> {
+		let header = Self::finalized_header(hash).ok_or(HeaderChainError::UnknownHeader)?;
+		let storage_proof_checker =
+			bp_runtime::StorageProofChecker::new(*header.state_root(), storage_proof)
+				.map_err(|_| HeaderChainError::StorageRootMismatch)?;
+
+		Ok(parse(storage_proof_checker))
+	}
+}
+
 /// A type that can be used as a parameter in a dispatchable function.
 ///
 /// When using `decl_module` all arguments for call functions must implement this trait.
@@ -79,6 +118,7 @@ pub trait FinalityProof<Number>: Clone + Send + Sync + Debug {
 
 /// A trait that provides helper methods for querying the consensus log.
 pub trait ConsensusLogReader {
+	/// Returns true if digest contains item that schedules authorities set change.
 	fn schedules_authorities_change(digest: &Digest) -> bool;
 }
 
diff --git a/bridges/primitives/runtime/src/chain.rs b/bridges/primitives/runtime/src/chain.rs
index be95f17480d..6f43c0f3549 100644
--- a/bridges/primitives/runtime/src/chain.rs
+++ b/bridges/primitives/runtime/src/chain.rs
@@ -195,6 +195,12 @@ pub trait Chain: Send + Sync + 'static {
 	fn max_extrinsic_weight() -> Weight;
 }
 
+/// Minimal parachain representation that may be used from no_std environment.
+pub trait Parachain: Chain {
+	/// Parachain identifier.
+	const PARACHAIN_ID: u32;
+}
+
 /// Block number used by the chain.
 pub type BlockNumberOf<C> = <C as Chain>::BlockNumber;
 
diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs
index 9f525f5ea85..83043f80621 100644
--- a/bridges/primitives/runtime/src/lib.rs
+++ b/bridges/primitives/runtime/src/lib.rs
@@ -32,7 +32,7 @@ use sp_std::{convert::TryFrom, fmt::Debug, vec, vec::Vec};
 
 pub use chain::{
 	AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, Chain, EncodedOrDecodedCall, HashOf,
-	HasherOf, HeaderOf, IndexOf, SignatureOf, TransactionEraOf,
+	HasherOf, HeaderOf, IndexOf, Parachain, SignatureOf, TransactionEraOf,
 };
 pub use frame_support::storage::storage_prefix as storage_value_final_key;
 use num_traits::{CheckedSub, One};
-- 
GitLab