From 418942826d58220620e23edfcf01c80cb24e7b52 Mon Sep 17 00:00:00 2001
From: Svyatoslav Nikolsky <svyatonik@gmail.com>
Date: Thu, 16 Jun 2022 15:18:34 +0300
Subject: [PATCH] Separate signers for different complex relay layers (#1465)

* separate accounts for different complex relay layers

* fix clippy issue

* cleanup + expose relay accounts balance metrics

* expose messages pallet owner balance metric

* use new metrics in maintenance dashboard

* tests + use separate accounts to sign RialtoHeaders -> Millau transactions in RialtoParachain<>Millau bridge

* clippy + fmt + spellcheck
---
 bridges/bin/millau/node/src/chain_spec.rs     |   2 +
 bridges/relays/bin-substrate/src/cli/mod.rs   |  42 +-
 .../src/cli/relay_headers_and_messages.rs     | 450 +++++++++++++++---
 bridges/relays/lib-substrate-relay/src/lib.rs |  58 +++
 .../src/messages_metrics.rs                   |  27 +-
 5 files changed, 498 insertions(+), 81 deletions(-)

diff --git a/bridges/bin/millau/node/src/chain_spec.rs b/bridges/bin/millau/node/src/chain_spec.rs
index 2b80b4aac81..3204173598a 100644
--- a/bridges/bin/millau/node/src/chain_spec.rs
+++ b/bridges/bin/millau/node/src/chain_spec.rs
@@ -143,6 +143,7 @@ fn endowed_accounts() -> Vec<AccountId> {
 		get_account_id_from_seed::<sr25519::Public>("George"),
 		get_account_id_from_seed::<sr25519::Public>("Harry"),
 		get_account_id_from_seed::<sr25519::Public>("Iden"),
+		get_account_id_from_seed::<sr25519::Public>("Ken"),
 		get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
 		get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
 		get_account_id_from_seed::<sr25519::Public>("Charlie//stash"),
@@ -152,6 +153,7 @@ fn endowed_accounts() -> Vec<AccountId> {
 		get_account_id_from_seed::<sr25519::Public>("George//stash"),
 		get_account_id_from_seed::<sr25519::Public>("Harry//stash"),
 		get_account_id_from_seed::<sr25519::Public>("Iden//stash"),
+		get_account_id_from_seed::<sr25519::Public>("Ken//stash"),
 		get_account_id_from_seed::<sr25519::Public>("RialtoMessagesOwner"),
 		get_account_id_from_seed::<sr25519::Public>("RialtoParachainMessagesOwner"),
 		pallet_bridge_messages::relayer_fund_account_id::<
diff --git a/bridges/relays/bin-substrate/src/cli/mod.rs b/bridges/relays/bin-substrate/src/cli/mod.rs
index 4748e42e3f3..5061c70eb78 100644
--- a/bridges/relays/bin-substrate/src/cli/mod.rs
+++ b/bridges/relays/bin-substrate/src/cli/mod.rs
@@ -22,6 +22,7 @@ use codec::{Decode, Encode};
 use relay_substrate_client::ChainRuntimeVersion;
 use structopt::{clap::arg_enum, StructOpt};
 use strum::{EnumString, EnumVariantNames};
+use substrate_relay_helper::TransactionParams;
 
 use bp_messages::LaneId;
 
@@ -64,7 +65,7 @@ pub enum Command {
 	/// and two `RelayMessages` relays. Headers are only relayed when they are required by
 	/// the message relays - i.e. when there are messages or confirmations that needs to be
 	/// relayed between chains.
-	RelayHeadersAndMessages(relay_headers_and_messages::RelayHeadersAndMessages),
+	RelayHeadersAndMessages(Box<relay_headers_and_messages::RelayHeadersAndMessages>),
 	/// Initialize on-chain bridge pallet with current header data.
 	///
 	/// Sends initialization transaction to bootstrap the bridge with current finalized block data.
@@ -238,7 +239,7 @@ impl HexBytes {
 }
 
 /// Prometheus metrics params.
-#[derive(Clone, Debug, StructOpt)]
+#[derive(Clone, Debug, PartialEq, StructOpt)]
 pub struct PrometheusParams {
 	/// Do not expose a Prometheus metric endpoint.
 	#[structopt(long)]
@@ -302,6 +303,29 @@ pub enum RuntimeVersionType {
 	Bundle,
 }
 
+/// Helper trait to override transaction parameters differently.
+pub trait TransactionParamsProvider {
+	/// Returns `true` if transaction parameters are defined by this provider.
+	fn is_defined(&self) -> bool;
+	/// Returns transaction parameters.
+	fn transaction_params<Chain: CliChain>(
+		&self,
+	) -> anyhow::Result<TransactionParams<Chain::KeyPair>>;
+
+	/// Returns transaction parameters, defined by `self` provider or, if they're not defined,
+	/// defined by `other` provider.
+	fn transaction_params_or<Chain: CliChain, T: TransactionParamsProvider>(
+		&self,
+		other: &T,
+	) -> anyhow::Result<TransactionParams<Chain::KeyPair>> {
+		if self.is_defined() {
+			self.transaction_params::<Chain>()
+		} else {
+			other.transaction_params::<Chain>()
+		}
+	}
+}
+
 /// Create chain-specific set of configuration objects: connection parameters,
 /// signing parameters and bridge initialization parameters.
 #[macro_export]
@@ -434,6 +458,20 @@ macro_rules! declare_chain_options {
 				}
 			}
 
+			#[allow(dead_code)]
+			impl TransactionParamsProvider for [<$chain SigningParams>] {
+				fn is_defined(&self) -> bool {
+					self.[<$chain_prefix _signer>].is_some() || self.[<$chain_prefix _signer_file>].is_some()
+				}
+
+				fn transaction_params<Chain: CliChain>(&self) -> anyhow::Result<TransactionParams<Chain::KeyPair>> {
+					Ok(TransactionParams {
+						mortality: self.transactions_mortality()?,
+						signer: self.to_keypair::<Chain>()?,
+					})
+				}
+			}
+
 			#[allow(dead_code)]
 			impl [<$chain MessagesPalletOwnerSigningParams>] {
 				/// Parse signing params into chain-specific KeyPair.
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 923d82c11f4..51c863080fd 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
@@ -44,11 +44,14 @@ use substrate_relay_helper::{
 		headers::OnDemandHeadersRelay, parachains::OnDemandParachainsRelay, OnDemandRelay,
 	},
 	parachains::SubstrateParachainsPipeline,
-	TransactionParams,
+	TaggedAccount, TransactionParams,
 };
 
 use crate::{
-	cli::{relay_messages::RelayerMode, CliChain, HexLaneId, PrometheusParams, RuntimeVersionType},
+	cli::{
+		relay_messages::RelayerMode, CliChain, HexLaneId, PrometheusParams, RuntimeVersionType,
+		TransactionParamsProvider,
+	},
 	declare_chain_options,
 };
 
@@ -61,7 +64,7 @@ use crate::{
 pub(crate) const CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO: f64 = 0.05;
 
 /// Start headers+messages relayer process.
-#[derive(StructOpt)]
+#[derive(Debug, PartialEq, StructOpt)]
 pub enum RelayHeadersAndMessages {
 	MillauRialto(MillauRialtoHeadersAndMessages),
 	MillauRialtoParachain(MillauRialtoParachainHeadersAndMessages),
@@ -70,7 +73,7 @@ pub enum RelayHeadersAndMessages {
 }
 
 /// Parameters that have the same names across all bridges.
-#[derive(StructOpt)]
+#[derive(Debug, PartialEq, StructOpt)]
 pub struct HeadersAndMessagesSharedParams {
 	/// Hex-encoded lane identifiers that should be served by the complex relay.
 	#[structopt(long, default_value = "00000000")]
@@ -95,21 +98,32 @@ macro_rules! declare_bridge_options {
 	// chain, parachain, relay-chain-of-parachain
 	($chain1:ident, $chain2:ident, $chain3:ident) => {
 		paste::item! {
-			#[doc = $chain1 ", " $chain2 " and " $chain3 " headers+messages relay params."]
-			#[derive(StructOpt)]
+			#[doc = $chain1 ", " $chain2 " and " $chain3 " headers+parachains+messages relay params."]
+			#[derive(Debug, PartialEq, StructOpt)]
 			pub struct [<$chain1 $chain2 HeadersAndMessages>] {
 				#[structopt(flatten)]
 				shared: HeadersAndMessagesSharedParams,
 				#[structopt(flatten)]
 				left: [<$chain1 ConnectionParams>],
+				// default signer, which is always used to sign messages relay transactions on the left chain
 				#[structopt(flatten)]
 				left_sign: [<$chain1 SigningParams>],
+				// override for right_relay->left headers signer
+				#[structopt(flatten)]
+				right_relay_headers_to_left_sign_override: [<$chain3 HeadersTo $chain1 SigningParams>],
+				// override for right->left parachains signer
+				#[structopt(flatten)]
+				right_parachains_to_left_sign_override: [<$chain3 ParachainsTo $chain1 SigningParams>],
 				#[structopt(flatten)]
 				left_messages_pallet_owner: [<$chain1 MessagesPalletOwnerSigningParams>],
 				#[structopt(flatten)]
 				right: [<$chain2 ConnectionParams>],
+				// default signer, which is always used to sign messages relay transactions on the right chain
 				#[structopt(flatten)]
 				right_sign: [<$chain2 SigningParams>],
+				// override for left->right headers signer
+				#[structopt(flatten)]
+				left_headers_to_right_sign_override: [<$chain1 HeadersTo $chain2 SigningParams>],
 				#[structopt(flatten)]
 				right_messages_pallet_owner: [<$chain2 MessagesPalletOwnerSigningParams>],
 				#[structopt(flatten)]
@@ -122,18 +136,26 @@ macro_rules! declare_bridge_options {
 	($chain1:ident, $chain2:ident) => {
 		paste::item! {
 			#[doc = $chain1 " and " $chain2 " headers+messages relay params."]
-			#[derive(StructOpt)]
+			#[derive(Debug, PartialEq, StructOpt)]
 			pub struct [<$chain1 $chain2 HeadersAndMessages>] {
 				#[structopt(flatten)]
 				shared: HeadersAndMessagesSharedParams,
+				// default signer, which is always used to sign messages relay transactions on the left chain
 				#[structopt(flatten)]
 				left: [<$chain1 ConnectionParams>],
+				// override for right->left headers signer
+				#[structopt(flatten)]
+				right_headers_to_left_sign_override: [<$chain2 HeadersTo $chain1 SigningParams>],
 				#[structopt(flatten)]
 				left_sign: [<$chain1 SigningParams>],
 				#[structopt(flatten)]
 				left_messages_pallet_owner: [<$chain1 MessagesPalletOwnerSigningParams>],
+				// default signer, which is always used to sign messages relay transactions on the right chain
 				#[structopt(flatten)]
 				right: [<$chain2 ConnectionParams>],
+				// override for left->right headers signer
+				#[structopt(flatten)]
+				left_headers_to_right_sign_override: [<$chain1 HeadersTo $chain2 SigningParams>],
 				#[structopt(flatten)]
 				right_sign: [<$chain2 SigningParams>],
 				#[structopt(flatten)]
@@ -178,6 +200,8 @@ macro_rules! select_bridge {
 					params: &Params,
 					left_client: Client<Left>,
 					right_client: Client<Right>,
+					at_left_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<Left>>>,
+					at_right_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<Right>>>,
 				) -> anyhow::Result<(
 					Arc<dyn OnDemandRelay<BlockNumberOf<Left>>>,
 					Arc<dyn OnDemandRelay<BlockNumberOf<Right>>>,
@@ -190,18 +214,14 @@ macro_rules! select_bridge {
 					>(
 						left_client,
 						right_client,
-						TransactionParams {
-							mortality: params.right_sign.transactions_mortality()?,
-							signer: params.right_sign.to_keypair::<Right>()?,
-						},
-						TransactionParams {
-							mortality: params.left_sign.transactions_mortality()?,
-							signer: params.left_sign.to_keypair::<Left>()?,
-						},
+						params.left_headers_to_right_sign_override.transaction_params_or::<Right, _>(&params.right_sign)?,
+						params.right_headers_to_left_sign_override.transaction_params_or::<Left, _>(&params.left_sign)?,
 						params.shared.only_mandatory_headers,
 						params.shared.only_mandatory_headers,
 						params.left.can_start_version_guard(),
 						params.right.can_start_version_guard(),
+						at_left_relay_accounts,
+						at_right_relay_accounts,
 					).await
 				}
 
@@ -241,12 +261,14 @@ macro_rules! select_bridge {
 					params: &Params,
 					left_client: Client<Left>,
 					right_client: Client<Right>,
+					at_left_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<Left>>>,
+					at_right_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<Right>>>,
 				) -> anyhow::Result<(
 					Arc<dyn OnDemandRelay<BlockNumberOf<Left>>>,
 					Arc<dyn OnDemandRelay<BlockNumberOf<Right>>>,
 				)> {
 					type RightRelayChain = relay_rialto_client::Rialto;
-					let rialto_relay_chain_client = params.right_relay.to_client::<RightRelayChain>().await?; // TODO: should be the relaychain connection params
+					let rialto_relay_chain_client = params.right_relay.to_client::<RightRelayChain>().await?;
 
 					start_on_demand_relay_to_parachain::<
 						Left,
@@ -259,18 +281,15 @@ macro_rules! select_bridge {
 						left_client,
 						right_client,
 						rialto_relay_chain_client,
-						TransactionParams {
-							mortality: params.right_sign.transactions_mortality()?,
-							signer: params.right_sign.to_keypair::<Right>()?,
-						},
-						TransactionParams {
-							mortality: params.left_sign.transactions_mortality()?,
-							signer: params.left_sign.to_keypair::<Left>()?,
-						},
+						params.left_headers_to_right_sign_override.transaction_params_or::<Right, _>(&params.right_sign)?,
+						params.right_relay_headers_to_left_sign_override.transaction_params_or::<Left, _>(&params.left_sign)?,
+						params.right_parachains_to_left_sign_override.transaction_params_or::<Left, _>(&params.left_sign)?,
 						params.shared.only_mandatory_headers,
 						params.shared.only_mandatory_headers,
 						params.left.can_start_version_guard(),
 						params.right.can_start_version_guard(),
+						at_left_relay_accounts,
+						at_right_relay_accounts,
 					).await
 				}
 
@@ -310,6 +329,8 @@ macro_rules! select_bridge {
 					params: &Params,
 					left_client: Client<Left>,
 					right_client: Client<Right>,
+					at_left_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<Left>>>,
+					at_right_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<Right>>>,
 				) -> anyhow::Result<(
 					Arc<dyn OnDemandRelay<BlockNumberOf<Left>>>,
 					Arc<dyn OnDemandRelay<BlockNumberOf<Right>>>,
@@ -322,18 +343,14 @@ macro_rules! select_bridge {
 					>(
 						left_client,
 						right_client,
-						TransactionParams {
-							mortality: params.right_sign.transactions_mortality()?,
-							signer: params.right_sign.to_keypair::<Right>()?,
-						},
-						TransactionParams {
-							mortality: params.left_sign.transactions_mortality()?,
-							signer: params.left_sign.to_keypair::<Left>()?,
-						},
+						params.left_headers_to_right_sign_override.transaction_params_or::<Right, _>(&params.right_sign)?,
+						params.right_headers_to_left_sign_override.transaction_params_or::<Left, _>(&params.left_sign)?,
 						params.shared.only_mandatory_headers,
 						params.shared.only_mandatory_headers,
 						params.left.can_start_version_guard(),
 						params.right.can_start_version_guard(),
+						at_left_relay_accounts,
+						at_right_relay_accounts,
 					).await
 				}
 
@@ -393,6 +410,8 @@ macro_rules! select_bridge {
 					params: &Params,
 					left_client: Client<Left>,
 					right_client: Client<Right>,
+					at_left_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<Left>>>,
+					at_right_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<Right>>>,
 				) -> anyhow::Result<(
 					Arc<dyn OnDemandRelay<BlockNumberOf<Left>>>,
 					Arc<dyn OnDemandRelay<BlockNumberOf<Right>>>,
@@ -405,18 +424,14 @@ macro_rules! select_bridge {
 					>(
 						left_client,
 						right_client,
-						TransactionParams {
-							mortality: params.right_sign.transactions_mortality()?,
-							signer: params.right_sign.to_keypair::<Right>()?,
-						},
-						TransactionParams {
-							mortality: params.left_sign.transactions_mortality()?,
-							signer: params.left_sign.to_keypair::<Left>()?,
-						},
+						params.left_headers_to_right_sign_override.transaction_params_or::<Right, _>(&params.right_sign)?,
+						params.right_headers_to_left_sign_override.transaction_params_or::<Left, _>(&params.left_sign)?,
 						params.shared.only_mandatory_headers,
 						params.shared.only_mandatory_headers,
 						params.left.can_start_version_guard(),
 						params.right.can_start_version_guard(),
+						at_left_relay_accounts,
+						at_right_relay_accounts,
 					).await
 				}
 
@@ -470,6 +485,15 @@ declare_chain_options!(Rococo, rococo);
 declare_chain_options!(Wococo, wococo);
 declare_chain_options!(Kusama, kusama);
 declare_chain_options!(Polkadot, polkadot);
+// Means to override signers of different layer transactions.
+declare_chain_options!(MillauHeadersToRialto, millau_headers_to_rialto);
+declare_chain_options!(MillauHeadersToRialtoParachain, millau_headers_to_rialto_parachain);
+declare_chain_options!(RialtoHeadersToMillau, rialto_headers_to_millau);
+declare_chain_options!(RialtoParachainsToMillau, rialto_parachains_to_millau);
+declare_chain_options!(WococoHeadersToRococo, wococo_headers_to_rococo);
+declare_chain_options!(RococoHeadersToWococo, rococo_headers_to_wococo);
+declare_chain_options!(KusamaHeadersToPolkadot, kusama_headers_to_polkadot);
+declare_chain_options!(PolkadotHeadersToKusama, polkadot_headers_to_kusama);
 // All supported bridges.
 declare_bridge_options!(Millau, Rialto);
 declare_bridge_options!(Millau, RialtoParachain, Rialto);
@@ -505,6 +529,14 @@ impl RelayHeadersAndMessages {
 					LeftToRightMessageLane,
 				>(left_client.clone(), right_client.clone())?;
 			let right_to_left_metrics = left_to_right_metrics.clone().reverse();
+			let mut at_left_relay_accounts = vec![TaggedAccount::Messages {
+				id: left_sign.public().into(),
+				bridged_chain: Right::NAME.to_string(),
+			}];
+			let mut at_right_relay_accounts = vec![TaggedAccount::Messages {
+				id: right_sign.public().into(),
+				bridged_chain: Left::NAME.to_string(),
+			}];
 
 			// start conversion rate update loops for left/right chains
 			if let Some(left_messages_pallet_owner) = left_messages_pallet_owner.clone() {
@@ -522,7 +554,7 @@ impl RelayHeadersAndMessages {
 				>(
 					left_client,
 					TransactionParams {
-						signer: left_messages_pallet_owner,
+						signer: left_messages_pallet_owner.clone(),
 						mortality: left_transactions_mortality,
 					},
 					left_to_right_metrics
@@ -542,6 +574,10 @@ impl RelayHeadersAndMessages {
 						.shared_value_ref(),
 					CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
 				);
+				at_left_relay_accounts.push(TaggedAccount::MessagesPalletOwner {
+					id: left_messages_pallet_owner.public().into(),
+					bridged_chain: Right::NAME.to_string(),
+				});
 			}
 			if let Some(right_messages_pallet_owner) = right_messages_pallet_owner.clone() {
 				let right_client = right_client.clone();
@@ -558,7 +594,7 @@ impl RelayHeadersAndMessages {
 				>(
 					right_client,
 					TransactionParams {
-						signer: right_messages_pallet_owner,
+						signer: right_messages_pallet_owner.clone(),
 						mortality: right_transactions_mortality,
 					},
 					right_to_left_metrics
@@ -578,6 +614,10 @@ impl RelayHeadersAndMessages {
 						.shared_value_ref(),
 					CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
 				);
+				at_right_relay_accounts.push(TaggedAccount::MessagesPalletOwner {
+					id: right_messages_pallet_owner.public().into(),
+					bridged_chain: Left::NAME.to_string(),
+				});
 			}
 
 			// optionally, create relayers fund account
@@ -619,27 +659,33 @@ impl RelayHeadersAndMessages {
 				}
 			}
 
+			// start on-demand header relays
+			let (left_to_right_on_demand_headers, right_to_left_on_demand_headers) =
+				start_on_demand_relays(
+					&params,
+					left_client.clone(),
+					right_client.clone(),
+					&mut at_left_relay_accounts,
+					&mut at_right_relay_accounts,
+				)
+				.await?;
+
 			// add balance-related metrics
 			let metrics_params =
 				substrate_relay_helper::messages_metrics::add_relay_balances_metrics(
 					left_client.clone(),
 					metrics_params,
-					Some(left_sign.public().into()),
-					left_messages_pallet_owner.map(|kp| kp.public().into()),
+					at_left_relay_accounts,
 				)
 				.await?;
 			let metrics_params =
 				substrate_relay_helper::messages_metrics::add_relay_balances_metrics(
 					right_client.clone(),
 					metrics_params,
-					Some(right_sign.public().into()),
-					right_messages_pallet_owner.map(|kp| kp.public().into()),
+					at_right_relay_accounts,
 				)
 				.await?;
 
-			// start on-demand header relays
-			let (left_to_right_on_demand_headers, right_to_left_on_demand_headers) =
-				start_on_demand_relays(&params, left_client.clone(), right_client.clone()).await?;
 			// Need 2x capacity since we consider both directions for each lane
 			let mut message_relays = Vec::with_capacity(lanes.len() * 2);
 			for lane in lanes {
@@ -714,13 +760,15 @@ async fn start_on_demand_relay_to_relay<LC, RC, LR, RL>(
 	right_to_left_only_mandatory_headers: bool,
 	left_can_start_version_guard: bool,
 	right_can_start_version_guard: bool,
+	at_left_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<LC>>>,
+	at_right_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<RC>>>,
 ) -> anyhow::Result<(
 	Arc<dyn OnDemandRelay<BlockNumberOf<LC>>>,
 	Arc<dyn OnDemandRelay<BlockNumberOf<RC>>>,
 )>
 where
-	LC: Chain + TransactionSignScheme<Chain = LC>,
-	RC: Chain + TransactionSignScheme<Chain = RC>,
+	LC: Chain + TransactionSignScheme<Chain = LC> + CliChain<KeyPair = AccountKeyPairOf<LC>>,
+	RC: Chain + TransactionSignScheme<Chain = RC> + CliChain<KeyPair = AccountKeyPairOf<RC>>,
 	LR: SubstrateFinalitySyncPipeline<
 		SourceChain = LC,
 		TargetChain = RC,
@@ -734,6 +782,15 @@ where
 	AccountIdOf<LC>: From<<<LC as TransactionSignScheme>::AccountKeyPair as Pair>::Public>,
 	AccountIdOf<RC>: From<<<RC as TransactionSignScheme>::AccountKeyPair as Pair>::Public>,
 {
+	at_left_relay_accounts.push(TaggedAccount::Headers {
+		id: right_to_left_transaction_params.signer.public().into(),
+		bridged_chain: RC::NAME.to_string(),
+	});
+	at_right_relay_accounts.push(TaggedAccount::Headers {
+		id: left_to_right_transaction_params.signer.public().into(),
+		bridged_chain: LC::NAME.to_string(),
+	});
+
 	LR::start_relay_guards(
 		&right_client,
 		&left_to_right_transaction_params,
@@ -768,21 +825,27 @@ async fn start_on_demand_relay_to_parachain<LC, RC, RRC, LR, RRF, RL>(
 	left_client: Client<LC>,
 	right_client: Client<RC>,
 	right_relay_client: Client<RRC>,
-	left_to_right_transaction_params: TransactionParams<AccountKeyPairOf<RC>>,
-	right_to_left_transaction_params: TransactionParams<AccountKeyPairOf<LC>>,
+	left_headers_to_right_transaction_params: TransactionParams<AccountKeyPairOf<RC>>,
+	right_headers_to_left_transaction_params: TransactionParams<AccountKeyPairOf<LC>>,
+	right_parachains_to_left_transaction_params: TransactionParams<AccountKeyPairOf<LC>>,
 	left_to_right_only_mandatory_headers: bool,
 	right_to_left_only_mandatory_headers: bool,
 	left_can_start_version_guard: bool,
 	right_can_start_version_guard: bool,
+	at_left_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<LC>>>,
+	at_right_relay_accounts: &mut Vec<TaggedAccount<AccountIdOf<RC>>>,
 ) -> anyhow::Result<(
 	Arc<dyn OnDemandRelay<BlockNumberOf<LC>>>,
 	Arc<dyn OnDemandRelay<BlockNumberOf<RC>>>,
 )>
 where
-	LC: Chain + TransactionSignScheme<Chain = LC>,
-	RC: Chain<Hash = ParaHash> + TransactionSignScheme<Chain = RC>,
+	LC: Chain + TransactionSignScheme<Chain = LC> + CliChain<KeyPair = AccountKeyPairOf<LC>>,
+	RC: Chain<Hash = ParaHash>
+		+ TransactionSignScheme<Chain = RC>
+		+ CliChain<KeyPair = AccountKeyPairOf<RC>>,
 	RRC: Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash, Hasher = RelayBlockHasher>
-		+ TransactionSignScheme<Chain = RRC>,
+		+ TransactionSignScheme<Chain = RRC>
+		+ CliChain<KeyPair = AccountKeyPairOf<RRC>>,
 	LR: SubstrateFinalitySyncPipeline<
 		SourceChain = LC,
 		TargetChain = RC,
@@ -802,34 +865,47 @@ where
 	AccountIdOf<LC>: From<<<LC as TransactionSignScheme>::AccountKeyPair as Pair>::Public>,
 	AccountIdOf<RC>: From<<<RC as TransactionSignScheme>::AccountKeyPair as Pair>::Public>,
 {
+	at_left_relay_accounts.push(TaggedAccount::Headers {
+		id: right_headers_to_left_transaction_params.signer.public().into(),
+		bridged_chain: RRC::NAME.to_string(),
+	});
+	at_left_relay_accounts.push(TaggedAccount::Parachains {
+		id: right_parachains_to_left_transaction_params.signer.public().into(),
+		bridged_chain: RRC::NAME.to_string(),
+	});
+	at_right_relay_accounts.push(TaggedAccount::Headers {
+		id: left_headers_to_right_transaction_params.signer.public().into(),
+		bridged_chain: LC::NAME.to_string(),
+	});
+
 	LR::start_relay_guards(
 		&right_client,
-		&left_to_right_transaction_params,
+		&left_headers_to_right_transaction_params,
 		right_can_start_version_guard,
 	)
 	.await?;
 	RRF::start_relay_guards(
 		&left_client,
-		&right_to_left_transaction_params,
+		&right_headers_to_left_transaction_params,
 		left_can_start_version_guard,
 	)
 	.await?;
 	let left_to_right_on_demand_headers = OnDemandHeadersRelay::new::<LR>(
 		left_client.clone(),
 		right_client,
-		left_to_right_transaction_params,
+		left_headers_to_right_transaction_params,
 		left_to_right_only_mandatory_headers,
 	);
 	let right_relay_to_left_on_demand_headers = OnDemandHeadersRelay::new::<RRF>(
 		right_relay_client.clone(),
 		left_client.clone(),
-		right_to_left_transaction_params.clone(),
+		right_headers_to_left_transaction_params,
 		right_to_left_only_mandatory_headers,
 	);
 	let right_to_left_on_demand_parachains = OnDemandParachainsRelay::new::<RL>(
 		right_relay_client,
 		left_client,
-		right_to_left_transaction_params,
+		right_parachains_to_left_transaction_params,
 		Arc::new(right_relay_to_left_on_demand_headers),
 	);
 
@@ -866,3 +942,255 @@ where
 		.map(drop)
 		.map_err(|e| anyhow::format_err!("{}", e))
 }
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+
+	#[test]
+	fn should_parse_relay_to_relay_options() {
+		// when
+		let res = RelayHeadersAndMessages::from_iter(vec![
+			"relay-headers-and-messages",
+			"millau-rialto",
+			"--millau-host",
+			"millau-node-alice",
+			"--millau-port",
+			"9944",
+			"--millau-signer",
+			"//Charlie",
+			"--millau-messages-pallet-owner",
+			"//RialtoMessagesOwner",
+			"--millau-transactions-mortality",
+			"64",
+			"--rialto-host",
+			"rialto-node-alice",
+			"--rialto-port",
+			"9944",
+			"--rialto-signer",
+			"//Charlie",
+			"--rialto-messages-pallet-owner",
+			"//MillauMessagesOwner",
+			"--rialto-transactions-mortality",
+			"64",
+			"--lane",
+			"00000000",
+			"--lane",
+			"73776170",
+			"--prometheus-host",
+			"0.0.0.0",
+		]);
+
+		// then
+		assert_eq!(
+			res,
+			RelayHeadersAndMessages::MillauRialto(MillauRialtoHeadersAndMessages {
+				shared: HeadersAndMessagesSharedParams {
+					lane: vec![
+						HexLaneId([0x00, 0x00, 0x00, 0x00]),
+						HexLaneId([0x73, 0x77, 0x61, 0x70])
+					],
+					relayer_mode: RelayerMode::Rational,
+					create_relayers_fund_accounts: false,
+					only_mandatory_headers: false,
+					prometheus_params: PrometheusParams {
+						no_prometheus: false,
+						prometheus_host: "0.0.0.0".into(),
+						prometheus_port: 9616,
+					},
+				},
+				left: MillauConnectionParams {
+					millau_host: "millau-node-alice".into(),
+					millau_port: 9944,
+					millau_secure: false,
+					millau_runtime_version: MillauRuntimeVersionParams {
+						millau_version_mode: RuntimeVersionType::Bundle,
+						millau_spec_version: None,
+						millau_transaction_version: None,
+					},
+				},
+				left_sign: MillauSigningParams {
+					millau_signer: Some("//Charlie".into()),
+					millau_signer_password: None,
+					millau_signer_file: None,
+					millau_signer_password_file: None,
+					millau_transactions_mortality: Some(64),
+				},
+				left_messages_pallet_owner: MillauMessagesPalletOwnerSigningParams {
+					millau_messages_pallet_owner: Some("//RialtoMessagesOwner".into()),
+					millau_messages_pallet_owner_password: None,
+				},
+				left_headers_to_right_sign_override: MillauHeadersToRialtoSigningParams {
+					millau_headers_to_rialto_signer: None,
+					millau_headers_to_rialto_signer_password: None,
+					millau_headers_to_rialto_signer_file: None,
+					millau_headers_to_rialto_signer_password_file: None,
+					millau_headers_to_rialto_transactions_mortality: None,
+				},
+				right: RialtoConnectionParams {
+					rialto_host: "rialto-node-alice".into(),
+					rialto_port: 9944,
+					rialto_secure: false,
+					rialto_runtime_version: RialtoRuntimeVersionParams {
+						rialto_version_mode: RuntimeVersionType::Bundle,
+						rialto_spec_version: None,
+						rialto_transaction_version: None,
+					},
+				},
+				right_sign: RialtoSigningParams {
+					rialto_signer: Some("//Charlie".into()),
+					rialto_signer_password: None,
+					rialto_signer_file: None,
+					rialto_signer_password_file: None,
+					rialto_transactions_mortality: Some(64),
+				},
+				right_messages_pallet_owner: RialtoMessagesPalletOwnerSigningParams {
+					rialto_messages_pallet_owner: Some("//MillauMessagesOwner".into()),
+					rialto_messages_pallet_owner_password: None,
+				},
+				right_headers_to_left_sign_override: RialtoHeadersToMillauSigningParams {
+					rialto_headers_to_millau_signer: None,
+					rialto_headers_to_millau_signer_password: None,
+					rialto_headers_to_millau_signer_file: None,
+					rialto_headers_to_millau_signer_password_file: None,
+					rialto_headers_to_millau_transactions_mortality: None,
+				},
+			}),
+		);
+	}
+
+	#[test]
+	fn should_parse_relay_to_parachain_options() {
+		// when
+		let res = RelayHeadersAndMessages::from_iter(vec![
+			"relay-headers-and-messages",
+			"millau-rialto-parachain",
+			"--millau-host",
+			"millau-node-alice",
+			"--millau-port",
+			"9944",
+			"--millau-signer",
+			"//Iden",
+			"--rialto-headers-to-millau-signer",
+			"//Ken",
+			"--millau-messages-pallet-owner",
+			"//RialtoParachainMessagesOwner",
+			"--millau-transactions-mortality",
+			"64",
+			"--rialto-parachain-host",
+			"rialto-parachain-collator-charlie",
+			"--rialto-parachain-port",
+			"9944",
+			"--rialto-parachain-signer",
+			"//George",
+			"--rialto-parachain-messages-pallet-owner",
+			"//MillauMessagesOwner",
+			"--rialto-parachain-transactions-mortality",
+			"64",
+			"--rialto-host",
+			"rialto-node-alice",
+			"--rialto-port",
+			"9944",
+			"--lane",
+			"00000000",
+			"--prometheus-host",
+			"0.0.0.0",
+		]);
+
+		// then
+		assert_eq!(
+			res,
+			RelayHeadersAndMessages::MillauRialtoParachain(
+				MillauRialtoParachainHeadersAndMessages {
+					shared: HeadersAndMessagesSharedParams {
+						lane: vec![HexLaneId([0x00, 0x00, 0x00, 0x00])],
+						relayer_mode: RelayerMode::Rational,
+						create_relayers_fund_accounts: false,
+						only_mandatory_headers: false,
+						prometheus_params: PrometheusParams {
+							no_prometheus: false,
+							prometheus_host: "0.0.0.0".into(),
+							prometheus_port: 9616,
+						},
+					},
+					left: MillauConnectionParams {
+						millau_host: "millau-node-alice".into(),
+						millau_port: 9944,
+						millau_secure: false,
+						millau_runtime_version: MillauRuntimeVersionParams {
+							millau_version_mode: RuntimeVersionType::Bundle,
+							millau_spec_version: None,
+							millau_transaction_version: None,
+						},
+					},
+					left_sign: MillauSigningParams {
+						millau_signer: Some("//Iden".into()),
+						millau_signer_password: None,
+						millau_signer_file: None,
+						millau_signer_password_file: None,
+						millau_transactions_mortality: Some(64),
+					},
+					left_messages_pallet_owner: MillauMessagesPalletOwnerSigningParams {
+						millau_messages_pallet_owner: Some("//RialtoParachainMessagesOwner".into()),
+						millau_messages_pallet_owner_password: None,
+					},
+					left_headers_to_right_sign_override:
+						MillauHeadersToRialtoParachainSigningParams {
+							millau_headers_to_rialto_parachain_signer: None,
+							millau_headers_to_rialto_parachain_signer_password: None,
+							millau_headers_to_rialto_parachain_signer_file: None,
+							millau_headers_to_rialto_parachain_signer_password_file: None,
+							millau_headers_to_rialto_parachain_transactions_mortality: None,
+						},
+					right: RialtoParachainConnectionParams {
+						rialto_parachain_host: "rialto-parachain-collator-charlie".into(),
+						rialto_parachain_port: 9944,
+						rialto_parachain_secure: false,
+						rialto_parachain_runtime_version: RialtoParachainRuntimeVersionParams {
+							rialto_parachain_version_mode: RuntimeVersionType::Bundle,
+							rialto_parachain_spec_version: None,
+							rialto_parachain_transaction_version: None,
+						},
+					},
+					right_sign: RialtoParachainSigningParams {
+						rialto_parachain_signer: Some("//George".into()),
+						rialto_parachain_signer_password: None,
+						rialto_parachain_signer_file: None,
+						rialto_parachain_signer_password_file: None,
+						rialto_parachain_transactions_mortality: Some(64),
+					},
+					right_messages_pallet_owner: RialtoParachainMessagesPalletOwnerSigningParams {
+						rialto_parachain_messages_pallet_owner: Some(
+							"//MillauMessagesOwner".into()
+						),
+						rialto_parachain_messages_pallet_owner_password: None,
+					},
+					right_relay_headers_to_left_sign_override: RialtoHeadersToMillauSigningParams {
+						rialto_headers_to_millau_signer: Some("//Ken".into()),
+						rialto_headers_to_millau_signer_password: None,
+						rialto_headers_to_millau_signer_file: None,
+						rialto_headers_to_millau_signer_password_file: None,
+						rialto_headers_to_millau_transactions_mortality: None,
+					},
+					right_parachains_to_left_sign_override: RialtoParachainsToMillauSigningParams {
+						rialto_parachains_to_millau_signer: None,
+						rialto_parachains_to_millau_signer_password: None,
+						rialto_parachains_to_millau_signer_file: None,
+						rialto_parachains_to_millau_signer_password_file: None,
+						rialto_parachains_to_millau_transactions_mortality: None,
+					},
+					right_relay: RialtoConnectionParams {
+						rialto_host: "rialto-node-alice".into(),
+						rialto_port: 9944,
+						rialto_secure: false,
+						rialto_runtime_version: RialtoRuntimeVersionParams {
+							rialto_version_mode: RuntimeVersionType::Bundle,
+							rialto_spec_version: None,
+							rialto_transaction_version: None,
+						},
+					},
+				}
+			),
+		);
+	}
+}
diff --git a/bridges/relays/lib-substrate-relay/src/lib.rs b/bridges/relays/lib-substrate-relay/src/lib.rs
index 73dedb3a334..51edf857838 100644
--- a/bridges/relays/lib-substrate-relay/src/lib.rs
+++ b/bridges/relays/lib-substrate-relay/src/lib.rs
@@ -48,3 +48,61 @@ pub struct TransactionParams<TS> {
 	/// Transactions mortality.
 	pub mortality: Option<u32>,
 }
+
+/// Tagged relay account, which balance may be exposed as metrics by the relay.
+#[derive(Clone, Debug)]
+pub enum TaggedAccount<AccountId> {
+	/// Account, used to sign headers relay transactions from given bridged chain.
+	Headers {
+		/// Account id.
+		id: AccountId,
+		/// Name of the bridged chain, which headers are relayed.
+		bridged_chain: String,
+	},
+	/// Account, used to sign parachains relay transactions from given bridged relay chain.
+	Parachains {
+		/// Account id.
+		id: AccountId,
+		/// Name of the bridged relay chain with parachain heads.
+		bridged_chain: String,
+	},
+	/// Account, used to sign message relay transactions from given bridged chain.
+	Messages {
+		/// Account id.
+		id: AccountId,
+		/// Name of the bridged chain, which sends us messages or delivery confirmations.
+		bridged_chain: String,
+	},
+	/// Account, used to sign messages with-bridged-chain pallet parameters update transactions.
+	MessagesPalletOwner {
+		/// Account id.
+		id: AccountId,
+		/// Name of the chain, bridged using messages pallet at our chain.
+		bridged_chain: String,
+	},
+}
+
+impl<AccountId> TaggedAccount<AccountId> {
+	/// Returns reference to the account id.
+	pub fn id(&self) -> &AccountId {
+		match *self {
+			TaggedAccount::Headers { ref id, .. } => id,
+			TaggedAccount::Parachains { ref id, .. } => id,
+			TaggedAccount::Messages { ref id, .. } => id,
+			TaggedAccount::MessagesPalletOwner { ref id, .. } => id,
+		}
+	}
+
+	/// Returns stringified account tag.
+	pub fn tag(&self) -> String {
+		match *self {
+			TaggedAccount::Headers { ref bridged_chain, .. } => format!("{}Headers", bridged_chain),
+			TaggedAccount::Parachains { ref bridged_chain, .. } =>
+				format!("{}Parachains", bridged_chain),
+			TaggedAccount::Messages { ref bridged_chain, .. } =>
+				format!("{}Messages", bridged_chain),
+			TaggedAccount::MessagesPalletOwner { ref bridged_chain, .. } =>
+				format!("{}MessagesPalletOwner", bridged_chain),
+		}
+	}
+}
diff --git a/bridges/relays/lib-substrate-relay/src/messages_metrics.rs b/bridges/relays/lib-substrate-relay/src/messages_metrics.rs
index b165892fda1..7c621b4c39a 100644
--- a/bridges/relays/lib-substrate-relay/src/messages_metrics.rs
+++ b/bridges/relays/lib-substrate-relay/src/messages_metrics.rs
@@ -16,7 +16,7 @@
 
 //! Tools for supporting message lanes between two Substrate-based chains.
 
-use crate::{helpers::tokens_conversion_rate, messages_lane::SubstrateMessageLane};
+use crate::{helpers::tokens_conversion_rate, messages_lane::SubstrateMessageLane, TaggedAccount};
 
 use codec::Decode;
 use frame_system::AccountInfo;
@@ -274,13 +274,12 @@ pub fn standalone_metrics<P: SubstrateMessageLane>(
 pub async fn add_relay_balances_metrics<C: ChainWithBalances>(
 	client: Client<C>,
 	metrics: MetricsParams,
-	relay_account_id: Option<AccountIdOf<C>>,
-	messages_pallet_owner_account_id: Option<AccountIdOf<C>>,
+	relay_accounts: Vec<TaggedAccount<AccountIdOf<C>>>,
 ) -> anyhow::Result<MetricsParams>
 where
 	BalanceOf<C>: Into<u128> + std::fmt::Debug,
 {
-	if relay_account_id.is_none() && messages_pallet_owner_account_id.is_none() {
+	if relay_accounts.is_empty() {
 		return Ok(metrics)
 	}
 
@@ -306,26 +305,18 @@ where
 			e,
 		)
 	})?;
-	if let Some(relay_account_id) = relay_account_id {
+
+	for account in relay_accounts {
 		let relay_account_balance_metric = FloatStorageValueMetric::new(
 			FreeAccountBalance::<C> { token_decimals, _phantom: Default::default() },
 			client.clone(),
-			C::account_info_storage_key(&relay_account_id),
-			format!("at_{}_relay_balance", C::NAME),
-			format!("Balance of the relay account at the {}", C::NAME),
+			C::account_info_storage_key(account.id()),
+			format!("at_{}_relay_{}_balance", C::NAME, account.tag()),
+			format!("Balance of the {} relay account at the {}", account.tag(), C::NAME),
 		)?;
 		relay_account_balance_metric.register_and_spawn(&metrics.registry)?;
 	}
-	if let Some(messages_pallet_owner_account_id) = messages_pallet_owner_account_id {
-		let pallet_owner_account_balance_metric = FloatStorageValueMetric::new(
-			FreeAccountBalance::<C> { token_decimals, _phantom: Default::default() },
-			client.clone(),
-			C::account_info_storage_key(&messages_pallet_owner_account_id),
-			format!("at_{}_messages_pallet_owner_balance", C::NAME),
-			format!("Balance of the messages pallet owner at the {}", C::NAME),
-		)?;
-		pallet_owner_account_balance_metric.register_and_spawn(&metrics.registry)?;
-	}
+
 	Ok(metrics)
 }
 
-- 
GitLab