Remove message fee + message send calls (#1642)

* remove message fee

* it is compiling!

* fixes + fmt

* more cleanup

* more cleanup

* restore MessageDeliveryAndDispatchPayment since we'll need relayer rewards

* started rational relayer removal

* more removal

* removed estimate fee subcommand

* remove DispatchFeePayment

* more removals

* removed conversion rates && some metrics

* - unneeded associated type

* - OutboundMessageFee

* fix benchmarks compilation

* fmt

* test + fix benchmarks

* fix send message

* clippy
This commit is contained in:
Svyatoslav Nikolsky
2022-11-18 12:24:45 +03:00
committed by Bastian Köcher
parent 1217b2cf80
commit 8c845602cf
92 changed files with 589 additions and 5796 deletions
@@ -1,439 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Tools for updating conversion rate that is stored in the runtime storage.
use crate::{messages_lane::SubstrateMessageLane, TransactionParams};
use relay_substrate_client::{
transaction_stall_timeout, AccountIdOf, AccountKeyPairOf, CallOf, Chain, ChainWithTransactions,
Client, SignParam, TransactionEra, UnsignedTransaction,
};
use relay_utils::metrics::F64SharedRef;
use sp_core::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(Instant, f64),
}
/// Different ways of building 'update conversion rate' calls.
pub trait UpdateConversionRateCallBuilder<C: Chain> {
/// 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<CallOf<C>>;
}
impl<C: Chain> UpdateConversionRateCallBuilder<C> for () {
fn build_update_conversion_rate_call(_conversion_rate: f64) -> anyhow::Result<CallOf<C>> {
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<relay_substrate_client::CallOf<$source_chain>> {
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<relay_substrate_client::CallOf<$source_chain>> {
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<Lane>(
client: Client<Lane::SourceChain>,
transaction_params: TransactionParams<AccountKeyPairOf<Lane::SourceChain>>,
left_to_right_stored_conversion_rate: F64SharedRef,
left_to_base_conversion_rate: F64SharedRef,
right_to_base_conversion_rate: F64SharedRef,
max_difference_ratio: f64,
) where
Lane: SubstrateMessageLane,
Lane::SourceChain: ChainWithTransactions,
AccountIdOf<Lane::SourceChain>: From<<AccountKeyPairOf<Lane::SourceChain> 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,
&right_to_base_conversion_rate,
max_difference_ratio,
)
.await;
if let Some((prev_conversion_rate, new_conversion_rate)) = maybe_new_conversion_rate {
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::<Lane>(
client.clone(),
transaction_params.clone(),
new_conversion_rate,
)
.await;
match result {
Ok(()) => {
transaction_status =
TransactionStatus::Submitted(Instant::now(), prev_conversion_rate);
},
Err(error) => {
log::error!(
target: "bridge",
"Failed to submit conversion rate update transaction: {:?}",
error,
);
},
}
}
}
});
}
/// 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,
right_to_base_conversion_rate: &F64SharedRef,
max_difference_ratio: f64,
) -> Option<(f64, f64)> {
let left_to_right_stored_conversion_rate =
(*left_to_right_stored_conversion_rate.read().await)?;
match *transaction_status {
TransactionStatus::Idle => (),
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.
//
// float comparison is ok here, because we compare same-origin (stored in runtime
// storage) values and if they are different, it means that the value has actually been
// updated
#[allow(clippy::float_cmp)]
if previous_left_to_right_stored_conversion_rate == left_to_right_stored_conversion_rate
{
// the rate has not been changed => we won't submit any transactions until it is
// accepted, or the rate is changed by someone else
return None
}
*transaction_status = TransactionStatus::Idle;
},
}
let left_to_base_conversion_rate = (*left_to_base_conversion_rate.read().await)?;
let right_to_base_conversion_rate = (*right_to_base_conversion_rate.read().await)?;
let actual_left_to_right_conversion_rate =
left_to_base_conversion_rate / right_to_base_conversion_rate;
let rate_difference =
(actual_left_to_right_conversion_rate - left_to_right_stored_conversion_rate).abs();
let rate_difference_ratio = rate_difference / left_to_right_stored_conversion_rate;
if rate_difference_ratio < max_difference_ratio {
return None
}
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<Lane>(
client: Client<Lane::SourceChain>,
transaction_params: TransactionParams<AccountKeyPairOf<Lane::SourceChain>>,
updated_rate: f64,
) -> anyhow::Result<()>
where
Lane: SubstrateMessageLane,
Lane::SourceChain: ChainWithTransactions,
AccountIdOf<Lane::SourceChain>: From<<AccountKeyPairOf<Lane::SourceChain> 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,
SignParam::<Lane::SourceChain> {
spec_version,
transaction_version,
genesis_hash,
signer: transaction_params.signer,
},
move |best_block_id, transaction_nonce| {
Ok(UnsignedTransaction::new(call.into(), transaction_nonce)
.era(TransactionEra::new(best_block_id, transaction_params.mortality)))
},
)
.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<f64>,
left_to_base_conversion_rate: Option<f64>,
right_to_base_conversion_rate: Option<f64>,
max_difference_ratio: f64,
) -> (Option<(f64, f64)>, TransactionStatus) {
let stored_conversion_rate = Arc::new(RwLock::new(stored_conversion_rate));
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,
&right_to_base_conversion_rate,
max_difference_ratio,
));
(result, transaction_status)
}
#[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(status, Some(10.0), Some(1.0), Some(1.0), 0.0),
(None, status),
);
}
#[test]
fn transaction_state_is_changed_to_idle_when_stored_rate_shanges() {
assert_eq!(
test_maybe_select_new_conversion_rate(
TransactionStatus::Submitted(Instant::now(), 1.0),
Some(10.0),
Some(1.0),
Some(1.0),
100.0
),
(None, TransactionStatus::Idle),
);
}
#[test]
fn transaction_is_not_submitted_when_left_to_base_rate_is_unknown() {
assert_eq!(
test_maybe_select_new_conversion_rate(
TransactionStatus::Idle,
Some(10.0),
None,
Some(1.0),
0.0
),
(None, TransactionStatus::Idle),
);
}
#[test]
fn transaction_is_not_submitted_when_right_to_base_rate_is_unknown() {
assert_eq!(
test_maybe_select_new_conversion_rate(
TransactionStatus::Idle,
Some(10.0),
Some(1.0),
None,
0.0
),
(None, TransactionStatus::Idle),
);
}
#[test]
fn transaction_is_not_submitted_when_stored_rate_is_unknown() {
assert_eq!(
test_maybe_select_new_conversion_rate(
TransactionStatus::Idle,
None,
Some(1.0),
Some(1.0),
0.0
),
(None, TransactionStatus::Idle),
);
}
#[test]
fn transaction_is_not_submitted_when_difference_is_below_threshold() {
assert_eq!(
test_maybe_select_new_conversion_rate(
TransactionStatus::Idle,
Some(1.0),
Some(1.0),
Some(1.01),
0.02
),
(None, TransactionStatus::Idle),
);
}
#[test]
fn transaction_is_submitted_when_difference_is_above_threshold() {
let left_to_right_stored_conversion_rate = 1.0;
let left_to_base_conversion_rate = 18f64;
let right_to_base_conversion_rate = 180f64;
assert!(left_to_base_conversion_rate < right_to_base_conversion_rate);
assert_eq!(
test_maybe_select_new_conversion_rate(
TransactionStatus::Idle,
Some(left_to_right_stored_conversion_rate),
Some(left_to_base_conversion_rate),
Some(right_to_base_conversion_rate),
0.02
),
(
Some((
left_to_right_stored_conversion_rate,
left_to_base_conversion_rate / right_to_base_conversion_rate,
)),
TransactionStatus::Idle
),
);
}
#[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),
);
}
}
@@ -1,106 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Substrate relay helpers
use relay_utils::metrics::{FloatJsonValueMetric, PrometheusError, StandaloneMetric};
/// Creates standalone token price metric.
pub fn token_price_metric(token_id: &str) -> Result<FloatJsonValueMetric, PrometheusError> {
FloatJsonValueMetric::new(
format!("https://api.coingecko.com/api/v3/simple/price?ids={token_id}&vs_currencies=btc"),
format!("$.{token_id}.btc"),
format!("{}_to_base_conversion_rate", token_id.replace('-', "_")),
format!("Rate used to convert from {} to some BASE tokens", token_id.to_uppercase()),
)
}
/// Compute conversion rate between two tokens immediately, without spawning any metrics.
///
/// Returned rate may be used in expression: `from_tokens * rate -> to_tokens`.
pub async fn tokens_conversion_rate_from_metrics(
from_token_id: &str,
to_token_id: &str,
) -> anyhow::Result<f64> {
let from_token_metric = token_price_metric(from_token_id)?;
from_token_metric.update().await;
let to_token_metric = token_price_metric(to_token_id)?;
to_token_metric.update().await;
let from_token_value = *from_token_metric.shared_value_ref().read().await;
let to_token_value = *to_token_metric.shared_value_ref().read().await;
// `FloatJsonValueMetric` guarantees that the value is positive && normal, so no additional
// checks required here
match (from_token_value, to_token_value) {
(Some(from_token_value), Some(to_token_value)) =>
Ok(tokens_conversion_rate(from_token_value, to_token_value)),
_ => Err(anyhow::format_err!(
"Failed to compute conversion rate from {} to {}",
from_token_id,
to_token_id,
)),
}
}
/// Compute conversion rate between two tokens, given token prices.
///
/// Returned rate may be used in expression: `from_tokens * rate -> to_tokens`.
///
/// Both prices are assumed to be normal and non-negative.
pub fn tokens_conversion_rate(from_token_value: f64, to_token_value: f64) -> f64 {
from_token_value / to_token_value
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rialto_to_millau_conversion_rate_is_correct() {
let rialto_price = 18.18;
let millau_price = 136.35;
assert!(rialto_price < millau_price);
let conversion_rate = tokens_conversion_rate(rialto_price, millau_price);
let rialto_amount = 100.0;
let millau_amount = rialto_amount * conversion_rate;
assert!(
rialto_amount > millau_amount,
"{} RLT * {} = {} MLU",
rialto_amount,
conversion_rate,
millau_amount,
);
}
#[test]
fn millau_to_rialto_conversion_rate_is_correct() {
let rialto_price = 18.18;
let millau_price = 136.35;
assert!(rialto_price < millau_price);
let conversion_rate = tokens_conversion_rate(millau_price, rialto_price);
let millau_amount = 100.0;
let rialto_amount = millau_amount * conversion_rate;
assert!(
rialto_amount > millau_amount,
"{} MLU * {} = {} RLT",
millau_amount,
conversion_rate,
rialto_amount,
);
}
}
@@ -18,10 +18,8 @@
#![warn(missing_docs)]
pub mod conversion_rate_update;
pub mod error;
pub mod finality;
pub mod helpers;
pub mod messages_lane;
pub mod messages_metrics;
pub mod messages_source;
@@ -17,8 +17,6 @@
//! 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},
on_demand::OnDemandRelay,
@@ -33,49 +31,21 @@ use bridge_runtime_common::messages::{
};
use codec::Encode;
use frame_support::{dispatch::GetDispatchInfo, weights::Weight};
use messages_relay::{message_lane::MessageLane, relay_strategy::RelayStrategy};
use messages_relay::message_lane::MessageLane;
use pallet_bridge_messages::{Call as BridgeMessagesCall, Config as BridgeMessagesConfig};
use relay_substrate_client::{
transaction_stall_timeout, AccountKeyPairOf, BalanceOf, BlockNumberOf, CallOf, Chain,
ChainWithMessages, ChainWithTransactions, Client, HashOf,
};
use relay_utils::{metrics::MetricsParams, STALL_TIMEOUT};
use relay_utils::{
metrics::{GlobalMetrics, MetricsParams, StandaloneMetric},
STALL_TIMEOUT,
};
use sp_core::Pair;
use std::{convert::TryFrom, fmt::Debug, marker::PhantomData};
/// Substrate -> Substrate messages synchronization pipeline.
pub trait SubstrateMessageLane: 'static + Clone + Debug + Send + Sync {
/// Name of the source -> target tokens conversion rate parameter.
///
/// The parameter is stored at the target chain and the storage key is computed using
/// `bp_runtime::storage_parameter_key` function. If value is unknown, it is assumed
/// to be 1.
const SOURCE_TO_TARGET_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str>;
/// Name of the target -> source tokens conversion rate parameter.
///
/// The parameter is stored at the source chain and the storage key is computed using
/// `bp_runtime::storage_parameter_key` function. If value is unknown, it is assumed
/// to be 1.
const TARGET_TO_SOURCE_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str>;
/// Name of the source chain fee multiplier parameter.
///
/// The parameter is stored at the target chain and the storage key is computed using
/// `bp_runtime::storage_parameter_key` function. If value is unknown, it is assumed
/// to be 1.
const SOURCE_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str>;
/// Name of the target chain fee multiplier parameter.
///
/// The parameter is stored at the source chain and the storage key is computed using
/// `bp_runtime::storage_parameter_key` function. If value is unknown, it is assumed
/// to be 1.
const TARGET_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str>;
/// Name of the transaction payment pallet, deployed at the source chain.
const AT_SOURCE_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str>;
/// Name of the transaction payment pallet, deployed at the target chain.
const AT_TARGET_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str>;
/// Messages of this chain are relayed to the `TargetChain`.
type SourceChain: ChainWithMessages + ChainWithTransactions;
/// Messages from the `SourceChain` are dispatched on this chain.
@@ -85,16 +55,6 @@ pub trait SubstrateMessageLane: 'static + Clone + Debug + Send + Sync {
type ReceiveMessagesProofCallBuilder: ReceiveMessagesProofCallBuilder<Self>;
/// How receive messages delivery proof call is built?
type ReceiveMessagesDeliveryProofCallBuilder: ReceiveMessagesDeliveryProofCallBuilder<Self>;
/// `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;
}
/// Adapter that allows all `SubstrateMessageLane` to act as `MessageLane`.
@@ -138,10 +98,6 @@ pub struct MessagesRelayParams<P: SubstrateMessageLane> {
pub lane_id: LaneId,
/// Metrics parameters.
pub metrics_params: MetricsParams,
/// Pre-registered standalone metrics.
pub standalone_metrics: Option<StandaloneMessagesMetrics<P::SourceChain, P::TargetChain>>,
/// Relay strategy.
pub relay_strategy: P::RelayStrategy,
}
/// Run Substrate-to-Substrate messages sync loop.
@@ -170,13 +126,6 @@ where
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
(max_messages_in_single_batch / 2, max_messages_weight_in_single_batch / 2);
let standalone_metrics = params.standalone_metrics.map(Ok).unwrap_or_else(|| {
crate::messages_metrics::standalone_metrics::<P>(
source_client.clone(),
target_client.clone(),
)
})?;
log::info!(
target: "bridge",
"Starting {} -> {} messages relay.\n\t\
@@ -220,7 +169,6 @@ where
max_messages_in_single_batch,
max_messages_weight_in_single_batch,
max_messages_size_in_single_batch,
relay_strategy: params.relay_strategy,
},
},
SubstrateMessagesSource::<P>::new(
@@ -236,10 +184,12 @@ where
params.lane_id,
relayer_id_at_source,
params.target_transaction_params,
standalone_metrics.clone(),
params.source_to_target_headers_relay,
),
standalone_metrics.register_and_spawn(params.metrics_params)?,
{
GlobalMetrics::new()?.register_and_spawn(&params.metrics_params.registry)?;
params.metrics_params
},
futures::future::pending(),
)
.await
@@ -271,7 +221,6 @@ where
R: BridgeMessagesConfig<I, InboundRelayer = AccountIdOf<P::SourceChain>>,
I: 'static,
R::SourceHeaderChain: bp_messages::target_chain::SourceHeaderChain<
R::InboundMessageFee,
MessagesProof = FromBridgedChainMessagesProof<HashOf<P::SourceChain>>,
>,
CallOf<P::TargetChain>: From<BridgeMessagesCall<R, I>> + GetDispatchInfo,
@@ -16,260 +16,20 @@
//! Tools for supporting message lanes between two Substrate-based chains.
use crate::{helpers::tokens_conversion_rate, messages_lane::SubstrateMessageLane, TaggedAccount};
use crate::TaggedAccount;
use codec::Decode;
use frame_system::AccountInfo;
use pallet_balances::AccountData;
use relay_substrate_client::{
metrics::{
FixedU128OrOne, FloatStorageValue, FloatStorageValueMetric, StorageProofOverheadMetric,
},
metrics::{FloatStorageValue, FloatStorageValueMetric},
AccountIdOf, BalanceOf, Chain, ChainWithBalances, Client, Error as SubstrateError, IndexOf,
};
use relay_utils::metrics::{
FloatJsonValueMetric, GlobalMetrics, MetricsParams, PrometheusError, StandaloneMetric,
};
use relay_utils::metrics::{MetricsParams, StandaloneMetric};
use sp_core::storage::StorageData;
use sp_runtime::{FixedPointNumber, FixedU128};
use std::{convert::TryFrom, fmt::Debug, marker::PhantomData};
/// Name of the `NextFeeMultiplier` storage value within the transaction payment pallet.
const NEXT_FEE_MULTIPLIER_VALUE_NAME: &str = "NextFeeMultiplier";
/// Shared references to the standalone metrics of the message lane relay loop.
#[derive(Debug, Clone)]
pub struct StandaloneMessagesMetrics<SC: Chain, TC: Chain> {
/// Global metrics.
pub global: GlobalMetrics,
/// Storage chain proof overhead metric.
pub source_storage_proof_overhead: StorageProofOverheadMetric<SC>,
/// Target chain proof overhead metric.
pub target_storage_proof_overhead: StorageProofOverheadMetric<TC>,
/// Source tokens to base conversion rate metric.
pub source_to_base_conversion_rate: Option<FloatJsonValueMetric>,
/// Target tokens to base conversion rate metric.
pub target_to_base_conversion_rate: Option<FloatJsonValueMetric>,
/// Source tokens to target tokens conversion rate metric. This rate is stored by the target
/// chain.
pub source_to_target_conversion_rate: Option<FloatStorageValueMetric<TC, FixedU128OrOne>>,
/// Target tokens to source tokens conversion rate metric. This rate is stored by the source
/// chain.
pub target_to_source_conversion_rate: Option<FloatStorageValueMetric<SC, FixedU128OrOne>>,
/// Actual source chain fee multiplier.
pub source_fee_multiplier: Option<FloatStorageValueMetric<SC, FixedU128OrOne>>,
/// Source chain fee multiplier, stored at the target chain.
pub source_fee_multiplier_at_target: Option<FloatStorageValueMetric<TC, FixedU128OrOne>>,
/// Actual target chain fee multiplier.
pub target_fee_multiplier: Option<FloatStorageValueMetric<TC, FixedU128OrOne>>,
/// Target chain fee multiplier, stored at the target chain.
pub target_fee_multiplier_at_source: Option<FloatStorageValueMetric<SC, FixedU128OrOne>>,
}
impl<SC: Chain, TC: Chain> StandaloneMessagesMetrics<SC, TC> {
/// Swap source and target sides.
pub fn reverse(self) -> StandaloneMessagesMetrics<TC, SC> {
StandaloneMessagesMetrics {
global: self.global,
source_storage_proof_overhead: self.target_storage_proof_overhead,
target_storage_proof_overhead: self.source_storage_proof_overhead,
source_to_base_conversion_rate: self.target_to_base_conversion_rate,
target_to_base_conversion_rate: self.source_to_base_conversion_rate,
source_to_target_conversion_rate: self.target_to_source_conversion_rate,
target_to_source_conversion_rate: self.source_to_target_conversion_rate,
source_fee_multiplier: self.target_fee_multiplier,
source_fee_multiplier_at_target: self.target_fee_multiplier_at_source,
target_fee_multiplier: self.source_fee_multiplier,
target_fee_multiplier_at_source: self.source_fee_multiplier_at_target,
}
}
/// Register all metrics in the registry.
pub fn register_and_spawn(
self,
metrics: MetricsParams,
) -> Result<MetricsParams, PrometheusError> {
self.global.register_and_spawn(&metrics.registry)?;
self.source_storage_proof_overhead.register_and_spawn(&metrics.registry)?;
self.target_storage_proof_overhead.register_and_spawn(&metrics.registry)?;
if let Some(m) = self.source_to_base_conversion_rate {
m.register_and_spawn(&metrics.registry)?;
}
if let Some(m) = self.target_to_base_conversion_rate {
m.register_and_spawn(&metrics.registry)?;
}
if let Some(m) = self.target_to_source_conversion_rate {
m.register_and_spawn(&metrics.registry)?;
}
if let Some(m) = self.source_fee_multiplier {
m.register_and_spawn(&metrics.registry)?;
}
if let Some(m) = self.source_fee_multiplier_at_target {
m.register_and_spawn(&metrics.registry)?;
}
if let Some(m) = self.target_fee_multiplier {
m.register_and_spawn(&metrics.registry)?;
}
if let Some(m) = self.target_fee_multiplier_at_source {
m.register_and_spawn(&metrics.registry)?;
}
Ok(metrics)
}
/// Return conversion rate from target to source tokens.
pub async fn target_to_source_conversion_rate(&self) -> Option<f64> {
let from_token_value =
(*self.target_to_base_conversion_rate.as_ref()?.shared_value_ref().read().await)?;
let to_token_value =
(*self.source_to_base_conversion_rate.as_ref()?.shared_value_ref().read().await)?;
Some(tokens_conversion_rate(from_token_value, to_token_value))
}
}
/// Create symmetric standalone metrics for the message lane relay loop.
///
/// All metrics returned by this function are exposed by loops that are serving given lane (`P`)
/// and by loops that are serving reverse lane (`P` with swapped `TargetChain` and `SourceChain`).
/// We assume that either conversion rate parameters have values in the storage, or they are
/// initialized with 1:1.
pub fn standalone_metrics<P: SubstrateMessageLane>(
source_client: Client<P::SourceChain>,
target_client: Client<P::TargetChain>,
) -> anyhow::Result<StandaloneMessagesMetrics<P::SourceChain, P::TargetChain>> {
Ok(StandaloneMessagesMetrics {
global: GlobalMetrics::new()?,
source_storage_proof_overhead: StorageProofOverheadMetric::new(
source_client.clone(),
format!("{}_storage_proof_overhead", P::SourceChain::NAME.to_lowercase()),
format!("{} storage proof overhead", P::SourceChain::NAME),
)?,
target_storage_proof_overhead: StorageProofOverheadMetric::new(
target_client.clone(),
format!("{}_storage_proof_overhead", P::TargetChain::NAME.to_lowercase()),
format!("{} storage proof overhead", P::TargetChain::NAME),
)?,
source_to_base_conversion_rate: P::SourceChain::TOKEN_ID
.map(|source_chain_token_id| {
crate::helpers::token_price_metric(source_chain_token_id).map(Some)
})
.unwrap_or(Ok(None))?,
target_to_base_conversion_rate: P::TargetChain::TOKEN_ID
.map(|target_chain_token_id| {
crate::helpers::token_price_metric(target_chain_token_id).map(Some)
})
.unwrap_or(Ok(None))?,
source_to_target_conversion_rate: P::SOURCE_TO_TARGET_CONVERSION_RATE_PARAMETER_NAME
.map(bp_runtime::storage_parameter_key)
.map(|key| {
FloatStorageValueMetric::new(
FixedU128OrOne::default(),
target_client.clone(),
key,
format!(
"{}_{}_to_{}_conversion_rate",
P::TargetChain::NAME,
P::SourceChain::NAME,
P::TargetChain::NAME
),
format!(
"{} to {} tokens conversion rate (used by {})",
P::SourceChain::NAME,
P::TargetChain::NAME,
P::TargetChain::NAME
),
)
.map(Some)
})
.unwrap_or(Ok(None))?,
target_to_source_conversion_rate: P::TARGET_TO_SOURCE_CONVERSION_RATE_PARAMETER_NAME
.map(bp_runtime::storage_parameter_key)
.map(|key| {
FloatStorageValueMetric::new(
FixedU128OrOne::default(),
source_client.clone(),
key,
format!(
"{}_{}_to_{}_conversion_rate",
P::SourceChain::NAME,
P::TargetChain::NAME,
P::SourceChain::NAME
),
format!(
"{} to {} tokens conversion rate (used by {})",
P::TargetChain::NAME,
P::SourceChain::NAME,
P::SourceChain::NAME
),
)
.map(Some)
})
.unwrap_or(Ok(None))?,
source_fee_multiplier: P::AT_SOURCE_TRANSACTION_PAYMENT_PALLET_NAME
.map(|pallet| bp_runtime::storage_value_key(pallet, NEXT_FEE_MULTIPLIER_VALUE_NAME))
.map(|key| {
log::trace!(target: "bridge", "{}_fee_multiplier", P::SourceChain::NAME);
FloatStorageValueMetric::new(
FixedU128OrOne::default(),
source_client.clone(),
key,
format!("{}_fee_multiplier", P::SourceChain::NAME,),
format!("{} fee multiplier", P::SourceChain::NAME,),
)
.map(Some)
})
.unwrap_or(Ok(None))?,
source_fee_multiplier_at_target: P::SOURCE_FEE_MULTIPLIER_PARAMETER_NAME
.map(bp_runtime::storage_parameter_key)
.map(|key| {
FloatStorageValueMetric::new(
FixedU128OrOne::default(),
target_client.clone(),
key,
format!("{}_{}_fee_multiplier", P::TargetChain::NAME, P::SourceChain::NAME,),
format!(
"{} fee multiplier stored at {}",
P::SourceChain::NAME,
P::TargetChain::NAME,
),
)
.map(Some)
})
.unwrap_or(Ok(None))?,
target_fee_multiplier: P::AT_TARGET_TRANSACTION_PAYMENT_PALLET_NAME
.map(|pallet| bp_runtime::storage_value_key(pallet, NEXT_FEE_MULTIPLIER_VALUE_NAME))
.map(|key| {
log::trace!(target: "bridge", "{}_fee_multiplier", P::TargetChain::NAME);
FloatStorageValueMetric::new(
FixedU128OrOne::default(),
target_client,
key,
format!("{}_fee_multiplier", P::TargetChain::NAME,),
format!("{} fee multiplier", P::TargetChain::NAME,),
)
.map(Some)
})
.unwrap_or(Ok(None))?,
target_fee_multiplier_at_source: P::TARGET_FEE_MULTIPLIER_PARAMETER_NAME
.map(bp_runtime::storage_parameter_key)
.map(|key| {
FloatStorageValueMetric::new(
FixedU128OrOne::default(),
source_client,
key,
format!("{}_{}_fee_multiplier", P::SourceChain::NAME, P::TargetChain::NAME,),
format!(
"{} fee multiplier stored at {}",
P::TargetChain::NAME,
P::SourceChain::NAME,
),
)
.map(Some)
})
.unwrap_or(Ok(None))?,
})
}
/// Add relay accounts balance metrics.
pub async fn add_relay_balances_metrics<C: ChainWithBalances>(
client: Client<C>,
@@ -359,9 +119,6 @@ fn convert_to_token_balance(balance: u128, token_decimals: u32) -> FixedU128 {
#[cfg(test)]
mod tests {
use super::*;
use frame_support::storage::generator::StorageValue;
use sp_core::storage::StorageKey;
#[test]
fn token_decimals_used_properly() {
let plancks = 425_000_000_000;
@@ -369,12 +126,4 @@ mod tests {
let dots = convert_to_token_balance(plancks, token_decimals);
assert_eq!(dots, FixedU128::saturating_from_rational(425, 10));
}
#[test]
fn next_fee_multiplier_storage_key_is_correct() {
assert_eq!(
bp_runtime::storage_value_key("TransactionPayment", NEXT_FEE_MULTIPLIER_VALUE_NAME),
StorageKey(pallet_transaction_payment::NextFeeMultiplier::<rialto_runtime::Runtime>::storage_value_final_key().to_vec()),
);
}
}
@@ -31,13 +31,11 @@ use async_std::sync::Arc;
use async_trait::async_trait;
use bp_messages::{
storage_keys::{operating_mode_key, outbound_lane_data_key},
InboundMessageDetails, LaneId, MessageData, MessageNonce, MessagePayload,
MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, UnrewardedRelayersState,
};
use bp_runtime::{messages::DispatchFeePayment, BasicOperatingMode, HeaderIdProvider};
use bridge_runtime_common::messages::{
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
InboundMessageDetails, LaneId, MessageNonce, MessagePayload, MessagesOperatingMode,
OutboundLaneData, OutboundMessageDetails,
};
use bp_runtime::{BasicOperatingMode, HeaderIdProvider};
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use codec::{Decode, Encode};
use frame_support::weights::Weight;
use messages_relay::{
@@ -47,11 +45,11 @@ use messages_relay::{
SourceClientState,
},
};
use num_traits::{Bounded, Zero};
use num_traits::Zero;
use relay_substrate_client::{
AccountIdOf, AccountKeyPairOf, BalanceOf, BlockNumberOf, Chain, ChainWithMessages,
ChainWithTransactions, Client, Error as SubstrateError, HashOf, HeaderIdOf, IndexOf, SignParam,
TransactionEra, TransactionTracker, UnsignedTransaction,
AccountIdOf, AccountKeyPairOf, BalanceOf, BlockNumberOf, Chain, ChainWithMessages, Client,
Error as SubstrateError, HashOf, HeaderIdOf, IndexOf, SignParam, TransactionEra,
TransactionTracker, UnsignedTransaction,
};
use relay_utils::{relay_loop::Client as RelayClient, HeaderId};
use sp_core::{Bytes, Pair};
@@ -62,7 +60,7 @@ use std::ops::RangeInclusive;
/// required to submit to the target node: cumulative dispatch weight of bundled messages and
/// the proof itself.
pub type SubstrateMessagesProof<C> = (Weight, FromBridgedChainMessagesProof<HashOf<C>>);
type MessagesToRefine<'a, Balance> = Vec<(MessagePayload, &'a mut OutboundMessageDetails<Balance>)>;
type MessagesToRefine<'a> = Vec<(MessagePayload, &'a mut OutboundMessageDetails)>;
/// Substrate client as Substrate messages source.
pub struct SubstrateMessagesSource<P: SubstrateMessageLane> {
@@ -207,9 +205,7 @@ where
// prepare arguments of the inbound message details call (if we need it)
let mut msgs_to_refine = vec![];
for out_msg_details in out_msgs_details.iter_mut() {
if out_msg_details.dispatch_fee_payment != DispatchFeePayment::AtTargetChain {
continue
}
// in our current strategy all messages are supposed to be paid at the target chain
// for pay-at-target messages we may want to ask target chain for
// refined dispatch weight
@@ -218,7 +214,7 @@ where
&self.lane_id,
out_msg_details.nonce,
);
let msg_data: MessageData<BalanceOf<P::SourceChain>> =
let msg_payload: MessagePayload =
self.source_client.storage_value(msg_key, Some(id.1)).await?.ok_or_else(|| {
SubstrateError::Custom(format!(
"Message to {} {:?}/{} is missing from runtime the storage of {} at {:?}",
@@ -230,7 +226,7 @@ where
))
})?;
msgs_to_refine.push((msg_data.payload, out_msg_details));
msgs_to_refine.push((msg_payload, out_msg_details));
}
for mut msgs_to_refine_batch in
@@ -277,8 +273,7 @@ where
MessageDetails {
dispatch_weight: out_msg_details.dispatch_weight,
size: out_msg_details.size as _,
reward: out_msg_details.delivery_and_dispatch_fee,
dispatch_fee_payment: out_msg_details.dispatch_fee_payment,
reward: Zero::zero(),
},
);
}
@@ -370,39 +365,6 @@ where
target_to_source_headers_relay.require_more_headers(id.0).await;
}
}
async fn estimate_confirmation_transaction(
&self,
) -> <MessageLaneAdapter<P> as MessageLane>::SourceChainBalance {
let runtime_version = match self.source_client.runtime_version().await {
Ok(v) => v,
Err(_) => return BalanceOf::<P::SourceChain>::max_value(),
};
async {
let dummy_tx = P::SourceChain::sign_transaction(
SignParam::<P::SourceChain> {
spec_version: runtime_version.spec_version,
transaction_version: runtime_version.transaction_version,
genesis_hash: *self.source_client.genesis_hash(),
signer: self.transaction_params.signer.clone(),
},
make_messages_delivery_proof_transaction::<P>(
&self.transaction_params,
HeaderId(Default::default(), Default::default()),
Zero::zero(),
prepare_dummy_messages_delivery_proof::<P::SourceChain, P::TargetChain>(),
false,
)?,
)?
.encode();
self.source_client
.estimate_extrinsic_fee(Bytes(dummy_tx))
.await
.map(|fee| fee.inclusion_fee())
}
.await
.unwrap_or_else(|_| BalanceOf::<P::SourceChain>::max_value())
}
}
/// Ensure that the messages pallet at source chain is active.
@@ -441,30 +403,6 @@ fn make_messages_delivery_proof_transaction<P: SubstrateMessageLane>(
.era(TransactionEra::new(source_best_block_id, source_transaction_params.mortality)))
}
/// Prepare 'dummy' messages delivery proof that will compose the delivery confirmation transaction.
///
/// We don't care about proof actually being the valid proof, because its validity doesn't
/// affect the call weight - we only care about its size.
fn prepare_dummy_messages_delivery_proof<SC: Chain, TC: Chain>(
) -> SubstrateMessagesDeliveryProof<TC> {
let single_message_confirmation_size =
bp_messages::InboundLaneData::<()>::encoded_size_hint_u32(1, 1);
let proof_size = TC::STORAGE_PROOF_OVERHEAD.saturating_add(single_message_confirmation_size);
(
UnrewardedRelayersState {
unrewarded_relayer_entries: 1,
messages_in_oldest_entry: 1,
total_messages: 1,
last_delivered_nonce: 1,
},
FromBridgedChainMessagesDeliveryProof {
bridged_header_hash: Default::default(),
storage_proof: vec![vec![0; proof_size as usize]],
lane: Default::default(),
},
)
}
/// Read best blocks from given client.
///
/// This function assumes that the chain that is followed by the `self_client` has
@@ -552,7 +490,7 @@ where
}
fn validate_out_msgs_details<C: Chain>(
out_msgs_details: &[OutboundMessageDetails<C::Balance>],
out_msgs_details: &[OutboundMessageDetails],
nonces: RangeInclusive<MessageNonce>,
) -> Result<(), SubstrateError> {
let make_missing_nonce_error = |expected_nonce| {
@@ -601,8 +539,8 @@ fn validate_out_msgs_details<C: Chain>(
fn split_msgs_to_refine<Source: Chain + ChainWithMessages, Target: Chain>(
lane_id: LaneId,
msgs_to_refine: MessagesToRefine<Source::Balance>,
) -> Result<Vec<MessagesToRefine<Source::Balance>>, SubstrateError> {
msgs_to_refine: MessagesToRefine,
) -> Result<Vec<MessagesToRefine>, SubstrateError> {
let max_batch_size = Target::max_extrinsic_size() as usize;
let mut batches = vec![];
@@ -635,23 +573,20 @@ fn split_msgs_to_refine<Source: Chain + ChainWithMessages, Target: Chain>(
#[cfg(test)]
mod tests {
use super::*;
use bp_runtime::{messages::DispatchFeePayment, Chain as ChainBase};
use codec::MaxEncodedLen;
use bp_runtime::Chain as ChainBase;
use relay_rialto_client::Rialto;
use relay_rococo_client::Rococo;
use relay_wococo_client::Wococo;
fn message_details_from_rpc(
nonces: RangeInclusive<MessageNonce>,
) -> Vec<OutboundMessageDetails<bp_wococo::Balance>> {
) -> Vec<OutboundMessageDetails> {
nonces
.into_iter()
.map(|nonce| bp_messages::OutboundMessageDetails {
nonce,
dispatch_weight: Weight::zero(),
size: 0,
delivery_and_dispatch_fee: 0,
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
})
.collect()
}
@@ -704,31 +639,16 @@ mod tests {
));
}
#[test]
fn prepare_dummy_messages_delivery_proof_works() {
let expected_minimal_size =
bp_wococo::AccountId::max_encoded_len() as u32 + Rococo::STORAGE_PROOF_OVERHEAD;
let dummy_proof = prepare_dummy_messages_delivery_proof::<Wococo, Rococo>();
assert!(
dummy_proof.1.encode().len() as u32 > expected_minimal_size,
"Expected proof size at least {}. Got: {}",
expected_minimal_size,
dummy_proof.1.encode().len(),
);
}
fn check_split_msgs_to_refine(
payload_sizes: Vec<usize>,
expected_batches: Result<Vec<usize>, ()>,
) {
let mut out_msgs_details = vec![];
for (idx, _) in payload_sizes.iter().enumerate() {
out_msgs_details.push(OutboundMessageDetails::<BalanceOf<Rialto>> {
out_msgs_details.push(OutboundMessageDetails {
nonce: idx as MessageNonce,
dispatch_weight: Weight::zero(),
size: 0,
delivery_and_dispatch_fee: 0,
dispatch_fee_payment: DispatchFeePayment::AtTargetChain,
});
}
@@ -20,7 +20,6 @@
use crate::{
messages_lane::{MessageLaneAdapter, ReceiveMessagesProofCallBuilder, SubstrateMessageLane},
messages_metrics::StandaloneMessagesMetrics,
messages_source::{ensure_messages_pallet_active, read_client_state, SubstrateMessagesProof},
on_demand::OnDemandRelay,
TransactionParams,
@@ -32,24 +31,18 @@ use bp_messages::{
storage_keys::inbound_lane_data_key, total_unrewarded_messages, InboundLaneData, LaneId,
MessageNonce, UnrewardedRelayersState,
};
use bridge_runtime_common::messages::{
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
};
use codec::Encode;
use frame_support::weights::{Weight, WeightToFee};
use bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof;
use messages_relay::{
message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf},
message_lane_loop::{NoncesSubmitArtifacts, TargetClient, TargetClientState},
};
use num_traits::{Bounded, Zero};
use relay_substrate_client::{
AccountIdOf, AccountKeyPairOf, BalanceOf, BlockNumberOf, Chain, ChainWithMessages,
ChainWithTransactions, Client, Error as SubstrateError, HashOf, HeaderIdOf, IndexOf, SignParam,
TransactionEra, TransactionTracker, UnsignedTransaction, WeightToFeeOf,
AccountIdOf, AccountKeyPairOf, BalanceOf, BlockNumberOf, Chain, ChainWithMessages, Client,
Error as SubstrateError, HashOf, HeaderIdOf, IndexOf, SignParam, TransactionEra,
TransactionTracker, UnsignedTransaction,
};
use relay_utils::{relay_loop::Client as RelayClient, HeaderId};
use sp_core::{Bytes, Pair};
use sp_runtime::{traits::Saturating, FixedPointNumber, FixedU128};
use relay_utils::relay_loop::Client as RelayClient;
use sp_core::Pair;
use std::{collections::VecDeque, convert::TryFrom, ops::RangeInclusive};
/// Message receiving proof returned by the target Substrate node.
@@ -63,7 +56,6 @@ pub struct SubstrateMessagesTarget<P: SubstrateMessageLane> {
lane_id: LaneId,
relayer_id_at_source: AccountIdOf<P::SourceChain>,
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
metric_values: StandaloneMessagesMetrics<P::SourceChain, P::TargetChain>,
source_to_target_headers_relay: Option<Arc<dyn OnDemandRelay<BlockNumberOf<P::SourceChain>>>>,
}
@@ -75,7 +67,6 @@ impl<P: SubstrateMessageLane> SubstrateMessagesTarget<P> {
lane_id: LaneId,
relayer_id_at_source: AccountIdOf<P::SourceChain>,
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
metric_values: StandaloneMessagesMetrics<P::SourceChain, P::TargetChain>,
source_to_target_headers_relay: Option<
Arc<dyn OnDemandRelay<BlockNumberOf<P::SourceChain>>>,
>,
@@ -86,7 +77,6 @@ impl<P: SubstrateMessageLane> SubstrateMessagesTarget<P> {
lane_id,
relayer_id_at_source,
transaction_params,
metric_values,
source_to_target_headers_relay,
}
}
@@ -121,7 +111,6 @@ impl<P: SubstrateMessageLane> Clone for SubstrateMessagesTarget<P> {
lane_id: self.lane_id,
relayer_id_at_source: self.relayer_id_at_source.clone(),
transaction_params: self.transaction_params.clone(),
metric_values: self.metric_values.clone(),
source_to_target_headers_relay: self.source_to_target_headers_relay.clone(),
}
}
@@ -283,142 +272,6 @@ where
source_to_target_headers_relay.require_more_headers(id.0).await;
}
}
async fn estimate_delivery_transaction_in_source_tokens(
&self,
nonces: RangeInclusive<MessageNonce>,
total_prepaid_nonces: MessageNonce,
total_dispatch_weight: Weight,
total_size: u32,
) -> Result<<MessageLaneAdapter<P> as MessageLane>::SourceChainBalance, SubstrateError> {
let conversion_rate =
self.metric_values.target_to_source_conversion_rate().await.ok_or_else(|| {
SubstrateError::Custom(format!(
"Failed to compute conversion rate from {} to {}",
P::TargetChain::NAME,
P::SourceChain::NAME,
))
})?;
let (spec_version, transaction_version) =
self.target_client.simple_runtime_version().await?;
// Prepare 'dummy' delivery transaction - we only care about its length and dispatch weight.
let delivery_tx = P::TargetChain::sign_transaction(
SignParam {
spec_version,
transaction_version,
genesis_hash: Default::default(),
signer: self.transaction_params.signer.clone(),
},
make_messages_delivery_transaction::<P>(
&self.transaction_params,
HeaderId(Default::default(), Default::default()),
Zero::zero(),
self.relayer_id_at_source.clone(),
nonces.clone(),
prepare_dummy_messages_proof::<P::SourceChain>(
nonces.clone(),
total_dispatch_weight,
total_size,
),
false,
)?,
)?
.encode();
let delivery_tx_fee = self.target_client.estimate_extrinsic_fee(Bytes(delivery_tx)).await?;
let inclusion_fee_in_target_tokens = delivery_tx_fee.inclusion_fee();
// The pre-dispatch cost of delivery transaction includes additional fee to cover dispatch
// fee payment (Currency::transfer in regular deployment). But if message dispatch has
// already been paid at the Source chain, the delivery transaction will refund relayer with
// this additional cost. But `estimate_extrinsic_fee` obviously just returns pre-dispatch
// cost of the transaction. So if transaction delivers prepaid message, then it may happen
// that pre-dispatch cost is larger than reward and `Rational` relayer will refuse to
// deliver this message.
//
// The most obvious solution would be to deduct total weight of dispatch fee payments from
// the `total_dispatch_weight` and use regular `estimate_extrinsic_fee` call. But what if
// `total_dispatch_weight` is less than total dispatch fee payments weight? Weight is
// strictly positive, so we can't use this option.
//
// Instead we'll be directly using `WeightToFee` and `NextFeeMultiplier` of the Target
// chain. This requires more knowledge of the Target chain, but seems there's no better way
// to solve this now.
let expected_refund_in_target_tokens = if total_prepaid_nonces != 0 {
const WEIGHT_DIFFERENCE: Weight = Weight::from_ref_time(100);
let (spec_version, transaction_version) =
self.target_client.simple_runtime_version().await?;
let larger_dispatch_weight = total_dispatch_weight.saturating_add(WEIGHT_DIFFERENCE);
let dummy_tx = P::TargetChain::sign_transaction(
SignParam {
spec_version,
transaction_version,
genesis_hash: Default::default(),
signer: self.transaction_params.signer.clone(),
},
make_messages_delivery_transaction::<P>(
&self.transaction_params,
HeaderId(Default::default(), Default::default()),
Zero::zero(),
self.relayer_id_at_source.clone(),
nonces.clone(),
prepare_dummy_messages_proof::<P::SourceChain>(
nonces.clone(),
larger_dispatch_weight,
total_size,
),
false,
)?,
)?
.encode();
let larger_delivery_tx_fee =
self.target_client.estimate_extrinsic_fee(Bytes(dummy_tx)).await?;
compute_prepaid_messages_refund::<P::TargetChain>(
total_prepaid_nonces,
compute_fee_multiplier::<P::TargetChain>(
delivery_tx_fee.adjusted_weight_fee,
total_dispatch_weight,
larger_delivery_tx_fee.adjusted_weight_fee,
larger_dispatch_weight,
),
)
} else {
Zero::zero()
};
let delivery_fee_in_source_tokens =
convert_target_tokens_to_source_tokens::<P::SourceChain, P::TargetChain>(
FixedU128::from_float(conversion_rate),
inclusion_fee_in_target_tokens.saturating_sub(expected_refund_in_target_tokens),
);
log::trace!(
target: "bridge",
"Estimated {} -> {} messages delivery transaction.\n\t\
Total nonces: {:?}\n\t\
Prepaid messages: {}\n\t\
Total messages size: {}\n\t\
Total messages dispatch weight: {}\n\t\
Inclusion fee (in {1} tokens): {:?}\n\t\
Expected refund (in {1} tokens): {:?}\n\t\
{1} -> {0} conversion rate: {:?}\n\t\
Expected delivery tx fee (in {0} tokens): {:?}",
P::SourceChain::NAME,
P::TargetChain::NAME,
nonces,
total_prepaid_nonces,
total_size,
total_dispatch_weight,
inclusion_fee_in_target_tokens,
expected_refund_in_target_tokens,
conversion_rate,
delivery_fee_in_source_tokens,
);
Ok(delivery_fee_in_source_tokens)
}
}
/// Make messages delivery transaction from given proof.
@@ -443,153 +296,3 @@ fn make_messages_delivery_transaction<P: SubstrateMessageLane>(
Ok(UnsignedTransaction::new(call.into(), transaction_nonce)
.era(TransactionEra::new(target_best_block_id, target_transaction_params.mortality)))
}
/// Prepare 'dummy' messages proof that will compose the delivery transaction.
///
/// We don't care about proof actually being the valid proof, because its validity doesn't
/// affect the call weight - we only care about its size.
fn prepare_dummy_messages_proof<SC: Chain>(
nonces: RangeInclusive<MessageNonce>,
total_dispatch_weight: Weight,
total_size: u32,
) -> SubstrateMessagesProof<SC> {
(
total_dispatch_weight,
FromBridgedChainMessagesProof {
bridged_header_hash: Default::default(),
storage_proof: vec![vec![
0;
SC::STORAGE_PROOF_OVERHEAD.saturating_add(total_size) as usize
]],
lane: Default::default(),
nonces_start: *nonces.start(),
nonces_end: *nonces.end(),
},
)
}
/// Given delivery transaction fee in target chain tokens and conversion rate to the source
/// chain tokens, compute transaction cost in source chain tokens.
fn convert_target_tokens_to_source_tokens<SC: Chain, TC: Chain>(
target_to_source_conversion_rate: FixedU128,
target_transaction_fee: TC::Balance,
) -> SC::Balance
where
SC::Balance: TryFrom<TC::Balance>,
{
SC::Balance::try_from(
target_to_source_conversion_rate.saturating_mul_int(target_transaction_fee),
)
.unwrap_or_else(|_| SC::Balance::max_value())
}
/// Compute fee multiplier that is used by the chain, given a couple of fees for transactions
/// that are only differ in dispatch weights.
///
/// This function assumes that standard transaction payment pallet is used by the chain.
/// The only fee component that depends on dispatch weight is the `adjusted_weight_fee`.
///
/// **WARNING**: this functions will only be accurate if weight-to-fee conversion function
/// is linear. For non-linear polynomials the error will grow with `weight_difference` growth.
/// So better to use smaller differences.
fn compute_fee_multiplier<C: ChainWithMessages>(
smaller_adjusted_weight_fee: BalanceOf<C>,
smaller_tx_weight: Weight,
larger_adjusted_weight_fee: BalanceOf<C>,
larger_tx_weight: Weight,
) -> FixedU128 {
let adjusted_weight_fee_difference =
larger_adjusted_weight_fee.saturating_sub(smaller_adjusted_weight_fee);
let smaller_tx_unadjusted_weight_fee = WeightToFeeOf::<C>::weight_to_fee(&smaller_tx_weight);
let larger_tx_unadjusted_weight_fee = WeightToFeeOf::<C>::weight_to_fee(&larger_tx_weight);
FixedU128::saturating_from_rational(
adjusted_weight_fee_difference,
larger_tx_unadjusted_weight_fee.saturating_sub(smaller_tx_unadjusted_weight_fee),
)
}
/// Compute fee that will be refunded to the relayer because dispatch of `total_prepaid_nonces`
/// messages has been paid at the source chain.
fn compute_prepaid_messages_refund<C: ChainWithMessages>(
total_prepaid_nonces: MessageNonce,
fee_multiplier: FixedU128,
) -> BalanceOf<C> {
fee_multiplier.saturating_mul_int(WeightToFeeOf::<C>::weight_to_fee(
&C::PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_CHAIN.saturating_mul(total_prepaid_nonces),
))
}
#[cfg(test)]
mod tests {
use super::*;
use relay_rialto_client::Rialto;
use relay_rococo_client::Rococo;
use relay_wococo_client::Wococo;
#[test]
fn prepare_dummy_messages_proof_works() {
const DISPATCH_WEIGHT: Weight = Weight::from_ref_time(1_000_000);
const SIZE: u32 = 1_000;
let dummy_proof = prepare_dummy_messages_proof::<Rococo>(1..=10, DISPATCH_WEIGHT, SIZE);
assert_eq!(dummy_proof.0, DISPATCH_WEIGHT);
assert!(
dummy_proof.1.encode().len() as u32 > SIZE,
"Expected proof size at least {}. Got: {}",
SIZE,
dummy_proof.1.encode().len(),
);
}
#[test]
fn convert_target_tokens_to_source_tokens_works() {
assert_eq!(
convert_target_tokens_to_source_tokens::<Rococo, Wococo>((150, 100).into(), 1_000),
1_500
);
assert_eq!(
convert_target_tokens_to_source_tokens::<Rococo, Wococo>((50, 100).into(), 1_000),
500
);
assert_eq!(
convert_target_tokens_to_source_tokens::<Rococo, Wococo>((100, 100).into(), 1_000),
1_000
);
}
#[test]
fn compute_fee_multiplier_returns_sane_results() {
let multiplier: FixedU128 =
bp_rialto::WeightToFee::weight_to_fee(&Weight::from_ref_time(1)).into();
let smaller_weight = 1_000_000;
let smaller_adjusted_weight_fee = multiplier.saturating_mul_int(
WeightToFeeOf::<Rialto>::weight_to_fee(&Weight::from_ref_time(smaller_weight)),
);
let larger_weight = smaller_weight + 200_000;
let larger_adjusted_weight_fee = multiplier.saturating_mul_int(
WeightToFeeOf::<Rialto>::weight_to_fee(&Weight::from_ref_time(larger_weight)),
);
assert_eq!(
compute_fee_multiplier::<Rialto>(
smaller_adjusted_weight_fee,
Weight::from_ref_time(smaller_weight),
larger_adjusted_weight_fee,
Weight::from_ref_time(larger_weight),
),
multiplier,
);
}
#[test]
fn compute_prepaid_messages_refund_returns_sane_results() {
assert!(
compute_prepaid_messages_refund::<Rialto>(
10,
FixedU128::saturating_from_rational(110, 100),
) > Rialto::PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_CHAIN
.saturating_mul(10u64)
.ref_time() as _
);
}
}