From 1b2e6cdeb0fbc6908207645c3ffbdd5811dfa4bb Mon Sep 17 00:00:00 2001
From: Svyatoslav Nikolsky <svyatonik@gmail.com>
Date: Mon, 22 Feb 2021 18:55:40 +0300
Subject: [PATCH] Generate large messages (#700)

* generate large messages

* consider headers sync lag when computing number of rewards in confirmation

* more fixes

* fix logs

* fix warnings

* do not wait until tx that has delivered nonces will be finalized before submitting other tx

* tests for maximal weight/size

* cleanup

* cleanup

* clippy

* compilation

* args for dispatch weight and remark size

* ExplicitOrMaximal

* clippy
---
 bridges/relays/substrate/src/cli.rs           |  45 +++-
 bridges/relays/substrate/src/main.rs          | 222 +++++++++++++++---
 bridges/relays/substrate/src/messages_lane.rs |   4 +-
 3 files changed, 238 insertions(+), 33 deletions(-)

diff --git a/bridges/relays/substrate/src/cli.rs b/bridges/relays/substrate/src/cli.rs
index 96f13d33841..8dc241f069e 100644
--- a/bridges/relays/substrate/src/cli.rs
+++ b/bridges/relays/substrate/src/cli.rs
@@ -17,6 +17,7 @@
 //! Deal with CLI args of substrate-to-substrate relay.
 
 use bp_message_lane::LaneId;
+use frame_support::weights::Weight;
 use sp_core::Bytes;
 use sp_finality_grandpa::SetId as GrandpaAuthoritiesSetId;
 use structopt::{clap::arg_enum, StructOpt};
@@ -153,6 +154,9 @@ pub enum SendMessage {
 		/// Hex-encoded lane id.
 		#[structopt(long)]
 		lane: HexLaneId,
+		/// Dispatch weight of the message. If not passed, determined automatically.
+		#[structopt(long)]
+		dispatch_weight: Option<ExplicitOrMaximal<Weight>>,
 		/// Delivery and dispatch fee. If not passed, determined automatically.
 		#[structopt(long)]
 		fee: Option<bp_millau::Balance>,
@@ -174,6 +178,9 @@ pub enum SendMessage {
 		/// Hex-encoded lane id.
 		#[structopt(long)]
 		lane: HexLaneId,
+		/// Dispatch weight of the message. If not passed, determined automatically.
+		#[structopt(long)]
+		dispatch_weight: Option<ExplicitOrMaximal<Weight>>,
 		/// Delivery and dispatch fee. If not passed, determined automatically.
 		#[structopt(long)]
 		fee: Option<bp_rialto::Balance>,
@@ -190,7 +197,11 @@ pub enum SendMessage {
 #[derive(StructOpt, Debug)]
 pub enum ToRialtoMessage {
 	/// Make an on-chain remark (comment).
-	Remark,
+	Remark {
+		/// Remark size. If not passed, small UTF8-encoded string is generated by relay as remark.
+		#[structopt(long)]
+		remark_size: Option<ExplicitOrMaximal<usize>>,
+	},
 	/// Transfer the specified `amount` of native tokens to a particular `recipient`.
 	Transfer {
 		#[structopt(long)]
@@ -204,7 +215,11 @@ pub enum ToRialtoMessage {
 #[derive(StructOpt, Debug)]
 pub enum ToMillauMessage {
 	/// Make an on-chain remark (comment).
-	Remark,
+	Remark {
+		/// Size of the remark. If not passed, small UTF8-encoded string is generated by relay as remark.
+		#[structopt(long)]
+		remark_size: Option<ExplicitOrMaximal<usize>>,
+	},
 	/// Transfer the specified `amount` of native tokens to a particular `recipient`.
 	Transfer {
 		#[structopt(long)]
@@ -273,6 +288,32 @@ impl From<PrometheusParams> for Option<relay_utils::metrics::MetricsParams> {
 	}
 }
 
+/// Either explicit or maximal allowed value.
+#[derive(Debug)]
+pub enum ExplicitOrMaximal<V> {
+	/// User has explicitly specified argument value.
+	Explicit(V),
+	/// Maximal allowed value for this argument.
+	Maximal,
+}
+
+impl<V: std::str::FromStr> std::str::FromStr for ExplicitOrMaximal<V>
+where
+	V::Err: std::fmt::Debug,
+{
+	type Err = String;
+
+	fn from_str(s: &str) -> Result<Self, Self::Err> {
+		if s.to_lowercase() == "max" {
+			return Ok(ExplicitOrMaximal::Maximal);
+		}
+
+		V::from_str(s)
+			.map(ExplicitOrMaximal::Explicit)
+			.map_err(|e| format!("Failed to parse '{:?}'. Expected 'max' or explicit value", e))
+	}
+}
+
 macro_rules! declare_chain_options {
 	($chain:ident, $chain_prefix:ident) => {
 		paste::item! {
diff --git a/bridges/relays/substrate/src/main.rs b/bridges/relays/substrate/src/main.rs
index c700022a0f1..c73533cdfca 100644
--- a/bridges/relays/substrate/src/main.rs
+++ b/bridges/relays/substrate/src/main.rs
@@ -19,7 +19,7 @@
 #![warn(missing_docs)]
 
 use codec::{Decode, Encode};
-use frame_support::weights::GetDispatchInfo;
+use frame_support::weights::{GetDispatchInfo, Weight};
 use pallet_bridge_call_dispatch::{CallOrigin, MessagePayload};
 use relay_kusama_client::Kusama;
 use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
@@ -232,6 +232,7 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 			rialto_sign,
 			lane,
 			message,
+			dispatch_weight,
 			fee,
 			origin,
 			..
@@ -241,7 +242,9 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 			let rialto_sign = rialto_sign.parse()?;
 			let rialto_call = message.into_call();
 
-			let payload = millau_to_rialto_message_payload(&millau_sign, &rialto_sign, &rialto_call, origin);
+			let payload =
+				millau_to_rialto_message_payload(&millau_sign, &rialto_sign, &rialto_call, origin, dispatch_weight);
+			let dispatch_weight = payload.weight;
 
 			let lane = lane.into();
 			let fee = get_fee(fee, || {
@@ -254,8 +257,6 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 			})
 			.await?;
 
-			log::info!(target: "bridge", "Sending message to Rialto. Fee: {}", fee);
-
 			let millau_call = millau_runtime::Call::BridgeRialtoMessageLane(
 				millau_runtime::MessageLaneCall::send_message(lane, payload, fee),
 			);
@@ -267,11 +268,18 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 					.next_account_index(millau_sign.signer.public().clone().into())
 					.await?,
 				millau_call,
+			)
+			.encode();
+
+			log::info!(
+				target: "bridge",
+				"Sending message to Rialto. Size: {}. Dispatch weight: {}. Fee: {}",
+				signed_millau_call.len(),
+				dispatch_weight,
+				fee,
 			);
 
-			millau_client
-				.submit_extrinsic(Bytes(signed_millau_call.encode()))
-				.await?;
+			millau_client.submit_extrinsic(Bytes(signed_millau_call)).await?;
 		}
 		cli::SendMessage::RialtoToMillau {
 			rialto,
@@ -279,6 +287,7 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 			millau_sign,
 			lane,
 			message,
+			dispatch_weight,
 			fee,
 			origin,
 			..
@@ -288,7 +297,9 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 			let millau_sign = millau_sign.parse()?;
 			let millau_call = message.into_call();
 
-			let payload = rialto_to_millau_message_payload(&rialto_sign, &millau_sign, &millau_call, origin);
+			let payload =
+				rialto_to_millau_message_payload(&rialto_sign, &millau_sign, &millau_call, origin, dispatch_weight);
+			let dispatch_weight = payload.weight;
 
 			let lane = lane.into();
 			let fee = get_fee(fee, || {
@@ -301,8 +312,6 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 			})
 			.await?;
 
-			log::info!(target: "bridge", "Sending message to Millau. Fee: {}", fee);
-
 			let rialto_call = rialto_runtime::Call::BridgeMillauMessageLane(
 				rialto_runtime::MessageLaneCall::send_message(lane, payload, fee),
 			);
@@ -314,11 +323,18 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 					.next_account_index(rialto_sign.signer.public().clone().into())
 					.await?,
 				rialto_call,
+			)
+			.encode();
+
+			log::info!(
+				target: "bridge",
+				"Sending message to Millau. Size: {}. Dispatch weight: {}. Fee: {}",
+				signed_rialto_call.len(),
+				dispatch_weight,
+				fee,
 			);
 
-			rialto_client
-				.submit_extrinsic(Bytes(signed_rialto_call.encode()))
-				.await?;
+			rialto_client.submit_extrinsic(Bytes(signed_rialto_call)).await?;
 		}
 	}
 	Ok(())
@@ -338,16 +354,20 @@ async fn estimate_message_delivery_and_dispatch_fee<Fee: Decode, C: Chain, P: En
 	Ok(decoded_response)
 }
 
-fn remark_payload() -> Vec<u8> {
-	format!(
-		"Unix time: {}",
-		std::time::SystemTime::now()
-			.duration_since(std::time::SystemTime::UNIX_EPOCH)
-			.unwrap_or_default()
-			.as_secs(),
-	)
-	.as_bytes()
-	.to_vec()
+fn remark_payload(remark_size: Option<cli::ExplicitOrMaximal<usize>>, maximal_allowed_size: u32) -> Vec<u8> {
+	match remark_size {
+		Some(cli::ExplicitOrMaximal::Explicit(remark_size)) => vec![0; remark_size],
+		Some(cli::ExplicitOrMaximal::Maximal) => vec![0; maximal_allowed_size as _],
+		None => format!(
+			"Unix time: {}",
+			std::time::SystemTime::now()
+				.duration_since(std::time::SystemTime::UNIX_EPOCH)
+				.unwrap_or_default()
+				.as_secs(),
+		)
+		.as_bytes()
+		.to_vec(),
+	}
 }
 
 fn rialto_to_millau_message_payload(
@@ -355,8 +375,13 @@ fn rialto_to_millau_message_payload(
 	millau_sign: &MillauSigningParams,
 	millau_call: &millau_runtime::Call,
 	origin: cli::Origins,
+	user_specified_dispatch_weight: Option<cli::ExplicitOrMaximal<Weight>>,
 ) -> rialto_runtime::millau_messages::ToMillauMessagePayload {
-	let millau_call_weight = millau_call.get_dispatch_info().weight;
+	let millau_call_weight = prepare_call_dispatch_weight(
+		user_specified_dispatch_weight,
+		cli::ExplicitOrMaximal::Explicit(millau_call.get_dispatch_info().weight),
+		compute_maximal_message_dispatch_weight(bp_millau::max_extrinsic_weight()),
+	);
 	let rialto_sender_public: bp_rialto::AccountSigner = rialto_sign.signer.public().clone().into();
 	let rialto_account_id: bp_rialto::AccountId = rialto_sender_public.into_account();
 	let millau_origin_public = millau_sign.signer.public();
@@ -387,8 +412,13 @@ fn millau_to_rialto_message_payload(
 	rialto_sign: &RialtoSigningParams,
 	rialto_call: &rialto_runtime::Call,
 	origin: cli::Origins,
+	user_specified_dispatch_weight: Option<cli::ExplicitOrMaximal<Weight>>,
 ) -> millau_runtime::rialto_messages::ToRialtoMessagePayload {
-	let rialto_call_weight = rialto_call.get_dispatch_info().weight;
+	let rialto_call_weight = prepare_call_dispatch_weight(
+		user_specified_dispatch_weight,
+		cli::ExplicitOrMaximal::Explicit(rialto_call.get_dispatch_info().weight),
+		compute_maximal_message_dispatch_weight(bp_rialto::max_extrinsic_weight()),
+	);
 	let millau_sender_public: bp_millau::AccountSigner = millau_sign.signer.public().clone().into();
 	let millau_account_id: bp_millau::AccountId = millau_sender_public.into_account();
 	let rialto_origin_public = rialto_sign.signer.public();
@@ -414,6 +444,17 @@ fn millau_to_rialto_message_payload(
 	}
 }
 
+fn prepare_call_dispatch_weight(
+	user_specified_dispatch_weight: Option<cli::ExplicitOrMaximal<Weight>>,
+	weight_from_pre_dispatch_call: cli::ExplicitOrMaximal<Weight>,
+	maximal_allowed_weight: Weight,
+) -> Weight {
+	match user_specified_dispatch_weight.unwrap_or(weight_from_pre_dispatch_call) {
+		cli::ExplicitOrMaximal::Explicit(weight) => weight,
+		cli::ExplicitOrMaximal::Maximal => maximal_allowed_weight,
+	}
+}
+
 async fn get_fee<Fee, F, R, E>(fee: Option<Fee>, f: F) -> Result<Fee, String>
 where
 	Fee: Decode,
@@ -431,6 +472,30 @@ where
 	}
 }
 
+fn compute_maximal_message_dispatch_weight(maximal_extrinsic_weight: Weight) -> Weight {
+	bridge_runtime_common::messages::target::maximal_incoming_message_dispatch_weight(maximal_extrinsic_weight)
+}
+
+fn compute_maximal_message_arguments_size(
+	maximal_source_extrinsic_size: u32,
+	maximal_target_extrinsic_size: u32,
+) -> u32 {
+	// assume that both signed extensions and other arguments fit 1KB
+	let service_tx_bytes_on_source_chain = 1024;
+	let maximal_source_extrinsic_size = maximal_source_extrinsic_size - service_tx_bytes_on_source_chain;
+	let maximal_call_size =
+		bridge_runtime_common::messages::target::maximal_incoming_message_size(maximal_target_extrinsic_size);
+	let maximal_call_size = if maximal_call_size > maximal_source_extrinsic_size {
+		maximal_source_extrinsic_size
+	} else {
+		maximal_call_size
+	};
+
+	// bytes in Call encoding that are used to encode everything except arguments
+	let service_bytes = 1 + 1 + 4;
+	maximal_call_size - service_bytes
+}
+
 impl crate::cli::RialtoSigningParams {
 	/// Parse CLI parameters into typed signing params.
 	pub fn parse(self) -> Result<RialtoSigningParams, String> {
@@ -472,8 +537,14 @@ impl crate::cli::ToRialtoMessage {
 	/// Convert CLI call request into runtime `Call` instance.
 	pub fn into_call(self) -> rialto_runtime::Call {
 		match self {
-			cli::ToRialtoMessage::Remark => {
-				rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(remark_payload()))
+			cli::ToRialtoMessage::Remark { remark_size } => {
+				rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(remark_payload(
+					remark_size,
+					compute_maximal_message_arguments_size(
+						bp_millau::max_extrinsic_size(),
+						bp_rialto::max_extrinsic_size(),
+					),
+				)))
 			}
 			cli::ToRialtoMessage::Transfer { recipient, amount } => {
 				rialto_runtime::Call::Balances(rialto_runtime::BalancesCall::transfer(recipient, amount))
@@ -486,8 +557,14 @@ impl crate::cli::ToMillauMessage {
 	/// Convert CLI call request into runtime `Call` instance.
 	pub fn into_call(self) -> millau_runtime::Call {
 		match self {
-			cli::ToMillauMessage::Remark => {
-				millau_runtime::Call::System(millau_runtime::SystemCall::remark(remark_payload()))
+			cli::ToMillauMessage::Remark { remark_size } => {
+				millau_runtime::Call::System(millau_runtime::SystemCall::remark(remark_payload(
+					remark_size,
+					compute_maximal_message_arguments_size(
+						bp_rialto::max_extrinsic_size(),
+						bp_millau::max_extrinsic_size(),
+					),
+				)))
 			}
 			cli::ToMillauMessage::Transfer { recipient, amount } => {
 				millau_runtime::Call::Balances(millau_runtime::BalancesCall::transfer(recipient, amount))
@@ -498,6 +575,8 @@ impl crate::cli::ToMillauMessage {
 
 #[cfg(test)]
 mod tests {
+	use super::*;
+	use bp_message_lane::source_chain::TargetHeaderChain;
 	use sp_core::Pair;
 	use sp_runtime::traits::{IdentifyAccount, Verify};
 
@@ -542,4 +621,89 @@ mod tests {
 
 		assert!(signature.verify(&digest[..], &millau_signer.signer.public()));
 	}
+
+	#[test]
+	fn maximal_rialto_to_millau_message_arguments_size_is_computed_correctly() {
+		use rialto_runtime::millau_messages::Millau;
+
+		let maximal_remark_size =
+			compute_maximal_message_arguments_size(bp_rialto::max_extrinsic_size(), bp_millau::max_extrinsic_size());
+
+		let call: millau_runtime::Call = millau_runtime::SystemCall::remark(vec![42; maximal_remark_size as _]).into();
+		let payload = pallet_bridge_call_dispatch::MessagePayload {
+			spec_version: Default::default(),
+			weight: call.get_dispatch_info().weight,
+			origin: pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
+			call: call.encode(),
+		};
+		assert_eq!(Millau::verify_message(&payload), Ok(()));
+
+		let call: millau_runtime::Call =
+			millau_runtime::SystemCall::remark(vec![42; (maximal_remark_size + 1) as _]).into();
+		let payload = pallet_bridge_call_dispatch::MessagePayload {
+			spec_version: Default::default(),
+			weight: call.get_dispatch_info().weight,
+			origin: pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
+			call: call.encode(),
+		};
+		assert!(Millau::verify_message(&payload).is_err());
+	}
+
+	#[test]
+	fn maximal_size_remark_to_rialto_is_generated_correctly() {
+		assert!(
+			bridge_runtime_common::messages::target::maximal_incoming_message_size(
+				bp_rialto::max_extrinsic_size()
+			) > bp_millau::max_extrinsic_size(),
+			"We can't actually send maximal messages to Rialto from Millau, because Millau extrinsics can't be that large",
+		)
+	}
+
+	#[test]
+	fn maximal_rialto_to_millau_message_dispatch_weight_is_computed_correctly() {
+		use rialto_runtime::millau_messages::Millau;
+
+		let maximal_dispatch_weight = compute_maximal_message_dispatch_weight(bp_millau::max_extrinsic_weight());
+		let call: millau_runtime::Call = rialto_runtime::SystemCall::remark(vec![]).into();
+
+		let payload = pallet_bridge_call_dispatch::MessagePayload {
+			spec_version: Default::default(),
+			weight: maximal_dispatch_weight,
+			origin: pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
+			call: call.encode(),
+		};
+		assert_eq!(Millau::verify_message(&payload), Ok(()));
+
+		let payload = pallet_bridge_call_dispatch::MessagePayload {
+			spec_version: Default::default(),
+			weight: maximal_dispatch_weight + 1,
+			origin: pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
+			call: call.encode(),
+		};
+		assert!(Millau::verify_message(&payload).is_err());
+	}
+
+	#[test]
+	fn maximal_weight_fill_block_to_rialto_is_generated_correctly() {
+		use millau_runtime::rialto_messages::Rialto;
+
+		let maximal_dispatch_weight = compute_maximal_message_dispatch_weight(bp_rialto::max_extrinsic_weight());
+		let call: rialto_runtime::Call = millau_runtime::SystemCall::remark(vec![]).into();
+
+		let payload = pallet_bridge_call_dispatch::MessagePayload {
+			spec_version: Default::default(),
+			weight: maximal_dispatch_weight,
+			origin: pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
+			call: call.encode(),
+		};
+		assert_eq!(Rialto::verify_message(&payload), Ok(()));
+
+		let payload = pallet_bridge_call_dispatch::MessagePayload {
+			spec_version: Default::default(),
+			weight: maximal_dispatch_weight + 1,
+			origin: pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
+			call: call.encode(),
+		};
+		assert!(Rialto::verify_message(&payload).is_err());
+	}
 }
diff --git a/bridges/relays/substrate/src/messages_lane.rs b/bridges/relays/substrate/src/messages_lane.rs
index 96efaa6a436..78b5f5c0248 100644
--- a/bridges/relays/substrate/src/messages_lane.rs
+++ b/bridges/relays/substrate/src/messages_lane.rs
@@ -166,7 +166,7 @@ mod tests {
 	#[test]
 	fn select_delivery_transaction_limits_works() {
 		let (max_count, max_weight) = select_delivery_transaction_limits::<RialtoToMillauMessageLaneWeights>(
-			bp_rialto::max_extrinsic_weight(),
+			bp_millau::max_extrinsic_weight(),
 			bp_millau::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
 		);
 		assert_eq!(
@@ -176,7 +176,7 @@ mod tests {
 			// reserved for messages dispatch allows dispatch of non-trivial messages.
 			//
 			// Any significant change in this values should attract additional attention.
-			(1024, 866_583_333_334),
+			(955, 216_583_333_334),
 		);
 	}
 }
-- 
GitLab