From 5ac18747007b682774ce98f293ef9d8920976bbb Mon Sep 17 00:00:00 2001
From: Svyatoslav Nikolsky <svyatonik@gmail.com>
Date: Tue, 14 May 2024 17:55:57 +0300
Subject: [PATCH] Bridge: add subcommand to relay messages range (#4383)

related to
https://github.com/paritytech/parity-bridges-common/issues/2962
Usage example:
```sh
RUST_LOG=runtime=trace,rpc=trace,bridge=trace \
        ./target/release/substrate-relay relay-messages-range bridge-hub-rococo-to-bridge-hub-westend \
        --source-host localhost \
        --source-port 8943 \
        --source-version-mode Auto \
        --source-signer //Eve \
        --source-transactions-mortality 4 \
        --target-host localhost \
        --target-port 8945 \
        --target-version-mode Auto \
        --target-signer //Eve \
        --target-transactions-mortality 4 \
        --lane 00000002 \
        --at-source-block 34 \
        --messages-start 1 \
        --messages-end 1
INFO bridge Connecting to BridgeHubRococo node at ws://localhost:8943
INFO bridge Connecting to BridgeHubWestend node at ws://localhost:8945
TRACE bridge Refined weight of BridgeHubRococo->BridgeHubWestend message [0, 0, 0, 2]/1: at-source: Weight(ref_time: 0, proof_size: 0), at-target: Weight(ref_time: 452953993, proof_size: 0)
TRACE bridge Sent transaction to BridgeHubWestend node: 0x38552f4db6bc78baecb52ebd2f7d103b1c919c16b83129dc083bf01b7281955b
TRACE bridge BridgeHubWestend transaction 0x38552f4db6bc78baecb52ebd2f7d103b1c919c16b83129dc083bf01b7281955b has been included in block: (0x29a20bdca8726df0b32af9067290b7fc0a886908da3a30f3db60a6ea52be4604, 0)
TRACE bridge BridgeHubWestend transaction 0x38552f4db6bc78baecb52ebd2f7d103b1c919c16b83129dc083bf01b7281955b has been finalized at block: 0x29a20bdca8726df0b32af9067290b7fc0a886908da3a30f3db60a6ea52be4604
```
---
 .../src/cli/relay_messages.rs                 | 70 ++++++++++++++++++-
 .../lib-substrate-relay/src/messages_lane.rs  | 45 +++++++++++-
 bridges/relays/messages/src/lib.rs            |  2 +
 .../messages/src/message_race_delivery.rs     | 65 ++++++++++++++++-
 4 files changed, 179 insertions(+), 3 deletions(-)

diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs
index b672bd4f9b8..e5b07b24158 100644
--- a/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs
+++ b/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs
@@ -26,9 +26,12 @@ use async_trait::async_trait;
 use sp_core::Pair;
 use structopt::StructOpt;
 
+use bp_messages::MessageNonce;
+use bp_runtime::HeaderIdProvider;
 use relay_substrate_client::{
-	AccountIdOf, AccountKeyPairOf, BalanceOf, ChainWithRuntimeVersion, ChainWithTransactions,
+	AccountIdOf, AccountKeyPairOf, BalanceOf, Chain, ChainWithRuntimeVersion, ChainWithTransactions,
 };
+use relay_utils::UniqueSaturatedInto;
 
 /// Messages relaying params.
 #[derive(StructOpt)]
@@ -48,6 +51,35 @@ pub struct RelayMessagesParams {
 	prometheus_params: PrometheusParams,
 }
 
+/// Messages range relaying params.
+#[derive(StructOpt)]
+pub struct RelayMessagesRangeParams {
+	/// Number of the source chain header that we will use to prepare a messages proof.
+	/// This header must be previously proved to the target chain.
+	#[structopt(long)]
+	at_source_block: u128,
+	/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
+	#[structopt(long, default_value = "00000000")]
+	lane: HexLaneId,
+	/// Nonce (inclusive) of the first message to relay.
+	#[structopt(long)]
+	messages_start: MessageNonce,
+	/// Nonce (inclusive) of the last message to relay.
+	#[structopt(long)]
+	messages_end: MessageNonce,
+	/// Whether the outbound lane state proof should be included into transaction.
+	#[structopt(long)]
+	outbound_state_proof_required: bool,
+	#[structopt(flatten)]
+	source: SourceConnectionParams,
+	#[structopt(flatten)]
+	source_sign: SourceSigningParams,
+	#[structopt(flatten)]
+	target: TargetConnectionParams,
+	#[structopt(flatten)]
+	target_sign: TargetSigningParams,
+}
+
 /// Trait used for relaying messages between 2 chains.
 #[async_trait]
 pub trait MessagesRelayer: MessagesCliBridge
@@ -86,4 +118,40 @@ where
 		.await
 		.map_err(|e| anyhow::format_err!("{}", e))
 	}
