From 1ef41a59be6c2842fd756c768cc44101b138ac31 Mon Sep 17 00:00:00 2001
From: Svyatoslav Nikolsky <svyatonik@gmail.com>
Date: Mon, 25 Oct 2021 13:24:48 +0300
Subject: [PATCH] Message transactions mortality (#1191)

* transactions mortality in message and complex relays

* logging + enable in test deployments

* spellcheck

* fmt
---
 bridges/primitives/runtime/src/lib.rs         | 19 ++++++-----
 .../src/chains/kusama_messages_to_polkadot.rs | 32 +++++++++++++++---
 .../src/chains/millau_messages_to_rialto.rs   | 32 +++++++++++++++---
 .../src/chains/polkadot_messages_to_kusama.rs | 32 +++++++++++++++---
 .../src/chains/rialto_messages_to_millau.rs   | 32 +++++++++++++++---
 .../src/chains/rococo_messages_to_wococo.rs   | 32 +++++++++++++++---
 .../src/chains/wococo_messages_to_rococo.rs   | 32 +++++++++++++++---
 .../src/cli/relay_headers_and_messages.rs     |  4 +++
 .../bin-substrate/src/cli/relay_messages.rs   |  4 +++
 bridges/relays/client-substrate/src/lib.rs    | 33 +++++++++++++++++--
 bridges/relays/lib-substrate-relay/Cargo.toml |  1 -
 .../src/finality_pipeline.rs                  | 16 +++------
 .../src/finality_target.rs                    |  3 +-
 bridges/relays/lib-substrate-relay/src/lib.rs | 11 +++++++
 .../lib-substrate-relay/src/messages_lane.rs  | 12 +++++++
 .../src/messages_source.rs                    |  4 ++-
 .../src/messages_target.rs                    |  7 +++-
 .../src/on_demand_headers.rs                  |  2 +-
 bridges/relays/utils/Cargo.toml               |  4 +++
 bridges/relays/utils/src/lib.rs               |  5 +--
 20 files changed, 253 insertions(+), 64 deletions(-)

diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs
index 9ee15df1a98..460f1b19dfe 100644
--- a/bridges/primitives/runtime/src/lib.rs
+++ b/bridges/primitives/runtime/src/lib.rs
@@ -69,6 +69,10 @@ pub const ACCOUNT_DERIVATION_PREFIX: &[u8] = b"pallet-bridge/account-derivation/
 /// A unique prefix for entropy when generating a cross-chain account ID for the Root account.
 pub const ROOT_ACCOUNT_DERIVATION_PREFIX: &[u8] = b"pallet-bridge/account-derivation/root";
 
+/// Generic header Id.
+#[derive(RuntimeDebug, Default, Clone, Copy, Eq, Hash, PartialEq)]
+pub struct HeaderId<Hash, Number>(pub Number, pub Hash);
+
 /// Unique identifier of the chain.
 ///
 /// In addition to its main function (identifying the chain), this type may also be used to
@@ -159,20 +163,17 @@ pub enum TransactionEra<BlockNumber, BlockHash> {
 	/// Transaction is immortal.
 	Immortal,
 	/// Transaction is valid for a given number of blocks, starting from given block.
-	Mortal(BlockNumber, BlockHash, u32),
+	Mortal(HeaderId<BlockHash, BlockNumber>, u32),
 }
 
 impl<BlockNumber: Copy + Into<u64>, BlockHash: Copy> TransactionEra<BlockNumber, BlockHash> {
 	/// Prepare transaction era, based on mortality period and current best block number.
 	pub fn new(
-		best_block_number: BlockNumber,
-		best_block_hash: BlockHash,
+		best_block_id: HeaderId<BlockHash, BlockNumber>,
 		mortality_period: Option<u32>,
 	) -> Self {
 		mortality_period
-			.map(|mortality_period| {
-				TransactionEra::Mortal(best_block_number, best_block_hash, mortality_period)
-			})
+			.map(|mortality_period| TransactionEra::Mortal(best_block_id, mortality_period))
 			.unwrap_or(TransactionEra::Immortal)
 	}
 
@@ -185,8 +186,8 @@ 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(),
-			TransactionEra::Mortal(header_number, _, period) =>
-				sp_runtime::generic::Era::mortal(period as _, header_number.into()),
+			TransactionEra::Mortal(header_id, period) =>
+				sp_runtime::generic::Era::mortal(period as _, header_id.0.into()),
 		}
 	}
 
@@ -194,7 +195,7 @@ impl<BlockNumber: Copy + Into<u64>, BlockHash: Copy> TransactionEra<BlockNumber,
 	pub fn signed_payload(&self, genesis_hash: BlockHash) -> BlockHash {
 		match *self {
 			TransactionEra::Immortal => genesis_hash,
-			TransactionEra::Mortal(_, header_hash, _) => header_hash,
+			TransactionEra::Mortal(header_id, _) => header_id.1,
 		}
 	}
 }
diff --git a/bridges/relays/bin-substrate/src/chains/kusama_messages_to_polkadot.rs b/bridges/relays/bin-substrate/src/chains/kusama_messages_to_polkadot.rs
index 40e03ef2668..86e2e22cd90 100644
--- a/bridges/relays/bin-substrate/src/chains/kusama_messages_to_polkadot.rs
+++ b/bridges/relays/bin-substrate/src/chains/kusama_messages_to_polkadot.rs
@@ -16,7 +16,7 @@
 
 //! Kusama-to-Polkadot messages sync entrypoint.
 
-use std::{ops::RangeInclusive, time::Duration};
+use std::ops::RangeInclusive;
 
 use codec::Encode;
 use sp_core::{Bytes, Pair};
@@ -41,6 +41,7 @@ use substrate_relay_helper::{
 	},
 	messages_source::SubstrateMessagesSource,
 	messages_target::SubstrateMessagesTarget,
+	STALL_TIMEOUT,
 };
 
 /// Kusama-to-Polkadot message lane.
