mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 21:01:02 +00:00
Squashed 'bridges/' changes from 23dda62482..407bf44a8a
407bf44a8a add missing license header (#1204) 9babb19810 Custom relay strategy (#1198) c287872a11 fix clippy things (#1200) 3a40e62789 Expose some const value and type (#1186) 32b61476d1 increase sleep before connectingMillau (#1195) aabe7041fa revert messages transactions mortality (#1194) 3651f4f909 Message transactions mortality (#1191) 364d6e155d Bump dependencies (#1180) f0389acc08 cargo +nightly fmt --all (#1192) b270b6a016 Unify error enums in substrate and ethereum clients with `thiserror` (#1094) 58c4946f74 Limit max call size of Rialto/Millau runtimes (#1187) fd56a8cd56 Add UI to the deployment (#1047) 16f01dc736 Westend -> Millau alerts are pending before notifications are sent (#1184) 5628c11ece replace collective flip with babe randomness in Rialto (#1188) 1094a63b00 ignore another (pretty bad) RUSTSEC (#1185) 379fe323ea fix/ignore cargo deny issues (#1183) 92af5e6e64 additional log in finality relay + rephrase "failed" (#1182) b996a3b681 Rialto parachain in test deployments (#1178) 28d9332b44 Resubmit transactions strategy for Polkadot/Kusama (#1175) d0172c6847 Playing with CI (#1179) fb6f42456d fix checks order when registering parachain (#1177) ee828c005a Register-parachain subcommand of substrate-relay (#1170) 8cd2b1a112 Token swap pallet benchmarks (#1174) bb811accb1 fix collision with westend bridge (#1172) 8d2fba70ed add token swaps to test deployments (#1169) b6d1bdfe2c publish rialto parachain collator image (#1171) 834ae4a10a Fix OutboundLaneData types (#1159) 5ee0ea1626 copypasted -> copied (#1168) c3bb835f18 fix spelling (#1167) f90d041dc9 Upgrade `jsonrpsee` to v0.3 (#1051) 598c9b6d0d add some basic tests for swap tokens (#1164) 05e88c61f5 publish images when tag of specific format(e.g. v2021-09-27 + v2021-09-27-1) is published (#1166) 7f3f94a6e0 Fix CI again (#1165) ff37de332f Move calculation relayer reward into `MessageDeliveryAndDispatchPayment` (#1153) 36fbba839b fix clippy warning (#1163) 16da44d018 explicit wasm build (#1158) c9c8226449 Match substrate's fmt (#1148) 2fdd7f3e5e Fix/ignore clippy warnings (#1157) 43dfcc2686 Adding LookupAddress (#1156) 951eaa5582 Add rialto-parachain runtime and node (#1142) 803d266d61 Rename MessageId -> BridgeMessageId (#1152) 5f234484fc Box large arguments of GRANDPA pallet (#1154) cf9abc1011 Fix spelling (#1150) ab83ba2e58 Relay subcommand that performs token RLT <> MLAU token swap (#1141) 832536caf0 Polkadot <> Kusama relayers (#1122) 6d0daa8975 Add `OnMessageAccepted` callback (#1134) 5d03a20b3e Integrate token swap pallet into Millau runtime (#1099) ea4cfa833e Adding MultiAddress type and ValidationCodeHash (#1139) c20325a784 Add tests for `Raw` and `BridgeSendMessage` enum `Call` variants (#1125) 6d802416e2 increase pause before pining Rialto nodes (#1137) b54fa56b62 calculate fee using full message payload (#1132) ca5d8178f5 Add parachain pallets to rialto runtime (#1053) 9eaae4142e fix transaction resubmitter limits for Millau -> Rialto transactions (#1135) 9d4e17783c add --mandatory-headers-only cli option to complex relay (#1129) 1c5e0ec1cb Add local CI info to README (#1131) a8e0929e14 chore: spellchecker fixes (#1130) 3b8e2118e3 set fee for importing mandatory headers to zero (#1127) 49bba9aa52 another bunch of words for spellchecker (#1128) 8a72eafef6 Increase pause before messages generation start (#1126) 1f0ba9a191 Move some associated types from relay_substrate_client::Chain to bp_runtime::Chain (#1087) 74bc1a5b54 Transactions resubmitter (#1083) 21ba001f26 log max balance drop when sending message (#1117) 638a7ddffa Code Cleaning (#1124) be6555c51b Fix buildah logout (#1120) 87539c4a98 Format code work (#1116) 526fe7fdd7 fix spelling (#1119) bd4ce7f241 Fix spelling (#1118) 3c1147858e added missing constants to Kusama/Polkadot primitives (#1114) 52093b22ab Fix delivery transaction estimation used by rational relayer (#1109) 77a2f2fbed Remove fund account checks from upgrade. (#1111) 824334802b Rename param and update comment (#1108) d7784bfe06 Fix spellcheck (#1110) 0b18f5906a Refactor substrate messages source and substrate messages target (#1105) b27240bbff fix compilation (#1107) 9697da4fe8 Emit mortal transactions from relay (#1073) b29396c077 Change vault vars type to env vars (#1084) 35e0bbdc0c Make clippy mandatory. (#1103) a517e8541f Remove unused deps (#1102) 873dae608a Remove unnessary deps (#1101) 13450b74ee Stored conversion rate updater (#1005) 74389829f3 [BREAKING] Migrate messages pallet to frame v2 (#1088) 424da938dd README fix (#1100) 865744c909 upgrade currency exchange pallet to frame v2 (#1097) b5038148b3 Add missing docs (#1095) 0791e911c1 Common crate for substrate-relay (#1082) 3834c9d880 Update high-level-overview.md (#1093) c93553face Increase the time window for messaging alerts. (#1092) 8b9cc3cecd migrate pallet-shift-session-manager to frame v2 (#1090) dc91813c22 migrate eth PoA pallet to frame v2 (#1091) f16bb098cc Migrate dispatch pallet to frame v2 (#1089) 19f4325348 Bridge/This Chain Ids should be exposed as constants on pallet level. (#1085) 6381122df7 Change ChainSpec::from_genesis for Rialto and Millau chains to reflect the chain names. (#1079) 0f1d33e973 Make CI happy again (#1086) 238e65d96f fix typo (#1080) fc008457b6 Token-swap-over-bridge pallet (#944) 3fb97fa5ef Fix full spellcheck (#1076) eae4ed7170 fixed wrong trace (#1075) 219a0fad04 merge two weight-related loops in messages pallet (#1071) fc85632fdb increase_message_fee depends on stored mesage size (#1066) 530f37a23b companion for https://github.com/paritytech/polkadot/pull/3507 (#1067) 53b8cba683 sc_basic_authorship=trace for millau nodes (#1074) 9874e05e98 Improve traces of message generator scripts (#1069) 7b5ee84fbb extract message_details impl into runtime common (#1070) 5a4aed5a8b refund weight for mot pruning messages (#1062) 90e3d1e111 Fix Westend -> Millau sync (#1064) 427d30ddfc When restarting client, also "restart" tokio runtime (#1065) d47c05eeef Change get pipeline sensitive variables from Vault instead of GitLab settings (#1063) d775a85415 use tokio reactor to execute jsonrpsee futures (#1061) 15c8cd61cb Use BABE to author blocks on Rialto (previously: Aura) (#1050) 5186293500 Allow reading suri && password override from file (#1059) b506298262 Update jsonrpsee reference (#1049) 1734d00517 enable weight fee adjustent in Rialto/Millau (#1044) 607265afae Pay dispatch fee at target chain cli option (#1043) ce79ef91be bump dependencies before start referencing polkadot repo (#1048) 924fa24f6d Cli option for greedy relayer + run no-losses relayer by default (#1042) e21eba7b59 Yrong README Fixup + M1 Fixes (#1045) 20d08204a2 Confirm delivery detects when more than expected messages are confirmed (#1039) 994b846b52 pre and post dispatch weights of OnDeliveryConfirmed callback (#1040) 1dd5297e84 give real value to Rialto and Millau tokens (#1038) 035bee8715 Use real conversion rate in greedy relayer strategy (#1035) 9cfaecd0f7 fixed metrics prefix (#1037) 1d8d224937 Use kebab-case for bridge arguments (#1036) f30a4c79a6 Shared reference to conversion rate metric value (#1034) c34d7a5cbb estimate transaction fee (#1015) 93404b18bb change alert period from 2m to 10m for Westend -> Millau (GRANDPA or public node itself is lagging sometimes) (#1032) git-subtree-dir: bridges git-subtree-split: 407bf44a8a5f4e60aceef2dc755cd9ff09929ac3
This commit is contained in:
@@ -1,64 +0,0 @@
|
||||
[package]
|
||||
name = "substrate-relay"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
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" }
|
||||
futures = "0.3.12"
|
||||
hex = "0.4"
|
||||
log = "0.4.14"
|
||||
num-format = "0.4"
|
||||
num-traits = "0.2"
|
||||
paste = "1.0"
|
||||
structopt = "0.3"
|
||||
|
||||
# Bridge dependencies
|
||||
|
||||
bp-header-chain = { path = "../../primitives/header-chain" }
|
||||
bp-kusama = { path = "../../primitives/chain-kusama" }
|
||||
bp-messages = { path = "../../primitives/messages" }
|
||||
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-rococo = { path = "../../primitives/chain-rococo" }
|
||||
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 = { version = "0.14.1" }
|
||||
finality-relay = { path = "../finality" }
|
||||
headers-relay = { path = "../headers" }
|
||||
messages-relay = { path = "../messages" }
|
||||
millau-runtime = { path = "../../bin/millau/runtime" }
|
||||
pallet-bridge-messages = { path = "../../modules/messages" }
|
||||
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-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-runtime = { path = "../../bin/rialto/runtime" }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
frame-support = { 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-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" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.3"
|
||||
pallet-bridge-grandpa = { path = "../../modules/grandpa" }
|
||||
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
@@ -1,105 +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/>.
|
||||
|
||||
//! Millau chain specification for CLI.
|
||||
|
||||
use crate::cli::{
|
||||
bridge,
|
||||
encode_call::{self, Call, CliEncodeCall},
|
||||
encode_message, send_message, CliChain,
|
||||
};
|
||||
use bp_message_dispatch::{CallOrigin, MessagePayload};
|
||||
use codec::Decode;
|
||||
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
|
||||
use relay_millau_client::Millau;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
impl CliEncodeCall for Millau {
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
bp_millau::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, .. } => 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
|
||||
),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn get_dispatch_info(call: &millau_runtime::Call) -> anyhow::Result<DispatchInfo> {
|
||||
Ok(call.get_dispatch_info())
|
||||
}
|
||||
}
|
||||
|
||||
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>>;
|
||||
|
||||
fn ss58_format() -> u16 {
|
||||
millau_runtime::SS58Prefix::get() as u16
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
bp_millau::max_extrinsic_weight()
|
||||
}
|
||||
|
||||
// TODO [#854|#843] support multiple bridges?
|
||||
fn encode_message(message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
|
||||
match message {
|
||||
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
|
||||
.map_err(|e| format!("Failed to decode Millau's MessagePayload: {:?}", e)),
|
||||
encode_message::MessagePayload::Call { mut call, mut sender } => {
|
||||
type Source = Millau;
|
||||
type Target = relay_rialto_client::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::MILLAU_TO_RIALTO_INDEX);
|
||||
let call = Target::encode_call(&call).map_err(|e| e.to_string())?;
|
||||
let weight = call.get_dispatch_info().weight;
|
||||
|
||||
Ok(send_message::message_payload(spec_version, weight, origin, &call))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +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/>.
|
||||
|
||||
//! 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};
|
||||
|
||||
/// Millau-to-Rialto finality sync pipeline.
|
||||
pub(crate) type MillauFinalityToRialto = SubstrateFinalityToSubstrate<Millau, Rialto, RialtoSigningParams>;
|
||||
|
||||
impl SubstrateFinalitySyncPipeline for MillauFinalityToRialto {
|
||||
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()
|
||||
}
|
||||
|
||||
fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Rialto as Chain>::Index,
|
||||
header: MillauSyncHeader,
|
||||
proof: GrandpaJustification<bp_millau::Header>,
|
||||
) -> Bytes {
|
||||
let call = rialto_runtime::BridgeGrandpaMillauCall::submit_finality_proof(header.into_inner(), proof).into();
|
||||
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Rialto::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
@@ -1,237 +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/>.
|
||||
|
||||
//! 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 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};
|
||||
|
||||
/// Millau-to-Rialto message lane.
|
||||
pub type MillauMessagesToRialto =
|
||||
SubstrateMessageLaneToSubstrate<Millau, MillauSigningParams, Rialto, RialtoSigningParams>;
|
||||
|
||||
impl SubstrateMessageLane for MillauMessagesToRialto {
|
||||
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 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 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;
|
||||
|
||||
type SourceChain = Millau;
|
||||
type TargetChain = Rialto;
|
||||
|
||||
fn source_transactions_author(&self) -> bp_millau::AccountId {
|
||||
(*self.source_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_receiving_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Millau as Chain>::Index,
|
||||
_generated_at_block: RialtoHeaderId,
|
||||
proof: <Self 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();
|
||||
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);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Rialto -> Millau confirmation transaction. Weight: {}/{}, size: {}/{}",
|
||||
call_weight,
|
||||
bp_millau::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_millau::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
|
||||
fn target_transactions_author(&self) -> bp_rialto::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_delivery_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Rialto as Chain>::Index,
|
||||
_generated_at_header: MillauHeaderId,
|
||||
_nonces: RangeInclusive<MessageNonce>,
|
||||
proof: <Self 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: rialto_runtime::Call = rialto_runtime::MessagesCall::receive_messages_proof(
|
||||
self.relayer_id_at_source.clone(),
|
||||
proof,
|
||||
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);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Millau -> Rialto delivery transaction. Weight: {}/{}, size: {}/{}",
|
||||
call_weight,
|
||||
bp_rialto::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_rialto::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
|
||||
/// Millau node as messages source.
|
||||
type MillauSourceClient =
|
||||
SubstrateMessagesSource<Millau, MillauMessagesToRialto, millau_runtime::WithRialtoMessagesInstance>;
|
||||
|
||||
/// Rialto node as messages target.
|
||||
type RialtoTargetClient =
|
||||
SubstrateMessagesTarget<Rialto, MillauMessagesToRialto, rialto_runtime::WithMillauMessagesInstance>;
|
||||
|
||||
/// 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);
|
||||
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,
|
||||
};
|
||||
|
||||
// 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>>(
|
||||
bp_rialto::max_extrinsic_weight(),
|
||||
bp_rialto::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
);
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Starting Millau -> Rialto messages relay.\n\t\
|
||||
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_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
);
|
||||
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
source_tick: Millau::AVERAGE_BLOCK_INTERVAL,
|
||||
target_tick: Rialto::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_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,
|
||||
},
|
||||
},
|
||||
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,
|
||||
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(),
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -1,341 +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/>.
|
||||
|
||||
//! Chain-specific relayer configuration.
|
||||
|
||||
pub mod millau_headers_to_rialto;
|
||||
pub mod millau_messages_to_rialto;
|
||||
pub mod rialto_headers_to_millau;
|
||||
pub mod rialto_messages_to_millau;
|
||||
pub mod rococo_headers_to_wococo;
|
||||
pub mod rococo_messages_to_wococo;
|
||||
pub mod westend_headers_to_millau;
|
||||
pub mod wococo_headers_to_rococo;
|
||||
pub mod wococo_messages_to_rococo;
|
||||
|
||||
mod millau;
|
||||
mod rialto;
|
||||
mod rococo;
|
||||
mod westend;
|
||||
mod wococo;
|
||||
|
||||
use relay_utils::metrics::{FloatJsonValueMetric, MetricsParams};
|
||||
|
||||
pub(crate) fn add_polkadot_kusama_price_metrics<T: finality_relay::FinalitySyncPipeline>(
|
||||
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(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cli::{encode_call, send_message};
|
||||
use bp_messages::source_chain::TargetHeaderChain;
|
||||
use codec::Encode;
|
||||
use frame_support::dispatch::GetDispatchInfo;
|
||||
use relay_millau_client::Millau;
|
||||
use relay_rialto_client::Rialto;
|
||||
use relay_substrate_client::TransactionSignScheme;
|
||||
use sp_core::Pair;
|
||||
use sp_runtime::traits::{IdentifyAccount, Verify};
|
||||
|
||||
#[test]
|
||||
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 millau_public: bp_millau::AccountSigner = millau_sign.public().into();
|
||||
let millau_account_id: bp_millau::AccountId = millau_public.into_account();
|
||||
|
||||
let digest = millau_runtime::millau_to_rialto_account_ownership_digest(
|
||||
&call,
|
||||
millau_account_id,
|
||||
rialto_runtime::VERSION.spec_version,
|
||||
);
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 rialto_public: bp_rialto::AccountSigner = rialto_sign.public().into();
|
||||
let rialto_account_id: bp_rialto::AccountId = rialto_public.into_account();
|
||||
|
||||
let digest = rialto_runtime::rialto_to_millau_account_ownership_digest(
|
||||
&call,
|
||||
rialto_account_id,
|
||||
millau_runtime::VERSION.spec_version,
|
||||
);
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maximal_rialto_to_millau_message_arguments_size_is_computed_correctly() {
|
||||
use rialto_runtime::millau_messages::Millau;
|
||||
|
||||
let maximal_remark_size = encode_call::compute_maximal_message_arguments_size(
|
||||
bp_rialto::max_extrinsic_size(),
|
||||
bp_millau::max_extrinsic_size(),
|
||||
);
|
||||
|
||||
let call: millau_runtime::Call = millau_runtime::SystemCall::remark(vec![42; maximal_remark_size as _]).into();
|
||||
let payload = send_message::message_payload(
|
||||
Default::default(),
|
||||
call.get_dispatch_info().weight,
|
||||
bp_message_dispatch::CallOrigin::SourceRoot,
|
||||
&call,
|
||||
);
|
||||
assert_eq!(Millau::verify_message(&payload), Ok(()));
|
||||
|
||||
let call: millau_runtime::Call =
|
||||
millau_runtime::SystemCall::remark(vec![42; (maximal_remark_size + 1) as _]).into();
|
||||
let payload = send_message::message_payload(
|
||||
Default::default(),
|
||||
call.get_dispatch_info().weight,
|
||||
bp_message_dispatch::CallOrigin::SourceRoot,
|
||||
&call,
|
||||
);
|
||||
assert!(Millau::verify_message(&payload).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maximal_size_remark_to_rialto_is_generated_correctly() {
|
||||
assert!(
|
||||
bridge_runtime_common::messages::target::maximal_incoming_message_size(
|
||||
bp_rialto::max_extrinsic_size()
|
||||
) > bp_millau::max_extrinsic_size(),
|
||||
"We can't actually send maximal messages to Rialto from Millau, because Millau extrinsics can't be that large",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maximal_rialto_to_millau_message_dispatch_weight_is_computed_correctly() {
|
||||
use rialto_runtime::millau_messages::Millau;
|
||||
|
||||
let maximal_dispatch_weight =
|
||||
send_message::compute_maximal_message_dispatch_weight(bp_millau::max_extrinsic_weight());
|
||||
let call: millau_runtime::Call = rialto_runtime::SystemCall::remark(vec![]).into();
|
||||
|
||||
let payload = send_message::message_payload(
|
||||
Default::default(),
|
||||
maximal_dispatch_weight,
|
||||
bp_message_dispatch::CallOrigin::SourceRoot,
|
||||
&call,
|
||||
);
|
||||
assert_eq!(Millau::verify_message(&payload), Ok(()));
|
||||
|
||||
let payload = send_message::message_payload(
|
||||
Default::default(),
|
||||
maximal_dispatch_weight + 1,
|
||||
bp_message_dispatch::CallOrigin::SourceRoot,
|
||||
&call,
|
||||
);
|
||||
assert!(Millau::verify_message(&payload).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maximal_weight_fill_block_to_rialto_is_generated_correctly() {
|
||||
use millau_runtime::rialto_messages::Rialto;
|
||||
|
||||
let maximal_dispatch_weight =
|
||||
send_message::compute_maximal_message_dispatch_weight(bp_rialto::max_extrinsic_weight());
|
||||
let call: rialto_runtime::Call = millau_runtime::SystemCall::remark(vec![]).into();
|
||||
|
||||
let payload = send_message::message_payload(
|
||||
Default::default(),
|
||||
maximal_dispatch_weight,
|
||||
bp_message_dispatch::CallOrigin::SourceRoot,
|
||||
&call,
|
||||
);
|
||||
assert_eq!(Rialto::verify_message(&payload), Ok(()));
|
||||
|
||||
let payload = send_message::message_payload(
|
||||
Default::default(),
|
||||
maximal_dispatch_weight + 1,
|
||||
bp_message_dispatch::CallOrigin::SourceRoot,
|
||||
&call,
|
||||
);
|
||||
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_tx = Rialto::sign_transaction(
|
||||
Default::default(),
|
||||
&sp_keyring::AccountKeyring::Alice.pair(),
|
||||
0,
|
||||
rialto_call.clone(),
|
||||
);
|
||||
let extra_bytes_in_transaction = rialto_tx.encode().len() - rialto_call.encode().len();
|
||||
assert!(
|
||||
bp_rialto::TX_EXTRA_BYTES as usize >= extra_bytes_in_transaction,
|
||||
"Hardcoded number of extra bytes in Rialto transaction {} is lower than actual value: {}",
|
||||
bp_rialto::TX_EXTRA_BYTES,
|
||||
extra_bytes_in_transaction,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn millau_tx_extra_bytes_constant_is_correct() {
|
||||
let millau_call = millau_runtime::Call::System(millau_runtime::SystemCall::remark(vec![]));
|
||||
let millau_tx = Millau::sign_transaction(
|
||||
Default::default(),
|
||||
&sp_keyring::AccountKeyring::Alice.pair(),
|
||||
0,
|
||||
millau_call.clone(),
|
||||
);
|
||||
let extra_bytes_in_transaction = millau_tx.encode().len() - millau_call.encode().len();
|
||||
assert!(
|
||||
bp_millau::TX_EXTRA_BYTES as usize >= extra_bytes_in_transaction,
|
||||
"Hardcoded number of extra bytes in Millau transaction {} is lower than actual value: {}",
|
||||
bp_millau::TX_EXTRA_BYTES,
|
||||
extra_bytes_in_transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod rococo_tests {
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use codec::Encode;
|
||||
|
||||
#[test]
|
||||
fn scale_compatibility_of_bridges_call() {
|
||||
// given
|
||||
let header = sp_runtime::generic::Header {
|
||||
parent_hash: Default::default(),
|
||||
number: Default::default(),
|
||||
state_root: Default::default(),
|
||||
extrinsics_root: Default::default(),
|
||||
digest: sp_runtime::generic::Digest { logs: vec![] },
|
||||
};
|
||||
|
||||
let justification = GrandpaJustification {
|
||||
round: 0,
|
||||
commit: finality_grandpa::Commit {
|
||||
target_hash: Default::default(),
|
||||
target_number: Default::default(),
|
||||
precommits: vec![],
|
||||
},
|
||||
votes_ancestries: vec![],
|
||||
};
|
||||
|
||||
let actual = relay_rococo_client::runtime::BridgeGrandpaWococoCall::submit_finality_proof(
|
||||
header.clone(),
|
||||
justification.clone(),
|
||||
);
|
||||
let expected = millau_runtime::BridgeGrandpaRialtoCall::<millau_runtime::Runtime>::submit_finality_proof(
|
||||
header,
|
||||
justification,
|
||||
);
|
||||
|
||||
// when
|
||||
let actual_encoded = actual.encode();
|
||||
let expected_encoded = expected.encode();
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
actual_encoded, expected_encoded,
|
||||
"\n\nEncoding difference.\nGot {:#?} \nExpected: {:#?}",
|
||||
actual, expected
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod westend_tests {
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use codec::Encode;
|
||||
|
||||
#[test]
|
||||
fn scale_compatibility_of_bridges_call() {
|
||||
// given
|
||||
let header = sp_runtime::generic::Header {
|
||||
parent_hash: Default::default(),
|
||||
number: Default::default(),
|
||||
state_root: Default::default(),
|
||||
extrinsics_root: Default::default(),
|
||||
digest: sp_runtime::generic::Digest { logs: vec![] },
|
||||
};
|
||||
|
||||
let justification = GrandpaJustification {
|
||||
round: 0,
|
||||
commit: finality_grandpa::Commit {
|
||||
target_hash: Default::default(),
|
||||
target_number: Default::default(),
|
||||
precommits: vec![],
|
||||
},
|
||||
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,
|
||||
);
|
||||
|
||||
// when
|
||||
let actual_encoded = actual.encode();
|
||||
let expected_encoded = expected.encode();
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
actual_encoded, expected_encoded,
|
||||
"\n\nEncoding difference.\nGot {:#?} \nExpected: {:#?}",
|
||||
actual, expected
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,102 +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/>.
|
||||
|
||||
//! Rialto chain specification for CLI.
|
||||
|
||||
use crate::cli::{
|
||||
bridge,
|
||||
encode_call::{self, Call, CliEncodeCall},
|
||||
encode_message, send_message, CliChain,
|
||||
};
|
||||
use bp_message_dispatch::{CallOrigin, MessagePayload};
|
||||
use codec::Decode;
|
||||
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
|
||||
use relay_rialto_client::Rialto;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
impl CliEncodeCall for Rialto {
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
bp_rialto::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_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
|
||||
),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn get_dispatch_info(call: &rialto_runtime::Call) -> anyhow::Result<DispatchInfo> {
|
||||
Ok(call.get_dispatch_info())
|
||||
}
|
||||
}
|
||||
|
||||
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>>;
|
||||
|
||||
fn ss58_format() -> u16 {
|
||||
rialto_runtime::SS58Prefix::get() as u16
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
bp_rialto::max_extrinsic_weight()
|
||||
}
|
||||
|
||||
fn encode_message(message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
|
||||
match message {
|
||||
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
|
||||
.map_err(|e| format!("Failed to decode Rialto's MessagePayload: {:?}", e)),
|
||||
encode_message::MessagePayload::Call { mut call, mut sender } => {
|
||||
type Source = Rialto;
|
||||
type Target = relay_millau_client::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::RIALTO_TO_MILLAU_INDEX);
|
||||
let call = Target::encode_call(&call).map_err(|e| e.to_string())?;
|
||||
let weight = call.get_dispatch_info().weight;
|
||||
|
||||
Ok(send_message::message_payload(spec_version, weight, origin, &call))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +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/>.
|
||||
|
||||
//! 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};
|
||||
|
||||
/// Rialto-to-Millau finality sync pipeline.
|
||||
pub(crate) type RialtoFinalityToMillau = SubstrateFinalityToSubstrate<Rialto, Millau, MillauSigningParams>;
|
||||
|
||||
impl SubstrateFinalitySyncPipeline for RialtoFinalityToMillau {
|
||||
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()
|
||||
}
|
||||
|
||||
fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Millau as Chain>::Index,
|
||||
header: RialtoSyncHeader,
|
||||
proof: GrandpaJustification<bp_rialto::Header>,
|
||||
) -> Bytes {
|
||||
let call = millau_runtime::BridgeGrandpaRialtoCall::<
|
||||
millau_runtime::Runtime,
|
||||
millau_runtime::RialtoGrandpaInstance,
|
||||
>::submit_finality_proof(header.into_inner(), proof)
|
||||
.into();
|
||||
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
@@ -1,236 +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/>.
|
||||
|
||||
//! 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 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};
|
||||
|
||||
/// Rialto-to-Millau message lane.
|
||||
pub type RialtoMessagesToMillau =
|
||||
SubstrateMessageLaneToSubstrate<Rialto, RialtoSigningParams, Millau, MillauSigningParams>;
|
||||
|
||||
impl SubstrateMessageLane for RialtoMessagesToMillau {
|
||||
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 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 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;
|
||||
|
||||
type SourceChain = Rialto;
|
||||
type TargetChain = Millau;
|
||||
|
||||
fn source_transactions_author(&self) -> bp_rialto::AccountId {
|
||||
(*self.source_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_receiving_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Rialto as Chain>::Index,
|
||||
_generated_at_block: MillauHeaderId,
|
||||
proof: <Self 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();
|
||||
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);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Millau -> Rialto confirmation transaction. Weight: {}/{}, size: {}/{}",
|
||||
call_weight,
|
||||
bp_rialto::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_rialto::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
|
||||
fn target_transactions_author(&self) -> bp_millau::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_delivery_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Millau as Chain>::Index,
|
||||
_generated_at_header: RialtoHeaderId,
|
||||
_nonces: RangeInclusive<MessageNonce>,
|
||||
proof: <Self 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: millau_runtime::Call = millau_runtime::MessagesCall::receive_messages_proof(
|
||||
self.relayer_id_at_source.clone(),
|
||||
proof,
|
||||
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);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Rialto -> Millau delivery transaction. Weight: {}/{}, size: {}/{}",
|
||||
call_weight,
|
||||
bp_millau::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_millau::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
|
||||
/// Rialto node as messages source.
|
||||
type RialtoSourceClient =
|
||||
SubstrateMessagesSource<Rialto, RialtoMessagesToMillau, rialto_runtime::WithMillauMessagesInstance>;
|
||||
|
||||
/// Millau node as messages target.
|
||||
type MillauTargetClient =
|
||||
SubstrateMessagesTarget<Millau, RialtoMessagesToMillau, millau_runtime::WithRialtoMessagesInstance>;
|
||||
|
||||
/// 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);
|
||||
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,
|
||||
};
|
||||
|
||||
// 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>>(
|
||||
bp_millau::max_extrinsic_weight(),
|
||||
bp_millau::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
|
||||
);
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Starting Rialto -> Millau messages relay.\n\t\
|
||||
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_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
);
|
||||
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
source_tick: Rialto::AVERAGE_BLOCK_INTERVAL,
|
||||
target_tick: Millau::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_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,
|
||||
},
|
||||
},
|
||||
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,
|
||||
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(),
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -1,98 +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 codec::Decode;
|
||||
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
|
||||
use relay_rococo_client::Rococo;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
use crate::cli::{
|
||||
bridge,
|
||||
encode_call::{Call, CliEncodeCall},
|
||||
encode_message, CliChain,
|
||||
};
|
||||
|
||||
/// Weight of the `system::remark` call at Rococo.
|
||||
///
|
||||
/// This weight is larger (x2) than actual weight at current Rooco 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;
|
||||
|
||||
impl CliEncodeCall for Rococo {
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
bp_rococo::max_extrinsic_size()
|
||||
}
|
||||
|
||||
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(
|
||||
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
|
||||
),
|
||||
},
|
||||
_ => anyhow::bail!("The call is not supported"),
|
||||
})
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
_ => anyhow::bail!("Unsupported Rococo call: {:?}", call),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CliChain for Rococo {
|
||||
const RUNTIME_VERSION: RuntimeVersion = bp_rococo::VERSION;
|
||||
|
||||
type KeyPair = sp_core::sr25519::Pair;
|
||||
type MessagePayload = ();
|
||||
|
||||
fn ss58_format() -> u16 {
|
||||
42
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -1,72 +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/>.
|
||||
|
||||
//! 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};
|
||||
|
||||
/// Rococo-to-Wococo finality sync pipeline.
|
||||
pub(crate) type RococoFinalityToWococo = SubstrateFinalityToSubstrate<Rococo, Wococo, WococoSigningParams>;
|
||||
|
||||
impl SubstrateFinalitySyncPipeline for RococoFinalityToWococo {
|
||||
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)
|
||||
}
|
||||
|
||||
fn start_relay_guards(&self) {
|
||||
relay_substrate_client::guard::abort_on_spec_version_change(
|
||||
self.target_client.clone(),
|
||||
bp_wococo::VERSION.spec_version,
|
||||
);
|
||||
relay_substrate_client::guard::abort_when_account_balance_decreased(
|
||||
self.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()
|
||||
}
|
||||
|
||||
fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Wococo as Chain>::Index,
|
||||
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),
|
||||
);
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Wococo::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
@@ -1,227 +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/>.
|
||||
|
||||
//! 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 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};
|
||||
|
||||
/// Rococo-to-Wococo message lane.
|
||||
pub type RococoMessagesToWococo =
|
||||
SubstrateMessageLaneToSubstrate<Rococo, RococoSigningParams, Wococo, WococoSigningParams>;
|
||||
|
||||
impl SubstrateMessageLane for RococoMessagesToWococo {
|
||||
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 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 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;
|
||||
|
||||
type SourceChain = Rococo;
|
||||
type TargetChain = Wococo;
|
||||
|
||||
fn source_transactions_author(&self) -> bp_rococo::AccountId {
|
||||
(*self.source_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_receiving_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Rococo as Chain>::Index,
|
||||
_generated_at_block: WococoHeaderId,
|
||||
proof: <Self as MessageLane>::MessagesReceivingProof,
|
||||
) -> Bytes {
|
||||
let (relayers_state, proof) = proof;
|
||||
let call = relay_rococo_client::runtime::Call::BridgeMessagesWococo(
|
||||
relay_rococo_client::runtime::BridgeMessagesWococoCall::receive_messages_delivery_proof(
|
||||
proof,
|
||||
relayers_state,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.source_client.genesis_hash();
|
||||
let transaction = Rococo::sign_transaction(genesis_hash, &self.source_sign, transaction_nonce, call);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Wococo -> Rococo confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
bp_rococo::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_rococo::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
|
||||
fn target_transactions_author(&self) -> bp_wococo::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_delivery_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Wococo as Chain>::Index,
|
||||
_generated_at_header: RococoHeaderId,
|
||||
_nonces: RangeInclusive<MessageNonce>,
|
||||
proof: <Self 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_wococo_client::runtime::Call::BridgeMessagesRococo(
|
||||
relay_wococo_client::runtime::BridgeMessagesRococoCall::receive_messages_proof(
|
||||
self.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);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Rococo -> Wococo delivery transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
bp_wococo::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_wococo::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
|
||||
/// Rococo node as messages source.
|
||||
type RococoSourceClient =
|
||||
SubstrateMessagesSource<Rococo, RococoMessagesToWococo, relay_rococo_client::runtime::WithWococoMessagesInstance>;
|
||||
|
||||
/// Wococo node as messages target.
|
||||
type WococoTargetClient =
|
||||
SubstrateMessagesTarget<Wococo, RococoMessagesToWococo, relay_wococo_client::runtime::WithRococoMessagesInstance>;
|
||||
|
||||
/// 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);
|
||||
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,
|
||||
};
|
||||
|
||||
// 2/3 is reserved for proofs and tx overhead
|
||||
let max_messages_size_in_single_batch = bp_wococo::max_extrinsic_size() / 3;
|
||||
// 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>>(
|
||||
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,
|
||||
);
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Starting Rococo -> Wococo messages relay.\n\t\
|
||||
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_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
);
|
||||
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
source_tick: Rococo::AVERAGE_BLOCK_INTERVAL,
|
||||
target_tick: Wococo::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_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,
|
||||
},
|
||||
},
|
||||
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,
|
||||
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(),
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -1,41 +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/>.
|
||||
|
||||
//! Westend chain specification for CLI.
|
||||
|
||||
use crate::cli::{encode_message, CliChain};
|
||||
use frame_support::weights::Weight;
|
||||
use relay_westend_client::Westend;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
impl CliChain for Westend {
|
||||
const RUNTIME_VERSION: RuntimeVersion = bp_westend::VERSION;
|
||||
|
||||
type KeyPair = sp_core::sr25519::Pair;
|
||||
type MessagePayload = ();
|
||||
|
||||
fn ss58_format() -> u16 {
|
||||
42
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
0
|
||||
}
|
||||
|
||||
fn encode_message(_message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
|
||||
Err("Sending messages from Westend is not yet supported.".into())
|
||||
}
|
||||
}
|
||||
@@ -1,62 +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/>.
|
||||
|
||||
//! 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};
|
||||
|
||||
/// Westend-to-Millau finality sync pipeline.
|
||||
pub(crate) type WestendFinalityToMillau = SubstrateFinalityToSubstrate<Westend, Millau, MillauSigningParams>;
|
||||
|
||||
impl SubstrateFinalitySyncPipeline for WestendFinalityToMillau {
|
||||
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)
|
||||
}
|
||||
|
||||
fn transactions_author(&self) -> bp_millau::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Millau as Chain>::Index,
|
||||
header: WestendSyncHeader,
|
||||
proof: GrandpaJustification<bp_westend::Header>,
|
||||
) -> Bytes {
|
||||
let call = millau_runtime::BridgeGrandpaWestendCall::<
|
||||
millau_runtime::Runtime,
|
||||
millau_runtime::WestendGrandpaInstance,
|
||||
>::submit_finality_proof(header.into_inner(), proof)
|
||||
.into();
|
||||
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
@@ -1,92 +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 codec::Decode;
|
||||
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
|
||||
use relay_wococo_client::Wococo;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
use crate::cli::{
|
||||
bridge,
|
||||
encode_call::{Call, CliEncodeCall},
|
||||
encode_message, CliChain,
|
||||
};
|
||||
|
||||
impl CliEncodeCall for Wococo {
|
||||
fn max_extrinsic_size() -> u32 {
|
||||
bp_wococo::max_extrinsic_size()
|
||||
}
|
||||
|
||||
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(
|
||||
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
|
||||
),
|
||||
},
|
||||
_ => anyhow::bail!("The call is not supported"),
|
||||
})
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
_ => anyhow::bail!("Unsupported Rococo call: {:?}", call),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CliChain for Wococo {
|
||||
const RUNTIME_VERSION: RuntimeVersion = bp_wococo::VERSION;
|
||||
|
||||
type KeyPair = sp_core::sr25519::Pair;
|
||||
type MessagePayload = ();
|
||||
|
||||
fn ss58_format() -> u16 {
|
||||
42
|
||||
}
|
||||
|
||||
fn max_extrinsic_weight() -> Weight {
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -1,116 +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/>.
|
||||
|
||||
//! Wococo-to-Rococo headers sync entrypoint.
|
||||
|
||||
use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
|
||||
|
||||
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_utils::metrics::MetricsParams;
|
||||
use relay_wococo_client::{SyncHeader as WococoSyncHeader, Wococo};
|
||||
use sp_core::{Bytes, Pair};
|
||||
|
||||
/// Maximal saturating difference between `balance(now)` and `balance(now-24h)` to treat
|
||||
/// relay as gone wild.
|
||||
///
|
||||
/// See `maximal_balance_decrease_per_day_is_sane` test for details.
|
||||
/// Note that this is in plancks, so this corresponds to `1500 UNITS`.
|
||||
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>;
|
||||
|
||||
impl SubstrateFinalitySyncPipeline for WococoFinalityToRococo {
|
||||
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)
|
||||
}
|
||||
|
||||
fn start_relay_guards(&self) {
|
||||
relay_substrate_client::guard::abort_on_spec_version_change(
|
||||
self.target_client.clone(),
|
||||
bp_rococo::VERSION.spec_version,
|
||||
);
|
||||
relay_substrate_client::guard::abort_when_account_balance_decreased(
|
||||
self.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()
|
||||
}
|
||||
|
||||
fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Rococo as Chain>::Index,
|
||||
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),
|
||||
);
|
||||
let genesis_hash = *self.target_client.genesis_hash();
|
||||
let transaction = Rococo::sign_transaction(genesis_hash, &self.target_sign, transaction_nonce, call);
|
||||
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use frame_support::weights::WeightToFeePolynomial;
|
||||
use pallet_bridge_grandpa::weights::WeightInfo;
|
||||
|
||||
#[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;
|
||||
assert!(
|
||||
MAXIMAL_BALANCE_DECREASE_PER_DAY >= maximal_expected_decrease,
|
||||
"Maximal expected loss per day {} is larger than hardcoded {}",
|
||||
maximal_expected_decrease,
|
||||
MAXIMAL_BALANCE_DECREASE_PER_DAY,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,227 +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/>.
|
||||
|
||||
//! 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 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};
|
||||
|
||||
/// Wococo-to-Rococo message lane.
|
||||
pub type WococoMessagesToRococo =
|
||||
SubstrateMessageLaneToSubstrate<Wococo, WococoSigningParams, Rococo, RococoSigningParams>;
|
||||
|
||||
impl SubstrateMessageLane for WococoMessagesToRococo {
|
||||
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 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 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;
|
||||
|
||||
type SourceChain = Wococo;
|
||||
type TargetChain = Rococo;
|
||||
|
||||
fn source_transactions_author(&self) -> bp_wococo::AccountId {
|
||||
(*self.source_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_receiving_proof_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Wococo as Chain>::Index,
|
||||
_generated_at_block: RococoHeaderId,
|
||||
proof: <Self as MessageLane>::MessagesReceivingProof,
|
||||
) -> Bytes {
|
||||
let (relayers_state, proof) = proof;
|
||||
let call = relay_wococo_client::runtime::Call::BridgeMessagesRococo(
|
||||
relay_wococo_client::runtime::BridgeMessagesRococoCall::receive_messages_delivery_proof(
|
||||
proof,
|
||||
relayers_state,
|
||||
),
|
||||
);
|
||||
let genesis_hash = *self.source_client.genesis_hash();
|
||||
let transaction = Wococo::sign_transaction(genesis_hash, &self.source_sign, transaction_nonce, call);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Rococo -> Wococo confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
bp_wococo::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_wococo::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
|
||||
fn target_transactions_author(&self) -> bp_rococo::AccountId {
|
||||
(*self.target_sign.public().as_array_ref()).into()
|
||||
}
|
||||
|
||||
fn make_messages_delivery_transaction(
|
||||
&self,
|
||||
transaction_nonce: <Rococo as Chain>::Index,
|
||||
_generated_at_header: WococoHeaderId,
|
||||
_nonces: RangeInclusive<MessageNonce>,
|
||||
proof: <Self 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_rococo_client::runtime::Call::BridgeMessagesWococo(
|
||||
relay_rococo_client::runtime::BridgeMessagesWococoCall::receive_messages_proof(
|
||||
self.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);
|
||||
log::trace!(
|
||||
target: "bridge",
|
||||
"Prepared Wococo -> Rococo delivery transaction. Weight: <unknown>/{}, size: {}/{}",
|
||||
bp_rococo::max_extrinsic_weight(),
|
||||
transaction.encode().len(),
|
||||
bp_rococo::max_extrinsic_size(),
|
||||
);
|
||||
Bytes(transaction.encode())
|
||||
}
|
||||
}
|
||||
|
||||
/// Wococo node as messages source.
|
||||
type WococoSourceClient =
|
||||
SubstrateMessagesSource<Wococo, WococoMessagesToRococo, relay_wococo_client::runtime::WithRococoMessagesInstance>;
|
||||
|
||||
/// Rococo node as messages target.
|
||||
type RococoTargetClient =
|
||||
SubstrateMessagesTarget<Rococo, WococoMessagesToRococo, relay_rococo_client::runtime::WithWococoMessagesInstance>;
|
||||
|
||||
/// 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);
|
||||
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,
|
||||
};
|
||||
|
||||
// 2/3 is reserved for proofs and tx overhead
|
||||
let max_messages_size_in_single_batch = bp_rococo::max_extrinsic_size() / 3;
|
||||
// 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>>(
|
||||
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,
|
||||
);
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Starting Wococo -> Rococo messages relay.\n\t\
|
||||
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_in_single_batch,
|
||||
max_messages_size_in_single_batch,
|
||||
max_messages_weight_in_single_batch,
|
||||
);
|
||||
|
||||
messages_relay::message_lane_loop::run(
|
||||
messages_relay::message_lane_loop::Params {
|
||||
lane: lane_id,
|
||||
source_tick: Wococo::AVERAGE_BLOCK_INTERVAL,
|
||||
target_tick: Rococo::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_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,
|
||||
},
|
||||
},
|
||||
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,
|
||||
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(),
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -1,146 +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 structopt::clap::arg_enum;
|
||||
|
||||
arg_enum! {
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
/// Supported full bridges (headers + messages).
|
||||
pub enum FullBridge {
|
||||
MillauToRialto,
|
||||
RialtoToMillau,
|
||||
RococoToWococo,
|
||||
WococoToRococo,
|
||||
}
|
||||
}
|
||||
|
||||
impl FullBridge {
|
||||
/// Return instance index of the bridge pallet in source runtime.
|
||||
pub fn bridge_instance_index(&self) -> u8 {
|
||||
match self {
|
||||
Self::MillauToRialto => MILLAU_TO_RIALTO_INDEX,
|
||||
Self::RialtoToMillau => RIALTO_TO_MILLAU_INDEX,
|
||||
Self::RococoToWococo => ROCOCO_TO_WOCOCO_INDEX,
|
||||
Self::WococoToRococo => WOCOCO_TO_ROCOCO_INDEX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/// The macro allows executing bridge-specific code without going fully generic.
|
||||
///
|
||||
/// It matches on the [`FullBridge`] enum, sets bridge-specific types or imports and injects
|
||||
/// the `$generic` code at every variant.
|
||||
#[macro_export]
|
||||
macro_rules! select_full_bridge {
|
||||
($bridge: expr, $generic: tt) => {
|
||||
match $bridge {
|
||||
FullBridge::MillauToRialto => {
|
||||
type Source = relay_millau_client::Millau;
|
||||
#[allow(dead_code)]
|
||||
type Target = relay_rialto_client::Rialto;
|
||||
|
||||
// Derive-account
|
||||
#[allow(unused_imports)]
|
||||
use bp_rialto::derive_account_from_millau_id as derive_account;
|
||||
|
||||
// Relay-messages
|
||||
#[allow(unused_imports)]
|
||||
use crate::chains::millau_messages_to_rialto::run as relay_messages;
|
||||
|
||||
// Send-message / Estimate-fee
|
||||
#[allow(unused_imports)]
|
||||
use bp_rialto::TO_RIALTO_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_MESSAGE_FEE_METHOD;
|
||||
// Send-message
|
||||
#[allow(unused_imports)]
|
||||
use millau_runtime::millau_to_rialto_account_ownership_digest as account_ownership_digest;
|
||||
|
||||
$generic
|
||||
}
|
||||
FullBridge::RialtoToMillau => {
|
||||
type Source = relay_rialto_client::Rialto;
|
||||
#[allow(dead_code)]
|
||||
type Target = relay_millau_client::Millau;
|
||||
|
||||
// Derive-account
|
||||
#[allow(unused_imports)]
|
||||
use bp_millau::derive_account_from_rialto_id as derive_account;
|
||||
|
||||
// Relay-messages
|
||||
#[allow(unused_imports)]
|
||||
use crate::chains::rialto_messages_to_millau::run as relay_messages;
|
||||
|
||||
// Send-message / Estimate-fee
|
||||
#[allow(unused_imports)]
|
||||
use bp_millau::TO_MILLAU_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_MESSAGE_FEE_METHOD;
|
||||
|
||||
// Send-message
|
||||
#[allow(unused_imports)]
|
||||
use rialto_runtime::rialto_to_millau_account_ownership_digest as account_ownership_digest;
|
||||
|
||||
$generic
|
||||
}
|
||||
FullBridge::RococoToWococo => {
|
||||
type Source = relay_rococo_client::Rococo;
|
||||
#[allow(dead_code)]
|
||||
type Target = relay_wococo_client::Wococo;
|
||||
|
||||
// Derive-account
|
||||
#[allow(unused_imports)]
|
||||
use bp_wococo::derive_account_from_rococo_id as derive_account;
|
||||
|
||||
// Relay-messages
|
||||
#[allow(unused_imports)]
|
||||
use crate::chains::rococo_messages_to_wococo::run as relay_messages;
|
||||
|
||||
// Send-message / Estimate-fee
|
||||
#[allow(unused_imports)]
|
||||
use bp_wococo::TO_WOCOCO_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_MESSAGE_FEE_METHOD;
|
||||
// Send-message
|
||||
#[allow(unused_imports)]
|
||||
use relay_rococo_client::runtime::rococo_to_wococo_account_ownership_digest as account_ownership_digest;
|
||||
|
||||
$generic
|
||||
}
|
||||
FullBridge::WococoToRococo => {
|
||||
type Source = relay_wococo_client::Wococo;
|
||||
#[allow(dead_code)]
|
||||
type Target = relay_rococo_client::Rococo;
|
||||
|
||||
// Derive-account
|
||||
#[allow(unused_imports)]
|
||||
use bp_rococo::derive_account_from_wococo_id as derive_account;
|
||||
|
||||
// Relay-messages
|
||||
#[allow(unused_imports)]
|
||||
use crate::chains::wococo_messages_to_rococo::run as relay_messages;
|
||||
|
||||
// Send-message / Estimate-fee
|
||||
#[allow(unused_imports)]
|
||||
use bp_rococo::TO_ROCOCO_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_MESSAGE_FEE_METHOD;
|
||||
// Send-message
|
||||
#[allow(unused_imports)]
|
||||
use relay_wococo_client::runtime::wococo_to_rococo_account_ownership_digest as account_ownership_digest;
|
||||
|
||||
$generic
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,102 +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::cli::{bridge::FullBridge, AccountId};
|
||||
use crate::select_full_bridge;
|
||||
use relay_substrate_client::Chain;
|
||||
use structopt::StructOpt;
|
||||
|
||||
/// Given a source chain `AccountId`, derive the corresponding `AccountId` for the target chain.
|
||||
///
|
||||
/// The (derived) target chain `AccountId` is going to be used as dispatch origin of the call
|
||||
/// that has been sent over the bridge.
|
||||
/// This account can also be used to receive target-chain funds (or other form of ownership),
|
||||
/// since messages sent over the bridge will be able to spend these.
|
||||
#[derive(StructOpt)]
|
||||
pub struct DeriveAccount {
|
||||
/// A bridge instance to initalize.
|
||||
#[structopt(possible_values = &FullBridge::variants(), case_insensitive = true)]
|
||||
bridge: FullBridge,
|
||||
/// Source-chain address to derive Target-chain address from.
|
||||
account: AccountId,
|
||||
}
|
||||
|
||||
impl DeriveAccount {
|
||||
/// Parse CLI arguments and derive account.
|
||||
///
|
||||
/// Returns both the Source account in correct SS58 format and the derived account.
|
||||
fn derive_account(&self) -> (AccountId, AccountId) {
|
||||
select_full_bridge!(self.bridge, {
|
||||
let mut account = self.account.clone();
|
||||
account.enforce_chain::<Source>();
|
||||
let acc = bp_runtime::SourceAccount::Account(account.raw_id());
|
||||
let id = derive_account(acc);
|
||||
let derived_account = AccountId::from_raw::<Target>(id);
|
||||
(account, derived_account)
|
||||
})
|
||||
}
|
||||
|
||||
/// Run the command.
|
||||
pub async fn run(self) -> anyhow::Result<()> {
|
||||
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,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn derive_account_cli(bridge: &str, account: &str) -> (AccountId, AccountId) {
|
||||
DeriveAccount::from_iter(vec!["derive-account", bridge, account]).derive_account()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_derive_accounts_correctly() {
|
||||
// given
|
||||
let rialto = "5sauUXUfPjmwxSgmb3tZ5d6yx24eZX4wWJ2JtVUBaQqFbvEU";
|
||||
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);
|
||||
|
||||
// then
|
||||
assert_eq!(format!("{}", rialto_parsed), rialto);
|
||||
assert_eq!(format!("{}", millau_parsed), millau);
|
||||
assert_eq!(format!("{}", millau2_parsed), millau);
|
||||
|
||||
assert_eq!(
|
||||
format!("{}", rialto_derived),
|
||||
"74GNQjmkcfstRftSQPJgMREchqHM56EvAUXRc266cZ1NYVW5"
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", millau_derived),
|
||||
"5rERgaT1Z8nM3et2epA5i1VtEBfp5wkhwHtVE8HK7BRbjAH2"
|
||||
);
|
||||
assert_eq!(millau_derived, millau2_derived);
|
||||
}
|
||||
}
|
||||
@@ -1,278 +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::cli::bridge::FullBridge;
|
||||
use crate::cli::{AccountId, Balance, CliChain, ExplicitOrMaximal, HexBytes, HexLaneId};
|
||||
use crate::select_full_bridge;
|
||||
use frame_support::weights::DispatchInfo;
|
||||
use relay_substrate_client::Chain;
|
||||
use structopt::StructOpt;
|
||||
|
||||
/// 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)]
|
||||
bridge: FullBridge,
|
||||
#[structopt(flatten)]
|
||||
call: Call,
|
||||
}
|
||||
|
||||
/// All possible messages that may be delivered to generic Substrate chain.
|
||||
///
|
||||
/// Note this enum may be used in the context of both Source (as part of `encode-call`)
|
||||
/// and Target chain (as part of `encode-message/send-message`).
|
||||
#[derive(StructOpt, Debug, PartialEq, Eq)]
|
||||
pub enum Call {
|
||||
/// Raw bytes for the message
|
||||
Raw {
|
||||
/// Raw, SCALE-encoded message
|
||||
data: HexBytes,
|
||||
},
|
||||
/// Make an on-chain remark (comment).
|
||||
Remark {
|
||||
/// Explicit remark payload.
|
||||
#[structopt(long, conflicts_with("remark-size"))]
|
||||
remark_payload: Option<HexBytes>,
|
||||
/// Remark size. If not passed, small UTF8-encoded string is generated by relay as remark.
|
||||
#[structopt(long, conflicts_with("remark-payload"))]
|
||||
remark_size: Option<ExplicitOrMaximal<usize>>,
|
||||
},
|
||||
/// Transfer the specified `amount` of native tokens to a particular `recipient`.
|
||||
Transfer {
|
||||
/// Address of an account to receive the transfer.
|
||||
#[structopt(long)]
|
||||
recipient: AccountId,
|
||||
/// Amount of target tokens to send in target chain base currency units.
|
||||
#[structopt(long)]
|
||||
amount: Balance,
|
||||
},
|
||||
/// A call to the specific Bridge Messages pallet to queue message to be sent over a bridge.
|
||||
BridgeSendMessage {
|
||||
/// An index of the bridge instance which represents the expected target chain.
|
||||
#[structopt(skip = 255)]
|
||||
bridge_instance_index: u8,
|
||||
/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
|
||||
#[structopt(long, default_value = "00000000")]
|
||||
lane: HexLaneId,
|
||||
/// Raw SCALE-encoded Message Payload to submit to the messages pallet.
|
||||
///
|
||||
/// This can be obtained by encoding call for the target chain.
|
||||
#[structopt(long)]
|
||||
payload: HexBytes,
|
||||
/// Declared delivery and dispatch fee in base source-chain currency units.
|
||||
#[structopt(long)]
|
||||
fee: Balance,
|
||||
},
|
||||
}
|
||||
|
||||
pub trait CliEncodeCall: Chain {
|
||||
/// Maximal size (in bytes) of any extrinsic (from the runtime).
|
||||
fn max_extrinsic_size() -> u32;
|
||||
|
||||
/// Encode a CLI call.
|
||||
fn encode_call(call: &Call) -> anyhow::Result<Self::Call>;
|
||||
|
||||
/// Get dispatch info for the call.
|
||||
fn get_dispatch_info(call: &Self::Call) -> anyhow::Result<DispatchInfo>;
|
||||
}
|
||||
|
||||
impl EncodeCall {
|
||||
fn encode(&mut self) -> anyhow::Result<HexBytes> {
|
||||
select_full_bridge!(self.bridge, {
|
||||
preprocess_call::<Source, Target>(&mut self.call, self.bridge.bridge_instance_index());
|
||||
let call = Source::encode_call(&self.call)?;
|
||||
|
||||
let encoded = HexBytes::encode(&call);
|
||||
|
||||
log::info!(target: "bridge", "Generated {} call: {:#?}", Source::NAME, call);
|
||||
log::info!(target: "bridge", "Weight of {} call: {}", Source::NAME, Source::get_dispatch_info(&call)?.weight);
|
||||
log::info!(target: "bridge", "Encoded {} call: {:?}", Source::NAME, encoded);
|
||||
|
||||
Ok(encoded)
|
||||
})
|
||||
}
|
||||
|
||||
/// Run the command.
|
||||
pub async fn run(mut self) -> anyhow::Result<()> {
|
||||
println!("{:?}", self.encode()?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare the call to be passed to [`CliEncodeCall::encode_call`].
|
||||
///
|
||||
/// This function will fill in all optional and missing pieces and will make sure that
|
||||
/// values are converted to bridge-specific ones.
|
||||
///
|
||||
/// Most importantly, the method will fill-in [`bridge_instance_index`] parameter for
|
||||
/// target-chain specific calls.
|
||||
pub(crate) fn preprocess_call<Source: CliEncodeCall + CliChain, Target: CliEncodeCall>(
|
||||
call: &mut Call,
|
||||
bridge_instance: u8,
|
||||
) {
|
||||
match *call {
|
||||
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()),
|
||||
)));
|
||||
}
|
||||
}
|
||||
Call::Transfer { ref mut recipient, .. } => {
|
||||
recipient.enforce_chain::<Source>();
|
||||
}
|
||||
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> {
|
||||
match remark_size {
|
||||
Some(ExplicitOrMaximal::Explicit(remark_size)) => vec![0; *remark_size],
|
||||
Some(ExplicitOrMaximal::Maximal) => vec![0; maximal_allowed_size as _],
|
||||
None => format!(
|
||||
"Unix time: {}",
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::SystemTime::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs(),
|
||||
)
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compute_maximal_message_arguments_size(
|
||||
maximal_source_extrinsic_size: u32,
|
||||
maximal_target_extrinsic_size: u32,
|
||||
) -> u32 {
|
||||
// assume that both signed extensions and other arguments fit 1KB
|
||||
let service_tx_bytes_on_source_chain = 1024;
|
||||
let maximal_source_extrinsic_size = maximal_source_extrinsic_size - service_tx_bytes_on_source_chain;
|
||||
let maximal_call_size =
|
||||
bridge_runtime_common::messages::target::maximal_incoming_message_size(maximal_target_extrinsic_size);
|
||||
let maximal_call_size = if maximal_call_size > maximal_source_extrinsic_size {
|
||||
maximal_source_extrinsic_size
|
||||
} else {
|
||||
maximal_call_size
|
||||
};
|
||||
|
||||
// bytes in Call encoding that are used to encode everything except arguments
|
||||
let service_bytes = 1 + 1 + 4;
|
||||
maximal_call_size - service_bytes
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_encode_transfer_call() {
|
||||
// given
|
||||
let mut encode_call = EncodeCall::from_iter(vec![
|
||||
"encode-call",
|
||||
"RialtoToMillau",
|
||||
"transfer",
|
||||
"--amount",
|
||||
"12345",
|
||||
"--recipient",
|
||||
"5sauUXUfPjmwxSgmb3tZ5d6yx24eZX4wWJ2JtVUBaQqFbvEU",
|
||||
]);
|
||||
|
||||
// when
|
||||
let hex = encode_call.encode().unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
format!("{:?}", hex),
|
||||
"0x0c00d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de5c0"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_encode_remark_with_default_payload() {
|
||||
// given
|
||||
let mut encode_call = EncodeCall::from_iter(vec!["encode-call", "RialtoToMillau", "remark"]);
|
||||
|
||||
// when
|
||||
let hex = encode_call.encode().unwrap();
|
||||
|
||||
// then
|
||||
assert!(format!("{:?}", hex).starts_with("0x070154556e69782074696d653a"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_encode_remark_with_explicit_payload() {
|
||||
// given
|
||||
let mut encode_call = EncodeCall::from_iter(vec![
|
||||
"encode-call",
|
||||
"RialtoToMillau",
|
||||
"remark",
|
||||
"--remark-payload",
|
||||
"1234",
|
||||
]);
|
||||
|
||||
// when
|
||||
let hex = encode_call.encode().unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(format!("{:?}", hex), "0x0701081234");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_encode_remark_with_size() {
|
||||
// given
|
||||
let mut encode_call =
|
||||
EncodeCall::from_iter(vec!["encode-call", "RialtoToMillau", "remark", "--remark-size", "12"]);
|
||||
|
||||
// when
|
||||
let hex = encode_call.encode().unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(format!("{:?}", hex), "0x070130000000000000000000000000");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_disallow_both_payload_and_size() {
|
||||
// when
|
||||
let err = EncodeCall::from_iter_safe(vec![
|
||||
"encode-call",
|
||||
"RialtoToMillau",
|
||||
"remark",
|
||||
"--remark-payload",
|
||||
"1234",
|
||||
"--remark-size",
|
||||
"12",
|
||||
])
|
||||
.unwrap_err();
|
||||
|
||||
// then
|
||||
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()))
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::cli::{bridge::FullBridge, AccountId, CliChain, HexBytes};
|
||||
use crate::select_full_bridge;
|
||||
use structopt::StructOpt;
|
||||
|
||||
/// Generic message payload.
|
||||
#[derive(StructOpt, Debug, PartialEq, Eq)]
|
||||
pub enum MessagePayload {
|
||||
/// Raw, SCALE-encoded `MessagePayload`.
|
||||
Raw {
|
||||
/// Hex-encoded SCALE data.
|
||||
data: HexBytes,
|
||||
},
|
||||
/// Construct message to send over the bridge.
|
||||
Call {
|
||||
/// Message details.
|
||||
#[structopt(flatten)]
|
||||
call: crate::cli::encode_call::Call,
|
||||
/// SS58 encoded Source account that will send the payload.
|
||||
#[structopt(long)]
|
||||
sender: AccountId,
|
||||
},
|
||||
}
|
||||
|
||||
/// A `MessagePayload` to encode.
|
||||
#[derive(StructOpt)]
|
||||
pub struct EncodeMessage {
|
||||
/// A bridge instance to initalize.
|
||||
#[structopt(possible_values = &FullBridge::variants(), case_insensitive = true)]
|
||||
bridge: FullBridge,
|
||||
#[structopt(flatten)]
|
||||
payload: MessagePayload,
|
||||
}
|
||||
|
||||
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))?;
|
||||
Ok(HexBytes::encode(&payload))
|
||||
})
|
||||
}
|
||||
|
||||
/// Run the command.
|
||||
pub async fn run(self) -> anyhow::Result<()> {
|
||||
let payload = self.encode()?;
|
||||
println!("{:?}", payload);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use sp_core::crypto::Ss58Codec;
|
||||
|
||||
#[test]
|
||||
fn should_encode_raw_message() {
|
||||
// given
|
||||
let msg = "01000000e88514000000000002d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d003c040130000000000000000000000000";
|
||||
let encode_message = EncodeMessage::from_iter(vec!["encode-message", "MillauToRialto", "raw", msg]);
|
||||
|
||||
// when
|
||||
let hex = encode_message.encode().unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(format!("{:?}", hex), format!("0x{}", msg));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_encode_remark_with_size() {
|
||||
// given
|
||||
let sender = sp_keyring::AccountKeyring::Alice.to_account_id().to_ss58check();
|
||||
let encode_message = EncodeMessage::from_iter(vec![
|
||||
"encode-message",
|
||||
"RialtoToMillau",
|
||||
"call",
|
||||
"--sender",
|
||||
&sender,
|
||||
"remark",
|
||||
"--remark-size",
|
||||
"12",
|
||||
]);
|
||||
|
||||
// when
|
||||
let hex = encode_message.encode().unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(format!("{:?}", hex), "0x01000000b0d60f000000000002d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d003c040130000000000000000000000000");
|
||||
}
|
||||
}
|
||||
@@ -1,128 +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::cli::bridge::FullBridge;
|
||||
use crate::cli::{Balance, CliChain, HexBytes, HexLaneId, SourceConnectionParams};
|
||||
use crate::select_full_bridge;
|
||||
use codec::{Decode, Encode};
|
||||
use relay_substrate_client::Chain;
|
||||
use structopt::StructOpt;
|
||||
|
||||
/// 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)]
|
||||
bridge: FullBridge,
|
||||
#[structopt(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
/// Hex-encoded id of lane that will be delivering the message.
|
||||
#[structopt(long, default_value = "00000000")]
|
||||
lane: HexLaneId,
|
||||
/// Payload to send over the bridge.
|
||||
#[structopt(flatten)]
|
||||
payload: crate::cli::encode_message::MessagePayload,
|
||||
}
|
||||
|
||||
impl EstimateFee {
|
||||
/// Run the command.
|
||||
pub async fn run(self) -> anyhow::Result<()> {
|
||||
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 fee: <Source as Chain>::Balance =
|
||||
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);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn estimate_message_delivery_and_dispatch_fee<Fee: Decode, C: Chain, P: Encode>(
|
||||
client: &relay_substrate_client::Client<C>,
|
||||
estimate_fee_method: &str,
|
||||
lane: bp_messages::LaneId,
|
||||
payload: P,
|
||||
) -> anyhow::Result<Fee> {
|
||||
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())))?;
|
||||
Ok(fee)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cli::encode_call;
|
||||
use sp_core::crypto::Ss58Codec;
|
||||
|
||||
#[test]
|
||||
fn should_parse_cli_options() {
|
||||
// given
|
||||
let alice = sp_keyring::AccountKeyring::Alice.to_account_id().to_ss58check();
|
||||
|
||||
// when
|
||||
let res = EstimateFee::from_iter(vec![
|
||||
"estimate_fee",
|
||||
"RialtoToMillau",
|
||||
"--source-port",
|
||||
"1234",
|
||||
"call",
|
||||
"--sender",
|
||||
&alice,
|
||||
"remark",
|
||||
"--remark-payload",
|
||||
"1234",
|
||||
]);
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
res,
|
||||
EstimateFee {
|
||||
bridge: FullBridge::RialtoToMillau,
|
||||
lane: HexLaneId([0, 0, 0, 0]),
|
||||
source: SourceConnectionParams {
|
||||
source_host: "127.0.0.1".into(),
|
||||
source_port: 1234,
|
||||
source_secure: false,
|
||||
},
|
||||
payload: crate::cli::encode_message::MessagePayload::Call {
|
||||
sender: alice.parse().unwrap(),
|
||||
call: encode_call::Call::Remark {
|
||||
remark_payload: Some(HexBytes(vec![0x12, 0x34])),
|
||||
remark_size: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,166 +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::cli::{SourceConnectionParams, TargetConnectionParams, TargetSigningParams};
|
||||
use bp_header_chain::InitializationData;
|
||||
use bp_runtime::Chain as ChainBase;
|
||||
use codec::Encode;
|
||||
use relay_substrate_client::{Chain, TransactionSignScheme};
|
||||
use sp_core::{Bytes, Pair};
|
||||
use structopt::{clap::arg_enum, StructOpt};
|
||||
|
||||
/// Initialize bridge pallet.
|
||||
#[derive(StructOpt)]
|
||||
pub struct InitBridge {
|
||||
/// A bridge instance to initalize.
|
||||
#[structopt(possible_values = &InitBridgeName::variants(), case_insensitive = true)]
|
||||
bridge: InitBridgeName,
|
||||
#[structopt(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[structopt(flatten)]
|
||||
target: TargetConnectionParams,
|
||||
#[structopt(flatten)]
|
||||
target_sign: TargetSigningParams,
|
||||
}
|
||||
|
||||
// TODO [#851] Use kebab-case.
|
||||
arg_enum! {
|
||||
#[derive(Debug)]
|
||||
/// Bridge to initialize.
|
||||
pub enum InitBridgeName {
|
||||
MillauToRialto,
|
||||
RialtoToMillau,
|
||||
WestendToMillau,
|
||||
RococoToWococo,
|
||||
WococoToRococo,
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! select_bridge {
|
||||
($bridge: expr, $generic: tt) => {
|
||||
match $bridge {
|
||||
InitBridgeName::MillauToRialto => {
|
||||
type Source = relay_millau_client::Millau;
|
||||
type Target = relay_rialto_client::Rialto;
|
||||
|
||||
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(),
|
||||
))
|
||||
.into()
|
||||
}
|
||||
|
||||
$generic
|
||||
}
|
||||
InitBridgeName::RialtoToMillau => {
|
||||
type Source = relay_rialto_client::Rialto;
|
||||
type Target = relay_millau_client::Millau;
|
||||
|
||||
fn encode_init_bridge(
|
||||
init_data: InitializationData<<Source as ChainBase>::Header>,
|
||||
) -> <Target as Chain>::Call {
|
||||
let initialize_call = millau_runtime::BridgeGrandpaRialtoCall::<
|
||||
millau_runtime::Runtime,
|
||||
millau_runtime::RialtoGrandpaInstance,
|
||||
>::initialize(init_data);
|
||||
millau_runtime::SudoCall::sudo(Box::new(initialize_call.into())).into()
|
||||
}
|
||||
|
||||
$generic
|
||||
}
|
||||
InitBridgeName::WestendToMillau => {
|
||||
type Source = relay_westend_client::Westend;
|
||||
type Target = relay_millau_client::Millau;
|
||||
|
||||
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::<
|
||||
millau_runtime::Runtime,
|
||||
millau_runtime::WestendGrandpaInstance,
|
||||
>::initialize(init_data)
|
||||
.into()
|
||||
}
|
||||
|
||||
$generic
|
||||
}
|
||||
InitBridgeName::RococoToWococo => {
|
||||
type Source = relay_rococo_client::Rococo;
|
||||
type Target = relay_wococo_client::Wococo;
|
||||
|
||||
fn encode_init_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),
|
||||
)
|
||||
}
|
||||
|
||||
$generic
|
||||
}
|
||||
InitBridgeName::WococoToRococo => {
|
||||
type Source = relay_wococo_client::Wococo;
|
||||
type Target = relay_rococo_client::Rococo;
|
||||
|
||||
fn encode_init_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),
|
||||
)
|
||||
}
|
||||
|
||||
$generic
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl InitBridge {
|
||||
/// Run the command.
|
||||
pub async fn run(self) -> anyhow::Result<()> {
|
||||
select_bridge!(self.bridge, {
|
||||
let source_client = self.source.to_client::<Source>().await?;
|
||||
let target_client = self.target.to_client::<Target>().await?;
|
||||
let target_sign = self.target_sign.to_keypair::<Target>()?;
|
||||
|
||||
crate::headers_initialize::initialize(
|
||||
source_client,
|
||||
target_client.clone(),
|
||||
target_sign.public().into(),
|
||||
move |transaction_nonce, initialization_data| {
|
||||
Bytes(
|
||||
Target::sign_transaction(
|
||||
*target_client.genesis_hash(),
|
||||
&target_sign,
|
||||
transaction_nonce,
|
||||
encode_init_bridge(initialization_data),
|
||||
)
|
||||
.encode(),
|
||||
)
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,459 +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/>.
|
||||
|
||||
//! Deal with CLI args of substrate-to-substrate relay.
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use bp_messages::LaneId;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::weights::Weight;
|
||||
use sp_runtime::app_crypto::Ss58Codec;
|
||||
use structopt::{clap::arg_enum, StructOpt};
|
||||
|
||||
pub(crate) mod bridge;
|
||||
pub(crate) mod encode_call;
|
||||
pub(crate) mod encode_message;
|
||||
pub(crate) mod estimate_fee;
|
||||
pub(crate) mod send_message;
|
||||
|
||||
mod derive_account;
|
||||
mod init_bridge;
|
||||
mod relay_headers;
|
||||
mod relay_headers_and_messages;
|
||||
mod relay_messages;
|
||||
|
||||
/// Parse relay CLI args.
|
||||
pub fn parse_args() -> Command {
|
||||
Command::from_args()
|
||||
}
|
||||
|
||||
/// Substrate-to-Substrate bridge utilities.
|
||||
#[derive(StructOpt)]
|
||||
#[structopt(about = "Substrate-to-Substrate relay")]
|
||||
pub enum Command {
|
||||
/// Start headers relay between two chains.
|
||||
///
|
||||
/// The on-chain bridge component should have been already initialized with
|
||||
/// `init-bridge` sub-command.
|
||||
RelayHeaders(relay_headers::RelayHeaders),
|
||||
/// Start messages relay between two chains.
|
||||
///
|
||||
/// Ties up to `Messages` pallets on both chains and starts relaying messages.
|
||||
/// Requires the header relay to be already running.
|
||||
RelayMessages(relay_messages::RelayMessages),
|
||||
/// Start headers and messages relay between two Substrate chains.
|
||||
///
|
||||
/// This high-level relay internally starts four low-level relays: two `RelayHeaders`
|
||||
/// and two `RelayMessages` relays. Headers are only relayed when they are required by
|
||||
/// the message relays - i.e. when there are messages or confirmations that needs to be
|
||||
/// relayed between chains.
|
||||
RelayHeadersAndMessages(relay_headers_and_messages::RelayHeadersAndMessages),
|
||||
/// Initialize on-chain bridge pallet with current header data.
|
||||
///
|
||||
/// Sends initialization transaction to bootstrap the bridge with current finalized block data.
|
||||
InitBridge(init_bridge::InitBridge),
|
||||
/// Send custom message over the bridge.
|
||||
///
|
||||
/// Allows interacting with the bridge by sending messages over `Messages` component.
|
||||
/// The message is being sent to the source chain, delivered to the target chain and dispatched
|
||||
/// there.
|
||||
SendMessage(send_message::SendMessage),
|
||||
/// Generate SCALE-encoded `Call` for choosen network.
|
||||
///
|
||||
/// The call can be used either as message payload or can be wrapped into a transaction
|
||||
/// and executed on the chain directly.
|
||||
EncodeCall(encode_call::EncodeCall),
|
||||
/// Generate SCALE-encoded `MessagePayload` object that can be sent over selected bridge.
|
||||
///
|
||||
/// The `MessagePayload` can be then fed to `Messages::send_message` function and sent over
|
||||
/// the bridge.
|
||||
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.
|
||||
DeriveAccount(derive_account::DeriveAccount),
|
||||
}
|
||||
|
||||
impl Command {
|
||||
// Initialize logger depending on the command.
|
||||
fn init_logger(&self) {
|
||||
use relay_utils::initialize::{initialize_logger, initialize_relay};
|
||||
|
||||
match self {
|
||||
Self::RelayHeaders(_) | Self::RelayMessages(_) | Self::RelayHeadersAndMessages(_) | Self::InitBridge(_) => {
|
||||
initialize_relay();
|
||||
}
|
||||
_ => {
|
||||
initialize_logger(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the command.
|
||||
pub async fn run(self) -> anyhow::Result<()> {
|
||||
self.init_logger();
|
||||
match self {
|
||||
Self::RelayHeaders(arg) => arg.run().await?,
|
||||
Self::RelayMessages(arg) => arg.run().await?,
|
||||
Self::RelayHeadersAndMessages(arg) => arg.run().await?,
|
||||
Self::InitBridge(arg) => arg.run().await?,
|
||||
Self::SendMessage(arg) => arg.run().await?,
|
||||
Self::EncodeCall(arg) => arg.run().await?,
|
||||
Self::EncodeMessage(arg) => arg.run().await?,
|
||||
Self::EstimateFee(arg) => arg.run().await?,
|
||||
Self::DeriveAccount(arg) => arg.run().await?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
arg_enum! {
|
||||
#[derive(Debug)]
|
||||
/// The origin to use when dispatching the message on the target chain.
|
||||
///
|
||||
/// - `Target` uses account existing on the target chain (requires target private key).
|
||||
/// - `Origin` uses account derived from the source-chain account.
|
||||
pub enum Origins {
|
||||
Target,
|
||||
Source,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic balance type.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Balance(pub u128);
|
||||
|
||||
impl std::fmt::Display for Balance {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
use num_format::{Locale, ToFormattedString};
|
||||
write!(fmt, "{}", self.0.to_formatted_string(&Locale::en))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Balance {
|
||||
type Err = <u128 as std::str::FromStr>::Err;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(s.parse()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Balance {
|
||||
/// Cast balance to `u64` type, panicking if it's too large.
|
||||
pub fn cast(&self) -> u64 {
|
||||
self.0.try_into().expect("Balance is too high for this chain.")
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic account id with custom parser.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountId {
|
||||
account: sp_runtime::AccountId32,
|
||||
ss58_format: sp_core::crypto::Ss58AddressFormat,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AccountId {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(fmt, "{}", self.account.to_ss58check_with_version(self.ss58_format))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for AccountId {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (account, ss58_format) = sp_runtime::AccountId32::from_ss58check_with_version(s)
|
||||
.map_err(|err| format!("Unable to decode SS58 address: {:?}", err))?;
|
||||
Ok(Self { account, ss58_format })
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enforces formatting account to be for given [`CliChain`] type.
|
||||
///
|
||||
/// This will change the `ss58format` of the account to match the requested one.
|
||||
/// Note that a warning will be produced in case the current format does not match
|
||||
/// the requested one, but the conversion always succeeds.
|
||||
pub fn enforce_chain<T: CliChain>(&mut self) {
|
||||
let original = self.clone();
|
||||
self.ss58_format = T::ss58_format().try_into().expect(SS58_FORMAT_PROOF);
|
||||
log::debug!("{} SS58 format: {} (RAW: {})", self, self.ss58_format, self.account);
|
||||
if original.ss58_format != self.ss58_format {
|
||||
log::warn!(
|
||||
target: "bridge",
|
||||
"Address {} does not seem to match {}'s SS58 format (got: {}, expected: {}).\nConverted to: {}",
|
||||
original,
|
||||
T::NAME,
|
||||
original.ss58_format,
|
||||
self.ss58_format,
|
||||
self,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the raw (no SS58-prefixed) account id.
|
||||
pub fn raw_id(&self) -> sp_runtime::AccountId32 {
|
||||
self.account.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Bridge-supported network definition.
|
||||
///
|
||||
/// Used to abstract away CLI commands.
|
||||
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.
|
||||
///
|
||||
/// In case of chains supporting multiple cryptos, pick one used by the CLI.
|
||||
type KeyPair: sp_core::crypto::Pair;
|
||||
|
||||
/// Bridge Message Payload type.
|
||||
///
|
||||
/// TODO [#854] This should be removed in favour of target-specifc types.
|
||||
type MessagePayload;
|
||||
|
||||
/// Numeric value of SS58 format.
|
||||
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>;
|
||||
|
||||
/// Maximal extrinsic weight (from the runtime).
|
||||
fn max_extrinsic_weight() -> Weight;
|
||||
}
|
||||
|
||||
/// Lane id.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct HexLaneId(pub LaneId);
|
||||
|
||||
impl From<HexLaneId> for LaneId {
|
||||
fn from(lane_id: HexLaneId) -> LaneId {
|
||||
lane_id.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for HexLaneId {
|
||||
type Err = hex::FromHexError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut lane_id = LaneId::default();
|
||||
hex::decode_to_slice(s, &mut lane_id)?;
|
||||
Ok(HexLaneId(lane_id))
|
||||
}
|
||||
}
|
||||
|
||||
/// Nicer formatting for raw bytes vectors.
|
||||
#[derive(Default, Encode, Decode, PartialEq, Eq)]
|
||||
pub struct HexBytes(pub Vec<u8>);
|
||||
|
||||
impl std::str::FromStr for HexBytes {
|
||||
type Err = hex::FromHexError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(hex::decode(s)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for HexBytes {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(fmt, "0x{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for HexBytes {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(fmt, "{}", hex::encode(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl HexBytes {
|
||||
/// Encode given object and wrap into nicely formatted bytes.
|
||||
pub fn encode<T: Encode>(t: &T) -> Self {
|
||||
Self(t.encode())
|
||||
}
|
||||
}
|
||||
|
||||
/// Prometheus metrics params.
|
||||
#[derive(StructOpt)]
|
||||
pub struct PrometheusParams {
|
||||
/// Do not expose a Prometheus metric endpoint.
|
||||
#[structopt(long)]
|
||||
pub no_prometheus: bool,
|
||||
/// Expose Prometheus endpoint at given interface.
|
||||
#[structopt(long, default_value = "127.0.0.1")]
|
||||
pub prometheus_host: String,
|
||||
/// Expose Prometheus endpoint at given port.
|
||||
#[structopt(long, default_value = "9616")]
|
||||
pub prometheus_port: u16,
|
||||
}
|
||||
|
||||
impl From<PrometheusParams> for relay_utils::metrics::MetricsParams {
|
||||
fn from(cli_params: PrometheusParams) -> relay_utils::metrics::MetricsParams {
|
||||
if !cli_params.no_prometheus {
|
||||
Some(relay_utils::metrics::MetricsAddress {
|
||||
host: cli_params.prometheus_host,
|
||||
port: cli_params.prometheus_port,
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
None.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Either explicit or maximal allowed value.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ExplicitOrMaximal<V> {
|
||||
/// User has explicitly specified argument value.
|
||||
Explicit(V),
|
||||
/// Maximal allowed value for this argument.
|
||||
Maximal,
|
||||
}
|
||||
|
||||
impl<V: std::str::FromStr> std::str::FromStr for ExplicitOrMaximal<V>
|
||||
where
|
||||
V::Err: std::fmt::Debug,
|
||||
{
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.to_lowercase() == "max" {
|
||||
return Ok(ExplicitOrMaximal::Maximal);
|
||||
}
|
||||
|
||||
V::from_str(s)
|
||||
.map(ExplicitOrMaximal::Explicit)
|
||||
.map_err(|e| format!("Failed to parse '{:?}'. Expected 'max' or explicit value", e))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create chain-specific set of configuration objects: connection parameters,
|
||||
/// signing parameters and bridge initialisation parameters.
|
||||
#[macro_export]
|
||||
macro_rules! declare_chain_options {
|
||||
($chain:ident, $chain_prefix:ident) => {
|
||||
paste::item! {
|
||||
#[doc = $chain " connection params."]
|
||||
#[derive(StructOpt, Debug, PartialEq, Eq)]
|
||||
pub struct [<$chain ConnectionParams>] {
|
||||
#[doc = "Connect to " $chain " node at given host."]
|
||||
#[structopt(long, default_value = "127.0.0.1")]
|
||||
pub [<$chain_prefix _host>]: String,
|
||||
#[doc = "Connect to " $chain " node websocket server at given port."]
|
||||
#[structopt(long)]
|
||||
pub [<$chain_prefix _port>]: u16,
|
||||
#[doc = "Use secure websocket connection."]
|
||||
#[structopt(long)]
|
||||
pub [<$chain_prefix _secure>]: bool,
|
||||
}
|
||||
|
||||
#[doc = $chain " signing params."]
|
||||
#[derive(StructOpt, Debug, PartialEq, Eq)]
|
||||
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,
|
||||
#[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>,
|
||||
}
|
||||
|
||||
impl [<$chain SigningParams>] {
|
||||
/// Parse signing params into chain-specific KeyPair.
|
||||
pub fn to_keypair<Chain: CliChain>(&self) -> anyhow::Result<Chain::KeyPair> {
|
||||
use sp_core::crypto::Pair;
|
||||
|
||||
Chain::KeyPair::from_string(
|
||||
&self.[<$chain_prefix _signer>],
|
||||
self.[<$chain_prefix _signer_password>].as_deref()
|
||||
).map_err(|e| anyhow::format_err!("{:?}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl [<$chain ConnectionParams>] {
|
||||
/// Convert connection params into Substrate client.
|
||||
pub async fn to_client<Chain: CliChain>(
|
||||
&self,
|
||||
) -> anyhow::Result<relay_substrate_client::Client<Chain>> {
|
||||
Ok(relay_substrate_client::Client::new(relay_substrate_client::ConnectionParams {
|
||||
host: self.[<$chain_prefix _host>].clone(),
|
||||
port: self.[<$chain_prefix _port>],
|
||||
secure: self.[<$chain_prefix _secure>],
|
||||
})
|
||||
.await
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
declare_chain_options!(Source, source);
|
||||
declare_chain_options!(Target, target);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_format_addresses_with_ss58_format() {
|
||||
// given
|
||||
let rialto1 = "5sauUXUfPjmwxSgmb3tZ5d6yx24eZX4wWJ2JtVUBaQqFbvEU";
|
||||
let rialto2 = "5rERgaT1Z8nM3et2epA5i1VtEBfp5wkhwHtVE8HK7BRbjAH2";
|
||||
let millau1 = "752paRyW1EGfq9YLTSSqcSJ5hqnBDidBmaftGhBo8fy6ypW9";
|
||||
let millau2 = "74GNQjmkcfstRftSQPJgMREchqHM56EvAUXRc266cZ1NYVW5";
|
||||
|
||||
let expected = vec![rialto1, rialto2, millau1, millau2];
|
||||
|
||||
// when
|
||||
let parsed = expected
|
||||
.iter()
|
||||
.map(|s| AccountId::from_str(s).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let actual = parsed.iter().map(|a| format!("{}", a)).collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(actual, expected)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_bytes_display_matches_from_str_for_clap() {
|
||||
// given
|
||||
let hex = HexBytes(vec![1, 2, 3, 4]);
|
||||
let display = format!("{}", hex);
|
||||
|
||||
// when
|
||||
let hex2: HexBytes = display.parse().unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(hex.0, hex2.0);
|
||||
}
|
||||
}
|
||||
@@ -1,116 +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::cli::{PrometheusParams, SourceConnectionParams, TargetConnectionParams, TargetSigningParams};
|
||||
use crate::finality_pipeline::SubstrateFinalitySyncPipeline;
|
||||
use structopt::{clap::arg_enum, StructOpt};
|
||||
|
||||
/// Start headers relayer process.
|
||||
#[derive(StructOpt)]
|
||||
pub struct RelayHeaders {
|
||||
/// A bridge instance to relay headers for.
|
||||
#[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.
|
||||
#[structopt(long)]
|
||||
only_mandatory_headers: bool,
|
||||
#[structopt(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[structopt(flatten)]
|
||||
target: TargetConnectionParams,
|
||||
#[structopt(flatten)]
|
||||
target_sign: TargetSigningParams,
|
||||
#[structopt(flatten)]
|
||||
prometheus_params: PrometheusParams,
|
||||
}
|
||||
|
||||
// TODO [#851] Use kebab-case.
|
||||
arg_enum! {
|
||||
#[derive(Debug)]
|
||||
/// Headers relay bridge.
|
||||
pub enum RelayHeadersBridge {
|
||||
MillauToRialto,
|
||||
RialtoToMillau,
|
||||
WestendToMillau,
|
||||
RococoToWococo,
|
||||
WococoToRococo,
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! select_bridge {
|
||||
($bridge: expr, $generic: tt) => {
|
||||
match $bridge {
|
||||
RelayHeadersBridge::MillauToRialto => {
|
||||
type Source = relay_millau_client::Millau;
|
||||
type Target = relay_rialto_client::Rialto;
|
||||
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
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl RelayHeaders {
|
||||
/// Run the command.
|
||||
pub async fn run(self) -> anyhow::Result<()> {
|
||||
select_bridge!(self.bridge, {
|
||||
let source_client = self.source.to_client::<Source>().await?;
|
||||
let target_client = self.target.to_client::<Target>().await?;
|
||||
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(
|
||||
finality,
|
||||
source_client,
|
||||
target_client,
|
||||
self.only_mandatory_headers,
|
||||
metrics_params,
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,215 +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/>.
|
||||
|
||||
//! Complex headers+messages relays support.
|
||||
//!
|
||||
//! To add new complex relay between `ChainA` and `ChainB`, you must:
|
||||
//!
|
||||
//! 1) ensure that there's a `declare_chain_options!(...)` for both chains;
|
||||
//! 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;
|
||||
|
||||
/// Start headers+messages relayer process.
|
||||
#[derive(StructOpt)]
|
||||
pub enum RelayHeadersAndMessages {
|
||||
MillauRialto(MillauRialtoHeadersAndMessages),
|
||||
RococoWococo(RococoWococoHeadersAndMessages),
|
||||
}
|
||||
|
||||
/// Parameters that have the same names across all bridges.
|
||||
#[derive(StructOpt)]
|
||||
pub struct HeadersAndMessagesSharedParams {
|
||||
/// Hex-encoded lane identifiers that should be served by the complex relay.
|
||||
#[structopt(long, default_value = "00000000")]
|
||||
lane: Vec<HexLaneId>,
|
||||
#[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?).
|
||||
macro_rules! declare_bridge_options {
|
||||
($chain1:ident, $chain2:ident) => {
|
||||
paste::item! {
|
||||
#[doc = $chain1 " and " $chain2 " headers+messages relay params."]
|
||||
#[derive(StructOpt)]
|
||||
pub struct [<$chain1 $chain2 HeadersAndMessages>] {
|
||||
#[structopt(flatten)]
|
||||
shared: HeadersAndMessagesSharedParams,
|
||||
#[structopt(flatten)]
|
||||
left: [<$chain1 ConnectionParams>],
|
||||
#[structopt(flatten)]
|
||||
left_sign: [<$chain1 SigningParams>],
|
||||
#[structopt(flatten)]
|
||||
right: [<$chain2 ConnectionParams>],
|
||||
#[structopt(flatten)]
|
||||
right_sign: [<$chain2 SigningParams>],
|
||||
}
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
impl From<RelayHeadersAndMessages> for [<$chain1 $chain2 HeadersAndMessages>] {
|
||||
fn from(relay_params: RelayHeadersAndMessages) -> [<$chain1 $chain2 HeadersAndMessages>] {
|
||||
match relay_params {
|
||||
RelayHeadersAndMessages::[<$chain1 $chain2>](params) => params,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! select_bridge {
|
||||
($bridge: expr, $generic: tt) => {
|
||||
match $bridge {
|
||||
RelayHeadersAndMessages::MillauRialto(_) => {
|
||||
type Params = MillauRialtoHeadersAndMessages;
|
||||
|
||||
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 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;
|
||||
|
||||
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;
|
||||
|
||||
$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 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;
|
||||
|
||||
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;
|
||||
|
||||
$generic
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// All supported chains.
|
||||
declare_chain_options!(Millau, millau);
|
||||
declare_chain_options!(Rialto, rialto);
|
||||
declare_chain_options!(Rococo, rococo);
|
||||
declare_chain_options!(Wococo, wococo);
|
||||
// All supported bridges.
|
||||
declare_bridge_options!(Millau, Rialto);
|
||||
declare_bridge_options!(Rococo, Wococo);
|
||||
|
||||
impl RelayHeadersAndMessages {
|
||||
/// Run the command.
|
||||
pub async fn run(self) -> anyhow::Result<()> {
|
||||
select_bridge!(self, {
|
||||
let params: Params = self.into();
|
||||
|
||||
let left_client = params.left.to_client::<Left>().await?;
|
||||
let left_sign = params.left_sign.to_keypair::<Left>()?;
|
||||
let right_client = params.right.to_client::<Right>().await?;
|
||||
let right_sign = params.right_sign.to_keypair::<Right>()?;
|
||||
|
||||
let lanes = params.shared.lane;
|
||||
|
||||
let metrics_params: MetricsParams = params.shared.prometheus_params.into();
|
||||
let metrics_params = relay_utils::relay_metrics(None, metrics_params).into_params();
|
||||
|
||||
let left_to_right_on_demand_headers = OnDemandHeadersRelay::new(
|
||||
left_client.clone(),
|
||||
right_client.clone(),
|
||||
LeftToRightFinality::new(right_client.clone(), right_sign.clone()),
|
||||
MAX_MISSING_LEFT_HEADERS_AT_RIGHT,
|
||||
);
|
||||
let right_to_left_on_demand_headers = OnDemandHeadersRelay::new(
|
||||
right_client.clone(),
|
||||
left_client.clone(),
|
||||
RightToLeftFinality::new(left_client.clone(), left_sign.clone()),
|
||||
MAX_MISSING_RIGHT_HEADERS_AT_LEFT,
|
||||
);
|
||||
|
||||
// Need 2x capacity since we consider both directions for each lane
|
||||
let mut message_relays = Vec::with_capacity(lanes.len() * 2);
|
||||
for lane in lanes {
|
||||
let lane = lane.into();
|
||||
let left_to_right_messages = left_to_right_messages(MessagesRelayParams {
|
||||
source_client: left_client.clone(),
|
||||
source_sign: left_sign.clone(),
|
||||
target_client: right_client.clone(),
|
||||
target_sign: right_sign.clone(),
|
||||
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),
|
||||
),
|
||||
})
|
||||
.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(),
|
||||
target_client: left_client.clone(),
|
||||
target_sign: left_sign.clone(),
|
||||
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),
|
||||
),
|
||||
})
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
.boxed();
|
||||
|
||||
message_relays.push(left_to_right_messages);
|
||||
message_relays.push(right_to_left_messages);
|
||||
}
|
||||
|
||||
relay_utils::relay_metrics(None, metrics_params)
|
||||
.expose()
|
||||
.await
|
||||
.map_err(|e| anyhow::format_err!("{}", e))?;
|
||||
|
||||
futures::future::select_all(message_relays).await.0
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,71 +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::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;
|
||||
|
||||
/// Start messages relayer process.
|
||||
#[derive(StructOpt)]
|
||||
pub struct RelayMessages {
|
||||
/// A bridge instance to relay messages for.
|
||||
#[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(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[structopt(flatten)]
|
||||
source_sign: SourceSigningParams,
|
||||
#[structopt(flatten)]
|
||||
target: TargetConnectionParams,
|
||||
#[structopt(flatten)]
|
||||
target_sign: TargetSigningParams,
|
||||
#[structopt(flatten)]
|
||||
prometheus_params: PrometheusParams,
|
||||
}
|
||||
|
||||
impl RelayMessages {
|
||||
/// Run the command.
|
||||
pub async fn run(self) -> anyhow::Result<()> {
|
||||
select_full_bridge!(self.bridge, {
|
||||
let source_client = self.source.to_client::<Source>().await?;
|
||||
let source_sign = self.source_sign.to_keypair::<Source>()?;
|
||||
let target_client = self.target.to_client::<Target>().await?;
|
||||
let target_sign = self.target_sign.to_keypair::<Target>()?;
|
||||
|
||||
relay_messages(MessagesRelayParams {
|
||||
source_client,
|
||||
source_sign,
|
||||
target_client,
|
||||
target_sign,
|
||||
source_to_target_headers_relay: None,
|
||||
target_to_source_headers_relay: None,
|
||||
lane_id: self.lane.into(),
|
||||
metrics_params: self.prometheus_params.into(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,352 +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::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,
|
||||
};
|
||||
use bp_message_dispatch::{CallOrigin, MessagePayload};
|
||||
use bp_runtime::messages::DispatchFeePayment;
|
||||
use codec::Encode;
|
||||
use frame_support::weights::Weight;
|
||||
use relay_substrate_client::{Chain, TransactionSignScheme};
|
||||
use sp_core::{Bytes, Pair};
|
||||
use sp_runtime::{traits::IdentifyAccount, AccountId32, MultiSignature, MultiSigner};
|
||||
use std::fmt::Debug;
|
||||
use structopt::StructOpt;
|
||||
|
||||
/// Send bridge message.
|
||||
#[derive(StructOpt)]
|
||||
pub struct SendMessage {
|
||||
/// A bridge instance to encode call for.
|
||||
#[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>,
|
||||
/// Hex-encoded lane id. Defaults to `00000000`.
|
||||
#[structopt(long, default_value = "00000000")]
|
||||
lane: HexLaneId,
|
||||
/// 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.
|
||||
#[structopt(long)]
|
||||
fee: Option<Balance>,
|
||||
/// Message type.
|
||||
#[structopt(subcommand)]
|
||||
message: crate::cli::encode_call::Call,
|
||||
/// The origin to use when dispatching the message on the target chain. Defaults to
|
||||
/// `SourceAccount`.
|
||||
#[structopt(long, possible_values = &Origins::variants(), default_value = "Source")]
|
||||
origin: Origins,
|
||||
}
|
||||
|
||||
impl SendMessage {
|
||||
pub fn encode_payload(
|
||||
&mut self,
|
||||
) -> anyhow::Result<MessagePayload<AccountId32, MultiSigner, MultiSignature, Vec<u8>>> {
|
||||
crate::select_full_bridge!(self.bridge, {
|
||||
let SendMessage {
|
||||
source_sign,
|
||||
target_signer,
|
||||
target_signer_password,
|
||||
ref mut message,
|
||||
dispatch_weight,
|
||||
origin,
|
||||
bridge,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let source_sign = source_sign.to_keypair::<Source>()?;
|
||||
|
||||
encode_call::preprocess_call::<Source, Target>(message, bridge.bridge_instance_index());
|
||||
let target_call = Target::encode_call(message)?;
|
||||
|
||||
let payload = {
|
||||
let target_call_weight = prepare_call_dispatch_weight(
|
||||
dispatch_weight,
|
||||
ExplicitOrMaximal::Explicit(Target::get_dispatch_info(&target_call)?.weight),
|
||||
compute_maximal_message_dispatch_weight(Target::max_extrinsic_weight()),
|
||||
);
|
||||
let source_sender_public: MultiSigner = source_sign.public().into();
|
||||
let source_account_id = source_sender_public.into_account();
|
||||
|
||||
message_payload(
|
||||
Target::RUNTIME_VERSION.spec_version,
|
||||
target_call_weight,
|
||||
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,
|
||||
source_account_id.clone(),
|
||||
Target::RUNTIME_VERSION.spec_version,
|
||||
);
|
||||
let target_origin_public = target_sign.public();
|
||||
let digest_signature = target_sign.sign(&digest);
|
||||
CallOrigin::TargetAccount(
|
||||
source_account_id,
|
||||
target_origin_public.into(),
|
||||
digest_signature.into(),
|
||||
)
|
||||
}
|
||||
},
|
||||
&target_call,
|
||||
)
|
||||
};
|
||||
Ok(payload)
|
||||
})
|
||||
}
|
||||
|
||||
/// Run the command.
|
||||
pub async fn run(mut self) -> anyhow::Result<()> {
|
||||
crate::select_full_bridge!(self.bridge, {
|
||||
let payload = self.encode_payload()?;
|
||||
|
||||
let source_client = self.source.to_client::<Source>().await?;
|
||||
let source_sign = self.source_sign.to_keypair::<Source>()?;
|
||||
|
||||
let lane = self.lane.clone().into();
|
||||
let fee = match self.fee {
|
||||
Some(fee) => fee,
|
||||
None => Balance(
|
||||
estimate_message_delivery_and_dispatch_fee::<<Source as Chain>::Balance, _, _>(
|
||||
&source_client,
|
||||
ESTIMATE_MESSAGE_FEE_METHOD,
|
||||
lane,
|
||||
payload.clone(),
|
||||
)
|
||||
.await? as _,
|
||||
),
|
||||
};
|
||||
let dispatch_weight = payload.weight;
|
||||
let send_message_call = Source::encode_call(&encode_call::Call::BridgeSendMessage {
|
||||
bridge_instance_index: self.bridge.bridge_instance_index(),
|
||||
lane: self.lane,
|
||||
payload: HexBytes::encode(&payload),
|
||||
fee,
|
||||
})?;
|
||||
|
||||
source_client
|
||||
.submit_signed_extrinsic(source_sign.public().into(), |transaction_nonce| {
|
||||
let signed_source_call = Source::sign_transaction(
|
||||
*source_client.genesis_hash(),
|
||||
&source_sign,
|
||||
transaction_nonce,
|
||||
send_message_call,
|
||||
)
|
||||
.encode();
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Sending message to {}. Size: {}. Dispatch weight: {}. Fee: {}",
|
||||
Target::NAME,
|
||||
signed_source_call.len(),
|
||||
dispatch_weight,
|
||||
fee,
|
||||
);
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Signed {} Call: {:?}",
|
||||
Source::NAME,
|
||||
HexBytes::encode(&signed_source_call)
|
||||
);
|
||||
|
||||
Bytes(signed_source_call)
|
||||
})
|
||||
.await?;
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_call_dispatch_weight(
|
||||
user_specified_dispatch_weight: &Option<ExplicitOrMaximal<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)
|
||||
{
|
||||
ExplicitOrMaximal::Explicit(weight) => weight,
|
||||
ExplicitOrMaximal::Maximal => maximal_allowed_weight,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn message_payload<SAccountId, TPublic, TSignature>(
|
||||
spec_version: u32,
|
||||
weight: Weight,
|
||||
origin: CallOrigin<SAccountId, TPublic, TSignature>,
|
||||
call: &impl Encode,
|
||||
) -> MessagePayload<SAccountId, TPublic, TSignature, Vec<u8>>
|
||||
where
|
||||
SAccountId: Encode + Debug,
|
||||
TPublic: Encode + Debug,
|
||||
TSignature: Encode + Debug,
|
||||
{
|
||||
// Display nicely formatted call.
|
||||
let payload = MessagePayload {
|
||||
spec_version,
|
||||
weight,
|
||||
origin,
|
||||
dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
|
||||
call: HexBytes::encode(call),
|
||||
};
|
||||
|
||||
log::info!(target: "bridge", "Created Message Payload: {:#?}", payload);
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use hex_literal::hex;
|
||||
|
||||
#[test]
|
||||
fn send_remark_rialto_to_millau() {
|
||||
// given
|
||||
let mut send_message = SendMessage::from_iter(vec![
|
||||
"send-message",
|
||||
"RialtoToMillau",
|
||||
"--source-port",
|
||||
"1234",
|
||||
"--source-signer",
|
||||
"//Alice",
|
||||
"remark",
|
||||
"--remark-payload",
|
||||
"1234",
|
||||
]);
|
||||
|
||||
// when
|
||||
let payload = send_message.encode_payload().unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
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(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_remark_millau_to_rialto() {
|
||||
// given
|
||||
let mut send_message = SendMessage::from_iter(vec![
|
||||
"send-message",
|
||||
"MillauToRialto",
|
||||
"--source-port",
|
||||
"1234",
|
||||
"--source-signer",
|
||||
"//Alice",
|
||||
"--origin",
|
||||
"Target",
|
||||
"--target-signer",
|
||||
"//Bob",
|
||||
"remark",
|
||||
"--remark-payload",
|
||||
"1234",
|
||||
]);
|
||||
|
||||
// when
|
||||
let payload = send_message.encode_payload().unwrap();
|
||||
|
||||
// then
|
||||
// Since signatures are randomized we extract it from here and only check the rest.
|
||||
let signature = match payload.origin {
|
||||
CallOrigin::TargetAccount(_, _, ref sig) => sig.clone(),
|
||||
_ => panic!("Unexpected `CallOrigin`: {:?}", payload),
|
||||
};
|
||||
assert_eq!(
|
||||
payload,
|
||||
MessagePayload {
|
||||
spec_version: relay_millau_client::Millau::RUNTIME_VERSION.spec_version,
|
||||
weight: 1038000,
|
||||
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(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_signer_must_exist_if_origin_is_target() {
|
||||
// given
|
||||
let send_message = SendMessage::from_iter_safe(vec![
|
||||
"send-message",
|
||||
"MillauToRialto",
|
||||
"--source-port",
|
||||
"1234",
|
||||
"--source-signer",
|
||||
"//Alice",
|
||||
"--origin",
|
||||
"Target",
|
||||
"remark",
|
||||
"--remark-payload",
|
||||
"1234",
|
||||
]);
|
||||
|
||||
assert!(send_message.is_err());
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -1,38 +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 relay entrypoint.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
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();
|
||||
let run = command.run();
|
||||
let result = async_std::task::block_on(run);
|
||||
if let Err(error) = result {
|
||||
log::error!(target: "bridge", "Failed to start relay: {}", error);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
/// Numebr 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 manadatory 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user