+
+	/// Relay a consequitive range of messages.
+	async fn relay_messages_range(data: RelayMessagesRangeParams) -> anyhow::Result<()> {
+		let source_client = data.source.into_client::<Self::Source>().await?;
+		let target_client = data.target.into_client::<Self::Target>().await?;
+		let source_sign = data.source_sign.to_keypair::<Self::Source>()?;
+		let source_transactions_mortality = data.source_sign.transactions_mortality()?;
+		let target_sign = data.target_sign.to_keypair::<Self::Target>()?;
+		let target_transactions_mortality = data.target_sign.transactions_mortality()?;
+
+		let at_source_block = source_client
+			.header_by_number(data.at_source_block.unique_saturated_into())
+			.await
+			.map_err(|e| {
+				log::trace!(
+					target: "bridge",
+					"Failed to read {} header with number {}: {e:?}",
+					Self::Source::NAME,
+					data.at_source_block,
+				);
+				anyhow::format_err!("The command has failed")
+			})?
+			.id();
+
+		crate::messages_lane::relay_messages_range::<Self::MessagesLane>(
+			source_client,
+			target_client,
+			TransactionParams { signer: source_sign, mortality: source_transactions_mortality },
+			TransactionParams { signer: target_sign, mortality: target_transactions_mortality },
+			at_source_block,
+			data.lane.into(),
+			data.messages_start..=data.messages_end,
+			data.outbound_state_proof_required,
+		)
+		.await
+	}
 }
diff --git a/bridges/relays/lib-substrate-relay/src/messages_lane.rs b/bridges/relays/lib-substrate-relay/src/messages_lane.rs
index 58e9ded312d..a34b165289b 100644
--- a/bridges/relays/lib-substrate-relay/src/messages_lane.rs
+++ b/bridges/relays/lib-substrate-relay/src/messages_lane.rs
@@ -46,7 +46,7 @@ use relay_utils::{
 };
 use sp_core::Pair;
 use sp_runtime::traits::Zero;
-use std::{fmt::Debug, marker::PhantomData};
+use std::{fmt::Debug, marker::PhantomData, ops::RangeInclusive};
 
 /// Substrate -> Substrate messages synchronization pipeline.
 pub trait SubstrateMessageLane: 'static + Clone + Debug + Send + Sync {
@@ -275,6 +275,49 @@ where
 	.map_err(Into::into)
 }
 