@@ -91,6 +92,7 @@ impl SubstrateMessageLane for KusamaMessagesToPolkadot {
 
 	fn make_messages_receiving_proof_transaction(
 		&self,
+		best_block_id: KusamaHeaderId,
 		transaction_nonce: bp_runtime::IndexOf<Kusama>,
 		_generated_at_block: PolkadotHeaderId,
 		proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
@@ -106,7 +108,10 @@ impl SubstrateMessageLane for KusamaMessagesToPolkadot {
 		let transaction = Kusama::sign_transaction(
 			genesis_hash,
 			&self.message_lane.source_sign,
-			relay_substrate_client::TransactionEra::immortal(),
+			relay_substrate_client::TransactionEra::new(
+				best_block_id,
+				self.message_lane.source_transactions_mortality,
+			),
 			UnsignedTransaction::new(call, transaction_nonce),
 		);
 		log::trace!(
@@ -125,6 +130,7 @@ impl SubstrateMessageLane for KusamaMessagesToPolkadot {
 
 	fn make_messages_delivery_transaction(
 		&self,
+		best_block_id: PolkadotHeaderId,
 		transaction_nonce: bp_runtime::IndexOf<Polkadot>,
 		_generated_at_header: KusamaHeaderId,
 		_nonces: RangeInclusive<MessageNonce>,
@@ -146,7 +152,10 @@ impl SubstrateMessageLane for KusamaMessagesToPolkadot {
 		let transaction = Polkadot::sign_transaction(
 			genesis_hash,
 			&self.message_lane.target_sign,
-			relay_substrate_client::TransactionEra::immortal(),
+			relay_substrate_client::TransactionEra::new(
+				best_block_id,
+				self.message_lane.target_transactions_mortality,
+			),
 			UnsignedTransaction::new(call, transaction_nonce),
 		);
 		log::trace!(
@@ -170,7 +179,13 @@ type PolkadotTargetClient = SubstrateMessagesTarget<KusamaMessagesToPolkadot>;
 pub async fn run(
 	params: MessagesRelayParams<Kusama, KusamaSigningParams, Polkadot, PolkadotSigningParams>,
 ) -> anyhow::Result<()> {
-	let stall_timeout = Duration::from_secs(5 * 60);
+	let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
+		params.source_transactions_mortality,
+		params.target_transactions_mortality,
+		Kusama::AVERAGE_BLOCK_INTERVAL,
+		Polkadot::AVERAGE_BLOCK_INTERVAL,
+		STALL_TIMEOUT,
+	);
 	let relayer_id_at_kusama = (*params.source_sign.public().as_array_ref()).into();
 
 	let lane_id = params.lane_id;
@@ -179,8 +194,10 @@ pub async fn run(
 		message_lane: SubstrateMessageLaneToSubstrate {
 			source_client: source_client.clone(),
 			source_sign: params.source_sign,
+			source_transactions_mortality: params.source_transactions_mortality,
 			target_client: params.target_client.clone(),
 			target_sign: params.target_sign,
+			target_transactions_mortality: params.target_transactions_mortality,
 			relayer_id_at_source: relayer_id_at_kusama,
 		},
 	};
@@ -206,12 +223,17 @@ pub async fn run(
 			Max messages in single transaction: {}\n\t\
 			Max messages size in single transaction: {}\n\t\
 			Max messages weight in single transaction: {}\n\t\
-			Relayer mode: {:?}",
+			Relayer mode: {:?}\n\t\
+			Tx mortality: {:?}/{:?}\n\t\
+			Stall timeout: {:?}",
 		lane.message_lane.relayer_id_at_source,
 		max_messages_in_single_batch,
 		max_messages_size_in_single_batch,
 		max_messages_weight_in_single_batch,
 		params.relayer_mode,
+		params.source_transactions_mortality,
+		params.target_transactions_mortality,
+		stall_timeout,
 	);
 
 	let (metrics_params, metrics_values) = add_standalone_metrics(
diff --git a/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs b/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs
index 612fd96d0fc..570a3449752 100644
--- a/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs
+++ b/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs
@@ -16,7 +16,7 @@
 
 //! Millau-to-Rialto messages sync entrypoint.
 
-use std::{ops::RangeInclusive, time::Duration};
+use std::ops::RangeInclusive;
 
 use codec::Encode;
 use frame_support::dispatch::GetDispatchInfo;
@@ -41,6 +41,7 @@ use substrate_relay_helper::{
 	},
 	messages_source::SubstrateMessagesSource,
 	messages_target::SubstrateMessagesTarget,
+	STALL_TIMEOUT,
 };
 
 /// Millau-to-Rialto message lane.
@@ -89,6 +90,7 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
 
 	fn make_messages_receiving_proof_transaction(
 		&self,
+		best_block_id: MillauHeaderId,
 		transaction_nonce: IndexOf<Millau>,
 		_generated_at_block: RialtoHeaderId,
 		proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
@@ -102,7 +104,10 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
 		let transaction = Millau::sign_transaction(
 			genesis_hash,
 			&self.message_lane.source_sign,
-			relay_substrate_client::TransactionEra::immortal(),
+			relay_substrate_client::TransactionEra::new(
+				best_block_id,
+				self.message_lane.source_transactions_mortality,
+			),
 			UnsignedTransaction::new(call, transaction_nonce),
 		);
 		log::trace!(
@@ -122,6 +127,7 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
 
 	fn make_messages_delivery_transaction(
 		&self,
+		best_block_id: RialtoHeaderId,
 		transaction_nonce: IndexOf<Rialto>,
 		_generated_at_header: MillauHeaderId,
 		_nonces: RangeInclusive<MessageNonce>,
@@ -142,7 +148,10 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
 		let transaction = Rialto::sign_transaction(
 			genesis_hash,
 			&self.message_lane.target_sign,
-			relay_substrate_client::TransactionEra::immortal(),
+			relay_substrate_client::TransactionEra::new(
+				best_block_id,
+				self.message_lane.target_transactions_mortality,
+			),
 			UnsignedTransaction::new(call, transaction_nonce),
 		);
 		log::trace!(
@@ -167,7 +176,13 @@ type RialtoTargetClient = SubstrateMessagesTarget<MillauMessagesToRialto>;
 pub async fn run(
 	params: MessagesRelayParams<Millau, MillauSigningParams, Rialto, RialtoSigningParams>,
 ) -> anyhow::Result<()> {
-	let stall_timeout = Duration::from_secs(5 * 60);
+	let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
+		params.source_transactions_mortality,
+		params.target_transactions_mortality,
+		Millau::AVERAGE_BLOCK_INTERVAL,
+		Rialto::AVERAGE_BLOCK_INTERVAL,
+		STALL_TIMEOUT,
+	);
 	let relayer_id_at_millau = (*params.source_sign.public().as_array_ref()).into();
 
 	let lane_id = params.lane_id;
@@ -176,8 +191,10 @@ pub async fn run(
 		message_lane: SubstrateMessageLaneToSubstrate {
 			source_client: source_client.clone(),
 			source_sign: params.source_sign,
+			source_transactions_mortality: params.source_transactions_mortality,
 			target_client: params.target_client.clone(),
 			target_sign: params.target_sign,
+			target_transactions_mortality: params.target_transactions_mortality,
 			relayer_id_at_source: relayer_id_at_millau,
 		},
 	};
@@ -200,12 +217,17 @@ pub async fn run(
 			Max messages in single transaction: {}\n\t\
 			Max messages size in single transaction: {}\n\t\
 			Max messages weight in single transaction: {}\n\t\
-			Relayer mode: {:?}",
+			Relayer mode: {:?}\n\t\
+			Tx mortality: {:?}/{:?}\n\t\
+			Stall timeout: {:?}",
 		lane.message_lane.relayer_id_at_source,
 		max_messages_in_single_batch,
 		max_messages_size_in_single_batch,
 		max_messages_weight_in_single_batch,
 		params.relayer_mode,
+		params.source_transactions_mortality,
+		params.target_transactions_mortality,
+		stall_timeout,
 	);
 
 	let (metrics_params, metrics_values) = add_standalone_metrics(
diff --git a/bridges/relays/bin-substrate/src/chains/polkadot_messages_to_kusama.rs b/bridges/relays/bin-substrate/src/chains/polkadot_messages_to_kusama.rs
index 31e8b710e34..8af62bc80b1 100644
--- a/bridges/relays/bin-substrate/src/chains/polkadot_messages_to_kusama.rs
+++ b/bridges/relays/bin-substrate/src/chains/polkadot_messages_to_kusama.rs
@@ -16,7 +16,7 @@
 
 //! Polkadot-to-Kusama messages sync entrypoint.
 
-use std::{ops::RangeInclusive, time::Duration};
+use std::ops::RangeInclusive;
 
 use codec::Encode;
 use sp_core::{Bytes, Pair};
@@ -41,6 +41,7 @@ use substrate_relay_helper::{
 	},
 	messages_source::SubstrateMessagesSource,
 	messages_target::SubstrateMessagesTarget,
+	STALL_TIMEOUT,
 };
 
 /// Polkadot-to-Kusama message lane.
@@ -90,6 +91,7 @@ impl SubstrateMessageLane for PolkadotMessagesToKusama {
 
 	fn make_messages_receiving_proof_transaction(
 		&self,
+		best_block_id: PolkadotHeaderId,
 		transaction_nonce: bp_runtime::IndexOf<Polkadot>,
 		_generated_at_block: KusamaHeaderId,
 		proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
@@ -105,7 +107,10 @@ impl SubstrateMessageLane for PolkadotMessagesToKusama {
 		let transaction = Polkadot::sign_transaction(
 			genesis_hash,
 			&self.message_lane.source_sign,
-			relay_substrate_client::TransactionEra::immortal(),
+			relay_substrate_client::TransactionEra::new(
+				best_block_id,
+				self.message_lane.source_transactions_mortality,
+			),
 			UnsignedTransaction::new(call, transaction_nonce),
 		);
 		log::trace!(
@@ -124,6 +129,7 @@ impl SubstrateMessageLane for PolkadotMessagesToKusama {
 
 	fn make_messages_delivery_transaction(
 		&self,
+		best_block_id: KusamaHeaderId,
 		transaction_nonce: bp_runtime::IndexOf<Kusama>,
 		_generated_at_header: PolkadotHeaderId,
 		_nonces: RangeInclusive<MessageNonce>,
@@ -145,7 +151,10 @@ impl SubstrateMessageLane for PolkadotMessagesToKusama {
 		let transaction = Kusama::sign_transaction(
 			genesis_hash,
 			&self.message_lane.target_sign,
-			relay_substrate_client::TransactionEra::immortal(),
+			relay_substrate_client::TransactionEra::new(
+				best_block_id,
+				self.message_lane.target_transactions_mortality,
+			),
 			UnsignedTransaction::new(call, transaction_nonce),
 		);
 		log::trace!(
@@ -169,7 +178,13 @@ type KusamaTargetClient = SubstrateMessagesTarget<PolkadotMessagesToKusama>;
 pub async fn run(
 	params: MessagesRelayParams<Polkadot, PolkadotSigningParams, Kusama, KusamaSigningParams>,
 ) -> anyhow::Result<()> {
-	let stall_timeout = Duration::from_secs(5 * 60);
+	let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
+		params.source_transactions_mortality,
+		params.target_transactions_mortality,
+		Polkadot::AVERAGE_BLOCK_INTERVAL,
+		Kusama::AVERAGE_BLOCK_INTERVAL,
+		STALL_TIMEOUT,
+	);
 	let relayer_id_at_polkadot = (*params.source_sign.public().as_array_ref()).into();
 
 	let lane_id = params.lane_id;
@@ -178,8 +193,10 @@ pub async fn run(
 		message_lane: SubstrateMessageLaneToSubstrate {
 			source_client: source_client.clone(),
 			source_sign: params.source_sign,
+			source_transactions_mortality: params.source_transactions_mortality,
 			target_client: params.target_client.clone(),
 			target_sign: params.target_sign,
+			target_transactions_mortality: params.target_transactions_mortality,
 			relayer_id_at_source: relayer_id_at_polkadot,
 		},
 	};
@@ -205,12 +222,17 @@ pub async fn run(
 			Max messages in single transaction: {}\n\t\
 			Max messages size in single transaction: {}\n\t\
 			Max messages weight in single transaction: {}\n\t\
-			Relayer mode: {:?}",
+			Relayer mode: {:?}\n\t\
+			Tx mortality: {:?}/{:?}\n\t\
+			Stall timeout: {:?}",
 		lane.message_lane.relayer_id_at_source,
 		max_messages_in_single_batch,
 		max_messages_size_in_single_batch,
 		max_messages_weight_in_single_batch,
 		params.relayer_mode,
+		params.source_transactions_mortality,
+		params.target_transactions_mortality,
+		stall_timeout,
 	);
 
 	let (metrics_params, metrics_values) = add_standalone_metrics(
diff --git a/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs b/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs
index 35f0852dcb6..f85a5e760e6 100644
--- a/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs
+++ b/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs
@@ -16,7 +16,7 @@
 
 //! Rialto-to-Millau messages sync entrypoint.
 
-use std::{ops::RangeInclusive, time::Duration};
+use std::ops::RangeInclusive;
 
 use codec::Encode;
 use frame_support::dispatch::GetDispatchInfo;
@@ -41,6 +41,7 @@ use substrate_relay_helper::{
 	},
 	messages_source::SubstrateMessagesSource,
 	messages_target::SubstrateMessagesTarget,
+	STALL_TIMEOUT,
 };
 
 /// Rialto-to-Millau message lane.
@@ -89,6 +90,7 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
 
 	fn make_messages_receiving_proof_transaction(
 		&self,
+		best_block_id: RialtoHeaderId,
 		transaction_nonce: IndexOf<Rialto>,
 		_generated_at_block: MillauHeaderId,
 		proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
@@ -102,7 +104,10 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
 		let transaction = Rialto::sign_transaction(
 			genesis_hash,
 			&self.message_lane.source_sign,
-			relay_substrate_client::TransactionEra::immortal(),
+			relay_substrate_client::TransactionEra::new(
+				best_block_id,
+				self.message_lane.source_transactions_mortality,
+			),
 			UnsignedTransaction::new(call, transaction_nonce),
 		);
 		log::trace!(
@@ -122,6 +127,7 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
 
 	fn make_messages_delivery_transaction(
 		&self,
+		best_block_id: MillauHeaderId,
 		transaction_nonce: IndexOf<Millau>,
 		_generated_at_header: RialtoHeaderId,
 		_nonces: RangeInclusive<MessageNonce>,
@@ -142,7 +148,10 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
 		let transaction = Millau::sign_transaction(
 			genesis_hash,
 			&self.message_lane.target_sign,
-			relay_substrate_client::TransactionEra::immortal(),
+			relay_substrate_client::TransactionEra::new(
+				best_block_id,
+				self.message_lane.target_transactions_mortality,
+			),
 			UnsignedTransaction::new(call, transaction_nonce),
 		);
 		log::trace!(
@@ -167,7 +176,13 @@ type MillauTargetClient = SubstrateMessagesTarget<RialtoMessagesToMillau>;
 pub async fn run(
 	params: MessagesRelayParams<Rialto, RialtoSigningParams, Millau, MillauSigningParams>,
 ) -> anyhow::Result<()> {
-	let stall_timeout = Duration::from_secs(5 * 60);
+	let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
+		params.source_transactions_mortality,
+		params.target_transactions_mortality,
+		Rialto::AVERAGE_BLOCK_INTERVAL,
+		Millau::AVERAGE_BLOCK_INTERVAL,
+		STALL_TIMEOUT,
+	);
 	let relayer_id_at_rialto = (*params.source_sign.public().as_array_ref()).into();
 
 	let lane_id = params.lane_id;
@@ -176,8 +191,10 @@ pub async fn run(
 		message_lane: SubstrateMessageLaneToSubstrate {
 			source_client: source_client.clone(),
 			source_sign: params.source_sign,
+			source_transactions_mortality: params.source_transactions_mortality,
 			target_client: params.target_client.clone(),
 			target_sign: params.target_sign,
+			target_transactions_mortality: params.target_transactions_mortality,
 			relayer_id_at_source: relayer_id_at_rialto,
 		},
 	};
@@ -199,12 +216,17 @@ pub async fn run(
 			Max messages in single transaction: {}\n\t\
 			Max messages size in single transaction: {}\n\t\
 			Max messages weight in single transaction: {}\n\t\
-			Relayer mode: {:?}",
+			Relayer mode: {:?}\n\t\
+			Tx mortality: {:?}/{:?}\n\t\
+			Stall timeout: {:?}",
 		lane.message_lane.relayer_id_at_source,
 		max_messages_in_single_batch,
 		max_messages_size_in_single_batch,
 		max_messages_weight_in_single_batch,
 		params.relayer_mode,
+		params.source_transactions_mortality,
+		params.target_transactions_mortality,
+		stall_timeout,
 	);
 
 	let (metrics_params, metrics_values) = add_standalone_metrics(
diff --git a/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs b/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs
index 65eda19c6ef..7f9a2eb98f0 100644
--- a/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs
+++ b/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs
@@ -16,7 +16,7 @@
 
 //! Rococo-to-Wococo messages sync entrypoint.
 
-use std::{ops::RangeInclusive, time::Duration};
+use std::ops::RangeInclusive;
 
 use codec::Encode;
 use sp_core::{Bytes, Pair};
@@ -40,6 +40,7 @@ use substrate_relay_helper::{
 	},
 	messages_source::SubstrateMessagesSource,
 	messages_target::SubstrateMessagesTarget,
+	STALL_TIMEOUT,
 };
 
 /// Rococo-to-Wococo message lane.
@@ -88,6 +89,7 @@ impl SubstrateMessageLane for RococoMessagesToWococo {
 
 	fn make_messages_receiving_proof_transaction(
 		&self,
+		best_block_id: RococoHeaderId,
 		transaction_nonce: IndexOf<Rococo>,
 		_generated_at_block: WococoHeaderId,
 		proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
@@ -103,7 +105,10 @@ impl SubstrateMessageLane for RococoMessagesToWococo {
 		let transaction = Rococo::sign_transaction(
 			genesis_hash,
 			&self.message_lane.source_sign,
-			relay_substrate_client::TransactionEra::immortal(),
+			relay_substrate_client::TransactionEra::new(
+				best_block_id,
+				self.message_lane.source_transactions_mortality,
+			),
 			UnsignedTransaction::new(call, transaction_nonce),
 		);
 		log::trace!(
@@ -122,6 +127,7 @@ impl SubstrateMessageLane for RococoMessagesToWococo {
 
 	fn make_messages_delivery_transaction(
 		&self,
+		best_block_id: WococoHeaderId,
 		transaction_nonce: IndexOf<Wococo>,
 		_generated_at_header: RococoHeaderId,
 		_nonces: RangeInclusive<MessageNonce>,
@@ -143,7 +149,10 @@ impl SubstrateMessageLane for RococoMessagesToWococo {
 		let transaction = Wococo::sign_transaction(
 			genesis_hash,
 			&self.message_lane.target_sign,
-			relay_substrate_client::TransactionEra::immortal(),
+			relay_substrate_client::TransactionEra::new(
+				best_block_id,
+				self.message_lane.target_transactions_mortality,
+			),
 			UnsignedTransaction::new(call, transaction_nonce),
 		);
 		log::trace!(
@@ -167,7 +176,13 @@ type WococoTargetClient = SubstrateMessagesTarget<RococoMessagesToWococo>;
 pub async fn run(
 	params: MessagesRelayParams<Rococo, RococoSigningParams, Wococo, WococoSigningParams>,
 ) -> anyhow::Result<()> {
-	let stall_timeout = Duration::from_secs(5 * 60);
+	let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
+		params.source_transactions_mortality,
+		params.target_transactions_mortality,
+		Rococo::AVERAGE_BLOCK_INTERVAL,
+		Wococo::AVERAGE_BLOCK_INTERVAL,
+		STALL_TIMEOUT,
+	);
 	let relayer_id_at_rococo = (*params.source_sign.public().as_array_ref()).into();
 
 	let lane_id = params.lane_id;
@@ -176,8 +191,10 @@ pub async fn run(
 		message_lane: SubstrateMessageLaneToSubstrate {
 			source_client: source_client.clone(),
 			source_sign: params.source_sign,
+			source_transactions_mortality: params.source_transactions_mortality,
 			target_client: params.target_client.clone(),
 			target_sign: params.target_sign,
+			target_transactions_mortality: params.target_transactions_mortality,
 			relayer_id_at_source: relayer_id_at_rococo,
 		},
 	};
@@ -203,12 +220,17 @@ pub async fn run(
 			Max messages in single transaction: {}\n\t\
 			Max messages size in single transaction: {}\n\t\
 			Max messages weight in single transaction: {}\n\t\
-			Relayer mode: {:?}",
+			Relayer mode: {:?}\n\t\
+			Tx mortality: {:?}/{:?}\n\t\
+			Stall timeout: {:?}",
 		lane.message_lane.relayer_id_at_source,
 		max_messages_in_single_batch,
 		max_messages_size_in_single_batch,
 		max_messages_weight_in_single_batch,
 		params.relayer_mode,
+		params.source_transactions_mortality,
+		params.target_transactions_mortality,
+		stall_timeout,
 	);
 
 	let (metrics_params, metrics_values) = add_standalone_metrics(
diff --git a/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs b/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs
index d380e6c29a4..420b9465060 100644
--- a/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs
+++ b/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs
@@ -16,7 +16,7 @@
 
 //! Wococo-to-Rococo messages sync entrypoint.
 
-use std::{ops::RangeInclusive, time::Duration};
+use std::ops::RangeInclusive;
 
 use codec::Encode;
 use sp_core::{Bytes, Pair};
@@ -40,6 +40,7 @@ use substrate_relay_helper::{
 	},
 	messages_source::SubstrateMessagesSource,
 	messages_target::SubstrateMessagesTarget,
+	STALL_TIMEOUT,
 };
 
 /// Wococo-to-Rococo message lane.
@@ -87,6 +88,7 @@ impl SubstrateMessageLane for WococoMessagesToRococo {
 
 	fn make_messages_receiving_proof_transaction(
 		&self,
+		best_block_id: WococoHeaderId,
 		transaction_nonce: IndexOf<Wococo>,
 		_generated_at_block: RococoHeaderId,
 		proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
@@ -102,7 +104,10 @@ impl SubstrateMessageLane for WococoMessagesToRococo {
 		let transaction = Wococo::sign_transaction(
 			genesis_hash,
 			&self.message_lane.source_sign,
-			relay_substrate_client::TransactionEra::immortal(),
+			relay_substrate_client::TransactionEra::new(
+				best_block_id,
+				self.message_lane.source_transactions_mortality,
+			),
 			UnsignedTransaction::new(call, transaction_nonce),
 		);
 		log::trace!(
@@ -121,6 +126,7 @@ impl SubstrateMessageLane for WococoMessagesToRococo {
 
 	fn make_messages_delivery_transaction(
 		&self,
+		best_block_id: WococoHeaderId,
 		transaction_nonce: IndexOf<Rococo>,
 		_generated_at_header: WococoHeaderId,
 		_nonces: RangeInclusive<MessageNonce>,
@@ -142,7 +148,10 @@ impl SubstrateMessageLane for WococoMessagesToRococo {
 		let transaction = Rococo::sign_transaction(
 			genesis_hash,
 			&self.message_lane.target_sign,
-			relay_substrate_client::TransactionEra::immortal(),
+			relay_substrate_client::TransactionEra::new(
+				best_block_id,
+				self.message_lane.target_transactions_mortality,
+			),
 			UnsignedTransaction::new(call, transaction_nonce),
 		);
 		log::trace!(
@@ -166,7 +175,13 @@ type RococoTargetClient = SubstrateMessagesTarget<WococoMessagesToRococo>;
 pub async fn run(
 	params: MessagesRelayParams<Wococo, WococoSigningParams, Rococo, RococoSigningParams>,
 ) -> anyhow::Result<()> {
-	let stall_timeout = Duration::from_secs(5 * 60);
+	let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
+		params.source_transactions_mortality,
+		params.target_transactions_mortality,
+		Wococo::AVERAGE_BLOCK_INTERVAL,
+		Rococo::AVERAGE_BLOCK_INTERVAL,
+		STALL_TIMEOUT,
+	);
 	let relayer_id_at_wococo = (*params.source_sign.public().as_array_ref()).into();
 
 	let lane_id = params.lane_id;
@@ -175,8 +190,10 @@ pub async fn run(
 		message_lane: SubstrateMessageLaneToSubstrate {
 			source_client: source_client.clone(),
 			source_sign: params.source_sign,
+			source_transactions_mortality: params.source_transactions_mortality,
 			target_client: params.target_client.clone(),
 			target_sign: params.target_sign,
+			target_transactions_mortality: params.target_transactions_mortality,
 			relayer_id_at_source: relayer_id_at_wococo,
 		},
 	};
@@ -202,12 +219,17 @@ pub async fn run(
 			Max messages in single transaction: {}\n\t\
 			Max messages size in single transaction: {}\n\t\
 			Max messages weight in single transaction: {}\n\t\
-			Relayer mode: {:?}",
+			Relayer mode: {:?}\n\t\
+			Tx mortality: {:?}/{:?}\n\t\
+			Stall timeout: {:?}",
 		lane.message_lane.relayer_id_at_source,
 		max_messages_in_single_batch,
 		max_messages_size_in_single_batch,
 		max_messages_weight_in_single_batch,
 		params.relayer_mode,
+		params.source_transactions_mortality,
+		params.target_transactions_mortality,
+		stall_timeout,
 	);
 
 	let (metrics_params, metrics_values) = add_standalone_metrics(
diff --git a/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages.rs b/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages.rs
index 303acf3f125..92343288ede 100644
--- a/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages.rs
+++ b/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages.rs
@@ -512,8 +512,10 @@ impl RelayHeadersAndMessages {
 				let left_to_right_messages = left_to_right_messages(MessagesRelayParams {
 					source_client: left_client.clone(),
 					source_sign: left_sign.clone(),
+					source_transactions_mortality: left_transactions_mortality.clone(),
 					target_client: right_client.clone(),
 					target_sign: right_sign.clone(),
+					target_transactions_mortality: right_transactions_mortality.clone(),
 					source_to_target_headers_relay: Some(left_to_right_on_demand_headers.clone()),
 					target_to_source_headers_relay: Some(right_to_left_on_demand_headers.clone()),
 					lane_id: lane,
@@ -529,8 +531,10 @@ impl RelayHeadersAndMessages {
 				let right_to_left_messages = right_to_left_messages(MessagesRelayParams {
 					source_client: right_client.clone(),
 					source_sign: right_sign.clone(),
+					source_transactions_mortality: right_transactions_mortality.clone(),
 					target_client: left_client.clone(),
 					target_sign: left_sign.clone(),
+					target_transactions_mortality: left_transactions_mortality.clone(),
 					source_to_target_headers_relay: Some(right_to_left_on_demand_headers.clone()),
 					target_to_source_headers_relay: Some(left_to_right_on_demand_headers.clone()),
 					lane_id: lane,
diff --git a/bridges/relays/bin-substrate/src/cli/relay_messages.rs b/bridges/relays/bin-substrate/src/cli/relay_messages.rs
index 3ccf53348d6..fd6875cc2a0 100644
--- a/bridges/relays/bin-substrate/src/cli/relay_messages.rs
+++ b/bridges/relays/bin-substrate/src/cli/relay_messages.rs
@@ -76,14 +76,18 @@ impl RelayMessages {
 		select_full_bridge!(self.bridge, {
 			let source_client = self.source.to_client::<Source>().await?;
 			let source_sign = self.source_sign.to_keypair::<Source>()?;
+			let source_transactions_mortality = self.source_sign.transactions_mortality()?;
 			let target_client = self.target.to_client::<Target>().await?;
 			let target_sign = self.target_sign.to_keypair::<Target>()?;
+			let target_transactions_mortality = self.target_sign.transactions_mortality()?;
 
 			relay_messages(MessagesRelayParams {
 				source_client,
 				source_sign,
+				source_transactions_mortality,
 				target_client,
 				target_sign,
+				target_transactions_mortality,
 				source_to_target_headers_relay: None,
 				target_to_source_headers_relay: None,
 				lane_id: self.lane.into(),
diff --git a/bridges/relays/client-substrate/src/lib.rs b/bridges/relays/client-substrate/src/lib.rs
index 634bdcdca19..1f6606ea287 100644
--- a/bridges/relays/client-substrate/src/lib.rs
+++ b/bridges/relays/client-substrate/src/lib.rs
@@ -69,13 +69,40 @@ impl Default for ConnectionParams {
 ///
 /// Relay considers himself stalled if he has submitted transaction to the node, but it has not
 /// been mined for this period.
-///
-/// Returns `None` if mortality period is `None`
 pub fn transaction_stall_timeout(
 	mortality_period: Option<u32>,
 	average_block_interval: Duration,
-) -> Option<Duration> {
+	default_stall_timeout: Duration,
+) -> Duration {
 	// 1 extra block for transaction to reach the pool && 1 for relayer to awake after it is mined
 	mortality_period
 		.map(|mortality_period| average_block_interval.saturating_mul(mortality_period + 1 + 1))
+		.unwrap_or(default_stall_timeout)
+}
+
+/// Returns stall timeout for relay loop that submit transactions to two chains.
+///
+/// Bidirectional relay may have two active transactions. Even if one of them has been spoiled, we
+/// can't just restart the loop - the other transaction may still be alive and we'll be submitting
+/// duplicate transaction, which may result in funds loss. So we'll be selecting maximal mortality
+/// for choosing loop stall timeout.
+pub fn bidirectional_transaction_stall_timeout(
+	left_mortality_period: Option<u32>,
+	right_mortality_period: Option<u32>,
+	left_average_block_interval: Duration,
+	right_average_block_interval: Duration,
+	default_stall_timeout: Duration,
+) -> Duration {
+	std::cmp::max(
+		transaction_stall_timeout(
+			left_mortality_period,
+			left_average_block_interval,
+			default_stall_timeout,
+		),
+		transaction_stall_timeout(
+			right_mortality_period,
+			right_average_block_interval,
+			default_stall_timeout,
+		),
+	)
 }
diff --git a/bridges/relays/lib-substrate-relay/Cargo.toml b/bridges/relays/lib-substrate-relay/Cargo.toml
index 5be28bccfb3..5bee10856da 100644
--- a/bridges/relays/lib-substrate-relay/Cargo.toml
+++ b/bridges/relays/lib-substrate-relay/Cargo.toml
@@ -32,7 +32,6 @@ pallet-bridge-messages = { path = "../../modules/messages" }
 bp-runtime = { path = "../../primitives/runtime" }
 bp-messages = { path = "../../primitives/messages" }
 
-
 # Substrate Dependencies
 
 frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
diff --git a/bridges/relays/lib-substrate-relay/src/finality_pipeline.rs b/bridges/relays/lib-substrate-relay/src/finality_pipeline.rs
index cca9e5196b3..cdfbb3354d2 100644
--- a/bridges/relays/lib-substrate-relay/src/finality_pipeline.rs
+++ b/bridges/relays/lib-substrate-relay/src/finality_pipeline.rs
@@ -16,7 +16,7 @@
 
 //! Substrate-to-Substrate headers sync entrypoint.
 
-use crate::finality_target::SubstrateFinalityTarget;
+use crate::{finality_target::SubstrateFinalityTarget, STALL_TIMEOUT};
 
 use bp_header_chain::justification::GrandpaJustification;
 use bp_runtime::AccountIdOf;
@@ -26,16 +26,8 @@ use relay_substrate_client::{
 };
 use relay_utils::{metrics::MetricsParams, BlockNumberBase};
 use sp_core::Bytes;
-use std::{fmt::Debug, marker::PhantomData, time::Duration};
+use std::{fmt::Debug, marker::PhantomData};
 
-/// Default synchronization loop timeout. If transactions generated by relay are immortal, then
-/// this timeout is used.
-///
-/// There are no any strict requirements on block time in Substrate. But we assume here that all
-/// Substrate-based chains will be designed to produce relatively fast (compared to the slowest
-/// blockchains) blocks. So 1 hour seems to be a good guess for (even congested) chains to mine
-/// transaction, or remove it from the pool.
-pub(crate) const STALL_TIMEOUT: Duration = Duration::from_secs(60 * 60);
 /// Default limit of recent finality proofs.
 ///
 /// Finality delay of 4096 blocks is unlikely to happen in practice in
@@ -165,8 +157,8 @@ where
 			stall_timeout: relay_substrate_client::transaction_stall_timeout(
 				transactions_mortality,
 				TargetChain::AVERAGE_BLOCK_INTERVAL,
-			)
-			.unwrap_or(STALL_TIMEOUT),
+				STALL_TIMEOUT,
+			),
 			only_mandatory_headers,
 		},
 		metrics_params,
diff --git a/bridges/relays/lib-substrate-relay/src/finality_target.rs b/bridges/relays/lib-substrate-relay/src/finality_target.rs
index 1353eec8727..f50bd103f43 100644
--- a/bridges/relays/lib-substrate-relay/src/finality_target.rs
+++ b/bridges/relays/lib-substrate-relay/src/finality_target.rs
@@ -98,8 +98,7 @@ where
 				move |best_block_id, transaction_nonce| {
 					pipeline.make_submit_finality_proof_transaction(
 						relay_substrate_client::TransactionEra::new(
-							best_block_id.0,
-							best_block_id.1,
+							best_block_id,
 							transactions_mortality,
 						),
 						transaction_nonce,
diff --git a/bridges/relays/lib-substrate-relay/src/lib.rs b/bridges/relays/lib-substrate-relay/src/lib.rs
index dac6b8caa8e..cc066bf501a 100644
--- a/bridges/relays/lib-substrate-relay/src/lib.rs
+++ b/bridges/relays/lib-substrate-relay/src/lib.rs
@@ -18,6 +18,8 @@
 
 #![warn(missing_docs)]
 
+use std::time::Duration;
+
 pub mod conversion_rate_update;
 pub mod error;
 pub mod finality_pipeline;
@@ -28,3 +30,12 @@ pub mod messages_lane;
 pub mod messages_source;
 pub mod messages_target;
 pub mod on_demand_headers;
+
+/// Default relay loop stall timeout. If transactions generated by relay are immortal, then
+/// this timeout is used.
+///
+/// There are no any strict requirements on block time in Substrate. But we assume here that all
+/// Substrate-based chains will be designed to produce relatively fast (compared to the slowest
+/// blockchains) blocks. So 1 hour seems to be a good guess for (even congested) chains to mine
+/// transaction, or remove it from the pool.
+pub const STALL_TIMEOUT: Duration = Duration::from_secs(60 * 60);
diff --git a/bridges/relays/lib-substrate-relay/src/messages_lane.rs b/bridges/relays/lib-substrate-relay/src/messages_lane.rs
index 973f52d60a3..876b2c2f257 100644
--- a/bridges/relays/lib-substrate-relay/src/messages_lane.rs
+++ b/bridges/relays/lib-substrate-relay/src/messages_lane.rs
@@ -44,10 +44,14 @@ pub struct MessagesRelayParams<SC: Chain, SS, TC: Chain, TS> {
 	pub source_client: Client<SC>,
 	/// Sign parameters for messages source chain.
 	pub source_sign: SS,
+	/// Mortality of source transactions.
+	pub source_transactions_mortality: Option<u32>,
 	/// Messages target client.
 	pub target_client: Client<TC>,
 	/// Sign parameters for messages target chain.
 	pub target_sign: TS,
+	/// Mortality of target transactions.
+	pub target_transactions_mortality: Option<u32>,
 	/// Optional on-demand source to target headers relay.
 	pub source_to_target_headers_relay: Option<OnDemandHeadersRelay<SC>>,
 	/// Optional on-demand target to source headers relay.
@@ -113,6 +117,7 @@ pub trait SubstrateMessageLane: 'static + Clone + Send + Sync {
 	/// Make messages delivery transaction.
 	fn make_messages_delivery_transaction(
 		&self,
+		best_block_id: TargetHeaderIdOf<Self::MessageLane>,
 		transaction_nonce: IndexOf<Self::TargetChain>,
 		generated_at_header: SourceHeaderIdOf<Self::MessageLane>,
 		nonces: RangeInclusive<MessageNonce>,
@@ -126,6 +131,7 @@ pub trait SubstrateMessageLane: 'static + Clone + Send + Sync {
 	/// Make messages receiving proof transaction.
 	fn make_messages_receiving_proof_transaction(
 		&self,
+		best_block_id: SourceHeaderIdOf<Self::MessageLane>,
 		transaction_nonce: IndexOf<Self::SourceChain>,
 		generated_at_header: TargetHeaderIdOf<Self::MessageLane>,
 		proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
@@ -144,10 +150,14 @@ pub struct SubstrateMessageLaneToSubstrate<
 	pub source_client: Client<Source>,
 	/// Parameters required to sign transactions for source chain.
 	pub source_sign: SourceSignParams,
+	/// Source transactions mortality.
+	pub source_transactions_mortality: Option<u32>,
 	/// Client for the target Substrate chain.
 	pub target_client: Client<Target>,
 	/// Parameters required to sign transactions for target chain.
 	pub target_sign: TargetSignParams,
+	/// Target transactions mortality.
+	pub target_transactions_mortality: Option<u32>,
 	/// Account id of relayer at the source chain.
 	pub relayer_id_at_source: Source::AccountId,
 }
@@ -159,8 +169,10 @@ impl<Source: Chain, SourceSignParams: Clone, Target: Chain, TargetSignParams: Cl
 		Self {
 			source_client: self.source_client.clone(),
 			source_sign: self.source_sign.clone(),
+			source_transactions_mortality: self.source_transactions_mortality,
 			target_client: self.target_client.clone(),
 			target_sign: self.target_sign.clone(),
+			target_transactions_mortality: self.target_transactions_mortality,
 			relayer_id_at_source: self.relayer_id_at_source.clone(),
 		}
 	}
diff --git a/bridges/relays/lib-substrate-relay/src/messages_source.rs b/bridges/relays/lib-substrate-relay/src/messages_source.rs
index c450144a9f6..5f066296e7e 100644
--- a/bridges/relays/lib-substrate-relay/src/messages_source.rs
+++ b/bridges/relays/lib-substrate-relay/src/messages_source.rs
@@ -245,8 +245,9 @@ where
 		self.client
 			.submit_signed_extrinsic(
 				self.lane.source_transactions_author(),
-				move |_, transaction_nonce| {
+				move |best_block_id, transaction_nonce| {
 					lane.make_messages_receiving_proof_transaction(
+						best_block_id,
 						transaction_nonce,
 						generated_at_block,
 						proof,
@@ -268,6 +269,7 @@ where
 	) -> <P::MessageLane as MessageLane>::SourceChainBalance {
 		self.client
 			.estimate_extrinsic_fee(self.lane.make_messages_receiving_proof_transaction(
+				HeaderId(Default::default(), Default::default()),
 				Zero::zero(),
 				HeaderId(Default::default(), Default::default()),
 				prepare_dummy_messages_delivery_proof::<P::SourceChain, P::TargetChain>(),
diff --git a/bridges/relays/lib-substrate-relay/src/messages_target.rs b/bridges/relays/lib-substrate-relay/src/messages_target.rs
index f7b911f2c02..6f95ffd12f0 100644
--- a/bridges/relays/lib-substrate-relay/src/messages_target.rs
+++ b/bridges/relays/lib-substrate-relay/src/messages_target.rs
@@ -227,8 +227,9 @@ where
 		self.client
 			.submit_signed_extrinsic(
 				self.lane.target_transactions_author(),
-				move |_, transaction_nonce| {
+				move |best_block_id, transaction_nonce| {
 					lane.make_messages_delivery_transaction(
+						best_block_id,
 						transaction_nonce,
 						generated_at_header,
 						nonces_clone,
@@ -264,6 +265,7 @@ where
 
 		// Prepare 'dummy' delivery transaction - we only care about its length and dispatch weight.
 		let delivery_tx = self.lane.make_messages_delivery_transaction(
+			HeaderId(Default::default(), Default::default()),
 			Zero::zero(),
 			HeaderId(Default::default(), Default::default()),
 			nonces.clone(),
@@ -299,6 +301,7 @@ where
 			let larger_delivery_tx_fee = self
 				.client
 				.estimate_extrinsic_fee(self.lane.make_messages_delivery_transaction(
+					HeaderId(Default::default(), Default::default()),
 					Zero::zero(),
 					HeaderId(Default::default(), Default::default()),
 					nonces.clone(),
@@ -473,6 +476,7 @@ mod tests {
 
 		fn make_messages_receiving_proof_transaction(
 			&self,
+			_best_block_id: SourceHeaderIdOf<Self::MessageLane>,
 			_transaction_nonce: IndexOf<Rococo>,
 			_generated_at_block: TargetHeaderIdOf<Self::MessageLane>,
 			_proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
@@ -486,6 +490,7 @@ mod tests {
 
 		fn make_messages_delivery_transaction(
 			&self,
+			_best_block_id: TargetHeaderIdOf<Self::MessageLane>,
 			_transaction_nonce: IndexOf<Wococo>,
 			_generated_at_header: SourceHeaderIdOf<Self::MessageLane>,
 			_nonces: RangeInclusive<MessageNonce>,
diff --git a/bridges/relays/lib-substrate-relay/src/on_demand_headers.rs b/bridges/relays/lib-substrate-relay/src/on_demand_headers.rs
index 4dad20579a9..ee141866eb9 100644
--- a/bridges/relays/lib-substrate-relay/src/on_demand_headers.rs
+++ b/bridges/relays/lib-substrate-relay/src/on_demand_headers.rs
@@ -38,9 +38,9 @@ use relay_utils::{
 use crate::{
 	finality_pipeline::{
 		SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate, RECENT_FINALITY_PROOFS_LIMIT,
-		STALL_TIMEOUT,
 	},
 	finality_target::SubstrateFinalityTarget,
+	STALL_TIMEOUT,
 };
 
 /// On-demand Substrate <-> Substrate headers relay.
diff --git a/bridges/relays/utils/Cargo.toml b/bridges/relays/utils/Cargo.toml
index 652ff540028..a08c3b3d688 100644
--- a/bridges/relays/utils/Cargo.toml
+++ b/bridges/relays/utils/Cargo.toml
@@ -22,6 +22,10 @@ sysinfo = "0.15"
 time = "0.2"
 thiserror = "1.0.26"
 
+# Bridge dependencies
+
+bp-runtime = { path = "../../primitives/runtime" }
+
 # Substrate dependencies
 
 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 936e9ba6815..a335be79124 100644
--- a/bridges/relays/utils/src/lib.rs
+++ b/bridges/relays/utils/src/lib.rs
@@ -16,6 +16,7 @@
 
 //! Utilities used by different relays.
 
+pub use bp_runtime::HeaderId;
 pub use error::Error;
 pub use relay_loop::{relay_loop, relay_metrics};
 
@@ -103,10 +104,6 @@ macro_rules! bail_on_arg_error {
 	};
 }
 
-/// Ethereum header Id.
-#[derive(Debug, Default, Clone, Copy, Eq, Hash, PartialEq)]
-pub struct HeaderId<Hash, Number>(pub Number, pub Hash);
-
 /// Error type that can signal connection errors.
 pub trait MaybeConnectionError {
 	/// Returns true if error (maybe) represents connection error.
-- 
GitLab