mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 14:31:02 +00:00
Update bridges subtree (#5165)
* Squashed 'bridges/' changes from 1602249f0a..f220d2fcca f220d2fcca Polkadot staging update (#1356) 02fd3d497c fix parse_transaction on Rialto+Millau (#1360) bc191fd9a2 update parity-scale-codec to 3.1.2 (#1359) a37226e79c update chain versions (#1358) ff5d539fcb Update Substrate/Polkadot/Cumulus references (#1353) 1581f60cd5 Support dedicated lanes for pallets (#962) 0a7ccf5c57 ignore more "increase" alerts that are sometimes signalling NoData at startup (#1351) 31165127cc added no_stack_overflow_when_decoding_nested_call_during_dispatch test (#1349) 7000619eb8 replace From<>InboundLaneApi with direct storage reads (#1348) 515df10ccc added alerts for relay balances (#1347) b56f6a87de Mortal conversion rate updater transactions (#1257) 20f2f331ec edition = "2021" (#1346) 99147d4f75 update regex to 1.5.5 (#1345) 686191f379 use DecodeLimit when decoding incoming calls (#1344) a70c276006 get rid of '[No Data] Messages from Millau to Rialto are not being delivered' warnings (#1342) 01f29b8ac1 fix conversion rate metric in dashboards (#1341) 51c3bf351f Increase rate from metric when estimating fee (#1340) 3bb9c4f68f fix generator scripts to be consistent with updatedrelay output (#1339) 0475a1667b fixed mess with conversion rates (#1338) d8fdd7d716 synchronize relay cli changes and token swap generator script (#1337) 6e928137a5 fix conversion rate override in token swap (#1336) 62d4a4811d override conversion rate in tokens swap generator (#1335) ed9e1c839c fi typo in generator script (#1334) 3254b5af7a Override conversion rate when computing message fee (#1261) 66df68b5b8 Revert "Revert "override conversion rate in estimate-message-fee RPC (#1189)" (#1275)" (#1333) 0ca6fc6ef8 fix clippy issues (#1332) 5414b2fffb Reinitialize bridge relay subcommand (#1331) a63d95ba7d removed extra *_RUNTIME_VERSION consts from relay code (#1330) 59fb18a310 fix typo in alert expression (#1329) a6267a47ee Using-same-fork metric for finality and complex relay (#1327) 88d684d37e use mortal transactions in transaction resubmitter (#1326) 8ff88b6844 impl Decode for SignedExtensions (otherwise transaction resubmitter panicks) (#1325) 1ed09854f0 Encode and estimate Rococo/Wococo/Kusama/Polkadot messages (#1322) ddb4517e13 Add some tests to check integrity of chain constants + bridge configuration (#1316) bdeedb7ab9 Fix issues from cargo deny (#1311) d3d79d01e0 expose fee multiplier metrics in messages relay (#1312) c8b3f0ea16 Endow relayer account at target chain in message benchmarks (#1310) f51ecd92b6 fix benchmarks before using it in Polkadot/Kusama/Rococo runtimes (#1309) 6935c619ad increase relay balance guard limits for Polkadot<>Kusama bridge (#1308) 7e31834c66 Fix mandatory headers scanning in on-demand relay (#1306) 92ddc3ea7a Polkadot-staging update (#1305) 3787193a31 fix session length of Rococo and Wococo (#1304) eb468d29c0 Revert nightly docker pin (#1301) e2d4c073e1 Use raw balance value if tokenDecimals property is missing (#1299) 108f4b29d1 Fix ss58 prefixes of Polkadot, Kusama and Westend used by relay (#1298) 64fbd2705e bump chain spec versions (#1297) 5707777b86 Bump Substrate/Polkadot/Cumulus refs (#1295) 29eecdf1fa Merge pull request #1294 from paritytech/polkadot-staging-update 1f0c05368e Relay balance metrics (#1291) 6356bb90b3 when messages pallet is halted, relay shall not submit messages delivery/confirmation transactions (#1289) 800dc2df8d when GRANDPA pallet is halted, relay shall not submit finality transactions (#1288) 3dd8e4f936 disable BEEFY allerts for Rialto (#1285) f58fed7380 support version mode cli options in send-message subcommand (#1284) 3aac448da3 reuse polkadot-service code (#1273) 2bdbb651e1 replace latest_confirmed_nonce runtime APIs with direct storage reads (#1282) 5f9c6d241f move "common" code of messages pallet benchmarks helpers to the common library (#1281) 173d2d8229 Merge pull request #1280 from paritytech/polkadot-staging-update 8b9c4ec16d do not start spec_version guard when version mode is set to auto (#1278) e98d682de2 removed extra messages benchmarks (#1279) c730e25b61 Move benchmarks from Rialto to Millau (#1277) 54146416e7 Merge pull request #1276 from paritytech/polkadot-staging-update df70118174 Merge branch 'master' into polkadot-staging-update ed7def64c4 Revert "override conversion rate in estimate-message-fee RPC (#1189)" (#1275) 38c6c3a49f Use "production" floating tag when uilding docker image from version git tags (#1272) ded9ff6dbb Replace InboundLaneApi::latest_received_nonce with direct storage read (#1269) f704a741ee Polkadot staging update (#1270) 8c65f0d7ab verify that GRANDPA pallet is not initialized before submitting initialization transaction (#1267) e7e83d8944 remove OutboundLaneApi::latest_received_nonce (#1262) 9f4b34acf1 bump rococo version (#1263) 82c08c5a87 read latest_generated_nonce directly from storage (#1260) 50ffb5dd08 override conversion rate in estimate-message-fee RPC (#1189) 467ca5ef59 move storage keys computation to primitivs (#1254) 4f9884066b remporary use pinned bridges-ci image in Dockerfile (#1258) edfcb74e00 Change submit transaction spec_version and transaction_version query from chain (#1248) 4009d970d0 pin bridges-ci image (#1256) 65e51b5e1c decrease startup sleep to 5s for relays and to 120s for generators + remove curl (#1251) 3bc74355d9 Add missing RPC APIs to rialto parachain node (#1250) 80c9429284 Bump relay version to 1.0.0 (#1249) 9ead06af2a runtimes: fix call_size() test (#1245) 4fc8a29357 Use same endowed accounts set on dev/local chains (#1244) fed54371c2 Refactor message relay helpers (#1234) a15b4faae7 post-merge build fix (#1243) 52232d8d54 Fix transactions mortality (#1196) c07bba931f Expose prometheus BEEFY metrics and add them to grafana dashboard (#1242) f927775bd5 Refactor finality relay helpers (#1220) 7bf76f14a8 Update Rococo/Wococo version + prepare relay for Rococo<>Wococo bridge (#1241) e860fecd04 Enable offchain indexing for Rialto/Millau nodes (#1239) 04d4d1c6b4 Enable Beefy debug logs in test deployment (#1237) cd771f1089 Fix storage parameter name computation (#1238) 816ddd2dd2 Integrate BEEFY with Rialto & Millau runtimes (#1227) d94b62b1ac update dependencies (#1229) 98eb9ee13d Add mut support (#1232) ffef6f89f9 fixed set_operational in GRANDPA pallet (#1226) bd2f8bfbd7 Add CODEOWNERS file (#1219) 6b5cf2b591 Unify metric names (#1209) d1541e797e remove abandoned exchange relay (#1217) 39140d0b34 Remove unused `relays/headers` (#1216) 9bc071d42b Remove unused PoA<>Substrate bridge (#1210) 877e8d01e3 Fix UI deployment. (#1211) 6cd5775ebe Add `AtLeast32BitUnsigned` for MessageLance::SourceChainBalance (#1207) git-subtree-dir: bridges git-subtree-split: f220d2fccabbf141101d19456ecb4e3576a1d797 * fix compilation warnings
This commit is contained in:
committed by
GitHub
parent
20da356434
commit
8e01ba9c03
@@ -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<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<
|
||||
SubmitConversionRateFuture: Future<Output = anyhow::Result<()>> + Send + 'static,
|
||||
>(
|
||||
pub fn run_conversion_rate_update_loop<Lane, Sign>(
|
||||
client: Client<Lane::SourceChain>,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<Sign>>,
|
||||
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<Chain = Lane::SourceChain>,
|
||||
AccountIdOf<Lane::SourceChain>: From<<AccountKeyPairOf<Sign> 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::<Lane, Sign>(
|
||||
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.
|
||||
@@ -106,7 +241,7 @@ async fn maybe_select_new_conversion_rate(
|
||||
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 =
|
||||
right_to_base_conversion_rate / left_to_base_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();
|
||||
@@ -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<Lane, Sign>(
|
||||
client: Client<Lane::SourceChain>,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<Sign>>,
|
||||
updated_rate: f64,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
Lane: SubstrateMessageLane,
|
||||
Sign: TransactionSignScheme<Chain = Lane::SourceChain>,
|
||||
AccountIdOf<Lane::SourceChain>: From<<AccountKeyPairOf<Sign> 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<f64>,
|
||||
@@ -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),
|
||||
@@ -229,15 +399,42 @@ mod tests {
|
||||
|
||||
#[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(1.0),
|
||||
Some(1.0),
|
||||
Some(1.03),
|
||||
Some(left_to_right_stored_conversion_rate),
|
||||
Some(left_to_base_conversion_rate),
|
||||
Some(right_to_base_conversion_rate),
|
||||
0.02
|
||||
),
|
||||
(Some((1.0, 1.03)), TransactionStatus::Idle),
|
||||
(
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,4 +55,7 @@ pub enum Error<Hash: Debug + MaybeDisplay, HeaderNumber: Debug + MaybeDisplay> {
|
||||
/// Failed to retrieve header by the hash from the source chain.
|
||||
#[error("Failed to retrieve {0} header with hash {1}: {:?}")]
|
||||
RetrieveHeader(&'static str, Hash, client::Error),
|
||||
/// Failed to retrieve best finalized source header hash from the target chain.
|
||||
#[error("Failed to retrieve best finalized {0} header from the target chain: {1}")]
|
||||
RetrieveBestFinalizedHeaderHash(&'static str, client::Error),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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 starting guards of finality relays.
|
||||
|
||||
use crate::TransactionParams;
|
||||
|
||||
use relay_substrate_client::{
|
||||
AccountIdOf, AccountKeyPairOf, ChainWithBalances, TransactionSignScheme,
|
||||
};
|
||||
use sp_core::Pair;
|
||||
|
||||
/// Start finality relay guards.
|
||||
pub async fn start<C: ChainWithBalances, S: TransactionSignScheme<Chain = C>>(
|
||||
target_client: &relay_substrate_client::Client<C>,
|
||||
transaction_params: &TransactionParams<S::AccountKeyPair>,
|
||||
enable_version_guard: bool,
|
||||
maximal_balance_decrease_per_day: C::Balance,
|
||||
) -> relay_substrate_client::Result<()>
|
||||
where
|
||||
AccountIdOf<C>: From<<AccountKeyPairOf<S> as Pair>::Public>,
|
||||
{
|
||||
if enable_version_guard {
|
||||
relay_substrate_client::guard::abort_on_spec_version_change(
|
||||
target_client.clone(),
|
||||
target_client.simple_runtime_version().await?.0,
|
||||
);
|
||||
}
|
||||
relay_substrate_client::guard::abort_when_account_balance_decreased(
|
||||
target_client.clone(),
|
||||
transaction_params.signer.public().into(),
|
||||
maximal_balance_decrease_per_day,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -14,18 +14,24 @@
|
||||
// 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-to-Substrate headers sync entrypoint.
|
||||
//! Types and functions intended to ease adding of new Substrate -> Substrate
|
||||
//! finality proofs synchronization pipelines.
|
||||
|
||||
use crate::{finality_target::SubstrateFinalityTarget, STALL_TIMEOUT};
|
||||
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use bp_runtime::AccountIdOf;
|
||||
use finality_relay::{FinalitySyncParams, FinalitySyncPipeline};
|
||||
use relay_substrate_client::{
|
||||
finality_source::FinalitySource, BlockNumberOf, Chain, Client, HashOf, SyncHeader,
|
||||
use crate::{
|
||||
finality_source::SubstrateFinalitySource, finality_target::SubstrateFinalityTarget,
|
||||
TransactionParams,
|
||||
};
|
||||
use relay_utils::{metrics::MetricsParams, BlockNumberBase};
|
||||
use sp_core::Bytes;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use finality_relay::FinalitySyncPipeline;
|
||||
use pallet_bridge_grandpa::{Call as BridgeGrandpaCall, Config as BridgeGrandpaConfig};
|
||||
use relay_substrate_client::{
|
||||
transaction_stall_timeout, AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain,
|
||||
ChainWithGrandpa, Client, HashOf, HeaderOf, SyncHeader, TransactionSignScheme,
|
||||
};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use sp_core::Pair;
|
||||
use std::{fmt::Debug, marker::PhantomData};
|
||||
|
||||
/// Default limit of recent finality proofs.
|
||||
@@ -34,130 +40,146 @@ use std::{fmt::Debug, marker::PhantomData};
|
||||
/// Substrate+GRANDPA based chains (good to know).
|
||||
pub(crate) const RECENT_FINALITY_PROOFS_LIMIT: usize = 4096;
|
||||
|
||||
/// Headers sync pipeline for Substrate <-> Substrate relays.
|
||||
/// Substrate -> Substrate finality proofs synchronization pipeline.
|
||||
#[async_trait]
|
||||
pub trait SubstrateFinalitySyncPipeline: 'static + Clone + Debug + Send + Sync {
|
||||
/// Pipeline for syncing finalized Source chain headers to Target chain.
|
||||
type FinalitySyncPipeline: FinalitySyncPipeline;
|
||||
|
||||
/// Name of the runtime method that returns id of best finalized source header at target chain.
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str;
|
||||
|
||||
/// Chain with GRANDPA bridge pallet.
|
||||
/// Headers of this chain are submitted to the `TargetChain`.
|
||||
type SourceChain: ChainWithGrandpa;
|
||||
/// Headers of the `SourceChain` are submitted to this chain.
|
||||
type TargetChain: Chain;
|
||||
|
||||
/// Customize metrics exposed by headers sync loop.
|
||||
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
|
||||
Ok(params)
|
||||
}
|
||||
/// How submit finality proof call is built?
|
||||
type SubmitFinalityProofCallBuilder: SubmitFinalityProofCallBuilder<Self>;
|
||||
/// Scheme used to sign target chain transactions.
|
||||
type TransactionSignScheme: TransactionSignScheme;
|
||||
|
||||
/// Start finality relay guards.
|
||||
///
|
||||
/// Different finality bridges may have different set of guards - e.g. on ephemeral chains we
|
||||
/// don't need a version guards, on test chains we don't care that much about relayer account
|
||||
/// balance, ... So the implementation is left to the specific bridges.
|
||||
fn start_relay_guards(&self) {}
|
||||
|
||||
/// Returns id of account that we're using to sign transactions at target chain.
|
||||
fn transactions_author(&self) -> AccountIdOf<Self::TargetChain>;
|
||||
|
||||
/// Make submit header transaction.
|
||||
fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
era: bp_runtime::TransactionEraOf<Self::TargetChain>,
|
||||
transaction_nonce: bp_runtime::IndexOf<Self::TargetChain>,
|
||||
header: <Self::FinalitySyncPipeline as FinalitySyncPipeline>::Header,
|
||||
proof: <Self::FinalitySyncPipeline as FinalitySyncPipeline>::FinalityProof,
|
||||
) -> Bytes;
|
||||
}
|
||||
|
||||
/// Substrate-to-Substrate finality proof pipeline.
|
||||
#[derive(Clone)]
|
||||
pub struct SubstrateFinalityToSubstrate<SourceChain, TargetChain: Chain, TargetSign> {
|
||||
/// Client for the target chain.
|
||||
pub target_client: Client<TargetChain>,
|
||||
/// Data required to sign target chain transactions.
|
||||
pub target_sign: TargetSign,
|
||||
/// Unused generic arguments dump.
|
||||
_marker: PhantomData<SourceChain>,
|
||||
}
|
||||
|
||||
impl<SourceChain, TargetChain: Chain, TargetSign> Debug
|
||||
for SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.debug_struct("SubstrateFinalityToSubstrate")
|
||||
.field("target_client", &self.target_client)
|
||||
.finish()
|
||||
/// Add relay guards if required.
|
||||
async fn start_relay_guards(
|
||||
_target_client: &Client<Self::TargetChain>,
|
||||
_transaction_params: &TransactionParams<AccountKeyPairOf<Self::TransactionSignScheme>>,
|
||||
_enable_version_guard: bool,
|
||||
) -> relay_substrate_client::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SourceChain, TargetChain: Chain, TargetSign>
|
||||
SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>
|
||||
{
|
||||
/// Create new Substrate-to-Substrate headers pipeline.
|
||||
pub fn new(target_client: Client<TargetChain>, target_sign: TargetSign) -> Self {
|
||||
SubstrateFinalityToSubstrate { target_client, target_sign, _marker: Default::default() }
|
||||
}
|
||||
/// Adapter that allows all `SubstrateFinalitySyncPipeline` to act as `FinalitySyncPipeline`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FinalitySyncPipelineAdapter<P: SubstrateFinalitySyncPipeline> {
|
||||
_phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<SourceChain, TargetChain, TargetSign> FinalitySyncPipeline
|
||||
for SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>
|
||||
impl<P: SubstrateFinalitySyncPipeline> FinalitySyncPipeline for FinalitySyncPipelineAdapter<P> {
|
||||
const SOURCE_NAME: &'static str = P::SourceChain::NAME;
|
||||
const TARGET_NAME: &'static str = P::TargetChain::NAME;
|
||||
|
||||
type Hash = HashOf<P::SourceChain>;
|
||||
type Number = BlockNumberOf<P::SourceChain>;
|
||||
type Header = relay_substrate_client::SyncHeader<HeaderOf<P::SourceChain>>;
|
||||
type FinalityProof = GrandpaJustification<HeaderOf<P::SourceChain>>;
|
||||
}
|
||||
|
||||
/// Different ways of building `submit_finality_proof` calls.
|
||||
pub trait SubmitFinalityProofCallBuilder<P: SubstrateFinalitySyncPipeline> {
|
||||
/// Given source chain header and its finality proofs, build call of `submit_finality_proof`
|
||||
/// function of bridge GRANDPA module at the target chain.
|
||||
fn build_submit_finality_proof_call(
|
||||
header: SyncHeader<HeaderOf<P::SourceChain>>,
|
||||
proof: GrandpaJustification<HeaderOf<P::SourceChain>>,
|
||||
) -> CallOf<P::TargetChain>;
|
||||
}
|
||||
|
||||
/// Building `submit_finality_proof` call when you have direct access to the target
|
||||
/// chain runtime.
|
||||
pub struct DirectSubmitFinalityProofCallBuilder<P, R, I> {
|
||||
_phantom: PhantomData<(P, R, I)>,
|
||||
}
|
||||
|
||||
impl<P, R, I> SubmitFinalityProofCallBuilder<P> for DirectSubmitFinalityProofCallBuilder<P, R, I>
|
||||
where
|
||||
SourceChain: Clone + Chain + Debug,
|
||||
BlockNumberOf<SourceChain>: BlockNumberBase,
|
||||
TargetChain: Clone + Chain + Debug,
|
||||
TargetSign: 'static + Clone + Send + Sync,
|
||||
P: SubstrateFinalitySyncPipeline,
|
||||
R: BridgeGrandpaConfig<I>,
|
||||
I: 'static,
|
||||
R::BridgedChain: bp_runtime::Chain<Header = HeaderOf<P::SourceChain>>,
|
||||
CallOf<P::TargetChain>: From<BridgeGrandpaCall<R, I>>,
|
||||
{
|
||||
const SOURCE_NAME: &'static str = SourceChain::NAME;
|
||||
const TARGET_NAME: &'static str = TargetChain::NAME;
|
||||
|
||||
type Hash = HashOf<SourceChain>;
|
||||
type Number = BlockNumberOf<SourceChain>;
|
||||
type Header = SyncHeader<SourceChain::Header>;
|
||||
type FinalityProof = GrandpaJustification<SourceChain::Header>;
|
||||
fn build_submit_finality_proof_call(
|
||||
header: SyncHeader<HeaderOf<P::SourceChain>>,
|
||||
proof: GrandpaJustification<HeaderOf<P::SourceChain>>,
|
||||
) -> CallOf<P::TargetChain> {
|
||||
BridgeGrandpaCall::<R, I>::submit_finality_proof {
|
||||
finality_target: Box::new(header.into_inner()),
|
||||
justification: proof,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Run Substrate-to-Substrate finality sync.
|
||||
pub async fn run<SourceChain, TargetChain, P>(
|
||||
pipeline: P,
|
||||
source_client: Client<SourceChain>,
|
||||
target_client: Client<TargetChain>,
|
||||
/// Macro that generates `SubmitFinalityProofCallBuilder` implementation for the case when
|
||||
/// you only have an access to the mocked version of target chain runtime. In this case you
|
||||
/// should provide "name" of the call variant for the bridge GRANDPA calls and the "name" of
|
||||
/// the variant for the `submit_finality_proof` call within that first option.
|
||||
#[rustfmt::skip]
|
||||
#[macro_export]
|
||||
macro_rules! generate_mocked_submit_finality_proof_call_builder {
|
||||
($pipeline:ident, $mocked_builder:ident, $bridge_grandpa:path, $submit_finality_proof:path) => {
|
||||
pub struct $mocked_builder;
|
||||
|
||||
impl $crate::finality_pipeline::SubmitFinalityProofCallBuilder<$pipeline>
|
||||
for $mocked_builder
|
||||
{
|
||||
fn build_submit_finality_proof_call(
|
||||
header: relay_substrate_client::SyncHeader<
|
||||
relay_substrate_client::HeaderOf<
|
||||
<$pipeline as $crate::finality_pipeline::SubstrateFinalitySyncPipeline>::SourceChain
|
||||
>
|
||||
>,
|
||||
proof: bp_header_chain::justification::GrandpaJustification<
|
||||
relay_substrate_client::HeaderOf<
|
||||
<$pipeline as $crate::finality_pipeline::SubstrateFinalitySyncPipeline>::SourceChain
|
||||
>
|
||||
>,
|
||||
) -> relay_substrate_client::CallOf<
|
||||
<$pipeline as $crate::finality_pipeline::SubstrateFinalitySyncPipeline>::TargetChain
|
||||
> {
|
||||
$bridge_grandpa($submit_finality_proof(Box::new(header.into_inner()), proof))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Run Substrate-to-Substrate finality sync loop.
|
||||
pub async fn run<P: SubstrateFinalitySyncPipeline>(
|
||||
source_client: Client<P::SourceChain>,
|
||||
target_client: Client<P::TargetChain>,
|
||||
only_mandatory_headers: bool,
|
||||
transactions_mortality: Option<u32>,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::TransactionSignScheme>>,
|
||||
metrics_params: MetricsParams,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
P: SubstrateFinalitySyncPipeline<TargetChain = TargetChain>,
|
||||
P::FinalitySyncPipeline: FinalitySyncPipeline<
|
||||
Hash = HashOf<SourceChain>,
|
||||
Number = BlockNumberOf<SourceChain>,
|
||||
Header = SyncHeader<SourceChain::Header>,
|
||||
FinalityProof = GrandpaJustification<SourceChain::Header>,
|
||||
>,
|
||||
SourceChain: Clone + Chain,
|
||||
BlockNumberOf<SourceChain>: BlockNumberBase,
|
||||
TargetChain: Clone + Chain,
|
||||
AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TransactionSignScheme> as Pair>::Public>,
|
||||
P::TransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
|
||||
{
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Starting {} -> {} finality proof relay",
|
||||
SourceChain::NAME,
|
||||
TargetChain::NAME,
|
||||
P::SourceChain::NAME,
|
||||
P::TargetChain::NAME,
|
||||
);
|
||||
|
||||
finality_relay::run(
|
||||
FinalitySource::new(source_client, None),
|
||||
SubstrateFinalityTarget::new(target_client, pipeline, transactions_mortality),
|
||||
FinalitySyncParams {
|
||||
SubstrateFinalitySource::<P>::new(source_client, None),
|
||||
SubstrateFinalityTarget::<P>::new(target_client, transaction_params.clone()),
|
||||
finality_relay::FinalitySyncParams {
|
||||
tick: std::cmp::max(
|
||||
SourceChain::AVERAGE_BLOCK_INTERVAL,
|
||||
TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
P::SourceChain::AVERAGE_BLOCK_INTERVAL,
|
||||
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
),
|
||||
recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT,
|
||||
stall_timeout: relay_substrate_client::transaction_stall_timeout(
|
||||
transactions_mortality,
|
||||
TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
STALL_TIMEOUT,
|
||||
stall_timeout: transaction_stall_timeout(
|
||||
transaction_params.mortality,
|
||||
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
crate::STALL_TIMEOUT,
|
||||
),
|
||||
only_mandatory_headers,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
// 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/>.
|
||||
|
||||
//! Default generic implementation of finality source for basic Substrate client.
|
||||
|
||||
use crate::finality_pipeline::{FinalitySyncPipelineAdapter, SubstrateFinalitySyncPipeline};
|
||||
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use async_trait::async_trait;
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use codec::Decode;
|
||||
use finality_relay::SourceClient;
|
||||
use futures::stream::{unfold, Stream, StreamExt};
|
||||
use relay_substrate_client::{
|
||||
BlockNumberOf, BlockWithJustification, Chain, Client, Error, HeaderOf,
|
||||
};
|
||||
use relay_utils::relay_loop::Client as RelayClient;
|
||||
use sp_runtime::traits::Header as HeaderT;
|
||||
use std::pin::Pin;
|
||||
|
||||
/// Shared updatable reference to the maximal header number that we want to sync from the source.
|
||||
pub type RequiredHeaderNumberRef<C> = Arc<Mutex<<C as bp_runtime::Chain>::BlockNumber>>;
|
||||
|
||||
/// Substrate finality proofs stream.
|
||||
pub type SubstrateFinalityProofsStream<P> = Pin<
|
||||
Box<
|
||||
dyn Stream<
|
||||
Item = GrandpaJustification<
|
||||
HeaderOf<<P as SubstrateFinalitySyncPipeline>::SourceChain>,
|
||||
>,
|
||||
> + Send,
|
||||
>,
|
||||
>;
|
||||
|
||||
/// Substrate node as finality source.
|
||||
pub struct SubstrateFinalitySource<P: SubstrateFinalitySyncPipeline> {
|
||||
client: Client<P::SourceChain>,
|
||||
maximal_header_number: Option<RequiredHeaderNumberRef<P::SourceChain>>,
|
||||
}
|
||||
|
||||
impl<P: SubstrateFinalitySyncPipeline> SubstrateFinalitySource<P> {
|
||||
/// Create new headers source using given client.
|
||||
pub fn new(
|
||||
client: Client<P::SourceChain>,
|
||||
maximal_header_number: Option<RequiredHeaderNumberRef<P::SourceChain>>,
|
||||
) -> Self {
|
||||
SubstrateFinalitySource { client, maximal_header_number }
|
||||
}
|
||||
|
||||
/// Returns reference to the underlying RPC client.
|
||||
pub fn client(&self) -> &Client<P::SourceChain> {
|
||||
&self.client
|
||||
}
|
||||
|
||||
/// Returns best finalized block number.
|
||||
pub async fn on_chain_best_finalized_block_number(
|
||||
&self,
|
||||
) -> Result<BlockNumberOf<P::SourceChain>, Error> {
|
||||
// we **CAN** continue to relay finality proofs if source node is out of sync, because
|
||||
// target node may be missing proofs that are already available at the source
|
||||
let finalized_header_hash = self.client.best_finalized_header_hash().await?;
|
||||
let finalized_header = self.client.header_by_hash(finalized_header_hash).await?;
|
||||
Ok(*finalized_header.number())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: SubstrateFinalitySyncPipeline> Clone for SubstrateFinalitySource<P> {
|
||||
fn clone(&self) -> Self {
|
||||
SubstrateFinalitySource {
|
||||
client: self.client.clone(),
|
||||
maximal_header_number: self.maximal_header_number.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: SubstrateFinalitySyncPipeline> RelayClient for SubstrateFinalitySource<P> {
|
||||
type Error = Error;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), Error> {
|
||||
self.client.reconnect().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: SubstrateFinalitySyncPipeline> SourceClient<FinalitySyncPipelineAdapter<P>>
|
||||
for SubstrateFinalitySource<P>
|
||||
{
|
||||
type FinalityProofsStream = SubstrateFinalityProofsStream<P>;
|
||||
|
||||
async fn best_finalized_block_number(&self) -> Result<BlockNumberOf<P::SourceChain>, Error> {
|
||||
let mut finalized_header_number = self.on_chain_best_finalized_block_number().await?;
|
||||
// never return block number larger than requested. This way we'll never sync headers
|
||||
// past `maximal_header_number`
|
||||
if let Some(ref maximal_header_number) = self.maximal_header_number {
|
||||
let maximal_header_number = *maximal_header_number.lock().await;
|
||||
if finalized_header_number > maximal_header_number {
|
||||
finalized_header_number = maximal_header_number;
|
||||
}
|
||||
}
|
||||
Ok(finalized_header_number)
|
||||
}
|
||||
|
||||
async fn header_and_finality_proof(
|
||||
&self,
|
||||
number: BlockNumberOf<P::SourceChain>,
|
||||
) -> Result<
|
||||
(
|
||||
relay_substrate_client::SyncHeader<HeaderOf<P::SourceChain>>,
|
||||
Option<GrandpaJustification<HeaderOf<P::SourceChain>>>,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let header_hash = self.client.block_hash_by_number(number).await?;
|
||||
let signed_block = self.client.get_block(Some(header_hash)).await?;
|
||||
|
||||
let justification = signed_block
|
||||
.justification()
|
||||
.map(|raw_justification| {
|
||||
GrandpaJustification::<HeaderOf<P::SourceChain>>::decode(
|
||||
&mut raw_justification.as_slice(),
|
||||
)
|
||||
})
|
||||
.transpose()
|
||||
.map_err(Error::ResponseParseFailed)?;
|
||||
|
||||
Ok((signed_block.header().into(), justification))
|
||||
}
|
||||
|
||||
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, Error> {
|
||||
Ok(unfold(
|
||||
self.client.clone().subscribe_justifications().await?,
|
||||
move |subscription| async move {
|
||||
loop {
|
||||
let log_error = |err| {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
"Failed to read justification target from the {} justifications stream: {:?}",
|
||||
P::SourceChain::NAME,
|
||||
err,
|
||||
);
|
||||
};
|
||||
|
||||
let next_justification = subscription
|
||||
.next()
|
||||
.await
|
||||
.map_err(|err| log_error(err.to_string()))
|
||||
.ok()??;
|
||||
|
||||
let decoded_justification =
|
||||
GrandpaJustification::<HeaderOf<P::SourceChain>>::decode(
|
||||
&mut &next_justification[..],
|
||||
);
|
||||
|
||||
let justification = match decoded_justification {
|
||||
Ok(j) => j,
|
||||
Err(err) => {
|
||||
log_error(format!("decode failed with error {:?}", err));
|
||||
continue
|
||||
},
|
||||
};
|
||||
|
||||
return Some((justification, subscription))
|
||||
}
|
||||
},
|
||||
)
|
||||
.boxed())
|
||||
}
|
||||
}
|
||||
@@ -15,96 +15,122 @@
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Substrate client as Substrate finality proof target. The chain we connect to should have
|
||||
//! runtime that implements `<BridgedChainName>FinalityApi` to allow bridging with
|
||||
//! <BridgedName> chain.
|
||||
//! bridge GRANDPA pallet deployed and provide `<BridgedChainName>FinalityApi` to allow bridging
|
||||
//! with <BridgedName> chain.
|
||||
|
||||
use crate::finality_pipeline::SubstrateFinalitySyncPipeline;
|
||||
use crate::{
|
||||
finality_pipeline::{
|
||||
FinalitySyncPipelineAdapter, SubmitFinalityProofCallBuilder, SubstrateFinalitySyncPipeline,
|
||||
},
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codec::Decode;
|
||||
use finality_relay::{FinalitySyncPipeline, TargetClient};
|
||||
use relay_substrate_client::{Chain, Client, Error as SubstrateError};
|
||||
use bp_header_chain::{justification::GrandpaJustification, storage_keys::is_halted_key};
|
||||
use codec::Encode;
|
||||
use finality_relay::TargetClient;
|
||||
use relay_substrate_client::{
|
||||
AccountIdOf, AccountKeyPairOf, Chain, ChainWithGrandpa, Client, Error, HeaderIdOf, HeaderOf,
|
||||
SignParam, SyncHeader, TransactionEra, TransactionSignScheme, UnsignedTransaction,
|
||||
};
|
||||
use relay_utils::relay_loop::Client as RelayClient;
|
||||
use sp_core::{Bytes, Pair};
|
||||
|
||||
/// Substrate client as Substrate finality target.
|
||||
pub struct SubstrateFinalityTarget<C: Chain, P> {
|
||||
client: Client<C>,
|
||||
pipeline: P,
|
||||
transactions_mortality: Option<u32>,
|
||||
pub struct SubstrateFinalityTarget<P: SubstrateFinalitySyncPipeline> {
|
||||
client: Client<P::TargetChain>,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::TransactionSignScheme>>,
|
||||
}
|
||||
|
||||
impl<C: Chain, P> SubstrateFinalityTarget<C, P> {
|
||||
impl<P: SubstrateFinalitySyncPipeline> SubstrateFinalityTarget<P> {
|
||||
/// Create new Substrate headers target.
|
||||
pub fn new(client: Client<C>, pipeline: P, transactions_mortality: Option<u32>) -> Self {
|
||||
SubstrateFinalityTarget { client, pipeline, transactions_mortality }
|
||||
pub fn new(
|
||||
client: Client<P::TargetChain>,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::TransactionSignScheme>>,
|
||||
) -> Self {
|
||||
SubstrateFinalityTarget { client, transaction_params }
|
||||
}
|
||||
|
||||
/// Ensure that the GRANDPA pallet at target chain is active.
|
||||
pub async fn ensure_pallet_active(&self) -> Result<(), Error> {
|
||||
let is_halted = self
|
||||
.client
|
||||
.storage_value(is_halted_key(P::SourceChain::WITH_CHAIN_GRANDPA_PALLET_NAME), None)
|
||||
.await?;
|
||||
if is_halted.unwrap_or(false) {
|
||||
Err(Error::BridgePalletIsHalted)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Chain, P: SubstrateFinalitySyncPipeline> Clone for SubstrateFinalityTarget<C, P> {
|
||||
impl<P: SubstrateFinalitySyncPipeline> Clone for SubstrateFinalityTarget<P> {
|
||||
fn clone(&self) -> Self {
|
||||
SubstrateFinalityTarget {
|
||||
client: self.client.clone(),
|
||||
pipeline: self.pipeline.clone(),
|
||||
transactions_mortality: self.transactions_mortality,
|
||||
transaction_params: self.transaction_params.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C: Chain, P: SubstrateFinalitySyncPipeline> RelayClient for SubstrateFinalityTarget<C, P> {
|
||||
type Error = SubstrateError;
|
||||
impl<P: SubstrateFinalitySyncPipeline> RelayClient for SubstrateFinalityTarget<P> {
|
||||
type Error = Error;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
|
||||
async fn reconnect(&mut self) -> Result<(), Error> {
|
||||
self.client.reconnect().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, P> TargetClient<P::FinalitySyncPipeline> for SubstrateFinalityTarget<C, P>
|
||||
impl<P: SubstrateFinalitySyncPipeline> TargetClient<FinalitySyncPipelineAdapter<P>>
|
||||
for SubstrateFinalityTarget<P>
|
||||
where
|
||||
C: Chain,
|
||||
P: SubstrateFinalitySyncPipeline<TargetChain = C>,
|
||||
<P::FinalitySyncPipeline as FinalitySyncPipeline>::Number: Decode,
|
||||
<P::FinalitySyncPipeline as FinalitySyncPipeline>::Hash: Decode,
|
||||
AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TransactionSignScheme> as Pair>::Public>,
|
||||
P::TransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
|
||||
{
|
||||
async fn best_finalized_source_block_number(
|
||||
&self,
|
||||
) -> Result<<P::FinalitySyncPipeline as FinalitySyncPipeline>::Number, SubstrateError> {
|
||||
async fn best_finalized_source_block_id(&self) -> Result<HeaderIdOf<P::SourceChain>, Error> {
|
||||
// we can't continue to relay finality if target node is out of sync, because
|
||||
// it may have already received (some of) headers that we're going to relay
|
||||
self.client.ensure_synced().await?;
|
||||
// we can't relay finality if GRANDPA pallet at target chain is halted
|
||||
self.ensure_pallet_active().await?;
|
||||
|
||||
Ok(crate::messages_source::read_client_state::<
|
||||
C,
|
||||
<P::FinalitySyncPipeline as FinalitySyncPipeline>::Hash,
|
||||
<P::FinalitySyncPipeline as FinalitySyncPipeline>::Number,
|
||||
>(&self.client, P::BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET)
|
||||
Ok(crate::messages_source::read_client_state::<P::TargetChain, P::SourceChain>(
|
||||
&self.client,
|
||||
None,
|
||||
P::SourceChain::BEST_FINALIZED_HEADER_ID_METHOD,
|
||||
)
|
||||
.await?
|
||||
.best_finalized_peer_at_best_self
|
||||
.0)
|
||||
.best_finalized_peer_at_best_self)
|
||||
}
|
||||
|
||||
async fn submit_finality_proof(
|
||||
&self,
|
||||
header: <P::FinalitySyncPipeline as FinalitySyncPipeline>::Header,
|
||||
proof: <P::FinalitySyncPipeline as FinalitySyncPipeline>::FinalityProof,
|
||||
) -> Result<(), SubstrateError> {
|
||||
let transactions_author = self.pipeline.transactions_author();
|
||||
let pipeline = self.pipeline.clone();
|
||||
let transactions_mortality = self.transactions_mortality;
|
||||
header: SyncHeader<HeaderOf<P::SourceChain>>,
|
||||
proof: GrandpaJustification<HeaderOf<P::SourceChain>>,
|
||||
) -> Result<(), Error> {
|
||||
let genesis_hash = *self.client.genesis_hash();
|
||||
let transaction_params = self.transaction_params.clone();
|
||||
let call =
|
||||
P::SubmitFinalityProofCallBuilder::build_submit_finality_proof_call(header, proof);
|
||||
let (spec_version, transaction_version) = self.client.simple_runtime_version().await?;
|
||||
self.client
|
||||
.submit_signed_extrinsic(
|
||||
transactions_author,
|
||||
self.transaction_params.signer.public().into(),
|
||||
move |best_block_id, transaction_nonce| {
|
||||
pipeline.make_submit_finality_proof_transaction(
|
||||
relay_substrate_client::TransactionEra::new(
|
||||
best_block_id,
|
||||
transactions_mortality,
|
||||
),
|
||||
transaction_nonce,
|
||||
header,
|
||||
proof,
|
||||
)
|
||||
Ok(Bytes(
|
||||
P::TransactionSignScheme::sign_transaction(SignParam {
|
||||
spec_version,
|
||||
transaction_version,
|
||||
genesis_hash,
|
||||
signer: transaction_params.signer.clone(),
|
||||
era: TransactionEra::new(best_block_id, transaction_params.mortality),
|
||||
unsigned: UnsignedTransaction::new(call.into(), transaction_nonce),
|
||||
})?
|
||||
.encode(),
|
||||
))
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -31,17 +31,22 @@ use bp_header_chain::{
|
||||
use codec::Decode;
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use num_traits::{One, Zero};
|
||||
use relay_substrate_client::{Chain, Client};
|
||||
use relay_substrate_client::{
|
||||
BlockNumberOf, Chain, ChainWithGrandpa, Client, Error as SubstrateError, HashOf,
|
||||
};
|
||||
use sp_core::Bytes;
|
||||
use sp_finality_grandpa::AuthorityList as GrandpaAuthoritiesSet;
|
||||
use sp_runtime::traits::Header as HeaderT;
|
||||
|
||||
/// Submit headers-bridge initialization transaction.
|
||||
pub async fn initialize<SourceChain: Chain, TargetChain: Chain>(
|
||||
pub async fn initialize<SourceChain: ChainWithGrandpa, TargetChain: Chain>(
|
||||
source_client: Client<SourceChain>,
|
||||
target_client: Client<TargetChain>,
|
||||
target_transactions_signer: TargetChain::AccountId,
|
||||
prepare_initialize_transaction: impl FnOnce(TargetChain::Index, InitializationData<SourceChain::Header>) -> Bytes
|
||||
prepare_initialize_transaction: impl FnOnce(
|
||||
TargetChain::Index,
|
||||
InitializationData<SourceChain::Header>,
|
||||
) -> Result<Bytes, SubstrateError>
|
||||
+ Send
|
||||
+ 'static,
|
||||
) {
|
||||
@@ -54,13 +59,14 @@ pub async fn initialize<SourceChain: Chain, TargetChain: Chain>(
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(tx_hash) => log::info!(
|
||||
Ok(Some(tx_hash)) => log::info!(
|
||||
target: "bridge",
|
||||
"Successfully submitted {}-headers bridge initialization transaction to {}: {:?}",
|
||||
SourceChain::NAME,
|
||||
TargetChain::NAME,
|
||||
tx_hash,
|
||||
),
|
||||
Ok(None) => (),
|
||||
Err(err) => log::error!(
|
||||
target: "bridge",
|
||||
"Failed to submit {}-headers bridge initialization transaction to {}: {:?}",
|
||||
@@ -72,14 +78,31 @@ pub async fn initialize<SourceChain: Chain, TargetChain: Chain>(
|
||||
}
|
||||
|
||||
/// Craft and submit initialization transaction, returning any error that may occur.
|
||||
async fn do_initialize<SourceChain: Chain, TargetChain: Chain>(
|
||||
async fn do_initialize<SourceChain: ChainWithGrandpa, TargetChain: Chain>(
|
||||
source_client: Client<SourceChain>,
|
||||
target_client: Client<TargetChain>,
|
||||
target_transactions_signer: TargetChain::AccountId,
|
||||
prepare_initialize_transaction: impl FnOnce(TargetChain::Index, InitializationData<SourceChain::Header>) -> Bytes
|
||||
prepare_initialize_transaction: impl FnOnce(
|
||||
TargetChain::Index,
|
||||
InitializationData<SourceChain::Header>,
|
||||
) -> Result<Bytes, SubstrateError>
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> Result<TargetChain::Hash, Error<SourceChain::Hash, <SourceChain::Header as HeaderT>::Number>> {
|
||||
) -> Result<
|
||||
Option<TargetChain::Hash>,
|
||||
Error<SourceChain::Hash, <SourceChain::Header as HeaderT>::Number>,
|
||||
> {
|
||||
let is_initialized = is_initialized::<SourceChain, TargetChain>(&target_client).await?;
|
||||
if is_initialized {
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"{}-headers bridge at {} is already initialized. Skipping",
|
||||
SourceChain::NAME,
|
||||
TargetChain::NAME,
|
||||
);
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
let initialization_data = prepare_initialization_data(source_client).await?;
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
@@ -95,7 +118,23 @@ async fn do_initialize<SourceChain: Chain, TargetChain: Chain>(
|
||||
})
|
||||
.await
|
||||
.map_err(|err| Error::SubmitTransaction(TargetChain::NAME, err))?;
|
||||
Ok(initialization_tx_hash)
|
||||
Ok(Some(initialization_tx_hash))
|
||||
}
|
||||
|
||||
/// Returns `Ok(true)` if bridge has already been initialized.
|
||||
async fn is_initialized<SourceChain: ChainWithGrandpa, TargetChain: Chain>(
|
||||
target_client: &Client<TargetChain>,
|
||||
) -> Result<bool, Error<HashOf<SourceChain>, BlockNumberOf<SourceChain>>> {
|
||||
Ok(target_client
|
||||
.raw_storage_value(
|
||||
bp_header_chain::storage_keys::best_finalized_hash_key(
|
||||
SourceChain::WITH_CHAIN_GRANDPA_PALLET_NAME,
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| Error::RetrieveBestFinalizedHeaderHash(SourceChain::NAME, err))?
|
||||
.is_some())
|
||||
}
|
||||
|
||||
/// Prepare initialization data for the GRANDPA verifier pallet.
|
||||
|
||||
@@ -16,14 +16,91 @@
|
||||
|
||||
//! Substrate relay helpers
|
||||
|
||||
use relay_utils::metrics::{FloatJsonValueMetric, PrometheusError};
|
||||
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={}&vs_currencies=btc", token_id),
|
||||
format!("$.{}.btc", token_id),
|
||||
format!("{}_to_base_conversion_rate", token_id.replace("-", "_")),
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,14 @@ use std::time::Duration;
|
||||
|
||||
pub mod conversion_rate_update;
|
||||
pub mod error;
|
||||
pub mod finality_guards;
|
||||
pub mod finality_pipeline;
|
||||
pub mod finality_source;
|
||||
pub mod finality_target;
|
||||
pub mod headers_initialize;
|
||||
pub mod helpers;
|
||||
pub mod messages_lane;
|
||||
pub mod messages_metrics;
|
||||
pub mod messages_source;
|
||||
pub mod messages_target;
|
||||
pub mod on_demand_headers;
|
||||
@@ -39,3 +42,12 @@ pub mod on_demand_headers;
|
||||
/// blockchains) blocks. So 1 hour seems to be a good guess for (even congested) chains to mine
|
||||
/// transaction, or remove it from the pool.
|
||||
pub const STALL_TIMEOUT: Duration = Duration::from_secs(60 * 60);
|
||||
|
||||
/// Transaction creation parameters.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TransactionParams<TS> {
|
||||
/// Transactions author.
|
||||
pub signer: TS,
|
||||
/// Transactions mortality.
|
||||
pub mortality: Option<u32>,
|
||||
}
|
||||
|
||||
@@ -17,194 +17,434 @@
|
||||
//! Tools for supporting message lanes between two Substrate-based chains.
|
||||
|
||||
use crate::{
|
||||
messages_source::SubstrateMessagesProof, messages_target::SubstrateMessagesReceivingProof,
|
||||
conversion_rate_update::UpdateConversionRateCallBuilder,
|
||||
messages_metrics::StandaloneMessagesMetrics,
|
||||
messages_source::{SubstrateMessagesProof, SubstrateMessagesSource},
|
||||
messages_target::{SubstrateMessagesDeliveryProof, SubstrateMessagesTarget},
|
||||
on_demand_headers::OnDemandHeadersRelay,
|
||||
TransactionParams, STALL_TIMEOUT,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_messages::{LaneId, MessageNonce};
|
||||
use bp_runtime::{AccountIdOf, IndexOf};
|
||||
use frame_support::weights::Weight;
|
||||
use messages_relay::{
|
||||
message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf},
|
||||
relay_strategy::RelayStrategy,
|
||||
use bp_runtime::{AccountIdOf, Chain as _};
|
||||
use bridge_runtime_common::messages::{
|
||||
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
|
||||
};
|
||||
use codec::Encode;
|
||||
use frame_support::weights::{GetDispatchInfo, Weight};
|
||||
use messages_relay::{message_lane::MessageLane, relay_strategy::RelayStrategy};
|
||||
use pallet_bridge_messages::{Call as BridgeMessagesCall, Config as BridgeMessagesConfig};
|
||||
use relay_substrate_client::{
|
||||
metrics::{FloatStorageValueMetric, StorageProofOverheadMetric},
|
||||
BlockNumberOf, Chain, Client, HashOf,
|
||||
transaction_stall_timeout, AccountKeyPairOf, BalanceOf, BlockNumberOf, CallOf, Chain,
|
||||
ChainWithMessages, Client, HashOf, TransactionSignScheme,
|
||||
};
|
||||
use relay_utils::{
|
||||
metrics::{
|
||||
FloatJsonValueMetric, GlobalMetrics, MetricsParams, PrometheusError, StandaloneMetric,
|
||||
},
|
||||
BlockNumberBase,
|
||||
};
|
||||
use sp_core::{storage::StorageKey, Bytes};
|
||||
use sp_runtime::FixedU128;
|
||||
use std::ops::RangeInclusive;
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
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;
|
||||
/// Messages from the `SourceChain` are dispatched on this chain.
|
||||
type TargetChain: ChainWithMessages;
|
||||
|
||||
/// Scheme used to sign source chain transactions.
|
||||
type SourceTransactionSignScheme: TransactionSignScheme;
|
||||
/// Scheme used to sign target chain transactions.
|
||||
type TargetTransactionSignScheme: TransactionSignScheme;
|
||||
|
||||
/// How receive messages proof call is built?
|
||||
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`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct MessageLaneAdapter<P: SubstrateMessageLane> {
|
||||
_phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: SubstrateMessageLane> MessageLane for MessageLaneAdapter<P> {
|
||||
const SOURCE_NAME: &'static str = P::SourceChain::NAME;
|
||||
const TARGET_NAME: &'static str = P::TargetChain::NAME;
|
||||
|
||||
type MessagesProof = SubstrateMessagesProof<P::SourceChain>;
|
||||
type MessagesReceivingProof = SubstrateMessagesDeliveryProof<P::TargetChain>;
|
||||
|
||||
type SourceChainBalance = BalanceOf<P::SourceChain>;
|
||||
type SourceHeaderNumber = BlockNumberOf<P::SourceChain>;
|
||||
type SourceHeaderHash = HashOf<P::SourceChain>;
|
||||
|
||||
type TargetHeaderNumber = BlockNumberOf<P::TargetChain>;
|
||||
type TargetHeaderHash = HashOf<P::TargetChain>;
|
||||
}
|
||||
|
||||
/// Substrate <-> Substrate messages relay parameters.
|
||||
pub struct MessagesRelayParams<SC: Chain, SS, TC: Chain, TS, Strategy: RelayStrategy> {
|
||||
pub struct MessagesRelayParams<P: SubstrateMessageLane> {
|
||||
/// Messages source client.
|
||||
pub source_client: Client<SC>,
|
||||
/// Sign parameters for messages source chain.
|
||||
pub source_sign: SS,
|
||||
/// Mortality of source transactions.
|
||||
pub source_transactions_mortality: Option<u32>,
|
||||
pub source_client: Client<P::SourceChain>,
|
||||
/// Source transaction params.
|
||||
pub source_transaction_params:
|
||||
TransactionParams<AccountKeyPairOf<P::SourceTransactionSignScheme>>,
|
||||
/// Messages target client.
|
||||
pub target_client: Client<TC>,
|
||||
/// Sign parameters for messages target chain.
|
||||
pub target_sign: TS,
|
||||
/// Mortality of target transactions.
|
||||
pub target_transactions_mortality: Option<u32>,
|
||||
pub target_client: Client<P::TargetChain>,
|
||||
/// Target transaction params.
|
||||
pub target_transaction_params:
|
||||
TransactionParams<AccountKeyPairOf<P::TargetTransactionSignScheme>>,
|
||||
/// Optional on-demand source to target headers relay.
|
||||
pub source_to_target_headers_relay: Option<OnDemandHeadersRelay<SC>>,
|
||||
pub source_to_target_headers_relay: Option<OnDemandHeadersRelay<P::SourceChain>>,
|
||||
/// Optional on-demand target to source headers relay.
|
||||
pub target_to_source_headers_relay: Option<OnDemandHeadersRelay<TC>>,
|
||||
pub target_to_source_headers_relay: Option<OnDemandHeadersRelay<P::TargetChain>>,
|
||||
/// Identifier of lane that needs to be served.
|
||||
pub lane_id: LaneId,
|
||||
/// Metrics parameters.
|
||||
pub metrics_params: MetricsParams,
|
||||
/// Pre-registered standalone metrics.
|
||||
pub standalone_metrics: Option<StandaloneMessagesMetrics<SC, TC>>,
|
||||
/// Relay strategy
|
||||
pub relay_strategy: Strategy,
|
||||
pub standalone_metrics: Option<StandaloneMessagesMetrics<P::SourceChain, P::TargetChain>>,
|
||||
/// Relay strategy.
|
||||
pub relay_strategy: P::RelayStrategy,
|
||||
}
|
||||
|
||||
/// Message sync pipeline for Substrate <-> Substrate relays.
|
||||
#[async_trait]
|
||||
pub trait SubstrateMessageLane: 'static + Clone + Send + Sync {
|
||||
/// Underlying generic message lane.
|
||||
type MessageLane: MessageLane;
|
||||
|
||||
/// Name of the runtime method that returns dispatch weight of outbound messages at the source
|
||||
/// chain.
|
||||
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str;
|
||||
/// Name of the runtime method that returns latest generated nonce at the source chain.
|
||||
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str;
|
||||
/// Name of the runtime method that returns latest received (confirmed) nonce at the the source
|
||||
/// chain.
|
||||
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str;
|
||||
|
||||
/// Name of the runtime method that returns latest received nonce at the target chain.
|
||||
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str;
|
||||
/// Name of the runtime method that returns the latest confirmed (reward-paid) nonce at the
|
||||
/// target chain.
|
||||
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str;
|
||||
/// Number of the runtime method that returns state of "unrewarded relayers" set at the target
|
||||
/// chain.
|
||||
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str;
|
||||
|
||||
/// Name of the runtime method that returns id of best finalized source header at target chain.
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str;
|
||||
/// Name of the runtime method that returns id of best finalized target header at source chain.
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str;
|
||||
|
||||
/// Name of the messages pallet as it is declared in the `construct_runtime!()` at source chain.
|
||||
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str;
|
||||
/// Name of the messages pallet as it is declared in the `construct_runtime!()` at target chain.
|
||||
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str;
|
||||
|
||||
/// Extra weight of the delivery transaction at the target chain, that is paid to cover
|
||||
/// dispatch fee payment.
|
||||
///
|
||||
/// If dispatch fee is paid at the source chain, then this weight is refunded by the
|
||||
/// delivery transaction.
|
||||
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight;
|
||||
|
||||
/// Source chain.
|
||||
type SourceChain: Chain;
|
||||
/// Target chain.
|
||||
type TargetChain: Chain;
|
||||
|
||||
/// Returns id of account that we're using to sign transactions at target chain (messages
|
||||
/// proof).
|
||||
fn target_transactions_author(&self) -> AccountIdOf<Self::TargetChain>;
|
||||
|
||||
/// Make messages delivery transaction.
|
||||
fn make_messages_delivery_transaction(
|
||||
&self,
|
||||
best_block_id: TargetHeaderIdOf<Self::MessageLane>,
|
||||
transaction_nonce: IndexOf<Self::TargetChain>,
|
||||
generated_at_header: SourceHeaderIdOf<Self::MessageLane>,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
proof: <Self::MessageLane as MessageLane>::MessagesProof,
|
||||
) -> Bytes;
|
||||
|
||||
/// Returns id of account that we're using to sign transactions at source chain (delivery
|
||||
/// proof).
|
||||
fn source_transactions_author(&self) -> AccountIdOf<Self::SourceChain>;
|
||||
|
||||
/// Make messages receiving proof transaction.
|
||||
fn make_messages_receiving_proof_transaction(
|
||||
&self,
|
||||
best_block_id: SourceHeaderIdOf<Self::MessageLane>,
|
||||
transaction_nonce: IndexOf<Self::SourceChain>,
|
||||
generated_at_header: TargetHeaderIdOf<Self::MessageLane>,
|
||||
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
|
||||
) -> Bytes;
|
||||
}
|
||||
|
||||
/// Substrate-to-Substrate message lane.
|
||||
#[derive(Debug)]
|
||||
pub struct SubstrateMessageLaneToSubstrate<
|
||||
Source: Chain,
|
||||
SourceSignParams,
|
||||
Target: Chain,
|
||||
TargetSignParams,
|
||||
> {
|
||||
/// Client for the source Substrate chain.
|
||||
pub source_client: Client<Source>,
|
||||
/// Parameters required to sign transactions for source chain.
|
||||
pub source_sign: SourceSignParams,
|
||||
/// Source transactions mortality.
|
||||
pub source_transactions_mortality: Option<u32>,
|
||||
/// Client for the target Substrate chain.
|
||||
pub target_client: Client<Target>,
|
||||
/// Parameters required to sign transactions for target chain.
|
||||
pub target_sign: TargetSignParams,
|
||||
/// Target transactions mortality.
|
||||
pub target_transactions_mortality: Option<u32>,
|
||||
/// Account id of relayer at the source chain.
|
||||
pub relayer_id_at_source: Source::AccountId,
|
||||
}
|
||||
|
||||
impl<Source: Chain, SourceSignParams: Clone, Target: Chain, TargetSignParams: Clone> Clone
|
||||
for SubstrateMessageLaneToSubstrate<Source, SourceSignParams, Target, TargetSignParams>
|
||||
/// Run Substrate-to-Substrate messages sync loop.
|
||||
pub async fn run<P: SubstrateMessageLane>(params: MessagesRelayParams<P>) -> anyhow::Result<()>
|
||||
where
|
||||
AccountIdOf<P::SourceChain>:
|
||||
From<<AccountKeyPairOf<P::SourceTransactionSignScheme> as Pair>::Public>,
|
||||
AccountIdOf<P::TargetChain>:
|
||||
From<<AccountKeyPairOf<P::TargetTransactionSignScheme> as Pair>::Public>,
|
||||
BalanceOf<P::SourceChain>: TryFrom<BalanceOf<P::TargetChain>>,
|
||||
P::SourceTransactionSignScheme: TransactionSignScheme<Chain = P::SourceChain>,
|
||||
P::TargetTransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
source_client: self.source_client.clone(),
|
||||
source_sign: self.source_sign.clone(),
|
||||
source_transactions_mortality: self.source_transactions_mortality,
|
||||
target_client: self.target_client.clone(),
|
||||
target_sign: self.target_sign.clone(),
|
||||
target_transactions_mortality: self.target_transactions_mortality,
|
||||
relayer_id_at_source: self.relayer_id_at_source.clone(),
|
||||
let source_client = params.source_client;
|
||||
let target_client = params.target_client;
|
||||
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
|
||||
params.source_transaction_params.mortality,
|
||||
params.target_transaction_params.mortality,
|
||||
P::SourceChain::AVERAGE_BLOCK_INTERVAL,
|
||||
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
STALL_TIMEOUT,
|
||||
);
|
||||
let relayer_id_at_source: AccountIdOf<P::SourceChain> =
|
||||
params.source_transaction_params.signer.public().into();
|
||||
|
||||
// 2/3 is reserved for proofs and tx overhead
|
||||
let max_messages_size_in_single_batch = P::TargetChain::max_extrinsic_size() / 3;
|
||||
// we don't know exact weights of the Polkadot runtime. So to guess weights we'll be using
|
||||
// weights from Rialto and then simply dividing it by x2.
|
||||
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
|
||||
crate::messages_lane::select_delivery_transaction_limits::<
|
||||
<P::TargetChain as ChainWithMessages>::WeightInfo,
|
||||
>(
|
||||
P::TargetChain::max_extrinsic_weight(),
|
||||
P::SourceChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
|
||||
);
|
||||
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\
|
||||
{} relayer account id: {:?}\n\t\
|
||||
Max messages in single transaction: {}\n\t\
|
||||
Max messages size in single transaction: {}\n\t\
|
||||
Max messages weight in single transaction: {}\n\t\
|
||||
Tx mortality: {:?} (~{}m)/{:?} (~{}m)\n\t\
|
||||
Stall timeout: {:?}",
|
||||
P::SourceChain::NAME,
|
||||
P::TargetChain::NAME,
|
||||
P::SourceChain::NAME,
|
||||
relayer_id_at_source,
|
||||
max_messages_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
params.source_transaction_params.mortality,
|
||||
transaction_stall_timeout(
|
||||
params.source_transaction_params.mortality,
|
||||
P::SourceChain::AVERAGE_BLOCK_INTERVAL,
|
||||
STALL_TIMEOUT,
|
||||
).as_secs_f64() / 60.0f64,
|
||||
params.target_transaction_params.mortality,
|
||||
transaction_stall_timeout(
|
||||
params.target_transaction_params.mortality,
|
||||
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
STALL_TIMEOUT,
|
||||
).as_secs_f64() / 60.0f64,
|
||||
stall_timeout,
|
||||
);
|
||||
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: params.lane_id,
|
||||
source_tick: P::SourceChain::AVERAGE_BLOCK_INTERVAL,
|
||||
target_tick: P::TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
|
||||
stall_timeout,
|
||||
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
|
||||
max_unrewarded_relayer_entries_at_target:
|
||||
P::SourceChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
|
||||
max_unconfirmed_nonces_at_target:
|
||||
P::SourceChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
|
||||
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(
|
||||
source_client.clone(),
|
||||
target_client.clone(),
|
||||
params.lane_id,
|
||||
params.source_transaction_params,
|
||||
params.target_to_source_headers_relay,
|
||||
),
|
||||
SubstrateMessagesTarget::<P>::new(
|
||||
target_client,
|
||||
source_client,
|
||||
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)?,
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Different ways of building `receive_messages_proof` calls.
|
||||
pub trait ReceiveMessagesProofCallBuilder<P: SubstrateMessageLane> {
|
||||
/// Given messages proof, build call of `receive_messages_proof` function of bridge
|
||||
/// messages module at the target chain.
|
||||
fn build_receive_messages_proof_call(
|
||||
relayer_id_at_source: AccountIdOf<P::SourceChain>,
|
||||
proof: SubstrateMessagesProof<P::SourceChain>,
|
||||
messages_count: u32,
|
||||
dispatch_weight: Weight,
|
||||
trace_call: bool,
|
||||
) -> CallOf<P::TargetChain>;
|
||||
}
|
||||
|
||||
/// Building `receive_messages_proof` call when you have direct access to the target
|
||||
/// chain runtime.
|
||||
pub struct DirectReceiveMessagesProofCallBuilder<P, R, I> {
|
||||
_phantom: PhantomData<(P, R, I)>,
|
||||
}
|
||||
|
||||
impl<P, R, I> ReceiveMessagesProofCallBuilder<P> for DirectReceiveMessagesProofCallBuilder<P, R, I>
|
||||
where
|
||||
P: SubstrateMessageLane,
|
||||
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,
|
||||
{
|
||||
fn build_receive_messages_proof_call(
|
||||
relayer_id_at_source: AccountIdOf<P::SourceChain>,
|
||||
proof: SubstrateMessagesProof<P::SourceChain>,
|
||||
messages_count: u32,
|
||||
dispatch_weight: Weight,
|
||||
trace_call: bool,
|
||||
) -> CallOf<P::TargetChain> {
|
||||
let call: CallOf<P::TargetChain> = BridgeMessagesCall::<R, I>::receive_messages_proof {
|
||||
relayer_id_at_bridged_chain: relayer_id_at_source,
|
||||
proof: proof.1,
|
||||
messages_count,
|
||||
dispatch_weight,
|
||||
}
|
||||
.into();
|
||||
if trace_call {
|
||||
// this trace isn't super-accurate, because limits are for transactions and we
|
||||
// have a call here, but it provides required information
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared {} -> {} messages delivery call. Weight: {}/{}, size: {}/{}",
|
||||
P::SourceChain::NAME,
|
||||
P::TargetChain::NAME,
|
||||
call.get_dispatch_info().weight,
|
||||
P::TargetChain::max_extrinsic_weight(),
|
||||
call.encode().len(),
|
||||
P::TargetChain::max_extrinsic_size(),
|
||||
);
|
||||
}
|
||||
call
|
||||
}
|
||||
}
|
||||
|
||||
impl<Source: Chain, SourceSignParams, Target: Chain, TargetSignParams> MessageLane
|
||||
for SubstrateMessageLaneToSubstrate<Source, SourceSignParams, Target, TargetSignParams>
|
||||
/// Macro that generates `ReceiveMessagesProofCallBuilder` implementation for the case when
|
||||
/// you only have an access to the mocked version of target chain runtime. In this case you
|
||||
/// should provide "name" of the call variant for the bridge messages calls and the "name" of
|
||||
/// the variant for the `receive_messages_proof` call within that first option.
|
||||
#[rustfmt::skip]
|
||||
#[macro_export]
|
||||
macro_rules! generate_mocked_receive_message_proof_call_builder {
|
||||
($pipeline:ident, $mocked_builder:ident, $bridge_messages:path, $receive_messages_proof:path) => {
|
||||
pub struct $mocked_builder;
|
||||
|
||||
impl $crate::messages_lane::ReceiveMessagesProofCallBuilder<$pipeline>
|
||||
for $mocked_builder
|
||||
{
|
||||
fn build_receive_messages_proof_call(
|
||||
relayer_id_at_source: relay_substrate_client::AccountIdOf<
|
||||
<$pipeline as $crate::messages_lane::SubstrateMessageLane>::SourceChain
|
||||
>,
|
||||
proof: $crate::messages_source::SubstrateMessagesProof<
|
||||
<$pipeline as $crate::messages_lane::SubstrateMessageLane>::SourceChain
|
||||
>,
|
||||
messages_count: u32,
|
||||
dispatch_weight: Weight,
|
||||
_trace_call: bool,
|
||||
) -> relay_substrate_client::CallOf<
|
||||
<$pipeline as $crate::messages_lane::SubstrateMessageLane>::TargetChain
|
||||
> {
|
||||
$bridge_messages($receive_messages_proof(
|
||||
relayer_id_at_source,
|
||||
proof.1,
|
||||
messages_count,
|
||||
dispatch_weight,
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Different ways of building `receive_messages_delivery_proof` calls.
|
||||
pub trait ReceiveMessagesDeliveryProofCallBuilder<P: SubstrateMessageLane> {
|
||||
/// Given messages delivery proof, build call of `receive_messages_delivery_proof` function of
|
||||
/// bridge messages module at the source chain.
|
||||
fn build_receive_messages_delivery_proof_call(
|
||||
proof: SubstrateMessagesDeliveryProof<P::TargetChain>,
|
||||
trace_call: bool,
|
||||
) -> CallOf<P::SourceChain>;
|
||||
}
|
||||
|
||||
/// Building `receive_messages_delivery_proof` call when you have direct access to the source
|
||||
/// chain runtime.
|
||||
pub struct DirectReceiveMessagesDeliveryProofCallBuilder<P, R, I> {
|
||||
_phantom: PhantomData<(P, R, I)>,
|
||||
}
|
||||
|
||||
impl<P, R, I> ReceiveMessagesDeliveryProofCallBuilder<P>
|
||||
for DirectReceiveMessagesDeliveryProofCallBuilder<P, R, I>
|
||||
where
|
||||
SourceSignParams: Clone + Send + Sync + 'static,
|
||||
TargetSignParams: Clone + Send + Sync + 'static,
|
||||
BlockNumberOf<Source>: BlockNumberBase,
|
||||
BlockNumberOf<Target>: BlockNumberBase,
|
||||
P: SubstrateMessageLane,
|
||||
R: BridgeMessagesConfig<I>,
|
||||
I: 'static,
|
||||
R::TargetHeaderChain: bp_messages::source_chain::TargetHeaderChain<
|
||||
R::OutboundPayload,
|
||||
R::AccountId,
|
||||
MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof<HashOf<P::TargetChain>>,
|
||||
>,
|
||||
CallOf<P::SourceChain>: From<BridgeMessagesCall<R, I>> + GetDispatchInfo,
|
||||
{
|
||||
const SOURCE_NAME: &'static str = Source::NAME;
|
||||
const TARGET_NAME: &'static str = Target::NAME;
|
||||
fn build_receive_messages_delivery_proof_call(
|
||||
proof: SubstrateMessagesDeliveryProof<P::TargetChain>,
|
||||
trace_call: bool,
|
||||
) -> CallOf<P::SourceChain> {
|
||||
let call: CallOf<P::SourceChain> =
|
||||
BridgeMessagesCall::<R, I>::receive_messages_delivery_proof {
|
||||
proof: proof.1,
|
||||
relayers_state: proof.0,
|
||||
}
|
||||
.into();
|
||||
if trace_call {
|
||||
// this trace isn't super-accurate, because limits are for transactions and we
|
||||
// have a call here, but it provides required information
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared {} -> {} delivery confirmation transaction. Weight: {}/{}, size: {}/{}",
|
||||
P::TargetChain::NAME,
|
||||
P::SourceChain::NAME,
|
||||
call.get_dispatch_info().weight,
|
||||
P::SourceChain::max_extrinsic_weight(),
|
||||
call.encode().len(),
|
||||
P::SourceChain::max_extrinsic_size(),
|
||||
);
|
||||
}
|
||||
call
|
||||
}
|
||||
}
|
||||
|
||||
type MessagesProof = SubstrateMessagesProof<Source>;
|
||||
type MessagesReceivingProof = SubstrateMessagesReceivingProof<Target>;
|
||||
/// Macro that generates `ReceiveMessagesDeliveryProofCallBuilder` 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 and the "name" of
|
||||
/// the variant for the `receive_messages_delivery_proof` call within that first option.
|
||||
#[rustfmt::skip]
|
||||
#[macro_export]
|
||||
macro_rules! generate_mocked_receive_message_delivery_proof_call_builder {
|
||||
($pipeline:ident, $mocked_builder:ident, $bridge_messages:path, $receive_messages_delivery_proof:path) => {
|
||||
pub struct $mocked_builder;
|
||||
|
||||
type SourceChainBalance = Source::Balance;
|
||||
type SourceHeaderNumber = BlockNumberOf<Source>;
|
||||
type SourceHeaderHash = HashOf<Source>;
|
||||
|
||||
type TargetHeaderNumber = BlockNumberOf<Target>;
|
||||
type TargetHeaderHash = HashOf<Target>;
|
||||
impl $crate::messages_lane::ReceiveMessagesDeliveryProofCallBuilder<$pipeline>
|
||||
for $mocked_builder
|
||||
{
|
||||
fn build_receive_messages_delivery_proof_call(
|
||||
proof: $crate::messages_target::SubstrateMessagesDeliveryProof<
|
||||
<$pipeline as $crate::messages_lane::SubstrateMessageLane>::TargetChain
|
||||
>,
|
||||
_trace_call: bool,
|
||||
) -> relay_substrate_client::CallOf<
|
||||
<$pipeline as $crate::messages_lane::SubstrateMessageLane>::SourceChain
|
||||
> {
|
||||
$bridge_messages($receive_messages_delivery_proof(proof.1, proof.0))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns maximal number of messages and their maximal cumulative dispatch weight, based
|
||||
@@ -245,165 +485,20 @@ pub fn select_delivery_transaction_limits<W: pallet_bridge_messages::WeightInfoE
|
||||
(max_number_of_messages, weight_for_messages_dispatch)
|
||||
}
|
||||
|
||||
/// 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, sp_runtime::FixedU128>>,
|
||||
/// 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, sp_runtime::FixedU128>>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)?;
|
||||
}
|
||||
Ok(metrics)
|
||||
}
|
||||
|
||||
/// Return conversion rate from target to source tokens.
|
||||
pub async fn target_to_source_conversion_rate(&self) -> Option<f64> {
|
||||
Self::compute_target_to_source_conversion_rate(
|
||||
*self.target_to_base_conversion_rate.as_ref()?.shared_value_ref().read().await,
|
||||
*self.source_to_base_conversion_rate.as_ref()?.shared_value_ref().read().await,
|
||||
)
|
||||
}
|
||||
|
||||
/// Return conversion rate from target to source tokens, given conversion rates from
|
||||
/// target/source tokens to some base token.
|
||||
fn compute_target_to_source_conversion_rate(
|
||||
target_to_base_conversion_rate: Option<f64>,
|
||||
source_to_base_conversion_rate: Option<f64>,
|
||||
) -> Option<f64> {
|
||||
Some(source_to_base_conversion_rate? / target_to_base_conversion_rate?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create 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`).
|
||||
pub fn standalone_metrics<SC: Chain, TC: Chain>(
|
||||
source_client: Client<SC>,
|
||||
target_client: Client<TC>,
|
||||
source_chain_token_id: Option<&str>,
|
||||
target_chain_token_id: Option<&str>,
|
||||
source_to_target_conversion_rate_params: Option<(StorageKey, FixedU128)>,
|
||||
target_to_source_conversion_rate_params: Option<(StorageKey, FixedU128)>,
|
||||
) -> anyhow::Result<StandaloneMessagesMetrics<SC, TC>> {
|
||||
Ok(StandaloneMessagesMetrics {
|
||||
global: GlobalMetrics::new()?,
|
||||
source_storage_proof_overhead: StorageProofOverheadMetric::new(
|
||||
source_client.clone(),
|
||||
format!("{}_storage_proof_overhead", SC::NAME.to_lowercase()),
|
||||
format!("{} storage proof overhead", SC::NAME),
|
||||
)?,
|
||||
target_storage_proof_overhead: StorageProofOverheadMetric::new(
|
||||
target_client.clone(),
|
||||
format!("{}_storage_proof_overhead", TC::NAME.to_lowercase()),
|
||||
format!("{} storage proof overhead", TC::NAME),
|
||||
)?,
|
||||
source_to_base_conversion_rate: source_chain_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: target_chain_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: source_to_target_conversion_rate_params
|
||||
.map(|(key, rate)| {
|
||||
FloatStorageValueMetric::<_, sp_runtime::FixedU128>::new(
|
||||
target_client,
|
||||
key,
|
||||
Some(rate),
|
||||
format!("{}_{}_to_{}_conversion_rate", TC::NAME, SC::NAME, TC::NAME),
|
||||
format!(
|
||||
"{} to {} tokens conversion rate (used by {})",
|
||||
SC::NAME,
|
||||
TC::NAME,
|
||||
TC::NAME
|
||||
),
|
||||
)
|
||||
.map(Some)
|
||||
})
|
||||
.unwrap_or(Ok(None))?,
|
||||
target_to_source_conversion_rate: target_to_source_conversion_rate_params
|
||||
.map(|(key, rate)| {
|
||||
FloatStorageValueMetric::<_, sp_runtime::FixedU128>::new(
|
||||
source_client,
|
||||
key,
|
||||
Some(rate),
|
||||
format!("{}_{}_to_{}_conversion_rate", SC::NAME, TC::NAME, SC::NAME),
|
||||
format!(
|
||||
"{} to {} tokens conversion rate (used by {})",
|
||||
TC::NAME,
|
||||
SC::NAME,
|
||||
SC::NAME
|
||||
),
|
||||
)
|
||||
.map(Some)
|
||||
})
|
||||
.unwrap_or(Ok(None))?,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bp_runtime::Chain;
|
||||
|
||||
type RialtoToMillauMessagesWeights =
|
||||
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>;
|
||||
pallet_bridge_messages::weights::MillauWeight<rialto_runtime::Runtime>;
|
||||
|
||||
#[test]
|
||||
fn select_delivery_transaction_limits_works() {
|
||||
let (max_count, max_weight) =
|
||||
select_delivery_transaction_limits::<RialtoToMillauMessagesWeights>(
|
||||
bp_millau::max_extrinsic_weight(),
|
||||
bp_millau::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
bp_millau::Millau::max_extrinsic_weight(),
|
||||
bp_rialto::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
|
||||
);
|
||||
assert_eq!(
|
||||
(max_count, max_weight),
|
||||
@@ -412,15 +507,7 @@ mod tests {
|
||||
// i.e. weight reserved for messages dispatch allows dispatch of non-trivial messages.
|
||||
//
|
||||
// Any significant change in this values should attract additional attention.
|
||||
(782, 216_583_333_334),
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn target_to_source_conversion_rate_works() {
|
||||
assert_eq!(
|
||||
StandaloneMessagesMetrics::<relay_rococo_client::Rococo, relay_wococo_client::Wococo>::compute_target_to_source_conversion_rate(Some(183.15), Some(12.32)),
|
||||
Some(12.32 / 183.15),
|
||||
(958, 216_583_333_334),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,389 @@
|
||||
// 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 supporting message lanes between two Substrate-based chains.
|
||||
|
||||
use crate::{helpers::tokens_conversion_rate, messages_lane::SubstrateMessageLane};
|
||||
|
||||
use codec::Decode;
|
||||
use frame_system::AccountInfo;
|
||||
use pallet_balances::AccountData;
|
||||
use relay_substrate_client::{
|
||||
metrics::{
|
||||
FixedU128OrOne, FloatStorageValue, FloatStorageValueMetric, StorageProofOverheadMetric,
|
||||
},
|
||||
AccountIdOf, BalanceOf, Chain, ChainWithBalances, Client, Error as SubstrateError, IndexOf,
|
||||
};
|
||||
use relay_utils::metrics::{
|
||||
FloatJsonValueMetric, GlobalMetrics, MetricsParams, PrometheusError, 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>,
|
||||
metrics: MetricsParams,
|
||||
relay_account_id: Option<AccountIdOf<C>>,
|
||||
messages_pallet_owner_account_id: Option<AccountIdOf<C>>,
|
||||
) -> anyhow::Result<MetricsParams>
|
||||
where
|
||||
BalanceOf<C>: Into<u128> + std::fmt::Debug,
|
||||
{
|
||||
if relay_account_id.is_none() && messages_pallet_owner_account_id.is_none() {
|
||||
return Ok(metrics)
|
||||
}
|
||||
|
||||
// if `tokenDecimals` is missing from system properties, we'll be using
|
||||
let token_decimals = client
|
||||
.token_decimals()
|
||||
.await?
|
||||
.map(|token_decimals| {
|
||||
log::info!(target: "bridge", "Read `tokenDecimals` for {}: {}", C::NAME, token_decimals);
|
||||
token_decimals
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
// turns out it is normal not to have this property - e.g. when polkadot binary is
|
||||
// started using `polkadot-local` chain. Let's use minimal nominal here
|
||||
log::info!(target: "bridge", "Using default (zero) `tokenDecimals` value for {}", C::NAME);
|
||||
0
|
||||
});
|
||||
let token_decimals = u32::try_from(token_decimals).map_err(|e| {
|
||||
anyhow::format_err!(
|
||||
"Token decimals value ({}) of {} doesn't fit into u32: {:?}",
|
||||
token_decimals,
|
||||
C::NAME,
|
||||
e,
|
||||
)
|
||||
})?;
|
||||
if let Some(relay_account_id) = relay_account_id {
|
||||
let relay_account_balance_metric = FloatStorageValueMetric::new(
|
||||
FreeAccountBalance::<C> { token_decimals, _phantom: Default::default() },
|
||||
client.clone(),
|
||||
C::account_info_storage_key(&relay_account_id),
|
||||
format!("at_{}_relay_balance", C::NAME),
|
||||
format!("Balance of the relay account at the {}", C::NAME),
|
||||
)?;
|
||||
relay_account_balance_metric.register_and_spawn(&metrics.registry)?;
|
||||
}
|
||||
if let Some(messages_pallet_owner_account_id) = messages_pallet_owner_account_id {
|
||||
let pallet_owner_account_balance_metric = FloatStorageValueMetric::new(
|
||||
FreeAccountBalance::<C> { token_decimals, _phantom: Default::default() },
|
||||
client.clone(),
|
||||
C::account_info_storage_key(&messages_pallet_owner_account_id),
|
||||
format!("at_{}_messages_pallet_owner_balance", C::NAME),
|
||||
format!("Balance of the messages pallet owner at the {}", C::NAME),
|
||||
)?;
|
||||
pallet_owner_account_balance_metric.register_and_spawn(&metrics.registry)?;
|
||||
}
|
||||
Ok(metrics)
|
||||
}
|
||||
|
||||
/// Adapter for `FloatStorageValueMetric` to decode account free balance.
|
||||
#[derive(Clone, Debug)]
|
||||
struct FreeAccountBalance<C> {
|
||||
token_decimals: u32,
|
||||
_phantom: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<C> FloatStorageValue for FreeAccountBalance<C>
|
||||
where
|
||||
C: Chain,
|
||||
BalanceOf<C>: Into<u128>,
|
||||
{
|
||||
type Value = FixedU128;
|
||||
|
||||
fn decode(
|
||||
&self,
|
||||
maybe_raw_value: Option<StorageData>,
|
||||
) -> Result<Option<Self::Value>, SubstrateError> {
|
||||
maybe_raw_value
|
||||
.map(|raw_value| {
|
||||
AccountInfo::<IndexOf<C>, AccountData<BalanceOf<C>>>::decode(&mut &raw_value.0[..])
|
||||
.map_err(SubstrateError::ResponseParseFailed)
|
||||
.map(|account_data| {
|
||||
convert_to_token_balance(account_data.data.free.into(), self.token_decimals)
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert from raw `u128` balance (nominated in smallest chain token units) to the float regular
|
||||
/// tokens value.
|
||||
fn convert_to_token_balance(balance: u128, token_decimals: u32) -> FixedU128 {
|
||||
FixedU128::from_inner(balance.saturating_mul(FixedU128::DIV / 10u128.pow(token_decimals)))
|
||||
}
|
||||
|
||||
#[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;
|
||||
let token_decimals = 10;
|
||||
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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,19 @@
|
||||
//! <BridgedName> chain.
|
||||
|
||||
use crate::{
|
||||
messages_lane::SubstrateMessageLane, messages_target::SubstrateMessagesReceivingProof,
|
||||
messages_lane::{
|
||||
MessageLaneAdapter, ReceiveMessagesDeliveryProofCallBuilder, SubstrateMessageLane,
|
||||
},
|
||||
messages_target::SubstrateMessagesDeliveryProof,
|
||||
on_demand_headers::OnDemandHeadersRelay,
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState};
|
||||
use bp_messages::{
|
||||
storage_keys::{operating_mode_key, outbound_lane_data_key},
|
||||
LaneId, MessageNonce, OperatingMode, OutboundLaneData, UnrewardedRelayersState,
|
||||
};
|
||||
use bridge_runtime_common::messages::{
|
||||
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
|
||||
};
|
||||
@@ -39,15 +46,13 @@ use messages_relay::{
|
||||
};
|
||||
use num_traits::{Bounded, Zero};
|
||||
use relay_substrate_client::{
|
||||
BalanceOf, BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf, HeaderIdOf, HeaderOf,
|
||||
IndexOf,
|
||||
};
|
||||
use relay_utils::{relay_loop::Client as RelayClient, BlockNumberBase, HeaderId};
|
||||
use sp_core::Bytes;
|
||||
use sp_runtime::{
|
||||
traits::{AtLeast32BitUnsigned, Header as HeaderT},
|
||||
DeserializeOwned,
|
||||
AccountIdOf, AccountKeyPairOf, BalanceOf, BlockNumberOf, Chain, ChainWithMessages, Client,
|
||||
Error as SubstrateError, HashOf, HeaderIdOf, IndexOf, SignParam, TransactionEra,
|
||||
TransactionSignScheme, UnsignedTransaction,
|
||||
};
|
||||
use relay_utils::{relay_loop::Client as RelayClient, HeaderId};
|
||||
use sp_core::{Bytes, Pair};
|
||||
use sp_runtime::{traits::Header as HeaderT, DeserializeOwned};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// Intermediate message proof returned by the source Substrate node. Includes everything
|
||||
@@ -57,30 +62,60 @@ pub type SubstrateMessagesProof<C> = (Weight, FromBridgedChainMessagesProof<Hash
|
||||
|
||||
/// Substrate client as Substrate messages source.
|
||||
pub struct SubstrateMessagesSource<P: SubstrateMessageLane> {
|
||||
client: Client<P::SourceChain>,
|
||||
lane: P,
|
||||
source_client: Client<P::SourceChain>,
|
||||
target_client: Client<P::TargetChain>,
|
||||
lane_id: LaneId,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::SourceTransactionSignScheme>>,
|
||||
target_to_source_headers_relay: Option<OnDemandHeadersRelay<P::TargetChain>>,
|
||||
}
|
||||
|
||||
impl<P: SubstrateMessageLane> SubstrateMessagesSource<P> {
|
||||
/// Create new Substrate headers source.
|
||||
pub fn new(
|
||||
client: Client<P::SourceChain>,
|
||||
lane: P,
|
||||
source_client: Client<P::SourceChain>,
|
||||
target_client: Client<P::TargetChain>,
|
||||
lane_id: LaneId,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::SourceTransactionSignScheme>>,
|
||||
target_to_source_headers_relay: Option<OnDemandHeadersRelay<P::TargetChain>>,
|
||||
) -> Self {
|
||||
SubstrateMessagesSource { client, lane, lane_id, target_to_source_headers_relay }
|
||||
SubstrateMessagesSource {
|
||||
source_client,
|
||||
target_client,
|
||||
lane_id,
|
||||
transaction_params,
|
||||
target_to_source_headers_relay,
|
||||
}
|
||||
}
|
||||
|
||||
/// Read outbound lane state from the on-chain storage at given block.
|
||||
async fn outbound_lane_data(
|
||||
&self,
|
||||
id: SourceHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
) -> Result<Option<OutboundLaneData>, SubstrateError> {
|
||||
self.source_client
|
||||
.storage_value(
|
||||
outbound_lane_data_key(
|
||||
P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&self.lane_id,
|
||||
),
|
||||
Some(id.1),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Ensure that the messages pallet at source chain is active.
|
||||
async fn ensure_pallet_active(&self) -> Result<(), SubstrateError> {
|
||||
ensure_messages_pallet_active::<P::SourceChain, P::TargetChain>(&self.source_client).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: SubstrateMessageLane> Clone for SubstrateMessagesSource<P> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
client: self.client.clone(),
|
||||
lane: self.lane.clone(),
|
||||
source_client: self.source_client.clone(),
|
||||
target_client: self.target_client.clone(),
|
||||
lane_id: self.lane_id,
|
||||
transaction_params: self.transaction_params.clone(),
|
||||
target_to_source_headers_relay: self.target_to_source_headers_relay.clone(),
|
||||
}
|
||||
}
|
||||
@@ -91,96 +126,71 @@ impl<P: SubstrateMessageLane> RelayClient for SubstrateMessagesSource<P> {
|
||||
type Error = SubstrateError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
|
||||
self.client.reconnect().await
|
||||
self.source_client.reconnect().await?;
|
||||
self.target_client.reconnect().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P> SourceClient<P::MessageLane> for SubstrateMessagesSource<P>
|
||||
impl<P: SubstrateMessageLane> SourceClient<MessageLaneAdapter<P>> for SubstrateMessagesSource<P>
|
||||
where
|
||||
P: SubstrateMessageLane,
|
||||
P::SourceChain: Chain<
|
||||
Hash = <P::MessageLane as MessageLane>::SourceHeaderHash,
|
||||
BlockNumber = <P::MessageLane as MessageLane>::SourceHeaderNumber,
|
||||
Balance = <P::MessageLane as MessageLane>::SourceChainBalance,
|
||||
>,
|
||||
BalanceOf<P::SourceChain>: Decode + Bounded,
|
||||
IndexOf<P::SourceChain>: DeserializeOwned,
|
||||
HashOf<P::SourceChain>: Copy,
|
||||
BlockNumberOf<P::SourceChain>: BlockNumberBase + Copy,
|
||||
HeaderOf<P::SourceChain>: DeserializeOwned,
|
||||
P::TargetChain: Chain<
|
||||
Hash = <P::MessageLane as MessageLane>::TargetHeaderHash,
|
||||
BlockNumber = <P::MessageLane as MessageLane>::TargetHeaderNumber,
|
||||
>,
|
||||
|
||||
P::MessageLane: MessageLane<
|
||||
MessagesProof = SubstrateMessagesProof<P::SourceChain>,
|
||||
MessagesReceivingProof = SubstrateMessagesReceivingProof<P::TargetChain>,
|
||||
>,
|
||||
<P::MessageLane as MessageLane>::TargetHeaderNumber: Decode,
|
||||
<P::MessageLane as MessageLane>::TargetHeaderHash: Decode,
|
||||
<P::MessageLane as MessageLane>::SourceChainBalance: AtLeast32BitUnsigned,
|
||||
AccountIdOf<P::SourceChain>:
|
||||
From<<AccountKeyPairOf<P::SourceTransactionSignScheme> as Pair>::Public>,
|
||||
P::SourceTransactionSignScheme: TransactionSignScheme<Chain = P::SourceChain>,
|
||||
{
|
||||
async fn state(&self) -> Result<SourceClientState<P::MessageLane>, SubstrateError> {
|
||||
async fn state(&self) -> Result<SourceClientState<MessageLaneAdapter<P>>, SubstrateError> {
|
||||
// we can't continue to deliver confirmations if source node is out of sync, because
|
||||
// it may have already received confirmations that we're going to deliver
|
||||
self.client.ensure_synced().await?;
|
||||
self.source_client.ensure_synced().await?;
|
||||
// we can't relay confirmations if messages pallet at source chain is halted
|
||||
self.ensure_pallet_active().await?;
|
||||
|
||||
read_client_state::<
|
||||
_,
|
||||
<P::MessageLane as MessageLane>::TargetHeaderHash,
|
||||
<P::MessageLane as MessageLane>::TargetHeaderNumber,
|
||||
>(&self.client, P::BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE)
|
||||
read_client_state(
|
||||
&self.source_client,
|
||||
Some(&self.target_client),
|
||||
P::TargetChain::BEST_FINALIZED_HEADER_ID_METHOD,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn latest_generated_nonce(
|
||||
&self,
|
||||
id: SourceHeaderIdOf<P::MessageLane>,
|
||||
) -> Result<(SourceHeaderIdOf<P::MessageLane>, MessageNonce), SubstrateError> {
|
||||
let encoded_response = self
|
||||
.client
|
||||
.state_call(
|
||||
P::OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD.into(),
|
||||
Bytes(self.lane_id.encode()),
|
||||
Some(id.1),
|
||||
)
|
||||
.await?;
|
||||
let latest_generated_nonce: MessageNonce = Decode::decode(&mut &encoded_response.0[..])
|
||||
.map_err(SubstrateError::ResponseParseFailed)?;
|
||||
id: SourceHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
) -> Result<(SourceHeaderIdOf<MessageLaneAdapter<P>>, MessageNonce), SubstrateError> {
|
||||
// lane data missing from the storage is fine until first message is sent
|
||||
let latest_generated_nonce = self
|
||||
.outbound_lane_data(id)
|
||||
.await?
|
||||
.map(|data| data.latest_generated_nonce)
|
||||
.unwrap_or(0);
|
||||
Ok((id, latest_generated_nonce))
|
||||
}
|
||||
|
||||
async fn latest_confirmed_received_nonce(
|
||||
&self,
|
||||
id: SourceHeaderIdOf<P::MessageLane>,
|
||||
) -> Result<(SourceHeaderIdOf<P::MessageLane>, MessageNonce), SubstrateError> {
|
||||
let encoded_response = self
|
||||
.client
|
||||
.state_call(
|
||||
P::OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD.into(),
|
||||
Bytes(self.lane_id.encode()),
|
||||
Some(id.1),
|
||||
)
|
||||
.await?;
|
||||
let latest_received_nonce: MessageNonce = Decode::decode(&mut &encoded_response.0[..])
|
||||
.map_err(SubstrateError::ResponseParseFailed)?;
|
||||
id: SourceHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
) -> Result<(SourceHeaderIdOf<MessageLaneAdapter<P>>, MessageNonce), SubstrateError> {
|
||||
// lane data missing from the storage is fine until first message is sent
|
||||
let latest_received_nonce = self
|
||||
.outbound_lane_data(id)
|
||||
.await?
|
||||
.map(|data| data.latest_received_nonce)
|
||||
.unwrap_or(0);
|
||||
Ok((id, latest_received_nonce))
|
||||
}
|
||||
|
||||
async fn generated_message_details(
|
||||
&self,
|
||||
id: SourceHeaderIdOf<P::MessageLane>,
|
||||
id: SourceHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
) -> Result<
|
||||
MessageDetailsMap<<P::MessageLane as MessageLane>::SourceChainBalance>,
|
||||
MessageDetailsMap<<MessageLaneAdapter<P> as MessageLane>::SourceChainBalance>,
|
||||
SubstrateError,
|
||||
> {
|
||||
let encoded_response = self
|
||||
.client
|
||||
.source_client
|
||||
.state_call(
|
||||
P::OUTBOUND_LANE_MESSAGE_DETAILS_METHOD.into(),
|
||||
P::TargetChain::TO_CHAIN_MESSAGE_DETAILS_METHOD.into(),
|
||||
Bytes((self.lane_id, nonces.start(), nonces.end()).encode()),
|
||||
Some(id.1),
|
||||
)
|
||||
@@ -195,14 +205,14 @@ where
|
||||
|
||||
async fn prove_messages(
|
||||
&self,
|
||||
id: SourceHeaderIdOf<P::MessageLane>,
|
||||
id: SourceHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
proof_parameters: MessageProofParameters,
|
||||
) -> Result<
|
||||
(
|
||||
SourceHeaderIdOf<P::MessageLane>,
|
||||
SourceHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
RangeInclusive<MessageNonce>,
|
||||
<P::MessageLane as MessageLane>::MessagesProof,
|
||||
<MessageLaneAdapter<P> as MessageLane>::MessagesProof,
|
||||
),
|
||||
SubstrateError,
|
||||
> {
|
||||
@@ -210,8 +220,8 @@ where
|
||||
Vec::with_capacity(nonces.end().saturating_sub(*nonces.start()) as usize + 1);
|
||||
let mut message_nonce = *nonces.start();
|
||||
while message_nonce <= *nonces.end() {
|
||||
let message_key = pallet_bridge_messages::storage_keys::message_key(
|
||||
P::MESSAGE_PALLET_NAME_AT_SOURCE,
|
||||
let message_key = bp_messages::storage_keys::message_key(
|
||||
P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&self.lane_id,
|
||||
message_nonce,
|
||||
);
|
||||
@@ -219,13 +229,18 @@ where
|
||||
message_nonce += 1;
|
||||
}
|
||||
if proof_parameters.outbound_state_proof_required {
|
||||
storage_keys.push(pallet_bridge_messages::storage_keys::outbound_lane_data_key(
|
||||
P::MESSAGE_PALLET_NAME_AT_SOURCE,
|
||||
storage_keys.push(bp_messages::storage_keys::outbound_lane_data_key(
|
||||
P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&self.lane_id,
|
||||
));
|
||||
}
|
||||
|
||||
let proof = self.client.prove_storage(storage_keys, id.1).await?.iter_nodes().collect();
|
||||
let proof = self
|
||||
.source_client
|
||||
.prove_storage(storage_keys, id.1)
|
||||
.await?
|
||||
.iter_nodes()
|
||||
.collect();
|
||||
let proof = FromBridgedChainMessagesProof {
|
||||
bridged_header_hash: id.1,
|
||||
storage_proof: proof,
|
||||
@@ -238,19 +253,26 @@ where
|
||||
|
||||
async fn submit_messages_receiving_proof(
|
||||
&self,
|
||||
generated_at_block: TargetHeaderIdOf<P::MessageLane>,
|
||||
proof: <P::MessageLane as MessageLane>::MessagesReceivingProof,
|
||||
_generated_at_block: TargetHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
proof: <MessageLaneAdapter<P> as MessageLane>::MessagesReceivingProof,
|
||||
) -> Result<(), SubstrateError> {
|
||||
let lane = self.lane.clone();
|
||||
self.client
|
||||
let genesis_hash = *self.source_client.genesis_hash();
|
||||
let transaction_params = self.transaction_params.clone();
|
||||
let (spec_version, transaction_version) =
|
||||
self.source_client.simple_runtime_version().await?;
|
||||
self.source_client
|
||||
.submit_signed_extrinsic(
|
||||
self.lane.source_transactions_author(),
|
||||
self.transaction_params.signer.public().into(),
|
||||
move |best_block_id, transaction_nonce| {
|
||||
lane.make_messages_receiving_proof_transaction(
|
||||
make_messages_delivery_proof_transaction::<P>(
|
||||
spec_version,
|
||||
transaction_version,
|
||||
&genesis_hash,
|
||||
&transaction_params,
|
||||
best_block_id,
|
||||
transaction_nonce,
|
||||
generated_at_block,
|
||||
proof,
|
||||
true,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -258,7 +280,7 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn require_target_header_on_source(&self, id: TargetHeaderIdOf<P::MessageLane>) {
|
||||
async fn require_target_header_on_source(&self, id: TargetHeaderIdOf<MessageLaneAdapter<P>>) {
|
||||
if let Some(ref target_to_source_headers_relay) = self.target_to_source_headers_relay {
|
||||
target_to_source_headers_relay.require_finalized_header(id).await;
|
||||
}
|
||||
@@ -266,26 +288,89 @@ where
|
||||
|
||||
async fn estimate_confirmation_transaction(
|
||||
&self,
|
||||
) -> <P::MessageLane as MessageLane>::SourceChainBalance {
|
||||
self.client
|
||||
.estimate_extrinsic_fee(self.lane.make_messages_receiving_proof_transaction(
|
||||
) -> <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 = make_messages_delivery_proof_transaction::<P>(
|
||||
runtime_version.spec_version,
|
||||
runtime_version.transaction_version,
|
||||
self.source_client.genesis_hash(),
|
||||
&self.transaction_params,
|
||||
HeaderId(Default::default(), Default::default()),
|
||||
Zero::zero(),
|
||||
HeaderId(Default::default(), Default::default()),
|
||||
prepare_dummy_messages_delivery_proof::<P::SourceChain, P::TargetChain>(),
|
||||
))
|
||||
.await
|
||||
.map(|fee| fee.inclusion_fee())
|
||||
.unwrap_or_else(|_| BalanceOf::<P::SourceChain>::max_value())
|
||||
false,
|
||||
)?;
|
||||
self.source_client
|
||||
.estimate_extrinsic_fee(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.
|
||||
pub(crate) async fn ensure_messages_pallet_active<AtChain, WithChain>(
|
||||
client: &Client<AtChain>,
|
||||
) -> Result<(), SubstrateError>
|
||||
where
|
||||
AtChain: ChainWithMessages,
|
||||
WithChain: ChainWithMessages,
|
||||
{
|
||||
let operating_mode = client
|
||||
.storage_value(operating_mode_key(WithChain::WITH_CHAIN_MESSAGES_PALLET_NAME), None)
|
||||
.await?;
|
||||
let is_halted = operating_mode == Some(OperatingMode::Halted);
|
||||
if is_halted {
|
||||
Err(SubstrateError::BridgePalletIsHalted)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Make messages delivery proof transaction from given proof.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn make_messages_delivery_proof_transaction<P: SubstrateMessageLane>(
|
||||
spec_version: u32,
|
||||
transaction_version: u32,
|
||||
source_genesis_hash: &HashOf<P::SourceChain>,
|
||||
source_transaction_params: &TransactionParams<AccountKeyPairOf<P::SourceTransactionSignScheme>>,
|
||||
source_best_block_id: HeaderIdOf<P::SourceChain>,
|
||||
transaction_nonce: IndexOf<P::SourceChain>,
|
||||
proof: SubstrateMessagesDeliveryProof<P::TargetChain>,
|
||||
trace_call: bool,
|
||||
) -> Result<Bytes, SubstrateError>
|
||||
where
|
||||
P::SourceTransactionSignScheme: TransactionSignScheme<Chain = P::SourceChain>,
|
||||
{
|
||||
let call =
|
||||
P::ReceiveMessagesDeliveryProofCallBuilder::build_receive_messages_delivery_proof_call(
|
||||
proof, trace_call,
|
||||
);
|
||||
Ok(Bytes(
|
||||
P::SourceTransactionSignScheme::sign_transaction(SignParam {
|
||||
spec_version,
|
||||
transaction_version,
|
||||
genesis_hash: *source_genesis_hash,
|
||||
signer: source_transaction_params.signer.clone(),
|
||||
era: TransactionEra::new(source_best_block_id, source_transaction_params.mortality),
|
||||
unsigned: UnsignedTransaction::new(call.into(), transaction_nonce),
|
||||
})?
|
||||
.encode(),
|
||||
))
|
||||
}
|
||||
|
||||
/// 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>(
|
||||
) -> SubstrateMessagesReceivingProof<TC> {
|
||||
) -> SubstrateMessagesDeliveryProof<TC> {
|
||||
let single_message_confirmation_size = bp_messages::InboundLaneData::<()>::encoded_size_hint(
|
||||
SC::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE,
|
||||
1,
|
||||
@@ -312,19 +397,19 @@ fn prepare_dummy_messages_delivery_proof<SC: Chain, TC: Chain>(
|
||||
/// This function assumes that the chain that is followed by the `self_client` has
|
||||
/// bridge GRANDPA pallet deployed and it provides `best_finalized_header_id_method_name`
|
||||
/// runtime API to read the best finalized Bridged chain header.
|
||||
pub async fn read_client_state<SelfChain, BridgedHeaderHash, BridgedHeaderNumber>(
|
||||
///
|
||||
/// If `peer_client` is `None`, the value of `actual_best_finalized_peer_at_best_self` will
|
||||
/// always match the `best_finalized_peer_at_best_self`.
|
||||
pub async fn read_client_state<SelfChain, PeerChain>(
|
||||
self_client: &Client<SelfChain>,
|
||||
peer_client: Option<&Client<PeerChain>>,
|
||||
best_finalized_header_id_method_name: &str,
|
||||
) -> Result<
|
||||
ClientState<HeaderIdOf<SelfChain>, HeaderId<BridgedHeaderHash, BridgedHeaderNumber>>,
|
||||
SubstrateError,
|
||||
>
|
||||
) -> Result<ClientState<HeaderIdOf<SelfChain>, HeaderIdOf<PeerChain>>, SubstrateError>
|
||||
where
|
||||
SelfChain: Chain,
|
||||
SelfChain::Header: DeserializeOwned,
|
||||
SelfChain::Index: DeserializeOwned,
|
||||
BridgedHeaderHash: Decode,
|
||||
BridgedHeaderNumber: Decode,
|
||||
PeerChain: Chain,
|
||||
{
|
||||
// let's read our state first: we need best finalized header hash on **this** chain
|
||||
let self_best_finalized_header_hash = self_client.best_finalized_header_hash().await?;
|
||||
@@ -346,16 +431,27 @@ where
|
||||
Some(self_best_hash),
|
||||
)
|
||||
.await?;
|
||||
let decoded_best_finalized_peer_on_self: (BridgedHeaderNumber, BridgedHeaderHash) =
|
||||
let decoded_best_finalized_peer_on_self: (BlockNumberOf<PeerChain>, HashOf<PeerChain>) =
|
||||
Decode::decode(&mut &encoded_best_finalized_peer_on_self.0[..])
|
||||
.map_err(SubstrateError::ResponseParseFailed)?;
|
||||
let peer_on_self_best_finalized_id =
|
||||
HeaderId(decoded_best_finalized_peer_on_self.0, decoded_best_finalized_peer_on_self.1);
|
||||
|
||||
// read actual header, matching the `peer_on_self_best_finalized_id` from the peer chain
|
||||
let actual_peer_on_self_best_finalized_id = match peer_client {
|
||||
Some(peer_client) => {
|
||||
let actual_peer_on_self_best_finalized =
|
||||
peer_client.header_by_number(peer_on_self_best_finalized_id.0).await?;
|
||||
HeaderId(peer_on_self_best_finalized_id.0, actual_peer_on_self_best_finalized.hash())
|
||||
},
|
||||
None => peer_on_self_best_finalized_id,
|
||||
};
|
||||
|
||||
Ok(ClientState {
|
||||
best_self: self_best_id,
|
||||
best_finalized_self: self_best_finalized_id,
|
||||
best_finalized_peer_at_best_self: peer_on_self_best_finalized_id,
|
||||
actual_best_finalized_peer_at_best_self: actual_peer_on_self_best_finalized_id,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -19,18 +19,22 @@
|
||||
//! <BridgedName> chain.
|
||||
|
||||
use crate::{
|
||||
messages_lane::{StandaloneMessagesMetrics, SubstrateMessageLane},
|
||||
messages_source::{read_client_state, SubstrateMessagesProof},
|
||||
messages_lane::{MessageLaneAdapter, ReceiveMessagesProofCallBuilder, SubstrateMessageLane},
|
||||
messages_metrics::StandaloneMessagesMetrics,
|
||||
messages_source::{ensure_messages_pallet_active, read_client_state, SubstrateMessagesProof},
|
||||
on_demand_headers::OnDemandHeadersRelay,
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState};
|
||||
|
||||
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::{Decode, Encode};
|
||||
use codec::Encode;
|
||||
use frame_support::weights::{Weight, WeightToFeePolynomial};
|
||||
use messages_relay::{
|
||||
message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf},
|
||||
@@ -38,23 +42,26 @@ use messages_relay::{
|
||||
};
|
||||
use num_traits::{Bounded, Zero};
|
||||
use relay_substrate_client::{
|
||||
BalanceOf, BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf, HeaderOf, IndexOf,
|
||||
WeightToFeeOf,
|
||||
AccountIdOf, AccountKeyPairOf, BalanceOf, Chain, ChainWithMessages, Client,
|
||||
Error as SubstrateError, HashOf, HeaderIdOf, IndexOf, SignParam, TransactionEra,
|
||||
TransactionSignScheme, UnsignedTransaction, WeightToFeeOf,
|
||||
};
|
||||
use relay_utils::{relay_loop::Client as RelayClient, BlockNumberBase, HeaderId};
|
||||
use sp_core::Bytes;
|
||||
use sp_runtime::{traits::Saturating, DeserializeOwned, FixedPointNumber, FixedU128};
|
||||
use std::{convert::TryFrom, ops::RangeInclusive};
|
||||
use relay_utils::{relay_loop::Client as RelayClient, HeaderId};
|
||||
use sp_core::{Bytes, Pair};
|
||||
use sp_runtime::{traits::Saturating, FixedPointNumber, FixedU128};
|
||||
use std::{collections::VecDeque, convert::TryFrom, ops::RangeInclusive};
|
||||
|
||||
/// Message receiving proof returned by the target Substrate node.
|
||||
pub type SubstrateMessagesReceivingProof<C> =
|
||||
pub type SubstrateMessagesDeliveryProof<C> =
|
||||
(UnrewardedRelayersState, FromBridgedChainMessagesDeliveryProof<HashOf<C>>);
|
||||
|
||||
/// Substrate client as Substrate messages target.
|
||||
pub struct SubstrateMessagesTarget<P: SubstrateMessageLane> {
|
||||
client: Client<P::TargetChain>,
|
||||
lane: P,
|
||||
target_client: Client<P::TargetChain>,
|
||||
source_client: Client<P::SourceChain>,
|
||||
lane_id: LaneId,
|
||||
relayer_id_at_source: AccountIdOf<P::SourceChain>,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetTransactionSignScheme>>,
|
||||
metric_values: StandaloneMessagesMetrics<P::SourceChain, P::TargetChain>,
|
||||
source_to_target_headers_relay: Option<OnDemandHeadersRelay<P::SourceChain>>,
|
||||
}
|
||||
@@ -62,28 +69,55 @@ pub struct SubstrateMessagesTarget<P: SubstrateMessageLane> {
|
||||
impl<P: SubstrateMessageLane> SubstrateMessagesTarget<P> {
|
||||
/// Create new Substrate headers target.
|
||||
pub fn new(
|
||||
client: Client<P::TargetChain>,
|
||||
lane: P,
|
||||
target_client: Client<P::TargetChain>,
|
||||
source_client: Client<P::SourceChain>,
|
||||
lane_id: LaneId,
|
||||
relayer_id_at_source: AccountIdOf<P::SourceChain>,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetTransactionSignScheme>>,
|
||||
metric_values: StandaloneMessagesMetrics<P::SourceChain, P::TargetChain>,
|
||||
source_to_target_headers_relay: Option<OnDemandHeadersRelay<P::SourceChain>>,
|
||||
) -> Self {
|
||||
SubstrateMessagesTarget {
|
||||
client,
|
||||
lane,
|
||||
target_client,
|
||||
source_client,
|
||||
lane_id,
|
||||
relayer_id_at_source,
|
||||
transaction_params,
|
||||
metric_values,
|
||||
source_to_target_headers_relay,
|
||||
}
|
||||
}
|
||||
|
||||
/// Read inbound lane state from the on-chain storage at given block.
|
||||
async fn inbound_lane_data(
|
||||
&self,
|
||||
id: TargetHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
) -> Result<Option<InboundLaneData<AccountIdOf<P::SourceChain>>>, SubstrateError> {
|
||||
self.target_client
|
||||
.storage_value(
|
||||
inbound_lane_data_key(
|
||||
P::SourceChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&self.lane_id,
|
||||
),
|
||||
Some(id.1),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Ensure that the messages pallet at target chain is active.
|
||||
async fn ensure_pallet_active(&self) -> Result<(), SubstrateError> {
|
||||
ensure_messages_pallet_active::<P::TargetChain, P::SourceChain>(&self.target_client).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: SubstrateMessageLane> Clone for SubstrateMessagesTarget<P> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
client: self.client.clone(),
|
||||
lane: self.lane.clone(),
|
||||
target_client: self.target_client.clone(),
|
||||
source_client: self.source_client.clone(),
|
||||
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(),
|
||||
}
|
||||
@@ -95,115 +129,98 @@ impl<P: SubstrateMessageLane> RelayClient for SubstrateMessagesTarget<P> {
|
||||
type Error = SubstrateError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
|
||||
self.client.reconnect().await
|
||||
self.target_client.reconnect().await?;
|
||||
self.source_client.reconnect().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P> TargetClient<P::MessageLane> for SubstrateMessagesTarget<P>
|
||||
impl<P: SubstrateMessageLane> TargetClient<MessageLaneAdapter<P>> for SubstrateMessagesTarget<P>
|
||||
where
|
||||
P: SubstrateMessageLane,
|
||||
P::SourceChain: Chain<
|
||||
Hash = <P::MessageLane as MessageLane>::SourceHeaderHash,
|
||||
BlockNumber = <P::MessageLane as MessageLane>::SourceHeaderNumber,
|
||||
Balance = <P::MessageLane as MessageLane>::SourceChainBalance,
|
||||
>,
|
||||
BalanceOf<P::SourceChain>: TryFrom<BalanceOf<P::TargetChain>> + Bounded,
|
||||
P::TargetChain: Chain<
|
||||
Hash = <P::MessageLane as MessageLane>::TargetHeaderHash,
|
||||
BlockNumber = <P::MessageLane as MessageLane>::TargetHeaderNumber,
|
||||
>,
|
||||
IndexOf<P::TargetChain>: DeserializeOwned,
|
||||
HashOf<P::TargetChain>: Copy,
|
||||
BlockNumberOf<P::TargetChain>: Copy,
|
||||
HeaderOf<P::TargetChain>: DeserializeOwned,
|
||||
BlockNumberOf<P::TargetChain>: BlockNumberBase,
|
||||
P::MessageLane: MessageLane<
|
||||
MessagesProof = SubstrateMessagesProof<P::SourceChain>,
|
||||
MessagesReceivingProof = SubstrateMessagesReceivingProof<P::TargetChain>,
|
||||
>,
|
||||
<P::MessageLane as MessageLane>::SourceHeaderNumber: Decode,
|
||||
<P::MessageLane as MessageLane>::SourceHeaderHash: Decode,
|
||||
AccountIdOf<P::TargetChain>:
|
||||
From<<AccountKeyPairOf<P::TargetTransactionSignScheme> as Pair>::Public>,
|
||||
P::TargetTransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
|
||||
BalanceOf<P::SourceChain>: TryFrom<BalanceOf<P::TargetChain>>,
|
||||
{
|
||||
async fn state(&self) -> Result<TargetClientState<P::MessageLane>, SubstrateError> {
|
||||
async fn state(&self) -> Result<TargetClientState<MessageLaneAdapter<P>>, SubstrateError> {
|
||||
// we can't continue to deliver messages if target node is out of sync, because
|
||||
// it may have already received (some of) messages that we're going to deliver
|
||||
self.client.ensure_synced().await?;
|
||||
self.target_client.ensure_synced().await?;
|
||||
// we can't relay messages if messages pallet at target chain is halted
|
||||
self.ensure_pallet_active().await?;
|
||||
|
||||
read_client_state::<
|
||||
_,
|
||||
<P::MessageLane as MessageLane>::SourceHeaderHash,
|
||||
<P::MessageLane as MessageLane>::SourceHeaderNumber,
|
||||
>(&self.client, P::BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET)
|
||||
read_client_state(
|
||||
&self.target_client,
|
||||
Some(&self.source_client),
|
||||
P::SourceChain::BEST_FINALIZED_HEADER_ID_METHOD,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn latest_received_nonce(
|
||||
&self,
|
||||
id: TargetHeaderIdOf<P::MessageLane>,
|
||||
) -> Result<(TargetHeaderIdOf<P::MessageLane>, MessageNonce), SubstrateError> {
|
||||
let encoded_response = self
|
||||
.client
|
||||
.state_call(
|
||||
P::INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD.into(),
|
||||
Bytes(self.lane_id.encode()),
|
||||
Some(id.1),
|
||||
)
|
||||
.await?;
|
||||
let latest_received_nonce: MessageNonce = Decode::decode(&mut &encoded_response.0[..])
|
||||
.map_err(SubstrateError::ResponseParseFailed)?;
|
||||
id: TargetHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
) -> Result<(TargetHeaderIdOf<MessageLaneAdapter<P>>, MessageNonce), SubstrateError> {
|
||||
// lane data missing from the storage is fine until first message is received
|
||||
let latest_received_nonce = self
|
||||
.inbound_lane_data(id)
|
||||
.await?
|
||||
.map(|data| data.last_delivered_nonce())
|
||||
.unwrap_or(0);
|
||||
Ok((id, latest_received_nonce))
|
||||
}
|
||||
|
||||
async fn latest_confirmed_received_nonce(
|
||||
&self,
|
||||
id: TargetHeaderIdOf<P::MessageLane>,
|
||||
) -> Result<(TargetHeaderIdOf<P::MessageLane>, MessageNonce), SubstrateError> {
|
||||
let encoded_response = self
|
||||
.client
|
||||
.state_call(
|
||||
P::INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD.into(),
|
||||
Bytes(self.lane_id.encode()),
|
||||
Some(id.1),
|
||||
)
|
||||
.await?;
|
||||
let latest_received_nonce: MessageNonce = Decode::decode(&mut &encoded_response.0[..])
|
||||
.map_err(SubstrateError::ResponseParseFailed)?;
|
||||
Ok((id, latest_received_nonce))
|
||||
id: TargetHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
) -> Result<(TargetHeaderIdOf<MessageLaneAdapter<P>>, MessageNonce), SubstrateError> {
|
||||
// lane data missing from the storage is fine until first message is received
|
||||
let last_confirmed_nonce = self
|
||||
.inbound_lane_data(id)
|
||||
.await?
|
||||
.map(|data| data.last_confirmed_nonce)
|
||||
.unwrap_or(0);
|
||||
Ok((id, last_confirmed_nonce))
|
||||
}
|
||||
|
||||
async fn unrewarded_relayers_state(
|
||||
&self,
|
||||
id: TargetHeaderIdOf<P::MessageLane>,
|
||||
) -> Result<(TargetHeaderIdOf<P::MessageLane>, UnrewardedRelayersState), SubstrateError> {
|
||||
let encoded_response = self
|
||||
.client
|
||||
.state_call(
|
||||
P::INBOUND_LANE_UNREWARDED_RELAYERS_STATE.into(),
|
||||
Bytes(self.lane_id.encode()),
|
||||
Some(id.1),
|
||||
)
|
||||
.await?;
|
||||
let unrewarded_relayers_state: UnrewardedRelayersState =
|
||||
Decode::decode(&mut &encoded_response.0[..])
|
||||
.map_err(SubstrateError::ResponseParseFailed)?;
|
||||
id: TargetHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
) -> Result<(TargetHeaderIdOf<MessageLaneAdapter<P>>, UnrewardedRelayersState), SubstrateError>
|
||||
{
|
||||
let relayers = self
|
||||
.inbound_lane_data(id)
|
||||
.await?
|
||||
.map(|data| data.relayers)
|
||||
.unwrap_or_else(|| VecDeque::new());
|
||||
let unrewarded_relayers_state = bp_messages::UnrewardedRelayersState {
|
||||
unrewarded_relayer_entries: relayers.len() as _,
|
||||
messages_in_oldest_entry: relayers
|
||||
.front()
|
||||
.map(|entry| 1 + entry.messages.end - entry.messages.begin)
|
||||
.unwrap_or(0),
|
||||
total_messages: total_unrewarded_messages(&relayers).unwrap_or(MessageNonce::MAX),
|
||||
};
|
||||
Ok((id, unrewarded_relayers_state))
|
||||
}
|
||||
|
||||
async fn prove_messages_receiving(
|
||||
&self,
|
||||
id: TargetHeaderIdOf<P::MessageLane>,
|
||||
id: TargetHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
) -> Result<
|
||||
(TargetHeaderIdOf<P::MessageLane>, <P::MessageLane as MessageLane>::MessagesReceivingProof),
|
||||
(
|
||||
TargetHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
<MessageLaneAdapter<P> as MessageLane>::MessagesReceivingProof,
|
||||
),
|
||||
SubstrateError,
|
||||
> {
|
||||
let (id, relayers_state) = self.unrewarded_relayers_state(id).await?;
|
||||
let inbound_data_key = pallet_bridge_messages::storage_keys::inbound_lane_data_key(
|
||||
P::MESSAGE_PALLET_NAME_AT_TARGET,
|
||||
let inbound_data_key = bp_messages::storage_keys::inbound_lane_data_key(
|
||||
P::SourceChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&self.lane_id,
|
||||
);
|
||||
let proof = self
|
||||
.client
|
||||
.target_client
|
||||
.prove_storage(vec![inbound_data_key], id.1)
|
||||
.await?
|
||||
.iter_nodes()
|
||||
@@ -218,22 +235,31 @@ where
|
||||
|
||||
async fn submit_messages_proof(
|
||||
&self,
|
||||
generated_at_header: SourceHeaderIdOf<P::MessageLane>,
|
||||
_generated_at_header: SourceHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
proof: <P::MessageLane as MessageLane>::MessagesProof,
|
||||
proof: <MessageLaneAdapter<P> as MessageLane>::MessagesProof,
|
||||
) -> Result<RangeInclusive<MessageNonce>, SubstrateError> {
|
||||
let lane = self.lane.clone();
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction_params = self.transaction_params.clone();
|
||||
let relayer_id_at_source = self.relayer_id_at_source.clone();
|
||||
let nonces_clone = nonces.clone();
|
||||
self.client
|
||||
let (spec_version, transaction_version) =
|
||||
self.target_client.simple_runtime_version().await?;
|
||||
self.target_client
|
||||
.submit_signed_extrinsic(
|
||||
self.lane.target_transactions_author(),
|
||||
self.transaction_params.signer.public().into(),
|
||||
move |best_block_id, transaction_nonce| {
|
||||
lane.make_messages_delivery_transaction(
|
||||
make_messages_delivery_transaction::<P>(
|
||||
spec_version,
|
||||
transaction_version,
|
||||
&genesis_hash,
|
||||
&transaction_params,
|
||||
best_block_id,
|
||||
transaction_nonce,
|
||||
generated_at_header,
|
||||
relayer_id_at_source,
|
||||
nonces_clone,
|
||||
proof,
|
||||
true,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -241,7 +267,7 @@ where
|
||||
Ok(nonces)
|
||||
}
|
||||
|
||||
async fn require_source_header_on_target(&self, id: SourceHeaderIdOf<P::MessageLane>) {
|
||||
async fn require_source_header_on_target(&self, id: SourceHeaderIdOf<MessageLaneAdapter<P>>) {
|
||||
if let Some(ref source_to_target_headers_relay) = self.source_to_target_headers_relay {
|
||||
source_to_target_headers_relay.require_finalized_header(id).await;
|
||||
}
|
||||
@@ -253,7 +279,7 @@ where
|
||||
total_prepaid_nonces: MessageNonce,
|
||||
total_dispatch_weight: Weight,
|
||||
total_size: u32,
|
||||
) -> Result<<P::MessageLane as MessageLane>::SourceChainBalance, SubstrateError> {
|
||||
) -> 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!(
|
||||
@@ -263,19 +289,26 @@ where
|
||||
))
|
||||
})?;
|
||||
|
||||
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 = self.lane.make_messages_delivery_transaction(
|
||||
let delivery_tx = make_messages_delivery_transaction::<P>(
|
||||
spec_version,
|
||||
transaction_version,
|
||||
self.target_client.genesis_hash(),
|
||||
&self.transaction_params,
|
||||
HeaderId(Default::default(), Default::default()),
|
||||
Zero::zero(),
|
||||
HeaderId(Default::default(), Default::default()),
|
||||
self.relayer_id_at_source.clone(),
|
||||
nonces.clone(),
|
||||
prepare_dummy_messages_proof::<P::SourceChain>(
|
||||
nonces.clone(),
|
||||
total_dispatch_weight,
|
||||
total_size,
|
||||
),
|
||||
);
|
||||
let delivery_tx_fee = self.client.estimate_extrinsic_fee(delivery_tx).await?;
|
||||
false,
|
||||
)?;
|
||||
let delivery_tx_fee = self.target_client.estimate_extrinsic_fee(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
|
||||
@@ -297,23 +330,29 @@ where
|
||||
let expected_refund_in_target_tokens = if total_prepaid_nonces != 0 {
|
||||
const WEIGHT_DIFFERENCE: Weight = 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 larger_delivery_tx_fee = self
|
||||
.client
|
||||
.estimate_extrinsic_fee(self.lane.make_messages_delivery_transaction(
|
||||
HeaderId(Default::default(), Default::default()),
|
||||
Zero::zero(),
|
||||
HeaderId(Default::default(), Default::default()),
|
||||
let dummy_tx = make_messages_delivery_transaction::<P>(
|
||||
spec_version,
|
||||
transaction_version,
|
||||
self.target_client.genesis_hash(),
|
||||
&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(),
|
||||
prepare_dummy_messages_proof::<P::SourceChain>(
|
||||
nonces.clone(),
|
||||
larger_dispatch_weight,
|
||||
total_size,
|
||||
),
|
||||
))
|
||||
.await?;
|
||||
larger_dispatch_weight,
|
||||
total_size,
|
||||
),
|
||||
false,
|
||||
)?;
|
||||
let larger_delivery_tx_fee =
|
||||
self.target_client.estimate_extrinsic_fee(dummy_tx).await?;
|
||||
|
||||
compute_prepaid_messages_refund::<P>(
|
||||
compute_prepaid_messages_refund::<P::TargetChain>(
|
||||
total_prepaid_nonces,
|
||||
compute_fee_multiplier::<P::TargetChain>(
|
||||
delivery_tx_fee.adjusted_weight_fee,
|
||||
@@ -359,6 +398,45 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Make messages delivery transaction from given proof.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn make_messages_delivery_transaction<P: SubstrateMessageLane>(
|
||||
spec_version: u32,
|
||||
transaction_version: u32,
|
||||
target_genesis_hash: &HashOf<P::TargetChain>,
|
||||
target_transaction_params: &TransactionParams<AccountKeyPairOf<P::TargetTransactionSignScheme>>,
|
||||
target_best_block_id: HeaderIdOf<P::TargetChain>,
|
||||
transaction_nonce: IndexOf<P::TargetChain>,
|
||||
relayer_id_at_source: AccountIdOf<P::SourceChain>,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
proof: SubstrateMessagesProof<P::SourceChain>,
|
||||
trace_call: bool,
|
||||
) -> Result<Bytes, SubstrateError>
|
||||
where
|
||||
P::TargetTransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
|
||||
{
|
||||
let messages_count = nonces.end() - nonces.start() + 1;
|
||||
let dispatch_weight = proof.0;
|
||||
let call = P::ReceiveMessagesProofCallBuilder::build_receive_messages_proof_call(
|
||||
relayer_id_at_source,
|
||||
proof,
|
||||
messages_count as _,
|
||||
dispatch_weight,
|
||||
trace_call,
|
||||
);
|
||||
Ok(Bytes(
|
||||
P::TargetTransactionSignScheme::sign_transaction(SignParam {
|
||||
spec_version,
|
||||
transaction_version,
|
||||
genesis_hash: *target_genesis_hash,
|
||||
signer: target_transaction_params.signer.clone(),
|
||||
era: TransactionEra::new(target_best_block_id, target_transaction_params.mortality),
|
||||
unsigned: UnsignedTransaction::new(call.into(), transaction_nonce),
|
||||
})?
|
||||
.encode(),
|
||||
))
|
||||
}
|
||||
|
||||
/// 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
|
||||
@@ -425,80 +503,20 @@ fn compute_fee_multiplier<C: Chain>(
|
||||
|
||||
/// 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<P: SubstrateMessageLane>(
|
||||
fn compute_prepaid_messages_refund<C: ChainWithMessages>(
|
||||
total_prepaid_nonces: MessageNonce,
|
||||
fee_multiplier: FixedU128,
|
||||
) -> BalanceOf<P::TargetChain> {
|
||||
fee_multiplier.saturating_mul_int(WeightToFeeOf::<P::TargetChain>::calc(
|
||||
&P::PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN.saturating_mul(total_prepaid_nonces),
|
||||
) -> BalanceOf<C> {
|
||||
fee_multiplier.saturating_mul_int(WeightToFeeOf::<C>::calc(
|
||||
&C::PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_CHAIN.saturating_mul(total_prepaid_nonces),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use relay_rococo_client::{Rococo, SigningParams as RococoSigningParams};
|
||||
use relay_wococo_client::{SigningParams as WococoSigningParams, Wococo};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TestSubstrateMessageLane;
|
||||
|
||||
impl SubstrateMessageLane for TestSubstrateMessageLane {
|
||||
type MessageLane = crate::messages_lane::SubstrateMessageLaneToSubstrate<
|
||||
Rococo,
|
||||
RococoSigningParams,
|
||||
Wococo,
|
||||
WococoSigningParams,
|
||||
>;
|
||||
|
||||
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str = "";
|
||||
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str = "";
|
||||
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = "";
|
||||
|
||||
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = "";
|
||||
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str = "";
|
||||
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = "";
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = "";
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = "";
|
||||
|
||||
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = "";
|
||||
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = "";
|
||||
|
||||
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight = 100_000;
|
||||
|
||||
type SourceChain = Rococo;
|
||||
type TargetChain = Wococo;
|
||||
|
||||
fn source_transactions_author(&self) -> bp_rococo::AccountId {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn make_messages_receiving_proof_transaction(
|
||||
&self,
|
||||
_best_block_id: SourceHeaderIdOf<Self::MessageLane>,
|
||||
_transaction_nonce: IndexOf<Rococo>,
|
||||
_generated_at_block: TargetHeaderIdOf<Self::MessageLane>,
|
||||
_proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
|
||||
) -> Bytes {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn target_transactions_author(&self) -> bp_wococo::AccountId {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn make_messages_delivery_transaction(
|
||||
&self,
|
||||
_best_block_id: TargetHeaderIdOf<Self::MessageLane>,
|
||||
_transaction_nonce: IndexOf<Wococo>,
|
||||
_generated_at_header: SourceHeaderIdOf<Self::MessageLane>,
|
||||
_nonces: RangeInclusive<MessageNonce>,
|
||||
_proof: <Self::MessageLane as MessageLane>::MessagesProof,
|
||||
) -> Bytes {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
use relay_rococo_client::Rococo;
|
||||
use relay_wococo_client::Wococo;
|
||||
|
||||
#[test]
|
||||
fn prepare_dummy_messages_proof_works() {
|
||||
@@ -556,11 +574,10 @@ mod tests {
|
||||
#[test]
|
||||
fn compute_prepaid_messages_refund_returns_sane_results() {
|
||||
assert!(
|
||||
compute_prepaid_messages_refund::<TestSubstrateMessageLane>(
|
||||
compute_prepaid_messages_refund::<Wococo>(
|
||||
10,
|
||||
FixedU128::saturating_from_rational(110, 100),
|
||||
) > (10 * TestSubstrateMessageLane::PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN)
|
||||
.into()
|
||||
) > (10 * Wococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_CHAIN).into()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,31 +16,24 @@
|
||||
|
||||
//! On-demand Substrate -> Substrate headers relay.
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use futures::{select, FutureExt};
|
||||
use num_traits::{CheckedSub, One, Zero};
|
||||
use num_traits::{One, Zero};
|
||||
|
||||
use finality_relay::{
|
||||
FinalitySyncParams, FinalitySyncPipeline, SourceClient as FinalitySourceClient, SourceHeader,
|
||||
TargetClient as FinalityTargetClient,
|
||||
};
|
||||
use finality_relay::{FinalitySyncParams, SourceHeader, TargetClient as FinalityTargetClient};
|
||||
use relay_substrate_client::{
|
||||
finality_source::{FinalitySource as SubstrateFinalitySource, RequiredHeaderNumberRef},
|
||||
Chain, Client, HeaderIdOf, SyncHeader,
|
||||
AccountIdOf, AccountKeyPairOf, BlockNumberOf, Chain, Client, HeaderIdOf, HeaderOf, SyncHeader,
|
||||
TransactionSignScheme,
|
||||
};
|
||||
use relay_utils::{
|
||||
metrics::MetricsParams, relay_loop::Client as RelayClient, BlockNumberBase, FailedClient,
|
||||
MaybeConnectionError,
|
||||
metrics::MetricsParams, relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
finality_pipeline::{
|
||||
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate, RECENT_FINALITY_PROOFS_LIMIT,
|
||||
},
|
||||
finality_pipeline::{SubstrateFinalitySyncPipeline, RECENT_FINALITY_PROOFS_LIMIT},
|
||||
finality_source::{RequiredHeaderNumberRef, SubstrateFinalitySource},
|
||||
finality_target::SubstrateFinalityTarget,
|
||||
STALL_TIMEOUT,
|
||||
TransactionParams, STALL_TIMEOUT,
|
||||
};
|
||||
|
||||
/// On-demand Substrate <-> Substrate headers relay.
|
||||
@@ -58,41 +51,27 @@ pub struct OnDemandHeadersRelay<SourceChain: Chain> {
|
||||
|
||||
impl<SourceChain: Chain> OnDemandHeadersRelay<SourceChain> {
|
||||
/// Create new on-demand headers relay.
|
||||
pub fn new<TargetChain: Chain, TargetSign, P>(
|
||||
source_client: Client<SourceChain>,
|
||||
target_client: Client<TargetChain>,
|
||||
target_transactions_mortality: Option<u32>,
|
||||
pipeline: P,
|
||||
maximal_headers_difference: SourceChain::BlockNumber,
|
||||
pub fn new<P: SubstrateFinalitySyncPipeline<SourceChain = SourceChain>>(
|
||||
source_client: Client<P::SourceChain>,
|
||||
target_client: Client<P::TargetChain>,
|
||||
target_transaction_params: TransactionParams<AccountKeyPairOf<P::TransactionSignScheme>>,
|
||||
only_mandatory_headers: bool,
|
||||
) -> Self
|
||||
where
|
||||
SourceChain: Chain + Debug,
|
||||
SourceChain::BlockNumber: BlockNumberBase,
|
||||
TargetChain: Chain + Debug,
|
||||
TargetChain::BlockNumber: BlockNumberBase,
|
||||
TargetSign: Clone + Send + Sync + 'static,
|
||||
P: SubstrateFinalitySyncPipeline<
|
||||
FinalitySyncPipeline = SubstrateFinalityToSubstrate<
|
||||
SourceChain,
|
||||
TargetChain,
|
||||
TargetSign,
|
||||
>,
|
||||
TargetChain = TargetChain,
|
||||
>,
|
||||
AccountIdOf<P::TargetChain>:
|
||||
From<<AccountKeyPairOf<P::TransactionSignScheme> as sp_core::Pair>::Public>,
|
||||
P::TransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
|
||||
{
|
||||
let required_header_number = Arc::new(Mutex::new(Zero::zero()));
|
||||
let this = OnDemandHeadersRelay {
|
||||
relay_task_name: on_demand_headers_relay_name::<SourceChain, TargetChain>(),
|
||||
relay_task_name: on_demand_headers_relay_name::<P::SourceChain, P::TargetChain>(),
|
||||
required_header_number: required_header_number.clone(),
|
||||
};
|
||||
async_std::task::spawn(async move {
|
||||
background_task(
|
||||
background_task::<P>(
|
||||
source_client,
|
||||
target_client,
|
||||
target_transactions_mortality,
|
||||
pipeline,
|
||||
maximal_headers_difference,
|
||||
target_transaction_params,
|
||||
only_mandatory_headers,
|
||||
required_header_number,
|
||||
)
|
||||
@@ -120,35 +99,25 @@ impl<SourceChain: Chain> OnDemandHeadersRelay<SourceChain> {
|
||||
}
|
||||
|
||||
/// Background task that is responsible for starting headers relay.
|
||||
async fn background_task<SourceChain, TargetChain, TargetSign, P>(
|
||||
source_client: Client<SourceChain>,
|
||||
target_client: Client<TargetChain>,
|
||||
target_transactions_mortality: Option<u32>,
|
||||
pipeline: P,
|
||||
maximal_headers_difference: SourceChain::BlockNumber,
|
||||
async fn background_task<P: SubstrateFinalitySyncPipeline>(
|
||||
source_client: Client<P::SourceChain>,
|
||||
target_client: Client<P::TargetChain>,
|
||||
target_transaction_params: TransactionParams<AccountKeyPairOf<P::TransactionSignScheme>>,
|
||||
only_mandatory_headers: bool,
|
||||
required_header_number: RequiredHeaderNumberRef<SourceChain>,
|
||||
required_header_number: RequiredHeaderNumberRef<P::SourceChain>,
|
||||
) where
|
||||
SourceChain: Chain + Debug,
|
||||
SourceChain::BlockNumber: BlockNumberBase,
|
||||
TargetChain: Chain + Debug,
|
||||
TargetChain::BlockNumber: BlockNumberBase,
|
||||
TargetSign: Clone + Send + Sync + 'static,
|
||||
P: SubstrateFinalitySyncPipeline<
|
||||
FinalitySyncPipeline = SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>,
|
||||
TargetChain = TargetChain,
|
||||
>,
|
||||
AccountIdOf<P::TargetChain>:
|
||||
From<<AccountKeyPairOf<P::TransactionSignScheme> as sp_core::Pair>::Public>,
|
||||
P::TransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
|
||||
{
|
||||
let relay_task_name = on_demand_headers_relay_name::<SourceChain, TargetChain>();
|
||||
let mut finality_source = SubstrateFinalitySource::<
|
||||
_,
|
||||
SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>,
|
||||
>::new(source_client.clone(), Some(required_header_number.clone()));
|
||||
let mut finality_target = SubstrateFinalityTarget::new(
|
||||
target_client.clone(),
|
||||
pipeline.clone(),
|
||||
target_transactions_mortality,
|
||||
let relay_task_name = on_demand_headers_relay_name::<P::SourceChain, P::TargetChain>();
|
||||
let target_transactions_mortality = target_transaction_params.mortality;
|
||||
let mut finality_source = SubstrateFinalitySource::<P>::new(
|
||||
source_client.clone(),
|
||||
Some(required_header_number.clone()),
|
||||
);
|
||||
let mut finality_target =
|
||||
SubstrateFinalityTarget::new(target_client.clone(), target_transaction_params);
|
||||
let mut latest_non_mandatory_at_source = Zero::zero();
|
||||
|
||||
let mut restart_relay = true;
|
||||
@@ -157,7 +126,7 @@ async fn background_task<SourceChain, TargetChain, TargetSign, P>(
|
||||
|
||||
loop {
|
||||
select! {
|
||||
_ = async_std::task::sleep(TargetChain::AVERAGE_BLOCK_INTERVAL).fuse() => {},
|
||||
_ = async_std::task::sleep(P::TargetChain::AVERAGE_BLOCK_INTERVAL).fuse() => {},
|
||||
_ = finality_relay_task => {
|
||||
// this should never happen in practice given the current code
|
||||
restart_relay = true;
|
||||
@@ -179,12 +148,8 @@ async fn background_task<SourceChain, TargetChain, TargetSign, P>(
|
||||
}
|
||||
|
||||
// read best finalized source header number from target
|
||||
let best_finalized_source_header_at_target = best_finalized_source_header_at_target::<
|
||||
SourceChain,
|
||||
_,
|
||||
_,
|
||||
>(&finality_target, &relay_task_name)
|
||||
.await;
|
||||
let best_finalized_source_header_at_target =
|
||||
best_finalized_source_header_at_target::<P>(&finality_target, &relay_task_name).await;
|
||||
if matches!(best_finalized_source_header_at_target, Err(ref e) if e.is_connection_error()) {
|
||||
relay_utils::relay_loop::reconnect_failed_client(
|
||||
FailedClient::Target,
|
||||
@@ -197,15 +162,28 @@ async fn background_task<SourceChain, TargetChain, TargetSign, P>(
|
||||
}
|
||||
|
||||
// submit mandatory header if some headers are missing
|
||||
let best_finalized_source_header_at_source_fmt =
|
||||
format!("{:?}", best_finalized_source_header_at_source);
|
||||
let best_finalized_source_header_at_target_fmt =
|
||||
format!("{:?}", best_finalized_source_header_at_target);
|
||||
let mandatory_scan_range = mandatory_headers_scan_range::<SourceChain>(
|
||||
let required_header_number_value = *required_header_number.lock().await;
|
||||
let mandatory_scan_range = mandatory_headers_scan_range::<P::SourceChain>(
|
||||
best_finalized_source_header_at_source.ok(),
|
||||
best_finalized_source_header_at_target.ok(),
|
||||
maximal_headers_difference,
|
||||
&required_header_number,
|
||||
required_header_number_value,
|
||||
)
|
||||
.await;
|
||||
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Mandatory headers scan range in {}: ({:?}, {:?}, {:?}) -> {:?}",
|
||||
relay_task_name,
|
||||
required_header_number_value,
|
||||
best_finalized_source_header_at_source_fmt,
|
||||
best_finalized_source_header_at_target_fmt,
|
||||
mandatory_scan_range,
|
||||
);
|
||||
|
||||
if let Some(mandatory_scan_range) = mandatory_scan_range {
|
||||
let relay_mandatory_header_result = relay_mandatory_header_from_range(
|
||||
&finality_source,
|
||||
@@ -224,8 +202,25 @@ async fn background_task<SourceChain, TargetChain, TargetSign, P>(
|
||||
// there are no (or we don't need to relay them) mandatory headers in the range
|
||||
// => to avoid scanning the same headers over and over again, remember that
|
||||
latest_non_mandatory_at_source = mandatory_scan_range.1;
|
||||
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"No mandatory {} headers in the range {:?} of {} relay",
|
||||
P::SourceChain::NAME,
|
||||
mandatory_scan_range,
|
||||
relay_task_name,
|
||||
);
|
||||
},
|
||||
Err(e) =>
|
||||
Err(e) => {
|
||||
log::warn!(
|
||||
target: "bridge",
|
||||
"Failed to scan mandatory {} headers range in {} relay (range: {:?}): {:?}",
|
||||
P::SourceChain::NAME,
|
||||
relay_task_name,
|
||||
mandatory_scan_range,
|
||||
e,
|
||||
);
|
||||
|
||||
if e.is_connection_error() {
|
||||
relay_utils::relay_loop::reconnect_failed_client(
|
||||
FailedClient::Source,
|
||||
@@ -235,23 +230,43 @@ async fn background_task<SourceChain, TargetChain, TargetSign, P>(
|
||||
)
|
||||
.await;
|
||||
continue
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// start/restart relay
|
||||
if restart_relay {
|
||||
let stall_timeout = relay_substrate_client::transaction_stall_timeout(
|
||||
target_transactions_mortality,
|
||||
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
STALL_TIMEOUT,
|
||||
);
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Starting {} relay\n\t\
|
||||
Only mandatory headers: {}\n\t\
|
||||
Tx mortality: {:?} (~{}m)\n\t\
|
||||
Stall timeout: {:?}",
|
||||
relay_task_name,
|
||||
only_mandatory_headers,
|
||||
target_transactions_mortality,
|
||||
stall_timeout.as_secs_f64() / 60.0f64,
|
||||
stall_timeout,
|
||||
);
|
||||
|
||||
finality_relay_task.set(
|
||||
finality_relay::run(
|
||||
finality_source.clone(),
|
||||
finality_target.clone(),
|
||||
FinalitySyncParams {
|
||||
tick: std::cmp::max(
|
||||
SourceChain::AVERAGE_BLOCK_INTERVAL,
|
||||
TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
P::SourceChain::AVERAGE_BLOCK_INTERVAL,
|
||||
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
),
|
||||
recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT,
|
||||
stall_timeout: STALL_TIMEOUT,
|
||||
stall_timeout,
|
||||
only_mandatory_headers,
|
||||
},
|
||||
MetricsParams::disabled(),
|
||||
@@ -270,11 +285,8 @@ async fn background_task<SourceChain, TargetChain, TargetSign, P>(
|
||||
async fn mandatory_headers_scan_range<C: Chain>(
|
||||
best_finalized_source_header_at_source: Option<C::BlockNumber>,
|
||||
best_finalized_source_header_at_target: Option<C::BlockNumber>,
|
||||
maximal_headers_difference: C::BlockNumber,
|
||||
required_header_number: &RequiredHeaderNumberRef<C>,
|
||||
required_header_number: BlockNumberOf<C>,
|
||||
) -> Option<(C::BlockNumber, C::BlockNumber)> {
|
||||
let required_header_number = *required_header_number.lock().await;
|
||||
|
||||
// if we have been unable to read header number from the target, then let's assume
|
||||
// that it is the same as required header number. Otherwise we risk submitting
|
||||
// unneeded transactions
|
||||
@@ -286,23 +298,8 @@ async fn mandatory_headers_scan_range<C: Chain>(
|
||||
let best_finalized_source_header_at_source =
|
||||
best_finalized_source_header_at_source.unwrap_or(best_finalized_source_header_at_target);
|
||||
|
||||
// if there are too many source headers missing from the target node, sync mandatory
|
||||
// headers to target
|
||||
//
|
||||
// why do we need that? When complex headers+messages relay is used, it'll normally only relay
|
||||
// headers when there are undelivered messages/confirmations. But security model of the
|
||||
// `pallet-bridge-grandpa` module relies on the fact that headers are synced in real-time and
|
||||
// that it'll see authorities-change header before unbonding period will end for previous
|
||||
// authorities set.
|
||||
let current_headers_difference = best_finalized_source_header_at_source
|
||||
.checked_sub(&best_finalized_source_header_at_target)
|
||||
.unwrap_or_else(Zero::zero);
|
||||
if current_headers_difference <= maximal_headers_difference {
|
||||
return None
|
||||
}
|
||||
|
||||
// if relay is already asked to sync headers, don't do anything yet
|
||||
if required_header_number > best_finalized_source_header_at_target {
|
||||
// if relay is already asked to sync more headers than we have at source, don't do anything yet
|
||||
if required_header_number >= best_finalized_source_header_at_source {
|
||||
return None
|
||||
}
|
||||
|
||||
@@ -316,17 +313,13 @@ async fn mandatory_headers_scan_range<C: Chain>(
|
||||
/// it.
|
||||
///
|
||||
/// Returns `true` if header was found and (asked to be) relayed and `false` otherwise.
|
||||
async fn relay_mandatory_header_from_range<SourceChain: Chain, P>(
|
||||
finality_source: &SubstrateFinalitySource<SourceChain, P>,
|
||||
required_header_number: &RequiredHeaderNumberRef<SourceChain>,
|
||||
async fn relay_mandatory_header_from_range<P: SubstrateFinalitySyncPipeline>(
|
||||
finality_source: &SubstrateFinalitySource<P>,
|
||||
required_header_number: &RequiredHeaderNumberRef<P::SourceChain>,
|
||||
best_finalized_source_header_at_target: String,
|
||||
range: (SourceChain::BlockNumber, SourceChain::BlockNumber),
|
||||
range: (BlockNumberOf<P::SourceChain>, BlockNumberOf<P::SourceChain>),
|
||||
relay_task_name: &str,
|
||||
) -> Result<bool, relay_substrate_client::Error>
|
||||
where
|
||||
SubstrateFinalitySource<SourceChain, P>: FinalitySourceClient<P>,
|
||||
P: FinalitySyncPipeline<Number = SourceChain::BlockNumber>,
|
||||
{
|
||||
) -> Result<bool, relay_substrate_client::Error> {
|
||||
// search for mandatory header first
|
||||
let mandatory_source_header_number =
|
||||
find_mandatory_header_in_range(finality_source, range).await?;
|
||||
@@ -347,7 +340,7 @@ where
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Too many {} headers missing at target in {} relay ({} vs {}). Going to sync up to the mandatory {}",
|
||||
SourceChain::NAME,
|
||||
P::SourceChain::NAME,
|
||||
relay_task_name,
|
||||
best_finalized_source_header_at_target,
|
||||
range.1,
|
||||
@@ -361,14 +354,10 @@ where
|
||||
/// Read best finalized source block number from source client.
|
||||
///
|
||||
/// Returns `None` if we have failed to read the number.
|
||||
async fn best_finalized_source_header_at_source<SourceChain: Chain, P>(
|
||||
finality_source: &SubstrateFinalitySource<SourceChain, P>,
|
||||
async fn best_finalized_source_header_at_source<P: SubstrateFinalitySyncPipeline>(
|
||||
finality_source: &SubstrateFinalitySource<P>,
|
||||
relay_task_name: &str,
|
||||
) -> Result<SourceChain::BlockNumber, relay_substrate_client::Error>
|
||||
where
|
||||
SubstrateFinalitySource<SourceChain, P>: FinalitySourceClient<P>,
|
||||
P: FinalitySyncPipeline<Number = SourceChain::BlockNumber>,
|
||||
{
|
||||
) -> Result<BlockNumberOf<P::SourceChain>, relay_substrate_client::Error> {
|
||||
finality_source.on_chain_best_finalized_block_number().await.map_err(|error| {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
@@ -384,41 +373,41 @@ where
|
||||
/// Read best finalized source block number from target client.
|
||||
///
|
||||
/// Returns `None` if we have failed to read the number.
|
||||
async fn best_finalized_source_header_at_target<SourceChain: Chain, TargetChain: Chain, P>(
|
||||
finality_target: &SubstrateFinalityTarget<TargetChain, P>,
|
||||
async fn best_finalized_source_header_at_target<P: SubstrateFinalitySyncPipeline>(
|
||||
finality_target: &SubstrateFinalityTarget<P>,
|
||||
relay_task_name: &str,
|
||||
) -> Result<SourceChain::BlockNumber, <SubstrateFinalityTarget<TargetChain, P> as RelayClient>::Error>
|
||||
) -> Result<BlockNumberOf<P::SourceChain>, <SubstrateFinalityTarget<P> as RelayClient>::Error>
|
||||
where
|
||||
SubstrateFinalityTarget<TargetChain, P>: FinalityTargetClient<P::FinalitySyncPipeline>,
|
||||
P: SubstrateFinalitySyncPipeline,
|
||||
P::FinalitySyncPipeline: FinalitySyncPipeline<Number = SourceChain::BlockNumber>,
|
||||
AccountIdOf<P::TargetChain>:
|
||||
From<<AccountKeyPairOf<P::TransactionSignScheme> as sp_core::Pair>::Public>,
|
||||
P::TransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
|
||||
{
|
||||
finality_target.best_finalized_source_block_number().await.map_err(|error| {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
"Failed to read best finalized source header from target in {} relay: {:?}",
|
||||
relay_task_name,
|
||||
error,
|
||||
);
|
||||
finality_target
|
||||
.best_finalized_source_block_id()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
log::error!(
|
||||
target: "bridge",
|
||||
"Failed to read best finalized source header from target in {} relay: {:?}",
|
||||
relay_task_name,
|
||||
error,
|
||||
);
|
||||
|
||||
error
|
||||
})
|
||||
error
|
||||
})
|
||||
.map(|id| id.0)
|
||||
}
|
||||
|
||||
/// Read first mandatory header in given inclusive range.
|
||||
///
|
||||
/// Returns `Ok(None)` if there were no mandatory headers in the range.
|
||||
async fn find_mandatory_header_in_range<SourceChain: Chain, P>(
|
||||
finality_source: &SubstrateFinalitySource<SourceChain, P>,
|
||||
range: (SourceChain::BlockNumber, SourceChain::BlockNumber),
|
||||
) -> Result<Option<SourceChain::BlockNumber>, relay_substrate_client::Error>
|
||||
where
|
||||
SubstrateFinalitySource<SourceChain, P>: FinalitySourceClient<P>,
|
||||
P: FinalitySyncPipeline<Number = SourceChain::BlockNumber>,
|
||||
{
|
||||
async fn find_mandatory_header_in_range<P: SubstrateFinalitySyncPipeline>(
|
||||
finality_source: &SubstrateFinalitySource<P>,
|
||||
range: (BlockNumberOf<P::SourceChain>, BlockNumberOf<P::SourceChain>),
|
||||
) -> Result<Option<BlockNumberOf<P::SourceChain>>, relay_substrate_client::Error> {
|
||||
let mut current = range.0;
|
||||
while current <= range.1 {
|
||||
let header: SyncHeader<SourceChain::Header> =
|
||||
let header: SyncHeader<HeaderOf<P::SourceChain>> =
|
||||
finality_source.client().header_by_number(current).await?.into();
|
||||
if header.is_mandatory() {
|
||||
return Ok(Some(current))
|
||||
@@ -445,29 +434,18 @@ mod tests {
|
||||
const AT_TARGET: Option<bp_rococo::BlockNumber> = Some(1);
|
||||
|
||||
#[async_std::test]
|
||||
async fn mandatory_headers_scan_range_selects_range_if_too_many_headers_are_missing() {
|
||||
async fn mandatory_headers_scan_range_selects_range_if_some_headers_are_missing() {
|
||||
assert_eq!(
|
||||
mandatory_headers_scan_range::<TestChain>(
|
||||
AT_SOURCE,
|
||||
AT_TARGET,
|
||||
5,
|
||||
&Arc::new(Mutex::new(0))
|
||||
)
|
||||
.await,
|
||||
mandatory_headers_scan_range::<TestChain>(AT_SOURCE, AT_TARGET, 0,).await,
|
||||
Some((AT_TARGET.unwrap() + 1, AT_SOURCE.unwrap())),
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn mandatory_headers_scan_range_selects_nothing_if_enough_headers_are_relayed() {
|
||||
async fn mandatory_headers_scan_range_selects_nothing_if_already_queued() {
|
||||
assert_eq!(
|
||||
mandatory_headers_scan_range::<TestChain>(
|
||||
AT_SOURCE,
|
||||
AT_TARGET,
|
||||
10,
|
||||
&Arc::new(Mutex::new(0))
|
||||
)
|
||||
.await,
|
||||
mandatory_headers_scan_range::<TestChain>(AT_SOURCE, AT_TARGET, AT_SOURCE.unwrap(),)
|
||||
.await,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user