+/// Deliver range of Substrate-to-Substrate messages. No checks are made to ensure that transaction
+/// will succeed.
+pub async fn relay_messages_range<P: SubstrateMessageLane>(
+	source_client: Client<P::SourceChain>,
+	target_client: Client<P::TargetChain>,
+	source_transaction_params: TransactionParams<AccountKeyPairOf<P::SourceChain>>,
+	target_transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
+	at_source_block: HeaderIdOf<P::SourceChain>,
+	lane_id: LaneId,
+	range: RangeInclusive<MessageNonce>,
+	outbound_state_proof_required: bool,
+) -> anyhow::Result<()>
+where
+	AccountIdOf<P::SourceChain>: From<<AccountKeyPairOf<P::SourceChain> as Pair>::Public>,
+	AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TargetChain> as Pair>::Public>,
+	BalanceOf<P::SourceChain>: TryFrom<BalanceOf<P::TargetChain>>,
+{
+	let relayer_id_at_source: AccountIdOf<P::SourceChain> =
+		source_transaction_params.signer.public().into();
+	messages_relay::relay_messages_range(
+		SubstrateMessagesSource::<P>::new(
+			source_client.clone(),
+			target_client.clone(),
+			lane_id,
+			source_transaction_params,
+			None,
+		),
+		SubstrateMessagesTarget::<P>::new(
+			target_client,
+			source_client,
+			lane_id,
+			relayer_id_at_source,
+			target_transaction_params,
+			None,
+		),
+		at_source_block,
+		range,
+		outbound_state_proof_required,
+	)
+	.await
+	.map_err(|_| anyhow::format_err!("The command has failed"))
+}
+
 /// Different ways of building `receive_messages_proof` calls.
 pub trait ReceiveMessagesProofCallBuilder<P: SubstrateMessageLane> {
 	/// Given messages proof, build call of `receive_messages_proof` function of bridge
diff --git a/bridges/relays/messages/src/lib.rs b/bridges/relays/messages/src/lib.rs
index 9c62cee5ee3..7c18b6b148f 100644
--- a/bridges/relays/messages/src/lib.rs
+++ b/bridges/relays/messages/src/lib.rs
@@ -35,3 +35,5 @@ mod message_race_limits;
 mod message_race_loop;
 mod message_race_receiving;
 mod message_race_strategy;
+
+pub use message_race_delivery::relay_messages_range;
diff --git a/bridges/relays/messages/src/message_race_delivery.rs b/bridges/relays/messages/src/message_race_delivery.rs
index f18c43cc7f0..cbb89baabcc 100644
--- a/bridges/relays/messages/src/message_race_delivery.rs
+++ b/bridges/relays/messages/src/message_race_delivery.rs
@@ -19,7 +19,7 @@ use async_trait::async_trait;
 use futures::stream::FusedStream;
 
 use bp_messages::{MessageNonce, UnrewardedRelayersState, Weight};
-use relay_utils::FailedClient;
+use relay_utils::{FailedClient, TrackedTransactionStatus, TransactionTracker};
 
 use crate::{
 	message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf},
@@ -77,6 +77,69 @@ pub async fn run<P: MessageLane>(
 	.await
 }
 
+/// Relay range of messages.
+pub async fn relay_messages_range<P: MessageLane>(
+	source_client: impl MessageLaneSourceClient<P>,
+	target_client: impl MessageLaneTargetClient<P>,
+	at: SourceHeaderIdOf<P>,
+	range: RangeInclusive<MessageNonce>,
+	outbound_state_proof_required: bool,
+) -> Result<(), ()> {
+	// compute cumulative dispatch weight of all messages in given range
+	let dispatch_weight = source_client
+		.generated_message_details(at.clone(), range.clone())
+		.await
+		.map_err(|e| {
+			log::error!(
+				target: "bridge",
+				"Failed to get generated message details at {:?} for messages {:?}: {:?}",
+				at,
+				range,
+				e,
+			);
+		})?
+		.values()
+		.fold(Weight::zero(), |total, details| total.saturating_add(details.dispatch_weight));
+	// prepare messages proof
+	let (at, range, proof) = source_client
+		.prove_messages(
+			at.clone(),
+			range.clone(),
+			MessageProofParameters { outbound_state_proof_required, dispatch_weight },
+		)
+		.await
+		.map_err(|e| {
+			log::error!(
+				target: "bridge",
+				"Failed to generate messages proof at {:?} for messages {:?}: {:?}",
+				at,
+				range,
+				e,
+			);
+		})?;
+	// submit messages proof to the target node
+	let tx_tracker = target_client
+		.submit_messages_proof(None, at, range.clone(), proof)
+		.await
+		.map_err(|e| {
+			log::error!(
+				target: "bridge",
+				"Failed to submit messages proof for messages {:?}: {:?}",
+				range,
+				e,
+			);
+		})?
+		.tx_tracker;
+
+	match tx_tracker.wait().await {
+		TrackedTransactionStatus::Finalized(_) => Ok(()),
+		TrackedTransactionStatus::Lost => {
+			log::error!("Transaction with messages {:?} is considered lost", range,);
+			Err(())
+		},
+	}
+}
+
 /// Message delivery race.
 struct MessageDeliveryRace<P>(std::marker::PhantomData<P>);
 
-- 
GitLab