Merge commit '392447f5c8f986ded2559a78457f4cd87942f393' into update-bridges-subtree-r/w

This commit is contained in:
antonio-dropulic
2021-12-01 09:46:14 +01:00
321 changed files with 28385 additions and 10466 deletions
@@ -8,15 +8,16 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
anyhow = "1.0"
async-std = "1.9.0"
async-trait = "0.1.42"
codec = { package = "parity-scale-codec", version = "2.0.0" }
codec = { package = "parity-scale-codec", version = "2.2.0" }
futures = "0.3.12"
hex = "0.4"
log = "0.4.14"
num-format = "0.4"
num-traits = "0.2"
paste = "1.0"
rand = "0.8"
structopt = "0.3"
strum = { version = "0.21.0", features = ["derive"] }
# Bridge dependencies
@@ -27,38 +28,52 @@ bp-message-dispatch = { path = "../../primitives/message-dispatch" }
bp-millau = { path = "../../primitives/chain-millau" }
bp-polkadot = { path = "../../primitives/chain-polkadot" }
bp-rialto = { path = "../../primitives/chain-rialto" }
bp-rialto-parachain = { path = "../../primitives/chain-rialto-parachain" }
bp-rococo = { path = "../../primitives/chain-rococo" }
bp-token-swap = { path = "../../primitives/token-swap" }
bp-wococo = { path = "../../primitives/chain-wococo" }
bp-runtime = { path = "../../primitives/runtime" }
bp-westend = { path = "../../primitives/chain-westend" }
bridge-runtime-common = { path = "../../bin/runtime-common" }
finality-grandpa = "0.14.1"
finality-relay = { path = "../finality" }
headers-relay = { path = "../headers" }
messages-relay = { path = "../messages" }
millau-runtime = { path = "../../bin/millau/runtime" }
pallet-bridge-dispatch = { path = "../../modules/dispatch" }
pallet-bridge-messages = { path = "../../modules/messages" }
pallet-bridge-token-swap = { path = "../../modules/token-swap" }
relay-kusama-client = { path = "../client-kusama" }
relay-millau-client = { path = "../client-millau" }
relay-polkadot-client = { path = "../client-polkadot" }
relay-rialto-client = { path = "../client-rialto" }
relay-rialto-parachain-client = { path = "../client-rialto-parachain" }
relay-rococo-client = { path = "../client-rococo" }
relay-wococo-client = { path = "../client-wococo" }
relay-substrate-client = { path = "../client-substrate" }
relay-utils = { path = "../utils" }
relay-westend-client = { path = "../client-westend" }
rialto-parachain-runtime = { path = "../../bin/rialto-parachain/runtime" }
rialto-runtime = { path = "../../bin/rialto/runtime" }
substrate-relay-helper = { path = "../lib-substrate-relay" }
# Substrate Dependencies
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-version = { git = "https://github.com/paritytech/substrate", branch = "master" }
# Polkadot Dependencies
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "master" }
[dev-dependencies]
hex-literal = "0.3"
pallet-bridge-grandpa = { path = "../../modules/grandpa" }
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
tempfile = "3.2"
finality-grandpa = { version = "0.14.0" }
@@ -0,0 +1,103 @@
// 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/>.
use codec::Decode;
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
use relay_kusama_client::Kusama;
use sp_version::RuntimeVersion;
use crate::cli::{
bridge,
encode_call::{Call, CliEncodeCall},
encode_message, CliChain,
};
/// Weight of the `system::remark` call at Kusama.
///
/// This weight is larger (x2) than actual weight at current Kusama runtime to avoid unsuccessful
/// calls in the future. But since it is used only in tests (and on test chains), this is ok.
pub(crate) const SYSTEM_REMARK_CALL_WEIGHT: Weight = 2 * 1_345_000;
/// Id of Kusama token that is used to fetch token price.
pub(crate) const TOKEN_ID: &str = "kusama";
impl CliEncodeCall for Kusama {
fn max_extrinsic_size() -> u32 {
bp_kusama::max_extrinsic_size()
}
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
Ok(match call {
Call::Remark { remark_payload, .. } => relay_kusama_client::runtime::Call::System(
relay_kusama_client::runtime::SystemCall::remark(
remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
),
),
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
match *bridge_instance_index {
bridge::KUSAMA_TO_POLKADOT_INDEX => {
let payload = Decode::decode(&mut &*payload.0)?;
relay_kusama_client::runtime::Call::BridgePolkadotMessages(
relay_kusama_client::runtime::BridgePolkadotMessagesCall::send_message(
lane.0, payload, fee.0,
),
)
},
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
bridge_instance_index
),
},
_ => anyhow::bail!("Unsupported Kusama call: {:?}", call),
})
}
fn get_dispatch_info(
call: &relay_kusama_client::runtime::Call,
) -> anyhow::Result<DispatchInfo> {
match *call {
relay_kusama_client::runtime::Call::System(
relay_kusama_client::runtime::SystemCall::remark(_),
) => Ok(DispatchInfo {
weight: crate::chains::kusama::SYSTEM_REMARK_CALL_WEIGHT,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
}),
_ => anyhow::bail!("Unsupported Kusama call: {:?}", call),
}
}
}
impl CliChain for Kusama {
const RUNTIME_VERSION: RuntimeVersion = bp_kusama::VERSION;
type KeyPair = sp_core::sr25519::Pair;
type MessagePayload = ();
fn ss58_format() -> u16 {
42
}
fn max_extrinsic_weight() -> Weight {
bp_kusama::max_extrinsic_weight()
}
fn encode_message(
_message: encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload> {
anyhow::bail!("Sending messages from Kusama is not yet supported.")
}
}
@@ -0,0 +1,171 @@
// 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/>.
//! Kusama-to-Polkadot headers sync entrypoint.
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_header_chain::justification::GrandpaJustification;
use relay_kusama_client::{Kusama, SyncHeader as KusamaSyncHeader};
use relay_polkadot_client::{Polkadot, SigningParams as PolkadotSigningParams};
use relay_substrate_client::{Client, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use substrate_relay_helper::finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
};
/// Maximal saturating difference between `balance(now)` and `balance(now-24h)` to treat
/// relay as gone wild.
///
/// Actual value, returned by `maximal_balance_decrease_per_day_is_sane` test is approximately 21
/// DOT, but let's round up to 30 DOT here.
pub(crate) const MAXIMAL_BALANCE_DECREASE_PER_DAY: bp_polkadot::Balance = 30_000_000_000;
/// Kusama-to-Polkadot finality sync pipeline.
pub(crate) type FinalityPipelineKusamaFinalityToPolkadot =
SubstrateFinalityToSubstrate<Kusama, Polkadot, PolkadotSigningParams>;
#[derive(Clone, Debug)]
pub(crate) struct KusamaFinalityToPolkadot {
finality_pipeline: FinalityPipelineKusamaFinalityToPolkadot,
}
impl KusamaFinalityToPolkadot {
pub fn new(target_client: Client<Polkadot>, target_sign: PolkadotSigningParams) -> Self {
Self {
finality_pipeline: FinalityPipelineKusamaFinalityToPolkadot::new(
target_client,
target_sign,
),
}
}
}
impl SubstrateFinalitySyncPipeline for KusamaFinalityToPolkadot {
type FinalitySyncPipeline = FinalityPipelineKusamaFinalityToPolkadot;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_kusama::BEST_FINALIZED_KUSAMA_HEADER_METHOD;
type TargetChain = Polkadot;
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(
Some(finality_relay::metrics_prefix::<Self::FinalitySyncPipeline>()),
params,
)
}
fn start_relay_guards(&self) {
relay_substrate_client::guard::abort_on_spec_version_change(
self.finality_pipeline.target_client.clone(),
bp_polkadot::VERSION.spec_version,
);
relay_substrate_client::guard::abort_when_account_balance_decreased(
self.finality_pipeline.target_client.clone(),
self.transactions_author(),
MAXIMAL_BALANCE_DECREASE_PER_DAY,
);
}
fn transactions_author(&self) -> bp_polkadot::AccountId {
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
}
fn make_submit_finality_proof_transaction(
&self,
era: bp_runtime::TransactionEraOf<Polkadot>,
transaction_nonce: bp_runtime::IndexOf<Polkadot>,
header: KusamaSyncHeader,
proof: GrandpaJustification<bp_kusama::Header>,
) -> Bytes {
let call = relay_polkadot_client::runtime::Call::BridgeKusamaGrandpa(
relay_polkadot_client::runtime::BridgeKusamaGrandpaCall::submit_finality_proof(
Box::new(header.into_inner()),
proof,
),
);
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
let transaction = Polkadot::sign_transaction(
genesis_hash,
&self.finality_pipeline.target_sign,
era,
UnsignedTransaction::new(call, transaction_nonce),
);
Bytes(transaction.encode())
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use frame_support::weights::WeightToFeePolynomial;
use pallet_bridge_grandpa::weights::WeightInfo;
pub fn compute_maximal_balance_decrease_per_day<B, W>(expected_source_headers_per_day: u32) -> B
where
B: From<u32> + std::ops::Mul<Output = B>,
W: WeightToFeePolynomial<Balance = B>,
{
// we assume that the GRANDPA is not lagging here => ancestry length will be near to 0
// (let's round up to 2)
const AVG_VOTES_ANCESTRIES_LEN: u32 = 2;
// let's assume number of validators is 1024 (more than on any existing well-known chain
// atm) => number of precommits is *2/3 + 1
const AVG_PRECOMMITS_LEN: u32 = 1024 * 2 / 3 + 1;
// GRANDPA pallet weights. We're now using Rialto weights everywhere.
//
// Using Rialto runtime is slightly incorrect, because `DbWeight` of other runtimes may
// differ from the `DbWeight` of Rialto runtime. But now (and most probably forever) it is
// the same.
type GrandpaPalletWeights =
pallet_bridge_grandpa::weights::RialtoWeight<rialto_runtime::Runtime>;
// The following formula shall not be treated as super-accurate - guard is to protect from
// mad relays, not to protect from over-average loses.
// increase number of headers a bit
let expected_source_headers_per_day = expected_source_headers_per_day * 110 / 100;
let single_source_header_submit_call_weight = GrandpaPalletWeights::submit_finality_proof(
AVG_VOTES_ANCESTRIES_LEN,
AVG_PRECOMMITS_LEN,
);
// for simplicity - add extra weight for base tx fee + fee that is paid for the tx size +
// adjusted fee
let single_source_header_submit_tx_weight = single_source_header_submit_call_weight * 3 / 2;
let single_source_header_tx_cost = W::calc(&single_source_header_submit_tx_weight);
single_source_header_tx_cost * B::from(expected_source_headers_per_day)
}
#[test]
fn maximal_balance_decrease_per_day_is_sane() {
// we expect Kusama -> Polkadot relay to be running in mandatory-headers-only mode
// => we expect single header for every Kusama session
let maximal_balance_decrease = compute_maximal_balance_decrease_per_day::<
bp_polkadot::Balance,
bp_polkadot::WeightToFee,
>(bp_kusama::DAYS / bp_kusama::SESSION_LENGTH + 1);
assert!(
MAXIMAL_BALANCE_DECREASE_PER_DAY >= maximal_balance_decrease,
"Maximal expected loss per day {} is larger than hardcoded {}",
maximal_balance_decrease,
MAXIMAL_BALANCE_DECREASE_PER_DAY,
);
}
}
@@ -0,0 +1,345 @@
// 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/>.
//! Kusama-to-Polkadot messages sync entrypoint.
use std::ops::RangeInclusive;
use codec::Encode;
use frame_support::weights::Weight;
use sp_core::{Bytes, Pair};
use sp_runtime::{FixedPointNumber, FixedU128};
use bp_messages::MessageNonce;
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
use relay_kusama_client::{
HeaderId as KusamaHeaderId, Kusama, SigningParams as KusamaSigningParams,
};
use relay_polkadot_client::{
HeaderId as PolkadotHeaderId, Polkadot, SigningParams as PolkadotSigningParams,
};
use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use substrate_relay_helper::{
messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
},
messages_source::SubstrateMessagesSource,
messages_target::SubstrateMessagesTarget,
STALL_TIMEOUT,
};
/// Kusama-to-Polkadot message lane.
pub type MessageLaneKusamaMessagesToPolkadot =
SubstrateMessageLaneToSubstrate<Kusama, KusamaSigningParams, Polkadot, PolkadotSigningParams>;
#[derive(Clone)]
pub struct KusamaMessagesToPolkadot {
message_lane: MessageLaneKusamaMessagesToPolkadot,
}
impl SubstrateMessageLane for KusamaMessagesToPolkadot {
type MessageLane = MessageLaneKusamaMessagesToPolkadot;
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str =
bp_polkadot::TO_POLKADOT_MESSAGE_DETAILS_METHOD;
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
bp_polkadot::TO_POLKADOT_LATEST_GENERATED_NONCE_METHOD;
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_polkadot::TO_POLKADOT_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_kusama::FROM_KUSAMA_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
bp_kusama::FROM_KUSAMA_LATEST_CONFIRMED_NONCE_METHOD;
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str =
bp_kusama::FROM_KUSAMA_UNREWARDED_RELAYERS_STATE;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_kusama::BEST_FINALIZED_KUSAMA_HEADER_METHOD;
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str =
bp_polkadot::BEST_FINALIZED_POLKADOT_HEADER_METHOD;
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str =
bp_kusama::WITH_POLKADOT_MESSAGES_PALLET_NAME;
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str =
bp_polkadot::WITH_KUSAMA_MESSAGES_PALLET_NAME;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
bp_polkadot::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
type SourceChain = Kusama;
type TargetChain = Polkadot;
fn source_transactions_author(&self) -> bp_kusama::AccountId {
(*self.message_lane.source_sign.public().as_array_ref()).into()
}
fn make_messages_receiving_proof_transaction(
&self,
best_block_id: KusamaHeaderId,
transaction_nonce: bp_runtime::IndexOf<Kusama>,
_generated_at_block: PolkadotHeaderId,
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
) -> Bytes {
let (relayers_state, proof) = proof;
let call = relay_kusama_client::runtime::Call::BridgePolkadotMessages(
relay_kusama_client::runtime::BridgePolkadotMessagesCall::receive_messages_delivery_proof(
proof,
relayers_state,
),
);
let genesis_hash = *self.message_lane.source_client.genesis_hash();
let transaction = Kusama::sign_transaction(
genesis_hash,
&self.message_lane.source_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.source_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Polkadot -> Kusama confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
bp_kusama::max_extrinsic_weight(),
transaction.encode().len(),
bp_kusama::max_extrinsic_size(),
);
Bytes(transaction.encode())
}
fn target_transactions_author(&self) -> bp_polkadot::AccountId {
(*self.message_lane.target_sign.public().as_array_ref()).into()
}
fn make_messages_delivery_transaction(
&self,
best_block_id: PolkadotHeaderId,
transaction_nonce: bp_runtime::IndexOf<Polkadot>,
_generated_at_header: KusamaHeaderId,
_nonces: RangeInclusive<MessageNonce>,
proof: <Self::MessageLane as MessageLane>::MessagesProof,
) -> Bytes {
let (dispatch_weight, proof) = proof;
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
let messages_count = nonces_end - nonces_start + 1;
let call = relay_polkadot_client::runtime::Call::BridgeKusamaMessages(
relay_polkadot_client::runtime::BridgeKusamaMessagesCall::receive_messages_proof(
self.message_lane.relayer_id_at_source.clone(),
proof,
messages_count as _,
dispatch_weight,
),
);
let genesis_hash = *self.message_lane.target_client.genesis_hash();
let transaction = Polkadot::sign_transaction(
genesis_hash,
&self.message_lane.target_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.target_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Kusama -> Polkadot delivery transaction. Weight: <unknown>/{}, size: {}/{}",
bp_polkadot::max_extrinsic_weight(),
transaction.encode().len(),
bp_polkadot::max_extrinsic_size(),
);
Bytes(transaction.encode())
}
}
/// Kusama node as messages source.
type KusamaSourceClient = SubstrateMessagesSource<KusamaMessagesToPolkadot>;
/// Polkadot node as messages target.
type PolkadotTargetClient = SubstrateMessagesTarget<KusamaMessagesToPolkadot>;
/// Run Kusama-to-Polkadot messages sync.
pub async fn run(
params: MessagesRelayParams<
Kusama,
KusamaSigningParams,
Polkadot,
PolkadotSigningParams,
MixStrategy,
>,
) -> anyhow::Result<()> {
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
params.source_transactions_mortality,
params.target_transactions_mortality,
Kusama::AVERAGE_BLOCK_INTERVAL,
Polkadot::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
);
let relayer_id_at_kusama = (*params.source_sign.public().as_array_ref()).into();
let lane_id = params.lane_id;
let source_client = params.source_client;
let lane = KusamaMessagesToPolkadot {
message_lane: SubstrateMessageLaneToSubstrate {
source_client: source_client.clone(),
source_sign: params.source_sign,
source_transactions_mortality: params.source_transactions_mortality,
target_client: params.target_client.clone(),
target_sign: params.target_sign,
target_transactions_mortality: params.target_transactions_mortality,
relayer_id_at_source: relayer_id_at_kusama,
},
};
// 2/3 is reserved for proofs and tx overhead
let max_messages_size_in_single_batch = bp_polkadot::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) =
select_delivery_transaction_limits::<
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>,
>(
bp_polkadot::max_extrinsic_weight(),
bp_polkadot::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
);
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);
log::info!(
target: "bridge",
"Starting Kusama -> Polkadot messages relay.\n\t\
Kusama 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: {:?}/{:?}\n\t\
Stall timeout: {:?}",
lane.message_lane.relayer_id_at_source,
max_messages_in_single_batch,
max_messages_size_in_single_batch,
max_messages_weight_in_single_batch,
params.source_transactions_mortality,
params.target_transactions_mortality,
stall_timeout,
);
let (metrics_params, metrics_values) = add_standalone_metrics(
Some(messages_relay::message_lane_loop::metrics_prefix::<
<KusamaMessagesToPolkadot as SubstrateMessageLane>::MessageLane,
>(&lane_id)),
params.metrics_params,
source_client.clone(),
)?;
messages_relay::message_lane_loop::run(
messages_relay::message_lane_loop::Params {
lane: lane_id,
source_tick: Kusama::AVERAGE_BLOCK_INTERVAL,
target_tick: Polkadot::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:
bp_polkadot::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
max_unconfirmed_nonces_at_target:
bp_polkadot::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
max_messages_in_single_batch,
max_messages_weight_in_single_batch,
max_messages_size_in_single_batch,
relay_strategy: params.relay_strategy,
},
},
KusamaSourceClient::new(
source_client.clone(),
lane.clone(),
lane_id,
params.target_to_source_headers_relay,
),
PolkadotTargetClient::new(
params.target_client,
lane,
lane_id,
metrics_values,
params.source_to_target_headers_relay,
),
metrics_params,
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Add standalone metrics for the Kusama -> Polkadot messages loop.
pub(crate) fn add_standalone_metrics(
metrics_prefix: Option<String>,
metrics_params: MetricsParams,
source_client: Client<Kusama>,
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
let polkadot_to_kusama_conversion_rate_key = bp_runtime::storage_parameter_key(
bp_kusama::POLKADOT_TO_KUSAMA_CONVERSION_RATE_PARAMETER_NAME,
)
.0;
substrate_relay_helper::messages_lane::add_standalone_metrics::<KusamaMessagesToPolkadot>(
metrics_prefix,
metrics_params,
source_client,
Some(crate::chains::polkadot::TOKEN_ID),
Some(crate::chains::kusama::TOKEN_ID),
Some((
sp_core::storage::StorageKey(polkadot_to_kusama_conversion_rate_key),
// starting relay before this parameter will be set to some value may cause troubles
FixedU128::from_inner(FixedU128::DIV),
)),
)
}
/// Update Polkadot -> Kusama conversion rate, stored in Kusama runtime storage.
pub(crate) async fn update_polkadot_to_kusama_conversion_rate(
client: Client<Kusama>,
signer: <Kusama as TransactionSignScheme>::AccountKeyPair,
updated_rate: f64,
) -> anyhow::Result<()> {
let genesis_hash = *client.genesis_hash();
let signer_id = (*signer.public().as_array_ref()).into();
client
.submit_signed_extrinsic(signer_id, move |_, transaction_nonce| {
Bytes(
Kusama::sign_transaction(
genesis_hash,
&signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
relay_kusama_client::runtime::Call::BridgePolkadotMessages(
relay_kusama_client::runtime::BridgePolkadotMessagesCall::update_pallet_parameter(
relay_kusama_client::runtime::BridgePolkadotMessagesParameter::PolkadotToKusamaConversionRate(
sp_runtime::FixedU128::from_float(updated_rate),
)
)
),
transaction_nonce,
),
)
.encode(),
)
})
.await
.map(drop)
.map_err(|err| anyhow::format_err!("{:?}", err))
}
@@ -19,8 +19,11 @@
use crate::cli::{
bridge,
encode_call::{self, Call, CliEncodeCall},
encode_message, send_message, CliChain,
encode_message,
send_message::{self, DispatchFeePayment},
CliChain,
};
use anyhow::anyhow;
use bp_message_dispatch::{CallOrigin, MessagePayload};
use codec::Decode;
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
@@ -35,31 +38,32 @@ impl CliEncodeCall for Millau {
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
Ok(match call {
Call::Raw { data } => Decode::decode(&mut &*data.0)?,
Call::Remark { remark_payload, .. } => millau_runtime::Call::System(millau_runtime::SystemCall::remark(
remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
)),
Call::Transfer { recipient, amount } => millau_runtime::Call::Balances(
millau_runtime::BalancesCall::transfer(recipient.raw_id(), amount.cast()),
),
Call::BridgeSendMessage {
lane,
payload,
fee,
bridge_instance_index,
} => match *bridge_instance_index {
bridge::MILLAU_TO_RIALTO_INDEX => {
let payload = Decode::decode(&mut &*payload.0)?;
millau_runtime::Call::BridgeRialtoMessages(millau_runtime::MessagesCall::send_message(
lane.0,
payload,
fee.cast(),
))
}
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
bridge_instance_index
),
},
Call::Remark { remark_payload, .. } =>
millau_runtime::Call::System(millau_runtime::SystemCall::remark {
remark: remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
}),
Call::Transfer { recipient, amount } =>
millau_runtime::Call::Balances(millau_runtime::BalancesCall::transfer {
dest: recipient.raw_id(),
value: amount.cast(),
}),
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
match *bridge_instance_index {
bridge::MILLAU_TO_RIALTO_INDEX => {
let payload = Decode::decode(&mut &*payload.0)?;
millau_runtime::Call::BridgeRialtoMessages(
millau_runtime::MessagesCall::send_message {
lane_id: lane.0,
payload,
delivery_and_dispatch_fee: fee.cast(),
},
)
},
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
bridge_instance_index
),
},
})
}
@@ -72,7 +76,12 @@ impl CliChain for Millau {
const RUNTIME_VERSION: RuntimeVersion = millau_runtime::VERSION;
type KeyPair = sp_core::sr25519::Pair;
type MessagePayload = MessagePayload<bp_millau::AccountId, bp_rialto::AccountSigner, bp_rialto::Signature, Vec<u8>>;
type MessagePayload = MessagePayload<
bp_millau::AccountId,
bp_rialto::AccountSigner,
bp_rialto::Signature,
Vec<u8>,
>;
fn ss58_format() -> u16 {
millau_runtime::SS58Prefix::get() as u16
@@ -83,10 +92,12 @@ impl CliChain for Millau {
}
// TODO [#854|#843] support multiple bridges?
fn encode_message(message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
fn encode_message(
message: encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload> {
match message {
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
.map_err(|e| format!("Failed to decode Millau's MessagePayload: {:?}", e)),
.map_err(|e| anyhow!("Failed to decode Millau's MessagePayload: {:?}", e)),
encode_message::MessagePayload::Call { mut call, mut sender } => {
type Source = Millau;
type Target = relay_rialto_client::Rialto;
@@ -94,12 +105,21 @@ impl CliChain for Millau {
sender.enforce_chain::<Source>();
let spec_version = Target::RUNTIME_VERSION.spec_version;
let origin = CallOrigin::SourceAccount(sender.raw_id());
encode_call::preprocess_call::<Source, Target>(&mut call, bridge::MILLAU_TO_RIALTO_INDEX);
let call = Target::encode_call(&call).map_err(|e| e.to_string())?;
encode_call::preprocess_call::<Source, Target>(
&mut call,
bridge::MILLAU_TO_RIALTO_INDEX,
);
let call = Target::encode_call(&call)?;
let weight = call.get_dispatch_info().weight;
Ok(send_message::message_payload(spec_version, weight, origin, &call))
}
Ok(send_message::message_payload(
spec_version,
weight,
origin,
&call,
DispatchFeePayment::AtSourceChain,
))
},
}
}
}
@@ -16,37 +16,64 @@
//! Millau-to-Rialto headers sync entrypoint.
use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
use bp_header_chain::justification::GrandpaJustification;
use codec::Encode;
use relay_millau_client::{Millau, SyncHeader as MillauSyncHeader};
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
use relay_substrate_client::{Chain, TransactionSignScheme};
use sp_core::{Bytes, Pair};
use bp_header_chain::justification::GrandpaJustification;
use relay_millau_client::{Millau, SyncHeader as MillauSyncHeader};
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
use relay_substrate_client::{Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use substrate_relay_helper::finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
};
/// Millau-to-Rialto finality sync pipeline.
pub(crate) type MillauFinalityToRialto = SubstrateFinalityToSubstrate<Millau, Rialto, RialtoSigningParams>;
pub(crate) type FinalityPipelineMillauToRialto =
SubstrateFinalityToSubstrate<Millau, Rialto, RialtoSigningParams>;
#[derive(Clone, Debug)]
pub(crate) struct MillauFinalityToRialto {
finality_pipeline: FinalityPipelineMillauToRialto,
}
impl MillauFinalityToRialto {
pub fn new(target_client: Client<Rialto>, target_sign: RialtoSigningParams) -> Self {
Self { finality_pipeline: FinalityPipelineMillauToRialto::new(target_client, target_sign) }
}
}
impl SubstrateFinalitySyncPipeline for MillauFinalityToRialto {
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
type FinalitySyncPipeline = FinalityPipelineMillauToRialto;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
type TargetChain = Rialto;
fn transactions_author(&self) -> bp_rialto::AccountId {
(*self.target_sign.public().as_array_ref()).into()
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
}
fn make_submit_finality_proof_transaction(
&self,
transaction_nonce: <Rialto as Chain>::Index,
era: bp_runtime::TransactionEraOf<Rialto>,
transaction_nonce: IndexOf<Rialto>,
header: MillauSyncHeader,
proof: GrandpaJustification<bp_millau::Header>,
) -> Bytes {
let call = rialto_runtime::BridgeGrandpaMillauCall::submit_finality_proof(header.into_inner(), proof).into();
let call = rialto_runtime::BridgeGrandpaMillauCall::submit_finality_proof {
finality_target: Box::new(header.into_inner()),
justification: proof,
}
.into();
let genesis_hash = *self.target_client.genesis_hash();
let transaction = Rialto::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
let transaction = Rialto::sign_transaction(
genesis_hash,
&self.finality_pipeline.target_sign,
era,
UnsignedTransaction::new(call, transaction_nonce),
);
Bytes(transaction.encode())
}
@@ -16,64 +16,100 @@
//! Millau-to-Rialto messages sync entrypoint.
use crate::messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
};
use crate::messages_source::SubstrateMessagesSource;
use crate::messages_target::SubstrateMessagesTarget;
use std::ops::RangeInclusive;
use bp_messages::MessageNonce;
use bp_runtime::{MILLAU_CHAIN_ID, RIALTO_CHAIN_ID};
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use codec::Encode;
use frame_support::dispatch::GetDispatchInfo;
use messages_relay::message_lane::MessageLane;
use relay_millau_client::{HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams};
use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams};
use relay_substrate_client::{
metrics::{FloatStorageValueMetric, StorageProofOverheadMetric},
Chain, TransactionSignScheme,
};
use sp_core::{Bytes, Pair};
use std::{ops::RangeInclusive, time::Duration};
use bp_messages::MessageNonce;
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use frame_support::weights::Weight;
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
use relay_millau_client::{
HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams,
};
use relay_rialto_client::{
HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams,
};
use relay_substrate_client::{Chain, Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use substrate_relay_helper::{
messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
},
messages_source::SubstrateMessagesSource,
messages_target::SubstrateMessagesTarget,
STALL_TIMEOUT,
};
/// Millau-to-Rialto message lane.
pub type MillauMessagesToRialto =
pub type MessageLaneMillauMessagesToRialto =
SubstrateMessageLaneToSubstrate<Millau, MillauSigningParams, Rialto, RialtoSigningParams>;
#[derive(Clone)]
pub struct MillauMessagesToRialto {
message_lane: MessageLaneMillauMessagesToRialto,
}
impl SubstrateMessageLane for MillauMessagesToRialto {
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str = bp_rialto::TO_RIALTO_MESSAGE_DETAILS_METHOD;
type MessageLane = MessageLaneMillauMessagesToRialto;
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str =
bp_rialto::TO_RIALTO_MESSAGE_DETAILS_METHOD;
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
bp_rialto::TO_RIALTO_LATEST_GENERATED_NONCE_METHOD;
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_rialto::TO_RIALTO_LATEST_RECEIVED_NONCE_METHOD;
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_rialto::TO_RIALTO_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_millau::FROM_MILLAU_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_millau::FROM_MILLAU_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
bp_millau::FROM_MILLAU_LATEST_CONFIRMED_NONCE_METHOD;
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = bp_millau::FROM_MILLAU_UNREWARDED_RELAYERS_STATE;
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str =
bp_millau::FROM_MILLAU_UNREWARDED_RELAYERS_STATE;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str =
bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_millau::WITH_RIALTO_MESSAGES_PALLET_NAME;
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_rialto::WITH_MILLAU_MESSAGES_PALLET_NAME;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
bp_rialto::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
type SourceChain = Millau;
type TargetChain = Rialto;
fn source_transactions_author(&self) -> bp_millau::AccountId {
(*self.source_sign.public().as_array_ref()).into()
(*self.message_lane.source_sign.public().as_array_ref()).into()
}
fn make_messages_receiving_proof_transaction(
&self,
transaction_nonce: <Millau as Chain>::Index,
best_block_id: MillauHeaderId,
transaction_nonce: IndexOf<Millau>,
_generated_at_block: RialtoHeaderId,
proof: <Self as MessageLane>::MessagesReceivingProof,
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
) -> Bytes {
let (relayers_state, proof) = proof;
let call: millau_runtime::Call =
millau_runtime::MessagesCall::receive_messages_delivery_proof(proof, relayers_state).into();
millau_runtime::MessagesCall::receive_messages_delivery_proof { proof, relayers_state }
.into();
let call_weight = call.get_dispatch_info().weight;
let genesis_hash = *self.source_client.genesis_hash();
let transaction = Millau::sign_transaction(genesis_hash, &self.source_sign, transaction_nonce, call);
let genesis_hash = *self.message_lane.source_client.genesis_hash();
let transaction = Millau::sign_transaction(
genesis_hash,
&self.message_lane.source_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.source_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Rialto -> Millau confirmation transaction. Weight: {}/{}, size: {}/{}",
@@ -86,33 +122,38 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
}
fn target_transactions_author(&self) -> bp_rialto::AccountId {
(*self.target_sign.public().as_array_ref()).into()
(*self.message_lane.target_sign.public().as_array_ref()).into()
}
fn make_messages_delivery_transaction(
&self,
transaction_nonce: <Rialto as Chain>::Index,
best_block_id: RialtoHeaderId,
transaction_nonce: IndexOf<Rialto>,
_generated_at_header: MillauHeaderId,
_nonces: RangeInclusive<MessageNonce>,
proof: <Self as MessageLane>::MessagesProof,
proof: <Self::MessageLane as MessageLane>::MessagesProof,
) -> Bytes {
let (dispatch_weight, proof) = proof;
let FromBridgedChainMessagesProof {
ref nonces_start,
ref nonces_end,
..
} = proof;
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
let messages_count = nonces_end - nonces_start + 1;
let call: rialto_runtime::Call = rialto_runtime::MessagesCall::receive_messages_proof(
self.relayer_id_at_source.clone(),
let call: rialto_runtime::Call = rialto_runtime::MessagesCall::receive_messages_proof {
relayer_id_at_bridged_chain: self.message_lane.relayer_id_at_source.clone(),
proof,
messages_count as _,
messages_count: messages_count as _,
dispatch_weight,
)
}
.into();
let call_weight = call.get_dispatch_info().weight;
let genesis_hash = *self.target_client.genesis_hash();
let transaction = Rialto::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
let genesis_hash = *self.message_lane.target_client.genesis_hash();
let transaction = Rialto::sign_transaction(
genesis_hash,
&self.message_lane.target_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.target_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Millau -> Rialto delivery transaction. Weight: {}/{}, size: {}/{}",
@@ -126,35 +167,51 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
}
/// Millau node as messages source.
type MillauSourceClient =
SubstrateMessagesSource<Millau, MillauMessagesToRialto, millau_runtime::WithRialtoMessagesInstance>;
type MillauSourceClient = SubstrateMessagesSource<MillauMessagesToRialto>;
/// Rialto node as messages target.
type RialtoTargetClient =
SubstrateMessagesTarget<Rialto, MillauMessagesToRialto, rialto_runtime::WithMillauMessagesInstance>;
type RialtoTargetClient = SubstrateMessagesTarget<MillauMessagesToRialto>;
/// Run Millau-to-Rialto messages sync.
pub async fn run(
params: MessagesRelayParams<Millau, MillauSigningParams, Rialto, RialtoSigningParams>,
) -> Result<(), String> {
let stall_timeout = Duration::from_secs(5 * 60);
params: MessagesRelayParams<
Millau,
MillauSigningParams,
Rialto,
RialtoSigningParams,
MixStrategy,
>,
) -> anyhow::Result<()> {
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
params.source_transactions_mortality,
params.target_transactions_mortality,
Millau::AVERAGE_BLOCK_INTERVAL,
Rialto::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
);
let relayer_id_at_millau = (*params.source_sign.public().as_array_ref()).into();
let lane_id = params.lane_id;
let source_client = params.source_client;
let lane = MillauMessagesToRialto {
source_client: source_client.clone(),
source_sign: params.source_sign,
target_client: params.target_client.clone(),
target_sign: params.target_sign,
relayer_id_at_source: relayer_id_at_millau,
message_lane: SubstrateMessageLaneToSubstrate {
source_client: source_client.clone(),
source_sign: params.source_sign,
source_transactions_mortality: params.source_transactions_mortality,
target_client: params.target_client.clone(),
target_sign: params.target_sign,
target_transactions_mortality: params.target_transactions_mortality,
relayer_id_at_source: relayer_id_at_millau,
},
};
// 2/3 is reserved for proofs and tx overhead
let max_messages_size_in_single_batch = bp_rialto::max_extrinsic_size() / 3;
// TODO: use Millau weights after https://github.com/paritytech/parity-bridges-common/issues/390
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
select_delivery_transaction_limits::<pallet_bridge_messages::weights::RialtoWeight<millau_runtime::Runtime>>(
select_delivery_transaction_limits::<
pallet_bridge_messages::weights::RialtoWeight<millau_runtime::Runtime>,
>(
bp_rialto::max_extrinsic_weight(),
bp_rialto::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
);
@@ -165,13 +222,25 @@ pub async fn run(
Millau 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: {}",
lane.relayer_id_at_source,
Max messages weight in single transaction: {}\n\t\
Tx mortality: {:?}/{:?}\n\t\
Stall timeout: {:?}",
lane.message_lane.relayer_id_at_source,
max_messages_in_single_batch,
max_messages_size_in_single_batch,
max_messages_weight_in_single_batch,
params.source_transactions_mortality,
params.target_transactions_mortality,
stall_timeout,
);
let (metrics_params, metrics_values) = add_standalone_metrics(
Some(messages_relay::message_lane_loop::metrics_prefix::<
<MillauMessagesToRialto as SubstrateMessageLane>::MessageLane,
>(&lane_id)),
params.metrics_params,
source_client.clone(),
)?;
messages_relay::message_lane_loop::run(
messages_relay::message_lane_loop::Params {
lane: lane_id,
@@ -180,58 +249,86 @@ pub async fn run(
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
stall_timeout,
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
max_unrewarded_relayer_entries_at_target: bp_rialto::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
max_unconfirmed_nonces_at_target: bp_rialto::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
max_unrewarded_relayer_entries_at_target:
bp_rialto::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
max_unconfirmed_nonces_at_target:
bp_rialto::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
max_messages_in_single_batch,
max_messages_weight_in_single_batch,
max_messages_size_in_single_batch,
relayer_mode: messages_relay::message_lane_loop::RelayerMode::Altruistic,
relay_strategy: params.relay_strategy,
},
},
MillauSourceClient::new(
source_client.clone(),
lane.clone(),
lane_id,
RIALTO_CHAIN_ID,
params.target_to_source_headers_relay,
),
RialtoTargetClient::new(
params.target_client,
lane,
lane_id,
MILLAU_CHAIN_ID,
metrics_values,
params.source_to_target_headers_relay,
),
relay_utils::relay_metrics(
Some(messages_relay::message_lane_loop::metrics_prefix::<
MillauMessagesToRialto,
>(&lane_id)),
params.metrics_params,
)
.standalone_metric(|registry, prefix| {
StorageProofOverheadMetric::new(
registry,
prefix,
source_client.clone(),
"millau_storage_proof_overhead".into(),
"Millau storage proof overhead".into(),
)
})?
.standalone_metric(|registry, prefix| {
FloatStorageValueMetric::<_, sp_runtime::FixedU128>::new(
registry,
prefix,
source_client,
sp_core::storage::StorageKey(
millau_runtime::rialto_messages::RialtoToMillauConversionRate::key().to_vec(),
),
Some(millau_runtime::rialto_messages::INITIAL_RIALTO_TO_MILLAU_CONVERSION_RATE),
"millau_rialto_to_millau_conversion_rate".into(),
"Rialto to Millau tokens conversion rate (used by Rialto)".into(),
)
})?
.into_params(),
metrics_params,
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Add standalone metrics for the Millau -> Rialto messages loop.
pub(crate) fn add_standalone_metrics(
metrics_prefix: Option<String>,
metrics_params: MetricsParams,
source_client: Client<Millau>,
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
substrate_relay_helper::messages_lane::add_standalone_metrics::<MillauMessagesToRialto>(
metrics_prefix,
metrics_params,
source_client,
Some(crate::chains::MILLAU_ASSOCIATED_TOKEN_ID),
Some(crate::chains::RIALTO_ASSOCIATED_TOKEN_ID),
Some((
sp_core::storage::StorageKey(
millau_runtime::rialto_messages::RialtoToMillauConversionRate::key().to_vec(),
),
millau_runtime::rialto_messages::INITIAL_RIALTO_TO_MILLAU_CONVERSION_RATE,
)),
)
}
/// Update Rialto -> Millau conversion rate, stored in Millau runtime storage.
pub(crate) async fn update_rialto_to_millau_conversion_rate(
client: Client<Millau>,
signer: <Millau as TransactionSignScheme>::AccountKeyPair,
updated_rate: f64,
) -> anyhow::Result<()> {
let genesis_hash = *client.genesis_hash();
let signer_id = (*signer.public().as_array_ref()).into();
client
.submit_signed_extrinsic(signer_id, move |_, transaction_nonce| {
Bytes(
Millau::sign_transaction(
genesis_hash,
&signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
millau_runtime::MessagesCall::update_pallet_parameter {
parameter: millau_runtime::rialto_messages::MillauToRialtoMessagesParameter::RialtoToMillauConversionRate(
sp_runtime::FixedU128::from_float(updated_rate),
),
}
.into(),
transaction_nonce,
),
)
.encode(),
)
})
.await
.map(drop)
.map_err(|err| anyhow::format_err!("{:?}", err))
}
@@ -16,8 +16,12 @@
//! Chain-specific relayer configuration.
pub mod kusama_headers_to_polkadot;
pub mod kusama_messages_to_polkadot;
pub mod millau_headers_to_rialto;
pub mod millau_messages_to_rialto;
pub mod polkadot_headers_to_kusama;
pub mod polkadot_messages_to_kusama;
pub mod rialto_headers_to_millau;
pub mod rialto_messages_to_millau;
pub mod rococo_headers_to_wococo;
@@ -26,45 +30,40 @@ pub mod westend_headers_to_millau;
pub mod wococo_headers_to_rococo;
pub mod wococo_messages_to_rococo;
mod kusama;
mod millau;
mod polkadot;
mod rialto;
mod rialto_parachain;
mod rococo;
mod westend;
mod wococo;
use relay_utils::metrics::{FloatJsonValueMetric, MetricsParams};
// Millau/Rialto tokens have no any real value, so the conversion rate we use is always 1:1. But we
// want to test our code that is intended to work with real-value chains. So to keep it close to
// 1:1, we'll be treating Rialto as BTC and Millau as wBTC (only in relayer).
/// The identifier of token, which value is associated with Rialto token value by relayer.
pub(crate) const RIALTO_ASSOCIATED_TOKEN_ID: &str = polkadot::TOKEN_ID;
/// The identifier of token, which value is associated with Millau token value by relayer.
pub(crate) const MILLAU_ASSOCIATED_TOKEN_ID: &str = kusama::TOKEN_ID;
use relay_utils::metrics::MetricsParams;
pub(crate) fn add_polkadot_kusama_price_metrics<T: finality_relay::FinalitySyncPipeline>(
prefix: Option<String>,
params: MetricsParams,
) -> anyhow::Result<MetricsParams> {
Ok(
relay_utils::relay_metrics(Some(finality_relay::metrics_prefix::<T>()), params)
// Polkadot/Kusama prices are added as metrics here, because atm we don't have Polkadot <-> Kusama
// relays, but we want to test metrics/dashboards in advance
.standalone_metric(|registry, prefix| {
FloatJsonValueMetric::new(
registry,
prefix,
"https://api.coingecko.com/api/v3/simple/price?ids=Polkadot&vs_currencies=btc".into(),
"$.polkadot.btc".into(),
"polkadot_to_base_conversion_rate".into(),
"Rate used to convert from DOT to some BASE tokens".into(),
)
})
.map_err(|e| anyhow::format_err!("{}", e))?
.standalone_metric(|registry, prefix| {
FloatJsonValueMetric::new(
registry,
prefix,
"https://api.coingecko.com/api/v3/simple/price?ids=Kusama&vs_currencies=btc".into(),
"$.kusama.btc".into(),
"kusama_to_base_conversion_rate".into(),
"Rate used to convert from KSM to some BASE tokens".into(),
)
})
.map_err(|e| anyhow::format_err!("{}", e))?
.into_params(),
)
// Polkadot/Kusama prices are added as metrics here, because atm we don't have Polkadot <->
// Kusama relays, but we want to test metrics/dashboards in advance
Ok(relay_utils::relay_metrics(prefix, params)
.standalone_metric(|registry, prefix| {
substrate_relay_helper::helpers::token_price_metric(registry, prefix, "polkadot")
})?
.standalone_metric(|registry, prefix| {
substrate_relay_helper::helpers::token_price_metric(registry, prefix, "kusama")
})?
.into_params())
}
#[cfg(test)]
@@ -75,7 +74,7 @@ mod tests {
use frame_support::dispatch::GetDispatchInfo;
use relay_millau_client::Millau;
use relay_rialto_client::Rialto;
use relay_substrate_client::TransactionSignScheme;
use relay_substrate_client::{TransactionSignScheme, UnsignedTransaction};
use sp_core::Pair;
use sp_runtime::traits::{IdentifyAccount, Verify};
@@ -83,7 +82,8 @@ mod tests {
fn millau_signature_is_valid_on_rialto() {
let millau_sign = relay_millau_client::SigningParams::from_string("//Dave", None).unwrap();
let call = rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(vec![]));
let call =
rialto_runtime::Call::System(rialto_runtime::SystemCall::remark { remark: vec![] });
let millau_public: bp_millau::AccountSigner = millau_sign.public().into();
let millau_account_id: bp_millau::AccountId = millau_public.into_account();
@@ -94,7 +94,8 @@ mod tests {
rialto_runtime::VERSION.spec_version,
);
let rialto_signer = relay_rialto_client::SigningParams::from_string("//Dave", None).unwrap();
let rialto_signer =
relay_rialto_client::SigningParams::from_string("//Dave", None).unwrap();
let signature = rialto_signer.sign(&digest);
assert!(signature.verify(&digest[..], &rialto_signer.public()));
@@ -104,7 +105,8 @@ mod tests {
fn rialto_signature_is_valid_on_millau() {
let rialto_sign = relay_rialto_client::SigningParams::from_string("//Dave", None).unwrap();
let call = millau_runtime::Call::System(millau_runtime::SystemCall::remark(vec![]));
let call =
millau_runtime::Call::System(millau_runtime::SystemCall::remark { remark: vec![] });
let rialto_public: bp_rialto::AccountSigner = rialto_sign.public().into();
let rialto_account_id: bp_rialto::AccountId = rialto_public.into_account();
@@ -115,7 +117,8 @@ mod tests {
millau_runtime::VERSION.spec_version,
);
let millau_signer = relay_millau_client::SigningParams::from_string("//Dave", None).unwrap();
let millau_signer =
relay_millau_client::SigningParams::from_string("//Dave", None).unwrap();
let signature = millau_signer.sign(&digest);
assert!(signature.verify(&digest[..], &millau_signer.public()));
@@ -130,22 +133,27 @@ mod tests {
bp_millau::max_extrinsic_size(),
);
let call: millau_runtime::Call = millau_runtime::SystemCall::remark(vec![42; maximal_remark_size as _]).into();
let call: millau_runtime::Call =
millau_runtime::SystemCall::remark { remark: vec![42; maximal_remark_size as _] }
.into();
let payload = send_message::message_payload(
Default::default(),
call.get_dispatch_info().weight,
bp_message_dispatch::CallOrigin::SourceRoot,
&call,
send_message::DispatchFeePayment::AtSourceChain,
);
assert_eq!(Millau::verify_message(&payload), Ok(()));
let call: millau_runtime::Call =
millau_runtime::SystemCall::remark(vec![42; (maximal_remark_size + 1) as _]).into();
millau_runtime::SystemCall::remark { remark: vec![42; (maximal_remark_size + 1) as _] }
.into();
let payload = send_message::message_payload(
Default::default(),
call.get_dispatch_info().weight,
bp_message_dispatch::CallOrigin::SourceRoot,
&call,
send_message::DispatchFeePayment::AtSourceChain,
);
assert!(Millau::verify_message(&payload).is_err());
}
@@ -164,15 +172,18 @@ mod tests {
fn maximal_rialto_to_millau_message_dispatch_weight_is_computed_correctly() {
use rialto_runtime::millau_messages::Millau;
let maximal_dispatch_weight =
send_message::compute_maximal_message_dispatch_weight(bp_millau::max_extrinsic_weight());
let call: millau_runtime::Call = rialto_runtime::SystemCall::remark(vec![]).into();
let maximal_dispatch_weight = send_message::compute_maximal_message_dispatch_weight(
bp_millau::max_extrinsic_weight(),
);
let call: millau_runtime::Call =
rialto_runtime::SystemCall::remark { remark: vec![] }.into();
let payload = send_message::message_payload(
Default::default(),
maximal_dispatch_weight,
bp_message_dispatch::CallOrigin::SourceRoot,
&call,
send_message::DispatchFeePayment::AtSourceChain,
);
assert_eq!(Millau::verify_message(&payload), Ok(()));
@@ -181,6 +192,7 @@ mod tests {
maximal_dispatch_weight + 1,
bp_message_dispatch::CallOrigin::SourceRoot,
&call,
send_message::DispatchFeePayment::AtSourceChain,
);
assert!(Millau::verify_message(&payload).is_err());
}
@@ -189,15 +201,18 @@ mod tests {
fn maximal_weight_fill_block_to_rialto_is_generated_correctly() {
use millau_runtime::rialto_messages::Rialto;
let maximal_dispatch_weight =
send_message::compute_maximal_message_dispatch_weight(bp_rialto::max_extrinsic_weight());
let call: rialto_runtime::Call = millau_runtime::SystemCall::remark(vec![]).into();
let maximal_dispatch_weight = send_message::compute_maximal_message_dispatch_weight(
bp_rialto::max_extrinsic_weight(),
);
let call: rialto_runtime::Call =
millau_runtime::SystemCall::remark { remark: vec![] }.into();
let payload = send_message::message_payload(
Default::default(),
maximal_dispatch_weight,
bp_message_dispatch::CallOrigin::SourceRoot,
&call,
send_message::DispatchFeePayment::AtSourceChain,
);
assert_eq!(Rialto::verify_message(&payload), Ok(()));
@@ -206,18 +221,20 @@ mod tests {
maximal_dispatch_weight + 1,
bp_message_dispatch::CallOrigin::SourceRoot,
&call,
send_message::DispatchFeePayment::AtSourceChain,
);
assert!(Rialto::verify_message(&payload).is_err());
}
#[test]
fn rialto_tx_extra_bytes_constant_is_correct() {
let rialto_call = rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(vec![]));
let rialto_call =
rialto_runtime::Call::System(rialto_runtime::SystemCall::remark { remark: vec![] });
let rialto_tx = Rialto::sign_transaction(
Default::default(),
&sp_keyring::AccountKeyring::Alice.pair(),
0,
rialto_call.clone(),
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(rialto_call.clone(), 0),
);
let extra_bytes_in_transaction = rialto_tx.encode().len() - rialto_call.encode().len();
assert!(
@@ -230,12 +247,13 @@ mod tests {
#[test]
fn millau_tx_extra_bytes_constant_is_correct() {
let millau_call = millau_runtime::Call::System(millau_runtime::SystemCall::remark(vec![]));
let millau_call =
millau_runtime::Call::System(millau_runtime::SystemCall::remark { remark: vec![] });
let millau_tx = Millau::sign_transaction(
Default::default(),
&sp_keyring::AccountKeyring::Alice.pair(),
0,
millau_call.clone(),
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(millau_call.clone(), 0),
);
let extra_bytes_in_transaction = millau_tx.encode().len() - millau_call.encode().len();
assert!(
@@ -274,13 +292,14 @@ mod rococo_tests {
};
let actual = relay_rococo_client::runtime::BridgeGrandpaWococoCall::submit_finality_proof(
header.clone(),
Box::new(header.clone()),
justification.clone(),
);
let expected = millau_runtime::BridgeGrandpaRialtoCall::<millau_runtime::Runtime>::submit_finality_proof(
header,
justification,
);
let expected =
millau_runtime::BridgeGrandpaCall::<millau_runtime::Runtime>::submit_finality_proof {
finality_target: Box::new(header),
justification,
};
// when
let actual_encoded = actual.encode();
@@ -321,11 +340,15 @@ mod westend_tests {
votes_ancestries: vec![],
};
let actual = bp_westend::BridgeGrandpaRococoCall::submit_finality_proof(header.clone(), justification.clone());
let expected = millau_runtime::BridgeGrandpaRialtoCall::<millau_runtime::Runtime>::submit_finality_proof(
header,
justification,
let actual = relay_kusama_client::runtime::BridgePolkadotGrandpaCall::submit_finality_proof(
Box::new(header.clone()),
justification.clone(),
);
let expected =
millau_runtime::BridgeGrandpaCall::<millau_runtime::Runtime>::submit_finality_proof {
finality_target: Box::new(header),
justification,
};
// when
let actual_encoded = actual.encode();
@@ -0,0 +1,103 @@
// 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/>.
use codec::Decode;
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
use relay_polkadot_client::Polkadot;
use sp_version::RuntimeVersion;
use crate::cli::{
bridge,
encode_call::{Call, CliEncodeCall},
encode_message, CliChain,
};
/// Weight of the `system::remark` call at Polkadot.
///
/// This weight is larger (x2) than actual weight at current Polkadot runtime to avoid unsuccessful
/// calls in the future. But since it is used only in tests (and on test chains), this is ok.
pub(crate) const SYSTEM_REMARK_CALL_WEIGHT: Weight = 2 * 1_345_000;
/// Id of Polkadot token that is used to fetch token price.
pub(crate) const TOKEN_ID: &str = "polkadot";
impl CliEncodeCall for Polkadot {
fn max_extrinsic_size() -> u32 {
bp_polkadot::max_extrinsic_size()
}
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
Ok(match call {
Call::Remark { remark_payload, .. } => relay_polkadot_client::runtime::Call::System(
relay_polkadot_client::runtime::SystemCall::remark(
remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
),
),
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
match *bridge_instance_index {
bridge::POLKADOT_TO_KUSAMA_INDEX => {
let payload = Decode::decode(&mut &*payload.0)?;
relay_polkadot_client::runtime::Call::BridgeKusamaMessages(
relay_polkadot_client::runtime::BridgeKusamaMessagesCall::send_message(
lane.0, payload, fee.0,
),
)
},
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
bridge_instance_index
),
},
_ => anyhow::bail!("Unsupported Polkadot call: {:?}", call),
})
}
fn get_dispatch_info(
call: &relay_polkadot_client::runtime::Call,
) -> anyhow::Result<DispatchInfo> {
match *call {
relay_polkadot_client::runtime::Call::System(
relay_polkadot_client::runtime::SystemCall::remark(_),
) => Ok(DispatchInfo {
weight: crate::chains::polkadot::SYSTEM_REMARK_CALL_WEIGHT,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
}),
_ => anyhow::bail!("Unsupported Polkadot call: {:?}", call),
}
}
}
impl CliChain for Polkadot {
const RUNTIME_VERSION: RuntimeVersion = bp_polkadot::VERSION;
type KeyPair = sp_core::sr25519::Pair;
type MessagePayload = ();
fn ss58_format() -> u16 {
42
}
fn max_extrinsic_weight() -> Weight {
bp_polkadot::max_extrinsic_weight()
}
fn encode_message(
_message: encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload> {
anyhow::bail!("Sending messages from Polkadot is not yet supported.")
}
}
@@ -0,0 +1,134 @@
// 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/>.
//! Polkadot-to-Kusama headers sync entrypoint.
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_header_chain::justification::GrandpaJustification;
use relay_kusama_client::{Kusama, SigningParams as KusamaSigningParams};
use relay_polkadot_client::{Polkadot, SyncHeader as PolkadotSyncHeader};
use relay_substrate_client::{Client, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use substrate_relay_helper::finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
};
/// Maximal saturating difference between `balance(now)` and `balance(now-24h)` to treat
/// relay as gone wild.
///
/// Actual value, returned by `maximal_balance_decrease_per_day_is_sane` test is approximately 0.001
/// KSM, but let's round up to 0.1 KSM here.
pub(crate) const MAXIMAL_BALANCE_DECREASE_PER_DAY: bp_polkadot::Balance = 100_000_000_000;
/// Polkadot-to-Kusama finality sync pipeline.
pub(crate) type FinalityPipelinePolkadotFinalityToKusama =
SubstrateFinalityToSubstrate<Polkadot, Kusama, KusamaSigningParams>;
#[derive(Clone, Debug)]
pub(crate) struct PolkadotFinalityToKusama {
finality_pipeline: FinalityPipelinePolkadotFinalityToKusama,
}
impl PolkadotFinalityToKusama {
pub fn new(target_client: Client<Kusama>, target_sign: KusamaSigningParams) -> Self {
Self {
finality_pipeline: FinalityPipelinePolkadotFinalityToKusama::new(
target_client,
target_sign,
),
}
}
}
impl SubstrateFinalitySyncPipeline for PolkadotFinalityToKusama {
type FinalitySyncPipeline = FinalityPipelinePolkadotFinalityToKusama;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_polkadot::BEST_FINALIZED_POLKADOT_HEADER_METHOD;
type TargetChain = Kusama;
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(
Some(finality_relay::metrics_prefix::<Self::FinalitySyncPipeline>()),
params,
)
}
fn start_relay_guards(&self) {
relay_substrate_client::guard::abort_on_spec_version_change(
self.finality_pipeline.target_client.clone(),
bp_kusama::VERSION.spec_version,
);
relay_substrate_client::guard::abort_when_account_balance_decreased(
self.finality_pipeline.target_client.clone(),
self.transactions_author(),
MAXIMAL_BALANCE_DECREASE_PER_DAY,
);
}
fn transactions_author(&self) -> bp_kusama::AccountId {
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
}
fn make_submit_finality_proof_transaction(
&self,
era: bp_runtime::TransactionEraOf<Kusama>,
transaction_nonce: bp_runtime::IndexOf<Kusama>,
header: PolkadotSyncHeader,
proof: GrandpaJustification<bp_polkadot::Header>,
) -> Bytes {
let call = relay_kusama_client::runtime::Call::BridgePolkadotGrandpa(
relay_kusama_client::runtime::BridgePolkadotGrandpaCall::submit_finality_proof(
Box::new(header.into_inner()),
proof,
),
);
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
let transaction = Kusama::sign_transaction(
genesis_hash,
&self.finality_pipeline.target_sign,
era,
UnsignedTransaction::new(call, transaction_nonce),
);
Bytes(transaction.encode())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chains::kusama_headers_to_polkadot::tests::compute_maximal_balance_decrease_per_day;
#[test]
fn maximal_balance_decrease_per_day_is_sane() {
// we expect Polkadot -> Kusama relay to be running in mandatory-headers-only mode
// => we expect single header for every Polkadot session
let maximal_balance_decrease = compute_maximal_balance_decrease_per_day::<
bp_kusama::Balance,
bp_kusama::WeightToFee,
>(bp_polkadot::DAYS / bp_polkadot::SESSION_LENGTH + 1);
assert!(
MAXIMAL_BALANCE_DECREASE_PER_DAY >= maximal_balance_decrease,
"Maximal expected loss per day {} is larger than hardcoded {}",
maximal_balance_decrease,
MAXIMAL_BALANCE_DECREASE_PER_DAY,
);
}
}
@@ -0,0 +1,344 @@
// 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/>.
//! Polkadot-to-Kusama messages sync entrypoint.
use std::ops::RangeInclusive;
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_messages::MessageNonce;
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use frame_support::weights::Weight;
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
use relay_kusama_client::{
HeaderId as KusamaHeaderId, Kusama, SigningParams as KusamaSigningParams,
};
use relay_polkadot_client::{
HeaderId as PolkadotHeaderId, Polkadot, SigningParams as PolkadotSigningParams,
};
use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use sp_runtime::{FixedPointNumber, FixedU128};
use substrate_relay_helper::{
messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
},
messages_source::SubstrateMessagesSource,
messages_target::SubstrateMessagesTarget,
STALL_TIMEOUT,
};
/// Polkadot-to-Kusama message lane.
pub type MessageLanePolkadotMessagesToKusama =
SubstrateMessageLaneToSubstrate<Polkadot, PolkadotSigningParams, Kusama, KusamaSigningParams>;
#[derive(Clone)]
pub struct PolkadotMessagesToKusama {
message_lane: MessageLanePolkadotMessagesToKusama,
}
impl SubstrateMessageLane for PolkadotMessagesToKusama {
type MessageLane = MessageLanePolkadotMessagesToKusama;
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str =
bp_kusama::TO_KUSAMA_MESSAGE_DETAILS_METHOD;
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
bp_kusama::TO_KUSAMA_LATEST_GENERATED_NONCE_METHOD;
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_kusama::TO_KUSAMA_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_polkadot::FROM_POLKADOT_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
bp_polkadot::FROM_POLKADOT_LATEST_CONFIRMED_NONCE_METHOD;
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str =
bp_polkadot::FROM_POLKADOT_UNREWARDED_RELAYERS_STATE;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_polkadot::BEST_FINALIZED_POLKADOT_HEADER_METHOD;
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str =
bp_kusama::BEST_FINALIZED_KUSAMA_HEADER_METHOD;
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str =
bp_polkadot::WITH_KUSAMA_MESSAGES_PALLET_NAME;
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str =
bp_kusama::WITH_POLKADOT_MESSAGES_PALLET_NAME;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
bp_kusama::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
type SourceChain = Polkadot;
type TargetChain = Kusama;
fn source_transactions_author(&self) -> bp_polkadot::AccountId {
(*self.message_lane.source_sign.public().as_array_ref()).into()
}
fn make_messages_receiving_proof_transaction(
&self,
best_block_id: PolkadotHeaderId,
transaction_nonce: bp_runtime::IndexOf<Polkadot>,
_generated_at_block: KusamaHeaderId,
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
) -> Bytes {
let (relayers_state, proof) = proof;
let call = relay_polkadot_client::runtime::Call::BridgeKusamaMessages(
relay_polkadot_client::runtime::BridgeKusamaMessagesCall::receive_messages_delivery_proof(
proof,
relayers_state,
),
);
let genesis_hash = *self.message_lane.source_client.genesis_hash();
let transaction = Polkadot::sign_transaction(
genesis_hash,
&self.message_lane.source_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.source_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Kusama -> Polkadot confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
bp_polkadot::max_extrinsic_weight(),
transaction.encode().len(),
bp_polkadot::max_extrinsic_size(),
);
Bytes(transaction.encode())
}
fn target_transactions_author(&self) -> bp_kusama::AccountId {
(*self.message_lane.target_sign.public().as_array_ref()).into()
}
fn make_messages_delivery_transaction(
&self,
best_block_id: KusamaHeaderId,
transaction_nonce: bp_runtime::IndexOf<Kusama>,
_generated_at_header: PolkadotHeaderId,
_nonces: RangeInclusive<MessageNonce>,
proof: <Self::MessageLane as MessageLane>::MessagesProof,
) -> Bytes {
let (dispatch_weight, proof) = proof;
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
let messages_count = nonces_end - nonces_start + 1;
let call = relay_kusama_client::runtime::Call::BridgePolkadotMessages(
relay_kusama_client::runtime::BridgePolkadotMessagesCall::receive_messages_proof(
self.message_lane.relayer_id_at_source.clone(),
proof,
messages_count as _,
dispatch_weight,
),
);
let genesis_hash = *self.message_lane.target_client.genesis_hash();
let transaction = Kusama::sign_transaction(
genesis_hash,
&self.message_lane.target_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.target_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Polkadot -> Kusama delivery transaction. Weight: <unknown>/{}, size: {}/{}",
bp_kusama::max_extrinsic_weight(),
transaction.encode().len(),
bp_kusama::max_extrinsic_size(),
);
Bytes(transaction.encode())
}
}
/// Polkadot node as messages source.
type PolkadotSourceClient = SubstrateMessagesSource<PolkadotMessagesToKusama>;
/// Kusama node as messages target.
type KusamaTargetClient = SubstrateMessagesTarget<PolkadotMessagesToKusama>;
/// Run Polkadot-to-Kusama messages sync.
pub async fn run(
params: MessagesRelayParams<
Polkadot,
PolkadotSigningParams,
Kusama,
KusamaSigningParams,
MixStrategy,
>,
) -> anyhow::Result<()> {
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
params.source_transactions_mortality,
params.target_transactions_mortality,
Polkadot::AVERAGE_BLOCK_INTERVAL,
Kusama::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
);
let relayer_id_at_polkadot = (*params.source_sign.public().as_array_ref()).into();
let lane_id = params.lane_id;
let source_client = params.source_client;
let lane = PolkadotMessagesToKusama {
message_lane: SubstrateMessageLaneToSubstrate {
source_client: source_client.clone(),
source_sign: params.source_sign,
source_transactions_mortality: params.source_transactions_mortality,
target_client: params.target_client.clone(),
target_sign: params.target_sign,
target_transactions_mortality: params.target_transactions_mortality,
relayer_id_at_source: relayer_id_at_polkadot,
},
};
// 2/3 is reserved for proofs and tx overhead
let max_messages_size_in_single_batch = bp_kusama::max_extrinsic_size() / 3;
// we don't know exact weights of the Kusama 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) =
select_delivery_transaction_limits::<
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>,
>(
bp_kusama::max_extrinsic_weight(),
bp_kusama::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
);
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);
log::info!(
target: "bridge",
"Starting Polkadot -> Kusama messages relay.\n\t\
Polkadot 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: {:?}/{:?}\n\t\
Stall timeout: {:?}",
lane.message_lane.relayer_id_at_source,
max_messages_in_single_batch,
max_messages_size_in_single_batch,
max_messages_weight_in_single_batch,
params.source_transactions_mortality,
params.target_transactions_mortality,
stall_timeout,
);
let (metrics_params, metrics_values) = add_standalone_metrics(
Some(messages_relay::message_lane_loop::metrics_prefix::<
<PolkadotMessagesToKusama as SubstrateMessageLane>::MessageLane,
>(&lane_id)),
params.metrics_params,
source_client.clone(),
)?;
messages_relay::message_lane_loop::run(
messages_relay::message_lane_loop::Params {
lane: lane_id,
source_tick: Polkadot::AVERAGE_BLOCK_INTERVAL,
target_tick: Kusama::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:
bp_kusama::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
max_unconfirmed_nonces_at_target:
bp_kusama::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
max_messages_in_single_batch,
max_messages_weight_in_single_batch,
max_messages_size_in_single_batch,
relay_strategy: params.relay_strategy,
},
},
PolkadotSourceClient::new(
source_client.clone(),
lane.clone(),
lane_id,
params.target_to_source_headers_relay,
),
KusamaTargetClient::new(
params.target_client,
lane,
lane_id,
metrics_values,
params.source_to_target_headers_relay,
),
metrics_params,
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Add standalone metrics for the Polkadot -> Kusama messages loop.
pub(crate) fn add_standalone_metrics(
metrics_prefix: Option<String>,
metrics_params: MetricsParams,
source_client: Client<Polkadot>,
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
let kusama_to_polkadot_conversion_rate_key = bp_runtime::storage_parameter_key(
bp_polkadot::KUSAMA_TO_POLKADOT_CONVERSION_RATE_PARAMETER_NAME,
)
.0;
substrate_relay_helper::messages_lane::add_standalone_metrics::<PolkadotMessagesToKusama>(
metrics_prefix,
metrics_params,
source_client,
Some(crate::chains::kusama::TOKEN_ID),
Some(crate::chains::polkadot::TOKEN_ID),
Some((
sp_core::storage::StorageKey(kusama_to_polkadot_conversion_rate_key),
// starting relay before this parameter will be set to some value may cause troubles
FixedU128::from_inner(FixedU128::DIV),
)),
)
}
/// Update Kusama -> Polkadot conversion rate, stored in Polkadot runtime storage.
pub(crate) async fn update_kusama_to_polkadot_conversion_rate(
client: Client<Polkadot>,
signer: <Polkadot as TransactionSignScheme>::AccountKeyPair,
updated_rate: f64,
) -> anyhow::Result<()> {
let genesis_hash = *client.genesis_hash();
let signer_id = (*signer.public().as_array_ref()).into();
client
.submit_signed_extrinsic(signer_id, move |_, transaction_nonce| {
Bytes(
Polkadot::sign_transaction(
genesis_hash,
&signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
relay_polkadot_client::runtime::Call::BridgeKusamaMessages(
relay_polkadot_client::runtime::BridgeKusamaMessagesCall::update_pallet_parameter(
relay_polkadot_client::runtime::BridgeKusamaMessagesParameter::KusamaToPolkadotConversionRate(
sp_runtime::FixedU128::from_float(updated_rate),
)
)
),
transaction_nonce,
),
)
.encode(),
)
})
.await
.map(drop)
.map_err(|err| anyhow::format_err!("{:?}", err))
}
@@ -19,8 +19,11 @@
use crate::cli::{
bridge,
encode_call::{self, Call, CliEncodeCall},
encode_message, send_message, CliChain,
encode_message,
send_message::{self, DispatchFeePayment},
CliChain,
};
use anyhow::anyhow;
use bp_message_dispatch::{CallOrigin, MessagePayload};
use codec::Decode;
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
@@ -35,29 +38,32 @@ impl CliEncodeCall for Rialto {
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
Ok(match call {
Call::Raw { data } => Decode::decode(&mut &*data.0)?,
Call::Remark { remark_payload, .. } => rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(
remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
)),
Call::Transfer { recipient, amount } => {
rialto_runtime::Call::Balances(rialto_runtime::BalancesCall::transfer(recipient.raw_id(), amount.0))
}
Call::BridgeSendMessage {
lane,
payload,
fee,
bridge_instance_index,
} => match *bridge_instance_index {
bridge::RIALTO_TO_MILLAU_INDEX => {
let payload = Decode::decode(&mut &*payload.0)?;
rialto_runtime::Call::BridgeMillauMessages(rialto_runtime::MessagesCall::send_message(
lane.0, payload, fee.0,
))
}
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
bridge_instance_index
),
},
Call::Remark { remark_payload, .. } =>
rialto_runtime::Call::System(rialto_runtime::SystemCall::remark {
remark: remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
}),
Call::Transfer { recipient, amount } =>
rialto_runtime::Call::Balances(rialto_runtime::BalancesCall::transfer {
dest: recipient.raw_id().into(),
value: amount.0,
}),
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
match *bridge_instance_index {
bridge::RIALTO_TO_MILLAU_INDEX => {
let payload = Decode::decode(&mut &*payload.0)?;
rialto_runtime::Call::BridgeMillauMessages(
rialto_runtime::MessagesCall::send_message {
lane_id: lane.0,
payload,
delivery_and_dispatch_fee: fee.0,
},
)
},
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
bridge_instance_index
),
},
})
}
@@ -70,7 +76,12 @@ impl CliChain for Rialto {
const RUNTIME_VERSION: RuntimeVersion = rialto_runtime::VERSION;
type KeyPair = sp_core::sr25519::Pair;
type MessagePayload = MessagePayload<bp_rialto::AccountId, bp_millau::AccountSigner, bp_millau::Signature, Vec<u8>>;
type MessagePayload = MessagePayload<
bp_rialto::AccountId,
bp_millau::AccountSigner,
bp_millau::Signature,
Vec<u8>,
>;
fn ss58_format() -> u16 {
rialto_runtime::SS58Prefix::get() as u16
@@ -80,10 +91,12 @@ impl CliChain for Rialto {
bp_rialto::max_extrinsic_weight()
}
fn encode_message(message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
fn encode_message(
message: encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload> {
match message {
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
.map_err(|e| format!("Failed to decode Rialto's MessagePayload: {:?}", e)),
.map_err(|e| anyhow!("Failed to decode Rialto's MessagePayload: {:?}", e)),
encode_message::MessagePayload::Call { mut call, mut sender } => {
type Source = Rialto;
type Target = relay_millau_client::Millau;
@@ -91,12 +104,21 @@ impl CliChain for Rialto {
sender.enforce_chain::<Source>();
let spec_version = Target::RUNTIME_VERSION.spec_version;
let origin = CallOrigin::SourceAccount(sender.raw_id());
encode_call::preprocess_call::<Source, Target>(&mut call, bridge::RIALTO_TO_MILLAU_INDEX);
let call = Target::encode_call(&call).map_err(|e| e.to_string())?;
encode_call::preprocess_call::<Source, Target>(
&mut call,
bridge::RIALTO_TO_MILLAU_INDEX,
);
let call = Target::encode_call(&call)?;
let weight = call.get_dispatch_info().weight;
Ok(send_message::message_payload(spec_version, weight, origin, &call))
}
Ok(send_message::message_payload(
spec_version,
weight,
origin,
&call,
DispatchFeePayment::AtSourceChain,
))
},
}
}
}
@@ -16,41 +16,72 @@
//! Rialto-to-Millau headers sync entrypoint.
use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
use bp_header_chain::justification::GrandpaJustification;
use codec::Encode;
use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
use relay_rialto_client::{Rialto, SyncHeader as RialtoSyncHeader};
use relay_substrate_client::{Chain, TransactionSignScheme};
use sp_core::{Bytes, Pair};
use bp_header_chain::justification::GrandpaJustification;
use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
use relay_rialto_client::{Rialto, SyncHeader as RialtoSyncHeader};
use relay_substrate_client::{Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use substrate_relay_helper::finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
};
/// Rialto-to-Millau finality sync pipeline.
pub(crate) type RialtoFinalityToMillau = SubstrateFinalityToSubstrate<Rialto, Millau, MillauSigningParams>;
pub(crate) type FinalityPipelineRialtoFinalityToMillau =
SubstrateFinalityToSubstrate<Rialto, Millau, MillauSigningParams>;
#[derive(Clone, Debug)]
pub struct RialtoFinalityToMillau {
finality_pipeline: FinalityPipelineRialtoFinalityToMillau,
}
impl RialtoFinalityToMillau {
pub fn new(target_client: Client<Millau>, target_sign: MillauSigningParams) -> Self {
Self {
finality_pipeline: FinalityPipelineRialtoFinalityToMillau::new(
target_client,
target_sign,
),
}
}
}
impl SubstrateFinalitySyncPipeline for RialtoFinalityToMillau {
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
type FinalitySyncPipeline = FinalityPipelineRialtoFinalityToMillau;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
type TargetChain = Millau;
fn transactions_author(&self) -> bp_millau::AccountId {
(*self.target_sign.public().as_array_ref()).into()
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
}
fn make_submit_finality_proof_transaction(
&self,
transaction_nonce: <Millau as Chain>::Index,
era: bp_runtime::TransactionEraOf<Millau>,
transaction_nonce: IndexOf<Millau>,
header: RialtoSyncHeader,
proof: GrandpaJustification<bp_rialto::Header>,
) -> Bytes {
let call = millau_runtime::BridgeGrandpaRialtoCall::<
let call = millau_runtime::BridgeGrandpaCall::<
millau_runtime::Runtime,
millau_runtime::RialtoGrandpaInstance,
>::submit_finality_proof(header.into_inner(), proof)
>::submit_finality_proof {
finality_target: Box::new(header.into_inner()),
justification: proof,
}
.into();
let genesis_hash = *self.target_client.genesis_hash();
let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
let transaction = Millau::sign_transaction(
genesis_hash,
&self.finality_pipeline.target_sign,
era,
UnsignedTransaction::new(call, transaction_nonce),
);
Bytes(transaction.encode())
}
@@ -16,64 +16,100 @@
//! Rialto-to-Millau messages sync entrypoint.
use crate::messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
};
use crate::messages_source::SubstrateMessagesSource;
use crate::messages_target::SubstrateMessagesTarget;
use std::ops::RangeInclusive;
use bp_messages::MessageNonce;
use bp_runtime::{MILLAU_CHAIN_ID, RIALTO_CHAIN_ID};
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use codec::Encode;
use frame_support::dispatch::GetDispatchInfo;
use messages_relay::message_lane::MessageLane;
use relay_millau_client::{HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams};
use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams};
use relay_substrate_client::{
metrics::{FloatStorageValueMetric, StorageProofOverheadMetric},
Chain, TransactionSignScheme,
};
use sp_core::{Bytes, Pair};
use std::{ops::RangeInclusive, time::Duration};
use bp_messages::MessageNonce;
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use frame_support::weights::Weight;
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
use relay_millau_client::{
HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams,
};
use relay_rialto_client::{
HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams,
};
use relay_substrate_client::{Chain, Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use substrate_relay_helper::{
messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
},
messages_source::SubstrateMessagesSource,
messages_target::SubstrateMessagesTarget,
STALL_TIMEOUT,
};
/// Rialto-to-Millau message lane.
pub type RialtoMessagesToMillau =
pub type MessageLaneRialtoMessagesToMillau =
SubstrateMessageLaneToSubstrate<Rialto, RialtoSigningParams, Millau, MillauSigningParams>;
#[derive(Clone)]
pub struct RialtoMessagesToMillau {
message_lane: MessageLaneRialtoMessagesToMillau,
}
impl SubstrateMessageLane for RialtoMessagesToMillau {
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str = bp_millau::TO_MILLAU_MESSAGE_DETAILS_METHOD;
type MessageLane = MessageLaneRialtoMessagesToMillau;
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str =
bp_millau::TO_MILLAU_MESSAGE_DETAILS_METHOD;
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
bp_millau::TO_MILLAU_LATEST_GENERATED_NONCE_METHOD;
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_millau::TO_MILLAU_LATEST_RECEIVED_NONCE_METHOD;
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_millau::TO_MILLAU_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_rialto::FROM_RIALTO_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_rialto::FROM_RIALTO_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
bp_rialto::FROM_RIALTO_LATEST_CONFIRMED_NONCE_METHOD;
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = bp_rialto::FROM_RIALTO_UNREWARDED_RELAYERS_STATE;
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str =
bp_rialto::FROM_RIALTO_UNREWARDED_RELAYERS_STATE;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str =
bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_rialto::WITH_MILLAU_MESSAGES_PALLET_NAME;
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_millau::WITH_RIALTO_MESSAGES_PALLET_NAME;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
bp_millau::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
type SourceChain = Rialto;
type TargetChain = Millau;
fn source_transactions_author(&self) -> bp_rialto::AccountId {
(*self.source_sign.public().as_array_ref()).into()
(*self.message_lane.source_sign.public().as_array_ref()).into()
}
fn make_messages_receiving_proof_transaction(
&self,
transaction_nonce: <Rialto as Chain>::Index,
best_block_id: RialtoHeaderId,
transaction_nonce: IndexOf<Rialto>,
_generated_at_block: MillauHeaderId,
proof: <Self as MessageLane>::MessagesReceivingProof,
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
) -> Bytes {
let (relayers_state, proof) = proof;
let call: rialto_runtime::Call =
rialto_runtime::MessagesCall::receive_messages_delivery_proof(proof, relayers_state).into();
rialto_runtime::MessagesCall::receive_messages_delivery_proof { proof, relayers_state }
.into();
let call_weight = call.get_dispatch_info().weight;
let genesis_hash = *self.source_client.genesis_hash();
let transaction = Rialto::sign_transaction(genesis_hash, &self.source_sign, transaction_nonce, call);
let genesis_hash = *self.message_lane.source_client.genesis_hash();
let transaction = Rialto::sign_transaction(
genesis_hash,
&self.message_lane.source_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.source_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Millau -> Rialto confirmation transaction. Weight: {}/{}, size: {}/{}",
@@ -86,33 +122,38 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
}
fn target_transactions_author(&self) -> bp_millau::AccountId {
(*self.target_sign.public().as_array_ref()).into()
(*self.message_lane.target_sign.public().as_array_ref()).into()
}
fn make_messages_delivery_transaction(
&self,
transaction_nonce: <Millau as Chain>::Index,
best_block_id: MillauHeaderId,
transaction_nonce: IndexOf<Millau>,
_generated_at_header: RialtoHeaderId,
_nonces: RangeInclusive<MessageNonce>,
proof: <Self as MessageLane>::MessagesProof,
proof: <Self::MessageLane as MessageLane>::MessagesProof,
) -> Bytes {
let (dispatch_weight, proof) = proof;
let FromBridgedChainMessagesProof {
ref nonces_start,
ref nonces_end,
..
} = proof;
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
let messages_count = nonces_end - nonces_start + 1;
let call: millau_runtime::Call = millau_runtime::MessagesCall::receive_messages_proof(
self.relayer_id_at_source.clone(),
let call: millau_runtime::Call = millau_runtime::MessagesCall::receive_messages_proof {
relayer_id_at_bridged_chain: self.message_lane.relayer_id_at_source.clone(),
proof,
messages_count as _,
messages_count: messages_count as _,
dispatch_weight,
)
}
.into();
let call_weight = call.get_dispatch_info().weight;
let genesis_hash = *self.target_client.genesis_hash();
let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
let genesis_hash = *self.message_lane.target_client.genesis_hash();
let transaction = Millau::sign_transaction(
genesis_hash,
&self.message_lane.target_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.target_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Rialto -> Millau delivery transaction. Weight: {}/{}, size: {}/{}",
@@ -126,34 +167,50 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
}
/// Rialto node as messages source.
type RialtoSourceClient =
SubstrateMessagesSource<Rialto, RialtoMessagesToMillau, rialto_runtime::WithMillauMessagesInstance>;
type RialtoSourceClient = SubstrateMessagesSource<RialtoMessagesToMillau>;
/// Millau node as messages target.
type MillauTargetClient =
SubstrateMessagesTarget<Millau, RialtoMessagesToMillau, millau_runtime::WithRialtoMessagesInstance>;
type MillauTargetClient = SubstrateMessagesTarget<RialtoMessagesToMillau>;
/// Run Rialto-to-Millau messages sync.
pub async fn run(
params: MessagesRelayParams<Rialto, RialtoSigningParams, Millau, MillauSigningParams>,
) -> Result<(), String> {
let stall_timeout = Duration::from_secs(5 * 60);
params: MessagesRelayParams<
Rialto,
RialtoSigningParams,
Millau,
MillauSigningParams,
MixStrategy,
>,
) -> anyhow::Result<()> {
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
params.source_transactions_mortality,
params.target_transactions_mortality,
Rialto::AVERAGE_BLOCK_INTERVAL,
Millau::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
);
let relayer_id_at_rialto = (*params.source_sign.public().as_array_ref()).into();
let lane_id = params.lane_id;
let source_client = params.source_client;
let lane = RialtoMessagesToMillau {
source_client: source_client.clone(),
source_sign: params.source_sign,
target_client: params.target_client.clone(),
target_sign: params.target_sign,
relayer_id_at_source: relayer_id_at_rialto,
message_lane: SubstrateMessageLaneToSubstrate {
source_client: source_client.clone(),
source_sign: params.source_sign,
source_transactions_mortality: params.source_transactions_mortality,
target_client: params.target_client.clone(),
target_sign: params.target_sign,
target_transactions_mortality: params.target_transactions_mortality,
relayer_id_at_source: relayer_id_at_rialto,
},
};
// 2/3 is reserved for proofs and tx overhead
let max_messages_size_in_single_batch = bp_millau::max_extrinsic_size() / 3;
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
select_delivery_transaction_limits::<pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>>(
select_delivery_transaction_limits::<
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>,
>(
bp_millau::max_extrinsic_weight(),
bp_millau::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
);
@@ -164,13 +221,25 @@ pub async fn run(
Rialto 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: {}",
lane.relayer_id_at_source,
Max messages weight in single transaction: {}\n\t\
Tx mortality: {:?}/{:?}\n\t\
Stall timeout: {:?}",
lane.message_lane.relayer_id_at_source,
max_messages_in_single_batch,
max_messages_size_in_single_batch,
max_messages_weight_in_single_batch,
params.source_transactions_mortality,
params.target_transactions_mortality,
stall_timeout,
);
let (metrics_params, metrics_values) = add_standalone_metrics(
Some(messages_relay::message_lane_loop::metrics_prefix::<
<RialtoMessagesToMillau as SubstrateMessageLane>::MessageLane,
>(&lane_id)),
params.metrics_params,
source_client.clone(),
)?;
messages_relay::message_lane_loop::run(
messages_relay::message_lane_loop::Params {
lane: lane_id,
@@ -179,58 +248,86 @@ pub async fn run(
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
stall_timeout,
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
max_unrewarded_relayer_entries_at_target: bp_millau::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
max_unconfirmed_nonces_at_target: bp_millau::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
max_unrewarded_relayer_entries_at_target:
bp_millau::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
max_unconfirmed_nonces_at_target:
bp_millau::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
max_messages_in_single_batch,
max_messages_weight_in_single_batch,
max_messages_size_in_single_batch,
relayer_mode: messages_relay::message_lane_loop::RelayerMode::Altruistic,
relay_strategy: params.relay_strategy,
},
},
RialtoSourceClient::new(
source_client.clone(),
lane.clone(),
lane_id,
MILLAU_CHAIN_ID,
params.target_to_source_headers_relay,
),
MillauTargetClient::new(
params.target_client,
lane,
lane_id,
RIALTO_CHAIN_ID,
metrics_values,
params.source_to_target_headers_relay,
),
relay_utils::relay_metrics(
Some(messages_relay::message_lane_loop::metrics_prefix::<
RialtoMessagesToMillau,
>(&lane_id)),
params.metrics_params,
)
.standalone_metric(|registry, prefix| {
StorageProofOverheadMetric::new(
registry,
prefix,
source_client.clone(),
"rialto_storage_proof_overhead".into(),
"Rialto storage proof overhead".into(),
)
})?
.standalone_metric(|registry, prefix| {
FloatStorageValueMetric::<_, sp_runtime::FixedU128>::new(
registry,
prefix,
source_client,
sp_core::storage::StorageKey(
rialto_runtime::millau_messages::MillauToRialtoConversionRate::key().to_vec(),
),
Some(rialto_runtime::millau_messages::INITIAL_MILLAU_TO_RIALTO_CONVERSION_RATE),
"rialto_millau_to_rialto_conversion_rate".into(),
"Millau to Rialto tokens conversion rate (used by Millau)".into(),
)
})?
.into_params(),
metrics_params,
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Add standalone metrics for the Rialto -> Millau messages loop.
pub(crate) fn add_standalone_metrics(
metrics_prefix: Option<String>,
metrics_params: MetricsParams,
source_client: Client<Rialto>,
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
substrate_relay_helper::messages_lane::add_standalone_metrics::<RialtoMessagesToMillau>(
metrics_prefix,
metrics_params,
source_client,
Some(crate::chains::RIALTO_ASSOCIATED_TOKEN_ID),
Some(crate::chains::MILLAU_ASSOCIATED_TOKEN_ID),
Some((
sp_core::storage::StorageKey(
rialto_runtime::millau_messages::MillauToRialtoConversionRate::key().to_vec(),
),
rialto_runtime::millau_messages::INITIAL_MILLAU_TO_RIALTO_CONVERSION_RATE,
)),
)
}
/// Update Millau -> Rialto conversion rate, stored in Rialto runtime storage.
pub(crate) async fn update_millau_to_rialto_conversion_rate(
client: Client<Rialto>,
signer: <Rialto as TransactionSignScheme>::AccountKeyPair,
updated_rate: f64,
) -> anyhow::Result<()> {
let genesis_hash = *client.genesis_hash();
let signer_id = (*signer.public().as_array_ref()).into();
client
.submit_signed_extrinsic(signer_id, move |_, transaction_nonce| {
Bytes(
Rialto::sign_transaction(
genesis_hash,
&signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
rialto_runtime::MessagesCall::update_pallet_parameter {
parameter: rialto_runtime::millau_messages::RialtoToMillauMessagesParameter::MillauToRialtoConversionRate(
sp_runtime::FixedU128::from_float(updated_rate),
),
}
.into(),
transaction_nonce,
),
)
.encode(),
)
})
.await
.map(drop)
.map_err(|err| anyhow::format_err!("{:?}", err))
}
@@ -0,0 +1,82 @@
// 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/>.
//! Rialto parachain specification for CLI.
use crate::cli::{
encode_call::{Call, CliEncodeCall},
encode_message, CliChain,
};
use bp_message_dispatch::MessagePayload;
use codec::Decode;
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
use relay_rialto_parachain_client::RialtoParachain;
use sp_version::RuntimeVersion;
impl CliEncodeCall for RialtoParachain {
fn max_extrinsic_size() -> u32 {
bp_rialto_parachain::max_extrinsic_size()
}
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
Ok(match call {
Call::Raw { data } => Decode::decode(&mut &*data.0)?,
Call::Remark { remark_payload, .. } => rialto_parachain_runtime::Call::System(
rialto_parachain_runtime::SystemCall::remark {
remark: remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
},
),
Call::Transfer { recipient, amount } => rialto_parachain_runtime::Call::Balances(
rialto_parachain_runtime::BalancesCall::transfer {
dest: recipient.raw_id().into(),
value: amount.0,
},
),
Call::BridgeSendMessage { .. } =>
anyhow::bail!("Bridge messages are not (yet) supported here",),
})
}
fn get_dispatch_info(call: &rialto_parachain_runtime::Call) -> anyhow::Result<DispatchInfo> {
Ok(call.get_dispatch_info())
}
}
impl CliChain for RialtoParachain {
const RUNTIME_VERSION: RuntimeVersion = rialto_parachain_runtime::VERSION;
type KeyPair = sp_core::sr25519::Pair;
type MessagePayload = MessagePayload<
bp_rialto_parachain::AccountId,
bp_millau::AccountSigner,
bp_millau::Signature,
Vec<u8>,
>;
fn ss58_format() -> u16 {
rialto_parachain_runtime::SS58Prefix::get() as u16
}
fn max_extrinsic_weight() -> Weight {
bp_rialto_parachain::max_extrinsic_weight()
}
fn encode_message(
_message: encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload> {
anyhow::bail!("Not supported")
}
}
@@ -14,6 +14,7 @@
// 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/>.
use anyhow::anyhow;
use codec::Decode;
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
use relay_rococo_client::Rococo;
@@ -27,7 +28,7 @@ use crate::cli::{
/// Weight of the `system::remark` call at Rococo.
///
/// This weight is larger (x2) than actual weight at current Rooco runtime to avoid unsuccessful
/// This weight is larger (x2) than actual weight at current Rococo runtime to avoid unsuccessful
/// calls in the future. But since it is used only in tests (and on test chains), this is ok.
pub(crate) const SYSTEM_REMARK_CALL_WEIGHT: Weight = 2 * 1_345_000;
@@ -38,41 +39,41 @@ impl CliEncodeCall for Rococo {
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
Ok(match call {
Call::Remark { remark_payload, .. } => {
relay_rococo_client::runtime::Call::System(relay_rococo_client::runtime::SystemCall::remark(
Call::Remark { remark_payload, .. } => relay_rococo_client::runtime::Call::System(
relay_rococo_client::runtime::SystemCall::remark(
remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
))
}
Call::BridgeSendMessage {
lane,
payload,
fee,
bridge_instance_index,
} => match *bridge_instance_index {
bridge::ROCOCO_TO_WOCOCO_INDEX => {
let payload = Decode::decode(&mut &*payload.0)?;
relay_rococo_client::runtime::Call::BridgeMessagesWococo(
relay_rococo_client::runtime::BridgeMessagesWococoCall::send_message(lane.0, payload, fee.0),
)
}
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
bridge_instance_index
),
},
),
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
match *bridge_instance_index {
bridge::ROCOCO_TO_WOCOCO_INDEX => {
let payload = Decode::decode(&mut &*payload.0)?;
relay_rococo_client::runtime::Call::BridgeMessagesWococo(
relay_rococo_client::runtime::BridgeMessagesWococoCall::send_message(
lane.0, payload, fee.0,
),
)
},
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
bridge_instance_index
),
},
_ => anyhow::bail!("The call is not supported"),
})
}
fn get_dispatch_info(call: &relay_rococo_client::runtime::Call) -> anyhow::Result<DispatchInfo> {
fn get_dispatch_info(
call: &relay_rococo_client::runtime::Call,
) -> anyhow::Result<DispatchInfo> {
match *call {
relay_rococo_client::runtime::Call::System(relay_rococo_client::runtime::SystemCall::remark(_)) => {
Ok(DispatchInfo {
weight: SYSTEM_REMARK_CALL_WEIGHT,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
})
}
relay_rococo_client::runtime::Call::System(
relay_rococo_client::runtime::SystemCall::remark(_),
) => Ok(DispatchInfo {
weight: SYSTEM_REMARK_CALL_WEIGHT,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
}),
_ => anyhow::bail!("Unsupported Rococo call: {:?}", call),
}
}
@@ -92,7 +93,9 @@ impl CliChain for Rococo {
bp_wococo::max_extrinsic_weight()
}
fn encode_message(_message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
Err("Sending messages from Rococo is not yet supported.".into())
fn encode_message(
_message: encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload> {
Err(anyhow!("Sending messages from Rococo is not yet supported."))
}
}
@@ -16,56 +16,91 @@
//! Rococo-to-Wococo headers sync entrypoint.
use crate::chains::wococo_headers_to_rococo::MAXIMAL_BALANCE_DECREASE_PER_DAY;
use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
use bp_header_chain::justification::GrandpaJustification;
use codec::Encode;
use relay_rococo_client::{Rococo, SyncHeader as RococoSyncHeader};
use relay_substrate_client::{Chain, TransactionSignScheme};
use relay_utils::metrics::MetricsParams;
use relay_wococo_client::{SigningParams as WococoSigningParams, Wococo};
use sp_core::{Bytes, Pair};
use bp_header_chain::justification::GrandpaJustification;
use relay_rococo_client::{Rococo, SyncHeader as RococoSyncHeader};
use relay_substrate_client::{Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use relay_wococo_client::{SigningParams as WococoSigningParams, Wococo};
use substrate_relay_helper::finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
};
use crate::chains::wococo_headers_to_rococo::MAXIMAL_BALANCE_DECREASE_PER_DAY;
/// Rococo-to-Wococo finality sync pipeline.
pub(crate) type RococoFinalityToWococo = SubstrateFinalityToSubstrate<Rococo, Wococo, WococoSigningParams>;
pub(crate) type FinalityPipelineRococoFinalityToWococo =
SubstrateFinalityToSubstrate<Rococo, Wococo, WococoSigningParams>;
#[derive(Clone, Debug)]
pub(crate) struct RococoFinalityToWococo {
finality_pipeline: FinalityPipelineRococoFinalityToWococo,
}
impl RococoFinalityToWococo {
pub fn new(target_client: Client<Wococo>, target_sign: WococoSigningParams) -> Self {
Self {
finality_pipeline: FinalityPipelineRococoFinalityToWococo::new(
target_client,
target_sign,
),
}
}
}
impl SubstrateFinalitySyncPipeline for RococoFinalityToWococo {
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_rococo::BEST_FINALIZED_ROCOCO_HEADER_METHOD;
type FinalitySyncPipeline = FinalityPipelineRococoFinalityToWococo;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_rococo::BEST_FINALIZED_ROCOCO_HEADER_METHOD;
type TargetChain = Wococo;
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
crate::chains::add_polkadot_kusama_price_metrics::<Self>(params)
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(
Some(finality_relay::metrics_prefix::<Self::FinalitySyncPipeline>()),
params,
)
}
fn start_relay_guards(&self) {
relay_substrate_client::guard::abort_on_spec_version_change(
self.target_client.clone(),
self.finality_pipeline.target_client.clone(),
bp_wococo::VERSION.spec_version,
);
relay_substrate_client::guard::abort_when_account_balance_decreased(
self.target_client.clone(),
self.finality_pipeline.target_client.clone(),
self.transactions_author(),
MAXIMAL_BALANCE_DECREASE_PER_DAY,
);
}
fn transactions_author(&self) -> bp_wococo::AccountId {
(*self.target_sign.public().as_array_ref()).into()
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
}
fn make_submit_finality_proof_transaction(
&self,
transaction_nonce: <Wococo as Chain>::Index,
era: bp_runtime::TransactionEraOf<Wococo>,
transaction_nonce: IndexOf<Wococo>,
header: RococoSyncHeader,
proof: GrandpaJustification<bp_rococo::Header>,
) -> Bytes {
let call = relay_wococo_client::runtime::Call::BridgeGrandpaRococo(
relay_wococo_client::runtime::BridgeGrandpaRococoCall::submit_finality_proof(header.into_inner(), proof),
relay_wococo_client::runtime::BridgeGrandpaRococoCall::submit_finality_proof(
Box::new(header.into_inner()),
proof,
),
);
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
let transaction = Wococo::sign_transaction(
genesis_hash,
&self.finality_pipeline.target_sign,
era,
UnsignedTransaction::new(call, transaction_nonce),
);
let genesis_hash = *self.target_client.genesis_hash();
let transaction = Wococo::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
Bytes(transaction.encode())
}
@@ -16,53 +16,83 @@
//! Rococo-to-Wococo messages sync entrypoint.
use crate::messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
};
use crate::messages_source::SubstrateMessagesSource;
use crate::messages_target::SubstrateMessagesTarget;
use std::ops::RangeInclusive;
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_messages::MessageNonce;
use bp_runtime::{ROCOCO_CHAIN_ID, WOCOCO_CHAIN_ID};
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use codec::Encode;
use messages_relay::message_lane::MessageLane;
use relay_rococo_client::{HeaderId as RococoHeaderId, Rococo, SigningParams as RococoSigningParams};
use relay_substrate_client::{metrics::StorageProofOverheadMetric, Chain, TransactionSignScheme};
use relay_wococo_client::{HeaderId as WococoHeaderId, SigningParams as WococoSigningParams, Wococo};
use sp_core::{Bytes, Pair};
use std::{ops::RangeInclusive, time::Duration};
use frame_support::weights::Weight;
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
use relay_rococo_client::{
HeaderId as RococoHeaderId, Rococo, SigningParams as RococoSigningParams,
};
use relay_substrate_client::{Chain, Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use relay_wococo_client::{
HeaderId as WococoHeaderId, SigningParams as WococoSigningParams, Wococo,
};
use substrate_relay_helper::{
messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
},
messages_source::SubstrateMessagesSource,
messages_target::SubstrateMessagesTarget,
STALL_TIMEOUT,
};
/// Rococo-to-Wococo message lane.
pub type RococoMessagesToWococo =
pub type MessageLaneRococoMessagesToWococo =
SubstrateMessageLaneToSubstrate<Rococo, RococoSigningParams, Wococo, WococoSigningParams>;
#[derive(Clone)]
pub struct RococoMessagesToWococo {
message_lane: MessageLaneRococoMessagesToWococo,
}
impl SubstrateMessageLane for RococoMessagesToWococo {
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str = bp_wococo::TO_WOCOCO_MESSAGE_DETAILS_METHOD;
type MessageLane = MessageLaneRococoMessagesToWococo;
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str =
bp_wococo::TO_WOCOCO_MESSAGE_DETAILS_METHOD;
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
bp_wococo::TO_WOCOCO_LATEST_GENERATED_NONCE_METHOD;
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_wococo::TO_WOCOCO_LATEST_RECEIVED_NONCE_METHOD;
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_wococo::TO_WOCOCO_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_rococo::FROM_ROCOCO_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_rococo::FROM_ROCOCO_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
bp_rococo::FROM_ROCOCO_LATEST_CONFIRMED_NONCE_METHOD;
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = bp_rococo::FROM_ROCOCO_UNREWARDED_RELAYERS_STATE;
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str =
bp_rococo::FROM_ROCOCO_UNREWARDED_RELAYERS_STATE;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_rococo::BEST_FINALIZED_ROCOCO_HEADER_METHOD;
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_wococo::BEST_FINALIZED_WOCOCO_HEADER_METHOD;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_rococo::BEST_FINALIZED_ROCOCO_HEADER_METHOD;
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str =
bp_wococo::BEST_FINALIZED_WOCOCO_HEADER_METHOD;
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_rococo::WITH_WOCOCO_MESSAGES_PALLET_NAME;
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_wococo::WITH_ROCOCO_MESSAGES_PALLET_NAME;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
bp_wococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
type SourceChain = Rococo;
type TargetChain = Wococo;
fn source_transactions_author(&self) -> bp_rococo::AccountId {
(*self.source_sign.public().as_array_ref()).into()
(*self.message_lane.source_sign.public().as_array_ref()).into()
}
fn make_messages_receiving_proof_transaction(
&self,
transaction_nonce: <Rococo as Chain>::Index,
best_block_id: RococoHeaderId,
transaction_nonce: IndexOf<Rococo>,
_generated_at_block: WococoHeaderId,
proof: <Self as MessageLane>::MessagesReceivingProof,
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
) -> Bytes {
let (relayers_state, proof) = proof;
let call = relay_rococo_client::runtime::Call::BridgeMessagesWococo(
@@ -71,8 +101,16 @@ impl SubstrateMessageLane for RococoMessagesToWococo {
relayers_state,
),
);
let genesis_hash = *self.source_client.genesis_hash();
let transaction = Rococo::sign_transaction(genesis_hash, &self.source_sign, transaction_nonce, call);
let genesis_hash = *self.message_lane.source_client.genesis_hash();
let transaction = Rococo::sign_transaction(
genesis_hash,
&self.message_lane.source_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.source_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Wococo -> Rococo confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
@@ -84,34 +122,39 @@ impl SubstrateMessageLane for RococoMessagesToWococo {
}
fn target_transactions_author(&self) -> bp_wococo::AccountId {
(*self.target_sign.public().as_array_ref()).into()
(*self.message_lane.target_sign.public().as_array_ref()).into()
}
fn make_messages_delivery_transaction(
&self,
transaction_nonce: <Wococo as Chain>::Index,
best_block_id: WococoHeaderId,
transaction_nonce: IndexOf<Wococo>,
_generated_at_header: RococoHeaderId,
_nonces: RangeInclusive<MessageNonce>,
proof: <Self as MessageLane>::MessagesProof,
proof: <Self::MessageLane as MessageLane>::MessagesProof,
) -> Bytes {
let (dispatch_weight, proof) = proof;
let FromBridgedChainMessagesProof {
ref nonces_start,
ref nonces_end,
..
} = proof;
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
let messages_count = nonces_end - nonces_start + 1;
let call = relay_wococo_client::runtime::Call::BridgeMessagesRococo(
relay_wococo_client::runtime::BridgeMessagesRococoCall::receive_messages_proof(
self.relayer_id_at_source.clone(),
self.message_lane.relayer_id_at_source.clone(),
proof,
messages_count as _,
dispatch_weight,
),
);
let genesis_hash = *self.target_client.genesis_hash();
let transaction = Wococo::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
let genesis_hash = *self.message_lane.target_client.genesis_hash();
let transaction = Wococo::sign_transaction(
genesis_hash,
&self.message_lane.target_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.target_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Rococo -> Wococo delivery transaction. Weight: <unknown>/{}, size: {}/{}",
@@ -124,28 +167,42 @@ impl SubstrateMessageLane for RococoMessagesToWococo {
}
/// Rococo node as messages source.
type RococoSourceClient =
SubstrateMessagesSource<Rococo, RococoMessagesToWococo, relay_rococo_client::runtime::WithWococoMessagesInstance>;
type RococoSourceClient = SubstrateMessagesSource<RococoMessagesToWococo>;
/// Wococo node as messages target.
type WococoTargetClient =
SubstrateMessagesTarget<Wococo, RococoMessagesToWococo, relay_wococo_client::runtime::WithRococoMessagesInstance>;
type WococoTargetClient = SubstrateMessagesTarget<RococoMessagesToWococo>;
/// Run Rococo-to-Wococo messages sync.
pub async fn run(
params: MessagesRelayParams<Rococo, RococoSigningParams, Wococo, WococoSigningParams>,
) -> Result<(), String> {
let stall_timeout = Duration::from_secs(5 * 60);
params: MessagesRelayParams<
Rococo,
RococoSigningParams,
Wococo,
WococoSigningParams,
MixStrategy,
>,
) -> anyhow::Result<()> {
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
params.source_transactions_mortality,
params.target_transactions_mortality,
Rococo::AVERAGE_BLOCK_INTERVAL,
Wococo::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
);
let relayer_id_at_rococo = (*params.source_sign.public().as_array_ref()).into();
let lane_id = params.lane_id;
let source_client = params.source_client;
let lane = RococoMessagesToWococo {
source_client: source_client.clone(),
source_sign: params.source_sign,
target_client: params.target_client.clone(),
target_sign: params.target_sign,
relayer_id_at_source: relayer_id_at_rococo,
message_lane: SubstrateMessageLaneToSubstrate {
source_client: source_client.clone(),
source_sign: params.source_sign,
source_transactions_mortality: params.source_transactions_mortality,
target_client: params.target_client.clone(),
target_sign: params.target_sign,
target_transactions_mortality: params.target_transactions_mortality,
relayer_id_at_source: relayer_id_at_rococo,
},
};
// 2/3 is reserved for proofs and tx overhead
@@ -153,14 +210,14 @@ pub async fn run(
// we don't know exact weights of the Wococo 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) =
select_delivery_transaction_limits::<pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>>(
select_delivery_transaction_limits::<
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>,
>(
bp_wococo::max_extrinsic_weight(),
bp_wococo::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
);
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 (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
(max_messages_in_single_batch / 2, max_messages_weight_in_single_batch / 2);
log::info!(
target: "bridge",
@@ -168,13 +225,25 @@ pub async fn run(
Rococo 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: {}",
lane.relayer_id_at_source,
Max messages weight in single transaction: {}\n\t\
Tx mortality: {:?}/{:?}\n\t\
Stall timeout: {:?}",
lane.message_lane.relayer_id_at_source,
max_messages_in_single_batch,
max_messages_size_in_single_batch,
max_messages_weight_in_single_batch,
params.source_transactions_mortality,
params.target_transactions_mortality,
stall_timeout,
);
let (metrics_params, metrics_values) = add_standalone_metrics(
Some(messages_relay::message_lane_loop::metrics_prefix::<
<RococoMessagesToWococo as SubstrateMessageLane>::MessageLane,
>(&lane_id)),
params.metrics_params,
source_client.clone(),
)?;
messages_relay::message_lane_loop::run(
messages_relay::message_lane_loop::Params {
lane: lane_id,
@@ -183,45 +252,48 @@ pub async fn run(
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
stall_timeout,
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
max_unrewarded_relayer_entries_at_target: bp_wococo::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
max_unconfirmed_nonces_at_target: bp_wococo::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
max_unrewarded_relayer_entries_at_target:
bp_wococo::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
max_unconfirmed_nonces_at_target:
bp_wococo::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
max_messages_in_single_batch,
max_messages_weight_in_single_batch,
max_messages_size_in_single_batch,
relayer_mode: messages_relay::message_lane_loop::RelayerMode::Altruistic,
relay_strategy: params.relay_strategy,
},
},
RococoSourceClient::new(
source_client.clone(),
lane.clone(),
lane_id,
WOCOCO_CHAIN_ID,
params.target_to_source_headers_relay,
),
WococoTargetClient::new(
params.target_client,
lane,
lane_id,
ROCOCO_CHAIN_ID,
metrics_values,
params.source_to_target_headers_relay,
),
relay_utils::relay_metrics(
Some(messages_relay::message_lane_loop::metrics_prefix::<
RococoMessagesToWococo,
>(&lane_id)),
params.metrics_params,
)
.standalone_metric(|registry, prefix| {
StorageProofOverheadMetric::new(
registry,
prefix,
source_client.clone(),
"rococo_storage_proof_overhead".into(),
"Rococo storage proof overhead".into(),
)
})?
.into_params(),
metrics_params,
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Add standalone metrics for the Rococo -> Wococo messages loop.
pub(crate) fn add_standalone_metrics(
metrics_prefix: Option<String>,
metrics_params: MetricsParams,
source_client: Client<Rococo>,
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
substrate_relay_helper::messages_lane::add_standalone_metrics::<RococoMessagesToWococo>(
metrics_prefix,
metrics_params,
source_client,
None,
None,
None,
)
}
@@ -17,6 +17,7 @@
//! Westend chain specification for CLI.
use crate::cli::{encode_message, CliChain};
use anyhow::anyhow;
use frame_support::weights::Weight;
use relay_westend_client::Westend;
use sp_version::RuntimeVersion;
@@ -35,7 +36,9 @@ impl CliChain for Westend {
0
}
fn encode_message(_message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
Err("Sending messages from Westend is not yet supported.".into())
fn encode_message(
_message: encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload> {
Err(anyhow!("Sending messages from Westend is not yet supported."))
}
}
@@ -16,46 +16,80 @@
//! Westend-to-Millau headers sync entrypoint.
use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
use bp_header_chain::justification::GrandpaJustification;
use codec::Encode;
use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
use relay_substrate_client::{Chain, TransactionSignScheme};
use relay_utils::metrics::MetricsParams;
use relay_westend_client::{SyncHeader as WestendSyncHeader, Westend};
use sp_core::{Bytes, Pair};
use bp_header_chain::justification::GrandpaJustification;
use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
use relay_substrate_client::{Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use relay_westend_client::{SyncHeader as WestendSyncHeader, Westend};
use substrate_relay_helper::finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
};
/// Westend-to-Millau finality sync pipeline.
pub(crate) type WestendFinalityToMillau = SubstrateFinalityToSubstrate<Westend, Millau, MillauSigningParams>;
pub(crate) type FinalityPipelineWestendFinalityToMillau =
SubstrateFinalityToSubstrate<Westend, Millau, MillauSigningParams>;
#[derive(Clone, Debug)]
pub(crate) struct WestendFinalityToMillau {
finality_pipeline: FinalityPipelineWestendFinalityToMillau,
}
impl WestendFinalityToMillau {
pub fn new(target_client: Client<Millau>, target_sign: MillauSigningParams) -> Self {
Self {
finality_pipeline: FinalityPipelineWestendFinalityToMillau::new(
target_client,
target_sign,
),
}
}
}
impl SubstrateFinalitySyncPipeline for WestendFinalityToMillau {
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_westend::BEST_FINALIZED_WESTEND_HEADER_METHOD;
type FinalitySyncPipeline = FinalityPipelineWestendFinalityToMillau;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_westend::BEST_FINALIZED_WESTEND_HEADER_METHOD;
type TargetChain = Millau;
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
crate::chains::add_polkadot_kusama_price_metrics::<Self>(params)
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(
Some(finality_relay::metrics_prefix::<Self::FinalitySyncPipeline>()),
params,
)
}
fn transactions_author(&self) -> bp_millau::AccountId {
(*self.target_sign.public().as_array_ref()).into()
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
}
fn make_submit_finality_proof_transaction(
&self,
transaction_nonce: <Millau as Chain>::Index,
era: bp_runtime::TransactionEraOf<Millau>,
transaction_nonce: IndexOf<Millau>,
header: WestendSyncHeader,
proof: GrandpaJustification<bp_westend::Header>,
) -> Bytes {
let call = millau_runtime::BridgeGrandpaWestendCall::<
let call = millau_runtime::BridgeGrandpaCall::<
millau_runtime::Runtime,
millau_runtime::WestendGrandpaInstance,
>::submit_finality_proof(header.into_inner(), proof)
>::submit_finality_proof {
finality_target: Box::new(header.into_inner()),
justification: proof,
}
.into();
let genesis_hash = *self.target_client.genesis_hash();
let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
let transaction = Millau::sign_transaction(
genesis_hash,
&self.finality_pipeline.target_sign,
era,
UnsignedTransaction::new(call, transaction_nonce),
);
Bytes(transaction.encode())
}
@@ -14,6 +14,7 @@
// 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/>.
use anyhow::anyhow;
use codec::Decode;
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
use relay_wococo_client::Wococo;
@@ -32,41 +33,41 @@ impl CliEncodeCall for Wococo {
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
Ok(match call {
Call::Remark { remark_payload, .. } => {
relay_wococo_client::runtime::Call::System(relay_wococo_client::runtime::SystemCall::remark(
Call::Remark { remark_payload, .. } => relay_wococo_client::runtime::Call::System(
relay_wococo_client::runtime::SystemCall::remark(
remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
))
}
Call::BridgeSendMessage {
lane,
payload,
fee,
bridge_instance_index,
} => match *bridge_instance_index {
bridge::WOCOCO_TO_ROCOCO_INDEX => {
let payload = Decode::decode(&mut &*payload.0)?;
relay_wococo_client::runtime::Call::BridgeMessagesRococo(
relay_wococo_client::runtime::BridgeMessagesRococoCall::send_message(lane.0, payload, fee.0),
)
}
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
bridge_instance_index
),
},
),
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
match *bridge_instance_index {
bridge::WOCOCO_TO_ROCOCO_INDEX => {
let payload = Decode::decode(&mut &*payload.0)?;
relay_wococo_client::runtime::Call::BridgeMessagesRococo(
relay_wococo_client::runtime::BridgeMessagesRococoCall::send_message(
lane.0, payload, fee.0,
),
)
},
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
bridge_instance_index
),
},
_ => anyhow::bail!("The call is not supported"),
})
}
fn get_dispatch_info(call: &relay_wococo_client::runtime::Call) -> anyhow::Result<DispatchInfo> {
fn get_dispatch_info(
call: &relay_wococo_client::runtime::Call,
) -> anyhow::Result<DispatchInfo> {
match *call {
relay_wococo_client::runtime::Call::System(relay_wococo_client::runtime::SystemCall::remark(_)) => {
Ok(DispatchInfo {
weight: crate::chains::rococo::SYSTEM_REMARK_CALL_WEIGHT,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
})
}
relay_wococo_client::runtime::Call::System(
relay_wococo_client::runtime::SystemCall::remark(_),
) => Ok(DispatchInfo {
weight: crate::chains::rococo::SYSTEM_REMARK_CALL_WEIGHT,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
}),
_ => anyhow::bail!("Unsupported Rococo call: {:?}", call),
}
}
@@ -86,7 +87,9 @@ impl CliChain for Wococo {
bp_wococo::max_extrinsic_weight()
}
fn encode_message(_message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
Err("Sending messages from Wococo is not yet supported.".into())
fn encode_message(
_message: encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload> {
Err(anyhow!("Sending messages from Wococo is not yet supported."))
}
}
@@ -16,15 +16,17 @@
//! Wococo-to-Rococo headers sync entrypoint.
use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_header_chain::justification::GrandpaJustification;
use codec::Encode;
use relay_rococo_client::{Rococo, SigningParams as RococoSigningParams};
use relay_substrate_client::{Chain, TransactionSignScheme};
use relay_substrate_client::{Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use relay_wococo_client::{SyncHeader as WococoSyncHeader, Wococo};
use sp_core::{Bytes, Pair};
use substrate_relay_helper::finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
};
/// Maximal saturating difference between `balance(now)` and `balance(now-24h)` to treat
/// relay as gone wild.
@@ -34,44 +36,76 @@ use sp_core::{Bytes, Pair};
pub(crate) const MAXIMAL_BALANCE_DECREASE_PER_DAY: bp_rococo::Balance = 1_500_000_000_000_000;
/// Wococo-to-Rococo finality sync pipeline.
pub(crate) type WococoFinalityToRococo = SubstrateFinalityToSubstrate<Wococo, Rococo, RococoSigningParams>;
pub(crate) type FinalityPipelineWococoFinalityToRococo =
SubstrateFinalityToSubstrate<Wococo, Rococo, RococoSigningParams>;
#[derive(Clone, Debug)]
pub(crate) struct WococoFinalityToRococo {
finality_pipeline: FinalityPipelineWococoFinalityToRococo,
}
impl WococoFinalityToRococo {
pub fn new(target_client: Client<Rococo>, target_sign: RococoSigningParams) -> Self {
Self {
finality_pipeline: FinalityPipelineWococoFinalityToRococo::new(
target_client,
target_sign,
),
}
}
}
impl SubstrateFinalitySyncPipeline for WococoFinalityToRococo {
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_wococo::BEST_FINALIZED_WOCOCO_HEADER_METHOD;
type FinalitySyncPipeline = FinalityPipelineWococoFinalityToRococo;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_wococo::BEST_FINALIZED_WOCOCO_HEADER_METHOD;
type TargetChain = Rococo;
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
crate::chains::add_polkadot_kusama_price_metrics::<Self>(params)
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(
Some(finality_relay::metrics_prefix::<Self::FinalitySyncPipeline>()),
params,
)
}
fn start_relay_guards(&self) {
relay_substrate_client::guard::abort_on_spec_version_change(
self.target_client.clone(),
self.finality_pipeline.target_client.clone(),
bp_rococo::VERSION.spec_version,
);
relay_substrate_client::guard::abort_when_account_balance_decreased(
self.target_client.clone(),
self.finality_pipeline.target_client.clone(),
self.transactions_author(),
MAXIMAL_BALANCE_DECREASE_PER_DAY,
);
}
fn transactions_author(&self) -> bp_rococo::AccountId {
(*self.target_sign.public().as_array_ref()).into()
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
}
fn make_submit_finality_proof_transaction(
&self,
transaction_nonce: <Rococo as Chain>::Index,
era: bp_runtime::TransactionEraOf<Rococo>,
transaction_nonce: IndexOf<Rococo>,
header: WococoSyncHeader,
proof: GrandpaJustification<bp_wococo::Header>,
) -> Bytes {
let call = relay_rococo_client::runtime::Call::BridgeGrandpaWococo(
relay_rococo_client::runtime::BridgeGrandpaWococoCall::submit_finality_proof(header.into_inner(), proof),
relay_rococo_client::runtime::BridgeGrandpaWococoCall::submit_finality_proof(
Box::new(header.into_inner()),
proof,
),
);
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
let transaction = Rococo::sign_transaction(
genesis_hash,
&self.finality_pipeline.target_sign,
era,
UnsignedTransaction::new(call, transaction_nonce),
);
let genesis_hash = *self.target_client.genesis_hash();
let transaction = Rococo::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
Bytes(transaction.encode())
}
@@ -80,36 +114,19 @@ impl SubstrateFinalitySyncPipeline for WococoFinalityToRococo {
#[cfg(test)]
mod tests {
use super::*;
use frame_support::weights::WeightToFeePolynomial;
use pallet_bridge_grandpa::weights::WeightInfo;
use crate::chains::kusama_headers_to_polkadot::tests::compute_maximal_balance_decrease_per_day;
#[test]
fn maximal_balance_decrease_per_day_is_sane() {
// Rococo/Wococo GRANDPA pallet weights. They're now using Rialto weights => using `RialtoWeight` is justified.
//
// Using Rialto runtime this is slightly incorrect, because `DbWeight` of Rococo/Wococo runtime may differ
// from the `DbWeight` of Rialto runtime. But now (and most probably forever) it is the same.
type RococoGrandpaPalletWeights = pallet_bridge_grandpa::weights::RialtoWeight<rialto_runtime::Runtime>;
// The following formula shall not be treated as super-accurate - guard is to protect from mad relays,
// not to protect from over-average loses.
//
// Worst case: we're submitting proof for every source header. Since we submit every header, the number of
// headers in ancestry proof is near to 0 (let's round up to 2). And the number of authorities is 1024,
// which is (now) larger than on any existing chain => normally there'll be ~1024*2/3+1 commits.
const AVG_VOTES_ANCESTRIES_LEN: u32 = 2;
const AVG_PRECOMMITS_LEN: u32 = 1024 * 2 / 3 + 1;
let number_of_source_headers_per_day: bp_wococo::Balance = bp_wococo::DAYS as _;
let single_source_header_submit_call_weight =
RococoGrandpaPalletWeights::submit_finality_proof(AVG_VOTES_ANCESTRIES_LEN, AVG_PRECOMMITS_LEN);
// for simplicity - add extra weight for base tx fee + fee that is paid for the tx size + adjusted fee
let single_source_header_submit_tx_weight = single_source_header_submit_call_weight * 3 / 2;
let single_source_header_tx_cost = bp_rococo::WeightToFee::calc(&single_source_header_submit_tx_weight);
let maximal_expected_decrease = single_source_header_tx_cost * number_of_source_headers_per_day;
// we expect Wococo -> Rococo relay to be running in all-headers mode
let maximal_balance_decrease = compute_maximal_balance_decrease_per_day::<
bp_kusama::Balance,
bp_kusama::WeightToFee,
>(bp_wococo::DAYS);
assert!(
MAXIMAL_BALANCE_DECREASE_PER_DAY >= maximal_expected_decrease,
MAXIMAL_BALANCE_DECREASE_PER_DAY >= maximal_balance_decrease,
"Maximal expected loss per day {} is larger than hardcoded {}",
maximal_expected_decrease,
maximal_balance_decrease,
MAXIMAL_BALANCE_DECREASE_PER_DAY,
);
}
@@ -16,53 +16,82 @@
//! Wococo-to-Rococo messages sync entrypoint.
use crate::messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
};
use crate::messages_source::SubstrateMessagesSource;
use crate::messages_target::SubstrateMessagesTarget;
use std::ops::RangeInclusive;
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_messages::MessageNonce;
use bp_runtime::{ROCOCO_CHAIN_ID, WOCOCO_CHAIN_ID};
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use codec::Encode;
use messages_relay::message_lane::MessageLane;
use relay_rococo_client::{HeaderId as RococoHeaderId, Rococo, SigningParams as RococoSigningParams};
use relay_substrate_client::{metrics::StorageProofOverheadMetric, Chain, TransactionSignScheme};
use relay_wococo_client::{HeaderId as WococoHeaderId, SigningParams as WococoSigningParams, Wococo};
use sp_core::{Bytes, Pair};
use std::{ops::RangeInclusive, time::Duration};
use frame_support::weights::Weight;
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
use relay_rococo_client::{
HeaderId as RococoHeaderId, Rococo, SigningParams as RococoSigningParams,
};
use relay_substrate_client::{Chain, Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use relay_wococo_client::{
HeaderId as WococoHeaderId, SigningParams as WococoSigningParams, Wococo,
};
use substrate_relay_helper::{
messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
},
messages_source::SubstrateMessagesSource,
messages_target::SubstrateMessagesTarget,
STALL_TIMEOUT,
};
/// Wococo-to-Rococo message lane.
pub type WococoMessagesToRococo =
pub type MessageLaneWococoMessagesToRococo =
SubstrateMessageLaneToSubstrate<Wococo, WococoSigningParams, Rococo, RococoSigningParams>;
#[derive(Clone)]
pub struct WococoMessagesToRococo {
message_lane: MessageLaneWococoMessagesToRococo,
}
impl SubstrateMessageLane for WococoMessagesToRococo {
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str = bp_rococo::TO_ROCOCO_MESSAGE_DETAILS_METHOD;
type MessageLane = MessageLaneWococoMessagesToRococo;
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str =
bp_rococo::TO_ROCOCO_MESSAGE_DETAILS_METHOD;
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
bp_rococo::TO_ROCOCO_LATEST_GENERATED_NONCE_METHOD;
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_rococo::TO_ROCOCO_LATEST_RECEIVED_NONCE_METHOD;
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_rococo::TO_ROCOCO_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_wococo::FROM_WOCOCO_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_wococo::FROM_WOCOCO_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
bp_wococo::FROM_WOCOCO_LATEST_CONFIRMED_NONCE_METHOD;
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = bp_wococo::FROM_WOCOCO_UNREWARDED_RELAYERS_STATE;
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str =
bp_wococo::FROM_WOCOCO_UNREWARDED_RELAYERS_STATE;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_wococo::BEST_FINALIZED_WOCOCO_HEADER_METHOD;
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_rococo::BEST_FINALIZED_ROCOCO_HEADER_METHOD;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_wococo::BEST_FINALIZED_WOCOCO_HEADER_METHOD;
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str =
bp_rococo::BEST_FINALIZED_ROCOCO_HEADER_METHOD;
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_wococo::WITH_ROCOCO_MESSAGES_PALLET_NAME;
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_rococo::WITH_WOCOCO_MESSAGES_PALLET_NAME;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
bp_rococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
type SourceChain = Wococo;
type TargetChain = Rococo;
fn source_transactions_author(&self) -> bp_wococo::AccountId {
(*self.source_sign.public().as_array_ref()).into()
(*self.message_lane.source_sign.public().as_array_ref()).into()
}
fn make_messages_receiving_proof_transaction(
&self,
transaction_nonce: <Wococo as Chain>::Index,
best_block_id: WococoHeaderId,
transaction_nonce: IndexOf<Wococo>,
_generated_at_block: RococoHeaderId,
proof: <Self as MessageLane>::MessagesReceivingProof,
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
) -> Bytes {
let (relayers_state, proof) = proof;
let call = relay_wococo_client::runtime::Call::BridgeMessagesRococo(
@@ -71,8 +100,16 @@ impl SubstrateMessageLane for WococoMessagesToRococo {
relayers_state,
),
);
let genesis_hash = *self.source_client.genesis_hash();
let transaction = Wococo::sign_transaction(genesis_hash, &self.source_sign, transaction_nonce, call);
let genesis_hash = *self.message_lane.source_client.genesis_hash();
let transaction = Wococo::sign_transaction(
genesis_hash,
&self.message_lane.source_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.source_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Rococo -> Wococo confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
@@ -84,34 +121,39 @@ impl SubstrateMessageLane for WococoMessagesToRococo {
}
fn target_transactions_author(&self) -> bp_rococo::AccountId {
(*self.target_sign.public().as_array_ref()).into()
(*self.message_lane.target_sign.public().as_array_ref()).into()
}
fn make_messages_delivery_transaction(
&self,
transaction_nonce: <Rococo as Chain>::Index,
best_block_id: WococoHeaderId,
transaction_nonce: IndexOf<Rococo>,
_generated_at_header: WococoHeaderId,
_nonces: RangeInclusive<MessageNonce>,
proof: <Self as MessageLane>::MessagesProof,
proof: <Self::MessageLane as MessageLane>::MessagesProof,
) -> Bytes {
let (dispatch_weight, proof) = proof;
let FromBridgedChainMessagesProof {
ref nonces_start,
ref nonces_end,
..
} = proof;
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
let messages_count = nonces_end - nonces_start + 1;
let call = relay_rococo_client::runtime::Call::BridgeMessagesWococo(
relay_rococo_client::runtime::BridgeMessagesWococoCall::receive_messages_proof(
self.relayer_id_at_source.clone(),
self.message_lane.relayer_id_at_source.clone(),
proof,
messages_count as _,
dispatch_weight,
),
);
let genesis_hash = *self.target_client.genesis_hash();
let transaction = Rococo::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
let genesis_hash = *self.message_lane.target_client.genesis_hash();
let transaction = Rococo::sign_transaction(
genesis_hash,
&self.message_lane.target_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.target_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Wococo -> Rococo delivery transaction. Weight: <unknown>/{}, size: {}/{}",
@@ -124,28 +166,42 @@ impl SubstrateMessageLane for WococoMessagesToRococo {
}
/// Wococo node as messages source.
type WococoSourceClient =
SubstrateMessagesSource<Wococo, WococoMessagesToRococo, relay_wococo_client::runtime::WithRococoMessagesInstance>;
type WococoSourceClient = SubstrateMessagesSource<WococoMessagesToRococo>;
/// Rococo node as messages target.
type RococoTargetClient =
SubstrateMessagesTarget<Rococo, WococoMessagesToRococo, relay_rococo_client::runtime::WithWococoMessagesInstance>;
type RococoTargetClient = SubstrateMessagesTarget<WococoMessagesToRococo>;
/// Run Wococo-to-Rococo messages sync.
pub async fn run(
params: MessagesRelayParams<Wococo, WococoSigningParams, Rococo, RococoSigningParams>,
) -> Result<(), String> {
let stall_timeout = Duration::from_secs(5 * 60);
params: MessagesRelayParams<
Wococo,
WococoSigningParams,
Rococo,
RococoSigningParams,
MixStrategy,
>,
) -> anyhow::Result<()> {
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
params.source_transactions_mortality,
params.target_transactions_mortality,
Wococo::AVERAGE_BLOCK_INTERVAL,
Rococo::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
);
let relayer_id_at_wococo = (*params.source_sign.public().as_array_ref()).into();
let lane_id = params.lane_id;
let source_client = params.source_client;
let lane = WococoMessagesToRococo {
source_client: source_client.clone(),
source_sign: params.source_sign,
target_client: params.target_client.clone(),
target_sign: params.target_sign,
relayer_id_at_source: relayer_id_at_wococo,
message_lane: SubstrateMessageLaneToSubstrate {
source_client: source_client.clone(),
source_sign: params.source_sign,
source_transactions_mortality: params.source_transactions_mortality,
target_client: params.target_client.clone(),
target_sign: params.target_sign,
target_transactions_mortality: params.target_transactions_mortality,
relayer_id_at_source: relayer_id_at_wococo,
},
};
// 2/3 is reserved for proofs and tx overhead
@@ -153,14 +209,14 @@ pub async fn run(
// we don't know exact weights of the Rococo 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) =
select_delivery_transaction_limits::<pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>>(
select_delivery_transaction_limits::<
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>,
>(
bp_rococo::max_extrinsic_weight(),
bp_rococo::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
);
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 (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
(max_messages_in_single_batch / 2, max_messages_weight_in_single_batch / 2);
log::info!(
target: "bridge",
@@ -168,13 +224,25 @@ pub async fn run(
Wococo 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: {}",
lane.relayer_id_at_source,
Max messages weight in single transaction: {}\n\t\
Tx mortality: {:?}/{:?}\n\t\
Stall timeout: {:?}",
lane.message_lane.relayer_id_at_source,
max_messages_in_single_batch,
max_messages_size_in_single_batch,
max_messages_weight_in_single_batch,
params.source_transactions_mortality,
params.target_transactions_mortality,
stall_timeout,
);
let (metrics_params, metrics_values) = add_standalone_metrics(
Some(messages_relay::message_lane_loop::metrics_prefix::<
<WococoMessagesToRococo as SubstrateMessageLane>::MessageLane,
>(&lane_id)),
params.metrics_params,
source_client.clone(),
)?;
messages_relay::message_lane_loop::run(
messages_relay::message_lane_loop::Params {
lane: lane_id,
@@ -183,45 +251,48 @@ pub async fn run(
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
stall_timeout,
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
max_unrewarded_relayer_entries_at_target: bp_rococo::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
max_unconfirmed_nonces_at_target: bp_rococo::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
max_unrewarded_relayer_entries_at_target:
bp_rococo::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
max_unconfirmed_nonces_at_target:
bp_rococo::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
max_messages_in_single_batch,
max_messages_weight_in_single_batch,
max_messages_size_in_single_batch,
relayer_mode: messages_relay::message_lane_loop::RelayerMode::Altruistic,
relay_strategy: params.relay_strategy,
},
},
WococoSourceClient::new(
source_client.clone(),
lane.clone(),
lane_id,
ROCOCO_CHAIN_ID,
params.target_to_source_headers_relay,
),
RococoTargetClient::new(
params.target_client,
lane,
lane_id,
WOCOCO_CHAIN_ID,
metrics_values,
params.source_to_target_headers_relay,
),
relay_utils::relay_metrics(
Some(messages_relay::message_lane_loop::metrics_prefix::<
WococoMessagesToRococo,
>(&lane_id)),
params.metrics_params,
)
.standalone_metric(|registry, prefix| {
StorageProofOverheadMetric::new(
registry,
prefix,
source_client.clone(),
"wococo_storage_proof_overhead".into(),
"Wococo storage proof overhead".into(),
)
})?
.into_params(),
metrics_params,
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Add standalone metrics for the Wococo -> Rococo messages loop.
pub(crate) fn add_standalone_metrics(
metrics_prefix: Option<String>,
metrics_params: MetricsParams,
source_client: Client<Wococo>,
) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
substrate_relay_helper::messages_lane::add_standalone_metrics::<WococoMessagesToRococo>(
metrics_prefix,
metrics_params,
source_client,
None,
None,
None,
)
}
@@ -14,17 +14,18 @@
// 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/>.
use structopt::clap::arg_enum;
use strum::{EnumString, EnumVariantNames};
arg_enum! {
#[derive(Debug, PartialEq, Eq)]
/// Supported full bridges (headers + messages).
pub enum FullBridge {
MillauToRialto,
RialtoToMillau,
RococoToWococo,
WococoToRococo,
}
#[derive(Debug, PartialEq, Eq, EnumString, EnumVariantNames)]
#[strum(serialize_all = "kebab_case")]
/// Supported full bridges (headers + messages).
pub enum FullBridge {
MillauToRialto,
RialtoToMillau,
RococoToWococo,
WococoToRococo,
KusamaToPolkadot,
PolkadotToKusama,
}
impl FullBridge {
@@ -35,6 +36,8 @@ impl FullBridge {
Self::RialtoToMillau => RIALTO_TO_MILLAU_INDEX,
Self::RococoToWococo => ROCOCO_TO_WOCOCO_INDEX,
Self::WococoToRococo => WOCOCO_TO_ROCOCO_INDEX,
Self::KusamaToPolkadot => KUSAMA_TO_POLKADOT_INDEX,
Self::PolkadotToKusama => POLKADOT_TO_KUSAMA_INDEX,
}
}
}
@@ -43,6 +46,8 @@ pub const RIALTO_TO_MILLAU_INDEX: u8 = 0;
pub const MILLAU_TO_RIALTO_INDEX: u8 = 0;
pub const ROCOCO_TO_WOCOCO_INDEX: u8 = 0;
pub const WOCOCO_TO_ROCOCO_INDEX: u8 = 0;
pub const KUSAMA_TO_POLKADOT_INDEX: u8 = 0;
pub const POLKADOT_TO_KUSAMA_INDEX: u8 = 0;
/// The macro allows executing bridge-specific code without going fully generic.
///
@@ -139,6 +144,50 @@ macro_rules! select_full_bridge {
#[allow(unused_imports)]
use relay_wococo_client::runtime::wococo_to_rococo_account_ownership_digest as account_ownership_digest;
$generic
}
FullBridge::KusamaToPolkadot => {
type Source = relay_kusama_client::Kusama;
#[allow(dead_code)]
type Target = relay_polkadot_client::Polkadot;
// Derive-account
#[allow(unused_imports)]
use bp_polkadot::derive_account_from_kusama_id as derive_account;
// Relay-messages
#[allow(unused_imports)]
use crate::chains::kusama_messages_to_polkadot::run as relay_messages;
// Send-message / Estimate-fee
#[allow(unused_imports)]
use bp_polkadot::TO_POLKADOT_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_MESSAGE_FEE_METHOD;
// Send-message
#[allow(unused_imports)]
use relay_kusama_client::runtime::kusama_to_polkadot_account_ownership_digest as account_ownership_digest;
$generic
}
FullBridge::PolkadotToKusama => {
type Source = relay_polkadot_client::Polkadot;
#[allow(dead_code)]
type Target = relay_kusama_client::Kusama;
// Derive-account
#[allow(unused_imports)]
use bp_kusama::derive_account_from_polkadot_id as derive_account;
// Relay-messages
#[allow(unused_imports)]
use crate::chains::polkadot_messages_to_kusama::run as relay_messages;
// Send-message / Estimate-fee
#[allow(unused_imports)]
use bp_kusama::TO_KUSAMA_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_MESSAGE_FEE_METHOD;
// Send-message
#[allow(unused_imports)]
use relay_polkadot_client::runtime::polkadot_to_kusama_account_ownership_digest as account_ownership_digest;
$generic
}
}
@@ -14,10 +14,13 @@
// 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/>.
use crate::cli::{bridge::FullBridge, AccountId};
use crate::select_full_bridge;
use crate::{
cli::{bridge::FullBridge, AccountId},
select_full_bridge,
};
use relay_substrate_client::Chain;
use structopt::StructOpt;
use strum::VariantNames;
/// Given a source chain `AccountId`, derive the corresponding `AccountId` for the target chain.
///
@@ -28,7 +31,7 @@ use structopt::StructOpt;
#[derive(StructOpt)]
pub struct DeriveAccount {
/// A bridge instance to initialize.
#[structopt(possible_values = &FullBridge::variants(), case_insensitive = true)]
#[structopt(possible_values = FullBridge::VARIANTS, case_insensitive = true)]
bridge: FullBridge,
/// Source-chain address to derive Target-chain address from.
account: AccountId,
@@ -54,11 +57,7 @@ impl DeriveAccount {
select_full_bridge!(self.bridge, {
let (account, derived_account) = self.derive_account();
println!("Source address:\n{} ({})", account, Source::NAME);
println!(
"->Corresponding (derived) address:\n{} ({})",
derived_account,
Target::NAME,
);
println!("->Corresponding (derived) address:\n{} ({})", derived_account, Target::NAME,);
Ok(())
})
@@ -80,9 +79,9 @@ mod tests {
let millau = "752paRyW1EGfq9YLTSSqcSJ5hqnBDidBmaftGhBo8fy6ypW9";
// when
let (rialto_parsed, rialto_derived) = derive_account_cli("RialtoToMillau", rialto);
let (millau_parsed, millau_derived) = derive_account_cli("MillauToRialto", millau);
let (millau2_parsed, millau2_derived) = derive_account_cli("MillauToRialto", rialto);
let (rialto_parsed, rialto_derived) = derive_account_cli("rialto-to-millau", rialto);
let (millau_parsed, millau_derived) = derive_account_cli("millau-to-rialto", millau);
let (millau2_parsed, millau2_derived) = derive_account_cli("millau-to-rialto", rialto);
// then
assert_eq!(format!("{}", rialto_parsed), rialto);
@@ -14,18 +14,22 @@
// 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/>.
use crate::cli::bridge::FullBridge;
use crate::cli::{AccountId, Balance, CliChain, ExplicitOrMaximal, HexBytes, HexLaneId};
use crate::select_full_bridge;
use crate::{
cli::{
bridge::FullBridge, AccountId, Balance, CliChain, ExplicitOrMaximal, HexBytes, HexLaneId,
},
select_full_bridge,
};
use frame_support::weights::DispatchInfo;
use relay_substrate_client::Chain;
use structopt::StructOpt;
use strum::VariantNames;
/// Encode source chain runtime call.
#[derive(StructOpt, Debug)]
pub struct EncodeCall {
/// A bridge instance to encode call for.
#[structopt(possible_values = &FullBridge::variants(), case_insensitive = true)]
#[structopt(possible_values = FullBridge::VARIANTS, case_insensitive = true)]
bridge: FullBridge,
#[structopt(flatten)]
call: Call,
@@ -125,31 +129,30 @@ pub(crate) fn preprocess_call<Source: CliEncodeCall + CliChain, Target: CliEncod
bridge_instance: u8,
) {
match *call {
Call::Raw { .. } => {}
Call::Remark {
ref remark_size,
ref mut remark_payload,
} => {
Call::Raw { .. } => {},
Call::Remark { ref remark_size, ref mut remark_payload } =>
if remark_payload.is_none() {
*remark_payload = Some(HexBytes(generate_remark_payload(
remark_size,
compute_maximal_message_arguments_size(Source::max_extrinsic_size(), Target::max_extrinsic_size()),
compute_maximal_message_arguments_size(
Source::max_extrinsic_size(),
Target::max_extrinsic_size(),
),
)));
}
}
},
Call::Transfer { ref mut recipient, .. } => {
recipient.enforce_chain::<Source>();
}
Call::BridgeSendMessage {
ref mut bridge_instance_index,
..
} => {
},
Call::BridgeSendMessage { ref mut bridge_instance_index, .. } => {
*bridge_instance_index = bridge_instance;
}
},
};
}
fn generate_remark_payload(remark_size: &Option<ExplicitOrMaximal<usize>>, maximal_allowed_size: u32) -> Vec<u8> {
fn generate_remark_payload(
remark_size: &Option<ExplicitOrMaximal<usize>>,
maximal_allowed_size: u32,
) -> Vec<u8> {
match remark_size {
Some(ExplicitOrMaximal::Explicit(remark_size)) => vec![0; *remark_size],
Some(ExplicitOrMaximal::Maximal) => vec![0; maximal_allowed_size as _],
@@ -171,9 +174,11 @@ pub(crate) fn compute_maximal_message_arguments_size(
) -> u32 {
// assume that both signed extensions and other arguments fit 1KB
let service_tx_bytes_on_source_chain = 1024;
let maximal_source_extrinsic_size = maximal_source_extrinsic_size - service_tx_bytes_on_source_chain;
let maximal_call_size =
bridge_runtime_common::messages::target::maximal_incoming_message_size(maximal_target_extrinsic_size);
let maximal_source_extrinsic_size =
maximal_source_extrinsic_size - service_tx_bytes_on_source_chain;
let maximal_call_size = bridge_runtime_common::messages::target::maximal_incoming_message_size(
maximal_target_extrinsic_size,
);
let maximal_call_size = if maximal_call_size > maximal_source_extrinsic_size {
maximal_source_extrinsic_size
} else {
@@ -188,13 +193,14 @@ pub(crate) fn compute_maximal_message_arguments_size(
#[cfg(test)]
mod tests {
use super::*;
use crate::cli::send_message::SendMessage;
#[test]
fn should_encode_transfer_call() {
// given
let mut encode_call = EncodeCall::from_iter(vec![
"encode-call",
"RialtoToMillau",
"rialto-to-millau",
"transfer",
"--amount",
"12345",
@@ -208,20 +214,21 @@ mod tests {
// then
assert_eq!(
format!("{:?}", hex),
"0x0c00d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de5c0"
"0x040000d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de5c0"
);
}
#[test]
fn should_encode_remark_with_default_payload() {
// given
let mut encode_call = EncodeCall::from_iter(vec!["encode-call", "RialtoToMillau", "remark"]);
let mut encode_call =
EncodeCall::from_iter(vec!["encode-call", "rialto-to-millau", "remark"]);
// when
let hex = encode_call.encode().unwrap();
// then
assert!(format!("{:?}", hex).starts_with("0x070154556e69782074696d653a"));
assert!(format!("{:?}", hex).starts_with("0x000154556e69782074696d653a"));
}
#[test]
@@ -229,7 +236,7 @@ mod tests {
// given
let mut encode_call = EncodeCall::from_iter(vec![
"encode-call",
"RialtoToMillau",
"rialto-to-millau",
"remark",
"--remark-payload",
"1234",
@@ -239,20 +246,25 @@ mod tests {
let hex = encode_call.encode().unwrap();
// then
assert_eq!(format!("{:?}", hex), "0x0701081234");
assert_eq!(format!("{:?}", hex), "0x0001081234");
}
#[test]
fn should_encode_remark_with_size() {
// given
let mut encode_call =
EncodeCall::from_iter(vec!["encode-call", "RialtoToMillau", "remark", "--remark-size", "12"]);
let mut encode_call = EncodeCall::from_iter(vec![
"encode-call",
"rialto-to-millau",
"remark",
"--remark-size",
"12",
]);
// when
let hex = encode_call.encode().unwrap();
// then
assert_eq!(format!("{:?}", hex), "0x070130000000000000000000000000");
assert_eq!(format!("{:?}", hex), "0x000130000000000000000000000000");
}
#[test]
@@ -260,7 +272,7 @@ mod tests {
// when
let err = EncodeCall::from_iter_safe(vec![
"encode-call",
"RialtoToMillau",
"rialto-to-millau",
"remark",
"--remark-payload",
"1234",
@@ -273,6 +285,68 @@ mod tests {
assert_eq!(err.kind, structopt::clap::ErrorKind::ArgumentConflict);
let info = err.info.unwrap();
assert!(info.contains(&"remark-payload".to_string()) | info.contains(&"remark-size".to_string()))
assert!(
info.contains(&"remark-payload".to_string()) |
info.contains(&"remark-size".to_string())
)
}
#[test]
fn should_encode_raw_call() {
// given
let mut encode_call = EncodeCall::from_iter(vec![
"encode-call",
"rialto-to-millau",
"raw",
"040000d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de5c0",
]);
// when
let hex = encode_call.encode().unwrap();
// then
assert_eq!(
format!("{:?}", hex),
"0x040000d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de5c0"
);
}
#[test]
fn should_encode_bridge_send_message_call() {
// given
let encode_message = SendMessage::from_iter(vec![
"send-message",
"millau-to-rialto",
"--source-port",
"10946",
"--source-signer",
"//Alice",
"--target-signer",
"//Alice",
"--origin",
"Target",
"remark",
])
.encode_payload()
.unwrap();
let mut encode_call = EncodeCall::from_iter(vec![
"encode-call",
"rialto-to-millau",
"bridge-send-message",
"--fee",
"12345",
"--payload",
format!("{:}", &HexBytes::encode(&encode_message)).as_str(),
]);
// when
let call_hex = encode_call.encode().unwrap();
// then
assert!(format!("{:?}", call_hex).starts_with(
"0x10030000000001000000381409000000000001d43593c715fdd31c61141abd04a99fd6822c8558854cc\
de39a5684e7a56da27d01d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01"
))
}
}
@@ -14,9 +14,12 @@
// 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/>.
use crate::cli::{bridge::FullBridge, AccountId, CliChain, HexBytes};
use crate::select_full_bridge;
use crate::{
cli::{bridge::FullBridge, AccountId, CliChain, HexBytes},
select_full_bridge,
};
use structopt::StructOpt;
use strum::VariantNames;
/// Generic message payload.
#[derive(StructOpt, Debug, PartialEq, Eq)]
@@ -41,7 +44,7 @@ pub enum MessagePayload {
#[derive(StructOpt)]
pub struct EncodeMessage {
/// A bridge instance to initialize.
#[structopt(possible_values = &FullBridge::variants(), case_insensitive = true)]
#[structopt(possible_values = FullBridge::VARIANTS, case_insensitive = true)]
bridge: FullBridge,
#[structopt(flatten)]
payload: MessagePayload,
@@ -51,7 +54,8 @@ impl EncodeMessage {
/// Run the command.
pub fn encode(self) -> anyhow::Result<HexBytes> {
select_full_bridge!(self.bridge, {
let payload = Source::encode_message(self.payload).map_err(|e| anyhow::format_err!("{}", e))?;
let payload =
Source::encode_message(self.payload).map_err(|e| anyhow::format_err!("{}", e))?;
Ok(HexBytes::encode(&payload))
})
}
@@ -73,7 +77,8 @@ mod tests {
fn should_encode_raw_message() {
// given
let msg = "01000000e88514000000000002d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d003c040130000000000000000000000000";
let encode_message = EncodeMessage::from_iter(vec!["encode-message", "MillauToRialto", "raw", msg]);
let encode_message =
EncodeMessage::from_iter(vec!["encode-message", "rialto-to-millau", "raw", msg]);
// when
let hex = encode_message.encode().unwrap();
@@ -88,7 +93,7 @@ mod tests {
let sender = sp_keyring::AccountKeyring::Alice.to_account_id().to_ss58check();
let encode_message = EncodeMessage::from_iter(vec![
"encode-message",
"RialtoToMillau",
"rialto-to-millau",
"call",
"--sender",
&sender,
@@ -101,6 +106,6 @@ mod tests {
let hex = encode_message.encode().unwrap();
// then
assert_eq!(format!("{:?}", hex), "0x01000000b0d60f000000000002d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d003c040130000000000000000000000000");
assert_eq!(format!("{:?}", hex), "0x0100000010f108000000000002d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d003c000130000000000000000000000000");
}
}
@@ -14,18 +14,21 @@
// 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/>.
use crate::cli::bridge::FullBridge;
use crate::cli::{Balance, CliChain, HexBytes, HexLaneId, SourceConnectionParams};
use crate::select_full_bridge;
use crate::{
cli::{bridge::FullBridge, Balance, CliChain, HexBytes, HexLaneId, SourceConnectionParams},
select_full_bridge,
};
use bp_runtime::BalanceOf;
use codec::{Decode, Encode};
use relay_substrate_client::Chain;
use structopt::StructOpt;
use strum::VariantNames;
/// Estimate Delivery & Dispatch Fee command.
#[derive(StructOpt, Debug, PartialEq, Eq)]
pub struct EstimateFee {
/// A bridge instance to encode call for.
#[structopt(possible_values = &FullBridge::variants(), case_insensitive = true)]
#[structopt(possible_values = FullBridge::VARIANTS, case_insensitive = true)]
bridge: FullBridge,
#[structopt(flatten)]
source: SourceConnectionParams,
@@ -40,21 +43,21 @@ pub struct EstimateFee {
impl EstimateFee {
/// Run the command.
pub async fn run(self) -> anyhow::Result<()> {
let Self {
source,
bridge,
lane,
payload,
} = self;
let Self { source, bridge, lane, payload } = self;
select_full_bridge!(bridge, {
let source_client = source.to_client::<Source>().await?;
let lane = lane.into();
let payload = Source::encode_message(payload).map_err(|e| anyhow::format_err!("{:?}", e))?;
let payload =
Source::encode_message(payload).map_err(|e| anyhow::format_err!("{:?}", e))?;
let fee: <Source as Chain>::Balance =
estimate_message_delivery_and_dispatch_fee(&source_client, ESTIMATE_MESSAGE_FEE_METHOD, lane, payload)
.await?;
let fee: BalanceOf<Source> = estimate_message_delivery_and_dispatch_fee(
&source_client,
ESTIMATE_MESSAGE_FEE_METHOD,
lane,
payload,
)
.await?;
log::info!(target: "bridge", "Fee: {:?}", Balance(fee as _));
println!("{}", fee);
@@ -72,10 +75,11 @@ pub(crate) async fn estimate_message_delivery_and_dispatch_fee<Fee: Decode, C: C
let encoded_response = client
.state_call(estimate_fee_method.into(), (lane, payload).encode().into(), None)
.await?;
let decoded_response: Option<Fee> =
Decode::decode(&mut &encoded_response.0[..]).map_err(relay_substrate_client::Error::ResponseParseFailed)?;
let fee = decoded_response
.ok_or_else(|| anyhow::format_err!("Unable to decode fee from: {:?}", HexBytes(encoded_response.to_vec())))?;
let decoded_response: Option<Fee> = Decode::decode(&mut &encoded_response.0[..])
.map_err(relay_substrate_client::Error::ResponseParseFailed)?;
let fee = decoded_response.ok_or_else(|| {
anyhow::format_err!("Unable to decode fee from: {:?}", HexBytes(encoded_response.to_vec()))
})?;
Ok(fee)
}
@@ -93,7 +97,7 @@ mod tests {
// when
let res = EstimateFee::from_iter(vec![
"estimate_fee",
"RialtoToMillau",
"rialto-to-millau",
"--source-port",
"1234",
"call",
@@ -18,15 +18,16 @@ use crate::cli::{SourceConnectionParams, TargetConnectionParams, TargetSigningPa
use bp_header_chain::InitializationData;
use bp_runtime::Chain as ChainBase;
use codec::Encode;
use relay_substrate_client::{Chain, TransactionSignScheme};
use relay_substrate_client::{Chain, TransactionSignScheme, UnsignedTransaction};
use sp_core::{Bytes, Pair};
use structopt::{clap::arg_enum, StructOpt};
use structopt::StructOpt;
use strum::{EnumString, EnumVariantNames, VariantNames};
/// Initialize bridge pallet.
#[derive(StructOpt)]
pub struct InitBridge {
/// A bridge instance to initialize.
#[structopt(possible_values = &InitBridgeName::variants(), case_insensitive = true)]
#[structopt(possible_values = InitBridgeName::VARIANTS, case_insensitive = true)]
bridge: InitBridgeName,
#[structopt(flatten)]
source: SourceConnectionParams,
@@ -36,17 +37,17 @@ pub struct InitBridge {
target_sign: TargetSigningParams,
}
// TODO [#851] Use kebab-case.
arg_enum! {
#[derive(Debug)]
/// Bridge to initialize.
pub enum InitBridgeName {
MillauToRialto,
RialtoToMillau,
WestendToMillau,
RococoToWococo,
WococoToRococo,
}
#[derive(Debug, EnumString, EnumVariantNames)]
#[strum(serialize_all = "kebab_case")]
/// Bridge to initialize.
pub enum InitBridgeName {
MillauToRialto,
RialtoToMillau,
WestendToMillau,
RococoToWococo,
WococoToRococo,
KusamaToPolkadot,
PolkadotToKusama,
}
macro_rules! select_bridge {
@@ -59,14 +60,17 @@ macro_rules! select_bridge {
fn encode_init_bridge(
init_data: InitializationData<<Source as ChainBase>::Header>,
) -> <Target as Chain>::Call {
rialto_runtime::SudoCall::sudo(Box::new(
rialto_runtime::BridgeGrandpaMillauCall::initialize(init_data).into(),
))
rialto_runtime::SudoCall::sudo {
call: Box::new(
rialto_runtime::BridgeGrandpaMillauCall::initialize { init_data }
.into(),
),
}
.into()
}
$generic
}
},
InitBridgeName::RialtoToMillau => {
type Source = relay_rialto_client::Rialto;
type Target = relay_millau_client::Millau;
@@ -74,15 +78,17 @@ macro_rules! select_bridge {
fn encode_init_bridge(
init_data: InitializationData<<Source as ChainBase>::Header>,
) -> <Target as Chain>::Call {
let initialize_call = millau_runtime::BridgeGrandpaRialtoCall::<
let initialize_call = millau_runtime::BridgeGrandpaCall::<
millau_runtime::Runtime,
millau_runtime::RialtoGrandpaInstance,
>::initialize(init_data);
millau_runtime::SudoCall::sudo(Box::new(initialize_call.into())).into()
>::initialize {
init_data,
};
millau_runtime::SudoCall::sudo { call: Box::new(initialize_call.into()) }.into()
}
$generic
}
},
InitBridgeName::WestendToMillau => {
type Source = relay_westend_client::Westend;
type Target = relay_millau_client::Millau;
@@ -90,18 +96,21 @@ macro_rules! select_bridge {
fn encode_init_bridge(
init_data: InitializationData<<Source as ChainBase>::Header>,
) -> <Target as Chain>::Call {
// at Westend -> Millau initialization we're not using sudo, because otherwise our deployments
// may fail, because we need to initialize both Rialto -> Millau and Westend -> Millau bridge.
// => since there's single possible sudo account, one of transaction may fail with duplicate nonce error
millau_runtime::BridgeGrandpaWestendCall::<
// at Westend -> Millau initialization we're not using sudo, because otherwise
// our deployments may fail, because we need to initialize both Rialto -> Millau
// and Westend -> Millau bridge. => since there's single possible sudo account,
// one of transaction may fail with duplicate nonce error
millau_runtime::BridgeGrandpaCall::<
millau_runtime::Runtime,
millau_runtime::WestendGrandpaInstance,
>::initialize(init_data)
>::initialize {
init_data,
}
.into()
}
$generic
}
},
InitBridgeName::RococoToWococo => {
type Source = relay_rococo_client::Rococo;
type Target = relay_wococo_client::Wococo;
@@ -110,12 +119,14 @@ macro_rules! select_bridge {
init_data: InitializationData<<Source as ChainBase>::Header>,
) -> <Target as Chain>::Call {
relay_wococo_client::runtime::Call::BridgeGrandpaRococo(
relay_wococo_client::runtime::BridgeGrandpaRococoCall::initialize(init_data),
relay_wococo_client::runtime::BridgeGrandpaRococoCall::initialize(
init_data,
),
)
}
$generic
}
},
InitBridgeName::WococoToRococo => {
type Source = relay_wococo_client::Wococo;
type Target = relay_rococo_client::Rococo;
@@ -124,12 +135,46 @@ macro_rules! select_bridge {
init_data: InitializationData<<Source as ChainBase>::Header>,
) -> <Target as Chain>::Call {
relay_rococo_client::runtime::Call::BridgeGrandpaWococo(
relay_rococo_client::runtime::BridgeGrandpaWococoCall::initialize(init_data),
relay_rococo_client::runtime::BridgeGrandpaWococoCall::initialize(
init_data,
),
)
}
$generic
}
},
InitBridgeName::KusamaToPolkadot => {
type Source = relay_kusama_client::Kusama;
type Target = relay_polkadot_client::Polkadot;
fn encode_init_bridge(
init_data: InitializationData<<Source as ChainBase>::Header>,
) -> <Target as Chain>::Call {
relay_polkadot_client::runtime::Call::BridgeKusamaGrandpa(
relay_polkadot_client::runtime::BridgeKusamaGrandpaCall::initialize(
init_data,
),
)
}
$generic
},
InitBridgeName::PolkadotToKusama => {
type Source = relay_polkadot_client::Polkadot;
type Target = relay_kusama_client::Kusama;
fn encode_init_bridge(
init_data: InitializationData<<Source as ChainBase>::Header>,
) -> <Target as Chain>::Call {
relay_kusama_client::runtime::Call::BridgePolkadotGrandpa(
relay_kusama_client::runtime::BridgePolkadotGrandpaCall::initialize(
init_data,
),
)
}
$generic
},
}
};
}
@@ -142,7 +187,7 @@ impl InitBridge {
let target_client = self.target.to_client::<Target>().await?;
let target_sign = self.target_sign.to_keypair::<Target>()?;
crate::headers_initialize::initialize(
substrate_relay_helper::headers_initialize::initialize(
source_client,
target_client.clone(),
target_sign.public().into(),
@@ -151,8 +196,11 @@ impl InitBridge {
Target::sign_transaction(
*target_client.genesis_hash(),
&target_sign,
transaction_nonce,
encode_init_bridge(initialization_data),
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
encode_init_bridge(initialization_data),
transaction_nonce,
),
)
.encode(),
)
@@ -32,9 +32,12 @@ pub(crate) mod send_message;
mod derive_account;
mod init_bridge;
mod register_parachain;
mod relay_headers;
mod relay_headers_and_messages;
mod relay_messages;
mod resubmit_transactions;
mod swap_tokens;
/// Parse relay CLI args.
pub fn parse_args() -> Command {
@@ -84,8 +87,15 @@ pub enum Command {
EncodeMessage(encode_message::EncodeMessage),
/// Estimate Delivery and Dispatch Fee required for message submission to messages pallet.
EstimateFee(estimate_fee::EstimateFee),
/// Given a source chain `AccountId`, derive the corresponding `AccountId` for the target chain.
/// Given a source chain `AccountId`, derive the corresponding `AccountId` for the target
/// chain.
DeriveAccount(derive_account::DeriveAccount),
/// Resubmit transactions with increased tip if they are stalled.
ResubmitTransactions(resubmit_transactions::ResubmitTransactions),
/// Swap tokens using token-swap bridge.
SwapTokens(swap_tokens::SwapTokens),
/// Register parachain.
RegisterParachain(register_parachain::RegisterParachain),
}
impl Command {
@@ -94,12 +104,15 @@ impl Command {
use relay_utils::initialize::{initialize_logger, initialize_relay};
match self {
Self::RelayHeaders(_) | Self::RelayMessages(_) | Self::RelayHeadersAndMessages(_) | Self::InitBridge(_) => {
Self::RelayHeaders(_) |
Self::RelayMessages(_) |
Self::RelayHeadersAndMessages(_) |
Self::InitBridge(_) => {
initialize_relay();
}
},
_ => {
initialize_logger(false);
}
},
}
}
@@ -116,6 +129,9 @@ impl Command {
Self::EncodeMessage(arg) => arg.run().await?,
Self::EstimateFee(arg) => arg.run().await?,
Self::DeriveAccount(arg) => arg.run().await?,
Self::ResubmitTransactions(arg) => arg.run().await?,
Self::SwapTokens(arg) => arg.run().await?,
Self::RegisterParachain(arg) => arg.run().await?,
}
Ok(())
}
@@ -187,10 +203,7 @@ const SS58_FORMAT_PROOF: &str = "u16 -> Ss58Format is infallible; qed";
impl AccountId {
/// Create new SS58-formatted address from raw account id.
pub fn from_raw<T: CliChain>(account: sp_runtime::AccountId32) -> Self {
Self {
account,
ss58_format: T::ss58_format().try_into().expect(SS58_FORMAT_PROOF),
}
Self { account, ss58_format: T::ss58_format().try_into().expect(SS58_FORMAT_PROOF) }
}
/// Enforces formatting account to be for given [`CliChain`] type.
@@ -228,7 +241,7 @@ pub trait CliChain: relay_substrate_client::Chain {
/// Chain's current version of the runtime.
const RUNTIME_VERSION: sp_version::RuntimeVersion;
/// Crypto keypair type used to send messages.
/// Crypto KeyPair type used to send messages.
///
/// In case of chains supporting multiple cryptos, pick one used by the CLI.
type KeyPair: sp_core::crypto::Pair;
@@ -242,7 +255,9 @@ pub trait CliChain: relay_substrate_client::Chain {
fn ss58_format() -> u16;
/// Construct message payload to be sent over the bridge.
fn encode_message(message: crate::cli::encode_message::MessagePayload) -> Result<Self::MessagePayload, String>;
fn encode_message(
message: crate::cli::encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload>;
/// Maximal extrinsic weight (from the runtime).
fn max_extrinsic_weight() -> Weight;
@@ -344,7 +359,7 @@ where
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.to_lowercase() == "max" {
return Ok(ExplicitOrMaximal::Maximal);
return Ok(ExplicitOrMaximal::Maximal)
}
V::from_str(s)
@@ -360,7 +375,7 @@ macro_rules! declare_chain_options {
($chain:ident, $chain_prefix:ident) => {
paste::item! {
#[doc = $chain " connection params."]
#[derive(StructOpt, Debug, PartialEq, Eq)]
#[derive(StructOpt, Debug, PartialEq, Eq, Clone)]
pub struct [<$chain ConnectionParams>] {
#[doc = "Connect to " $chain " node at given host."]
#[structopt(long, default_value = "127.0.0.1")]
@@ -374,28 +389,117 @@ macro_rules! declare_chain_options {
}
#[doc = $chain " signing params."]
#[derive(StructOpt, Debug, PartialEq, Eq)]
#[derive(StructOpt, Debug, PartialEq, Eq, Clone)]
pub struct [<$chain SigningParams>] {
#[doc = "The SURI of secret key to use when transactions are submitted to the " $chain " node."]
#[structopt(long)]
pub [<$chain_prefix _signer>]: String,
pub [<$chain_prefix _signer>]: Option<String>,
#[doc = "The password for the SURI of secret key to use when transactions are submitted to the " $chain " node."]
#[structopt(long)]
pub [<$chain_prefix _signer_password>]: Option<String>,
#[doc = "Path to the file, that contains SURI of secret key to use when transactions are submitted to the " $chain " node. Can be overridden with " $chain_prefix "_signer option."]
#[structopt(long)]
pub [<$chain_prefix _signer_file>]: Option<std::path::PathBuf>,
#[doc = "Path to the file, that password for the SURI of secret key to use when transactions are submitted to the " $chain " node. Can be overridden with " $chain_prefix "_signer_password option."]
#[structopt(long)]
pub [<$chain_prefix _signer_password_file>]: Option<std::path::PathBuf>,
#[doc = "Transactions mortality period, in blocks. MUST be a power of two in [4; 65536] range. MAY NOT be larger than `BlockHashCount` parameter of the chain system module."]
#[structopt(long)]
pub [<$chain_prefix _transactions_mortality>]: Option<u32>,
}
#[doc = "Parameters required to sign transaction on behalf of owner of the messages pallet at " $chain "."]
#[derive(StructOpt, Debug, PartialEq, Eq)]
pub struct [<$chain MessagesPalletOwnerSigningParams>] {
#[doc = "The SURI of secret key to use when transactions are submitted to the " $chain " node."]
#[structopt(long)]
pub [<$chain_prefix _messages_pallet_owner>]: Option<String>,
#[doc = "The password for the SURI of secret key to use when transactions are submitted to the " $chain " node."]
#[structopt(long)]
pub [<$chain_prefix _messages_pallet_owner_password>]: Option<String>,
}
impl [<$chain SigningParams>] {
/// Parse signing params into chain-specific `KeyPair`.
/// Return transactions mortality.
#[allow(dead_code)]
pub fn transactions_mortality(&self) -> anyhow::Result<Option<u32>> {
self.[<$chain_prefix _transactions_mortality>]
.map(|transactions_mortality| {
if !(4..=65536).contains(&transactions_mortality)
|| !transactions_mortality.is_power_of_two()
{
Err(anyhow::format_err!(
"Transactions mortality {} is not a power of two in a [4; 65536] range",
transactions_mortality,
))
} else {
Ok(transactions_mortality)
}
})
.transpose()
}
/// Parse signing params into chain-specific KeyPair.
#[allow(dead_code)]
pub fn to_keypair<Chain: CliChain>(&self) -> anyhow::Result<Chain::KeyPair> {
let suri = match (self.[<$chain_prefix _signer>].as_ref(), self.[<$chain_prefix _signer_file>].as_ref()) {
(Some(suri), _) => suri.to_owned(),
(None, Some(suri_file)) => std::fs::read_to_string(suri_file)
.map_err(|err| anyhow::format_err!(
"Failed to read SURI from file {:?}: {}",
suri_file,
err,
))?,
(None, None) => return Err(anyhow::format_err!(
"One of options must be specified: '{}' or '{}'",
stringify!([<$chain_prefix _signer>]),
stringify!([<$chain_prefix _signer_file>]),
)),
};
let suri_password = match (
self.[<$chain_prefix _signer_password>].as_ref(),
self.[<$chain_prefix _signer_password_file>].as_ref(),
) {
(Some(suri_password), _) => Some(suri_password.to_owned()),
(None, Some(suri_password_file)) => std::fs::read_to_string(suri_password_file)
.map(Some)
.map_err(|err| anyhow::format_err!(
"Failed to read SURI password from file {:?}: {}",
suri_password_file,
err,
))?,
_ => None,
};
use sp_core::crypto::Pair;
Chain::KeyPair::from_string(
&self.[<$chain_prefix _signer>],
self.[<$chain_prefix _signer_password>].as_deref()
&suri,
suri_password.as_deref()
).map_err(|e| anyhow::format_err!("{:?}", e))
}
}
#[allow(dead_code)]
impl [<$chain MessagesPalletOwnerSigningParams>] {
/// Parse signing params into chain-specific KeyPair.
pub fn to_keypair<Chain: CliChain>(&self) -> anyhow::Result<Option<Chain::KeyPair>> {
use sp_core::crypto::Pair;
let [<$chain_prefix _messages_pallet_owner>] = match self.[<$chain_prefix _messages_pallet_owner>] {
Some(ref messages_pallet_owner) => messages_pallet_owner,
None => return Ok(None),
};
Chain::KeyPair::from_string(
[<$chain_prefix _messages_pallet_owner>],
self.[<$chain_prefix _messages_pallet_owner_password>].as_deref()
).map_err(|e| anyhow::format_err!("{:?}", e)).map(Some)
}
}
impl [<$chain ConnectionParams>] {
/// Convert connection params into Substrate client.
pub async fn to_client<Chain: CliChain>(
@@ -416,9 +520,12 @@ macro_rules! declare_chain_options {
declare_chain_options!(Source, source);
declare_chain_options!(Target, target);
declare_chain_options!(Relaychain, relaychain);
declare_chain_options!(Parachain, parachain);
#[cfg(test)]
mod tests {
use sp_core::Pair;
use std::str::FromStr;
use super::*;
@@ -434,10 +541,7 @@ mod tests {
let expected = vec![rialto1, rialto2, millau1, millau2];
// when
let parsed = expected
.iter()
.map(|s| AccountId::from_str(s).unwrap())
.collect::<Vec<_>>();
let parsed = expected.iter().map(|s| AccountId::from_str(s).unwrap()).collect::<Vec<_>>();
let actual = parsed.iter().map(|a| format!("{}", a)).collect::<Vec<_>>();
@@ -456,4 +560,93 @@ mod tests {
// then
assert_eq!(hex.0, hex2.0);
}
#[test]
fn reads_suri_from_file() {
const ALICE: &str = "//Alice";
const BOB: &str = "//Bob";
const ALICE_PASSWORD: &str = "alice_password";
const BOB_PASSWORD: &str = "bob_password";
let alice = sp_core::sr25519::Pair::from_string(ALICE, Some(ALICE_PASSWORD)).unwrap();
let bob = sp_core::sr25519::Pair::from_string(BOB, Some(BOB_PASSWORD)).unwrap();
let bob_with_alice_password =
sp_core::sr25519::Pair::from_string(BOB, Some(ALICE_PASSWORD)).unwrap();
let temp_dir = tempfile::tempdir().unwrap();
let mut suri_file_path = temp_dir.path().to_path_buf();
let mut password_file_path = temp_dir.path().to_path_buf();
suri_file_path.push("suri");
password_file_path.push("password");
std::fs::write(&suri_file_path, BOB.as_bytes()).unwrap();
std::fs::write(&password_file_path, BOB_PASSWORD.as_bytes()).unwrap();
// when both seed and password are read from file
assert_eq!(
TargetSigningParams {
target_signer: Some(ALICE.into()),
target_signer_password: Some(ALICE_PASSWORD.into()),
target_signer_file: None,
target_signer_password_file: None,
target_transactions_mortality: None,
}
.to_keypair::<relay_rialto_client::Rialto>()
.map(|p| p.public())
.map_err(drop),
Ok(alice.public()),
);
// when both seed and password are read from file
assert_eq!(
TargetSigningParams {
target_signer: None,
target_signer_password: None,
target_signer_file: Some(suri_file_path.clone()),
target_signer_password_file: Some(password_file_path.clone()),
target_transactions_mortality: None,
}
.to_keypair::<relay_rialto_client::Rialto>()
.map(|p| p.public())
.map_err(drop),
Ok(bob.public()),
);
// when password are is overriden by cli option
assert_eq!(
TargetSigningParams {
target_signer: None,
target_signer_password: Some(ALICE_PASSWORD.into()),
target_signer_file: Some(suri_file_path.clone()),
target_signer_password_file: Some(password_file_path.clone()),
target_transactions_mortality: None,
}
.to_keypair::<relay_rialto_client::Rialto>()
.map(|p| p.public())
.map_err(drop),
Ok(bob_with_alice_password.public()),
);
// when both seed and password are overriden by cli options
assert_eq!(
TargetSigningParams {
target_signer: Some(ALICE.into()),
target_signer_password: Some(ALICE_PASSWORD.into()),
target_signer_file: Some(suri_file_path),
target_signer_password_file: Some(password_file_path),
target_transactions_mortality: None,
}
.to_keypair::<relay_rialto_client::Rialto>()
.map(|p| p.public())
.map_err(drop),
Ok(alice.public()),
);
}
}
@@ -0,0 +1,346 @@
// 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/>.
use crate::cli::{
swap_tokens::wait_until_transaction_is_finalized, Balance, ParachainConnectionParams,
RelaychainConnectionParams, RelaychainSigningParams,
};
use codec::Encode;
use num_traits::Zero;
use polkadot_parachain::primitives::{
HeadData as ParaHeadData, Id as ParaId, ValidationCode as ParaValidationCode,
};
use polkadot_runtime_common::{
paras_registrar::Call as ParaRegistrarCall, slots::Call as ParaSlotsCall,
};
use polkadot_runtime_parachains::paras::ParaLifecycle;
use relay_substrate_client::{
AccountIdOf, CallOf, Chain, Client, TransactionSignScheme, UnsignedTransaction,
};
use rialto_runtime::SudoCall;
use sp_core::{
storage::{well_known_keys::CODE, StorageKey},
Bytes, Pair,
};
use structopt::StructOpt;
use strum::{EnumString, EnumVariantNames, VariantNames};
/// Name of the `NextFreeParaId` value in the `polkadot_runtime_common::paras_registrar` pallet.
const NEXT_FREE_PARA_ID_STORAGE_NAME: &str = "NextFreeParaId";
/// Name of the `ParaLifecycles` map in the `polkadot_runtime_parachains::paras` pallet.
const PARAS_LIFECYCLES_STORAGE_NAME: &str = "ParaLifecycles";
/// Register parachain.
#[derive(StructOpt, Debug, PartialEq)]
pub struct RegisterParachain {
/// A parachain to register.
#[structopt(possible_values = Parachain::VARIANTS, case_insensitive = true)]
parachain: Parachain,
/// Parachain deposit.
#[structopt(long, default_value = "0")]
deposit: Balance,
/// Lease begin.
#[structopt(long, default_value = "0")]
lease_begin: u32,
/// Lease end.
#[structopt(long, default_value = "256")]
lease_end: u32,
#[structopt(flatten)]
relay_connection: RelaychainConnectionParams,
#[structopt(flatten)]
relay_sign: RelaychainSigningParams,
#[structopt(flatten)]
para_connection: ParachainConnectionParams,
}
/// Parachain to register.
#[derive(Debug, EnumString, EnumVariantNames, PartialEq)]
#[strum(serialize_all = "kebab_case")]
pub enum Parachain {
RialtoParachain,
}
macro_rules! select_bridge {
($bridge: expr, $generic: tt) => {
match $bridge {
Parachain::RialtoParachain => {
type Relaychain = relay_rialto_client::Rialto;
type Parachain = relay_rialto_parachain_client::RialtoParachain;
use bp_rialto::{PARAS_PALLET_NAME, PARAS_REGISTRAR_PALLET_NAME};
$generic
},
}
};
}
impl RegisterParachain {
/// Run the command.
pub async fn run(self) -> anyhow::Result<()> {
select_bridge!(self.parachain, {
let relay_client = self.relay_connection.to_client::<Relaychain>().await?;
let relay_sign = self.relay_sign.to_keypair::<Relaychain>()?;
let para_client = self.para_connection.to_client::<Parachain>().await?;
// hopefully we're the only actor that is registering parachain right now
// => read next parachain id
let para_id_key = bp_runtime::storage_value_final_key(
PARAS_REGISTRAR_PALLET_NAME.as_bytes(),
NEXT_FREE_PARA_ID_STORAGE_NAME.as_bytes(),
);
let para_id: ParaId = relay_client
.storage_value(StorageKey(para_id_key.to_vec()), None)
.await?
.unwrap_or(polkadot_primitives::v1::LOWEST_PUBLIC_ID)
.max(polkadot_primitives::v1::LOWEST_PUBLIC_ID);
log::info!(target: "bridge", "Going to reserve parachain id: {:?}", para_id);
// step 1: reserve a parachain id
let relay_genesis_hash = *relay_client.genesis_hash();
let relay_sudo_account: AccountIdOf<Relaychain> = relay_sign.public().into();
let reserve_parachain_id_call: CallOf<Relaychain> =
ParaRegistrarCall::reserve {}.into();
let reserve_parachain_signer = relay_sign.clone();
wait_until_transaction_is_finalized::<Relaychain>(
relay_client
.submit_and_watch_signed_extrinsic(
relay_sudo_account.clone(),
move |_, transaction_nonce| {
Bytes(
Relaychain::sign_transaction(
relay_genesis_hash,
&reserve_parachain_signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
reserve_parachain_id_call,
transaction_nonce,
),
)
.encode(),
)
},
)
.await?,
)
.await?;
log::info!(target: "bridge", "Reserved parachain id: {:?}", para_id);
// step 2: register parathread
let para_genesis_header = para_client.header_by_number(Zero::zero()).await?;
let para_code = para_client
.raw_storage_value(StorageKey(CODE.to_vec()), Some(para_genesis_header.hash()))
.await?
.ok_or_else(|| {
anyhow::format_err!("Cannot fetch validation code of {}", Parachain::NAME)
})?
.0;
log::info!(
target: "bridge",
"Going to register parachain {:?}: genesis len = {} code len = {}",
para_id,
para_genesis_header.encode().len(),
para_code.len(),
);
let register_parathread_call: CallOf<Relaychain> = ParaRegistrarCall::register {
id: para_id,
genesis_head: ParaHeadData(para_genesis_header.encode()),
validation_code: ParaValidationCode(para_code),
}
.into();
let register_parathread_signer = relay_sign.clone();
wait_until_transaction_is_finalized::<Relaychain>(
relay_client
.submit_and_watch_signed_extrinsic(
relay_sudo_account.clone(),
move |_, transaction_nonce| {
Bytes(
Relaychain::sign_transaction(
relay_genesis_hash,
&register_parathread_signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
register_parathread_call,
transaction_nonce,
),
)
.encode(),
)
},
)
.await?,
)
.await?;
log::info!(target: "bridge", "Registered parachain: {:?}. Waiting for onboarding", para_id);
// wait until parathread is onboarded
let para_state_key = bp_runtime::storage_map_final_key_twox64_concat(
PARAS_PALLET_NAME,
PARAS_LIFECYCLES_STORAGE_NAME,
&para_id.encode(),
);
wait_para_state(
&relay_client,
&para_state_key.0,
&[ParaLifecycle::Onboarding, ParaLifecycle::Parathread],
ParaLifecycle::Parathread,
)
.await?;
// step 3: force parachain leases
let lease_begin = self.lease_begin;
let lease_end = self.lease_end;
let para_deposit = self.deposit.cast().into();
log::info!(
target: "bridge",
"Going to force leases of parachain {:?}: [{}; {}]",
para_id,
lease_begin,
lease_end,
);
let force_lease_call: CallOf<Relaychain> = SudoCall::sudo {
call: Box::new(
ParaSlotsCall::force_lease {
para: para_id,
leaser: relay_sudo_account.clone(),
amount: para_deposit,
period_begin: lease_begin,
period_count: lease_end.saturating_sub(lease_begin).saturating_add(1),
}
.into(),
),
}
.into();
let force_lease_signer = relay_sign.clone();
relay_client
.submit_signed_extrinsic(relay_sudo_account.clone(), move |_, transaction_nonce| {
Bytes(
Relaychain::sign_transaction(
relay_genesis_hash,
&force_lease_signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(force_lease_call, transaction_nonce),
)
.encode(),
)
})
.await?;
log::info!(target: "bridge", "Registered parachain leases: {:?}. Waiting for onboarding", para_id);
// wait until parachain is onboarded
wait_para_state(
&relay_client,
&para_state_key.0,
&[
ParaLifecycle::Onboarding,
ParaLifecycle::UpgradingParathread,
ParaLifecycle::Parathread,
],
ParaLifecycle::Parachain,
)
.await?;
Ok(())
})
}
}
/// Wait until parachain state is changed.
async fn wait_para_state<Relaychain: Chain>(
relay_client: &Client<Relaychain>,
para_state_key: &[u8],
from_states: &[ParaLifecycle],
to_state: ParaLifecycle,
) -> anyhow::Result<()> {
loop {
let para_state: ParaLifecycle = relay_client
.storage_value(StorageKey(para_state_key.to_vec()), None)
.await?
.ok_or_else(|| {
anyhow::format_err!(
"Cannot fetch next free parachain lifecycle from the runtime storage of {}",
Relaychain::NAME,
)
})?;
if para_state == to_state {
log::info!(target: "bridge", "Parachain state is now: {:?}", to_state);
return Ok(())
}
if !from_states.contains(&para_state) {
return Err(anyhow::format_err!("Invalid parachain lifecycle: {:?}", para_state))
}
log::info!(target: "bridge", "Parachain state: {:?}. Waiting for {:?}", para_state, to_state);
async_std::task::sleep(Relaychain::AVERAGE_BLOCK_INTERVAL).await;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn register_rialto_parachain() {
let register_parachain = RegisterParachain::from_iter(vec![
"register-parachain",
"rialto-parachain",
"--parachain-host",
"127.0.0.1",
"--parachain-port",
"11949",
"--relaychain-host",
"127.0.0.1",
"--relaychain-port",
"9944",
"--relaychain-signer",
"//Alice",
"--deposit",
"42",
"--lease-begin",
"100",
"--lease-end",
"200",
]);
assert_eq!(
register_parachain,
RegisterParachain {
parachain: Parachain::RialtoParachain,
deposit: Balance(42),
lease_begin: 100,
lease_end: 200,
relay_connection: RelaychainConnectionParams {
relaychain_host: "127.0.0.1".into(),
relaychain_port: 9944,
relaychain_secure: false,
},
relay_sign: RelaychainSigningParams {
relaychain_signer: Some("//Alice".into()),
relaychain_signer_password: None,
relaychain_signer_file: None,
relaychain_signer_password_file: None,
relaychain_transactions_mortality: None,
},
para_connection: ParachainConnectionParams {
parachain_host: "127.0.0.1".into(),
parachain_port: 11949,
parachain_secure: false,
},
}
);
}
}
@@ -14,17 +14,23 @@
// 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/>.
use crate::cli::{PrometheusParams, SourceConnectionParams, TargetConnectionParams, TargetSigningParams};
use crate::finality_pipeline::SubstrateFinalitySyncPipeline;
use structopt::{clap::arg_enum, StructOpt};
use structopt::StructOpt;
use strum::{EnumString, EnumVariantNames, VariantNames};
use substrate_relay_helper::finality_pipeline::SubstrateFinalitySyncPipeline;
use crate::cli::{
PrometheusParams, SourceConnectionParams, TargetConnectionParams, TargetSigningParams,
};
/// Start headers relayer process.
#[derive(StructOpt)]
pub struct RelayHeaders {
/// A bridge instance to relay headers for.
#[structopt(possible_values = &RelayHeadersBridge::variants(), case_insensitive = true)]
#[structopt(possible_values = RelayHeadersBridge::VARIANTS, case_insensitive = true)]
bridge: RelayHeadersBridge,
/// If passed, only mandatory headers (headers that are changing the GRANDPA authorities set) are relayed.
/// If passed, only mandatory headers (headers that are changing the GRANDPA authorities set)
/// are relayed.
#[structopt(long)]
only_mandatory_headers: bool,
#[structopt(flatten)]
@@ -37,17 +43,17 @@ pub struct RelayHeaders {
prometheus_params: PrometheusParams,
}
// TODO [#851] Use kebab-case.
arg_enum! {
#[derive(Debug)]
/// Headers relay bridge.
pub enum RelayHeadersBridge {
MillauToRialto,
RialtoToMillau,
WestendToMillau,
RococoToWococo,
WococoToRococo,
}
#[derive(Debug, EnumString, EnumVariantNames)]
#[strum(serialize_all = "kebab_case")]
/// Headers relay bridge.
pub enum RelayHeadersBridge {
MillauToRialto,
RialtoToMillau,
WestendToMillau,
RococoToWococo,
WococoToRococo,
KusamaToPolkadot,
PolkadotToKusama,
}
macro_rules! select_bridge {
@@ -59,35 +65,49 @@ macro_rules! select_bridge {
type Finality = crate::chains::millau_headers_to_rialto::MillauFinalityToRialto;
$generic
}
},
RelayHeadersBridge::RialtoToMillau => {
type Source = relay_rialto_client::Rialto;
type Target = relay_millau_client::Millau;
type Finality = crate::chains::rialto_headers_to_millau::RialtoFinalityToMillau;
$generic
}
},
RelayHeadersBridge::WestendToMillau => {
type Source = relay_westend_client::Westend;
type Target = relay_millau_client::Millau;
type Finality = crate::chains::westend_headers_to_millau::WestendFinalityToMillau;
$generic
}
},
RelayHeadersBridge::RococoToWococo => {
type Source = relay_rococo_client::Rococo;
type Target = relay_wococo_client::Wococo;
type Finality = crate::chains::rococo_headers_to_wococo::RococoFinalityToWococo;
$generic
}
},
RelayHeadersBridge::WococoToRococo => {
type Source = relay_wococo_client::Wococo;
type Target = relay_rococo_client::Rococo;
type Finality = crate::chains::wococo_headers_to_rococo::WococoFinalityToRococo;
$generic
}
},
RelayHeadersBridge::KusamaToPolkadot => {
type Source = relay_kusama_client::Kusama;
type Target = relay_polkadot_client::Polkadot;
type Finality = crate::chains::kusama_headers_to_polkadot::KusamaFinalityToPolkadot;
$generic
},
RelayHeadersBridge::PolkadotToKusama => {
type Source = relay_polkadot_client::Polkadot;
type Target = relay_kusama_client::Kusama;
type Finality = crate::chains::polkadot_headers_to_kusama::PolkadotFinalityToKusama;
$generic
},
}
};
}
@@ -98,16 +118,18 @@ impl RelayHeaders {
select_bridge!(self.bridge, {
let source_client = self.source.to_client::<Source>().await?;
let target_client = self.target.to_client::<Target>().await?;
let target_transactions_mortality = self.target_sign.target_transactions_mortality;
let target_sign = self.target_sign.to_keypair::<Target>()?;
let metrics_params = Finality::customize_metrics(self.prometheus_params.into())?;
let finality = Finality::new(target_client.clone(), target_sign);
finality.start_relay_guards();
crate::finality_pipeline::run(
substrate_relay_helper::finality_pipeline::run(
finality,
source_client,
target_client,
self.only_mandatory_headers,
target_transactions_mortality,
metrics_params,
)
.await
@@ -22,20 +22,41 @@
//! 2) add `declare_bridge_options!(...)` for the bridge;
//! 3) add bridge support to the `select_bridge! { ... }` macro.
use crate::cli::{CliChain, HexLaneId, PrometheusParams};
use crate::declare_chain_options;
use crate::messages_lane::MessagesRelayParams;
use crate::on_demand_headers::OnDemandHeadersRelay;
use futures::{FutureExt, TryFutureExt};
use relay_utils::metrics::MetricsParams;
use structopt::StructOpt;
use strum::VariantNames;
use codec::Encode;
use messages_relay::relay_strategy::MixStrategy;
use relay_substrate_client::{
AccountIdOf, Chain, Client, TransactionSignScheme, UnsignedTransaction,
};
use relay_utils::metrics::MetricsParams;
use sp_core::{Bytes, Pair};
use substrate_relay_helper::{
messages_lane::{MessagesRelayParams, SubstrateMessageLane},
on_demand_headers::OnDemandHeadersRelay,
};
use crate::{
cli::{relay_messages::RelayerMode, CliChain, HexLaneId, PrometheusParams},
declare_chain_options,
};
/// Maximal allowed conversion rate error ratio (abs(real - stored) / stored) that we allow.
///
/// If it is zero, then transaction will be submitted every time we see difference between
/// stored and real conversion rates. If it is large enough (e.g. > than 10 percents, which is 0.1),
/// then rational relayers may stop relaying messages because they were submitted using
/// lesser conversion rate.
const CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO: f64 = 0.05;
/// Start headers+messages relayer process.
#[derive(StructOpt)]
pub enum RelayHeadersAndMessages {
MillauRialto(MillauRialtoHeadersAndMessages),
RococoWococo(RococoWococoHeadersAndMessages),
KusamaPolkadot(KusamaPolkadotHeadersAndMessages),
}
/// Parameters that have the same names across all bridges.
@@ -44,13 +65,22 @@ pub struct HeadersAndMessagesSharedParams {
/// Hex-encoded lane identifiers that should be served by the complex relay.
#[structopt(long, default_value = "00000000")]
lane: Vec<HexLaneId>,
#[structopt(long, possible_values = RelayerMode::VARIANTS, case_insensitive = true, default_value = "rational")]
relayer_mode: RelayerMode,
/// Create relayers fund accounts on both chains, if it does not exists yet.
#[structopt(long)]
create_relayers_fund_accounts: bool,
/// If passed, only mandatory headers (headers that are changing the GRANDPA authorities set)
/// are relayed.
#[structopt(long)]
only_mandatory_headers: bool,
#[structopt(flatten)]
prometheus_params: PrometheusParams,
}
// The reason behind this macro is that 'normal' relays are using source and target chains terminology,
// which is unusable for both-way relays (if you're relaying headers from Rialto to Millau and from
// Millau to Rialto, then which chain is source?).
// The reason behind this macro is that 'normal' relays are using source and target chains
// terminology, which is unusable for both-way relays (if you're relaying headers from Rialto to
// Millau and from Millau to Rialto, then which chain is source?).
macro_rules! declare_bridge_options {
($chain1:ident, $chain2:ident) => {
paste::item! {
@@ -64,12 +94,15 @@ macro_rules! declare_bridge_options {
#[structopt(flatten)]
left_sign: [<$chain1 SigningParams>],
#[structopt(flatten)]
left_messages_pallet_owner: [<$chain1 MessagesPalletOwnerSigningParams>],
#[structopt(flatten)]
right: [<$chain2 ConnectionParams>],
#[structopt(flatten)]
right_sign: [<$chain2 SigningParams>],
#[structopt(flatten)]
right_messages_pallet_owner: [<$chain2 MessagesPalletOwnerSigningParams>],
}
#[allow(unreachable_patterns)]
impl From<RelayHeadersAndMessages> for [<$chain1 $chain2 HeadersAndMessages>] {
fn from(relay_params: RelayHeadersAndMessages) -> [<$chain1 $chain2 HeadersAndMessages>] {
match relay_params {
@@ -91,40 +124,223 @@ macro_rules! select_bridge {
type Left = relay_millau_client::Millau;
type Right = relay_rialto_client::Rialto;
type LeftToRightFinality = crate::chains::millau_headers_to_rialto::MillauFinalityToRialto;
type RightToLeftFinality = crate::chains::rialto_headers_to_millau::RialtoFinalityToMillau;
type LeftToRightFinality =
crate::chains::millau_headers_to_rialto::MillauFinalityToRialto;
type RightToLeftFinality =
crate::chains::rialto_headers_to_millau::RialtoFinalityToMillau;
type LeftToRightMessages = crate::chains::millau_messages_to_rialto::MillauMessagesToRialto;
type RightToLeftMessages = crate::chains::rialto_messages_to_millau::RialtoMessagesToMillau;
type LeftToRightMessages =
crate::chains::millau_messages_to_rialto::MillauMessagesToRialto;
type RightToLeftMessages =
crate::chains::rialto_messages_to_millau::RialtoMessagesToMillau;
const MAX_MISSING_LEFT_HEADERS_AT_RIGHT: bp_millau::BlockNumber = bp_millau::SESSION_LENGTH;
const MAX_MISSING_RIGHT_HEADERS_AT_LEFT: bp_rialto::BlockNumber = bp_rialto::SESSION_LENGTH;
type LeftAccountIdConverter = bp_millau::AccountIdConverter;
type RightAccountIdConverter = bp_rialto::AccountIdConverter;
use crate::chains::millau_messages_to_rialto::run as left_to_right_messages;
use crate::chains::rialto_messages_to_millau::run as right_to_left_messages;
const MAX_MISSING_LEFT_HEADERS_AT_RIGHT: bp_millau::BlockNumber =
bp_millau::SESSION_LENGTH;
const MAX_MISSING_RIGHT_HEADERS_AT_LEFT: bp_rialto::BlockNumber =
bp_rialto::SESSION_LENGTH;
use crate::chains::{
millau_messages_to_rialto::{
add_standalone_metrics as add_left_to_right_standalone_metrics,
run as left_to_right_messages,
update_rialto_to_millau_conversion_rate as update_right_to_left_conversion_rate,
},
rialto_messages_to_millau::{
add_standalone_metrics as add_right_to_left_standalone_metrics,
run as right_to_left_messages,
update_millau_to_rialto_conversion_rate as update_left_to_right_conversion_rate,
},
};
async fn left_create_account(
_left_client: Client<Left>,
_left_sign: <Left as TransactionSignScheme>::AccountKeyPair,
_account_id: AccountIdOf<Left>,
) -> anyhow::Result<()> {
Err(anyhow::format_err!("Account creation is not supported by this bridge"))
}
async fn right_create_account(
_right_client: Client<Right>,
_right_sign: <Right as TransactionSignScheme>::AccountKeyPair,
_account_id: AccountIdOf<Right>,
) -> anyhow::Result<()> {
Err(anyhow::format_err!("Account creation is not supported by this bridge"))
}
$generic
}
},
RelayHeadersAndMessages::RococoWococo(_) => {
type Params = RococoWococoHeadersAndMessages;
type Left = relay_rococo_client::Rococo;
type Right = relay_wococo_client::Wococo;
type LeftToRightFinality = crate::chains::rococo_headers_to_wococo::RococoFinalityToWococo;
type RightToLeftFinality = crate::chains::wococo_headers_to_rococo::WococoFinalityToRococo;
type LeftToRightFinality =
crate::chains::rococo_headers_to_wococo::RococoFinalityToWococo;
type RightToLeftFinality =
crate::chains::wococo_headers_to_rococo::WococoFinalityToRococo;
type LeftToRightMessages = crate::chains::rococo_messages_to_wococo::RococoMessagesToWococo;
type RightToLeftMessages = crate::chains::wococo_messages_to_rococo::WococoMessagesToRococo;
type LeftToRightMessages =
crate::chains::rococo_messages_to_wococo::RococoMessagesToWococo;
type RightToLeftMessages =
crate::chains::wococo_messages_to_rococo::WococoMessagesToRococo;
const MAX_MISSING_LEFT_HEADERS_AT_RIGHT: bp_rococo::BlockNumber = bp_rococo::SESSION_LENGTH;
const MAX_MISSING_RIGHT_HEADERS_AT_LEFT: bp_wococo::BlockNumber = bp_wococo::SESSION_LENGTH;
type LeftAccountIdConverter = bp_rococo::AccountIdConverter;
type RightAccountIdConverter = bp_wococo::AccountIdConverter;
use crate::chains::rococo_messages_to_wococo::run as left_to_right_messages;
use crate::chains::wococo_messages_to_rococo::run as right_to_left_messages;
const MAX_MISSING_LEFT_HEADERS_AT_RIGHT: bp_rococo::BlockNumber =
bp_rococo::SESSION_LENGTH;
const MAX_MISSING_RIGHT_HEADERS_AT_LEFT: bp_wococo::BlockNumber =
bp_wococo::SESSION_LENGTH;
use crate::chains::{
rococo_messages_to_wococo::{
add_standalone_metrics as add_left_to_right_standalone_metrics,
run as left_to_right_messages,
},
wococo_messages_to_rococo::{
add_standalone_metrics as add_right_to_left_standalone_metrics,
run as right_to_left_messages,
},
};
async fn update_right_to_left_conversion_rate(
_client: Client<Left>,
_signer: <Left as TransactionSignScheme>::AccountKeyPair,
_updated_rate: f64,
) -> anyhow::Result<()> {
Err(anyhow::format_err!("Conversion rate is not supported by this bridge"))
}
async fn update_left_to_right_conversion_rate(
_client: Client<Right>,
_signer: <Right as TransactionSignScheme>::AccountKeyPair,
_updated_rate: f64,
) -> anyhow::Result<()> {
Err(anyhow::format_err!("Conversion rate is not supported by this bridge"))
}
async fn left_create_account(
_left_client: Client<Left>,
_left_sign: <Left as TransactionSignScheme>::AccountKeyPair,
_account_id: AccountIdOf<Left>,
) -> anyhow::Result<()> {
Err(anyhow::format_err!("Account creation is not supported by this bridge"))
}
async fn right_create_account(
_right_client: Client<Right>,
_right_sign: <Right as TransactionSignScheme>::AccountKeyPair,
_account_id: AccountIdOf<Right>,
) -> anyhow::Result<()> {
Err(anyhow::format_err!("Account creation is not supported by this bridge"))
}
$generic
}
},
RelayHeadersAndMessages::KusamaPolkadot(_) => {
type Params = KusamaPolkadotHeadersAndMessages;
type Left = relay_kusama_client::Kusama;
type Right = relay_polkadot_client::Polkadot;
type LeftToRightFinality =
crate::chains::kusama_headers_to_polkadot::KusamaFinalityToPolkadot;
type RightToLeftFinality =
crate::chains::polkadot_headers_to_kusama::PolkadotFinalityToKusama;
type LeftToRightMessages =
crate::chains::kusama_messages_to_polkadot::KusamaMessagesToPolkadot;
type RightToLeftMessages =
crate::chains::polkadot_messages_to_kusama::PolkadotMessagesToKusama;
type LeftAccountIdConverter = bp_kusama::AccountIdConverter;
type RightAccountIdConverter = bp_polkadot::AccountIdConverter;
const MAX_MISSING_LEFT_HEADERS_AT_RIGHT: bp_kusama::BlockNumber =
bp_kusama::SESSION_LENGTH;
const MAX_MISSING_RIGHT_HEADERS_AT_LEFT: bp_polkadot::BlockNumber =
bp_polkadot::SESSION_LENGTH;
use crate::chains::{
kusama_messages_to_polkadot::{
add_standalone_metrics as add_left_to_right_standalone_metrics,
run as left_to_right_messages,
update_polkadot_to_kusama_conversion_rate as update_right_to_left_conversion_rate,
},
polkadot_messages_to_kusama::{
add_standalone_metrics as add_right_to_left_standalone_metrics,
run as right_to_left_messages,
update_kusama_to_polkadot_conversion_rate as update_left_to_right_conversion_rate,
},
};
async fn left_create_account(
left_client: Client<Left>,
left_sign: <Left as TransactionSignScheme>::AccountKeyPair,
account_id: AccountIdOf<Left>,
) -> anyhow::Result<()> {
let left_genesis_hash = *left_client.genesis_hash();
left_client
.submit_signed_extrinsic(
left_sign.public().into(),
move |_, transaction_nonce| {
Bytes(
Left::sign_transaction(left_genesis_hash, &left_sign, relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
relay_kusama_client::runtime::Call::Balances(
relay_kusama_client::runtime::BalancesCall::transfer(
bp_kusama::AccountAddress::Id(account_id),
bp_kusama::EXISTENTIAL_DEPOSIT.into(),
),
),
transaction_nonce,
),
).encode()
)
},
)
.await
.map(drop)
.map_err(|e| anyhow::format_err!("{}", e))
}
async fn right_create_account(
right_client: Client<Right>,
right_sign: <Right as TransactionSignScheme>::AccountKeyPair,
account_id: AccountIdOf<Right>,
) -> anyhow::Result<()> {
let right_genesis_hash = *right_client.genesis_hash();
right_client
.submit_signed_extrinsic(
right_sign.public().into(),
move |_, transaction_nonce| {
Bytes(
Right::sign_transaction(right_genesis_hash, &right_sign, relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
relay_polkadot_client::runtime::Call::Balances(
relay_polkadot_client::runtime::BalancesCall::transfer(
bp_polkadot::AccountAddress::Id(account_id),
bp_polkadot::EXISTENTIAL_DEPOSIT.into(),
),
),
transaction_nonce,
),
).encode()
)
},
)
.await
.map(drop)
.map_err(|e| anyhow::format_err!("{}", e))
}
$generic
},
}
};
}
@@ -134,9 +350,12 @@ declare_chain_options!(Millau, millau);
declare_chain_options!(Rialto, rialto);
declare_chain_options!(Rococo, rococo);
declare_chain_options!(Wococo, wococo);
declare_chain_options!(Kusama, kusama);
declare_chain_options!(Polkadot, polkadot);
// All supported bridges.
declare_bridge_options!(Millau, Rialto);
declare_bridge_options!(Rococo, Wococo);
declare_bridge_options!(Kusama, Polkadot);
impl RelayHeadersAndMessages {
/// Run the command.
@@ -145,26 +364,147 @@ impl RelayHeadersAndMessages {
let params: Params = self.into();
let left_client = params.left.to_client::<Left>().await?;
let left_transactions_mortality = params.left_sign.transactions_mortality()?;
let left_sign = params.left_sign.to_keypair::<Left>()?;
let left_messages_pallet_owner =
params.left_messages_pallet_owner.to_keypair::<Left>()?;
let right_client = params.right.to_client::<Right>().await?;
let right_transactions_mortality = params.right_sign.transactions_mortality()?;
let right_sign = params.right_sign.to_keypair::<Right>()?;
let right_messages_pallet_owner =
params.right_messages_pallet_owner.to_keypair::<Right>()?;
let lanes = params.shared.lane;
let relayer_mode = params.shared.relayer_mode.into();
let relay_strategy = MixStrategy::new(relayer_mode);
const METRIC_IS_SOME_PROOF: &str =
"it is `None` when metric has been already registered; \
this is the command entrypoint, so nothing has been registered yet; \
qed";
let metrics_params: MetricsParams = params.shared.prometheus_params.into();
let metrics_params = relay_utils::relay_metrics(None, metrics_params).into_params();
let (metrics_params, left_to_right_metrics) =
add_left_to_right_standalone_metrics(None, metrics_params, left_client.clone())?;
let (metrics_params, right_to_left_metrics) =
add_right_to_left_standalone_metrics(None, metrics_params, right_client.clone())?;
if let Some(left_messages_pallet_owner) = left_messages_pallet_owner {
let left_client = left_client.clone();
substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop(
left_to_right_metrics
.target_to_source_conversion_rate
.expect(METRIC_IS_SOME_PROOF),
left_to_right_metrics
.target_to_base_conversion_rate
.clone()
.expect(METRIC_IS_SOME_PROOF),
left_to_right_metrics
.source_to_base_conversion_rate
.clone()
.expect(METRIC_IS_SOME_PROOF),
CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
move |new_rate| {
log::info!(
target: "bridge",
"Going to update {} -> {} (on {}) conversion rate to {}.",
Right::NAME,
Left::NAME,
Left::NAME,
new_rate,
);
update_right_to_left_conversion_rate(
left_client.clone(),
left_messages_pallet_owner.clone(),
new_rate,
)
},
);
}
if let Some(right_messages_pallet_owner) = right_messages_pallet_owner {
let right_client = right_client.clone();
substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop(
right_to_left_metrics
.target_to_source_conversion_rate
.expect(METRIC_IS_SOME_PROOF),
left_to_right_metrics
.source_to_base_conversion_rate
.expect(METRIC_IS_SOME_PROOF),
left_to_right_metrics
.target_to_base_conversion_rate
.expect(METRIC_IS_SOME_PROOF),
CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
move |new_rate| {
log::info!(
target: "bridge",
"Going to update {} -> {} (on {}) conversion rate to {}.",
Left::NAME,
Right::NAME,
Right::NAME,
new_rate,
);
update_left_to_right_conversion_rate(
right_client.clone(),
right_messages_pallet_owner.clone(),
new_rate,
)
},
);
}
if params.shared.create_relayers_fund_accounts {
let relayer_fund_acount_id = pallet_bridge_messages::relayer_fund_account_id::<
AccountIdOf<Left>,
LeftAccountIdConverter,
>();
let relayers_fund_account_balance =
left_client.free_native_balance(relayer_fund_acount_id.clone()).await;
if let Err(relay_substrate_client::Error::AccountDoesNotExist) =
relayers_fund_account_balance
{
log::info!(target: "bridge", "Going to create relayers fund account at {}.", Left::NAME);
left_create_account(
left_client.clone(),
left_sign.clone(),
relayer_fund_acount_id,
)
.await?;
}
let relayer_fund_acount_id = pallet_bridge_messages::relayer_fund_account_id::<
AccountIdOf<Right>,
RightAccountIdConverter,
>();
let relayers_fund_account_balance =
right_client.free_native_balance(relayer_fund_acount_id.clone()).await;
if let Err(relay_substrate_client::Error::AccountDoesNotExist) =
relayers_fund_account_balance
{
log::info!(target: "bridge", "Going to create relayers fund account at {}.", Right::NAME);
right_create_account(
right_client.clone(),
right_sign.clone(),
relayer_fund_acount_id,
)
.await?;
}
}
let left_to_right_on_demand_headers = OnDemandHeadersRelay::new(
left_client.clone(),
right_client.clone(),
right_transactions_mortality,
LeftToRightFinality::new(right_client.clone(), right_sign.clone()),
MAX_MISSING_LEFT_HEADERS_AT_RIGHT,
params.shared.only_mandatory_headers,
);
let right_to_left_on_demand_headers = OnDemandHeadersRelay::new(
right_client.clone(),
left_client.clone(),
left_transactions_mortality,
RightToLeftFinality::new(left_client.clone(), left_sign.clone()),
MAX_MISSING_RIGHT_HEADERS_AT_LEFT,
params.shared.only_mandatory_headers,
);
// Need 2x capacity since we consider both directions for each lane
@@ -174,28 +514,38 @@ impl RelayHeadersAndMessages {
let left_to_right_messages = left_to_right_messages(MessagesRelayParams {
source_client: left_client.clone(),
source_sign: left_sign.clone(),
source_transactions_mortality: left_transactions_mortality,
target_client: right_client.clone(),
target_sign: right_sign.clone(),
target_transactions_mortality: right_transactions_mortality,
source_to_target_headers_relay: Some(left_to_right_on_demand_headers.clone()),
target_to_source_headers_relay: Some(right_to_left_on_demand_headers.clone()),
lane_id: lane,
metrics_params: metrics_params.clone().disable().metrics_prefix(
messages_relay::message_lane_loop::metrics_prefix::<LeftToRightMessages>(&lane),
messages_relay::message_lane_loop::metrics_prefix::<
<LeftToRightMessages as SubstrateMessageLane>::MessageLane,
>(&lane),
),
relay_strategy: relay_strategy.clone(),
})
.map_err(|e| anyhow::format_err!("{}", e))
.boxed();
let right_to_left_messages = right_to_left_messages(MessagesRelayParams {
source_client: right_client.clone(),
source_sign: right_sign.clone(),
source_transactions_mortality: right_transactions_mortality,
target_client: left_client.clone(),
target_sign: left_sign.clone(),
target_transactions_mortality: left_transactions_mortality,
source_to_target_headers_relay: Some(right_to_left_on_demand_headers.clone()),
target_to_source_headers_relay: Some(left_to_right_on_demand_headers.clone()),
lane_id: lane,
metrics_params: metrics_params.clone().disable().metrics_prefix(
messages_relay::message_lane_loop::metrics_prefix::<RightToLeftMessages>(&lane),
messages_relay::message_lane_loop::metrics_prefix::<
<RightToLeftMessages as SubstrateMessageLane>::MessageLane,
>(&lane),
),
relay_strategy: relay_strategy.clone(),
})
.map_err(|e| anyhow::format_err!("{}", e))
.boxed();
@@ -14,25 +14,51 @@
// 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/>.
use crate::cli::bridge::FullBridge;
use crate::cli::{
HexLaneId, PrometheusParams, SourceConnectionParams, SourceSigningParams, TargetConnectionParams,
TargetSigningParams,
};
use crate::messages_lane::MessagesRelayParams;
use crate::select_full_bridge;
use structopt::StructOpt;
use strum::{EnumString, EnumVariantNames, VariantNames};
use messages_relay::relay_strategy::MixStrategy;
use substrate_relay_helper::messages_lane::MessagesRelayParams;
use crate::{
cli::{
bridge::FullBridge, HexLaneId, PrometheusParams, SourceConnectionParams,
SourceSigningParams, TargetConnectionParams, TargetSigningParams,
},
select_full_bridge,
};
/// Relayer operating mode.
#[derive(Debug, EnumString, EnumVariantNames, Clone, Copy, PartialEq)]
#[strum(serialize_all = "kebab_case")]
pub enum RelayerMode {
/// The relayer doesn't care about rewards.
Altruistic,
/// The relayer will deliver all messages and confirmations as long as he's not losing any
/// funds.
Rational,
}
impl From<RelayerMode> for messages_relay::message_lane_loop::RelayerMode {
fn from(mode: RelayerMode) -> Self {
match mode {
RelayerMode::Altruistic => Self::Altruistic,
RelayerMode::Rational => Self::Rational,
}
}
}
/// Start messages relayer process.
#[derive(StructOpt)]
pub struct RelayMessages {
/// A bridge instance to relay messages for.
#[structopt(possible_values = &FullBridge::variants(), case_insensitive = true)]
#[structopt(possible_values = FullBridge::VARIANTS, case_insensitive = true)]
bridge: FullBridge,
/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
#[structopt(long, default_value = "00000000")]
lane: HexLaneId,
#[structopt(long, possible_values = RelayerMode::VARIANTS, case_insensitive = true, default_value = "rational")]
relayer_mode: RelayerMode,
#[structopt(flatten)]
source: SourceConnectionParams,
#[structopt(flatten)]
@@ -51,21 +77,68 @@ impl RelayMessages {
select_full_bridge!(self.bridge, {
let source_client = self.source.to_client::<Source>().await?;
let source_sign = self.source_sign.to_keypair::<Source>()?;
let source_transactions_mortality = self.source_sign.transactions_mortality()?;
let target_client = self.target.to_client::<Target>().await?;
let target_sign = self.target_sign.to_keypair::<Target>()?;
let target_transactions_mortality = self.target_sign.transactions_mortality()?;
let relayer_mode = self.relayer_mode.into();
let relay_strategy = MixStrategy::new(relayer_mode);
relay_messages(MessagesRelayParams {
source_client,
source_sign,
source_transactions_mortality,
target_client,
target_sign,
target_transactions_mortality,
source_to_target_headers_relay: None,
target_to_source_headers_relay: None,
lane_id: self.lane.into(),
metrics_params: self.prometheus_params.into(),
relay_strategy,
})
.await
.map_err(|e| anyhow::format_err!("{}", e))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_use_rational_relayer_mode_by_default() {
assert_eq!(
RelayMessages::from_iter(vec![
"relay-messages",
"rialto-to-millau",
"--source-port=0",
"--source-signer=//Alice",
"--target-port=0",
"--target-signer=//Alice",
"--lane=00000000",
])
.relayer_mode,
RelayerMode::Rational,
);
}
#[test]
fn should_accept_altruistic_relayer_mode() {
assert_eq!(
RelayMessages::from_iter(vec![
"relay-messages",
"rialto-to-millau",
"--source-port=0",
"--source-signer=//Alice",
"--target-port=0",
"--target-signer=//Alice",
"--lane=00000000",
"--relayer-mode=altruistic",
])
.relayer_mode,
RelayerMode::Altruistic,
);
}
}
@@ -0,0 +1,559 @@
// 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/>.
use crate::cli::{Balance, TargetConnectionParams, TargetSigningParams};
use codec::{Decode, Encode};
use num_traits::{One, Zero};
use relay_substrate_client::{
BlockWithJustification, Chain, Client, Error as SubstrateError, HeaderOf, TransactionSignScheme,
};
use relay_utils::FailedClient;
use sp_core::Bytes;
use sp_runtime::{
traits::{Hash, Header as HeaderT},
transaction_validity::TransactionPriority,
};
use structopt::StructOpt;
use strum::{EnumString, EnumVariantNames, VariantNames};
/// Start resubmit transactions process.
#[derive(StructOpt)]
pub struct ResubmitTransactions {
/// A bridge instance to relay headers for.
#[structopt(possible_values = RelayChain::VARIANTS, case_insensitive = true)]
chain: RelayChain,
#[structopt(flatten)]
target: TargetConnectionParams,
#[structopt(flatten)]
target_sign: TargetSigningParams,
/// Number of blocks we see before considering queued transaction as stalled.
#[structopt(long, default_value = "5")]
stalled_blocks: u32,
/// Tip limit. We'll never submit transaction with larger tip.
#[structopt(long)]
tip_limit: Balance,
/// Tip increase step. We'll be checking updated transaction priority by increasing its tip by
/// this step.
#[structopt(long)]
tip_step: Balance,
/// Priority selection strategy.
#[structopt(subcommand)]
strategy: PrioritySelectionStrategy,
}
/// Chain, which transactions we're going to track && resubmit.
#[derive(Debug, EnumString, EnumVariantNames)]
#[strum(serialize_all = "kebab_case")]
pub enum RelayChain {
Millau,
Kusama,
Polkadot,
}
/// Strategy to use for priority selection.
#[derive(StructOpt, Debug, PartialEq, Eq, Clone, Copy)]
pub enum PrioritySelectionStrategy {
/// Strategy selects tip that changes transaction priority to be better than priority of
/// the first transaction of previous block.
///
/// It only makes sense to use this strategy for Millau transactions. Millau has transactions
/// that are close to block limits, so if there are any other queued transactions, 'large'
/// transaction won't fit the block && will be postponed. To avoid this, we change its priority
/// to some large value, making it best transaction => it'll be 'mined' first.
MakeItBestTransaction,
/// Strategy selects tip that changes transaction priority to be better than priority of
/// selected queued transaction.
///
/// When we first see stalled transaction, we make it better than worst 1/4 of queued
/// transactions. If it is still stalled, we'll make it better than 1/3 of queued transactions,
/// ...
MakeItBetterThanQueuedTransaction,
}
macro_rules! select_bridge {
($bridge: expr, $generic: tt) => {
match $bridge {
RelayChain::Millau => {
type Target = relay_millau_client::Millau;
type TargetSign = relay_millau_client::Millau;
$generic
},
RelayChain::Kusama => {
type Target = relay_kusama_client::Kusama;
type TargetSign = relay_kusama_client::Kusama;
$generic
},
RelayChain::Polkadot => {
type Target = relay_polkadot_client::Polkadot;
type TargetSign = relay_polkadot_client::Polkadot;
$generic
},
}
};
}
impl ResubmitTransactions {
/// Run the command.
pub async fn run(self) -> anyhow::Result<()> {
select_bridge!(self.chain, {
let relay_loop_name = format!("ResubmitTransactions{}", Target::NAME);
let client = self.target.to_client::<Target>().await?;
let key_pair = self.target_sign.to_keypair::<Target>()?;
relay_utils::relay_loop((), client)
.run(relay_loop_name, move |_, client, _| {
run_until_connection_lost::<Target, TargetSign>(
client,
key_pair.clone(),
Context {
strategy: self.strategy,
best_header: HeaderOf::<Target>::new(
Default::default(),
Default::default(),
Default::default(),
Default::default(),
Default::default(),
),
transaction: None,
resubmitted: 0,
stalled_for: Zero::zero(),
stalled_for_limit: self.stalled_blocks as _,
tip_step: self.tip_step.cast() as _,
tip_limit: self.tip_limit.cast() as _,
},
)
})
.await
.map_err(Into::into)
})
}
}
impl PrioritySelectionStrategy {
/// Select target priority.
async fn select_target_priority<C: Chain, S: TransactionSignScheme<Chain = C>>(
&self,
client: &Client<C>,
context: &Context<C>,
) -> Result<Option<TransactionPriority>, SubstrateError> {
match *self {
PrioritySelectionStrategy::MakeItBestTransaction =>
read_previous_block_best_priority::<C, S>(client, context).await,
PrioritySelectionStrategy::MakeItBetterThanQueuedTransaction =>
select_priority_from_queue::<C, S>(client, context).await,
}
}
}
#[derive(Debug)]
struct Context<C: Chain> {
/// Priority selection strategy.
strategy: PrioritySelectionStrategy,
/// Best known block header.
best_header: C::Header,
/// Hash of the (potentially) stalled transaction.
transaction: Option<C::Hash>,
/// How many times we have resubmitted this `transaction`?
resubmitted: u32,
/// This transaction is in pool for `stalled_for` wakeup intervals.
stalled_for: C::BlockNumber,
/// When `stalled_for` reaching this limit, transaction is considered stalled.
stalled_for_limit: C::BlockNumber,
/// Tip step interval.
tip_step: C::Balance,
/// Maximal tip.
tip_limit: C::Balance,
}
impl<C: Chain> Context<C> {
/// Return true if transaction has stalled.
fn is_stalled(&self) -> bool {
self.stalled_for >= self.stalled_for_limit
}
/// Notice resubmitted transaction.
fn notice_resubmitted_transaction(mut self, transaction: C::Hash) -> Self {
self.transaction = Some(transaction);
self.stalled_for = Zero::zero();
self.resubmitted += 1;
self
}
/// Notice transaction from the transaction pool.
fn notice_transaction(mut self, transaction: C::Hash) -> Self {
if self.transaction == Some(transaction) {
self.stalled_for += One::one();
} else {
self.transaction = Some(transaction);
self.stalled_for = One::one();
self.resubmitted = 0;
}
self
}
}
/// Run resubmit transactions loop.
async fn run_until_connection_lost<C: Chain, S: TransactionSignScheme<Chain = C>>(
client: Client<C>,
key_pair: S::AccountKeyPair,
mut context: Context<C>,
) -> Result<(), FailedClient> {
loop {
async_std::task::sleep(C::AVERAGE_BLOCK_INTERVAL).await;
let result = run_loop_iteration::<C, S>(client.clone(), key_pair.clone(), context).await;
context = match result {
Ok(context) => context,
Err(error) => {
log::error!(
target: "bridge",
"Resubmit {} transactions loop has failed with error: {:?}",
C::NAME,
error,
);
return Err(FailedClient::Target)
},
};
}
}
/// Run single loop iteration.
async fn run_loop_iteration<C: Chain, S: TransactionSignScheme<Chain = C>>(
client: Client<C>,
key_pair: S::AccountKeyPair,
mut context: Context<C>,
) -> Result<Context<C>, SubstrateError> {
// correct best header is required for all other actions
context.best_header = client.best_header().await?;
// check if there's queued transaction, signed by given author
let original_transaction = match lookup_signer_transaction::<C, S>(&client, &key_pair).await? {
Some(original_transaction) => original_transaction,
None => {
log::trace!(target: "bridge", "No {} transactions from required signer in the txpool", C::NAME);
return Ok(context)
},
};
let original_transaction_hash = C::Hasher::hash(&original_transaction.encode());
let context = context.notice_transaction(original_transaction_hash);
// if transaction hasn't been mined for `stalled_blocks`, we'll need to resubmit it
if !context.is_stalled() {
log::trace!(
target: "bridge",
"{} transaction {:?} is not yet stalled ({:?}/{:?})",
C::NAME,
context.transaction,
context.stalled_for,
context.stalled_for_limit,
);
return Ok(context)
}
// select priority for updated transaction
let target_priority =
match context.strategy.select_target_priority::<C, S>(&client, &context).await? {
Some(target_priority) => target_priority,
None => {
log::trace!(target: "bridge", "Failed to select target priority");
return Ok(context)
},
};
// update transaction tip
let (is_updated, updated_transaction) = update_transaction_tip::<C, S>(
&client,
&key_pair,
context.best_header.hash(),
original_transaction,
context.tip_step,
context.tip_limit,
target_priority,
)
.await?;
if !is_updated {
log::trace!(target: "bridge", "{} transaction tip can not be updated. Reached limit?", C::NAME);
return Ok(context)
}
let updated_transaction = updated_transaction.encode();
let updated_transaction_hash = C::Hasher::hash(&updated_transaction);
client.submit_unsigned_extrinsic(Bytes(updated_transaction)).await?;
log::info!(
target: "bridge",
"Replaced {} transaction {} with {} in txpool",
C::NAME,
original_transaction_hash,
updated_transaction_hash,
);
Ok(context.notice_resubmitted_transaction(updated_transaction_hash))
}
/// Search transaction pool for transaction, signed by given key pair.
async fn lookup_signer_transaction<C: Chain, S: TransactionSignScheme<Chain = C>>(
client: &Client<C>,
key_pair: &S::AccountKeyPair,
) -> Result<Option<S::SignedTransaction>, SubstrateError> {
let pending_transactions = client.pending_extrinsics().await?;
for pending_transaction in pending_transactions {
let pending_transaction = S::SignedTransaction::decode(&mut &pending_transaction.0[..])
.map_err(SubstrateError::ResponseParseFailed)?;
if !S::is_signed_by(key_pair, &pending_transaction) {
continue
}
return Ok(Some(pending_transaction))
}
Ok(None)
}
/// Read priority of best signed transaction of previous block.
async fn read_previous_block_best_priority<C: Chain, S: TransactionSignScheme<Chain = C>>(
client: &Client<C>,
context: &Context<C>,
) -> Result<Option<TransactionPriority>, SubstrateError> {
let best_block = client.get_block(Some(context.best_header.hash())).await?;
let best_transaction = best_block
.extrinsics()
.iter()
.filter_map(|xt| S::SignedTransaction::decode(&mut &xt[..]).ok())
.find(|xt| S::is_signed(xt));
match best_transaction {
Some(best_transaction) => Ok(Some(
client
.validate_transaction(*context.best_header.parent_hash(), best_transaction)
.await??
.priority,
)),
None => Ok(None),
}
}
/// Select priority of some queued transaction.
async fn select_priority_from_queue<C: Chain, S: TransactionSignScheme<Chain = C>>(
client: &Client<C>,
context: &Context<C>,
) -> Result<Option<TransactionPriority>, SubstrateError> {
// select transaction from the queue
let queued_transactions = client.pending_extrinsics().await?;
let selected_transaction = match select_transaction_from_queue(queued_transactions, context) {
Some(selected_transaction) => selected_transaction,
None => return Ok(None),
};
let selected_transaction = S::SignedTransaction::decode(&mut &selected_transaction[..])
.map_err(SubstrateError::ResponseParseFailed)?;
let target_priority = client
.validate_transaction(context.best_header.hash(), selected_transaction)
.await??
.priority;
Ok(Some(target_priority))
}
/// Select transaction with target priority from the vec of queued transactions.
fn select_transaction_from_queue<C: Chain>(
mut queued_transactions: Vec<Bytes>,
context: &Context<C>,
) -> Option<Bytes> {
if queued_transactions.is_empty() {
return None
}
// the more times we resubmit transaction (`context.resubmitted`), the closer we move
// to the front of the transaction queue
let total_transactions = queued_transactions.len();
let resubmitted_factor = context.resubmitted;
let divisor =
1usize.saturating_add(1usize.checked_shl(resubmitted_factor).unwrap_or(usize::MAX));
let transactions_to_skip = total_transactions / divisor;
Some(
queued_transactions
.swap_remove(std::cmp::min(total_transactions - 1, transactions_to_skip)),
)
}
/// Try to find appropriate tip for transaction so that its priority is larger than given.
async fn update_transaction_tip<C: Chain, S: TransactionSignScheme<Chain = C>>(
client: &Client<C>,
key_pair: &S::AccountKeyPair,
at_block: C::Hash,
tx: S::SignedTransaction,
tip_step: C::Balance,
tip_limit: C::Balance,
target_priority: TransactionPriority,
) -> Result<(bool, S::SignedTransaction), SubstrateError> {
let stx = format!("{:?}", tx);
let mut current_priority = client.validate_transaction(at_block, tx.clone()).await??.priority;
let mut unsigned_tx = S::parse_transaction(tx).ok_or_else(|| {
SubstrateError::Custom(format!("Failed to parse {} transaction {}", C::NAME, stx,))
})?;
let old_tip = unsigned_tx.tip;
while current_priority < target_priority {
let next_tip = unsigned_tx.tip + tip_step;
if next_tip > tip_limit {
break
}
log::trace!(
target: "bridge",
"{} transaction priority with tip={:?}: {}. Target priority: {}",
C::NAME,
unsigned_tx.tip,
current_priority,
target_priority,
);
unsigned_tx.tip = next_tip;
current_priority = client
.validate_transaction(
at_block,
S::sign_transaction(
*client.genesis_hash(),
key_pair,
relay_substrate_client::TransactionEra::immortal(),
unsigned_tx.clone(),
),
)
.await??
.priority;
}
log::debug!(
target: "bridge",
"{} transaction tip has changed from {:?} to {:?}",
C::NAME,
old_tip,
unsigned_tx.tip,
);
Ok((
old_tip != unsigned_tx.tip,
S::sign_transaction(
*client.genesis_hash(),
key_pair,
relay_substrate_client::TransactionEra::immortal(),
unsigned_tx,
),
))
}
#[cfg(test)]
mod tests {
use super::*;
use bp_rialto::Hash;
use relay_rialto_client::Rialto;
fn context() -> Context<Rialto> {
Context {
strategy: PrioritySelectionStrategy::MakeItBestTransaction,
best_header: HeaderOf::<Rialto>::new(
Default::default(),
Default::default(),
Default::default(),
Default::default(),
Default::default(),
),
transaction: None,
resubmitted: 0,
stalled_for: Zero::zero(),
stalled_for_limit: 3,
tip_step: 100,
tip_limit: 1000,
}
}
#[test]
fn context_works() {
let mut context = context();
// when transaction is noticed 2/3 times, it isn't stalled
context = context.notice_transaction(Default::default());
assert!(!context.is_stalled());
assert_eq!(context.stalled_for, 1);
assert_eq!(context.resubmitted, 0);
context = context.notice_transaction(Default::default());
assert!(!context.is_stalled());
assert_eq!(context.stalled_for, 2);
assert_eq!(context.resubmitted, 0);
// when transaction is noticed for 3rd time in a row, it is considered stalled
context = context.notice_transaction(Default::default());
assert!(context.is_stalled());
assert_eq!(context.stalled_for, 3);
assert_eq!(context.resubmitted, 0);
// and after we resubmit it, we forget previous transaction
context = context.notice_resubmitted_transaction(Hash::from([1; 32]));
assert_eq!(context.transaction, Some(Hash::from([1; 32])));
assert_eq!(context.resubmitted, 1);
assert_eq!(context.stalled_for, 0);
}
#[test]
fn select_transaction_from_queue_works_with_empty_queue() {
assert_eq!(select_transaction_from_queue(vec![], &context()), None);
}
#[test]
fn select_transaction_from_queue_works() {
let mut context = context();
let queued_transactions = vec![
Bytes(vec![1]),
Bytes(vec![2]),
Bytes(vec![3]),
Bytes(vec![4]),
Bytes(vec![5]),
Bytes(vec![6]),
];
// when we resubmit tx for the first time, 1/2 of queue is skipped
assert_eq!(
select_transaction_from_queue(queued_transactions.clone(), &context),
Some(Bytes(vec![4])),
);
// when we resubmit tx for the second time, 1/3 of queue is skipped
context = context.notice_resubmitted_transaction(Hash::from([1; 32]));
assert_eq!(
select_transaction_from_queue(queued_transactions.clone(), &context),
Some(Bytes(vec![3])),
);
// when we resubmit tx for the third time, 1/5 of queue is skipped
context = context.notice_resubmitted_transaction(Hash::from([2; 32]));
assert_eq!(
select_transaction_from_queue(queued_transactions.clone(), &context),
Some(Bytes(vec![2])),
);
// when we resubmit tx for the second time, 1/9 of queue is skipped
context = context.notice_resubmitted_transaction(Hash::from([3; 32]));
assert_eq!(
select_transaction_from_queue(queued_transactions, &context),
Some(Bytes(vec![1])),
);
}
}
@@ -14,46 +14,71 @@
// 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/>.
use crate::cli::bridge::FullBridge;
use crate::cli::encode_call::{self, CliEncodeCall};
use crate::cli::estimate_fee::estimate_message_delivery_and_dispatch_fee;
use crate::cli::{
Balance, CliChain, ExplicitOrMaximal, HexBytes, HexLaneId, Origins, SourceConnectionParams, SourceSigningParams,
TargetSigningParams,
bridge::FullBridge,
encode_call::{self, CliEncodeCall},
estimate_fee::estimate_message_delivery_and_dispatch_fee,
Balance, CliChain, ExplicitOrMaximal, HexBytes, HexLaneId, Origins, SourceConnectionParams,
SourceSigningParams, TargetSigningParams,
};
use bp_message_dispatch::{CallOrigin, MessagePayload};
use bp_runtime::messages::DispatchFeePayment;
use bp_runtime::BalanceOf;
use codec::Encode;
use frame_support::weights::Weight;
use relay_substrate_client::{Chain, TransactionSignScheme};
use relay_substrate_client::{Chain, TransactionSignScheme, UnsignedTransaction};
use sp_core::{Bytes, Pair};
use sp_runtime::{traits::IdentifyAccount, AccountId32, MultiSignature, MultiSigner};
use std::fmt::Debug;
use structopt::StructOpt;
use strum::{EnumString, EnumVariantNames, VariantNames};
/// Relayer operating mode.
#[derive(Debug, EnumString, EnumVariantNames, Clone, Copy, PartialEq, Eq)]
#[strum(serialize_all = "kebab_case")]
pub enum DispatchFeePayment {
/// The dispatch fee is paid at the source chain.
AtSourceChain,
/// The dispatch fee is paid at the target chain.
AtTargetChain,
}
impl From<DispatchFeePayment> for bp_runtime::messages::DispatchFeePayment {
fn from(dispatch_fee_payment: DispatchFeePayment) -> Self {
match dispatch_fee_payment {
DispatchFeePayment::AtSourceChain => Self::AtSourceChain,
DispatchFeePayment::AtTargetChain => Self::AtTargetChain,
}
}
}
/// Send bridge message.
#[derive(StructOpt)]
pub struct SendMessage {
/// A bridge instance to encode call for.
#[structopt(possible_values = &FullBridge::variants(), case_insensitive = true)]
#[structopt(possible_values = FullBridge::VARIANTS, case_insensitive = true)]
bridge: FullBridge,
#[structopt(flatten)]
source: SourceConnectionParams,
#[structopt(flatten)]
source_sign: SourceSigningParams,
/// The SURI of secret key to use when transactions are submitted to the Target node.
#[structopt(long, required_if("origin", "Target"))]
target_signer: Option<String>,
/// The password for the SURI of secret key to use when transactions are submitted to the Target node.
#[structopt(long)]
target_signer_password: Option<String>,
#[structopt(flatten)]
target_sign: TargetSigningParams,
/// Hex-encoded lane id. Defaults to `00000000`.
#[structopt(long, default_value = "00000000")]
lane: HexLaneId,
/// Where dispatch fee is paid?
#[structopt(
long,
possible_values = DispatchFeePayment::VARIANTS,
case_insensitive = true,
default_value = "at-source-chain",
)]
dispatch_fee_payment: DispatchFeePayment,
/// Dispatch weight of the message. If not passed, determined automatically.
#[structopt(long)]
dispatch_weight: Option<ExplicitOrMaximal<Weight>>,
/// Delivery and dispatch fee in source chain base currency units. If not passed, determined automatically.
/// Delivery and dispatch fee in source chain base currency units. If not passed, determined
/// automatically.
#[structopt(long)]
fee: Option<Balance>,
/// Message type.
@@ -72,9 +97,9 @@ impl SendMessage {
crate::select_full_bridge!(self.bridge, {
let SendMessage {
source_sign,
target_signer,
target_signer_password,
target_sign,
ref mut message,
dispatch_fee_payment,
dispatch_weight,
origin,
bridge,
@@ -101,12 +126,6 @@ impl SendMessage {
match origin {
Origins::Source => CallOrigin::SourceAccount(source_account_id),
Origins::Target => {
let target_sign = TargetSigningParams {
target_signer: target_signer.clone().ok_or_else(|| {
anyhow::format_err!("The argument target_signer is not available")
})?,
target_signer_password: target_signer_password.clone(),
};
let target_sign = target_sign.to_keypair::<Target>()?;
let digest = account_ownership_digest(
&target_call,
@@ -120,9 +139,10 @@ impl SendMessage {
target_origin_public.into(),
digest_signature.into(),
)
}
},
},
&target_call,
*dispatch_fee_payment,
)
};
Ok(payload)
@@ -141,7 +161,7 @@ impl SendMessage {
let fee = match self.fee {
Some(fee) => fee,
None => Balance(
estimate_message_delivery_and_dispatch_fee::<<Source as Chain>::Balance, _, _>(
estimate_message_delivery_and_dispatch_fee::<BalanceOf<Source>, _, _>(
&source_client,
ESTIMATE_MESSAGE_FEE_METHOD,
lane,
@@ -158,24 +178,46 @@ impl SendMessage {
fee,
})?;
source_client
.submit_signed_extrinsic(source_sign.public().into(), |transaction_nonce| {
let signed_source_call = Source::sign_transaction(
*source_client.genesis_hash(),
let source_genesis_hash = *source_client.genesis_hash();
let estimated_transaction_fee = source_client
.estimate_extrinsic_fee(Bytes(
Source::sign_transaction(
source_genesis_hash,
&source_sign,
transaction_nonce,
send_message_call,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(send_message_call.clone(), 0),
)
.encode(),
))
.await?;
source_client
.submit_signed_extrinsic(source_sign.public().into(), move |_, transaction_nonce| {
let signed_source_call = Source::sign_transaction(
source_genesis_hash,
&source_sign,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(send_message_call, transaction_nonce),
)
.encode();
log::info!(
target: "bridge",
"Sending message to {}. Size: {}. Dispatch weight: {}. Fee: {}",
"Sending message to {}. Lane: {:?}. Size: {}. Dispatch weight: {}. Fee: {}",
Target::NAME,
lane,
signed_source_call.len(),
dispatch_weight,
fee,
);
log::info!(
target: "bridge",
"The source account ({:?}) balance will be reduced by (at most) {} (message fee) + {} (tx fee ) = {} {} tokens",
AccountId32::from(source_sign.public()),
fee.0,
estimated_transaction_fee.inclusion_fee(),
fee.0.saturating_add(estimated_transaction_fee.inclusion_fee() as _),
Source::NAME,
);
log::info!(
target: "bridge",
"Signed {} Call: {:?}",
@@ -197,10 +239,7 @@ fn prepare_call_dispatch_weight(
weight_from_pre_dispatch_call: ExplicitOrMaximal<Weight>,
maximal_allowed_weight: Weight,
) -> Weight {
match user_specified_dispatch_weight
.clone()
.unwrap_or(weight_from_pre_dispatch_call)
{
match user_specified_dispatch_weight.clone().unwrap_or(weight_from_pre_dispatch_call) {
ExplicitOrMaximal::Explicit(weight) => weight,
ExplicitOrMaximal::Maximal => maximal_allowed_weight,
}
@@ -211,6 +250,7 @@ pub(crate) fn message_payload<SAccountId, TPublic, TSignature>(
weight: Weight,
origin: CallOrigin<SAccountId, TPublic, TSignature>,
call: &impl Encode,
dispatch_fee_payment: DispatchFeePayment,
) -> MessagePayload<SAccountId, TPublic, TSignature, Vec<u8>>
where
SAccountId: Encode + Debug,
@@ -222,7 +262,7 @@ where
spec_version,
weight,
origin,
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
dispatch_fee_payment: dispatch_fee_payment.into(),
call: HexBytes::encode(call),
};
@@ -230,24 +270,14 @@ where
log::info!(target: "bridge", "Encoded Message Payload: {:?}", HexBytes::encode(&payload));
// re-pack to return `Vec<u8>`
let MessagePayload {
spec_version,
weight,
origin,
dispatch_fee_payment,
call,
} = payload;
MessagePayload {
spec_version,
weight,
origin,
dispatch_fee_payment,
call: call.0,
}
let MessagePayload { spec_version, weight, origin, dispatch_fee_payment, call } = payload;
MessagePayload { spec_version, weight, origin, dispatch_fee_payment, call: call.0 }
}
pub(crate) fn compute_maximal_message_dispatch_weight(maximal_extrinsic_weight: Weight) -> Weight {
bridge_runtime_common::messages::target::maximal_incoming_message_dispatch_weight(maximal_extrinsic_weight)
bridge_runtime_common::messages::target::maximal_incoming_message_dispatch_weight(
maximal_extrinsic_weight,
)
}
#[cfg(test)]
@@ -260,7 +290,7 @@ mod tests {
// given
let mut send_message = SendMessage::from_iter(vec![
"send-message",
"RialtoToMillau",
"rialto-to-millau",
"--source-port",
"1234",
"--source-signer",
@@ -278,10 +308,12 @@ mod tests {
payload,
MessagePayload {
spec_version: relay_millau_client::Millau::RUNTIME_VERSION.spec_version,
weight: 1038000,
origin: CallOrigin::SourceAccount(sp_keyring::AccountKeyring::Alice.to_account_id()),
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
call: hex!("0401081234").to_vec(),
weight: 576000,
origin: CallOrigin::SourceAccount(
sp_keyring::AccountKeyring::Alice.to_account_id()
),
dispatch_fee_payment: bp_runtime::messages::DispatchFeePayment::AtSourceChain,
call: hex!("0001081234").to_vec(),
}
);
}
@@ -291,7 +323,7 @@ mod tests {
// given
let mut send_message = SendMessage::from_iter(vec![
"send-message",
"MillauToRialto",
"millau-to-rialto",
"--source-port",
"1234",
"--source-signer",
@@ -318,24 +350,24 @@ mod tests {
payload,
MessagePayload {
spec_version: relay_millau_client::Millau::RUNTIME_VERSION.spec_version,
weight: 1038000,
weight: 576000,
origin: CallOrigin::TargetAccount(
sp_keyring::AccountKeyring::Alice.to_account_id(),
sp_keyring::AccountKeyring::Bob.into(),
signature,
),
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
call: hex!("0701081234").to_vec(),
dispatch_fee_payment: bp_runtime::messages::DispatchFeePayment::AtSourceChain,
call: hex!("0001081234").to_vec(),
}
);
}
#[test]
fn target_signer_must_exist_if_origin_is_target() {
fn accepts_send_message_command_without_target_sign_options() {
// given
let send_message = SendMessage::from_iter_safe(vec![
"send-message",
"MillauToRialto",
"rialto-to-millau",
"--source-port",
"1234",
"--source-signer",
@@ -347,6 +379,31 @@ mod tests {
"1234",
]);
assert!(send_message.is_err());
assert!(send_message.is_ok());
}
#[test]
fn accepts_non_default_dispatch_fee_payment() {
// given
let mut send_message = SendMessage::from_iter(vec![
"send-message",
"rialto-to-millau",
"--source-port",
"1234",
"--source-signer",
"//Alice",
"--dispatch-fee-payment",
"at-target-chain",
"remark",
]);
// when
let payload = send_message.encode_payload().unwrap();
// then
assert_eq!(
payload.dispatch_fee_payment,
bp_runtime::messages::DispatchFeePayment::AtTargetChain
);
}
}
@@ -0,0 +1,798 @@
// 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/>.
//! Tokens swap using token-swap bridge pallet.
// TokenSwapBalances fields are never directly accessed, but the whole struct is printed
// to show token swap progress
#![allow(dead_code)]
use codec::Encode;
use num_traits::One;
use rand::random;
use structopt::StructOpt;
use strum::{EnumString, EnumVariantNames, VariantNames};
use frame_support::dispatch::GetDispatchInfo;
use relay_substrate_client::{
AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, CallOf, Chain, ChainWithBalances,
Client, Error as SubstrateError, HashOf, SignatureOf, Subscription, TransactionSignScheme,
TransactionStatusOf, UnsignedTransaction,
};
use sp_core::{blake2_256, storage::StorageKey, Bytes, Pair, H256, U256};
use sp_runtime::traits::{Convert, Header as HeaderT};
use crate::cli::{
Balance, CliChain, SourceConnectionParams, SourceSigningParams, TargetConnectionParams,
TargetSigningParams,
};
/// Swap tokens.
#[derive(StructOpt, Debug, PartialEq)]
pub struct SwapTokens {
/// A bridge instance to use in token swap.
#[structopt(possible_values = SwapTokensBridge::VARIANTS, case_insensitive = true)]
bridge: SwapTokensBridge,
#[structopt(flatten)]
source: SourceConnectionParams,
#[structopt(flatten)]
source_sign: SourceSigningParams,
#[structopt(flatten)]
target: TargetConnectionParams,
#[structopt(flatten)]
target_sign: TargetSigningParams,
#[structopt(subcommand)]
swap_type: TokenSwapType,
/// Source chain balance that source signer wants to swap.
#[structopt(long)]
source_balance: Balance,
/// Target chain balance that target signer wants to swap.
#[structopt(long)]
target_balance: Balance,
}
/// Token swap type.
#[derive(StructOpt, Debug, PartialEq, Eq, Clone)]
pub enum TokenSwapType {
/// The `target_sign` is temporary and only have funds for single swap.
NoLock,
/// This swap type prevents `source_signer` from restarting the swap after it has been
/// completed.
LockUntilBlock {
/// Number of blocks before the swap expires.
#[structopt(long)]
blocks_before_expire: u32,
/// Unique swap nonce.
#[structopt(long)]
swap_nonce: Option<U256>,
},
}
/// Swap tokens bridge.
#[derive(Debug, EnumString, EnumVariantNames, PartialEq)]
#[strum(serialize_all = "kebab_case")]
pub enum SwapTokensBridge {
/// Use token-swap pallet deployed at Millau to swap tokens with Rialto.
MillauToRialto,
}
macro_rules! select_bridge {
($bridge: expr, $generic: tt) => {
match $bridge {
SwapTokensBridge::MillauToRialto => {
type Source = relay_millau_client::Millau;
type Target = relay_rialto_client::Rialto;
type FromSwapToThisAccountIdConverter = bp_rialto::AccountIdConverter;
use bp_millau::{
derive_account_from_rialto_id as derive_source_account_from_target_account,
TO_MILLAU_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_TARGET_TO_SOURCE_MESSAGE_FEE_METHOD,
WITH_RIALTO_TOKEN_SWAP_PALLET_NAME as TOKEN_SWAP_PALLET_NAME,
};
use bp_rialto::{
derive_account_from_millau_id as derive_target_account_from_source_account,
TO_RIALTO_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_SOURCE_TO_TARGET_MESSAGE_FEE_METHOD,
};
const SOURCE_CHAIN_ID: bp_runtime::ChainId = bp_runtime::MILLAU_CHAIN_ID;
const TARGET_CHAIN_ID: bp_runtime::ChainId = bp_runtime::RIALTO_CHAIN_ID;
const SOURCE_SPEC_VERSION: u32 = millau_runtime::VERSION.spec_version;
const TARGET_SPEC_VERSION: u32 = rialto_runtime::VERSION.spec_version;
const SOURCE_TO_TARGET_LANE_ID: bp_messages::LaneId = *b"swap";
const TARGET_TO_SOURCE_LANE_ID: bp_messages::LaneId = [0, 0, 0, 0];
$generic
},
}
};
}
impl SwapTokens {
/// Run the command.
pub async fn run(self) -> anyhow::Result<()> {
select_bridge!(self.bridge, {
let source_client = self.source.to_client::<Source>().await?;
let source_sign = self.source_sign.to_keypair::<Target>()?;
let target_client = self.target.to_client::<Target>().await?;
let target_sign = self.target_sign.to_keypair::<Target>()?;
// names of variables in this function are matching names used by the
// `pallet-bridge-token-swap`
// prepare token swap intention
let token_swap = self
.prepare_token_swap::<Source, Target>(&source_client, &source_sign, &target_sign)
.await?;
// group all accounts that will be used later
let accounts = TokenSwapAccounts {
source_account_at_bridged_chain: derive_target_account_from_source_account(
bp_runtime::SourceAccount::Account(
token_swap.source_account_at_this_chain.clone(),
),
),
target_account_at_this_chain: derive_source_account_from_target_account(
bp_runtime::SourceAccount::Account(
token_swap.target_account_at_bridged_chain.clone(),
),
),
source_account_at_this_chain: token_swap.source_account_at_this_chain.clone(),
target_account_at_bridged_chain: token_swap.target_account_at_bridged_chain.clone(),
swap_account: FromSwapToThisAccountIdConverter::convert(
token_swap.using_encoded(blake2_256).into(),
),
};
// account balances are used to demonstrate what's happening :)
let initial_balances =
read_account_balances(&accounts, &source_client, &target_client).await?;
// before calling something that may fail, log what we're trying to do
log::info!(target: "bridge", "Starting swap: {:?}", token_swap);
log::info!(target: "bridge", "Swap accounts: {:?}", accounts);
log::info!(target: "bridge", "Initial account balances: {:?}", initial_balances);
//
// Step 1: swap is created
//
// prepare `Currency::transfer` call that will happen at the target chain
let bridged_currency_transfer: CallOf<Target> = pallet_balances::Call::transfer {
dest: accounts.source_account_at_bridged_chain.clone().into(),
value: token_swap.target_balance_at_bridged_chain,
}
.into();
let bridged_currency_transfer_weight =
bridged_currency_transfer.get_dispatch_info().weight;
// sign message
let bridged_chain_spec_version = TARGET_SPEC_VERSION;
let signature_payload = pallet_bridge_dispatch::account_ownership_digest(
&bridged_currency_transfer,
&accounts.swap_account,
&bridged_chain_spec_version,
SOURCE_CHAIN_ID,
TARGET_CHAIN_ID,
);
let bridged_currency_transfer_signature: SignatureOf<Target> =
target_sign.sign(&signature_payload).into();
// prepare `create_swap` call
let target_public_at_bridged_chain: AccountPublicOf<Target> =
target_sign.public().into();
let swap_delivery_and_dispatch_fee: BalanceOf<Source> =
crate::cli::estimate_fee::estimate_message_delivery_and_dispatch_fee(
&source_client,
ESTIMATE_SOURCE_TO_TARGET_MESSAGE_FEE_METHOD,
SOURCE_TO_TARGET_LANE_ID,
bp_message_dispatch::MessagePayload {
spec_version: TARGET_SPEC_VERSION,
weight: bridged_currency_transfer_weight,
origin: bp_message_dispatch::CallOrigin::TargetAccount(
accounts.swap_account.clone(),
target_public_at_bridged_chain.clone(),
bridged_currency_transfer_signature.clone(),
),
dispatch_fee_payment:
bp_runtime::messages::DispatchFeePayment::AtTargetChain,
call: bridged_currency_transfer.encode(),
},
)
.await?;
let create_swap_call: CallOf<Source> = pallet_bridge_token_swap::Call::create_swap {
swap: token_swap.clone(),
swap_creation_params: Box::new(bp_token_swap::TokenSwapCreation {
target_public_at_bridged_chain,
swap_delivery_and_dispatch_fee,
bridged_chain_spec_version,
bridged_currency_transfer: bridged_currency_transfer.encode(),
bridged_currency_transfer_weight,
bridged_currency_transfer_signature,
}),
}
.into();
// start tokens swap
let source_genesis_hash = *source_client.genesis_hash();
let create_swap_signer = source_sign.clone();
let swap_created_at = wait_until_transaction_is_finalized::<Source>(
source_client
.submit_and_watch_signed_extrinsic(
accounts.source_account_at_this_chain.clone(),
move |_, transaction_nonce| {
Bytes(
Source::sign_transaction(
source_genesis_hash,
&create_swap_signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(create_swap_call, transaction_nonce),
)
.encode(),
)
},
)
.await?,
)
.await?;
// read state of swap after it has been created
let token_swap_hash: H256 = token_swap.using_encoded(blake2_256).into();
let token_swap_storage_key = bp_runtime::storage_map_final_key_identity(
TOKEN_SWAP_PALLET_NAME,
pallet_bridge_token_swap::PENDING_SWAPS_MAP_NAME,
token_swap_hash.as_ref(),
);
match read_token_swap_state(&source_client, swap_created_at, &token_swap_storage_key)
.await?
{
Some(bp_token_swap::TokenSwapState::Started) => {
log::info!(target: "bridge", "Swap has been successfully started");
let intermediate_balances =
read_account_balances(&accounts, &source_client, &target_client).await?;
log::info!(target: "bridge", "Intermediate balances: {:?}", intermediate_balances);
},
Some(token_swap_state) =>
return Err(anyhow::format_err!(
"Fresh token swap has unexpected state: {:?}",
token_swap_state,
)),
None => return Err(anyhow::format_err!("Failed to start token swap")),
};
//
// Step 2: message is being relayed to the target chain and dispathed there
//
// wait until message is dispatched at the target chain and dispatch result delivered
// back to source chain
let token_swap_state = wait_until_token_swap_state_is_changed(
&source_client,
&token_swap_storage_key,
bp_token_swap::TokenSwapState::Started,
)
.await?;
let is_transfer_succeeded = match token_swap_state {
Some(bp_token_swap::TokenSwapState::Started) => {
unreachable!("wait_until_token_swap_state_is_changed only returns if state is not Started; qed",)
},
None =>
return Err(anyhow::format_err!("Fresh token swap has disappeared unexpectedly")),
Some(bp_token_swap::TokenSwapState::Confirmed) => {
log::info!(
target: "bridge",
"Transfer has been successfully dispatched at the target chain. Swap can be claimed",
);
true
},
Some(bp_token_swap::TokenSwapState::Failed) => {
log::info!(
target: "bridge",
"Transfer has been dispatched with an error at the target chain. Swap can be canceled",
);
false
},
};
// by this time: (1) token swap account has been created and (2) if transfer has been
// successfully dispatched, both target chain balances have changed
let intermediate_balances =
read_account_balances(&accounts, &source_client, &target_client).await?;
log::info!(target: "bridge", "Intermediate balances: {:?}", intermediate_balances);
// transfer has been dispatched, but we may need to wait until block where swap can be
// claimed/canceled
if let bp_token_swap::TokenSwapType::LockClaimUntilBlock(
ref last_available_block_number,
_,
) = token_swap.swap_type
{
wait_until_swap_unlocked(
&source_client,
last_available_block_number + BlockNumberOf::<Source>::one(),
)
.await?;
}
//
// Step 3: we may now claim or cancel the swap
//
if is_transfer_succeeded {
log::info!(target: "bridge", "Claiming the swap swap");
// prepare `claim_swap` message that will be sent over the bridge
let claim_swap_call: CallOf<Source> =
pallet_bridge_token_swap::Call::claim_swap { swap: token_swap }.into();
let claim_swap_message = bp_message_dispatch::MessagePayload {
spec_version: SOURCE_SPEC_VERSION,
weight: claim_swap_call.get_dispatch_info().weight,
origin: bp_message_dispatch::CallOrigin::SourceAccount(
accounts.target_account_at_bridged_chain.clone(),
),
dispatch_fee_payment: bp_runtime::messages::DispatchFeePayment::AtSourceChain,
call: claim_swap_call.encode(),
};
let claim_swap_delivery_and_dispatch_fee: BalanceOf<Target> =
crate::cli::estimate_fee::estimate_message_delivery_and_dispatch_fee(
&target_client,
ESTIMATE_TARGET_TO_SOURCE_MESSAGE_FEE_METHOD,
TARGET_TO_SOURCE_LANE_ID,
claim_swap_message.clone(),
)
.await?;
let send_message_call: CallOf<Target> =
pallet_bridge_messages::Call::send_message {
lane_id: TARGET_TO_SOURCE_LANE_ID,
payload: claim_swap_message,
delivery_and_dispatch_fee: claim_swap_delivery_and_dispatch_fee,
}
.into();
// send `claim_swap` message
let target_genesis_hash = *target_client.genesis_hash();
let _ = wait_until_transaction_is_finalized::<Target>(
target_client
.submit_and_watch_signed_extrinsic(
accounts.target_account_at_bridged_chain.clone(),
move |_, transaction_nonce| {
Bytes(
Target::sign_transaction(
target_genesis_hash,
&target_sign,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
send_message_call,
transaction_nonce,
),
)
.encode(),
)
},
)
.await?,
)
.await?;
// wait until swap state is updated
let token_swap_state = wait_until_token_swap_state_is_changed(
&source_client,
&token_swap_storage_key,
bp_token_swap::TokenSwapState::Confirmed,
)
.await?;
if token_swap_state != None {
return Err(anyhow::format_err!(
"Confirmed token swap state has been changed to {:?} unexpectedly"
))
}
} else {
log::info!(target: "bridge", "Cancelling the swap");
let cancel_swap_call: CallOf<Source> =
pallet_bridge_token_swap::Call::cancel_swap { swap: token_swap.clone() }.into();
let _ = wait_until_transaction_is_finalized::<Source>(
source_client
.submit_and_watch_signed_extrinsic(
accounts.source_account_at_this_chain.clone(),
move |_, transaction_nonce| {
Bytes(
Source::sign_transaction(
source_genesis_hash,
&source_sign,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
cancel_swap_call,
transaction_nonce,
),
)
.encode(),
)
},
)
.await?,
)
.await?;
}
// print final balances
let final_balances =
read_account_balances(&accounts, &source_client, &target_client).await?;
log::info!(target: "bridge", "Final account balances: {:?}", final_balances);
Ok(())
})
}
/// Prepare token swap intention.
async fn prepare_token_swap<Source: CliChain, Target: CliChain>(
&self,
source_client: &Client<Source>,
source_sign: &Source::KeyPair,
target_sign: &Target::KeyPair,
) -> anyhow::Result<
bp_token_swap::TokenSwap<
BlockNumberOf<Source>,
BalanceOf<Source>,
AccountIdOf<Source>,
BalanceOf<Target>,
AccountIdOf<Target>,
>,
>
where
AccountIdOf<Source>: From<<Source::KeyPair as Pair>::Public>,
AccountIdOf<Target>: From<<Target::KeyPair as Pair>::Public>,
BalanceOf<Source>: From<u64>,
BalanceOf<Target>: From<u64>,
{
// accounts that are directly controlled by participants
let source_account_at_this_chain: AccountIdOf<Source> = source_sign.public().into();
let target_account_at_bridged_chain: AccountIdOf<Target> = target_sign.public().into();
// balances that we're going to swap
let source_balance_at_this_chain: BalanceOf<Source> = self.source_balance.cast().into();
let target_balance_at_bridged_chain: BalanceOf<Target> = self.target_balance.cast().into();
// prepare token swap intention
Ok(bp_token_swap::TokenSwap {
swap_type: self.prepare_token_swap_type(source_client).await?,
source_balance_at_this_chain,
source_account_at_this_chain: source_account_at_this_chain.clone(),
target_balance_at_bridged_chain,
target_account_at_bridged_chain: target_account_at_bridged_chain.clone(),
})
}
/// Prepare token swap type.
async fn prepare_token_swap_type<Source: Chain>(
&self,
source_client: &Client<Source>,
) -> anyhow::Result<bp_token_swap::TokenSwapType<BlockNumberOf<Source>>> {
match self.swap_type {
TokenSwapType::NoLock =>
Ok(bp_token_swap::TokenSwapType::TemporaryTargetAccountAtBridgedChain),
TokenSwapType::LockUntilBlock { blocks_before_expire, ref swap_nonce } => {
let blocks_before_expire: BlockNumberOf<Source> = blocks_before_expire.into();
let current_source_block_number = *source_client.best_header().await?.number();
Ok(bp_token_swap::TokenSwapType::LockClaimUntilBlock(
current_source_block_number + blocks_before_expire,
swap_nonce.unwrap_or_else(|| {
U256::from(random::<u128>()).overflowing_mul(U256::from(random::<u128>())).0
}),
))
},
}
}
}
/// Accounts that are participating in the swap.
#[derive(Debug)]
struct TokenSwapAccounts<ThisAccountId, BridgedAccountId> {
source_account_at_this_chain: ThisAccountId,
source_account_at_bridged_chain: BridgedAccountId,
target_account_at_bridged_chain: BridgedAccountId,
target_account_at_this_chain: ThisAccountId,
swap_account: ThisAccountId,
}
/// Swap accounts balances.
#[derive(Debug)]
struct TokenSwapBalances<ThisBalance, BridgedBalance> {
source_account_at_this_chain_balance: Option<ThisBalance>,
source_account_at_bridged_chain_balance: Option<BridgedBalance>,
target_account_at_bridged_chain_balance: Option<BridgedBalance>,
target_account_at_this_chain_balance: Option<ThisBalance>,
swap_account_balance: Option<ThisBalance>,
}
/// Read swap accounts balances.
async fn read_account_balances<Source: ChainWithBalances, Target: ChainWithBalances>(
accounts: &TokenSwapAccounts<AccountIdOf<Source>, AccountIdOf<Target>>,
source_client: &Client<Source>,
target_client: &Client<Target>,
) -> anyhow::Result<TokenSwapBalances<BalanceOf<Source>, BalanceOf<Target>>> {
Ok(TokenSwapBalances {
source_account_at_this_chain_balance: read_account_balance(
source_client,
&accounts.source_account_at_this_chain,
)
.await?,
source_account_at_bridged_chain_balance: read_account_balance(
target_client,
&accounts.source_account_at_bridged_chain,
)
.await?,
target_account_at_bridged_chain_balance: read_account_balance(
target_client,
&accounts.target_account_at_bridged_chain,
)
.await?,
target_account_at_this_chain_balance: read_account_balance(
source_client,
&accounts.target_account_at_this_chain,
)
.await?,
swap_account_balance: read_account_balance(source_client, &accounts.swap_account).await?,
})
}
/// Read account balance.
async fn read_account_balance<C: ChainWithBalances>(
client: &Client<C>,
account: &AccountIdOf<C>,
) -> anyhow::Result<Option<BalanceOf<C>>> {
match client.free_native_balance(account.clone()).await {
Ok(balance) => Ok(Some(balance)),
Err(SubstrateError::AccountDoesNotExist) => Ok(None),
Err(error) => Err(anyhow::format_err!(
"Failed to read balance of {} account {:?}: {:?}",
C::NAME,
account,
error,
)),
}
}
/// Wait until transaction is included into finalized block.
///
/// Returns the hash of the finalized block with transaction.
pub(crate) async fn wait_until_transaction_is_finalized<C: Chain>(
subscription: Subscription<TransactionStatusOf<C>>,
) -> anyhow::Result<HashOf<C>> {
loop {
let transaction_status = subscription.next().await?;
match transaction_status {
Some(TransactionStatusOf::<C>::FinalityTimeout(_)) |
Some(TransactionStatusOf::<C>::Usurped(_)) |
Some(TransactionStatusOf::<C>::Dropped) |
Some(TransactionStatusOf::<C>::Invalid) |
None =>
return Err(anyhow::format_err!(
"We've been waiting for finalization of {} transaction, but it now has the {:?} status",
C::NAME,
transaction_status,
)),
Some(TransactionStatusOf::<C>::Finalized(block_hash)) => {
log::trace!(
target: "bridge",
"{} transaction has been finalized at block {}",
C::NAME,
block_hash,
);
return Ok(block_hash)
},
_ => {
log::trace!(
target: "bridge",
"Received intermediate status of {} transaction: {:?}",
C::NAME,
transaction_status,
);
},
}
}
}
/// Waits until token swap state is changed from `Started` to something else.
async fn wait_until_token_swap_state_is_changed<C: Chain>(
client: &Client<C>,
swap_state_storage_key: &StorageKey,
previous_token_swap_state: bp_token_swap::TokenSwapState,
) -> anyhow::Result<Option<bp_token_swap::TokenSwapState>> {
log::trace!(target: "bridge", "Waiting for token swap state change");
loop {
async_std::task::sleep(C::AVERAGE_BLOCK_INTERVAL).await;
let best_block = client.best_finalized_header_number().await?;
let best_block_hash = client.block_hash_by_number(best_block).await?;
log::trace!(target: "bridge", "Inspecting {} block {}/{}", C::NAME, best_block, best_block_hash);
let token_swap_state =
read_token_swap_state(client, best_block_hash, swap_state_storage_key).await?;
match token_swap_state {
Some(new_token_swap_state) if new_token_swap_state == previous_token_swap_state => {},
_ => {
log::trace!(
target: "bridge",
"Token swap state has been changed from {:?} to {:?}",
previous_token_swap_state,
token_swap_state,
);
return Ok(token_swap_state)
},
}
}
}
/// Waits until swap can be claimed or canceled.
async fn wait_until_swap_unlocked<C: Chain>(
client: &Client<C>,
required_block_number: BlockNumberOf<C>,
) -> anyhow::Result<()> {
log::trace!(target: "bridge", "Waiting for token swap unlock");
loop {
async_std::task::sleep(C::AVERAGE_BLOCK_INTERVAL).await;
let best_block = client.best_finalized_header_number().await?;
let best_block_hash = client.block_hash_by_number(best_block).await?;
if best_block >= required_block_number {
return Ok(())
}
log::trace!(target: "bridge", "Skipping {} block {}/{}", C::NAME, best_block, best_block_hash);
}
}
/// Read state of the active token swap.
async fn read_token_swap_state<C: Chain>(
client: &Client<C>,
at_block: C::Hash,
swap_state_storage_key: &StorageKey,
) -> anyhow::Result<Option<bp_token_swap::TokenSwapState>> {
Ok(client.storage_value(swap_state_storage_key.clone(), Some(at_block)).await?)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn swap_tokens_millau_to_rialto_no_lock() {
let swap_tokens = SwapTokens::from_iter(vec![
"swap-tokens",
"millau-to-rialto",
"--source-host",
"127.0.0.1",
"--source-port",
"9000",
"--source-signer",
"//Alice",
"--source-balance",
"8000000000",
"--target-host",
"127.0.0.1",
"--target-port",
"9001",
"--target-signer",
"//Bob",
"--target-balance",
"9000000000",
"no-lock",
]);
assert_eq!(
swap_tokens,
SwapTokens {
bridge: SwapTokensBridge::MillauToRialto,
source: SourceConnectionParams {
source_host: "127.0.0.1".into(),
source_port: 9000,
source_secure: false,
},
source_sign: SourceSigningParams {
source_signer: Some("//Alice".into()),
source_signer_password: None,
source_signer_file: None,
source_signer_password_file: None,
source_transactions_mortality: None,
},
target: TargetConnectionParams {
target_host: "127.0.0.1".into(),
target_port: 9001,
target_secure: false,
},
target_sign: TargetSigningParams {
target_signer: Some("//Bob".into()),
target_signer_password: None,
target_signer_file: None,
target_signer_password_file: None,
target_transactions_mortality: None,
},
swap_type: TokenSwapType::NoLock,
source_balance: Balance(8000000000),
target_balance: Balance(9000000000),
}
);
}
#[test]
fn swap_tokens_millau_to_rialto_lock_until() {
let swap_tokens = SwapTokens::from_iter(vec![
"swap-tokens",
"millau-to-rialto",
"--source-host",
"127.0.0.1",
"--source-port",
"9000",
"--source-signer",
"//Alice",
"--source-balance",
"8000000000",
"--target-host",
"127.0.0.1",
"--target-port",
"9001",
"--target-signer",
"//Bob",
"--target-balance",
"9000000000",
"lock-until-block",
"--blocks-before-expire",
"1",
]);
assert_eq!(
swap_tokens,
SwapTokens {
bridge: SwapTokensBridge::MillauToRialto,
source: SourceConnectionParams {
source_host: "127.0.0.1".into(),
source_port: 9000,
source_secure: false,
},
source_sign: SourceSigningParams {
source_signer: Some("//Alice".into()),
source_signer_password: None,
source_signer_file: None,
source_signer_password_file: None,
source_transactions_mortality: None,
},
target: TargetConnectionParams {
target_host: "127.0.0.1".into(),
target_port: 9001,
target_secure: false,
},
target_sign: TargetSigningParams {
target_signer: Some("//Bob".into()),
target_signer_password: None,
target_signer_file: None,
target_signer_password_file: None,
target_transactions_mortality: None,
},
swap_type: TokenSwapType::LockUntilBlock {
blocks_before_expire: 1,
swap_nonce: None,
},
source_balance: Balance(8000000000),
target_balance: Balance(9000000000),
}
);
}
}
@@ -1,158 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Substrate-to-Substrate headers sync entrypoint.
use crate::finality_target::SubstrateFinalityTarget;
use bp_header_chain::justification::GrandpaJustification;
use finality_relay::{FinalitySyncParams, FinalitySyncPipeline};
use relay_substrate_client::{finality_source::FinalitySource, BlockNumberOf, Chain, Client, HashOf, SyncHeader};
use relay_utils::{metrics::MetricsParams, BlockNumberBase};
use sp_core::Bytes;
use std::{fmt::Debug, marker::PhantomData, time::Duration};
/// Default synchronization loop timeout.
pub(crate) const STALL_TIMEOUT: Duration = Duration::from_secs(120);
/// Default limit of recent finality proofs.
///
/// Finality delay of 4096 blocks is unlikely to happen in practice in
/// Substrate+GRANDPA based chains (good to know).
pub(crate) const RECENT_FINALITY_PROOFS_LIMIT: usize = 4096;
/// Headers sync pipeline for Substrate <-> Substrate relays.
pub trait SubstrateFinalitySyncPipeline: 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.
type TargetChain: Chain;
/// Customize metrics exposed by headers sync loop.
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
Ok(params)
}
/// Start finality relay guards.
///
/// Different finality bridges may have different set of guards - e.g. on ephemeral chains we
/// don't need 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) -> <Self::TargetChain as Chain>::AccountId;
/// Make submit header transaction.
fn make_submit_finality_proof_transaction(
&self,
transaction_nonce: <Self::TargetChain as Chain>::Index,
header: Self::Header,
proof: Self::FinalityProof,
) -> Bytes;
}
/// Substrate-to-Substrate finality proof pipeline.
#[derive(Clone)]
pub struct SubstrateFinalityToSubstrate<SourceChain, TargetChain: Chain, TargetSign> {
/// Client for the target chain.
pub(crate) target_client: Client<TargetChain>,
/// Data required to sign target chain transactions.
pub(crate) 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()
}
}
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(),
}
}
}
impl<SourceChain, TargetChain, TargetSign> FinalitySyncPipeline
for SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>
where
SourceChain: Clone + Chain + Debug,
BlockNumberOf<SourceChain>: BlockNumberBase,
TargetChain: Clone + Chain + Debug,
TargetSign: 'static + Clone + Send + Sync,
{
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>;
}
/// Run Substrate-to-Substrate finality sync.
pub async fn run<SourceChain, TargetChain, P>(
pipeline: P,
source_client: Client<SourceChain>,
target_client: Client<TargetChain>,
only_mandatory_headers: bool,
metrics_params: MetricsParams,
) -> anyhow::Result<()>
where
P: SubstrateFinalitySyncPipeline<
Hash = HashOf<SourceChain>,
Number = BlockNumberOf<SourceChain>,
Header = SyncHeader<SourceChain::Header>,
FinalityProof = GrandpaJustification<SourceChain::Header>,
TargetChain = TargetChain,
>,
SourceChain: Clone + Chain,
BlockNumberOf<SourceChain>: BlockNumberBase,
TargetChain: Clone + Chain,
{
log::info!(
target: "bridge",
"Starting {} -> {} finality proof relay",
SourceChain::NAME,
TargetChain::NAME,
);
finality_relay::run(
FinalitySource::new(source_client, None),
SubstrateFinalityTarget::new(target_client, pipeline),
FinalitySyncParams {
tick: std::cmp::max(SourceChain::AVERAGE_BLOCK_INTERVAL, TargetChain::AVERAGE_BLOCK_INTERVAL),
recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT,
stall_timeout: STALL_TIMEOUT,
only_mandatory_headers,
},
metrics_params,
futures::future::pending(),
)
.await
.map_err(|e| anyhow::format_err!("{}", e))
}
@@ -1,91 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Substrate client as Substrate finality proof target. The chain we connect to should have
//! runtime that implements `<BridgedChainName>FinalityApi` to allow bridging with
//! <BridgedName> chain.
use crate::finality_pipeline::SubstrateFinalitySyncPipeline;
use async_trait::async_trait;
use codec::Decode;
use finality_relay::TargetClient;
use relay_substrate_client::{Chain, Client, Error as SubstrateError};
use relay_utils::relay_loop::Client as RelayClient;
/// Substrate client as Substrate finality target.
pub struct SubstrateFinalityTarget<C: Chain, P> {
client: Client<C>,
pipeline: P,
}
impl<C: Chain, P> SubstrateFinalityTarget<C, P> {
/// Create new Substrate headers target.
pub fn new(client: Client<C>, pipeline: P) -> Self {
SubstrateFinalityTarget { client, pipeline }
}
}
impl<C: Chain, P: SubstrateFinalitySyncPipeline> Clone for SubstrateFinalityTarget<C, P> {
fn clone(&self) -> Self {
SubstrateFinalityTarget {
client: self.client.clone(),
pipeline: self.pipeline.clone(),
}
}
}
#[async_trait]
impl<C: Chain, P: SubstrateFinalitySyncPipeline> RelayClient for SubstrateFinalityTarget<C, P> {
type Error = SubstrateError;
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
self.client.reconnect().await
}
}
#[async_trait]
impl<C, P> TargetClient<P> for SubstrateFinalityTarget<C, P>
where
C: Chain,
P::Number: Decode,
P::Hash: Decode,
P: SubstrateFinalitySyncPipeline<TargetChain = C>,
{
async fn best_finalized_source_block_number(&self) -> Result<P::Number, SubstrateError> {
// 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?;
Ok(crate::messages_source::read_client_state::<C, P::Hash, P::Number>(
&self.client,
P::BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET,
)
.await?
.best_finalized_peer_at_best_self
.0)
}
async fn submit_finality_proof(&self, header: P::Header, proof: P::FinalityProof) -> Result<(), SubstrateError> {
self.client
.submit_signed_extrinsic(self.pipeline.transactions_author(), move |transaction_nonce| {
self.pipeline
.make_submit_finality_proof_transaction(transaction_nonce, header, proof)
})
.await
.map(drop)
}
}
@@ -1,256 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Initialize Substrate -> Substrate headers bridge.
//!
//! Initialization is a transaction that calls `initialize()` function of the
//! `pallet-bridge-grandpa` pallet. This transaction brings initial header
//! and authorities set from source to target chain. The headers sync starts
//! with this header.
use bp_header_chain::InitializationData;
use bp_header_chain::{
find_grandpa_authorities_scheduled_change,
justification::{verify_justification, GrandpaJustification},
};
use codec::Decode;
use finality_grandpa::voter_set::VoterSet;
use num_traits::{One, Zero};
use relay_substrate_client::{Chain, Client};
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>(
source_client: Client<SourceChain>,
target_client: Client<TargetChain>,
target_transactions_signer: TargetChain::AccountId,
prepare_initialize_transaction: impl FnOnce(TargetChain::Index, InitializationData<SourceChain::Header>) -> Bytes,
) {
let result = do_initialize(
source_client,
target_client,
target_transactions_signer,
prepare_initialize_transaction,
)
.await;
match result {
Ok(tx_hash) => log::info!(
target: "bridge",
"Successfully submitted {}-headers bridge initialization transaction to {}: {:?}",
SourceChain::NAME,
TargetChain::NAME,
tx_hash,
),
Err(err) => log::error!(
target: "bridge",
"Failed to submit {}-headers bridge initialization transaction to {}: {:?}",
SourceChain::NAME,
TargetChain::NAME,
err,
),
}
}
/// Craft and submit initialization transaction, returning any error that may occur.
async fn do_initialize<SourceChain: Chain, 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,
) -> Result<TargetChain::Hash, String> {
let initialization_data = prepare_initialization_data(source_client).await?;
log::info!(
target: "bridge",
"Prepared initialization data for {}-headers bridge at {}: {:?}",
SourceChain::NAME,
TargetChain::NAME,
initialization_data,
);
let initialization_tx_hash = target_client
.submit_signed_extrinsic(target_transactions_signer, move |transaction_nonce| {
prepare_initialize_transaction(transaction_nonce, initialization_data)
})
.await
.map_err(|err| format!("Failed to submit {} transaction: {:?}", TargetChain::NAME, err))?;
Ok(initialization_tx_hash)
}
/// Prepare initialization data for the GRANDPA verifier pallet.
async fn prepare_initialization_data<SourceChain: Chain>(
source_client: Client<SourceChain>,
) -> Result<InitializationData<SourceChain::Header>, String> {
// In ideal world we just need to get best finalized header and then to read GRANDPA authorities
// set (`pallet_grandpa::CurrentSetId` + `GrandpaApi::grandpa_authorities()`) at this header.
//
// But now there are problems with this approach - `CurrentSetId` may return invalid value. So here
// we're waiting for the next justification, read the authorities set and then try to figure out
// the set id with bruteforce.
let mut justifications = source_client
.subscribe_justifications()
.await
.map_err(|err| format!("Failed to subscribe to {} justifications: {:?}", SourceChain::NAME, err))?;
// Read next justification - the header that it finalizes will be used as initial header.
let justification = justifications.next().await.ok_or_else(|| {
format!(
"Failed to read {} justification from the stream: stream has ended unexpectedly",
SourceChain::NAME,
)
})?;
// Read initial header.
let justification: GrandpaJustification<SourceChain::Header> = Decode::decode(&mut &justification.0[..])
.map_err(|err| format!("Failed to decode {} justification: {:?}", SourceChain::NAME, err))?;
let (initial_header_hash, initial_header_number) =
(justification.commit.target_hash, justification.commit.target_number);
let initial_header = source_header(&source_client, initial_header_hash).await?;
log::trace!(target: "bridge", "Selected {} initial header: {}/{}",
SourceChain::NAME,
initial_header_number,
initial_header_hash,
);
// Read GRANDPA authorities set at initial header.
let initial_authorities_set = source_authorities_set(&source_client, initial_header_hash).await?;
log::trace!(target: "bridge", "Selected {} initial authorities set: {:?}",
SourceChain::NAME,
initial_authorities_set,
);
// If initial header changes the GRANDPA authorities set, then we need previous authorities
// to verify justification.
let mut authorities_for_verification = initial_authorities_set.clone();
let scheduled_change = find_grandpa_authorities_scheduled_change(&initial_header);
assert!(
scheduled_change.as_ref().map(|c| c.delay.is_zero()).unwrap_or(true),
"GRANDPA authorities change at {} scheduled to happen in {:?} blocks. We expect\
regular hange to have zero delay",
initial_header_hash,
scheduled_change.as_ref().map(|c| c.delay),
);
let schedules_change = scheduled_change.is_some();
if schedules_change {
authorities_for_verification = source_authorities_set(&source_client, *initial_header.parent_hash()).await?;
log::trace!(
target: "bridge",
"Selected {} header is scheduling GRANDPA authorities set changes. Using previous set: {:?}",
SourceChain::NAME,
authorities_for_verification,
);
}
// Now let's try to guess authorities set id by verifying justification.
let mut initial_authorities_set_id = 0;
let mut min_possible_block_number = SourceChain::BlockNumber::zero();
let authorities_for_verification = VoterSet::new(authorities_for_verification.clone()).ok_or_else(|| {
format!(
"Read invalid {} authorities set: {:?}",
SourceChain::NAME,
authorities_for_verification,
)
})?;
loop {
log::trace!(
target: "bridge", "Trying {} GRANDPA authorities set id: {}",
SourceChain::NAME,
initial_authorities_set_id,
);
let is_valid_set_id = verify_justification::<SourceChain::Header>(
(initial_header_hash, initial_header_number),
initial_authorities_set_id,
&authorities_for_verification,
&justification,
)
.is_ok();
if is_valid_set_id {
break;
}
initial_authorities_set_id += 1;
min_possible_block_number += One::one();
if min_possible_block_number > initial_header_number {
// there can't be more authorities set changes than headers => if we have reached `initial_block_number`
// and still have not found correct value of `initial_authorities_set_id`, then something
// else is broken => fail
return Err(format!(
"Failed to guess initial {} GRANDPA authorities set id: checked all\
possible ids in range [0; {}]",
SourceChain::NAME,
initial_header_number
));
}
}
Ok(InitializationData {
header: initial_header,
authority_list: initial_authorities_set,
set_id: if schedules_change {
initial_authorities_set_id + 1
} else {
initial_authorities_set_id
},
is_halted: false,
})
}
/// Read header by hash from the source client.
async fn source_header<SourceChain: Chain>(
source_client: &Client<SourceChain>,
header_hash: SourceChain::Hash,
) -> Result<SourceChain::Header, String> {
source_client.header_by_hash(header_hash).await.map_err(|err| {
format!(
"Failed to retrive {} header with hash {}: {:?}",
SourceChain::NAME,
header_hash,
err,
)
})
}
/// Read GRANDPA authorities set at given header.
async fn source_authorities_set<SourceChain: Chain>(
source_client: &Client<SourceChain>,
header_hash: SourceChain::Hash,
) -> Result<GrandpaAuthoritiesSet, String> {
let raw_authorities_set = source_client
.grandpa_authorities_set(header_hash)
.await
.map_err(|err| {
format!(
"Failed to retrive {} GRANDPA authorities set at header {}: {:?}",
SourceChain::NAME,
header_hash,
err,
)
})?;
GrandpaAuthoritiesSet::decode(&mut &raw_authorities_set[..]).map_err(|err| {
format!(
"Failed to decode {} GRANDPA authorities set at header {}: {:?}",
SourceChain::NAME,
header_hash,
err,
)
})
}
@@ -20,13 +20,6 @@
mod chains;
mod cli;
mod finality_pipeline;
mod finality_target;
mod headers_initialize;
mod messages_lane;
mod messages_source;
mod messages_target;
mod on_demand_headers;
fn main() {
let command = cli::parse_args();
@@ -1,210 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::messages_source::SubstrateMessagesProof;
use crate::messages_target::SubstrateMessagesReceivingProof;
use crate::on_demand_headers::OnDemandHeadersRelay;
use bp_messages::{LaneId, MessageNonce};
use frame_support::weights::Weight;
use messages_relay::message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf};
use relay_substrate_client::{BlockNumberOf, Chain, Client, HashOf};
use relay_utils::{metrics::MetricsParams, BlockNumberBase};
use sp_core::Bytes;
use std::ops::RangeInclusive;
/// Substrate <-> Substrate messages relay parameters.
pub struct MessagesRelayParams<SC: Chain, SS, TC: Chain, TS> {
/// Messages source client.
pub source_client: Client<SC>,
/// Sign parameters for messages source chain.
pub source_sign: SS,
/// Messages target client.
pub target_client: Client<TC>,
/// Sign parameters for messages target chain.
pub target_sign: TS,
/// Optional on-demand source to target headers relay.
pub source_to_target_headers_relay: Option<OnDemandHeadersRelay<SC>>,
/// Optional on-demand target to source headers relay.
pub target_to_source_headers_relay: Option<OnDemandHeadersRelay<TC>>,
/// Identifier of lane that needs to be served.
pub lane_id: LaneId,
/// Metrics parameters.
pub metrics_params: MetricsParams,
}
/// Message sync pipeline for Substrate <-> Substrate relays.
pub trait SubstrateMessageLane: 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 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;
/// 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) -> <Self::TargetChain as Chain>::AccountId;
/// Make messages delivery transaction.
fn make_messages_delivery_transaction(
&self,
transaction_nonce: <Self::TargetChain as Chain>::Index,
generated_at_header: SourceHeaderIdOf<Self>,
nonces: RangeInclusive<MessageNonce>,
proof: Self::MessagesProof,
) -> Bytes;
/// Returns id of account that we're using to sign transactions at source chain (delivery proof).
fn source_transactions_author(&self) -> <Self::SourceChain as Chain>::AccountId;
/// Make messages receiving proof transaction.
fn make_messages_receiving_proof_transaction(
&self,
transaction_nonce: <Self::SourceChain as Chain>::Index,
generated_at_header: TargetHeaderIdOf<Self>,
proof: Self::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(crate) source_client: Client<Source>,
/// Parameters required to sign transactions for source chain.
pub(crate) source_sign: SourceSignParams,
/// Client for the target Substrate chain.
pub(crate) target_client: Client<Target>,
/// Parameters required to sign transactions for target chain.
pub(crate) target_sign: TargetSignParams,
/// Account id of relayer at the source chain.
pub(crate) relayer_id_at_source: Source::AccountId,
}
impl<Source: Chain, SourceSignParams: Clone, Target: Chain, TargetSignParams: Clone> Clone
for SubstrateMessageLaneToSubstrate<Source, SourceSignParams, Target, TargetSignParams>
{
fn clone(&self) -> Self {
Self {
source_client: self.source_client.clone(),
source_sign: self.source_sign.clone(),
target_client: self.target_client.clone(),
target_sign: self.target_sign.clone(),
relayer_id_at_source: self.relayer_id_at_source.clone(),
}
}
}
impl<Source: Chain, SourceSignParams, Target: Chain, TargetSignParams> MessageLane
for SubstrateMessageLaneToSubstrate<Source, SourceSignParams, Target, TargetSignParams>
where
SourceSignParams: Clone + Send + Sync + 'static,
TargetSignParams: Clone + Send + Sync + 'static,
BlockNumberOf<Source>: BlockNumberBase,
BlockNumberOf<Target>: BlockNumberBase,
{
const SOURCE_NAME: &'static str = Source::NAME;
const TARGET_NAME: &'static str = Target::NAME;
type MessagesProof = SubstrateMessagesProof<Source>;
type MessagesReceivingProof = SubstrateMessagesReceivingProof<Target>;
type SourceChainBalance = Source::Balance;
type SourceHeaderNumber = BlockNumberOf<Source>;
type SourceHeaderHash = HashOf<Source>;
type TargetHeaderNumber = BlockNumberOf<Target>;
type TargetHeaderHash = HashOf<Target>;
}
/// Returns maximal number of messages and their maximal cumulative dispatch weight, based
/// on given chain parameters.
pub fn select_delivery_transaction_limits<W: pallet_bridge_messages::WeightInfoExt>(
max_extrinsic_weight: Weight,
max_unconfirmed_messages_at_inbound_lane: MessageNonce,
) -> (MessageNonce, Weight) {
// We may try to guess accurate value, based on maximal number of messages and per-message
// weight overhead, but the relay loop isn't using this info in a super-accurate way anyway.
// So just a rough guess: let's say 1/3 of max tx weight is for tx itself and the rest is
// for messages dispatch.
// Another thing to keep in mind is that our runtimes (when this code was written) accept
// messages with dispatch weight <= max_extrinsic_weight/2. So we can't reserve less than
// that for dispatch.
let weight_for_delivery_tx = max_extrinsic_weight / 3;
let weight_for_messages_dispatch = max_extrinsic_weight - weight_for_delivery_tx;
let delivery_tx_base_weight =
W::receive_messages_proof_overhead() + W::receive_messages_proof_outbound_lane_state_overhead();
let delivery_tx_weight_rest = weight_for_delivery_tx - delivery_tx_base_weight;
let max_number_of_messages = std::cmp::min(
delivery_tx_weight_rest / W::receive_messages_proof_messages_overhead(1),
max_unconfirmed_messages_at_inbound_lane,
);
assert!(
max_number_of_messages > 0,
"Relay should fit at least one message in every delivery transaction",
);
assert!(
weight_for_messages_dispatch >= max_extrinsic_weight / 2,
"Relay shall be able to deliver messages with dispatch weight = max_extrinsic_weight / 2",
);
(max_number_of_messages, weight_for_messages_dispatch)
}
#[cfg(test)]
mod tests {
use super::*;
type RialtoToMillauMessagesWeights = pallet_bridge_messages::weights::RialtoWeight<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,
);
assert_eq!(
(max_count, max_weight),
// We don't actually care about these values, so feel free to update them whenever test
// fails. The only thing to do before that is to ensure that new values looks sane: 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),
);
}
}
@@ -1,472 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Substrate client as Substrate messages source. The chain we connect to should have
//! runtime that implements `<BridgedChainName>HeaderApi` to allow bridging with
//! <BridgedName> chain.
use crate::messages_lane::SubstrateMessageLane;
use crate::on_demand_headers::OnDemandHeadersRelay;
use async_trait::async_trait;
use bp_messages::{LaneId, MessageNonce};
use bp_runtime::{messages::DispatchFeePayment, ChainId};
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use codec::{Decode, Encode};
use frame_support::{traits::Instance, weights::Weight};
use messages_relay::{
message_lane::{SourceHeaderIdOf, TargetHeaderIdOf},
message_lane_loop::{
ClientState, MessageDetails, MessageDetailsMap, MessageProofParameters, SourceClient, SourceClientState,
},
};
use relay_substrate_client::{Chain, Client, Error as SubstrateError, HashOf, HeaderIdOf};
use relay_utils::{relay_loop::Client as RelayClient, BlockNumberBase, HeaderId};
use sp_core::Bytes;
use sp_runtime::{traits::Header as HeaderT, DeserializeOwned};
use std::{marker::PhantomData, ops::RangeInclusive};
/// Intermediate message proof returned by the source Substrate node. Includes everything
/// required to submit to the target node: cumulative dispatch weight of bundled messages and
/// the proof itself.
pub type SubstrateMessagesProof<C> = (Weight, FromBridgedChainMessagesProof<HashOf<C>>);
/// Substrate client as Substrate messages source.
pub struct SubstrateMessagesSource<C: Chain, P: SubstrateMessageLane, I> {
client: Client<C>,
lane: P,
lane_id: LaneId,
instance: ChainId,
target_to_source_headers_relay: Option<OnDemandHeadersRelay<P::TargetChain>>,
_phantom: PhantomData<I>,
}
impl<C: Chain, P: SubstrateMessageLane, I> SubstrateMessagesSource<C, P, I> {
/// Create new Substrate headers source.
pub fn new(
client: Client<C>,
lane: P,
lane_id: LaneId,
instance: ChainId,
target_to_source_headers_relay: Option<OnDemandHeadersRelay<P::TargetChain>>,
) -> Self {
SubstrateMessagesSource {
client,
lane,
lane_id,
instance,
target_to_source_headers_relay,
_phantom: Default::default(),
}
}
}
impl<C: Chain, P: SubstrateMessageLane, I> Clone for SubstrateMessagesSource<C, P, I> {
fn clone(&self) -> Self {
Self {
client: self.client.clone(),
lane: self.lane.clone(),
lane_id: self.lane_id,
instance: self.instance,
target_to_source_headers_relay: self.target_to_source_headers_relay.clone(),
_phantom: Default::default(),
}
}
}
#[async_trait]
impl<C, P, I> RelayClient for SubstrateMessagesSource<C, P, I>
where
C: Chain,
P: SubstrateMessageLane,
I: Send + Sync + Instance,
{
type Error = SubstrateError;
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
self.client.reconnect().await
}
}
#[async_trait]
impl<C, P, I> SourceClient<P> for SubstrateMessagesSource<C, P, I>
where
C: Chain,
C::Header: DeserializeOwned,
C::Index: DeserializeOwned,
C::BlockNumber: BlockNumberBase,
P: SubstrateMessageLane<
MessagesProof = SubstrateMessagesProof<C>,
SourceChainBalance = C::Balance,
SourceHeaderNumber = <C::Header as HeaderT>::Number,
SourceHeaderHash = <C::Header as HeaderT>::Hash,
SourceChain = C,
>,
P::TargetChain: Chain<Hash = P::TargetHeaderHash, BlockNumber = P::TargetHeaderNumber>,
P::TargetHeaderNumber: Decode,
P::TargetHeaderHash: Decode,
I: Send + Sync + Instance,
{
async fn state(&self) -> Result<SourceClientState<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?;
read_client_state::<_, P::TargetHeaderHash, P::TargetHeaderNumber>(
&self.client,
P::BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE,
)
.await
}
async fn latest_generated_nonce(
&self,
id: SourceHeaderIdOf<P>,
) -> Result<(SourceHeaderIdOf<P>, 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)?;
Ok((id, latest_generated_nonce))
}
async fn latest_confirmed_received_nonce(
&self,
id: SourceHeaderIdOf<P>,
) -> Result<(SourceHeaderIdOf<P>, 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)?;
Ok((id, latest_received_nonce))
}
async fn generated_message_details(
&self,
id: SourceHeaderIdOf<P>,
nonces: RangeInclusive<MessageNonce>,
) -> Result<MessageDetailsMap<P::SourceChainBalance>, SubstrateError> {
let encoded_response = self
.client
.state_call(
P::OUTBOUND_LANE_MESSAGE_DETAILS_METHOD.into(),
Bytes((self.lane_id, nonces.start(), nonces.end()).encode()),
Some(id.1),
)
.await?;
make_message_details_map::<C>(
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?,
nonces,
)
}
async fn prove_messages(
&self,
id: SourceHeaderIdOf<P>,
nonces: RangeInclusive<MessageNonce>,
proof_parameters: MessageProofParameters,
) -> Result<(SourceHeaderIdOf<P>, RangeInclusive<MessageNonce>, P::MessagesProof), SubstrateError> {
let mut storage_keys = 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::<I>(&self.lane_id, message_nonce);
storage_keys.push(message_key);
message_nonce += 1;
}
if proof_parameters.outbound_state_proof_required {
storage_keys.push(pallet_bridge_messages::storage_keys::outbound_lane_data_key::<I>(
&self.lane_id,
));
}
let proof = self
.client
.prove_storage(storage_keys, id.1)
.await?
.iter_nodes()
.collect();
let proof = FromBridgedChainMessagesProof {
bridged_header_hash: id.1,
storage_proof: proof,
lane: self.lane_id,
nonces_start: *nonces.start(),
nonces_end: *nonces.end(),
};
Ok((id, nonces, (proof_parameters.dispatch_weight, proof)))
}
async fn submit_messages_receiving_proof(
&self,
generated_at_block: TargetHeaderIdOf<P>,
proof: P::MessagesReceivingProof,
) -> Result<(), SubstrateError> {
self.client
.submit_signed_extrinsic(self.lane.source_transactions_author(), move |transaction_nonce| {
self.lane
.make_messages_receiving_proof_transaction(transaction_nonce, generated_at_block, proof)
})
.await?;
Ok(())
}
async fn require_target_header_on_source(&self, id: TargetHeaderIdOf<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;
}
}
async fn estimate_confirmation_transaction(&self) -> P::SourceChainBalance {
num_traits::Zero::zero() // TODO: https://github.com/paritytech/parity-bridges-common/issues/997
}
}
pub async fn read_client_state<SelfChain, BridgedHeaderHash, BridgedHeaderNumber>(
self_client: &Client<SelfChain>,
best_finalized_header_id_method_name: &str,
) -> Result<ClientState<HeaderIdOf<SelfChain>, HeaderId<BridgedHeaderHash, BridgedHeaderNumber>>, SubstrateError>
where
SelfChain: Chain,
SelfChain::Header: DeserializeOwned,
SelfChain::Index: DeserializeOwned,
BridgedHeaderHash: Decode,
BridgedHeaderNumber: Decode,
{
// 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?;
let self_best_finalized_header = self_client.header_by_hash(self_best_finalized_header_hash).await?;
let self_best_finalized_id = HeaderId(*self_best_finalized_header.number(), self_best_finalized_header_hash);
// now let's read our best header on **this** chain
let self_best_header = self_client.best_header().await?;
let self_best_hash = self_best_header.hash();
let self_best_id = HeaderId(*self_best_header.number(), self_best_hash);
// now let's read id of best finalized peer header at our best finalized block
let encoded_best_finalized_peer_on_self = self_client
.state_call(
best_finalized_header_id_method_name.into(),
Bytes(Vec::new()),
Some(self_best_hash),
)
.await?;
let decoded_best_finalized_peer_on_self: (BridgedHeaderNumber, BridgedHeaderHash) =
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,
);
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,
})
}
fn make_message_details_map<C: Chain>(
weights: Vec<bp_messages::MessageDetails<C::Balance>>,
nonces: RangeInclusive<MessageNonce>,
) -> Result<MessageDetailsMap<C::Balance>, SubstrateError> {
let make_missing_nonce_error = |expected_nonce| {
Err(SubstrateError::Custom(format!(
"Missing nonce {} in messages_dispatch_weight call result. Expected all nonces from {:?}",
expected_nonce, nonces,
)))
};
let mut weights_map = MessageDetailsMap::new();
// this is actually prevented by external logic
if nonces.is_empty() {
return Ok(weights_map);
}
// check if last nonce is missing - loop below is not checking this
let last_nonce_is_missing = weights
.last()
.map(|details| details.nonce != *nonces.end())
.unwrap_or(true);
if last_nonce_is_missing {
return make_missing_nonce_error(*nonces.end());
}
let mut expected_nonce = *nonces.start();
let mut is_at_head = true;
for details in weights {
match (details.nonce == expected_nonce, is_at_head) {
(true, _) => (),
(false, true) => {
// this may happen if some messages were already pruned from the source node
//
// this is not critical error and will be auto-resolved by messages lane (and target node)
log::info!(
target: "bridge",
"Some messages are missing from the {} node: {:?}. Target node may be out of sync?",
C::NAME,
expected_nonce..details.nonce,
);
}
(false, false) => {
// some nonces are missing from the middle/tail of the range
//
// this is critical error, because we can't miss any nonces
return make_missing_nonce_error(expected_nonce);
}
}
weights_map.insert(
details.nonce,
MessageDetails {
dispatch_weight: details.dispatch_weight,
size: details.size as _,
// TODO: https://github.com/paritytech/parity-bridges-common/issues/997
reward: num_traits::Zero::zero(),
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
},
);
expected_nonce = details.nonce + 1;
is_at_head = false;
}
Ok(weights_map)
}
#[cfg(test)]
mod tests {
use super::*;
fn message_details_from_rpc(
nonces: RangeInclusive<MessageNonce>,
) -> Vec<bp_messages::MessageDetails<bp_rialto::Balance>> {
nonces
.into_iter()
.map(|nonce| bp_messages::MessageDetails {
nonce,
dispatch_weight: 0,
size: 0,
delivery_and_dispatch_fee: 0,
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
})
.collect()
}
#[test]
fn make_message_details_map_succeeds_if_no_messages_are_missing() {
assert_eq!(
make_message_details_map::<relay_rialto_client::Rialto>(message_details_from_rpc(1..=3), 1..=3,).unwrap(),
vec![
(
1,
MessageDetails {
dispatch_weight: 0,
size: 0,
reward: 0,
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
}
),
(
2,
MessageDetails {
dispatch_weight: 0,
size: 0,
reward: 0,
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
}
),
(
3,
MessageDetails {
dispatch_weight: 0,
size: 0,
reward: 0,
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
}
),
]
.into_iter()
.collect(),
);
}
#[test]
fn make_message_details_map_succeeds_if_head_messages_are_missing() {
assert_eq!(
make_message_details_map::<relay_rialto_client::Rialto>(message_details_from_rpc(2..=3), 1..=3,).unwrap(),
vec![
(
2,
MessageDetails {
dispatch_weight: 0,
size: 0,
reward: 0,
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
}
),
(
3,
MessageDetails {
dispatch_weight: 0,
size: 0,
reward: 0,
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
}
),
]
.into_iter()
.collect(),
);
}
#[test]
fn make_message_details_map_fails_if_mid_messages_are_missing() {
let mut message_details_from_rpc = message_details_from_rpc(1..=3);
message_details_from_rpc.remove(1);
assert!(matches!(
make_message_details_map::<relay_rialto_client::Rialto>(message_details_from_rpc, 1..=3,),
Err(SubstrateError::Custom(_))
));
}
#[test]
fn make_message_details_map_fails_if_tail_messages_are_missing() {
assert!(matches!(
make_message_details_map::<relay_rialto_client::Rialto>(message_details_from_rpc(1..=2), 1..=3,),
Err(SubstrateError::Custom(_))
));
}
#[test]
fn make_message_details_map_fails_if_all_messages_are_missing() {
assert!(matches!(
make_message_details_map::<relay_rialto_client::Rialto>(vec![], 1..=3),
Err(SubstrateError::Custom(_))
));
}
}
@@ -1,238 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Substrate client as Substrate messages target. The chain we connect to should have
//! runtime that implements `<BridgedChainName>HeaderApi` to allow bridging with
//! <BridgedName> chain.
use crate::messages_lane::SubstrateMessageLane;
use crate::messages_source::read_client_state;
use crate::on_demand_headers::OnDemandHeadersRelay;
use async_trait::async_trait;
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState};
use bp_runtime::ChainId;
use bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof;
use codec::{Decode, Encode};
use frame_support::{traits::Instance, weights::Weight};
use messages_relay::{
message_lane::{SourceHeaderIdOf, TargetHeaderIdOf},
message_lane_loop::{TargetClient, TargetClientState},
};
use relay_substrate_client::{Chain, Client, Error as SubstrateError, HashOf};
use relay_utils::{relay_loop::Client as RelayClient, BlockNumberBase};
use sp_core::Bytes;
use sp_runtime::{traits::Header as HeaderT, DeserializeOwned};
use std::{marker::PhantomData, ops::RangeInclusive};
/// Message receiving proof returned by the target Substrate node.
pub type SubstrateMessagesReceivingProof<C> = (
UnrewardedRelayersState,
FromBridgedChainMessagesDeliveryProof<HashOf<C>>,
);
/// Substrate client as Substrate messages target.
pub struct SubstrateMessagesTarget<C: Chain, P: SubstrateMessageLane, I> {
client: Client<C>,
lane: P,
lane_id: LaneId,
instance: ChainId,
source_to_target_headers_relay: Option<OnDemandHeadersRelay<P::SourceChain>>,
_phantom: PhantomData<I>,
}
impl<C: Chain, P: SubstrateMessageLane, I> SubstrateMessagesTarget<C, P, I> {
/// Create new Substrate headers target.
pub fn new(
client: Client<C>,
lane: P,
lane_id: LaneId,
instance: ChainId,
source_to_target_headers_relay: Option<OnDemandHeadersRelay<P::SourceChain>>,
) -> Self {
SubstrateMessagesTarget {
client,
lane,
lane_id,
instance,
source_to_target_headers_relay,
_phantom: Default::default(),
}
}
}
impl<C: Chain, P: SubstrateMessageLane, I> Clone for SubstrateMessagesTarget<C, P, I> {
fn clone(&self) -> Self {
Self {
client: self.client.clone(),
lane: self.lane.clone(),
lane_id: self.lane_id,
instance: self.instance,
source_to_target_headers_relay: self.source_to_target_headers_relay.clone(),
_phantom: Default::default(),
}
}
}
#[async_trait]
impl<C, P, I> RelayClient for SubstrateMessagesTarget<C, P, I>
where
C: Chain,
P: SubstrateMessageLane,
I: Send + Sync + Instance,
{
type Error = SubstrateError;
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
self.client.reconnect().await
}
}
#[async_trait]
impl<C, P, I> TargetClient<P> for SubstrateMessagesTarget<C, P, I>
where
C: Chain,
C::Header: DeserializeOwned,
C::Index: DeserializeOwned,
<C::Header as HeaderT>::Number: BlockNumberBase,
P: SubstrateMessageLane<
TargetChain = C,
MessagesReceivingProof = SubstrateMessagesReceivingProof<C>,
TargetHeaderNumber = <C::Header as HeaderT>::Number,
TargetHeaderHash = <C::Header as HeaderT>::Hash,
>,
P::SourceChain: Chain<Hash = P::SourceHeaderHash, BlockNumber = P::SourceHeaderNumber>,
P::SourceHeaderNumber: Decode,
P::SourceHeaderHash: Decode,
I: Send + Sync + Instance,
{
async fn state(&self) -> Result<TargetClientState<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?;
read_client_state::<_, P::SourceHeaderHash, P::SourceHeaderNumber>(
&self.client,
P::BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET,
)
.await
}
async fn latest_received_nonce(
&self,
id: TargetHeaderIdOf<P>,
) -> Result<(TargetHeaderIdOf<P>, 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)?;
Ok((id, latest_received_nonce))
}
async fn latest_confirmed_received_nonce(
&self,
id: TargetHeaderIdOf<P>,
) -> Result<(TargetHeaderIdOf<P>, 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))
}
async fn unrewarded_relayers_state(
&self,
id: TargetHeaderIdOf<P>,
) -> Result<(TargetHeaderIdOf<P>, 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)?;
Ok((id, unrewarded_relayers_state))
}
async fn prove_messages_receiving(
&self,
id: TargetHeaderIdOf<P>,
) -> Result<(TargetHeaderIdOf<P>, P::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::<I>(&self.lane_id);
let proof = self
.client
.prove_storage(vec![inbound_data_key], id.1)
.await?
.iter_nodes()
.collect();
let proof = FromBridgedChainMessagesDeliveryProof {
bridged_header_hash: id.1,
storage_proof: proof,
lane: self.lane_id,
};
Ok((id, (relayers_state, proof)))
}
async fn submit_messages_proof(
&self,
generated_at_header: SourceHeaderIdOf<P>,
nonces: RangeInclusive<MessageNonce>,
proof: P::MessagesProof,
) -> Result<RangeInclusive<MessageNonce>, SubstrateError> {
self.client
.submit_signed_extrinsic(self.lane.target_transactions_author(), |transaction_nonce| {
self.lane.make_messages_delivery_transaction(
transaction_nonce,
generated_at_header,
nonces.clone(),
proof,
)
})
.await?;
Ok(nonces)
}
async fn require_source_header_on_target(&self, id: SourceHeaderIdOf<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;
}
}
async fn estimate_delivery_transaction_in_source_tokens(
&self,
_nonces: RangeInclusive<MessageNonce>,
_total_dispatch_weight: Weight,
_total_size: u32,
) -> P::SourceChainBalance {
num_traits::Zero::zero() // TODO: https://github.com/paritytech/parity-bridges-common/issues/997
}
}
@@ -1,447 +0,0 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! On-demand Substrate -> Substrate headers relay.
use crate::finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate, RECENT_FINALITY_PROOFS_LIMIT, STALL_TIMEOUT,
};
use crate::finality_target::SubstrateFinalityTarget;
use async_std::sync::{Arc, Mutex};
use bp_header_chain::justification::GrandpaJustification;
use finality_relay::{
FinalitySyncParams, FinalitySyncPipeline, SourceClient as FinalitySourceClient, SourceHeader,
TargetClient as FinalityTargetClient,
};
use futures::{select, FutureExt};
use num_traits::{CheckedSub, One, Zero};
use relay_substrate_client::{
finality_source::{FinalitySource as SubstrateFinalitySource, RequiredHeaderNumberRef},
BlockNumberOf, Chain, Client, HashOf, HeaderIdOf, SyncHeader,
};
use relay_utils::{
metrics::MetricsParams, relay_loop::Client as RelayClient, BlockNumberBase, FailedClient, MaybeConnectionError,
};
use std::fmt::Debug;
/// On-demand Substrate <-> Substrate headers relay.
///
/// This relay may be requested to sync more headers, whenever some other relay (e.g. messages relay) needs
/// it to continue its regular work. When enough headers are relayed, on-demand stops syncing headers.
#[derive(Clone)]
pub struct OnDemandHeadersRelay<SourceChain: Chain> {
/// Relay task name.
relay_task_name: String,
/// Shared reference to maximal required finalized header number.
required_header_number: RequiredHeaderNumberRef<SourceChain>,
}
impl<SourceChain: Chain> OnDemandHeadersRelay<SourceChain> {
/// Create new on-demand headers relay.
pub fn new<TargetChain: Chain, TargetSign>(
source_client: Client<SourceChain>,
target_client: Client<TargetChain>,
pipeline: SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>,
maximal_headers_difference: SourceChain::BlockNumber,
) -> Self
where
SourceChain: Chain + Debug,
SourceChain::BlockNumber: BlockNumberBase,
TargetChain: Chain + Debug,
TargetChain::BlockNumber: BlockNumberBase,
TargetSign: Clone + Send + Sync + 'static,
SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>: SubstrateFinalitySyncPipeline<
Hash = HashOf<SourceChain>,
Number = BlockNumberOf<SourceChain>,
Header = SyncHeader<SourceChain::Header>,
FinalityProof = GrandpaJustification<SourceChain::Header>,
TargetChain = TargetChain,
>,
SubstrateFinalityTarget<TargetChain, SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>>:
FinalityTargetClient<SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>>,
{
let required_header_number = Arc::new(Mutex::new(Zero::zero()));
let this = OnDemandHeadersRelay {
relay_task_name: on_demand_headers_relay_name::<SourceChain, TargetChain>(),
required_header_number: required_header_number.clone(),
};
async_std::task::spawn(async move {
background_task(
source_client,
target_client,
pipeline,
maximal_headers_difference,
required_header_number,
)
.await;
});
this
}
/// Someone is asking us to relay given finalized header.
pub async fn require_finalized_header(&self, header_id: HeaderIdOf<SourceChain>) {
let mut required_header_number = self.required_header_number.lock().await;
if header_id.0 > *required_header_number {
log::trace!(
target: "bridge",
"More {} headers required in {} relay. Going to sync up to the {}",
SourceChain::NAME,
self.relay_task_name,
header_id.0,
);
*required_header_number = header_id.0;
}
}
}
/// Background task that is responsible for starting headers relay.
async fn background_task<SourceChain, TargetChain, TargetSign>(
source_client: Client<SourceChain>,
target_client: Client<TargetChain>,
pipeline: SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>,
maximal_headers_difference: SourceChain::BlockNumber,
required_header_number: RequiredHeaderNumberRef<SourceChain>,
) where
SourceChain: Chain + Debug,
SourceChain::BlockNumber: BlockNumberBase,
TargetChain: Chain + Debug,
TargetChain::BlockNumber: BlockNumberBase,
TargetSign: Clone + Send + Sync + 'static,
SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>: SubstrateFinalitySyncPipeline<
Hash = HashOf<SourceChain>,
Number = BlockNumberOf<SourceChain>,
Header = SyncHeader<SourceChain::Header>,
FinalityProof = GrandpaJustification<SourceChain::Header>,
TargetChain = TargetChain,
>,
SubstrateFinalityTarget<TargetChain, SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>>:
FinalityTargetClient<SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>>,
{
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());
let mut latest_non_mandatory_at_source = Zero::zero();
let mut restart_relay = true;
let finality_relay_task = futures::future::Fuse::terminated();
futures::pin_mut!(finality_relay_task);
loop {
select! {
_ = async_std::task::sleep(TargetChain::AVERAGE_BLOCK_INTERVAL).fuse() => {},
_ = finality_relay_task => {
// this should never happen in practice given the current code
restart_relay = true;
},
}
// read best finalized source header number from source
let best_finalized_source_header_at_source =
best_finalized_source_header_at_source(&finality_source, &relay_task_name).await;
if matches!(best_finalized_source_header_at_source, Err(ref e) if e.is_connection_error()) {
relay_utils::relay_loop::reconnect_failed_client(
FailedClient::Source,
relay_utils::relay_loop::RECONNECT_DELAY,
&mut finality_source,
&mut finality_target,
)
.await;
continue;
}
// 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;
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,
relay_utils::relay_loop::RECONNECT_DELAY,
&mut finality_source,
&mut finality_target,
)
.await;
continue;
}
// submit mandatory header if some headers are missing
let best_finalized_source_header_at_target_fmt = format!("{:?}", best_finalized_source_header_at_target);
let mandatory_scan_range = mandatory_headers_scan_range::<SourceChain>(
best_finalized_source_header_at_source.ok(),
best_finalized_source_header_at_target.ok(),
maximal_headers_difference,
&required_header_number,
)
.await;
if let Some(mandatory_scan_range) = mandatory_scan_range {
let relay_mandatory_header_result = relay_mandatory_header_from_range(
&finality_source,
&required_header_number,
best_finalized_source_header_at_target_fmt,
(
std::cmp::max(mandatory_scan_range.0, latest_non_mandatory_at_source),
mandatory_scan_range.1,
),
&relay_task_name,
)
.await;
match relay_mandatory_header_result {
Ok(true) => (),
Ok(false) => {
// 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;
}
Err(e) => {
if e.is_connection_error() {
relay_utils::relay_loop::reconnect_failed_client(
FailedClient::Source,
relay_utils::relay_loop::RECONNECT_DELAY,
&mut finality_source,
&mut finality_target,
)
.await;
continue;
}
}
}
}
// start/restart relay
if restart_relay {
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),
recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT,
stall_timeout: STALL_TIMEOUT,
only_mandatory_headers: false,
},
MetricsParams::disabled(),
futures::future::pending(),
)
.fuse(),
);
restart_relay = false;
}
}
}
/// Returns `Some()` with inclusive range of headers which must be scanned for mandatory headers
/// and the first of such headers must be submitted to the target node.
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>,
) -> 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
let best_finalized_source_header_at_target =
best_finalized_source_header_at_target.unwrap_or(required_header_number);
// if we have been unable to read header number from the source, then let's assume
// that it is the same as at the target
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 {
return None;
}
Some((
best_finalized_source_header_at_target + One::one(),
best_finalized_source_header_at_source,
))
}
/// Try to find mandatory header in the inclusive headers range and, if one is found, ask to relay 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>,
best_finalized_source_header_at_target: String,
range: (SourceChain::BlockNumber, SourceChain::BlockNumber),
relay_task_name: &str,
) -> Result<bool, relay_substrate_client::Error>
where
SubstrateFinalitySource<SourceChain, P>: FinalitySourceClient<P>,
P: FinalitySyncPipeline<Number = SourceChain::BlockNumber>,
{
// search for mandatory header first
let mandatory_source_header_number = find_mandatory_header_in_range(finality_source, range).await?;
// if there are no mandatory headers - we have nothing to do
let mandatory_source_header_number = match mandatory_source_header_number {
Some(mandatory_source_header_number) => mandatory_source_header_number,
None => return Ok(false),
};
// `find_mandatory_header` call may take a while => check if `required_header_number` is still
// less than our `mandatory_source_header_number` before logging anything
let mut required_header_number = required_header_number.lock().await;
if *required_header_number >= mandatory_source_header_number {
return Ok(false);
}
log::trace!(
target: "bridge",
"Too many {} headers missing at target in {} relay ({} vs {}). Going to sync up to the mandatory {}",
SourceChain::NAME,
relay_task_name,
best_finalized_source_header_at_target,
range.1,
mandatory_source_header_number,
);
*required_header_number = mandatory_source_header_number;
Ok(true)
}
/// 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>,
relay_task_name: &str,
) -> Result<SourceChain::BlockNumber, relay_substrate_client::Error>
where
SubstrateFinalitySource<SourceChain, P>: FinalitySourceClient<P>,
P: FinalitySyncPipeline<Number = SourceChain::BlockNumber>,
{
finality_source
.on_chain_best_finalized_block_number()
.await
.map_err(|error| {
log::error!(
target: "bridge",
"Failed to read best finalized source header from source in {} relay: {:?}",
relay_task_name,
error,
);
error
})
}
/// 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>,
relay_task_name: &str,
) -> Result<SourceChain::BlockNumber, <SubstrateFinalityTarget<TargetChain, P> as RelayClient>::Error>
where
SubstrateFinalityTarget<TargetChain, P>: FinalityTargetClient<P>,
P: FinalitySyncPipeline<Number = SourceChain::BlockNumber>,
{
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,
);
error
})
}
/// 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>,
{
let mut current = range.0;
while current <= range.1 {
let header: SyncHeader<SourceChain::Header> = finality_source.client().header_by_number(current).await?.into();
if header.is_mandatory() {
return Ok(Some(current));
}
current += One::one();
}
Ok(None)
}
/// On-demand headers relay task name.
fn on_demand_headers_relay_name<SourceChain: Chain, TargetChain: Chain>() -> String {
format!("on-demand-{}-to-{}", SourceChain::NAME, TargetChain::NAME)
}
#[cfg(test)]
mod tests {
use super::*;
type TestChain = relay_millau_client::Millau;
const AT_SOURCE: Option<bp_millau::BlockNumber> = Some(10);
const AT_TARGET: Option<bp_millau::BlockNumber> = Some(1);
#[async_std::test]
async fn mandatory_headers_scan_range_selects_range_if_too_many_headers_are_missing() {
assert_eq!(
mandatory_headers_scan_range::<TestChain>(AT_SOURCE, AT_TARGET, 5, &Arc::new(Mutex::new(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() {
assert_eq!(
mandatory_headers_scan_range::<TestChain>(AT_SOURCE, AT_TARGET, 10, &Arc::new(Mutex::new(0))).await,
None,
);
}
}