From 9138e1a33e2c73e958cb1e7fa170885861907e67 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 9 Mar 2022 11:57:59 +0300 Subject: [PATCH] Mortal conversion rate updater transactions (#1257) * merge all similar update_conversion_rate functions * stall timeout in conversion rate update loop * fmt * fix --- .../src/chains/kusama_messages_to_polkadot.rs | 50 +--- .../src/chains/millau_messages_to_rialto.rs | 49 +--- .../src/chains/polkadot_messages_to_kusama.rs | 51 +--- .../src/chains/rialto_messages_to_millau.rs | 49 +--- .../src/chains/rococo_messages_to_wococo.rs | 2 + .../src/chains/wococo_messages_to_rococo.rs | 2 + .../src/cli/relay_headers_and_messages.rs | 86 ++----- .../src/conversion_rate_update.rs | 227 ++++++++++++++++-- .../lib-substrate-relay/src/messages_lane.rs | 8 + 9 files changed, 280 insertions(+), 244 deletions(-) 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 3679b26478371..9a71fbe3c6213 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,14 +16,11 @@ //! Kusama-to-Polkadot messages sync entrypoint. -use codec::Encode; use frame_support::weights::Weight; -use sp_core::{Bytes, Pair}; use messages_relay::relay_strategy::MixStrategy; use relay_kusama_client::Kusama; use relay_polkadot_client::Polkadot; -use relay_substrate_client::{Client, SignParam, TransactionSignScheme, UnsignedTransaction}; use substrate_relay_helper::messages_lane::SubstrateMessageLane; /// Description of Kusama -> Polkadot messages bridge. @@ -41,6 +38,13 @@ substrate_relay_helper::generate_mocked_receive_message_delivery_proof_call_buil relay_kusama_client::runtime::Call::BridgePolkadotMessages, relay_kusama_client::runtime::BridgePolkadotMessagesCall::receive_messages_delivery_proof ); +substrate_relay_helper::generate_mocked_update_conversion_rate_call_builder!( + Kusama, + KusamaMessagesToPolkadotUpdateConversionRateCallBuilder, + relay_kusama_client::runtime::Call::BridgePolkadotMessages, + relay_kusama_client::runtime::BridgePolkadotMessagesCall::update_pallet_parameter, + relay_kusama_client::runtime::BridgePolkadotMessagesParameter::PolkadotToKusamaConversionRate +); impl SubstrateMessageLane for KusamaMessagesToPolkadot { const SOURCE_TO_TARGET_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> = @@ -68,42 +72,8 @@ impl SubstrateMessageLane for KusamaMessagesToPolkadot { type ReceiveMessagesDeliveryProofCallBuilder = KusamaMessagesToPolkadotReceiveMessagesDeliveryProofCallBuilder; - type RelayStrategy = MixStrategy; -} + type TargetToSourceChainConversionRateUpdateBuilder = + KusamaMessagesToPolkadotUpdateConversionRateCallBuilder; -/// Update Polkadot -> Kusama conversion rate, stored in Kusama runtime storage. -pub(crate) async fn update_polkadot_to_kusama_conversion_rate( - client: Client, - signer: ::AccountKeyPair, - updated_rate: f64, -) -> anyhow::Result<()> { - let genesis_hash = *client.genesis_hash(); - let signer_id = (*signer.public().as_array_ref()).into(); - let (spec_version, transaction_version) = client.simple_runtime_version().await?; - client - .submit_signed_extrinsic(signer_id, move |_, transaction_nonce| { - Ok(Bytes( - Kusama::sign_transaction(SignParam { - spec_version, - transaction_version, - genesis_hash, - signer, - era: relay_substrate_client::TransactionEra::immortal(), - unsigned: UnsignedTransaction::new( - relay_kusama_client::runtime::Call::BridgePolkadotMessages( - relay_kusama_client::runtime::BridgePolkadotMessagesCall::update_pallet_parameter( - relay_kusama_client::runtime::BridgePolkadotMessagesParameter::PolkadotToKusamaConversionRate( - sp_runtime::FixedU128::from_float(updated_rate), - ) - ) - ).into(), - transaction_nonce, - ), - })? - .encode(), - )) - }) - .await - .map(drop) - .map_err(|err| anyhow::format_err!("{:?}", err)) + type RelayStrategy = MixStrategy; } 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 a93007c1bb5a1..f20669e6c7a58 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,13 +16,9 @@ //! Millau-to-Rialto messages sync entrypoint. -use codec::Encode; -use sp_core::{Bytes, Pair}; - use messages_relay::relay_strategy::MixStrategy; use relay_millau_client::Millau; use relay_rialto_client::Rialto; -use relay_substrate_client::{Client, SignParam, TransactionSignScheme, UnsignedTransaction}; use substrate_relay_helper::messages_lane::{ DirectReceiveMessagesDeliveryProofCallBuilder, DirectReceiveMessagesProofCallBuilder, SubstrateMessageLane, @@ -31,6 +27,13 @@ use substrate_relay_helper::messages_lane::{ /// Description of Millau -> Rialto messages bridge. #[derive(Clone, Debug)] pub struct MillauMessagesToRialto; +substrate_relay_helper::generate_direct_update_conversion_rate_call_builder!( + Millau, + MillauMessagesToRialtoUpdateConversionRateCallBuilder, + millau_runtime::Runtime, + millau_runtime::WithRialtoMessagesInstance, + millau_runtime::rialto_messages::MillauToRialtoMessagesParameter::RialtoToMillauConversionRate +); impl SubstrateMessageLane for MillauMessagesToRialto { const SOURCE_TO_TARGET_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> = @@ -60,40 +63,8 @@ impl SubstrateMessageLane for MillauMessagesToRialto { millau_runtime::WithRialtoMessagesInstance, >; - type RelayStrategy = MixStrategy; -} + type TargetToSourceChainConversionRateUpdateBuilder = + MillauMessagesToRialtoUpdateConversionRateCallBuilder; -/// Update Rialto -> Millau conversion rate, stored in Millau runtime storage. -pub(crate) async fn update_rialto_to_millau_conversion_rate( - client: Client, - signer: ::AccountKeyPair, - updated_rate: f64, -) -> anyhow::Result<()> { - let genesis_hash = *client.genesis_hash(); - let signer_id = (*signer.public().as_array_ref()).into(); - let (spec_version, transaction_version) = client.simple_runtime_version().await?; - client - .submit_signed_extrinsic(signer_id, move |_, transaction_nonce| { - Ok(Bytes( - Millau::sign_transaction(SignParam { - spec_version, - transaction_version, - genesis_hash, - signer, - era: relay_substrate_client::TransactionEra::immortal(), - unsigned: UnsignedTransaction::new( - millau_runtime::Call::from(millau_runtime::MessagesCall::update_pallet_parameter { - parameter: millau_runtime::rialto_messages::MillauToRialtoMessagesParameter::RialtoToMillauConversionRate( - sp_runtime::FixedU128::from_float(updated_rate), - ), - }).into(), - transaction_nonce, - ), - })? - .encode(), - )) - }) - .await - .map(drop) - .map_err(|err| anyhow::format_err!("{:?}", err)) + type RelayStrategy = MixStrategy; } 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 9f8ec346db266..9c4a4640eb99d 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,14 +16,10 @@ //! Polkadot-to-Kusama messages sync entrypoint. -use codec::Encode; -use sp_core::{Bytes, Pair}; - use frame_support::weights::Weight; use messages_relay::relay_strategy::MixStrategy; use relay_kusama_client::Kusama; use relay_polkadot_client::Polkadot; -use relay_substrate_client::{Client, SignParam, TransactionSignScheme, UnsignedTransaction}; use substrate_relay_helper::messages_lane::SubstrateMessageLane; /// Description of Polkadot -> Kusama messages bridge. @@ -41,6 +37,13 @@ substrate_relay_helper::generate_mocked_receive_message_delivery_proof_call_buil relay_polkadot_client::runtime::Call::BridgeKusamaMessages, relay_polkadot_client::runtime::BridgeKusamaMessagesCall::receive_messages_delivery_proof ); +substrate_relay_helper::generate_mocked_update_conversion_rate_call_builder!( + Polkadot, + PolkadotMessagesToKusamaUpdateConversionRateCallBuilder, + relay_polkadot_client::runtime::Call::BridgeKusamaMessages, + relay_polkadot_client::runtime::BridgeKusamaMessagesCall::update_pallet_parameter, + relay_polkadot_client::runtime::BridgeKusamaMessagesParameter::KusamaToPolkadotConversionRate +); impl SubstrateMessageLane for PolkadotMessagesToKusama { const SOURCE_TO_TARGET_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> = @@ -68,42 +71,8 @@ impl SubstrateMessageLane for PolkadotMessagesToKusama { type ReceiveMessagesDeliveryProofCallBuilder = PolkadotMessagesToKusamaReceiveMessagesDeliveryProofCallBuilder; - type RelayStrategy = MixStrategy; -} + type TargetToSourceChainConversionRateUpdateBuilder = + PolkadotMessagesToKusamaUpdateConversionRateCallBuilder; -/// Update Kusama -> Polkadot conversion rate, stored in Polkadot runtime storage. -pub(crate) async fn update_kusama_to_polkadot_conversion_rate( - client: Client, - signer: ::AccountKeyPair, - updated_rate: f64, -) -> anyhow::Result<()> { - let genesis_hash = *client.genesis_hash(); - let signer_id = (*signer.public().as_array_ref()).into(); - let (spec_version, transaction_version) = client.simple_runtime_version().await?; - client - .submit_signed_extrinsic(signer_id, move |_, transaction_nonce| { - Ok(Bytes( - Polkadot::sign_transaction(SignParam { - spec_version, - transaction_version, - genesis_hash, - signer, - era: relay_substrate_client::TransactionEra::immortal(), - unsigned: UnsignedTransaction::new( - relay_polkadot_client::runtime::Call::BridgeKusamaMessages( - relay_polkadot_client::runtime::BridgeKusamaMessagesCall::update_pallet_parameter( - relay_polkadot_client::runtime::BridgeKusamaMessagesParameter::KusamaToPolkadotConversionRate( - sp_runtime::FixedU128::from_float(updated_rate), - ) - ) - ).into(), - transaction_nonce, - ) - })? - .encode(), - )) - }) - .await - .map(drop) - .map_err(|err| anyhow::format_err!("{:?}", err)) + type RelayStrategy = MixStrategy; } 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 6de10d2346f93..d34f471464492 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,13 +16,9 @@ //! Rialto-to-Millau messages sync entrypoint. -use codec::Encode; -use sp_core::{Bytes, Pair}; - use messages_relay::relay_strategy::MixStrategy; use relay_millau_client::Millau; use relay_rialto_client::Rialto; -use relay_substrate_client::{Client, SignParam, TransactionSignScheme, UnsignedTransaction}; use substrate_relay_helper::messages_lane::{ DirectReceiveMessagesDeliveryProofCallBuilder, DirectReceiveMessagesProofCallBuilder, SubstrateMessageLane, @@ -31,6 +27,13 @@ use substrate_relay_helper::messages_lane::{ /// Description of Rialto -> Millau messages bridge. #[derive(Clone, Debug)] pub struct RialtoMessagesToMillau; +substrate_relay_helper::generate_direct_update_conversion_rate_call_builder!( + Rialto, + RialtoMessagesToMillauUpdateConversionRateCallBuilder, + rialto_runtime::Runtime, + rialto_runtime::WithMillauMessagesInstance, + rialto_runtime::millau_messages::RialtoToMillauMessagesParameter::MillauToRialtoConversionRate +); impl SubstrateMessageLane for RialtoMessagesToMillau { const SOURCE_TO_TARGET_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> = @@ -60,40 +63,8 @@ impl SubstrateMessageLane for RialtoMessagesToMillau { rialto_runtime::WithMillauMessagesInstance, >; - type RelayStrategy = MixStrategy; -} + type TargetToSourceChainConversionRateUpdateBuilder = + RialtoMessagesToMillauUpdateConversionRateCallBuilder; -/// Update Millau -> Rialto conversion rate, stored in Rialto runtime storage. -pub(crate) async fn update_millau_to_rialto_conversion_rate( - client: Client, - signer: ::AccountKeyPair, - updated_rate: f64, -) -> anyhow::Result<()> { - let genesis_hash = *client.genesis_hash(); - let signer_id = (*signer.public().as_array_ref()).into(); - let (spec_version, transaction_version) = client.simple_runtime_version().await?; - client - .submit_signed_extrinsic(signer_id, move |_, transaction_nonce| { - Ok(Bytes( - Rialto::sign_transaction(SignParam { - spec_version, - transaction_version, - genesis_hash, - signer, - era: relay_substrate_client::TransactionEra::immortal(), - unsigned: UnsignedTransaction::new( - rialto_runtime::Call::from(rialto_runtime::MessagesCall::update_pallet_parameter { - parameter: rialto_runtime::millau_messages::RialtoToMillauMessagesParameter::MillauToRialtoConversionRate( - sp_runtime::FixedU128::from_float(updated_rate), - ), - }).into(), - transaction_nonce, - ) - })? - .encode(), - )) - }) - .await - .map(drop) - .map_err(|err| anyhow::format_err!("{:?}", err)) + type RelayStrategy = MixStrategy; } 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 48e41d02da4e0..4e67c87fa8c45 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 @@ -57,5 +57,7 @@ impl SubstrateMessageLane for RococoMessagesToWococo { type ReceiveMessagesDeliveryProofCallBuilder = RococoMessagesToWococoReceiveMessagesDeliveryProofCallBuilder; + type TargetToSourceChainConversionRateUpdateBuilder = (); + type RelayStrategy = MixStrategy; } 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 33d7d9adfcc42..2c44803f2c06a 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 @@ -58,5 +58,7 @@ impl SubstrateMessageLane for WococoMessagesToRococo { type ReceiveMessagesDeliveryProofCallBuilder = WococoMessagesToRococoReceiveMessagesDeliveryProofCallBuilder; + type TargetToSourceChainConversionRateUpdateBuilder = (); + type RelayStrategy = MixStrategy; } 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 9f19d82bdd405..4ff6ee0947cb2 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 @@ -134,14 +134,8 @@ macro_rules! select_bridge { type RightAccountIdConverter = bp_rialto::AccountIdConverter; use crate::chains::{ - millau_messages_to_rialto::{ - update_rialto_to_millau_conversion_rate as update_right_to_left_conversion_rate, - MillauMessagesToRialto as LeftToRightMessageLane, - }, - rialto_messages_to_millau::{ - update_millau_to_rialto_conversion_rate as update_left_to_right_conversion_rate, - RialtoMessagesToMillau as RightToLeftMessageLane, - }, + millau_messages_to_rialto::MillauMessagesToRialto as LeftToRightMessageLane, + rialto_messages_to_millau::RialtoMessagesToMillau as RightToLeftMessageLane, }; async fn left_create_account( @@ -181,22 +175,6 @@ macro_rules! select_bridge { wococo_messages_to_rococo::WococoMessagesToRococo as RightToLeftMessageLane, }; - async fn update_right_to_left_conversion_rate( - _client: Client, - _signer: ::AccountKeyPair, - _updated_rate: f64, - ) -> anyhow::Result<()> { - Err(anyhow::format_err!("Conversion rate is not supported by this bridge")) - } - - async fn update_left_to_right_conversion_rate( - _client: Client, - _signer: ::AccountKeyPair, - _updated_rate: f64, - ) -> anyhow::Result<()> { - Err(anyhow::format_err!("Conversion rate is not supported by this bridge")) - } - async fn left_create_account( left_client: Client, left_sign: ::AccountKeyPair, @@ -250,14 +228,8 @@ macro_rules! select_bridge { type RightAccountIdConverter = bp_polkadot::AccountIdConverter; use crate::chains::{ - kusama_messages_to_polkadot::{ - update_polkadot_to_kusama_conversion_rate as update_right_to_left_conversion_rate, - KusamaMessagesToPolkadot as LeftToRightMessageLane, - }, - polkadot_messages_to_kusama::{ - update_kusama_to_polkadot_conversion_rate as update_left_to_right_conversion_rate, - PolkadotMessagesToKusama as RightToLeftMessageLane, - }, + kusama_messages_to_polkadot::KusamaMessagesToPolkadot as LeftToRightMessageLane, + polkadot_messages_to_kusama::PolkadotMessagesToKusama as RightToLeftMessageLane, }; async fn left_create_account( @@ -354,7 +326,15 @@ impl RelayHeadersAndMessages { Left::NAME ) }; - substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop( + substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop::< + LeftToRightMessageLane, + Left, + >( + left_client.clone(), + TransactionParams { + signer: left_messages_pallet_owner.clone(), + mortality: left_transactions_mortality, + }, left_to_right_metrics .target_to_source_conversion_rate .as_ref() @@ -371,21 +351,6 @@ impl RelayHeadersAndMessages { .ok_or_else(format_err)? .shared_value_ref(), CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO, - move |new_rate| { - log::info!( - target: "bridge", - "Going to update {} -> {} (on {}) conversion rate to {}.", - Right::NAME, - Left::NAME, - Left::NAME, - new_rate, - ); - update_right_to_left_conversion_rate( - left_client.clone(), - left_messages_pallet_owner.clone(), - new_rate, - ) - }, ); } if let Some(right_messages_pallet_owner) = right_messages_pallet_owner.clone() { @@ -397,7 +362,15 @@ impl RelayHeadersAndMessages { Right::NAME ) }; - substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop( + substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop::< + RightToLeftMessageLane, + Right, + >( + right_client.clone(), + TransactionParams { + signer: right_messages_pallet_owner.clone(), + mortality: right_transactions_mortality, + }, right_to_left_metrics .target_to_source_conversion_rate .as_ref() @@ -414,21 +387,6 @@ impl RelayHeadersAndMessages { .ok_or_else(format_err)? .shared_value_ref(), CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO, - move |new_rate| { - log::info!( - target: "bridge", - "Going to update {} -> {} (on {}) conversion rate to {}.", - Left::NAME, - Right::NAME, - Right::NAME, - new_rate, - ); - update_left_to_right_conversion_rate( - right_client.clone(), - right_messages_pallet_owner.clone(), - new_rate, - ) - }, ); } diff --git a/bridges/relays/lib-substrate-relay/src/conversion_rate_update.rs b/bridges/relays/lib-substrate-relay/src/conversion_rate_update.rs index 482e6e4c86166..469bc5589932b 100644 --- a/bridges/relays/lib-substrate-relay/src/conversion_rate_update.rs +++ b/bridges/relays/lib-substrate-relay/src/conversion_rate_update.rs @@ -16,39 +16,143 @@ //! Tools for updating conversion rate that is stored in the runtime storage. +use crate::{messages_lane::SubstrateMessageLane, TransactionParams}; + +use codec::Encode; +use relay_substrate_client::{ + transaction_stall_timeout, AccountIdOf, AccountKeyPairOf, CallOf, Chain, Client, SignParam, + TransactionEra, TransactionSignScheme, UnsignedTransaction, +}; use relay_utils::metrics::F64SharedRef; -use std::{future::Future, time::Duration}; +use sp_core::{Bytes, Pair}; +use std::time::{Duration, Instant}; /// Duration between updater iterations. const SLEEP_DURATION: Duration = Duration::from_secs(60); +/// Duration which will almost never expire. Since changing conversion rate may require manual +/// intervention (e.g. if call is made through `multisig` pallet), we don't want relayer to +/// resubmit transaction often. +const ALMOST_NEVER_DURATION: Duration = Duration::from_secs(60 * 60 * 24 * 30); + /// Update-conversion-rate transaction status. #[derive(Debug, Clone, Copy, PartialEq)] enum TransactionStatus { /// We have not submitted any transaction recently. Idle, /// We have recently submitted transaction that should update conversion rate. - Submitted(f64), + Submitted(Instant, f64), +} + +/// Different ways of building 'update conversion rate' calls. +pub trait UpdateConversionRateCallBuilder { + /// Given conversion rate, build call that updates conversion rate in given chain runtime + /// storage. + fn build_update_conversion_rate_call(conversion_rate: f64) -> anyhow::Result>; +} + +impl UpdateConversionRateCallBuilder for () { + fn build_update_conversion_rate_call(_conversion_rate: f64) -> anyhow::Result> { + Err(anyhow::format_err!("Conversion rate update is not supported at {}", C::NAME)) + } +} + +/// Macro that generates `UpdateConversionRateCallBuilder` implementation for the case when +/// you have a direct access to the source chain runtime. +#[rustfmt::skip] +#[macro_export] +macro_rules! generate_direct_update_conversion_rate_call_builder { + ( + $source_chain:ident, + $mocked_builder:ident, + $runtime:ty, + $instance:ty, + $parameter:path + ) => { + pub struct $mocked_builder; + + impl $crate::conversion_rate_update::UpdateConversionRateCallBuilder<$source_chain> + for $mocked_builder + { + fn build_update_conversion_rate_call( + conversion_rate: f64, + ) -> anyhow::Result> { + Ok(pallet_bridge_messages::Call::update_pallet_parameter::<$runtime, $instance> { + parameter: $parameter(sp_runtime::FixedU128::from_float(conversion_rate)), + }.into()) + } + } + }; +} + +/// Macro that generates `UpdateConversionRateCallBuilder` implementation for the case when +/// you only have an access to the mocked version of source chain runtime. In this case you +/// should provide "name" of the call variant for the bridge messages calls, the "name" of +/// the variant for the `update_pallet_parameter` call within that first option and the name +/// of the conversion rate parameter itself. +#[rustfmt::skip] +#[macro_export] +macro_rules! generate_mocked_update_conversion_rate_call_builder { + ( + $source_chain:ident, + $mocked_builder:ident, + $bridge_messages:path, + $update_pallet_parameter:path, + $parameter:path + ) => { + pub struct $mocked_builder; + + impl $crate::conversion_rate_update::UpdateConversionRateCallBuilder<$source_chain> + for $mocked_builder + { + fn build_update_conversion_rate_call( + conversion_rate: f64, + ) -> anyhow::Result> { + Ok($bridge_messages($update_pallet_parameter($parameter( + sp_runtime::FixedU128::from_float(conversion_rate), + )))) + } + } + }; } /// Run infinite conversion rate updater loop. /// /// The loop is maintaining the Left -> Right conversion rate, used as `RightTokens = LeftTokens * /// Rate`. -pub fn run_conversion_rate_update_loop< - SubmitConversionRateFuture: Future> + Send + 'static, ->( +pub fn run_conversion_rate_update_loop( + client: Client, + transaction_params: TransactionParams>, left_to_right_stored_conversion_rate: F64SharedRef, left_to_base_conversion_rate: F64SharedRef, right_to_base_conversion_rate: F64SharedRef, max_difference_ratio: f64, - submit_conversion_rate: impl Fn(f64) -> SubmitConversionRateFuture + Send + 'static, -) { +) where + Lane: SubstrateMessageLane, + Sign: TransactionSignScheme, + AccountIdOf: From< as Pair>::Public>, +{ + let stall_timeout = transaction_stall_timeout( + transaction_params.mortality, + Lane::SourceChain::AVERAGE_BLOCK_INTERVAL, + ALMOST_NEVER_DURATION, + ); + + log::info!( + target: "bridge", + "Starting {} -> {} conversion rate (on {}) update loop. Stall timeout: {}s", + Lane::TargetChain::NAME, + Lane::SourceChain::NAME, + Lane::SourceChain::NAME, + stall_timeout.as_secs(), + ); + async_std::task::spawn(async move { let mut transaction_status = TransactionStatus::Idle; loop { async_std::task::sleep(SLEEP_DURATION).await; let maybe_new_conversion_rate = maybe_select_new_conversion_rate( + stall_timeout, &mut transaction_status, &left_to_right_stored_conversion_rate, &left_to_base_conversion_rate, @@ -57,13 +161,32 @@ pub fn run_conversion_rate_update_loop< ) .await; if let Some((prev_conversion_rate, new_conversion_rate)) = maybe_new_conversion_rate { - let submit_conversion_rate_future = submit_conversion_rate(new_conversion_rate); - match submit_conversion_rate_future.await { + log::info!( + target: "bridge", + "Going to update {} -> {} (on {}) conversion rate to {}.", + Lane::TargetChain::NAME, + Lane::SourceChain::NAME, + Lane::SourceChain::NAME, + new_conversion_rate, + ); + + let result = update_target_to_source_conversion_rate::( + client.clone(), + transaction_params.clone(), + new_conversion_rate, + ) + .await; + match result { Ok(()) => { - transaction_status = TransactionStatus::Submitted(prev_conversion_rate); + transaction_status = + TransactionStatus::Submitted(Instant::now(), prev_conversion_rate); }, Err(error) => { - log::trace!(target: "bridge", "Failed to submit conversion rate update transaction: {:?}", error); + log::error!( + target: "bridge", + "Failed to submit conversion rate update transaction: {:?}", + error, + ); }, } } @@ -73,6 +196,7 @@ pub fn run_conversion_rate_update_loop< /// Select new conversion rate to submit to the node. async fn maybe_select_new_conversion_rate( + stall_timeout: Duration, transaction_status: &mut TransactionStatus, left_to_right_stored_conversion_rate: &F64SharedRef, left_to_base_conversion_rate: &F64SharedRef, @@ -83,7 +207,18 @@ async fn maybe_select_new_conversion_rate( (*left_to_right_stored_conversion_rate.read().await)?; match *transaction_status { TransactionStatus::Idle => (), - TransactionStatus::Submitted(previous_left_to_right_stored_conversion_rate) => { + TransactionStatus::Submitted(submitted_at, _) + if Instant::now() - submitted_at > stall_timeout => + { + log::error!( + target: "bridge", + "Conversion rate update transaction has been lost and loop stalled. Restarting", + ); + + // we assume that our transaction has been lost + *transaction_status = TransactionStatus::Idle; + }, + TransactionStatus::Submitted(_, previous_left_to_right_stored_conversion_rate) => { // we can't compare float values from different sources directly, so we only care // whether the stored rate has been changed or not. If it has been changed, then we // assume that our proposal has been accepted. @@ -118,11 +253,50 @@ async fn maybe_select_new_conversion_rate( Some((left_to_right_stored_conversion_rate, actual_left_to_right_conversion_rate)) } +/// Update Target -> Source tokens conversion rate, stored in the Source runtime storage. +pub async fn update_target_to_source_conversion_rate( + client: Client, + transaction_params: TransactionParams>, + updated_rate: f64, +) -> anyhow::Result<()> +where + Lane: SubstrateMessageLane, + Sign: TransactionSignScheme, + AccountIdOf: From< as Pair>::Public>, +{ + let genesis_hash = *client.genesis_hash(); + let signer_id = transaction_params.signer.public().into(); + let (spec_version, transaction_version) = client.simple_runtime_version().await?; + let call = + Lane::TargetToSourceChainConversionRateUpdateBuilder::build_update_conversion_rate_call( + updated_rate, + )?; + client + .submit_signed_extrinsic(signer_id, move |best_block_id, transaction_nonce| { + Ok(Bytes( + Sign::sign_transaction(SignParam { + spec_version, + transaction_version, + genesis_hash, + signer: transaction_params.signer, + era: TransactionEra::new(best_block_id, transaction_params.mortality), + unsigned: UnsignedTransaction::new(call.into(), transaction_nonce).into(), + })? + .encode(), + )) + }) + .await + .map(drop) + .map_err(|err| anyhow::format_err!("{:?}", err)) +} + #[cfg(test)] mod tests { use super::*; use async_std::sync::{Arc, RwLock}; + const TEST_STALL_TIMEOUT: Duration = Duration::from_secs(60); + fn test_maybe_select_new_conversion_rate( mut transaction_status: TransactionStatus, stored_conversion_rate: Option, @@ -134,6 +308,7 @@ mod tests { let left_to_base_conversion_rate = Arc::new(RwLock::new(left_to_base_conversion_rate)); let right_to_base_conversion_rate = Arc::new(RwLock::new(right_to_base_conversion_rate)); let result = async_std::task::block_on(maybe_select_new_conversion_rate( + TEST_STALL_TIMEOUT, &mut transaction_status, &stored_conversion_rate, &left_to_base_conversion_rate, @@ -145,15 +320,10 @@ mod tests { #[test] fn rate_is_not_updated_when_transaction_is_submitted() { + let status = TransactionStatus::Submitted(Instant::now(), 10.0); assert_eq!( - test_maybe_select_new_conversion_rate( - TransactionStatus::Submitted(10.0), - Some(10.0), - Some(1.0), - Some(1.0), - 0.0 - ), - (None, TransactionStatus::Submitted(10.0)), + test_maybe_select_new_conversion_rate(status, Some(10.0), Some(1.0), Some(1.0), 0.0), + (None, status), ); } @@ -161,7 +331,7 @@ mod tests { fn transaction_state_is_changed_to_idle_when_stored_rate_shanges() { assert_eq!( test_maybe_select_new_conversion_rate( - TransactionStatus::Submitted(1.0), + TransactionStatus::Submitted(Instant::now(), 1.0), Some(10.0), Some(1.0), Some(1.0), @@ -252,4 +422,19 @@ mod tests { ), ); } + + #[test] + fn transaction_expires() { + let status = TransactionStatus::Submitted(Instant::now() - TEST_STALL_TIMEOUT / 2, 10.0); + assert_eq!( + test_maybe_select_new_conversion_rate(status, Some(10.0), Some(1.0), Some(1.0), 0.0), + (None, status), + ); + + let status = TransactionStatus::Submitted(Instant::now() - TEST_STALL_TIMEOUT * 2, 10.0); + assert_eq!( + test_maybe_select_new_conversion_rate(status, Some(10.0), Some(1.0), Some(1.0), 0.0), + (Some((10.0, 1.0)), TransactionStatus::Idle), + ); + } } diff --git a/bridges/relays/lib-substrate-relay/src/messages_lane.rs b/bridges/relays/lib-substrate-relay/src/messages_lane.rs index 2da434c8c7c68..fadf5e62245ad 100644 --- a/bridges/relays/lib-substrate-relay/src/messages_lane.rs +++ b/bridges/relays/lib-substrate-relay/src/messages_lane.rs @@ -17,6 +17,7 @@ //! Tools for supporting message lanes between two Substrate-based chains. use crate::{ + conversion_rate_update::UpdateConversionRateCallBuilder, messages_metrics::StandaloneMessagesMetrics, messages_source::{SubstrateMessagesProof, SubstrateMessagesSource}, messages_target::{SubstrateMessagesDeliveryProof, SubstrateMessagesTarget}, @@ -89,6 +90,13 @@ pub trait SubstrateMessageLane: 'static + Clone + Debug + Send + Sync { /// How receive messages delivery proof call is built? type ReceiveMessagesDeliveryProofCallBuilder: ReceiveMessagesDeliveryProofCallBuilder; + /// `TargetChain` tokens to `SourceChain` tokens conversion rate update builder. + /// + /// If not applicable to this bridge, you may use `()` here. + type TargetToSourceChainConversionRateUpdateBuilder: UpdateConversionRateCallBuilder< + Self::SourceChain, + >; + /// Message relay strategy. type RelayStrategy: RelayStrategy; }