Update bridges subtree (#5165)

* Squashed 'bridges/' changes from 1602249f0a..f220d2fcca

f220d2fcca Polkadot staging update (#1356)
02fd3d497c fix parse_transaction on Rialto+Millau (#1360)
bc191fd9a2 update parity-scale-codec to 3.1.2 (#1359)
a37226e79c update chain versions (#1358)
ff5d539fcb Update Substrate/Polkadot/Cumulus references (#1353)
1581f60cd5 Support dedicated lanes for pallets (#962)
0a7ccf5c57 ignore more "increase" alerts that are sometimes signalling NoData at startup (#1351)
31165127cc added no_stack_overflow_when_decoding_nested_call_during_dispatch test (#1349)
7000619eb8 replace From<>InboundLaneApi with direct storage reads (#1348)
515df10ccc added alerts for relay balances (#1347)
b56f6a87de Mortal conversion rate updater transactions (#1257)
20f2f331ec edition = "2021" (#1346)
99147d4f75 update regex to 1.5.5 (#1345)
686191f379 use DecodeLimit when decoding incoming calls (#1344)
a70c276006 get rid of '[No Data] Messages from Millau to Rialto are not being delivered' warnings (#1342)
01f29b8ac1 fix conversion rate metric in dashboards (#1341)
51c3bf351f Increase rate from metric when estimating fee (#1340)
3bb9c4f68f fix generator scripts to be consistent with updatedrelay output (#1339)
0475a1667b fixed mess with conversion rates (#1338)
d8fdd7d716 synchronize relay cli changes and token swap generator script (#1337)
6e928137a5 fix conversion rate override in token swap (#1336)
62d4a4811d override conversion rate in tokens swap generator (#1335)
ed9e1c839c fi typo in generator script (#1334)
3254b5af7a Override conversion rate when computing message fee (#1261)
66df68b5b8 Revert "Revert "override conversion rate in estimate-message-fee RPC (#1189)" (#1275)" (#1333)
0ca6fc6ef8 fix clippy issues (#1332)
5414b2fffb Reinitialize bridge relay subcommand (#1331)
a63d95ba7d removed extra *_RUNTIME_VERSION consts from relay code (#1330)
59fb18a310 fix typo in alert expression (#1329)
a6267a47ee Using-same-fork metric for finality and complex relay (#1327)
88d684d37e use mortal transactions in transaction resubmitter (#1326)
8ff88b6844 impl Decode for SignedExtensions (otherwise transaction resubmitter panicks) (#1325)
1ed09854f0 Encode and estimate Rococo/Wococo/Kusama/Polkadot messages (#1322)
ddb4517e13 Add some tests to check integrity of chain constants + bridge configuration (#1316)
bdeedb7ab9 Fix issues from cargo deny (#1311)
d3d79d01e0 expose fee multiplier metrics in messages relay (#1312)
c8b3f0ea16 Endow relayer account at target chain in message benchmarks (#1310)
f51ecd92b6 fix benchmarks before using it in Polkadot/Kusama/Rococo runtimes (#1309)
6935c619ad increase relay balance guard limits for Polkadot<>Kusama bridge (#1308)
7e31834c66 Fix mandatory headers scanning in on-demand relay (#1306)
92ddc3ea7a Polkadot-staging update (#1305)
3787193a31 fix session length of Rococo and Wococo (#1304)
eb468d29c0 Revert nightly docker pin (#1301)
e2d4c073e1 Use raw balance value if tokenDecimals property is missing (#1299)
108f4b29d1 Fix ss58 prefixes of Polkadot, Kusama and Westend used by relay (#1298)
64fbd2705e bump chain spec versions (#1297)
5707777b86 Bump Substrate/Polkadot/Cumulus refs (#1295)
29eecdf1fa Merge pull request #1294 from paritytech/polkadot-staging-update
1f0c05368e Relay balance metrics (#1291)
6356bb90b3 when messages pallet is halted, relay shall not submit messages delivery/confirmation transactions (#1289)
800dc2df8d when GRANDPA pallet is halted, relay shall not submit finality transactions (#1288)
3dd8e4f936 disable BEEFY allerts for Rialto (#1285)
f58fed7380 support version mode cli options in send-message subcommand (#1284)
3aac448da3 reuse polkadot-service code (#1273)
2bdbb651e1 replace latest_confirmed_nonce runtime APIs with direct storage reads (#1282)
5f9c6d241f move "common" code of messages pallet benchmarks helpers to the common library (#1281)
173d2d8229 Merge pull request #1280 from paritytech/polkadot-staging-update
8b9c4ec16d do not start spec_version guard when version mode is set to auto (#1278)
e98d682de2 removed extra messages benchmarks (#1279)
c730e25b61 Move benchmarks from Rialto to Millau (#1277)
54146416e7 Merge pull request #1276 from paritytech/polkadot-staging-update
df70118174 Merge branch 'master' into polkadot-staging-update
ed7def64c4 Revert "override conversion rate in estimate-message-fee RPC (#1189)" (#1275)
38c6c3a49f Use "production" floating tag when uilding docker image from version git tags (#1272)
ded9ff6dbb Replace InboundLaneApi::latest_received_nonce with direct storage read (#1269)
f704a741ee Polkadot staging update (#1270)
8c65f0d7ab verify that GRANDPA pallet is not initialized before submitting initialization transaction (#1267)
e7e83d8944 remove OutboundLaneApi::latest_received_nonce (#1262)
9f4b34acf1 bump rococo version (#1263)
82c08c5a87 read latest_generated_nonce directly from storage (#1260)
50ffb5dd08 override conversion rate in estimate-message-fee RPC (#1189)
467ca5ef59 move storage keys computation to primitivs (#1254)
4f9884066b remporary use pinned bridges-ci image in Dockerfile (#1258)
edfcb74e00 Change submit transaction spec_version and transaction_version query from chain (#1248)
4009d970d0 pin bridges-ci image (#1256)
65e51b5e1c decrease startup sleep to 5s for relays and to 120s for generators + remove curl (#1251)
3bc74355d9 Add missing RPC APIs to rialto parachain node (#1250)
80c9429284 Bump relay version to 1.0.0 (#1249)
9ead06af2a runtimes: fix call_size() test (#1245)
4fc8a29357 Use same endowed accounts set on dev/local chains (#1244)
fed54371c2 Refactor message relay helpers (#1234)
a15b4faae7 post-merge build fix (#1243)
52232d8d54 Fix transactions mortality (#1196)
c07bba931f Expose prometheus BEEFY metrics and add them to grafana dashboard (#1242)
f927775bd5 Refactor finality relay helpers (#1220)
7bf76f14a8 Update Rococo/Wococo version + prepare relay for Rococo<>Wococo bridge (#1241)
e860fecd04 Enable offchain indexing for Rialto/Millau nodes (#1239)
04d4d1c6b4 Enable Beefy debug logs in test deployment (#1237)
cd771f1089 Fix storage parameter name computation (#1238)
816ddd2dd2 Integrate BEEFY with Rialto & Millau runtimes (#1227)
d94b62b1ac update dependencies (#1229)
98eb9ee13d Add mut support (#1232)
ffef6f89f9 fixed set_operational in GRANDPA pallet (#1226)
bd2f8bfbd7 Add CODEOWNERS file (#1219)
6b5cf2b591 Unify metric names (#1209)
d1541e797e remove abandoned exchange relay (#1217)
39140d0b34 Remove unused `relays/headers` (#1216)
9bc071d42b Remove unused PoA<>Substrate bridge (#1210)
877e8d01e3 Fix UI deployment. (#1211)
6cd5775ebe Add `AtLeast32BitUnsigned` for MessageLance::SourceChainBalance (#1207)

git-subtree-dir: bridges
git-subtree-split: f220d2fccabbf141101d19456ecb4e3576a1d797

* fix compilation warnings
This commit is contained in:
Svyatoslav Nikolsky
2022-03-21 13:19:29 +03:00
committed by GitHub
parent 20da356434
commit 8e01ba9c03
212 changed files with 9704 additions and 7984 deletions
@@ -1,14 +1,15 @@
[package]
name = "substrate-relay"
version = "0.1.0"
version = "1.0.1"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
anyhow = "1.0"
async-std = "1.9.0"
codec = { package = "parity-scale-codec", version = "2.2.0" }
async-trait = "0.1.42"
codec = { package = "parity-scale-codec", version = "3.0.0" }
futures = "0.3.12"
hex = "0.4"
log = "0.4.14"
@@ -30,15 +31,16 @@ bp-polkadot = { path = "../../primitives/chain-polkadot" }
bp-rialto = { path = "../../primitives/chain-rialto" }
bp-rialto-parachain = { path = "../../primitives/chain-rialto-parachain" }
bp-rococo = { path = "../../primitives/chain-rococo" }
bp-token-swap = { path = "../../primitives/token-swap" }
bp-wococo = { path = "../../primitives/chain-wococo" }
bp-runtime = { path = "../../primitives/runtime" }
bp-token-swap = { path = "../../primitives/token-swap" }
bp-westend = { path = "../../primitives/chain-westend" }
bp-wococo = { path = "../../primitives/chain-wococo" }
bridge-runtime-common = { path = "../../bin/runtime-common" }
finality-relay = { path = "../finality" }
messages-relay = { path = "../messages" }
millau-runtime = { path = "../../bin/millau/runtime" }
pallet-bridge-dispatch = { path = "../../modules/dispatch" }
pallet-bridge-grandpa = { path = "../../modules/grandpa" }
pallet-bridge-messages = { path = "../../modules/messages" }
pallet-bridge-token-swap = { path = "../../modules/token-swap" }
relay-kusama-client = { path = "../client-kusama" }
@@ -72,8 +74,9 @@ polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot", bran
polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "master" }
[dev-dependencies]
bp-test-utils = { path = "../../primitives/test-utils" }
hex-literal = "0.3"
pallet-bridge-grandpa = { path = "../../modules/grandpa" }
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
tempfile = "3.2"
finality-grandpa = { version = "0.14.0" }
finality-grandpa = { version = "0.15.0" }
@@ -14,17 +14,20 @@
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use anyhow::anyhow;
use bp_message_dispatch::{CallOrigin, MessagePayload};
use bp_runtime::EncodedOrDecodedCall;
use codec::Decode;
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
use relay_kusama_client::Kusama;
use sp_core::storage::StorageKey;
use sp_runtime::{FixedPointNumber, FixedU128};
use sp_version::RuntimeVersion;
use crate::cli::{
bridge,
encode_call::{Call, CliEncodeCall},
encode_message, CliChain,
encode_call::{self, Call, CliEncodeCall},
encode_message,
send_message::{self, DispatchFeePayment},
CliChain,
};
/// Weight of the `system::remark` call at Kusama.
@@ -33,21 +36,16 @@ use crate::cli::{
/// calls in the future. But since it is used only in tests (and on test chains), this is ok.
pub(crate) const SYSTEM_REMARK_CALL_WEIGHT: Weight = 2 * 1_345_000;
/// Id of Kusama token that is used to fetch token price.
pub(crate) const TOKEN_ID: &str = "kusama";
impl CliEncodeCall for Kusama {
fn max_extrinsic_size() -> u32 {
bp_kusama::max_extrinsic_size()
}
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
fn encode_call(call: &Call) -> anyhow::Result<EncodedOrDecodedCall<Self::Call>> {
Ok(match call {
Call::Raw { data } => EncodedOrDecodedCall::Encoded(data.0.clone()),
Call::Remark { remark_payload, .. } => relay_kusama_client::runtime::Call::System(
relay_kusama_client::runtime::SystemCall::remark(
remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
),
),
)
.into(),
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
match *bridge_instance_index {
bridge::KUSAMA_TO_POLKADOT_INDEX => {
@@ -57,6 +55,7 @@ impl CliEncodeCall for Kusama {
lane.0, payload, fee.0,
),
)
.into()
},
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
@@ -67,13 +66,11 @@ impl CliEncodeCall for Kusama {
})
}
fn get_dispatch_info(
call: &relay_kusama_client::runtime::Call,
) -> anyhow::Result<DispatchInfo> {
fn get_dispatch_info(call: &EncodedOrDecodedCall<Self::Call>) -> anyhow::Result<DispatchInfo> {
match *call {
relay_kusama_client::runtime::Call::System(
EncodedOrDecodedCall::Decoded(relay_kusama_client::runtime::Call::System(
relay_kusama_client::runtime::SystemCall::remark(_),
) => Ok(DispatchInfo {
)) => Ok(DispatchInfo {
weight: crate::chains::kusama::SYSTEM_REMARK_CALL_WEIGHT,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
@@ -87,30 +84,52 @@ impl CliChain for Kusama {
const RUNTIME_VERSION: RuntimeVersion = bp_kusama::VERSION;
type KeyPair = sp_core::sr25519::Pair;
type MessagePayload = ();
type MessagePayload = MessagePayload<
bp_kusama::AccountId,
bp_polkadot::AccountPublic,
bp_polkadot::Signature,
Vec<u8>,
>;
fn ss58_format() -> u16 {
42
}
fn max_extrinsic_weight() -> Weight {
bp_kusama::max_extrinsic_weight()
sp_core::crypto::Ss58AddressFormat::from(
sp_core::crypto::Ss58AddressFormatRegistry::KusamaAccount,
)
.into()
}
fn encode_message(
_message: encode_message::MessagePayload,
message: encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload> {
anyhow::bail!("Sending messages from Kusama is not yet supported.")
match message {
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
.map_err(|e| anyhow!("Failed to decode Kusama's MessagePayload: {:?}", e)),
encode_message::MessagePayload::Call { mut call, mut sender, dispatch_weight } => {
type Source = Kusama;
type Target = relay_polkadot_client::Polkadot;
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::KUSAMA_TO_POLKADOT_INDEX,
);
let call = Target::encode_call(&call)?;
let dispatch_weight = dispatch_weight.map(Ok).unwrap_or_else(|| {
Err(anyhow::format_err!(
"Please specify dispatch weight of the encoded Polkadot call"
))
})?;
Ok(send_message::message_payload(
spec_version,
dispatch_weight,
origin,
&call,
DispatchFeePayment::AtSourceChain,
))
},
}
}
}
/// Storage key and initial value of Polkadot -> Kusama conversion rate.
pub(crate) fn polkadot_to_kusama_conversion_rate_params() -> (StorageKey, FixedU128) {
(
bp_runtime::storage_parameter_key(
bp_kusama::POLKADOT_TO_KUSAMA_CONVERSION_RATE_PARAMETER_NAME,
),
// starting relay before this parameter will be set to some value may cause troubles
FixedU128::from_inner(FixedU128::DIV),
)
}
@@ -16,95 +16,52 @@
//! Kusama-to-Polkadot headers sync entrypoint.
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_header_chain::justification::GrandpaJustification;
use relay_kusama_client::{Kusama, SyncHeader as KusamaSyncHeader};
use relay_polkadot_client::{Polkadot, SigningParams as PolkadotSigningParams};
use relay_substrate_client::{Client, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use substrate_relay_helper::finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
};
use async_trait::async_trait;
use relay_polkadot_client::Polkadot;
use substrate_relay_helper::{finality_pipeline::SubstrateFinalitySyncPipeline, TransactionParams};
/// Maximal saturating difference between `balance(now)` and `balance(now-24h)` to treat
/// relay as gone wild.
///
/// Actual value, returned by `maximal_balance_decrease_per_day_is_sane` test is approximately 21
/// DOT, but let's round up to 30 DOT here.
pub(crate) const MAXIMAL_BALANCE_DECREASE_PER_DAY: bp_polkadot::Balance = 30_000_000_000;
/// Kusama-to-Polkadot finality sync pipeline.
pub(crate) type FinalityPipelineKusamaFinalityToPolkadot =
SubstrateFinalityToSubstrate<Kusama, Polkadot, PolkadotSigningParams>;
/// DOT, and initial value of this constant was rounded up to 30 DOT. But for actual Kusama <>
/// Polkadot deployment we'll be using the same account for delivering finality (free for mandatory
/// headers) and messages. It means that we can't predict maximal loss. But to protect funds against
/// relay/deployment issues, let's limit it so something that is much larger than this estimation -
/// e.g. to 100 DOT.
// TODO: https://github.com/paritytech/parity-bridges-common/issues/1307
pub(crate) const MAXIMAL_BALANCE_DECREASE_PER_DAY: bp_polkadot::Balance = 100 * 10_000_000_000;
/// Description of Kusama -> Polkadot finalized headers bridge.
#[derive(Clone, Debug)]
pub(crate) struct KusamaFinalityToPolkadot {
finality_pipeline: FinalityPipelineKusamaFinalityToPolkadot,
}
impl KusamaFinalityToPolkadot {
pub fn new(target_client: Client<Polkadot>, target_sign: PolkadotSigningParams) -> Self {
Self {
finality_pipeline: FinalityPipelineKusamaFinalityToPolkadot::new(
target_client,
target_sign,
),
}
}
}
pub struct KusamaFinalityToPolkadot;
substrate_relay_helper::generate_mocked_submit_finality_proof_call_builder!(
KusamaFinalityToPolkadot,
KusamaFinalityToPolkadotCallBuilder,
relay_polkadot_client::runtime::Call::BridgeKusamaGrandpa,
relay_polkadot_client::runtime::BridgeKusamaGrandpaCall::submit_finality_proof
);
#[async_trait]
impl SubstrateFinalitySyncPipeline for KusamaFinalityToPolkadot {
type FinalitySyncPipeline = FinalityPipelineKusamaFinalityToPolkadot;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_kusama::BEST_FINALIZED_KUSAMA_HEADER_METHOD;
type SourceChain = relay_kusama_client::Kusama;
type TargetChain = Polkadot;
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(params)
}
type SubmitFinalityProofCallBuilder = KusamaFinalityToPolkadotCallBuilder;
type TransactionSignScheme = Polkadot;
fn start_relay_guards(&self) {
relay_substrate_client::guard::abort_on_spec_version_change(
self.finality_pipeline.target_client.clone(),
bp_polkadot::VERSION.spec_version,
);
relay_substrate_client::guard::abort_when_account_balance_decreased(
self.finality_pipeline.target_client.clone(),
self.transactions_author(),
async fn start_relay_guards(
target_client: &relay_substrate_client::Client<Polkadot>,
transaction_params: &TransactionParams<sp_core::sr25519::Pair>,
enable_version_guard: bool,
) -> relay_substrate_client::Result<()> {
substrate_relay_helper::finality_guards::start::<Polkadot, Polkadot>(
target_client,
transaction_params,
enable_version_guard,
MAXIMAL_BALANCE_DECREASE_PER_DAY,
);
}
fn transactions_author(&self) -> bp_polkadot::AccountId {
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
}
fn make_submit_finality_proof_transaction(
&self,
era: bp_runtime::TransactionEraOf<Polkadot>,
transaction_nonce: bp_runtime::IndexOf<Polkadot>,
header: KusamaSyncHeader,
proof: GrandpaJustification<bp_kusama::Header>,
) -> Bytes {
let call = relay_polkadot_client::runtime::Call::BridgeKusamaGrandpa(
relay_polkadot_client::runtime::BridgeKusamaGrandpaCall::submit_finality_proof(
Box::new(header.into_inner()),
proof,
),
);
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
let transaction = Polkadot::sign_transaction(
genesis_hash,
&self.finality_pipeline.target_sign,
era,
UnsignedTransaction::new(call, transaction_nonce),
);
Bytes(transaction.encode())
)
.await
}
}
@@ -132,7 +89,7 @@ pub(crate) mod tests {
// differ from the `DbWeight` of Rialto runtime. But now (and most probably forever) it is
// the same.
type GrandpaPalletWeights =
pallet_bridge_grandpa::weights::RialtoWeight<rialto_runtime::Runtime>;
pallet_bridge_grandpa::weights::MillauWeight<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.
@@ -16,316 +16,64 @@
//! Kusama-to-Polkadot messages sync entrypoint.
use std::ops::RangeInclusive;
use codec::Encode;
use frame_support::weights::Weight;
use sp_core::{Bytes, Pair};
use bp_messages::MessageNonce;
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
use relay_kusama_client::{
HeaderId as KusamaHeaderId, Kusama, SigningParams as KusamaSigningParams,
};
use relay_polkadot_client::{
HeaderId as PolkadotHeaderId, Polkadot, SigningParams as PolkadotSigningParams,
};
use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction};
use substrate_relay_helper::{
messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
},
messages_source::SubstrateMessagesSource,
messages_target::SubstrateMessagesTarget,
STALL_TIMEOUT,
};
use messages_relay::relay_strategy::MixStrategy;
use relay_kusama_client::Kusama;
use relay_polkadot_client::Polkadot;
use substrate_relay_helper::messages_lane::SubstrateMessageLane;
/// Kusama-to-Polkadot message lane.
pub type MessageLaneKusamaMessagesToPolkadot =
SubstrateMessageLaneToSubstrate<Kusama, KusamaSigningParams, Polkadot, PolkadotSigningParams>;
#[derive(Clone)]
pub struct KusamaMessagesToPolkadot {
message_lane: MessageLaneKusamaMessagesToPolkadot,
}
/// Description of Kusama -> Polkadot messages bridge.
#[derive(Clone, Debug)]
pub struct KusamaMessagesToPolkadot;
substrate_relay_helper::generate_mocked_receive_message_proof_call_builder!(
KusamaMessagesToPolkadot,
KusamaMessagesToPolkadotReceiveMessagesProofCallBuilder,
relay_polkadot_client::runtime::Call::BridgeKusamaMessages,
relay_polkadot_client::runtime::BridgeKusamaMessagesCall::receive_messages_proof
);
substrate_relay_helper::generate_mocked_receive_message_delivery_proof_call_builder!(
KusamaMessagesToPolkadot,
KusamaMessagesToPolkadotReceiveMessagesDeliveryProofCallBuilder,
relay_kusama_client::runtime::Call::BridgePolkadotMessages,
relay_kusama_client::runtime::BridgePolkadotMessagesCall::receive_messages_delivery_proof
);
substrate_relay_helper::generate_mocked_update_conversion_rate_call_builder!(
Kusama,
KusamaMessagesToPolkadotUpdateConversionRateCallBuilder,
relay_kusama_client::runtime::Call::BridgePolkadotMessages,
relay_kusama_client::runtime::BridgePolkadotMessagesCall::update_pallet_parameter,
relay_kusama_client::runtime::BridgePolkadotMessagesParameter::PolkadotToKusamaConversionRate
);
impl SubstrateMessageLane for KusamaMessagesToPolkadot {
type MessageLane = MessageLaneKusamaMessagesToPolkadot;
const SOURCE_TO_TARGET_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> =
Some(bp_polkadot::KUSAMA_TO_POLKADOT_CONVERSION_RATE_PARAMETER_NAME);
const TARGET_TO_SOURCE_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> =
Some(bp_kusama::POLKADOT_TO_KUSAMA_CONVERSION_RATE_PARAMETER_NAME);
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str =
bp_polkadot::TO_POLKADOT_MESSAGE_DETAILS_METHOD;
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
bp_polkadot::TO_POLKADOT_LATEST_GENERATED_NONCE_METHOD;
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_polkadot::TO_POLKADOT_LATEST_RECEIVED_NONCE_METHOD;
const SOURCE_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str> =
Some(bp_polkadot::KUSAMA_FEE_MULTIPLIER_PARAMETER_NAME);
const TARGET_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str> =
Some(bp_kusama::POLKADOT_FEE_MULTIPLIER_PARAMETER_NAME);
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_kusama::FROM_KUSAMA_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
bp_kusama::FROM_KUSAMA_LATEST_CONFIRMED_NONCE_METHOD;
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str =
bp_kusama::FROM_KUSAMA_UNREWARDED_RELAYERS_STATE;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_kusama::BEST_FINALIZED_KUSAMA_HEADER_METHOD;
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str =
bp_polkadot::BEST_FINALIZED_POLKADOT_HEADER_METHOD;
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str =
bp_kusama::WITH_POLKADOT_MESSAGES_PALLET_NAME;
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str =
bp_polkadot::WITH_KUSAMA_MESSAGES_PALLET_NAME;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
bp_polkadot::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
const AT_SOURCE_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str> =
Some(bp_kusama::TRANSACTION_PAYMENT_PALLET_NAME);
const AT_TARGET_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str> =
Some(bp_polkadot::TRANSACTION_PAYMENT_PALLET_NAME);
type SourceChain = Kusama;
type TargetChain = Polkadot;
fn source_transactions_author(&self) -> bp_kusama::AccountId {
(*self.message_lane.source_sign.public().as_array_ref()).into()
}
type SourceTransactionSignScheme = Kusama;
type TargetTransactionSignScheme = Polkadot;
fn make_messages_receiving_proof_transaction(
&self,
best_block_id: KusamaHeaderId,
transaction_nonce: bp_runtime::IndexOf<Kusama>,
_generated_at_block: PolkadotHeaderId,
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
) -> Bytes {
let (relayers_state, proof) = proof;
let call = relay_kusama_client::runtime::Call::BridgePolkadotMessages(
relay_kusama_client::runtime::BridgePolkadotMessagesCall::receive_messages_delivery_proof(
proof,
relayers_state,
),
);
let genesis_hash = *self.message_lane.source_client.genesis_hash();
let transaction = Kusama::sign_transaction(
genesis_hash,
&self.message_lane.source_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.source_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Polkadot -> Kusama confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
bp_kusama::max_extrinsic_weight(),
transaction.encode().len(),
bp_kusama::max_extrinsic_size(),
);
Bytes(transaction.encode())
}
type ReceiveMessagesProofCallBuilder = KusamaMessagesToPolkadotReceiveMessagesProofCallBuilder;
type ReceiveMessagesDeliveryProofCallBuilder =
KusamaMessagesToPolkadotReceiveMessagesDeliveryProofCallBuilder;
fn target_transactions_author(&self) -> bp_polkadot::AccountId {
(*self.message_lane.target_sign.public().as_array_ref()).into()
}
type TargetToSourceChainConversionRateUpdateBuilder =
KusamaMessagesToPolkadotUpdateConversionRateCallBuilder;
fn make_messages_delivery_transaction(
&self,
best_block_id: PolkadotHeaderId,
transaction_nonce: bp_runtime::IndexOf<Polkadot>,
_generated_at_header: KusamaHeaderId,
_nonces: RangeInclusive<MessageNonce>,
proof: <Self::MessageLane as MessageLane>::MessagesProof,
) -> Bytes {
let (dispatch_weight, proof) = proof;
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
let messages_count = nonces_end - nonces_start + 1;
let call = relay_polkadot_client::runtime::Call::BridgeKusamaMessages(
relay_polkadot_client::runtime::BridgeKusamaMessagesCall::receive_messages_proof(
self.message_lane.relayer_id_at_source.clone(),
proof,
messages_count as _,
dispatch_weight,
),
);
let genesis_hash = *self.message_lane.target_client.genesis_hash();
let transaction = Polkadot::sign_transaction(
genesis_hash,
&self.message_lane.target_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.target_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Kusama -> Polkadot delivery transaction. Weight: <unknown>/{}, size: {}/{}",
bp_polkadot::max_extrinsic_weight(),
transaction.encode().len(),
bp_polkadot::max_extrinsic_size(),
);
Bytes(transaction.encode())
}
}
/// Kusama node as messages source.
type KusamaSourceClient = SubstrateMessagesSource<KusamaMessagesToPolkadot>;
/// Polkadot node as messages target.
type PolkadotTargetClient = SubstrateMessagesTarget<KusamaMessagesToPolkadot>;
/// Run Kusama-to-Polkadot messages sync.
pub async fn run(
params: MessagesRelayParams<
Kusama,
KusamaSigningParams,
Polkadot,
PolkadotSigningParams,
MixStrategy,
>,
) -> anyhow::Result<()> {
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
params.source_transactions_mortality,
params.target_transactions_mortality,
Kusama::AVERAGE_BLOCK_INTERVAL,
Polkadot::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
);
let relayer_id_at_kusama = (*params.source_sign.public().as_array_ref()).into();
let lane_id = params.lane_id;
let source_client = params.source_client;
let target_client = params.target_client;
let lane = KusamaMessagesToPolkadot {
message_lane: SubstrateMessageLaneToSubstrate {
source_client: source_client.clone(),
source_sign: params.source_sign,
source_transactions_mortality: params.source_transactions_mortality,
target_client: target_client.clone(),
target_sign: params.target_sign,
target_transactions_mortality: params.target_transactions_mortality,
relayer_id_at_source: relayer_id_at_kusama,
},
};
// 2/3 is reserved for proofs and tx overhead
let max_messages_size_in_single_batch = bp_polkadot::max_extrinsic_size() / 3;
// we don't know exact weights of the Polkadot runtime. So to guess weights we'll be using
// weights from Rialto and then simply dividing it by x2.
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
select_delivery_transaction_limits::<
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>,
>(
bp_polkadot::max_extrinsic_weight(),
bp_polkadot::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
);
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
(max_messages_in_single_batch / 2, max_messages_weight_in_single_batch / 2);
log::info!(
target: "bridge",
"Starting Kusama -> Polkadot messages relay.\n\t\
Kusama relayer account id: {:?}\n\t\
Max messages in single transaction: {}\n\t\
Max messages size in single transaction: {}\n\t\
Max messages weight in single transaction: {}\n\t\
Tx mortality: {:?}/{:?}\n\t\
Stall timeout: {:?}",
lane.message_lane.relayer_id_at_source,
max_messages_in_single_batch,
max_messages_size_in_single_batch,
max_messages_weight_in_single_batch,
params.source_transactions_mortality,
params.target_transactions_mortality,
stall_timeout,
);
let standalone_metrics = params
.standalone_metrics
.map(Ok)
.unwrap_or_else(|| standalone_metrics(source_client.clone(), target_client.clone()))?;
messages_relay::message_lane_loop::run(
messages_relay::message_lane_loop::Params {
lane: lane_id,
source_tick: Kusama::AVERAGE_BLOCK_INTERVAL,
target_tick: Polkadot::AVERAGE_BLOCK_INTERVAL,
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
stall_timeout,
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
max_unrewarded_relayer_entries_at_target:
bp_polkadot::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
max_unconfirmed_nonces_at_target:
bp_polkadot::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
max_messages_in_single_batch,
max_messages_weight_in_single_batch,
max_messages_size_in_single_batch,
relay_strategy: params.relay_strategy,
},
},
KusamaSourceClient::new(
source_client.clone(),
lane.clone(),
lane_id,
params.target_to_source_headers_relay,
),
PolkadotTargetClient::new(
target_client,
lane,
lane_id,
standalone_metrics.clone(),
params.source_to_target_headers_relay,
),
standalone_metrics.register_and_spawn(params.metrics_params)?,
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Create standalone metrics for the Kusama -> Polkadot messages loop.
pub(crate) fn standalone_metrics(
source_client: Client<Kusama>,
target_client: Client<Polkadot>,
) -> anyhow::Result<StandaloneMessagesMetrics<Kusama, Polkadot>> {
substrate_relay_helper::messages_lane::standalone_metrics(
source_client,
target_client,
Some(crate::chains::kusama::TOKEN_ID),
Some(crate::chains::polkadot::TOKEN_ID),
Some(crate::chains::polkadot::kusama_to_polkadot_conversion_rate_params()),
Some(crate::chains::kusama::polkadot_to_kusama_conversion_rate_params()),
)
}
/// Update Polkadot -> Kusama conversion rate, stored in Kusama runtime storage.
pub(crate) async fn update_polkadot_to_kusama_conversion_rate(
client: Client<Kusama>,
signer: <Kusama as TransactionSignScheme>::AccountKeyPair,
updated_rate: f64,
) -> anyhow::Result<()> {
let genesis_hash = *client.genesis_hash();
let signer_id = (*signer.public().as_array_ref()).into();
client
.submit_signed_extrinsic(signer_id, move |_, transaction_nonce| {
Bytes(
Kusama::sign_transaction(
genesis_hash,
&signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
relay_kusama_client::runtime::Call::BridgePolkadotMessages(
relay_kusama_client::runtime::BridgePolkadotMessagesCall::update_pallet_parameter(
relay_kusama_client::runtime::BridgePolkadotMessagesParameter::PolkadotToKusamaConversionRate(
sp_runtime::FixedU128::from_float(updated_rate),
)
)
),
transaction_nonce,
),
)
.encode(),
)
})
.await
.map(drop)
.map_err(|err| anyhow::format_err!("{:?}", err))
type RelayStrategy = MixStrategy;
}
@@ -25,37 +25,27 @@ use crate::cli::{
};
use anyhow::anyhow;
use bp_message_dispatch::{CallOrigin, MessagePayload};
use bp_runtime::EncodedOrDecodedCall;
use codec::Decode;
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
use frame_support::weights::{DispatchInfo, GetDispatchInfo};
use relay_millau_client::Millau;
use sp_core::storage::StorageKey;
use sp_runtime::FixedU128;
use sp_version::RuntimeVersion;
// Millau/Rialto tokens have no any real value, so the conversion rate we use is always 1:1. But we
// want to test our code that is intended to work with real-value chains. So to keep it close to
// 1:1, we'll be treating Rialto as BTC and Millau as wBTC (only in relayer).
/// The identifier of token, which value is associated with Millau token value by relayer.
pub(crate) const ASSOCIATED_TOKEN_ID: &str = crate::chains::kusama::TOKEN_ID;
impl CliEncodeCall for Millau {
fn max_extrinsic_size() -> u32 {
bp_millau::max_extrinsic_size()
}
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
fn encode_call(call: &Call) -> anyhow::Result<EncodedOrDecodedCall<Self::Call>> {
Ok(match call {
Call::Raw { data } => Decode::decode(&mut &*data.0)?,
Call::Raw { data } => Self::Call::decode(&mut &*data.0)?.into(),
Call::Remark { remark_payload, .. } =>
millau_runtime::Call::System(millau_runtime::SystemCall::remark {
remark: remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
}),
})
.into(),
Call::Transfer { recipient, amount } =>
millau_runtime::Call::Balances(millau_runtime::BalancesCall::transfer {
dest: recipient.raw_id(),
value: amount.cast(),
}),
})
.into(),
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
match *bridge_instance_index {
bridge::MILLAU_TO_RIALTO_INDEX => {
@@ -67,6 +57,7 @@ impl CliEncodeCall for Millau {
delivery_and_dispatch_fee: fee.cast(),
},
)
.into()
},
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
@@ -76,8 +67,8 @@ impl CliEncodeCall for Millau {
})
}
fn get_dispatch_info(call: &millau_runtime::Call) -> anyhow::Result<DispatchInfo> {
Ok(call.get_dispatch_info())
fn get_dispatch_info(call: &EncodedOrDecodedCall<Self::Call>) -> anyhow::Result<DispatchInfo> {
Ok(call.to_decoded()?.get_dispatch_info())
}
}
@@ -96,10 +87,6 @@ impl CliChain for Millau {
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,
@@ -107,7 +94,7 @@ impl CliChain for Millau {
match message {
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
.map_err(|e| anyhow!("Failed to decode Millau's MessagePayload: {:?}", e)),
encode_message::MessagePayload::Call { mut call, mut sender } => {
encode_message::MessagePayload::Call { mut call, mut sender, dispatch_weight } => {
type Source = Millau;
type Target = relay_rialto_client::Rialto;
@@ -119,11 +106,13 @@ impl CliChain for Millau {
bridge::MILLAU_TO_RIALTO_INDEX,
);
let call = Target::encode_call(&call)?;
let weight = call.get_dispatch_info().weight;
let dispatch_weight = dispatch_weight.map(Ok).unwrap_or_else(|| {
call.to_decoded().map(|call| call.get_dispatch_info().weight)
})?;
Ok(send_message::message_payload(
spec_version,
weight,
dispatch_weight,
origin,
&call,
DispatchFeePayment::AtSourceChain,
@@ -132,11 +121,3 @@ impl CliChain for Millau {
}
}
}
/// Storage key and initial value of Rialto -> Millau conversion rate.
pub(crate) fn rialto_to_millau_conversion_rate_params() -> (StorageKey, FixedU128) {
(
StorageKey(millau_runtime::rialto_messages::RialtoToMillauConversionRate::key().to_vec()),
millau_runtime::rialto_messages::INITIAL_RIALTO_TO_MILLAU_CONVERSION_RATE,
)
}
@@ -16,65 +16,22 @@
//! Millau-to-Rialto headers sync entrypoint.
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_header_chain::justification::GrandpaJustification;
use relay_millau_client::{Millau, SyncHeader as MillauSyncHeader};
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
use relay_substrate_client::{Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use substrate_relay_helper::finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
DirectSubmitFinalityProofCallBuilder, SubstrateFinalitySyncPipeline,
};
/// Millau-to-Rialto finality sync pipeline.
pub(crate) type FinalityPipelineMillauToRialto =
SubstrateFinalityToSubstrate<Millau, Rialto, RialtoSigningParams>;
/// Description of Millau -> Rialto finalized headers bridge.
#[derive(Clone, Debug)]
pub(crate) struct MillauFinalityToRialto {
finality_pipeline: FinalityPipelineMillauToRialto,
}
impl MillauFinalityToRialto {
pub fn new(target_client: Client<Rialto>, target_sign: RialtoSigningParams) -> Self {
Self { finality_pipeline: FinalityPipelineMillauToRialto::new(target_client, target_sign) }
}
}
pub struct MillauFinalityToRialto;
impl SubstrateFinalitySyncPipeline for MillauFinalityToRialto {
type FinalitySyncPipeline = FinalityPipelineMillauToRialto;
type SourceChain = relay_millau_client::Millau;
type TargetChain = relay_rialto_client::Rialto;
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.finality_pipeline.target_sign.public().as_array_ref()).into()
}
fn make_submit_finality_proof_transaction(
&self,
era: bp_runtime::TransactionEraOf<Rialto>,
transaction_nonce: IndexOf<Rialto>,
header: MillauSyncHeader,
proof: GrandpaJustification<bp_millau::Header>,
) -> Bytes {
let call = rialto_runtime::BridgeGrandpaMillauCall::submit_finality_proof {
finality_target: Box::new(header.into_inner()),
justification: proof,
}
.into();
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
let transaction = Rialto::sign_transaction(
genesis_hash,
&self.finality_pipeline.target_sign,
era,
UnsignedTransaction::new(call, transaction_nonce),
);
Bytes(transaction.encode())
}
type SubmitFinalityProofCallBuilder = DirectSubmitFinalityProofCallBuilder<
Self,
rialto_runtime::Runtime,
rialto_runtime::MillauGrandpaInstance,
>;
type TransactionSignScheme = relay_rialto_client::Rialto;
}
@@ -16,310 +16,55 @@
//! Millau-to-Rialto messages sync entrypoint.
use std::ops::RangeInclusive;
use codec::Encode;
use frame_support::dispatch::GetDispatchInfo;
use sp_core::{Bytes, Pair};
use bp_messages::MessageNonce;
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use frame_support::weights::Weight;
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
use relay_millau_client::{
HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams,
};
use relay_rialto_client::{
HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams,
};
use relay_substrate_client::{Chain, Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use substrate_relay_helper::{
messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
},
messages_source::SubstrateMessagesSource,
messages_target::SubstrateMessagesTarget,
STALL_TIMEOUT,
use messages_relay::relay_strategy::MixStrategy;
use relay_millau_client::Millau;
use relay_rialto_client::Rialto;
use substrate_relay_helper::messages_lane::{
DirectReceiveMessagesDeliveryProofCallBuilder, DirectReceiveMessagesProofCallBuilder,
SubstrateMessageLane,
};
/// Millau-to-Rialto message lane.
pub type MessageLaneMillauMessagesToRialto =
SubstrateMessageLaneToSubstrate<Millau, MillauSigningParams, Rialto, RialtoSigningParams>;
#[derive(Clone)]
pub struct MillauMessagesToRialto {
message_lane: MessageLaneMillauMessagesToRialto,
}
/// Description of Millau -> Rialto messages bridge.
#[derive(Clone, Debug)]
pub struct MillauMessagesToRialto;
substrate_relay_helper::generate_direct_update_conversion_rate_call_builder!(
Millau,
MillauMessagesToRialtoUpdateConversionRateCallBuilder,
millau_runtime::Runtime,
millau_runtime::WithRialtoMessagesInstance,
millau_runtime::rialto_messages::MillauToRialtoMessagesParameter::RialtoToMillauConversionRate
);
impl SubstrateMessageLane for MillauMessagesToRialto {
type MessageLane = MessageLaneMillauMessagesToRialto;
const SOURCE_TO_TARGET_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> =
Some(bp_rialto::MILLAU_TO_RIALTO_CONVERSION_RATE_PARAMETER_NAME);
const TARGET_TO_SOURCE_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> =
Some(bp_millau::RIALTO_TO_MILLAU_CONVERSION_RATE_PARAMETER_NAME);
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;
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_millau::WITH_RIALTO_MESSAGES_PALLET_NAME;
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_rialto::WITH_MILLAU_MESSAGES_PALLET_NAME;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
bp_rialto::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
const SOURCE_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str> = None;
const TARGET_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str> = None;
const AT_SOURCE_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str> = None;
const AT_TARGET_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str> = None;
type SourceChain = Millau;
type TargetChain = Rialto;
fn source_transactions_author(&self) -> bp_millau::AccountId {
(*self.message_lane.source_sign.public().as_array_ref()).into()
}
type SourceTransactionSignScheme = Millau;
type TargetTransactionSignScheme = Rialto;
fn make_messages_receiving_proof_transaction(
&self,
best_block_id: MillauHeaderId,
transaction_nonce: IndexOf<Millau>,
_generated_at_block: RialtoHeaderId,
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
) -> Bytes {
let (relayers_state, proof) = proof;
let call: millau_runtime::Call =
millau_runtime::MessagesCall::receive_messages_delivery_proof { proof, relayers_state }
.into();
let call_weight = call.get_dispatch_info().weight;
let genesis_hash = *self.message_lane.source_client.genesis_hash();
let transaction = Millau::sign_transaction(
genesis_hash,
&self.message_lane.source_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.source_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Rialto -> Millau confirmation transaction. Weight: {}/{}, size: {}/{}",
call_weight,
bp_millau::max_extrinsic_weight(),
transaction.encode().len(),
bp_millau::max_extrinsic_size(),
);
Bytes(transaction.encode())
}
type ReceiveMessagesProofCallBuilder = DirectReceiveMessagesProofCallBuilder<
Self,
rialto_runtime::Runtime,
rialto_runtime::WithMillauMessagesInstance,
>;
type ReceiveMessagesDeliveryProofCallBuilder = DirectReceiveMessagesDeliveryProofCallBuilder<
Self,
millau_runtime::Runtime,
millau_runtime::WithRialtoMessagesInstance,
>;
fn target_transactions_author(&self) -> bp_rialto::AccountId {
(*self.message_lane.target_sign.public().as_array_ref()).into()
}
type TargetToSourceChainConversionRateUpdateBuilder =
MillauMessagesToRialtoUpdateConversionRateCallBuilder;
fn make_messages_delivery_transaction(
&self,
best_block_id: RialtoHeaderId,
transaction_nonce: IndexOf<Rialto>,
_generated_at_header: MillauHeaderId,
_nonces: RangeInclusive<MessageNonce>,
proof: <Self::MessageLane as MessageLane>::MessagesProof,
) -> Bytes {
let (dispatch_weight, proof) = proof;
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
let messages_count = nonces_end - nonces_start + 1;
let call: rialto_runtime::Call = rialto_runtime::MessagesCall::receive_messages_proof {
relayer_id_at_bridged_chain: self.message_lane.relayer_id_at_source.clone(),
proof,
messages_count: messages_count as _,
dispatch_weight,
}
.into();
let call_weight = call.get_dispatch_info().weight;
let genesis_hash = *self.message_lane.target_client.genesis_hash();
let transaction = Rialto::sign_transaction(
genesis_hash,
&self.message_lane.target_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.target_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Millau -> Rialto delivery transaction. Weight: {}/{}, size: {}/{}",
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<MillauMessagesToRialto>;
/// Rialto node as messages target.
type RialtoTargetClient = SubstrateMessagesTarget<MillauMessagesToRialto>;
/// Run Millau-to-Rialto messages sync.
pub async fn run(
params: MessagesRelayParams<
Millau,
MillauSigningParams,
Rialto,
RialtoSigningParams,
MixStrategy,
>,
) -> anyhow::Result<()> {
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
params.source_transactions_mortality,
params.target_transactions_mortality,
Millau::AVERAGE_BLOCK_INTERVAL,
Rialto::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
);
let relayer_id_at_millau = (*params.source_sign.public().as_array_ref()).into();
let lane_id = params.lane_id;
let source_client = params.source_client;
let target_client = params.target_client;
let lane = MillauMessagesToRialto {
message_lane: SubstrateMessageLaneToSubstrate {
source_client: source_client.clone(),
source_sign: params.source_sign,
source_transactions_mortality: params.source_transactions_mortality,
target_client: target_client.clone(),
target_sign: params.target_sign,
target_transactions_mortality: params.target_transactions_mortality,
relayer_id_at_source: relayer_id_at_millau,
},
};
// 2/3 is reserved for proofs and tx overhead
let max_messages_size_in_single_batch = bp_rialto::max_extrinsic_size() / 3;
// TODO: use Millau weights after https://github.com/paritytech/parity-bridges-common/issues/390
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
select_delivery_transaction_limits::<
pallet_bridge_messages::weights::RialtoWeight<millau_runtime::Runtime>,
>(
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: {}\n\t\
Tx mortality: {:?}/{:?}\n\t\
Stall timeout: {:?}",
lane.message_lane.relayer_id_at_source,
max_messages_in_single_batch,
max_messages_size_in_single_batch,
max_messages_weight_in_single_batch,
params.source_transactions_mortality,
params.target_transactions_mortality,
stall_timeout,
);
let standalone_metrics = params
.standalone_metrics
.map(Ok)
.unwrap_or_else(|| standalone_metrics(source_client.clone(), target_client.clone()))?;
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,
relay_strategy: params.relay_strategy,
},
},
MillauSourceClient::new(
source_client.clone(),
lane.clone(),
lane_id,
params.target_to_source_headers_relay,
),
RialtoTargetClient::new(
target_client,
lane,
lane_id,
standalone_metrics.clone(),
params.source_to_target_headers_relay,
),
standalone_metrics.register_and_spawn(params.metrics_params)?,
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Create standalone metrics for the Millau -> Rialto messages loop.
pub(crate) fn standalone_metrics(
source_client: Client<Millau>,
target_client: Client<Rialto>,
) -> anyhow::Result<StandaloneMessagesMetrics<Millau, Rialto>> {
substrate_relay_helper::messages_lane::standalone_metrics(
source_client,
target_client,
Some(crate::chains::millau::ASSOCIATED_TOKEN_ID),
Some(crate::chains::rialto::ASSOCIATED_TOKEN_ID),
Some(crate::chains::rialto::millau_to_rialto_conversion_rate_params()),
Some(crate::chains::millau::rialto_to_millau_conversion_rate_params()),
)
}
/// Update Rialto -> Millau conversion rate, stored in Millau runtime storage.
pub(crate) async fn update_rialto_to_millau_conversion_rate(
client: Client<Millau>,
signer: <Millau as TransactionSignScheme>::AccountKeyPair,
updated_rate: f64,
) -> anyhow::Result<()> {
let genesis_hash = *client.genesis_hash();
let signer_id = (*signer.public().as_array_ref()).into();
client
.submit_signed_extrinsic(signer_id, move |_, transaction_nonce| {
Bytes(
Millau::sign_transaction(
genesis_hash,
&signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
millau_runtime::MessagesCall::update_pallet_parameter {
parameter: millau_runtime::rialto_messages::MillauToRialtoMessagesParameter::RialtoToMillauConversionRate(
sp_runtime::FixedU128::from_float(updated_rate),
),
}
.into(),
transaction_nonce,
),
)
.encode(),
)
})
.await
.map(drop)
.map_err(|err| anyhow::format_err!("{:?}", err))
type RelayStrategy = MixStrategy;
}
@@ -39,27 +39,16 @@ mod rococo;
mod westend;
mod wococo;
use relay_utils::metrics::{MetricsParams, StandaloneMetric};
pub(crate) fn add_polkadot_kusama_price_metrics<T: finality_relay::FinalitySyncPipeline>(
params: MetricsParams,
) -> anyhow::Result<MetricsParams> {
substrate_relay_helper::helpers::token_price_metric(polkadot::TOKEN_ID)?
.register_and_spawn(&params.registry)?;
substrate_relay_helper::helpers::token_price_metric(kusama::TOKEN_ID)?
.register_and_spawn(&params.registry)?;
Ok(params)
}
#[cfg(test)]
mod tests {
use crate::cli::{encode_call, send_message};
use bp_messages::source_chain::TargetHeaderChain;
use bp_runtime::Chain as _;
use codec::Encode;
use frame_support::dispatch::GetDispatchInfo;
use relay_millau_client::Millau;
use relay_rialto_client::Rialto;
use relay_substrate_client::{TransactionSignScheme, UnsignedTransaction};
use relay_substrate_client::{SignParam, TransactionSignScheme, UnsignedTransaction};
use sp_core::Pair;
use sp_runtime::traits::{IdentifyAccount, Verify};
@@ -114,8 +103,8 @@ mod tests {
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(),
bp_rialto::Rialto::max_extrinsic_size(),
bp_millau::Millau::max_extrinsic_size(),
);
let call: millau_runtime::Call =
@@ -147,8 +136,8 @@ mod tests {
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(),
bp_rialto::Rialto::max_extrinsic_size()
) > bp_millau::Millau::max_extrinsic_size(),
"We can't actually send maximal messages to Rialto from Millau, because Millau extrinsics can't be that large",
)
}
@@ -158,7 +147,7 @@ mod tests {
use rialto_runtime::millau_messages::Millau;
let maximal_dispatch_weight = send_message::compute_maximal_message_dispatch_weight(
bp_millau::max_extrinsic_weight(),
bp_millau::Millau::max_extrinsic_weight(),
);
let call: millau_runtime::Call =
rialto_runtime::SystemCall::remark { remark: vec![] }.into();
@@ -187,7 +176,7 @@ mod tests {
use millau_runtime::rialto_messages::Rialto;
let maximal_dispatch_weight = send_message::compute_maximal_message_dispatch_weight(
bp_rialto::max_extrinsic_weight(),
bp_rialto::Rialto::max_extrinsic_weight(),
);
let call: rialto_runtime::Call =
millau_runtime::SystemCall::remark { remark: vec![] }.into();
@@ -215,12 +204,15 @@ mod tests {
fn rialto_tx_extra_bytes_constant_is_correct() {
let rialto_call =
rialto_runtime::Call::System(rialto_runtime::SystemCall::remark { remark: vec![] });
let rialto_tx = Rialto::sign_transaction(
Default::default(),
&sp_keyring::AccountKeyring::Alice.pair(),
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(rialto_call.clone(), 0),
);
let rialto_tx = Rialto::sign_transaction(SignParam {
spec_version: 1,
transaction_version: 1,
genesis_hash: Default::default(),
signer: sp_keyring::AccountKeyring::Alice.pair(),
era: relay_substrate_client::TransactionEra::immortal(),
unsigned: UnsignedTransaction::new(rialto_call.clone().into(), 0),
})
.unwrap();
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,
@@ -234,12 +226,15 @@ mod tests {
fn millau_tx_extra_bytes_constant_is_correct() {
let millau_call =
millau_runtime::Call::System(millau_runtime::SystemCall::remark { remark: vec![] });
let millau_tx = Millau::sign_transaction(
Default::default(),
&sp_keyring::AccountKeyring::Alice.pair(),
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(millau_call.clone(), 0),
);
let millau_tx = Millau::sign_transaction(SignParam {
spec_version: 0,
transaction_version: 0,
genesis_hash: Default::default(),
signer: sp_keyring::AccountKeyring::Alice.pair(),
era: relay_substrate_client::TransactionEra::immortal(),
unsigned: UnsignedTransaction::new(millau_call.clone().into(), 0),
})
.unwrap();
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,
@@ -14,17 +14,20 @@
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use anyhow::anyhow;
use bp_message_dispatch::{CallOrigin, MessagePayload};
use bp_runtime::EncodedOrDecodedCall;
use codec::Decode;
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
use relay_polkadot_client::Polkadot;
use sp_core::storage::StorageKey;
use sp_runtime::{FixedPointNumber, FixedU128};
use sp_version::RuntimeVersion;
use crate::cli::{
bridge,
encode_call::{Call, CliEncodeCall},
encode_message, CliChain,
encode_call::{self, Call, CliEncodeCall},
encode_message,
send_message::{self, DispatchFeePayment},
CliChain,
};
/// Weight of the `system::remark` call at Polkadot.
@@ -33,21 +36,16 @@ use crate::cli::{
/// calls in the future. But since it is used only in tests (and on test chains), this is ok.
pub(crate) const SYSTEM_REMARK_CALL_WEIGHT: Weight = 2 * 1_345_000;
/// Id of Polkadot token that is used to fetch token price.
pub(crate) const TOKEN_ID: &str = "polkadot";
impl CliEncodeCall for Polkadot {
fn max_extrinsic_size() -> u32 {
bp_polkadot::max_extrinsic_size()
}
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
fn encode_call(call: &Call) -> anyhow::Result<EncodedOrDecodedCall<Self::Call>> {
Ok(match call {
Call::Raw { data } => EncodedOrDecodedCall::Encoded(data.0.clone()),
Call::Remark { remark_payload, .. } => relay_polkadot_client::runtime::Call::System(
relay_polkadot_client::runtime::SystemCall::remark(
remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
),
),
)
.into(),
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
match *bridge_instance_index {
bridge::POLKADOT_TO_KUSAMA_INDEX => {
@@ -57,6 +55,7 @@ impl CliEncodeCall for Polkadot {
lane.0, payload, fee.0,
),
)
.into()
},
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
@@ -67,13 +66,11 @@ impl CliEncodeCall for Polkadot {
})
}
fn get_dispatch_info(
call: &relay_polkadot_client::runtime::Call,
) -> anyhow::Result<DispatchInfo> {
fn get_dispatch_info(call: &EncodedOrDecodedCall<Self::Call>) -> anyhow::Result<DispatchInfo> {
match *call {
relay_polkadot_client::runtime::Call::System(
EncodedOrDecodedCall::Decoded(relay_polkadot_client::runtime::Call::System(
relay_polkadot_client::runtime::SystemCall::remark(_),
) => Ok(DispatchInfo {
)) => Ok(DispatchInfo {
weight: crate::chains::polkadot::SYSTEM_REMARK_CALL_WEIGHT,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
@@ -87,30 +84,52 @@ impl CliChain for Polkadot {
const RUNTIME_VERSION: RuntimeVersion = bp_polkadot::VERSION;
type KeyPair = sp_core::sr25519::Pair;
type MessagePayload = ();
type MessagePayload = MessagePayload<
bp_polkadot::AccountId,
bp_kusama::AccountPublic,
bp_kusama::Signature,
Vec<u8>,
>;
fn ss58_format() -> u16 {
42
}
fn max_extrinsic_weight() -> Weight {
bp_polkadot::max_extrinsic_weight()
sp_core::crypto::Ss58AddressFormat::from(
sp_core::crypto::Ss58AddressFormatRegistry::PolkadotAccount,
)
.into()
}
fn encode_message(
_message: encode_message::MessagePayload,
message: encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload> {
anyhow::bail!("Sending messages from Polkadot is not yet supported.")
match message {
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
.map_err(|e| anyhow!("Failed to decode Polkadot's MessagePayload: {:?}", e)),
encode_message::MessagePayload::Call { mut call, mut sender, dispatch_weight } => {
type Source = Polkadot;
type Target = relay_kusama_client::Kusama;
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::POLKADOT_TO_KUSAMA_INDEX,
);
let call = Target::encode_call(&call)?;
let dispatch_weight = dispatch_weight.map(Ok).unwrap_or_else(|| {
Err(anyhow::format_err!(
"Please specify dispatch weight of the encoded Kusama call"
))
})?;
Ok(send_message::message_payload(
spec_version,
dispatch_weight,
origin,
&call,
DispatchFeePayment::AtSourceChain,
))
},
}
}
}
/// Storage key and initial value of Kusama -> Polkadot conversion rate.
pub(crate) fn kusama_to_polkadot_conversion_rate_params() -> (StorageKey, FixedU128) {
(
bp_runtime::storage_parameter_key(
bp_polkadot::KUSAMA_TO_POLKADOT_CONVERSION_RATE_PARAMETER_NAME,
),
// starting relay before this parameter will be set to some value may cause troubles
FixedU128::from_inner(FixedU128::DIV),
)
}
@@ -16,95 +16,52 @@
//! Polkadot-to-Kusama headers sync entrypoint.
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_header_chain::justification::GrandpaJustification;
use relay_kusama_client::{Kusama, SigningParams as KusamaSigningParams};
use relay_polkadot_client::{Polkadot, SyncHeader as PolkadotSyncHeader};
use relay_substrate_client::{Client, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use substrate_relay_helper::finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
};
use async_trait::async_trait;
use relay_kusama_client::Kusama;
use substrate_relay_helper::{finality_pipeline::SubstrateFinalitySyncPipeline, TransactionParams};
/// Maximal saturating difference between `balance(now)` and `balance(now-24h)` to treat
/// relay as gone wild.
///
/// Actual value, returned by `maximal_balance_decrease_per_day_is_sane` test is approximately 0.001
/// KSM, but let's round up to 0.1 KSM here.
pub(crate) const MAXIMAL_BALANCE_DECREASE_PER_DAY: bp_polkadot::Balance = 100_000_000_000;
/// Polkadot-to-Kusama finality sync pipeline.
pub(crate) type FinalityPipelinePolkadotFinalityToKusama =
SubstrateFinalityToSubstrate<Polkadot, Kusama, KusamaSigningParams>;
/// KSM, and initial value of this constant was rounded up to 0.1 KSM. But for actual Kusama <>
/// Polkadot deployment we'll be using the same account for delivering finality (free for mandatory
/// headers) and messages. It means that we can't predict maximal loss. But to protect funds against
/// relay/deployment issues, let's limit it so something that is much larger than this estimation -
/// e.g. to 2 KSM.
// TODO: https://github.com/paritytech/parity-bridges-common/issues/1307
pub(crate) const MAXIMAL_BALANCE_DECREASE_PER_DAY: bp_kusama::Balance = 2 * 1_000_000_000_000;
/// Description of Polkadot -> Kusama finalized headers bridge.
#[derive(Clone, Debug)]
pub(crate) struct PolkadotFinalityToKusama {
finality_pipeline: FinalityPipelinePolkadotFinalityToKusama,
}
impl PolkadotFinalityToKusama {
pub fn new(target_client: Client<Kusama>, target_sign: KusamaSigningParams) -> Self {
Self {
finality_pipeline: FinalityPipelinePolkadotFinalityToKusama::new(
target_client,
target_sign,
),
}
}
}
pub struct PolkadotFinalityToKusama;
substrate_relay_helper::generate_mocked_submit_finality_proof_call_builder!(
PolkadotFinalityToKusama,
PolkadotFinalityToKusamaCallBuilder,
relay_kusama_client::runtime::Call::BridgePolkadotGrandpa,
relay_kusama_client::runtime::BridgePolkadotGrandpaCall::submit_finality_proof
);
#[async_trait]
impl SubstrateFinalitySyncPipeline for PolkadotFinalityToKusama {
type FinalitySyncPipeline = FinalityPipelinePolkadotFinalityToKusama;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_polkadot::BEST_FINALIZED_POLKADOT_HEADER_METHOD;
type SourceChain = relay_polkadot_client::Polkadot;
type TargetChain = Kusama;
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(params)
}
type SubmitFinalityProofCallBuilder = PolkadotFinalityToKusamaCallBuilder;
type TransactionSignScheme = Kusama;
fn start_relay_guards(&self) {
relay_substrate_client::guard::abort_on_spec_version_change(
self.finality_pipeline.target_client.clone(),
bp_kusama::VERSION.spec_version,
);
relay_substrate_client::guard::abort_when_account_balance_decreased(
self.finality_pipeline.target_client.clone(),
self.transactions_author(),
async fn start_relay_guards(
target_client: &relay_substrate_client::Client<Kusama>,
transaction_params: &TransactionParams<sp_core::sr25519::Pair>,
enable_version_guard: bool,
) -> relay_substrate_client::Result<()> {
substrate_relay_helper::finality_guards::start::<Kusama, Kusama>(
target_client,
transaction_params,
enable_version_guard,
MAXIMAL_BALANCE_DECREASE_PER_DAY,
);
}
fn transactions_author(&self) -> bp_kusama::AccountId {
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
}
fn make_submit_finality_proof_transaction(
&self,
era: bp_runtime::TransactionEraOf<Kusama>,
transaction_nonce: bp_runtime::IndexOf<Kusama>,
header: PolkadotSyncHeader,
proof: GrandpaJustification<bp_polkadot::Header>,
) -> Bytes {
let call = relay_kusama_client::runtime::Call::BridgePolkadotGrandpa(
relay_kusama_client::runtime::BridgePolkadotGrandpaCall::submit_finality_proof(
Box::new(header.into_inner()),
proof,
),
);
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
let transaction = Kusama::sign_transaction(
genesis_hash,
&self.finality_pipeline.target_sign,
era,
UnsignedTransaction::new(call, transaction_nonce),
);
Bytes(transaction.encode())
)
.await
}
}
@@ -16,315 +16,63 @@
//! Polkadot-to-Kusama messages sync entrypoint.
use std::ops::RangeInclusive;
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_messages::MessageNonce;
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use frame_support::weights::Weight;
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
use relay_kusama_client::{
HeaderId as KusamaHeaderId, Kusama, SigningParams as KusamaSigningParams,
};
use relay_polkadot_client::{
HeaderId as PolkadotHeaderId, Polkadot, SigningParams as PolkadotSigningParams,
};
use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction};
use substrate_relay_helper::{
messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
},
messages_source::SubstrateMessagesSource,
messages_target::SubstrateMessagesTarget,
STALL_TIMEOUT,
};
use messages_relay::relay_strategy::MixStrategy;
use relay_kusama_client::Kusama;
use relay_polkadot_client::Polkadot;
use substrate_relay_helper::messages_lane::SubstrateMessageLane;
/// Polkadot-to-Kusama message lane.
pub type MessageLanePolkadotMessagesToKusama =
SubstrateMessageLaneToSubstrate<Polkadot, PolkadotSigningParams, Kusama, KusamaSigningParams>;
#[derive(Clone)]
pub struct PolkadotMessagesToKusama {
message_lane: MessageLanePolkadotMessagesToKusama,
}
/// Description of Polkadot -> Kusama messages bridge.
#[derive(Clone, Debug)]
pub struct PolkadotMessagesToKusama;
substrate_relay_helper::generate_mocked_receive_message_proof_call_builder!(
PolkadotMessagesToKusama,
PolkadotMessagesToKusamaReceiveMessagesProofCallBuilder,
relay_kusama_client::runtime::Call::BridgePolkadotMessages,
relay_kusama_client::runtime::BridgePolkadotMessagesCall::receive_messages_proof
);
substrate_relay_helper::generate_mocked_receive_message_delivery_proof_call_builder!(
PolkadotMessagesToKusama,
PolkadotMessagesToKusamaReceiveMessagesDeliveryProofCallBuilder,
relay_polkadot_client::runtime::Call::BridgeKusamaMessages,
relay_polkadot_client::runtime::BridgeKusamaMessagesCall::receive_messages_delivery_proof
);
substrate_relay_helper::generate_mocked_update_conversion_rate_call_builder!(
Polkadot,
PolkadotMessagesToKusamaUpdateConversionRateCallBuilder,
relay_polkadot_client::runtime::Call::BridgeKusamaMessages,
relay_polkadot_client::runtime::BridgeKusamaMessagesCall::update_pallet_parameter,
relay_polkadot_client::runtime::BridgeKusamaMessagesParameter::KusamaToPolkadotConversionRate
);
impl SubstrateMessageLane for PolkadotMessagesToKusama {
type MessageLane = MessageLanePolkadotMessagesToKusama;
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str =
bp_kusama::TO_KUSAMA_MESSAGE_DETAILS_METHOD;
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
bp_kusama::TO_KUSAMA_LATEST_GENERATED_NONCE_METHOD;
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_kusama::TO_KUSAMA_LATEST_RECEIVED_NONCE_METHOD;
const SOURCE_TO_TARGET_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> =
Some(bp_kusama::POLKADOT_TO_KUSAMA_CONVERSION_RATE_PARAMETER_NAME);
const TARGET_TO_SOURCE_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> =
Some(bp_polkadot::KUSAMA_TO_POLKADOT_CONVERSION_RATE_PARAMETER_NAME);
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_polkadot::FROM_POLKADOT_LATEST_RECEIVED_NONCE_METHOD;
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
bp_polkadot::FROM_POLKADOT_LATEST_CONFIRMED_NONCE_METHOD;
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str =
bp_polkadot::FROM_POLKADOT_UNREWARDED_RELAYERS_STATE;
const SOURCE_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str> =
Some(bp_kusama::POLKADOT_FEE_MULTIPLIER_PARAMETER_NAME);
const TARGET_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str> =
Some(bp_polkadot::KUSAMA_FEE_MULTIPLIER_PARAMETER_NAME);
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_polkadot::BEST_FINALIZED_POLKADOT_HEADER_METHOD;
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str =
bp_kusama::BEST_FINALIZED_KUSAMA_HEADER_METHOD;
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str =
bp_polkadot::WITH_KUSAMA_MESSAGES_PALLET_NAME;
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str =
bp_kusama::WITH_POLKADOT_MESSAGES_PALLET_NAME;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
bp_kusama::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
const AT_SOURCE_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str> =
Some(bp_polkadot::TRANSACTION_PAYMENT_PALLET_NAME);
const AT_TARGET_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str> =
Some(bp_kusama::TRANSACTION_PAYMENT_PALLET_NAME);
type SourceChain = Polkadot;
type TargetChain = Kusama;
fn source_transactions_author(&self) -> bp_polkadot::AccountId {
(*self.message_lane.source_sign.public().as_array_ref()).into()
}
type SourceTransactionSignScheme = Polkadot;
type TargetTransactionSignScheme = Kusama;
fn make_messages_receiving_proof_transaction(
&self,
best_block_id: PolkadotHeaderId,
transaction_nonce: bp_runtime::IndexOf<Polkadot>,
_generated_at_block: KusamaHeaderId,
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
) -> Bytes {
let (relayers_state, proof) = proof;
let call = relay_polkadot_client::runtime::Call::BridgeKusamaMessages(
relay_polkadot_client::runtime::BridgeKusamaMessagesCall::receive_messages_delivery_proof(
proof,
relayers_state,
),
);
let genesis_hash = *self.message_lane.source_client.genesis_hash();
let transaction = Polkadot::sign_transaction(
genesis_hash,
&self.message_lane.source_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.source_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Kusama -> Polkadot confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
bp_polkadot::max_extrinsic_weight(),
transaction.encode().len(),
bp_polkadot::max_extrinsic_size(),
);
Bytes(transaction.encode())
}
type ReceiveMessagesProofCallBuilder = PolkadotMessagesToKusamaReceiveMessagesProofCallBuilder;
type ReceiveMessagesDeliveryProofCallBuilder =
PolkadotMessagesToKusamaReceiveMessagesDeliveryProofCallBuilder;
fn target_transactions_author(&self) -> bp_kusama::AccountId {
(*self.message_lane.target_sign.public().as_array_ref()).into()
}
type TargetToSourceChainConversionRateUpdateBuilder =
PolkadotMessagesToKusamaUpdateConversionRateCallBuilder;
fn make_messages_delivery_transaction(
&self,
best_block_id: KusamaHeaderId,
transaction_nonce: bp_runtime::IndexOf<Kusama>,
_generated_at_header: PolkadotHeaderId,
_nonces: RangeInclusive<MessageNonce>,
proof: <Self::MessageLane as MessageLane>::MessagesProof,
) -> Bytes {
let (dispatch_weight, proof) = proof;
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
let messages_count = nonces_end - nonces_start + 1;
let call = relay_kusama_client::runtime::Call::BridgePolkadotMessages(
relay_kusama_client::runtime::BridgePolkadotMessagesCall::receive_messages_proof(
self.message_lane.relayer_id_at_source.clone(),
proof,
messages_count as _,
dispatch_weight,
),
);
let genesis_hash = *self.message_lane.target_client.genesis_hash();
let transaction = Kusama::sign_transaction(
genesis_hash,
&self.message_lane.target_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.target_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Polkadot -> Kusama delivery transaction. Weight: <unknown>/{}, size: {}/{}",
bp_kusama::max_extrinsic_weight(),
transaction.encode().len(),
bp_kusama::max_extrinsic_size(),
);
Bytes(transaction.encode())
}
}
/// Polkadot node as messages source.
type PolkadotSourceClient = SubstrateMessagesSource<PolkadotMessagesToKusama>;
/// Kusama node as messages target.
type KusamaTargetClient = SubstrateMessagesTarget<PolkadotMessagesToKusama>;
/// Run Polkadot-to-Kusama messages sync.
pub async fn run(
params: MessagesRelayParams<
Polkadot,
PolkadotSigningParams,
Kusama,
KusamaSigningParams,
MixStrategy,
>,
) -> anyhow::Result<()> {
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
params.source_transactions_mortality,
params.target_transactions_mortality,
Polkadot::AVERAGE_BLOCK_INTERVAL,
Kusama::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
);
let relayer_id_at_polkadot = (*params.source_sign.public().as_array_ref()).into();
let lane_id = params.lane_id;
let source_client = params.source_client;
let target_client = params.target_client;
let lane = PolkadotMessagesToKusama {
message_lane: SubstrateMessageLaneToSubstrate {
source_client: source_client.clone(),
source_sign: params.source_sign,
source_transactions_mortality: params.source_transactions_mortality,
target_client: target_client.clone(),
target_sign: params.target_sign,
target_transactions_mortality: params.target_transactions_mortality,
relayer_id_at_source: relayer_id_at_polkadot,
},
};
// 2/3 is reserved for proofs and tx overhead
let max_messages_size_in_single_batch = bp_kusama::max_extrinsic_size() / 3;
// we don't know exact weights of the Kusama runtime. So to guess weights we'll be using
// weights from Rialto and then simply dividing it by x2.
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
select_delivery_transaction_limits::<
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>,
>(
bp_kusama::max_extrinsic_weight(),
bp_kusama::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
);
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
(max_messages_in_single_batch / 2, max_messages_weight_in_single_batch / 2);
log::info!(
target: "bridge",
"Starting Polkadot -> Kusama messages relay.\n\t\
Polkadot relayer account id: {:?}\n\t\
Max messages in single transaction: {}\n\t\
Max messages size in single transaction: {}\n\t\
Max messages weight in single transaction: {}\n\t\
Tx mortality: {:?}/{:?}\n\t\
Stall timeout: {:?}",
lane.message_lane.relayer_id_at_source,
max_messages_in_single_batch,
max_messages_size_in_single_batch,
max_messages_weight_in_single_batch,
params.source_transactions_mortality,
params.target_transactions_mortality,
stall_timeout,
);
let standalone_metrics = params
.standalone_metrics
.map(Ok)
.unwrap_or_else(|| standalone_metrics(source_client.clone(), target_client.clone()))?;
messages_relay::message_lane_loop::run(
messages_relay::message_lane_loop::Params {
lane: lane_id,
source_tick: Polkadot::AVERAGE_BLOCK_INTERVAL,
target_tick: Kusama::AVERAGE_BLOCK_INTERVAL,
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
stall_timeout,
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
max_unrewarded_relayer_entries_at_target:
bp_kusama::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
max_unconfirmed_nonces_at_target:
bp_kusama::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
max_messages_in_single_batch,
max_messages_weight_in_single_batch,
max_messages_size_in_single_batch,
relay_strategy: params.relay_strategy,
},
},
PolkadotSourceClient::new(
source_client.clone(),
lane.clone(),
lane_id,
params.target_to_source_headers_relay,
),
KusamaTargetClient::new(
target_client,
lane,
lane_id,
standalone_metrics.clone(),
params.source_to_target_headers_relay,
),
standalone_metrics.register_and_spawn(params.metrics_params)?,
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Create standalone metrics for the Polkadot -> Kusama messages loop.
pub(crate) fn standalone_metrics(
source_client: Client<Polkadot>,
target_client: Client<Kusama>,
) -> anyhow::Result<StandaloneMessagesMetrics<Polkadot, Kusama>> {
substrate_relay_helper::messages_lane::standalone_metrics(
source_client,
target_client,
Some(crate::chains::polkadot::TOKEN_ID),
Some(crate::chains::kusama::TOKEN_ID),
Some(crate::chains::kusama::polkadot_to_kusama_conversion_rate_params()),
Some(crate::chains::polkadot::kusama_to_polkadot_conversion_rate_params()),
)
}
/// Update Kusama -> Polkadot conversion rate, stored in Polkadot runtime storage.
pub(crate) async fn update_kusama_to_polkadot_conversion_rate(
client: Client<Polkadot>,
signer: <Polkadot as TransactionSignScheme>::AccountKeyPair,
updated_rate: f64,
) -> anyhow::Result<()> {
let genesis_hash = *client.genesis_hash();
let signer_id = (*signer.public().as_array_ref()).into();
client
.submit_signed_extrinsic(signer_id, move |_, transaction_nonce| {
Bytes(
Polkadot::sign_transaction(
genesis_hash,
&signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
relay_polkadot_client::runtime::Call::BridgeKusamaMessages(
relay_polkadot_client::runtime::BridgeKusamaMessagesCall::update_pallet_parameter(
relay_polkadot_client::runtime::BridgeKusamaMessagesParameter::KusamaToPolkadotConversionRate(
sp_runtime::FixedU128::from_float(updated_rate),
)
)
),
transaction_nonce,
),
)
.encode(),
)
})
.await
.map(drop)
.map_err(|err| anyhow::format_err!("{:?}", err))
type RelayStrategy = MixStrategy;
}
@@ -25,37 +25,27 @@ use crate::cli::{
};
use anyhow::anyhow;
use bp_message_dispatch::{CallOrigin, MessagePayload};
use bp_runtime::EncodedOrDecodedCall;
use codec::Decode;
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
use frame_support::weights::{DispatchInfo, GetDispatchInfo};
use relay_rialto_client::Rialto;
use sp_core::storage::StorageKey;
use sp_runtime::FixedU128;
use sp_version::RuntimeVersion;
// Millau/Rialto tokens have no any real value, so the conversion rate we use is always 1:1. But we
// want to test our code that is intended to work with real-value chains. So to keep it close to
// 1:1, we'll be treating Rialto as BTC and Millau as wBTC (only in relayer).
/// The identifier of token, which value is associated with Rialto token value by relayer.
pub(crate) const ASSOCIATED_TOKEN_ID: &str = crate::chains::polkadot::TOKEN_ID;
impl CliEncodeCall for Rialto {
fn max_extrinsic_size() -> u32 {
bp_rialto::max_extrinsic_size()
}
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
fn encode_call(call: &Call) -> anyhow::Result<EncodedOrDecodedCall<Self::Call>> {
Ok(match call {
Call::Raw { data } => Decode::decode(&mut &*data.0)?,
Call::Raw { data } => Self::Call::decode(&mut &*data.0)?.into(),
Call::Remark { remark_payload, .. } =>
rialto_runtime::Call::System(rialto_runtime::SystemCall::remark {
remark: remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
}),
})
.into(),
Call::Transfer { recipient, amount } =>
rialto_runtime::Call::Balances(rialto_runtime::BalancesCall::transfer {
dest: recipient.raw_id().into(),
value: amount.0,
}),
})
.into(),
Call::BridgeSendMessage { lane, payload, fee, bridge_instance_index } =>
match *bridge_instance_index {
bridge::RIALTO_TO_MILLAU_INDEX => {
@@ -67,6 +57,7 @@ impl CliEncodeCall for Rialto {
delivery_and_dispatch_fee: fee.0,
},
)
.into()
},
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
@@ -76,8 +67,8 @@ impl CliEncodeCall for Rialto {
})
}
fn get_dispatch_info(call: &rialto_runtime::Call) -> anyhow::Result<DispatchInfo> {
Ok(call.get_dispatch_info())
fn get_dispatch_info(call: &EncodedOrDecodedCall<Self::Call>) -> anyhow::Result<DispatchInfo> {
Ok(call.to_decoded()?.get_dispatch_info())
}
}
@@ -96,17 +87,13 @@ impl CliChain for Rialto {
rialto_runtime::SS58Prefix::get() as u16
}
fn max_extrinsic_weight() -> Weight {
bp_rialto::max_extrinsic_weight()
}
fn encode_message(
message: encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload> {
match message {
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
.map_err(|e| anyhow!("Failed to decode Rialto's MessagePayload: {:?}", e)),
encode_message::MessagePayload::Call { mut call, mut sender } => {
encode_message::MessagePayload::Call { mut call, mut sender, dispatch_weight } => {
type Source = Rialto;
type Target = relay_millau_client::Millau;
@@ -118,11 +105,13 @@ impl CliChain for Rialto {
bridge::RIALTO_TO_MILLAU_INDEX,
);
let call = Target::encode_call(&call)?;
let weight = call.get_dispatch_info().weight;
let dispatch_weight = dispatch_weight.map(Ok).unwrap_or_else(|| {
call.to_decoded().map(|call| call.get_dispatch_info().weight)
})?;
Ok(send_message::message_payload(
spec_version,
weight,
dispatch_weight,
origin,
&call,
DispatchFeePayment::AtSourceChain,
@@ -131,11 +120,3 @@ impl CliChain for Rialto {
}
}
}
/// Storage key and initial value of Millau -> Rialto conversion rate.
pub(crate) fn millau_to_rialto_conversion_rate_params() -> (StorageKey, FixedU128) {
(
StorageKey(rialto_runtime::millau_messages::MillauToRialtoConversionRate::key().to_vec()),
rialto_runtime::millau_messages::INITIAL_MILLAU_TO_RIALTO_CONVERSION_RATE,
)
}
@@ -16,73 +16,22 @@
//! Rialto-to-Millau headers sync entrypoint.
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_header_chain::justification::GrandpaJustification;
use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
use relay_rialto_client::{Rialto, SyncHeader as RialtoSyncHeader};
use relay_substrate_client::{Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use substrate_relay_helper::finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
DirectSubmitFinalityProofCallBuilder, SubstrateFinalitySyncPipeline,
};
/// Rialto-to-Millau finality sync pipeline.
pub(crate) type FinalityPipelineRialtoFinalityToMillau =
SubstrateFinalityToSubstrate<Rialto, Millau, MillauSigningParams>;
/// Description of Millau -> Rialto finalized headers bridge.
#[derive(Clone, Debug)]
pub struct RialtoFinalityToMillau {
finality_pipeline: FinalityPipelineRialtoFinalityToMillau,
}
impl RialtoFinalityToMillau {
pub fn new(target_client: Client<Millau>, target_sign: MillauSigningParams) -> Self {
Self {
finality_pipeline: FinalityPipelineRialtoFinalityToMillau::new(
target_client,
target_sign,
),
}
}
}
pub struct RialtoFinalityToMillau;
impl SubstrateFinalitySyncPipeline for RialtoFinalityToMillau {
type FinalitySyncPipeline = FinalityPipelineRialtoFinalityToMillau;
type SourceChain = relay_rialto_client::Rialto;
type TargetChain = relay_millau_client::Millau;
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.finality_pipeline.target_sign.public().as_array_ref()).into()
}
fn make_submit_finality_proof_transaction(
&self,
era: bp_runtime::TransactionEraOf<Millau>,
transaction_nonce: IndexOf<Millau>,
header: RialtoSyncHeader,
proof: GrandpaJustification<bp_rialto::Header>,
) -> Bytes {
let call = millau_runtime::BridgeGrandpaCall::<
millau_runtime::Runtime,
millau_runtime::RialtoGrandpaInstance,
>::submit_finality_proof {
finality_target: Box::new(header.into_inner()),
justification: proof,
}
.into();
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
let transaction = Millau::sign_transaction(
genesis_hash,
&self.finality_pipeline.target_sign,
era,
UnsignedTransaction::new(call, transaction_nonce),
);
Bytes(transaction.encode())
}
type SubmitFinalityProofCallBuilder = DirectSubmitFinalityProofCallBuilder<
Self,
millau_runtime::Runtime,
millau_runtime::RialtoGrandpaInstance,
>;
type TransactionSignScheme = relay_millau_client::Millau;
}
@@ -16,309 +16,55 @@
//! Rialto-to-Millau messages sync entrypoint.
use std::ops::RangeInclusive;
use codec::Encode;
use frame_support::dispatch::GetDispatchInfo;
use sp_core::{Bytes, Pair};
use bp_messages::MessageNonce;
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use frame_support::weights::Weight;
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
use relay_millau_client::{
HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams,
};
use relay_rialto_client::{
HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams,
};
use relay_substrate_client::{Chain, Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use substrate_relay_helper::{
messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
},
messages_source::SubstrateMessagesSource,
messages_target::SubstrateMessagesTarget,
STALL_TIMEOUT,
use messages_relay::relay_strategy::MixStrategy;
use relay_millau_client::Millau;
use relay_rialto_client::Rialto;
use substrate_relay_helper::messages_lane::{
DirectReceiveMessagesDeliveryProofCallBuilder, DirectReceiveMessagesProofCallBuilder,
SubstrateMessageLane,
};
/// Rialto-to-Millau message lane.
pub type MessageLaneRialtoMessagesToMillau =
SubstrateMessageLaneToSubstrate<Rialto, RialtoSigningParams, Millau, MillauSigningParams>;
#[derive(Clone)]
pub struct RialtoMessagesToMillau {
message_lane: MessageLaneRialtoMessagesToMillau,
}
/// Description of Rialto -> Millau messages bridge.
#[derive(Clone, Debug)]
pub struct RialtoMessagesToMillau;
substrate_relay_helper::generate_direct_update_conversion_rate_call_builder!(
Rialto,
RialtoMessagesToMillauUpdateConversionRateCallBuilder,
rialto_runtime::Runtime,
rialto_runtime::WithMillauMessagesInstance,
rialto_runtime::millau_messages::RialtoToMillauMessagesParameter::MillauToRialtoConversionRate
);
impl SubstrateMessageLane for RialtoMessagesToMillau {
type MessageLane = MessageLaneRialtoMessagesToMillau;
const SOURCE_TO_TARGET_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> =
Some(bp_millau::RIALTO_TO_MILLAU_CONVERSION_RATE_PARAMETER_NAME);
const TARGET_TO_SOURCE_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> =
Some(bp_rialto::MILLAU_TO_RIALTO_CONVERSION_RATE_PARAMETER_NAME);
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;
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_rialto::WITH_MILLAU_MESSAGES_PALLET_NAME;
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_millau::WITH_RIALTO_MESSAGES_PALLET_NAME;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
bp_millau::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
const SOURCE_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str> = None;
const TARGET_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str> = None;
const AT_SOURCE_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str> = None;
const AT_TARGET_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str> = None;
type SourceChain = Rialto;
type TargetChain = Millau;
fn source_transactions_author(&self) -> bp_rialto::AccountId {
(*self.message_lane.source_sign.public().as_array_ref()).into()
}
type SourceTransactionSignScheme = Rialto;
type TargetTransactionSignScheme = Millau;
fn make_messages_receiving_proof_transaction(
&self,
best_block_id: RialtoHeaderId,
transaction_nonce: IndexOf<Rialto>,
_generated_at_block: MillauHeaderId,
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
) -> Bytes {
let (relayers_state, proof) = proof;
let call: rialto_runtime::Call =
rialto_runtime::MessagesCall::receive_messages_delivery_proof { proof, relayers_state }
.into();
let call_weight = call.get_dispatch_info().weight;
let genesis_hash = *self.message_lane.source_client.genesis_hash();
let transaction = Rialto::sign_transaction(
genesis_hash,
&self.message_lane.source_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.source_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Millau -> Rialto confirmation transaction. Weight: {}/{}, size: {}/{}",
call_weight,
bp_rialto::max_extrinsic_weight(),
transaction.encode().len(),
bp_rialto::max_extrinsic_size(),
);
Bytes(transaction.encode())
}
type ReceiveMessagesProofCallBuilder = DirectReceiveMessagesProofCallBuilder<
Self,
millau_runtime::Runtime,
millau_runtime::WithRialtoMessagesInstance,
>;
type ReceiveMessagesDeliveryProofCallBuilder = DirectReceiveMessagesDeliveryProofCallBuilder<
Self,
rialto_runtime::Runtime,
rialto_runtime::WithMillauMessagesInstance,
>;
fn target_transactions_author(&self) -> bp_millau::AccountId {
(*self.message_lane.target_sign.public().as_array_ref()).into()
}
type TargetToSourceChainConversionRateUpdateBuilder =
RialtoMessagesToMillauUpdateConversionRateCallBuilder;
fn make_messages_delivery_transaction(
&self,
best_block_id: MillauHeaderId,
transaction_nonce: IndexOf<Millau>,
_generated_at_header: RialtoHeaderId,
_nonces: RangeInclusive<MessageNonce>,
proof: <Self::MessageLane as MessageLane>::MessagesProof,
) -> Bytes {
let (dispatch_weight, proof) = proof;
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
let messages_count = nonces_end - nonces_start + 1;
let call: millau_runtime::Call = millau_runtime::MessagesCall::receive_messages_proof {
relayer_id_at_bridged_chain: self.message_lane.relayer_id_at_source.clone(),
proof,
messages_count: messages_count as _,
dispatch_weight,
}
.into();
let call_weight = call.get_dispatch_info().weight;
let genesis_hash = *self.message_lane.target_client.genesis_hash();
let transaction = Millau::sign_transaction(
genesis_hash,
&self.message_lane.target_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.target_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Rialto -> Millau delivery transaction. Weight: {}/{}, size: {}/{}",
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<RialtoMessagesToMillau>;
/// Millau node as messages target.
type MillauTargetClient = SubstrateMessagesTarget<RialtoMessagesToMillau>;
/// Run Rialto-to-Millau messages sync.
pub async fn run(
params: MessagesRelayParams<
Rialto,
RialtoSigningParams,
Millau,
MillauSigningParams,
MixStrategy,
>,
) -> anyhow::Result<()> {
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
params.source_transactions_mortality,
params.target_transactions_mortality,
Rialto::AVERAGE_BLOCK_INTERVAL,
Millau::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
);
let relayer_id_at_rialto = (*params.source_sign.public().as_array_ref()).into();
let lane_id = params.lane_id;
let source_client = params.source_client;
let target_client = params.target_client;
let lane = RialtoMessagesToMillau {
message_lane: SubstrateMessageLaneToSubstrate {
source_client: source_client.clone(),
source_sign: params.source_sign,
source_transactions_mortality: params.source_transactions_mortality,
target_client: target_client.clone(),
target_sign: params.target_sign,
target_transactions_mortality: params.target_transactions_mortality,
relayer_id_at_source: relayer_id_at_rialto,
},
};
// 2/3 is reserved for proofs and tx overhead
let max_messages_size_in_single_batch = bp_millau::max_extrinsic_size() / 3;
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
select_delivery_transaction_limits::<
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>,
>(
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: {}\n\t\
Tx mortality: {:?}/{:?}\n\t\
Stall timeout: {:?}",
lane.message_lane.relayer_id_at_source,
max_messages_in_single_batch,
max_messages_size_in_single_batch,
max_messages_weight_in_single_batch,
params.source_transactions_mortality,
params.target_transactions_mortality,
stall_timeout,
);
let standalone_metrics = params
.standalone_metrics
.map(Ok)
.unwrap_or_else(|| standalone_metrics(source_client.clone(), target_client.clone()))?;
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,
relay_strategy: params.relay_strategy,
},
},
RialtoSourceClient::new(
source_client.clone(),
lane.clone(),
lane_id,
params.target_to_source_headers_relay,
),
MillauTargetClient::new(
target_client,
lane,
lane_id,
standalone_metrics.clone(),
params.source_to_target_headers_relay,
),
standalone_metrics.register_and_spawn(params.metrics_params)?,
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Create standalone metrics for the Rialto -> Millau messages loop.
pub(crate) fn standalone_metrics(
source_client: Client<Rialto>,
target_client: Client<Millau>,
) -> anyhow::Result<StandaloneMessagesMetrics<Rialto, Millau>> {
substrate_relay_helper::messages_lane::standalone_metrics(
source_client,
target_client,
Some(crate::chains::rialto::ASSOCIATED_TOKEN_ID),
Some(crate::chains::millau::ASSOCIATED_TOKEN_ID),
Some(crate::chains::millau::rialto_to_millau_conversion_rate_params()),
Some(crate::chains::rialto::millau_to_rialto_conversion_rate_params()),
)
}
/// Update Millau -> Rialto conversion rate, stored in Rialto runtime storage.
pub(crate) async fn update_millau_to_rialto_conversion_rate(
client: Client<Rialto>,
signer: <Rialto as TransactionSignScheme>::AccountKeyPair,
updated_rate: f64,
) -> anyhow::Result<()> {
let genesis_hash = *client.genesis_hash();
let signer_id = (*signer.public().as_array_ref()).into();
client
.submit_signed_extrinsic(signer_id, move |_, transaction_nonce| {
Bytes(
Rialto::sign_transaction(
genesis_hash,
&signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
rialto_runtime::MessagesCall::update_pallet_parameter {
parameter: rialto_runtime::millau_messages::RialtoToMillauMessagesParameter::MillauToRialtoConversionRate(
sp_runtime::FixedU128::from_float(updated_rate),
),
}
.into(),
transaction_nonce,
),
)
.encode(),
)
})
.await
.map(drop)
.map_err(|err| anyhow::format_err!("{:?}", err))
type RelayStrategy = MixStrategy;
}
@@ -21,37 +21,37 @@ use crate::cli::{
encode_message, CliChain,
};
use bp_message_dispatch::MessagePayload;
use bp_runtime::EncodedOrDecodedCall;
use codec::Decode;
use frame_support::weights::{DispatchInfo, GetDispatchInfo, Weight};
use frame_support::weights::{DispatchInfo, GetDispatchInfo};
use relay_rialto_parachain_client::RialtoParachain;
use sp_version::RuntimeVersion;
impl CliEncodeCall for RialtoParachain {
fn max_extrinsic_size() -> u32 {
bp_rialto_parachain::max_extrinsic_size()
}
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
fn encode_call(call: &Call) -> anyhow::Result<EncodedOrDecodedCall<Self::Call>> {
Ok(match call {
Call::Raw { data } => Decode::decode(&mut &*data.0)?,
Call::Raw { data } => Self::Call::decode(&mut &*data.0)?.into(),
Call::Remark { remark_payload, .. } => rialto_parachain_runtime::Call::System(
rialto_parachain_runtime::SystemCall::remark {
remark: remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
},
),
)
.into(),
Call::Transfer { recipient, amount } => rialto_parachain_runtime::Call::Balances(
rialto_parachain_runtime::BalancesCall::transfer {
dest: recipient.raw_id().into(),
value: amount.0,
},
),
Call::BridgeSendMessage { .. } =>
anyhow::bail!("Bridge messages are not (yet) supported here",),
)
.into(),
Call::BridgeSendMessage { .. } => {
anyhow::bail!("Bridge messages are not (yet) supported here",)
},
})
}
fn get_dispatch_info(call: &rialto_parachain_runtime::Call) -> anyhow::Result<DispatchInfo> {
Ok(call.get_dispatch_info())
fn get_dispatch_info(call: &EncodedOrDecodedCall<Self::Call>) -> anyhow::Result<DispatchInfo> {
Ok(call.to_decoded()?.get_dispatch_info())
}
}
@@ -70,10 +70,6 @@ impl CliChain for RialtoParachain {
rialto_parachain_runtime::SS58Prefix::get() as u16
}
fn max_extrinsic_weight() -> Weight {
bp_rialto_parachain::max_extrinsic_weight()
}
fn encode_message(
_message: encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload> {
@@ -15,6 +15,8 @@
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use anyhow::anyhow;
use bp_message_dispatch::{CallOrigin, MessagePayload};
use bp_runtime::EncodedOrDecodedCall;
use codec::Decode;
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
use relay_rococo_client::Rococo;
@@ -22,8 +24,10 @@ use sp_version::RuntimeVersion;
use crate::cli::{
bridge,
encode_call::{Call, CliEncodeCall},
encode_message, CliChain,
encode_call::{self, Call, CliEncodeCall},
encode_message,
send_message::{self, DispatchFeePayment},
CliChain,
};
/// Weight of the `system::remark` call at Rococo.
@@ -33,26 +37,25 @@ use crate::cli::{
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> {
fn encode_call(call: &Call) -> anyhow::Result<EncodedOrDecodedCall<Self::Call>> {
Ok(match call {
Call::Raw { data } => EncodedOrDecodedCall::Encoded(data.0.clone()),
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(),
),
),
)
.into(),
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(
relay_rococo_client::runtime::Call::BridgeWococoMessages(
relay_rococo_client::runtime::BridgeWococoMessagesCall::send_message(
lane.0, payload, fee.0,
),
)
.into()
},
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
@@ -63,13 +66,11 @@ impl CliEncodeCall for Rococo {
})
}
fn get_dispatch_info(
call: &relay_rococo_client::runtime::Call,
) -> anyhow::Result<DispatchInfo> {
fn get_dispatch_info(call: &EncodedOrDecodedCall<Self::Call>) -> anyhow::Result<DispatchInfo> {
match *call {
relay_rococo_client::runtime::Call::System(
EncodedOrDecodedCall::Decoded(relay_rococo_client::runtime::Call::System(
relay_rococo_client::runtime::SystemCall::remark(_),
) => Ok(DispatchInfo {
)) => Ok(DispatchInfo {
weight: SYSTEM_REMARK_CALL_WEIGHT,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
@@ -83,19 +84,49 @@ impl CliChain for Rococo {
const RUNTIME_VERSION: RuntimeVersion = bp_rococo::VERSION;
type KeyPair = sp_core::sr25519::Pair;
type MessagePayload = ();
type MessagePayload = MessagePayload<
bp_rococo::AccountId,
bp_wococo::AccountPublic,
bp_wococo::Signature,
Vec<u8>,
>;
fn ss58_format() -> u16 {
42
}
fn max_extrinsic_weight() -> Weight {
bp_wococo::max_extrinsic_weight()
}
fn encode_message(
_message: encode_message::MessagePayload,
message: encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload> {
Err(anyhow!("Sending messages from Rococo is not yet supported."))
match message {
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
.map_err(|e| anyhow!("Failed to decode Rococo's MessagePayload: {:?}", e)),
encode_message::MessagePayload::Call { mut call, mut sender, dispatch_weight } => {
type Source = Rococo;
type Target = relay_wococo_client::Wococo;
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::ROCOCO_TO_WOCOCO_INDEX,
);
let call = Target::encode_call(&call)?;
let dispatch_weight = dispatch_weight.map(Ok).unwrap_or_else(|| {
Err(anyhow::format_err!(
"Please specify dispatch weight of the encoded Wococo call"
))
})?;
Ok(send_message::message_payload(
spec_version,
dispatch_weight,
origin,
&call,
DispatchFeePayment::AtSourceChain,
))
},
}
}
}
@@ -16,89 +16,41 @@
//! Rococo-to-Wococo headers sync entrypoint.
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_header_chain::justification::GrandpaJustification;
use relay_rococo_client::{Rococo, SyncHeader as RococoSyncHeader};
use relay_substrate_client::{Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use relay_wococo_client::{SigningParams as WococoSigningParams, Wococo};
use substrate_relay_helper::finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
};
use crate::chains::wococo_headers_to_rococo::MAXIMAL_BALANCE_DECREASE_PER_DAY;
/// Rococo-to-Wococo finality sync pipeline.
pub(crate) type FinalityPipelineRococoFinalityToWococo =
SubstrateFinalityToSubstrate<Rococo, Wococo, WococoSigningParams>;
use async_trait::async_trait;
use relay_wococo_client::Wococo;
use substrate_relay_helper::{finality_pipeline::SubstrateFinalitySyncPipeline, TransactionParams};
/// Description of Rococo -> Wococo finalized headers bridge.
#[derive(Clone, Debug)]
pub(crate) struct RococoFinalityToWococo {
finality_pipeline: FinalityPipelineRococoFinalityToWococo,
}
impl RococoFinalityToWococo {
pub fn new(target_client: Client<Wococo>, target_sign: WococoSigningParams) -> Self {
Self {
finality_pipeline: FinalityPipelineRococoFinalityToWococo::new(
target_client,
target_sign,
),
}
}
}
pub struct RococoFinalityToWococo;
substrate_relay_helper::generate_mocked_submit_finality_proof_call_builder!(
RococoFinalityToWococo,
RococoFinalityToWococoCallBuilder,
relay_wococo_client::runtime::Call::BridgeGrandpaRococo,
relay_wococo_client::runtime::BridgeGrandpaRococoCall::submit_finality_proof
);
#[async_trait]
impl SubstrateFinalitySyncPipeline for RococoFinalityToWococo {
type FinalitySyncPipeline = FinalityPipelineRococoFinalityToWococo;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_rococo::BEST_FINALIZED_ROCOCO_HEADER_METHOD;
type SourceChain = relay_rococo_client::Rococo;
type TargetChain = Wococo;
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(params)
}
type SubmitFinalityProofCallBuilder = RococoFinalityToWococoCallBuilder;
type TransactionSignScheme = Wococo;
fn start_relay_guards(&self) {
relay_substrate_client::guard::abort_on_spec_version_change(
self.finality_pipeline.target_client.clone(),
bp_wococo::VERSION.spec_version,
);
relay_substrate_client::guard::abort_when_account_balance_decreased(
self.finality_pipeline.target_client.clone(),
self.transactions_author(),
async fn start_relay_guards(
target_client: &relay_substrate_client::Client<Wococo>,
transaction_params: &TransactionParams<sp_core::sr25519::Pair>,
enable_version_guard: bool,
) -> relay_substrate_client::Result<()> {
substrate_relay_helper::finality_guards::start::<Wococo, Wococo>(
target_client,
transaction_params,
enable_version_guard,
MAXIMAL_BALANCE_DECREASE_PER_DAY,
);
}
fn transactions_author(&self) -> bp_wococo::AccountId {
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
}
fn make_submit_finality_proof_transaction(
&self,
era: bp_runtime::TransactionEraOf<Wococo>,
transaction_nonce: IndexOf<Wococo>,
header: RococoSyncHeader,
proof: GrandpaJustification<bp_rococo::Header>,
) -> Bytes {
let call = relay_wococo_client::runtime::Call::BridgeGrandpaRococo(
relay_wococo_client::runtime::BridgeGrandpaRococoCall::submit_finality_proof(
Box::new(header.into_inner()),
proof,
),
);
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
let transaction = Wococo::sign_transaction(
genesis_hash,
&self.finality_pipeline.target_sign,
era,
UnsignedTransaction::new(call, transaction_nonce),
);
Bytes(transaction.encode())
)
.await
}
}
@@ -16,280 +16,48 @@
//! Rococo-to-Wococo messages sync entrypoint.
use std::ops::RangeInclusive;
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_messages::MessageNonce;
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use frame_support::weights::Weight;
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
use relay_rococo_client::{
HeaderId as RococoHeaderId, Rococo, SigningParams as RococoSigningParams,
};
use relay_substrate_client::{Chain, Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use relay_wococo_client::{
HeaderId as WococoHeaderId, SigningParams as WococoSigningParams, Wococo,
};
use substrate_relay_helper::{
messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
},
messages_source::SubstrateMessagesSource,
messages_target::SubstrateMessagesTarget,
STALL_TIMEOUT,
};
use messages_relay::relay_strategy::MixStrategy;
use relay_rococo_client::Rococo;
use relay_wococo_client::Wococo;
use substrate_relay_helper::messages_lane::SubstrateMessageLane;
/// Rococo-to-Wococo message lane.
pub type MessageLaneRococoMessagesToWococo =
SubstrateMessageLaneToSubstrate<Rococo, RococoSigningParams, Wococo, WococoSigningParams>;
#[derive(Clone)]
pub struct RococoMessagesToWococo {
message_lane: MessageLaneRococoMessagesToWococo,
}
/// Description of Rococo -> Wococo messages bridge.
#[derive(Clone, Debug)]
pub struct RococoMessagesToWococo;
substrate_relay_helper::generate_mocked_receive_message_proof_call_builder!(
RococoMessagesToWococo,
RococoMessagesToWococoReceiveMessagesProofCallBuilder,
relay_wococo_client::runtime::Call::BridgeRococoMessages,
relay_wococo_client::runtime::BridgeRococoMessagesCall::receive_messages_proof
);
substrate_relay_helper::generate_mocked_receive_message_delivery_proof_call_builder!(
RococoMessagesToWococo,
RococoMessagesToWococoReceiveMessagesDeliveryProofCallBuilder,
relay_rococo_client::runtime::Call::BridgeWococoMessages,
relay_rococo_client::runtime::BridgeWococoMessagesCall::receive_messages_delivery_proof
);
impl SubstrateMessageLane for RococoMessagesToWococo {
type MessageLane = MessageLaneRococoMessagesToWococo;
const SOURCE_TO_TARGET_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> = None;
const TARGET_TO_SOURCE_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> = None;
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;
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_rococo::WITH_WOCOCO_MESSAGES_PALLET_NAME;
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_wococo::WITH_ROCOCO_MESSAGES_PALLET_NAME;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
bp_wococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
const SOURCE_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str> = None;
const TARGET_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str> = None;
const AT_SOURCE_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str> = None;
const AT_TARGET_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str> = None;
type SourceChain = Rococo;
type TargetChain = Wococo;
fn source_transactions_author(&self) -> bp_rococo::AccountId {
(*self.message_lane.source_sign.public().as_array_ref()).into()
}
type SourceTransactionSignScheme = Rococo;
type TargetTransactionSignScheme = Wococo;
fn make_messages_receiving_proof_transaction(
&self,
best_block_id: RococoHeaderId,
transaction_nonce: IndexOf<Rococo>,
_generated_at_block: WococoHeaderId,
proof: <Self::MessageLane 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.message_lane.source_client.genesis_hash();
let transaction = Rococo::sign_transaction(
genesis_hash,
&self.message_lane.source_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.source_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Wococo -> Rococo confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
bp_rococo::max_extrinsic_weight(),
transaction.encode().len(),
bp_rococo::max_extrinsic_size(),
);
Bytes(transaction.encode())
}
type ReceiveMessagesProofCallBuilder = RococoMessagesToWococoReceiveMessagesProofCallBuilder;
type ReceiveMessagesDeliveryProofCallBuilder =
RococoMessagesToWococoReceiveMessagesDeliveryProofCallBuilder;
fn target_transactions_author(&self) -> bp_wococo::AccountId {
(*self.message_lane.target_sign.public().as_array_ref()).into()
}
type TargetToSourceChainConversionRateUpdateBuilder = ();
fn make_messages_delivery_transaction(
&self,
best_block_id: WococoHeaderId,
transaction_nonce: IndexOf<Wococo>,
_generated_at_header: RococoHeaderId,
_nonces: RangeInclusive<MessageNonce>,
proof: <Self::MessageLane as MessageLane>::MessagesProof,
) -> Bytes {
let (dispatch_weight, proof) = proof;
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
let messages_count = nonces_end - nonces_start + 1;
let call = relay_wococo_client::runtime::Call::BridgeMessagesRococo(
relay_wococo_client::runtime::BridgeMessagesRococoCall::receive_messages_proof(
self.message_lane.relayer_id_at_source.clone(),
proof,
messages_count as _,
dispatch_weight,
),
);
let genesis_hash = *self.message_lane.target_client.genesis_hash();
let transaction = Wococo::sign_transaction(
genesis_hash,
&self.message_lane.target_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.target_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Rococo -> Wococo delivery transaction. Weight: <unknown>/{}, size: {}/{}",
bp_wococo::max_extrinsic_weight(),
transaction.encode().len(),
bp_wococo::max_extrinsic_size(),
);
Bytes(transaction.encode())
}
}
/// Rococo node as messages source.
type RococoSourceClient = SubstrateMessagesSource<RococoMessagesToWococo>;
/// Wococo node as messages target.
type WococoTargetClient = SubstrateMessagesTarget<RococoMessagesToWococo>;
/// Run Rococo-to-Wococo messages sync.
pub async fn run(
params: MessagesRelayParams<
Rococo,
RococoSigningParams,
Wococo,
WococoSigningParams,
MixStrategy,
>,
) -> anyhow::Result<()> {
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
params.source_transactions_mortality,
params.target_transactions_mortality,
Rococo::AVERAGE_BLOCK_INTERVAL,
Wococo::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
);
let relayer_id_at_rococo = (*params.source_sign.public().as_array_ref()).into();
let lane_id = params.lane_id;
let source_client = params.source_client;
let target_client = params.target_client;
let lane = RococoMessagesToWococo {
message_lane: SubstrateMessageLaneToSubstrate {
source_client: source_client.clone(),
source_sign: params.source_sign,
source_transactions_mortality: params.source_transactions_mortality,
target_client: target_client.clone(),
target_sign: params.target_sign,
target_transactions_mortality: params.target_transactions_mortality,
relayer_id_at_source: relayer_id_at_rococo,
},
};
// 2/3 is reserved for proofs and tx overhead
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: {}\n\t\
Tx mortality: {:?}/{:?}\n\t\
Stall timeout: {:?}",
lane.message_lane.relayer_id_at_source,
max_messages_in_single_batch,
max_messages_size_in_single_batch,
max_messages_weight_in_single_batch,
params.source_transactions_mortality,
params.target_transactions_mortality,
stall_timeout,
);
let standalone_metrics = params
.standalone_metrics
.map(Ok)
.unwrap_or_else(|| standalone_metrics(source_client.clone(), target_client.clone()))?;
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,
relay_strategy: params.relay_strategy,
},
},
RococoSourceClient::new(
source_client.clone(),
lane.clone(),
lane_id,
params.target_to_source_headers_relay,
),
WococoTargetClient::new(
target_client,
lane,
lane_id,
standalone_metrics.clone(),
params.source_to_target_headers_relay,
),
standalone_metrics.register_and_spawn(params.metrics_params)?,
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Create standalone metrics for the Rococo -> Wococo messages loop.
pub(crate) fn standalone_metrics(
source_client: Client<Rococo>,
target_client: Client<Wococo>,
) -> anyhow::Result<StandaloneMessagesMetrics<Rococo, Wococo>> {
substrate_relay_helper::messages_lane::standalone_metrics(
source_client,
target_client,
None,
None,
None,
None,
)
type RelayStrategy = MixStrategy;
}
@@ -18,7 +18,6 @@
use crate::cli::{encode_message, CliChain};
use anyhow::anyhow;
use frame_support::weights::Weight;
use relay_westend_client::Westend;
use sp_version::RuntimeVersion;
@@ -29,11 +28,10 @@ impl CliChain for Westend {
type MessagePayload = ();
fn ss58_format() -> u16 {
42
}
fn max_extrinsic_weight() -> Weight {
0
sp_core::crypto::Ss58AddressFormat::from(
sp_core::crypto::Ss58AddressFormatRegistry::SubstrateAccount,
)
.into()
}
fn encode_message(
@@ -16,78 +16,22 @@
//! Westend-to-Millau headers sync entrypoint.
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_header_chain::justification::GrandpaJustification;
use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
use relay_substrate_client::{Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use relay_westend_client::{SyncHeader as WestendSyncHeader, Westend};
use substrate_relay_helper::finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
DirectSubmitFinalityProofCallBuilder, SubstrateFinalitySyncPipeline,
};
/// Westend-to-Millau finality sync pipeline.
pub(crate) type FinalityPipelineWestendFinalityToMillau =
SubstrateFinalityToSubstrate<Westend, Millau, MillauSigningParams>;
/// Description of Westend -> Millau finalized headers bridge.
#[derive(Clone, Debug)]
pub(crate) struct WestendFinalityToMillau {
finality_pipeline: FinalityPipelineWestendFinalityToMillau,
}
impl WestendFinalityToMillau {
pub fn new(target_client: Client<Millau>, target_sign: MillauSigningParams) -> Self {
Self {
finality_pipeline: FinalityPipelineWestendFinalityToMillau::new(
target_client,
target_sign,
),
}
}
}
pub struct WestendFinalityToMillau;
impl SubstrateFinalitySyncPipeline for WestendFinalityToMillau {
type FinalitySyncPipeline = FinalityPipelineWestendFinalityToMillau;
type SourceChain = relay_westend_client::Westend;
type TargetChain = relay_millau_client::Millau;
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::FinalitySyncPipeline>(params)
}
fn transactions_author(&self) -> bp_millau::AccountId {
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
}
fn make_submit_finality_proof_transaction(
&self,
era: bp_runtime::TransactionEraOf<Millau>,
transaction_nonce: IndexOf<Millau>,
header: WestendSyncHeader,
proof: GrandpaJustification<bp_westend::Header>,
) -> Bytes {
let call = millau_runtime::BridgeGrandpaCall::<
millau_runtime::Runtime,
millau_runtime::WestendGrandpaInstance,
>::submit_finality_proof {
finality_target: Box::new(header.into_inner()),
justification: proof,
}
.into();
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
let transaction = Millau::sign_transaction(
genesis_hash,
&self.finality_pipeline.target_sign,
era,
UnsignedTransaction::new(call, transaction_nonce),
);
Bytes(transaction.encode())
}
type SubmitFinalityProofCallBuilder = DirectSubmitFinalityProofCallBuilder<
Self,
millau_runtime::Runtime,
millau_runtime::WestendGrandpaInstance,
>;
type TransactionSignScheme = relay_millau_client::Millau;
}
@@ -15,38 +15,41 @@
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use anyhow::anyhow;
use bp_message_dispatch::{CallOrigin, MessagePayload};
use bp_runtime::EncodedOrDecodedCall;
use codec::Decode;
use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
use frame_support::weights::{DispatchClass, DispatchInfo, Pays};
use relay_wococo_client::Wococo;
use sp_version::RuntimeVersion;
use crate::cli::{
bridge,
encode_call::{Call, CliEncodeCall},
encode_message, CliChain,
encode_call::{self, Call, CliEncodeCall},
encode_message,
send_message::{self, DispatchFeePayment},
CliChain,
};
impl CliEncodeCall for Wococo {
fn max_extrinsic_size() -> u32 {
bp_wococo::max_extrinsic_size()
}
fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
fn encode_call(call: &Call) -> anyhow::Result<EncodedOrDecodedCall<Self::Call>> {
Ok(match call {
Call::Raw { data } => EncodedOrDecodedCall::Encoded(data.0.clone()),
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(),
),
),
)
.into(),
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(
relay_wococo_client::runtime::Call::BridgeRococoMessages(
relay_wococo_client::runtime::BridgeRococoMessagesCall::send_message(
lane.0, payload, fee.0,
),
)
.into()
},
_ => anyhow::bail!(
"Unsupported target bridge pallet with instance index: {}",
@@ -57,18 +60,16 @@ impl CliEncodeCall for Wococo {
})
}
fn get_dispatch_info(
call: &relay_wococo_client::runtime::Call,
) -> anyhow::Result<DispatchInfo> {
fn get_dispatch_info(call: &EncodedOrDecodedCall<Self::Call>) -> anyhow::Result<DispatchInfo> {
match *call {
relay_wococo_client::runtime::Call::System(
EncodedOrDecodedCall::Decoded(relay_wococo_client::runtime::Call::System(
relay_wococo_client::runtime::SystemCall::remark(_),
) => Ok(DispatchInfo {
)) => Ok(DispatchInfo {
weight: crate::chains::rococo::SYSTEM_REMARK_CALL_WEIGHT,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
}),
_ => anyhow::bail!("Unsupported Rococo call: {:?}", call),
_ => anyhow::bail!("Unsupported Wococo call: {:?}", call),
}
}
}
@@ -77,19 +78,49 @@ impl CliChain for Wococo {
const RUNTIME_VERSION: RuntimeVersion = bp_wococo::VERSION;
type KeyPair = sp_core::sr25519::Pair;
type MessagePayload = ();
type MessagePayload = MessagePayload<
bp_wococo::AccountId,
bp_rococo::AccountPublic,
bp_rococo::Signature,
Vec<u8>,
>;
fn ss58_format() -> u16 {
42
}
fn max_extrinsic_weight() -> Weight {
bp_wococo::max_extrinsic_weight()
}
fn encode_message(
_message: encode_message::MessagePayload,
message: encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload> {
Err(anyhow!("Sending messages from Wococo is not yet supported."))
match message {
encode_message::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
.map_err(|e| anyhow!("Failed to decode Wococo's MessagePayload: {:?}", e)),
encode_message::MessagePayload::Call { mut call, mut sender, dispatch_weight } => {
type Source = Wococo;
type Target = relay_rococo_client::Rococo;
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::WOCOCO_TO_ROCOCO_INDEX,
);
let call = Target::encode_call(&call)?;
let dispatch_weight = dispatch_weight.map(Ok).unwrap_or_else(|| {
Err(anyhow::format_err!(
"Please specify dispatch weight of the encoded Rococo call"
))
})?;
Ok(send_message::message_payload(
spec_version,
dispatch_weight,
origin,
&call,
DispatchFeePayment::AtSourceChain,
))
},
}
}
}
@@ -16,17 +16,9 @@
//! Wococo-to-Rococo headers sync entrypoint.
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_header_chain::justification::GrandpaJustification;
use relay_rococo_client::{Rococo, SigningParams as RococoSigningParams};
use relay_substrate_client::{Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use relay_utils::metrics::MetricsParams;
use relay_wococo_client::{SyncHeader as WococoSyncHeader, Wococo};
use substrate_relay_helper::finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate,
};
use async_trait::async_trait;
use relay_rococo_client::Rococo;
use substrate_relay_helper::{finality_pipeline::SubstrateFinalitySyncPipeline, TransactionParams};
/// Maximal saturating difference between `balance(now)` and `balance(now-24h)` to treat
/// relay as gone wild.
@@ -35,76 +27,36 @@ use substrate_relay_helper::finality_pipeline::{
/// 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 FinalityPipelineWococoFinalityToRococo =
SubstrateFinalityToSubstrate<Wococo, Rococo, RococoSigningParams>;
/// Description of Wococo -> Rococo finalized headers bridge.
#[derive(Clone, Debug)]
pub(crate) struct WococoFinalityToRococo {
finality_pipeline: FinalityPipelineWococoFinalityToRococo,
}
impl WococoFinalityToRococo {
pub fn new(target_client: Client<Rococo>, target_sign: RococoSigningParams) -> Self {
Self {
finality_pipeline: FinalityPipelineWococoFinalityToRococo::new(
target_client,
target_sign,
),
}
}
}
pub struct WococoFinalityToRococo;
substrate_relay_helper::generate_mocked_submit_finality_proof_call_builder!(
WococoFinalityToRococo,
WococoFinalityToRococoCallBuilder,
relay_rococo_client::runtime::Call::BridgeGrandpaWococo,
relay_rococo_client::runtime::BridgeGrandpaWococoCall::submit_finality_proof
);
#[async_trait]
impl SubstrateFinalitySyncPipeline for WococoFinalityToRococo {
type FinalitySyncPipeline = FinalityPipelineWococoFinalityToRococo;
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str =
bp_wococo::BEST_FINALIZED_WOCOCO_HEADER_METHOD;
type SourceChain = relay_wococo_client::Wococo;
type TargetChain = Rococo;
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(params)
}
type SubmitFinalityProofCallBuilder = WococoFinalityToRococoCallBuilder;
type TransactionSignScheme = Rococo;
fn start_relay_guards(&self) {
relay_substrate_client::guard::abort_on_spec_version_change(
self.finality_pipeline.target_client.clone(),
bp_rococo::VERSION.spec_version,
);
relay_substrate_client::guard::abort_when_account_balance_decreased(
self.finality_pipeline.target_client.clone(),
self.transactions_author(),
async fn start_relay_guards(
target_client: &relay_substrate_client::Client<Rococo>,
transaction_params: &TransactionParams<sp_core::sr25519::Pair>,
enable_version_guard: bool,
) -> relay_substrate_client::Result<()> {
substrate_relay_helper::finality_guards::start::<Rococo, Rococo>(
target_client,
transaction_params,
enable_version_guard,
MAXIMAL_BALANCE_DECREASE_PER_DAY,
);
}
fn transactions_author(&self) -> bp_rococo::AccountId {
(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
}
fn make_submit_finality_proof_transaction(
&self,
era: bp_runtime::TransactionEraOf<Rococo>,
transaction_nonce: IndexOf<Rococo>,
header: WococoSyncHeader,
proof: GrandpaJustification<bp_wococo::Header>,
) -> Bytes {
let call = relay_rococo_client::runtime::Call::BridgeGrandpaWococo(
relay_rococo_client::runtime::BridgeGrandpaWococoCall::submit_finality_proof(
Box::new(header.into_inner()),
proof,
),
);
let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
let transaction = Rococo::sign_transaction(
genesis_hash,
&self.finality_pipeline.target_sign,
era,
UnsignedTransaction::new(call, transaction_nonce),
);
Bytes(transaction.encode())
)
.await
}
}
@@ -16,279 +16,49 @@
//! Wococo-to-Rococo messages sync entrypoint.
use std::ops::RangeInclusive;
use codec::Encode;
use sp_core::{Bytes, Pair};
use bp_messages::MessageNonce;
use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
use frame_support::weights::Weight;
use messages_relay::{message_lane::MessageLane, relay_strategy::MixStrategy};
use relay_rococo_client::{
HeaderId as RococoHeaderId, Rococo, SigningParams as RococoSigningParams,
};
use relay_substrate_client::{Chain, Client, IndexOf, TransactionSignScheme, UnsignedTransaction};
use relay_wococo_client::{
HeaderId as WococoHeaderId, SigningParams as WococoSigningParams, Wococo,
};
use substrate_relay_helper::{
messages_lane::{
select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics,
SubstrateMessageLane, SubstrateMessageLaneToSubstrate,
},
messages_source::SubstrateMessagesSource,
messages_target::SubstrateMessagesTarget,
STALL_TIMEOUT,
};
/// Wococo-to-Rococo message lane.
pub type MessageLaneWococoMessagesToRococo =
SubstrateMessageLaneToSubstrate<Wococo, WococoSigningParams, Rococo, RococoSigningParams>;
use messages_relay::relay_strategy::MixStrategy;
use relay_rococo_client::Rococo;
use relay_wococo_client::Wococo;
use substrate_relay_helper::messages_lane::SubstrateMessageLane;
#[derive(Clone)]
pub struct WococoMessagesToRococo {
message_lane: MessageLaneWococoMessagesToRococo,
}
/// Description of Wococo -> Rococo messages bridge.
#[derive(Clone, Debug)]
pub struct WococoMessagesToRococo;
substrate_relay_helper::generate_mocked_receive_message_proof_call_builder!(
WococoMessagesToRococo,
WococoMessagesToRococoReceiveMessagesProofCallBuilder,
relay_rococo_client::runtime::Call::BridgeWococoMessages,
relay_rococo_client::runtime::BridgeWococoMessagesCall::receive_messages_proof
);
substrate_relay_helper::generate_mocked_receive_message_delivery_proof_call_builder!(
WococoMessagesToRococo,
WococoMessagesToRococoReceiveMessagesDeliveryProofCallBuilder,
relay_wococo_client::runtime::Call::BridgeRococoMessages,
relay_wococo_client::runtime::BridgeRococoMessagesCall::receive_messages_delivery_proof
);
impl SubstrateMessageLane for WococoMessagesToRococo {
type MessageLane = MessageLaneWococoMessagesToRococo;
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str =
bp_rococo::TO_ROCOCO_MESSAGE_DETAILS_METHOD;
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
bp_rococo::TO_ROCOCO_LATEST_GENERATED_NONCE_METHOD;
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
bp_rococo::TO_ROCOCO_LATEST_RECEIVED_NONCE_METHOD;
const SOURCE_TO_TARGET_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> = None;
const TARGET_TO_SOURCE_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str> = None;
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;
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_wococo::WITH_ROCOCO_MESSAGES_PALLET_NAME;
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_rococo::WITH_WOCOCO_MESSAGES_PALLET_NAME;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight =
bp_rococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
const SOURCE_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str> = None;
const TARGET_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str> = None;
const AT_SOURCE_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str> = None;
const AT_TARGET_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str> = None;
type SourceChain = Wococo;
type TargetChain = Rococo;
fn source_transactions_author(&self) -> bp_wococo::AccountId {
(*self.message_lane.source_sign.public().as_array_ref()).into()
}
type SourceTransactionSignScheme = Wococo;
type TargetTransactionSignScheme = Rococo;
fn make_messages_receiving_proof_transaction(
&self,
best_block_id: WococoHeaderId,
transaction_nonce: IndexOf<Wococo>,
_generated_at_block: RococoHeaderId,
proof: <Self::MessageLane 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.message_lane.source_client.genesis_hash();
let transaction = Wococo::sign_transaction(
genesis_hash,
&self.message_lane.source_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.source_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Rococo -> Wococo confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
bp_wococo::max_extrinsic_weight(),
transaction.encode().len(),
bp_wococo::max_extrinsic_size(),
);
Bytes(transaction.encode())
}
type ReceiveMessagesProofCallBuilder = WococoMessagesToRococoReceiveMessagesProofCallBuilder;
type ReceiveMessagesDeliveryProofCallBuilder =
WococoMessagesToRococoReceiveMessagesDeliveryProofCallBuilder;
fn target_transactions_author(&self) -> bp_rococo::AccountId {
(*self.message_lane.target_sign.public().as_array_ref()).into()
}
type TargetToSourceChainConversionRateUpdateBuilder = ();
fn make_messages_delivery_transaction(
&self,
best_block_id: WococoHeaderId,
transaction_nonce: IndexOf<Rococo>,
_generated_at_header: WococoHeaderId,
_nonces: RangeInclusive<MessageNonce>,
proof: <Self::MessageLane as MessageLane>::MessagesProof,
) -> Bytes {
let (dispatch_weight, proof) = proof;
let FromBridgedChainMessagesProof { ref nonces_start, ref nonces_end, .. } = proof;
let messages_count = nonces_end - nonces_start + 1;
let call = relay_rococo_client::runtime::Call::BridgeMessagesWococo(
relay_rococo_client::runtime::BridgeMessagesWococoCall::receive_messages_proof(
self.message_lane.relayer_id_at_source.clone(),
proof,
messages_count as _,
dispatch_weight,
),
);
let genesis_hash = *self.message_lane.target_client.genesis_hash();
let transaction = Rococo::sign_transaction(
genesis_hash,
&self.message_lane.target_sign,
relay_substrate_client::TransactionEra::new(
best_block_id,
self.message_lane.target_transactions_mortality,
),
UnsignedTransaction::new(call, transaction_nonce),
);
log::trace!(
target: "bridge",
"Prepared Wococo -> Rococo delivery transaction. Weight: <unknown>/{}, size: {}/{}",
bp_rococo::max_extrinsic_weight(),
transaction.encode().len(),
bp_rococo::max_extrinsic_size(),
);
Bytes(transaction.encode())
}
}
/// Wococo node as messages source.
type WococoSourceClient = SubstrateMessagesSource<WococoMessagesToRococo>;
/// Rococo node as messages target.
type RococoTargetClient = SubstrateMessagesTarget<WococoMessagesToRococo>;
/// Run Wococo-to-Rococo messages sync.
pub async fn run(
params: MessagesRelayParams<
Wococo,
WococoSigningParams,
Rococo,
RococoSigningParams,
MixStrategy,
>,
) -> anyhow::Result<()> {
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
params.source_transactions_mortality,
params.target_transactions_mortality,
Wococo::AVERAGE_BLOCK_INTERVAL,
Rococo::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
);
let relayer_id_at_wococo = (*params.source_sign.public().as_array_ref()).into();
let lane_id = params.lane_id;
let source_client = params.source_client;
let target_client = params.target_client;
let lane = WococoMessagesToRococo {
message_lane: SubstrateMessageLaneToSubstrate {
source_client: source_client.clone(),
source_sign: params.source_sign,
source_transactions_mortality: params.source_transactions_mortality,
target_client: target_client.clone(),
target_sign: params.target_sign,
target_transactions_mortality: params.target_transactions_mortality,
relayer_id_at_source: relayer_id_at_wococo,
},
};
// 2/3 is reserved for proofs and tx overhead
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: {}\n\t\
Tx mortality: {:?}/{:?}\n\t\
Stall timeout: {:?}",
lane.message_lane.relayer_id_at_source,
max_messages_in_single_batch,
max_messages_size_in_single_batch,
max_messages_weight_in_single_batch,
params.source_transactions_mortality,
params.target_transactions_mortality,
stall_timeout,
);
let standalone_metrics = params
.standalone_metrics
.map(Ok)
.unwrap_or_else(|| standalone_metrics(source_client.clone(), target_client.clone()))?;
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,
relay_strategy: params.relay_strategy,
},
},
WococoSourceClient::new(
source_client.clone(),
lane.clone(),
lane_id,
params.target_to_source_headers_relay,
),
RococoTargetClient::new(
target_client,
lane,
lane_id,
standalone_metrics.clone(),
params.source_to_target_headers_relay,
),
standalone_metrics.register_and_spawn(params.metrics_params)?,
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Create standalone metrics for the Wococo -> Rococo messages loop.
pub(crate) fn standalone_metrics(
source_client: Client<Wococo>,
target_client: Client<Rococo>,
) -> anyhow::Result<StandaloneMessagesMetrics<Wococo, Rococo>> {
substrate_relay_helper::messages_lane::standalone_metrics(
source_client,
target_client,
None,
None,
None,
None,
)
type RelayStrategy = MixStrategy;
}
@@ -68,7 +68,7 @@ macro_rules! select_full_bridge {
// Relay-messages
#[allow(unused_imports)]
use crate::chains::millau_messages_to_rialto::run as relay_messages;
use crate::chains::millau_messages_to_rialto::MillauMessagesToRialto as MessagesLane;
// Send-message / Estimate-fee
#[allow(unused_imports)]
@@ -90,7 +90,7 @@ macro_rules! select_full_bridge {
// Relay-messages
#[allow(unused_imports)]
use crate::chains::rialto_messages_to_millau::run as relay_messages;
use crate::chains::rialto_messages_to_millau::RialtoMessagesToMillau as MessagesLane;
// Send-message / Estimate-fee
#[allow(unused_imports)]
@@ -113,7 +113,7 @@ macro_rules! select_full_bridge {
// Relay-messages
#[allow(unused_imports)]
use crate::chains::rococo_messages_to_wococo::run as relay_messages;
use crate::chains::rococo_messages_to_wococo::RococoMessagesToWococo as MessagesLane;
// Send-message / Estimate-fee
#[allow(unused_imports)]
@@ -135,7 +135,7 @@ macro_rules! select_full_bridge {
// Relay-messages
#[allow(unused_imports)]
use crate::chains::wococo_messages_to_rococo::run as relay_messages;
use crate::chains::wococo_messages_to_rococo::WococoMessagesToRococo as MessagesLane;
// Send-message / Estimate-fee
#[allow(unused_imports)]
@@ -157,7 +157,7 @@ macro_rules! select_full_bridge {
// Relay-messages
#[allow(unused_imports)]
use crate::chains::kusama_messages_to_polkadot::run as relay_messages;
use crate::chains::kusama_messages_to_polkadot::KusamaMessagesToPolkadot as MessagesLane;
// Send-message / Estimate-fee
#[allow(unused_imports)]
@@ -179,7 +179,7 @@ macro_rules! select_full_bridge {
// Relay-messages
#[allow(unused_imports)]
use crate::chains::polkadot_messages_to_kusama::run as relay_messages;
use crate::chains::polkadot_messages_to_kusama::PolkadotMessagesToKusama as MessagesLane;
// Send-message / Estimate-fee
#[allow(unused_imports)]
@@ -20,6 +20,7 @@ use crate::{
},
select_full_bridge,
};
use bp_runtime::EncodedOrDecodedCall;
use frame_support::weights::DispatchInfo;
use relay_substrate_client::Chain;
use structopt::StructOpt;
@@ -84,14 +85,11 @@ pub enum Call {
}
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>;
fn encode_call(call: &Call) -> anyhow::Result<EncodedOrDecodedCall<Self::Call>>;
/// Get dispatch info for the call.
fn get_dispatch_info(call: &Self::Call) -> anyhow::Result<DispatchInfo>;
fn get_dispatch_info(call: &EncodedOrDecodedCall<Self::Call>) -> anyhow::Result<DispatchInfo>;
}
impl EncodeCall {
@@ -103,7 +101,10 @@ impl EncodeCall {
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", "Weight of {} call: {}", Source::NAME, Source::get_dispatch_info(&call)
.map(|dispatch_info| format!("{}", dispatch_info.weight))
.unwrap_or_else(|_| "<unknown>".to_string())
);
log::info!(target: "bridge", "Encoded {} call: {:?}", Source::NAME, encoded);
Ok(encoded)
@@ -311,8 +312,8 @@ mod tests {
);
}
#[test]
fn should_encode_bridge_send_message_call() {
#[async_std::test]
async fn should_encode_bridge_send_message_call() {
// given
let encode_message = SendMessage::from_iter(vec![
"send-message",
@@ -328,6 +329,7 @@ mod tests {
"remark",
])
.encode_payload()
.await
.unwrap();
let mut encode_call = EncodeCall::from_iter(vec![
@@ -345,7 +347,7 @@ mod tests {
// then
assert!(format!("{:?}", call_hex).starts_with(
"0x0f030000000001000000381409000000000001d43593c715fdd31c61141abd04a99fd6822c8558854cc\
"0x0f030000000001000000000000000000000001d43593c715fdd31c61141abd04a99fd6822c8558854cc\
de39a5684e7a56da27d01d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01"
))
}
@@ -18,6 +18,7 @@ use crate::{
cli::{bridge::FullBridge, AccountId, CliChain, HexBytes},
select_full_bridge,
};
use frame_support::weights::Weight;
use structopt::StructOpt;
use strum::VariantNames;
@@ -37,6 +38,12 @@ pub enum MessagePayload {
/// SS58 encoded Source account that will send the payload.
#[structopt(long)]
sender: AccountId,
/// Weight of the call.
///
/// It must be specified if the chain runtime is not bundled with the relay, or if
/// you want to override bundled weight.
#[structopt(long)]
dispatch_weight: Option<Weight>,
},
}
@@ -97,6 +104,8 @@ mod tests {
"call",
"--sender",
&sender,
"--dispatch-weight",
"42",
"remark",
"--remark-size",
"12",
@@ -106,6 +115,6 @@ mod tests {
let hex = encode_message.encode().unwrap();
// then
assert_eq!(format!("{:?}", hex), "0x0100000010f108000000000002d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d003c000130000000000000000000000000");
assert_eq!(format!("{:?}", hex), "0x010000002a0000000000000002d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d003c000130000000000000000000000000");
}
}
@@ -15,17 +15,22 @@
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::{
cli::{bridge::FullBridge, Balance, CliChain, HexBytes, HexLaneId, SourceConnectionParams},
cli::{
bridge::FullBridge, relay_headers_and_messages::CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
Balance, CliChain, HexBytes, HexLaneId, SourceConnectionParams,
},
select_full_bridge,
};
use bp_runtime::BalanceOf;
use codec::{Decode, Encode};
use relay_substrate_client::Chain;
use sp_runtime::FixedU128;
use structopt::StructOpt;
use strum::VariantNames;
use substrate_relay_helper::helpers::tokens_conversion_rate_from_metrics;
/// Estimate Delivery & Dispatch Fee command.
#[derive(StructOpt, Debug, PartialEq, Eq)]
#[derive(StructOpt, Debug, PartialEq)]
pub struct EstimateFee {
/// A bridge instance to encode call for.
#[structopt(possible_values = FullBridge::VARIANTS, case_insensitive = true)]
@@ -35,15 +40,44 @@ pub struct EstimateFee {
/// Hex-encoded id of lane that will be delivering the message.
#[structopt(long, default_value = "00000000")]
lane: HexLaneId,
/// A way to override conversion rate between bridge tokens.
///
/// If not specified, conversion rate from runtime storage is used. It may be obsolete and
/// your message won't be relayed.
#[structopt(long)]
conversion_rate_override: Option<ConversionRateOverride>,
/// Payload to send over the bridge.
#[structopt(flatten)]
payload: crate::cli::encode_message::MessagePayload,
}
/// A way to override conversion rate between bridge tokens.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ConversionRateOverride {
/// The actual conversion rate is computed in the same way how rate metric works.
Metric,
/// The actual conversion rate is specified explicitly.
Explicit(f64),
}
impl std::str::FromStr for ConversionRateOverride {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.to_lowercase() == "metric" {
return Ok(ConversionRateOverride::Metric)
}
f64::from_str(s)
.map(ConversionRateOverride::Explicit)
.map_err(|e| format!("Failed to parse '{:?}'. Expected 'metric' or explicit value", e))
}
}
impl EstimateFee {
/// Run the command.
pub async fn run(self) -> anyhow::Result<()> {
let Self { source, bridge, lane, payload } = self;
let Self { source, bridge, lane, conversion_rate_override, payload } = self;
select_full_bridge!(bridge, {
let source_client = source.to_client::<Source>().await?;
@@ -51,8 +85,9 @@ impl EstimateFee {
let payload =
Source::encode_message(payload).map_err(|e| anyhow::format_err!("{:?}", e))?;
let fee: BalanceOf<Source> = estimate_message_delivery_and_dispatch_fee(
let fee = estimate_message_delivery_and_dispatch_fee::<Source, Target, _>(
&source_client,
conversion_rate_override,
ESTIMATE_MESSAGE_FEE_METHOD,
lane,
payload,
@@ -66,16 +101,114 @@ impl EstimateFee {
}
}
pub(crate) async fn estimate_message_delivery_and_dispatch_fee<Fee: Decode, C: Chain, P: Encode>(
client: &relay_substrate_client::Client<C>,
/// The caller may provide target to source tokens conversion rate override to use in fee
/// computation.
pub(crate) async fn estimate_message_delivery_and_dispatch_fee<
Source: Chain,
Target: Chain,
P: Clone + Encode,
>(
client: &relay_substrate_client::Client<Source>,
conversion_rate_override: Option<ConversionRateOverride>,
estimate_fee_method: &str,
lane: bp_messages::LaneId,
payload: P,
) -> anyhow::Result<Fee> {
) -> anyhow::Result<BalanceOf<Source>> {
// actual conversion rate CAN be lesser than the rate stored in the runtime. So we may try to
// pay lesser fee for the message delivery. But in this case, message may be rejected by the
// lane. So we MUST use the larger of two fees - one computed with stored fee and the one
// computed with actual fee.
let conversion_rate_override =
match (conversion_rate_override, Source::TOKEN_ID, Target::TOKEN_ID) {
(Some(ConversionRateOverride::Explicit(v)), _, _) => {
let conversion_rate_override = FixedU128::from_float(v);
log::info!(
target: "bridge",
"{} -> {} conversion rate override: {:?} (explicit)",
Target::NAME,
Source::NAME,
conversion_rate_override.to_float(),
);
Some(conversion_rate_override)
},
(
Some(ConversionRateOverride::Metric),
Some(source_token_id),
Some(target_token_id),
) => {
let conversion_rate_override =
tokens_conversion_rate_from_metrics(target_token_id, source_token_id).await?;
// So we have current actual conversion rate and rate that is stored in the runtime.
// And we may simply choose the maximal of these. But what if right now there's
// rate update transaction on the way, that is updating rate to 10 seconds old
// actual rate, which is bigger than the current rate? Then our message will be
// rejected.
//
// So let's increase the actual rate by the same value that the conversion rate
// updater is using.
let increased_conversion_rate_override = FixedU128::from_float(
conversion_rate_override * (1.0 + CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO),
);
log::info!(
target: "bridge",
"{} -> {} conversion rate override: {} (value from metric - {})",
Target::NAME,
Source::NAME,
increased_conversion_rate_override.to_float(),
conversion_rate_override,
);
Some(increased_conversion_rate_override)
},
_ => None,
};
let without_override = do_estimate_message_delivery_and_dispatch_fee(
client,
estimate_fee_method,
lane,
payload.clone(),
None,
)
.await?;
let with_override = do_estimate_message_delivery_and_dispatch_fee(
client,
estimate_fee_method,
lane,
payload.clone(),
conversion_rate_override,
)
.await?;
let maximal_fee = std::cmp::max(without_override, with_override);
log::info!(
target: "bridge",
"Estimated message fee: {:?} = max of {:?} (without rate override) and {:?} (with override to {:?})",
maximal_fee,
without_override,
with_override,
conversion_rate_override,
);
Ok(maximal_fee)
}
/// Estimate message delivery and dispatch fee with given conversion rate override.
async fn do_estimate_message_delivery_and_dispatch_fee<Source: Chain, P: Encode>(
client: &relay_substrate_client::Client<Source>,
estimate_fee_method: &str,
lane: bp_messages::LaneId,
payload: P,
conversion_rate_override: Option<FixedU128>,
) -> anyhow::Result<BalanceOf<Source>> {
let encoded_response = client
.state_call(estimate_fee_method.into(), (lane, payload).encode().into(), None)
.state_call(
estimate_fee_method.into(),
(lane, payload, conversion_rate_override).encode().into(),
None,
)
.await?;
let decoded_response: Option<Fee> = Decode::decode(&mut &encoded_response.0[..])
let decoded_response: Option<BalanceOf<Source>> = 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()))
@@ -86,7 +219,7 @@ pub(crate) async fn estimate_message_delivery_and_dispatch_fee<Fee: Decode, C: C
#[cfg(test)]
mod tests {
use super::*;
use crate::cli::encode_call;
use crate::cli::{encode_call, RuntimeVersionType, SourceRuntimeVersionParams};
use sp_core::crypto::Ss58Codec;
#[test]
@@ -100,9 +233,13 @@ mod tests {
"rialto-to-millau",
"--source-port",
"1234",
"--conversion-rate-override",
"42.5",
"call",
"--sender",
&alice,
"--dispatch-weight",
"42",
"remark",
"--remark-payload",
"1234",
@@ -114,17 +251,24 @@ mod tests {
EstimateFee {
bridge: FullBridge::RialtoToMillau,
lane: HexLaneId([0, 0, 0, 0]),
conversion_rate_override: Some(ConversionRateOverride::Explicit(42.5)),
source: SourceConnectionParams {
source_host: "127.0.0.1".into(),
source_port: 1234,
source_secure: false,
source_runtime_version: SourceRuntimeVersionParams {
source_version_mode: RuntimeVersionType::Bundle,
source_spec_version: None,
source_transaction_version: None,
}
},
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,
}
},
dispatch_weight: Some(42),
}
}
);
@@ -18,7 +18,7 @@ use crate::cli::{SourceConnectionParams, TargetConnectionParams, TargetSigningPa
use bp_header_chain::InitializationData;
use bp_runtime::Chain as ChainBase;
use codec::Encode;
use relay_substrate_client::{Chain, TransactionSignScheme, UnsignedTransaction};
use relay_substrate_client::{Chain, SignParam, TransactionSignScheme, UnsignedTransaction};
use sp_core::{Bytes, Pair};
use structopt::StructOpt;
use strum::{EnumString, EnumVariantNames, VariantNames};
@@ -187,23 +187,27 @@ impl InitBridge {
let target_client = self.target.to_client::<Target>().await?;
let target_sign = self.target_sign.to_keypair::<Target>()?;
let (spec_version, transaction_version) =
target_client.simple_runtime_version().await?;
substrate_relay_helper::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,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
encode_init_bridge(initialization_data),
Ok(Bytes(
Target::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash: *target_client.genesis_hash(),
signer: target_sign,
era: relay_substrate_client::TransactionEra::immortal(),
unsigned: UnsignedTransaction::new(
encode_init_bridge(initialization_data).into(),
transaction_nonce,
),
)
})?
.encode(),
)
))
},
)
.await;
@@ -18,11 +18,13 @@
use std::convert::TryInto;
use bp_messages::LaneId;
use codec::{Decode, Encode};
use frame_support::weights::Weight;
use relay_substrate_client::ChainRuntimeVersion;
use sp_runtime::app_crypto::Ss58Codec;
use structopt::{clap::arg_enum, StructOpt};
use strum::{EnumString, EnumVariantNames};
use bp_messages::LaneId;
pub(crate) mod bridge;
pub(crate) mod encode_call;
@@ -33,6 +35,7 @@ pub(crate) mod send_message;
mod derive_account;
mod init_bridge;
mod register_parachain;
mod reinit_bridge;
mod relay_headers;
mod relay_headers_and_messages;
mod relay_messages;
@@ -69,6 +72,11 @@ pub enum Command {
///
/// Sends initialization transaction to bootstrap the bridge with current finalized block data.
InitBridge(init_bridge::InitBridge),
/// Reinitialize on-chain bridge pallet with current header data.
///
/// Sends all missing mandatory headers to bootstrap the bridge with current finalized block
/// data.
ReinitBridge(reinit_bridge::ReinitBridge),
/// Send custom message over the bridge.
///
/// Allows interacting with the bridge by sending messages over `Messages` component.
@@ -124,6 +132,7 @@ impl Command {
Self::RelayMessages(arg) => arg.run().await?,
Self::RelayHeadersAndMessages(arg) => arg.run().await?,
Self::InitBridge(arg) => arg.run().await?,
Self::ReinitBridge(arg) => arg.run().await?,
Self::SendMessage(arg) => arg.run().await?,
Self::EncodeCall(arg) => arg.run().await?,
Self::EncodeMessage(arg) => arg.run().await?,
@@ -238,7 +247,7 @@ impl AccountId {
///
/// Used to abstract away CLI commands.
pub trait CliChain: relay_substrate_client::Chain {
/// Chain's current version of the runtime.
/// Current version of the chain runtime, known to relay.
const RUNTIME_VERSION: sp_version::RuntimeVersion;
/// Crypto KeyPair type used to send messages.
@@ -258,9 +267,6 @@ pub trait CliChain: relay_substrate_client::Chain {
fn encode_message(
message: crate::cli::encode_message::MessagePayload,
) -> anyhow::Result<Self::MessagePayload>;
/// Maximal extrinsic weight (from the runtime).
fn max_extrinsic_weight() -> Weight;
}
/// Lane id.
@@ -368,6 +374,17 @@ where
}
}
#[doc = "Runtime version params."]
#[derive(StructOpt, Debug, PartialEq, Eq, Clone, Copy, EnumString, EnumVariantNames)]
pub enum RuntimeVersionType {
/// Auto query version from chain
Auto,
/// Custom `spec_version` and `transaction_version`
Custom,
/// Read version from bundle dependencies directly.
Bundle,
}
/// Create chain-specific set of configuration objects: connection parameters,
/// signing parameters and bridge initialization parameters.
#[macro_export]
@@ -381,11 +398,28 @@ macro_rules! declare_chain_options {
#[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)]
#[structopt(long, default_value = "9944")]
pub [<$chain_prefix _port>]: u16,
#[doc = "Use secure websocket connection."]
#[structopt(long)]
pub [<$chain_prefix _secure>]: bool,
#[doc = "Custom runtime version"]
#[structopt(flatten)]
pub [<$chain_prefix _runtime_version>]: [<$chain RuntimeVersionParams>],
}
#[doc = $chain " runtime version params."]
#[derive(StructOpt, Debug, PartialEq, Eq, Clone, Copy)]
pub struct [<$chain RuntimeVersionParams>] {
#[doc = "The type of runtime version for chain " $chain]
#[structopt(long, default_value = "Bundle")]
pub [<$chain_prefix _version_mode>]: RuntimeVersionType,
#[doc = "The custom sepc_version for chain " $chain]
#[structopt(long)]
pub [<$chain_prefix _spec_version>]: Option<u32>,
#[doc = "The custom transaction_version for chain " $chain]
#[structopt(long)]
pub [<$chain_prefix _transaction_version>]: Option<u32>,
}
#[doc = $chain " signing params."]
@@ -501,18 +535,82 @@ macro_rules! declare_chain_options {
}
impl [<$chain ConnectionParams>] {
/// Returns `true` if version guard can be started.
///
/// There's no reason to run version guard when version mode is set to `Auto`. It can
/// lead to relay shutdown when chain is upgraded, even though we have explicitly
/// said that we don't want to shutdown.
#[allow(dead_code)]
pub fn can_start_version_guard(&self) -> bool {
self.[<$chain_prefix _runtime_version>].[<$chain_prefix _version_mode>] != RuntimeVersionType::Auto
}
/// Convert connection params into Substrate client.
pub async fn to_client<Chain: CliChain>(
&self,
) -> anyhow::Result<relay_substrate_client::Client<Chain>> {
let chain_runtime_version = self
.[<$chain_prefix _runtime_version>]
.into_runtime_version(Some(Chain::RUNTIME_VERSION))?;
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>],
chain_runtime_version,
})
.await
)
}
/// Return selected `chain_spec` version.
///
/// This function only connects to the node if version mode is set to `Auto`.
#[allow(dead_code)]
pub async fn selected_chain_spec_version<Chain: CliChain>(
&self,
) -> anyhow::Result<u32> {
let chain_runtime_version = self
.[<$chain_prefix _runtime_version>]
.into_runtime_version(Some(Chain::RUNTIME_VERSION))?;
Ok(match chain_runtime_version {
ChainRuntimeVersion::Auto => self
.to_client::<Chain>()
.await?
.simple_runtime_version()
.await?
.0,
ChainRuntimeVersion::Custom(spec_version, _) => spec_version,
})
}
}
impl [<$chain RuntimeVersionParams>] {
/// Converts self into `ChainRuntimeVersion`.
pub fn into_runtime_version(
self,
bundle_runtime_version: Option<sp_version::RuntimeVersion>,
) -> anyhow::Result<ChainRuntimeVersion> {
Ok(match self.[<$chain_prefix _version_mode>] {
RuntimeVersionType::Auto => ChainRuntimeVersion::Auto,
RuntimeVersionType::Custom => {
let except_spec_version = self.[<$chain_prefix _spec_version>]
.ok_or_else(|| anyhow::Error::msg(format!("The {}-spec-version is required when choose custom mode", stringify!($chain_prefix))))?;
let except_transaction_version = self.[<$chain_prefix _transaction_version>]
.ok_or_else(|| anyhow::Error::msg(format!("The {}-transaction-version is required when choose custom mode", stringify!($chain_prefix))))?;
ChainRuntimeVersion::Custom(
except_spec_version,
except_transaction_version
)
},
RuntimeVersionType::Bundle => match bundle_runtime_version {
Some(runtime_version) => ChainRuntimeVersion::Custom(
runtime_version.spec_version,
runtime_version.transaction_version
),
None => ChainRuntimeVersion::Auto
},
})
}
}
}
};
@@ -525,9 +623,10 @@ declare_chain_options!(Parachain, parachain);
#[cfg(test)]
mod tests {
use sp_core::Pair;
use std::str::FromStr;
use sp_core::Pair;
use super::*;
#[test]
@@ -20,6 +20,7 @@ use crate::cli::{
};
use codec::Encode;
use frame_support::Twox64Concat;
use num_traits::Zero;
use polkadot_parachain::primitives::{
HeadData as ParaHeadData, Id as ParaId, ValidationCode as ParaValidationCode,
@@ -29,7 +30,7 @@ use polkadot_runtime_common::{
};
use polkadot_runtime_parachains::paras::ParaLifecycle;
use relay_substrate_client::{
AccountIdOf, CallOf, Chain, Client, TransactionSignScheme, UnsignedTransaction,
AccountIdOf, CallOf, Chain, Client, SignParam, TransactionSignScheme, UnsignedTransaction,
};
use rialto_runtime::SudoCall;
use sp_core::{
@@ -116,23 +117,26 @@ impl RegisterParachain {
let reserve_parachain_id_call: CallOf<Relaychain> =
ParaRegistrarCall::reserve {}.into();
let reserve_parachain_signer = relay_sign.clone();
let (spec_version, transaction_version) = relay_client.simple_runtime_version().await?;
wait_until_transaction_is_finalized::<Relaychain>(
relay_client
.submit_and_watch_signed_extrinsic(
relay_sudo_account.clone(),
move |_, transaction_nonce| {
Bytes(
Relaychain::sign_transaction(
relay_genesis_hash,
&reserve_parachain_signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
reserve_parachain_id_call,
Ok(Bytes(
Relaychain::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash: relay_genesis_hash,
signer: reserve_parachain_signer,
era: relay_substrate_client::TransactionEra::immortal(),
unsigned: UnsignedTransaction::new(
reserve_parachain_id_call.into(),
transaction_nonce,
),
)
})?
.encode(),
)
))
},
)
.await?,
@@ -168,18 +172,20 @@ impl RegisterParachain {
.submit_and_watch_signed_extrinsic(
relay_sudo_account.clone(),
move |_, transaction_nonce| {
Bytes(
Relaychain::sign_transaction(
relay_genesis_hash,
&register_parathread_signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
register_parathread_call,
Ok(Bytes(
Relaychain::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash: relay_genesis_hash,
signer: register_parathread_signer,
era: relay_substrate_client::TransactionEra::immortal(),
unsigned: UnsignedTransaction::new(
register_parathread_call.into(),
transaction_nonce,
),
)
})?
.encode(),
)
))
},
)
.await?,
@@ -188,7 +194,7 @@ impl RegisterParachain {
log::info!(target: "bridge", "Registered parachain: {:?}. Waiting for onboarding", para_id);
// wait until parathread is onboarded
let para_state_key = bp_runtime::storage_map_final_key_twox64_concat(
let para_state_key = bp_runtime::storage_map_final_key::<Twox64Concat>(
PARAS_PALLET_NAME,
PARAS_LIFECYCLES_STORAGE_NAME,
&para_id.encode(),
@@ -228,15 +234,20 @@ impl RegisterParachain {
let force_lease_signer = relay_sign.clone();
relay_client
.submit_signed_extrinsic(relay_sudo_account.clone(), move |_, transaction_nonce| {
Bytes(
Relaychain::sign_transaction(
relay_genesis_hash,
&force_lease_signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(force_lease_call, transaction_nonce),
)
Ok(Bytes(
Relaychain::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash: relay_genesis_hash,
signer: force_lease_signer,
era: relay_substrate_client::TransactionEra::immortal(),
unsigned: UnsignedTransaction::new(
force_lease_call.into(),
transaction_nonce,
),
})?
.encode(),
)
))
})
.await?;
log::info!(target: "bridge", "Registered parachain leases: {:?}. Waiting for onboarding", para_id);
@@ -292,6 +303,9 @@ async fn wait_para_state<Relaychain: Chain>(
#[cfg(test)]
mod tests {
use super::*;
use crate::cli::{
ParachainRuntimeVersionParams, RelaychainRuntimeVersionParams, RuntimeVersionType,
};
#[test]
fn register_rialto_parachain() {
@@ -327,6 +341,11 @@ mod tests {
relaychain_host: "127.0.0.1".into(),
relaychain_port: 9944,
relaychain_secure: false,
relaychain_runtime_version: RelaychainRuntimeVersionParams {
relaychain_version_mode: RuntimeVersionType::Bundle,
relaychain_spec_version: None,
relaychain_transaction_version: None,
}
},
relay_sign: RelaychainSigningParams {
relaychain_signer: Some("//Alice".into()),
@@ -339,6 +358,11 @@ mod tests {
parachain_host: "127.0.0.1".into(),
parachain_port: 11949,
parachain_secure: false,
parachain_runtime_version: ParachainRuntimeVersionParams {
parachain_version_mode: RuntimeVersionType::Bundle,
parachain_spec_version: None,
parachain_transaction_version: None,
}
},
}
);
@@ -0,0 +1,553 @@
// 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::{
chains::{
kusama_headers_to_polkadot::KusamaFinalityToPolkadot,
polkadot_headers_to_kusama::PolkadotFinalityToKusama,
},
cli::{
swap_tokens::wait_until_transaction_is_finalized, SourceConnectionParams,
TargetConnectionParams, TargetSigningParams,
},
};
use bp_header_chain::justification::GrandpaJustification;
use bp_runtime::Chain;
use codec::Encode;
use finality_relay::{SourceClient, SourceHeader};
use frame_support::weights::Weight;
use num_traits::One;
use pallet_bridge_grandpa::weights::WeightInfo;
use relay_substrate_client::{
AccountIdOf, BlockNumberOf, Chain as _, Client, Error as SubstrateError, HeaderOf, SignParam,
SyncHeader, TransactionEra, TransactionSignScheme, UnsignedTransaction,
};
use sp_core::{Bytes, Pair};
use std::convert::{TryFrom, TryInto};
use structopt::StructOpt;
use strum::{EnumString, EnumVariantNames, VariantNames};
use substrate_relay_helper::{
finality_pipeline::SubstrateFinalitySyncPipeline, finality_source::SubstrateFinalitySource,
finality_target::SubstrateFinalityTarget, messages_source::read_client_state,
TransactionParams,
};
/// Reinitialize bridge pallet.
#[derive(Debug, PartialEq, StructOpt)]
pub struct ReinitBridge {
/// A bridge instance to reinitialize.
#[structopt(possible_values = ReinitBridgeName::VARIANTS, case_insensitive = true)]
bridge: ReinitBridgeName,
#[structopt(flatten)]
source: SourceConnectionParams,
#[structopt(flatten)]
target: TargetConnectionParams,
#[structopt(flatten)]
target_sign: TargetSigningParams,
}
#[derive(Debug, EnumString, EnumVariantNames, PartialEq)]
#[strum(serialize_all = "kebab_case")]
/// Bridge to initialize.
pub enum ReinitBridgeName {
KusamaToPolkadot,
PolkadotToKusama,
}
macro_rules! select_bridge {
($bridge: expr, $generic: tt) => {
match $bridge {
ReinitBridgeName::KusamaToPolkadot => {
use relay_polkadot_client::runtime;
type Finality = KusamaFinalityToPolkadot;
type Call = runtime::Call;
fn submit_finality_proof_call(
header_and_proof: HeaderAndProof<Finality>,
) -> runtime::Call {
runtime::Call::BridgeKusamaGrandpa(
runtime::BridgeKusamaGrandpaCall::submit_finality_proof(
Box::new(header_and_proof.0.into_inner()),
header_and_proof.1,
),
)
}
fn set_pallet_operation_mode_call(operational: bool) -> runtime::Call {
runtime::Call::BridgeKusamaGrandpa(
runtime::BridgeKusamaGrandpaCall::set_operational(operational),
)
}
fn batch_all_call(calls: Vec<Call>) -> runtime::Call {
runtime::Call::Utility(runtime::UtilityCall::batch_all(calls))
}
$generic
},
ReinitBridgeName::PolkadotToKusama => {
use relay_kusama_client::runtime;
type Finality = PolkadotFinalityToKusama;
type Call = runtime::Call;
fn submit_finality_proof_call(
header_and_proof: HeaderAndProof<Finality>,
) -> runtime::Call {
runtime::Call::BridgePolkadotGrandpa(
runtime::BridgePolkadotGrandpaCall::submit_finality_proof(
Box::new(header_and_proof.0.into_inner()),
header_and_proof.1,
),
)
}
fn set_pallet_operation_mode_call(operational: bool) -> runtime::Call {
runtime::Call::BridgePolkadotGrandpa(
runtime::BridgePolkadotGrandpaCall::set_operational(operational),
)
}
fn batch_all_call(calls: Vec<Call>) -> runtime::Call {
runtime::Call::Utility(runtime::UtilityCall::batch_all(calls))
}
$generic
},
}
};
}
impl ReinitBridge {
/// Run the command.
pub async fn run(self) -> anyhow::Result<()> {
select_bridge!(self.bridge, {
type Source = <Finality as SubstrateFinalitySyncPipeline>::SourceChain;
type Target = <Finality as SubstrateFinalitySyncPipeline>::TargetChain;
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 transaction_params = TransactionParams {
signer: target_sign,
mortality: self.target_sign.target_transactions_mortality,
};
let finality_source =
SubstrateFinalitySource::<Finality>::new(source_client.clone(), None);
let finality_target = SubstrateFinalityTarget::<Finality>::new(
target_client.clone(),
transaction_params.clone(),
);
// this subcommand assumes that the pallet at the target chain is halted
ensure_pallet_operating_mode(&finality_target, false).await?;
// we can't call `finality_target.best_finalized_source_block_id()`, because pallet is
// halted and the call will fail => just use what it uses internally
let current_number =
best_source_block_number_at_target::<Finality>(&target_client).await?;
let target_number = finality_source.best_finalized_block_number().await?;
log::info!(
target: "bridge",
"Best finalized {} header: at {}: {}, at {}: {}",
Source::NAME,
Source::NAME,
target_number,
Target::NAME,
current_number,
);
// prepare list of mandatory headers from the range `(current_number; target_number]`
let headers_to_submit = find_mandatory_headers_in_range(
&finality_source,
(current_number + 1, target_number),
)
.await?;
let latest_andatory_header_number = headers_to_submit.last().map(|(h, _)| h.number());
log::info!(
target: "bridge",
"Missing {} mandatory {} headers at {}",
headers_to_submit.len(),
Source::NAME,
Target::NAME,
);
// split all mandatory headers into batches
let headers_batches =
make_mandatory_headers_batches::<Finality, _>(headers_to_submit, |(_, proof)| {
// we don't have an access to the Kusama/Polkadot chain runtimes here, so we'll
// be using Millau weights. It isn't super-critical, unless real weights are
// magnitude higher or so
pallet_bridge_grandpa::weights::MillauWeight::<millau_runtime::Runtime>::submit_finality_proof(
proof.commit.precommits.len().try_into().unwrap_or(u32::MAX),
proof.votes_ancestries.len().try_into().unwrap_or(u32::MAX),
)
});
log::info!(
target: "bridge",
"We're going to submit {} transactions to {} node",
headers_batches.len(),
Target::NAME,
);
// each batch is submitted as a separate transaction
let signer_account_id: AccountIdOf<Target> = transaction_params.signer.public().into();
let genesis_hash = *target_client.genesis_hash();
let (spec_version, transaction_version) =
target_client.simple_runtime_version().await?;
let last_batch_index = headers_batches.len() - 1;
for (i, headers_batch) in headers_batches.into_iter().enumerate() {
let is_last_batch = i == last_batch_index;
let expected_number =
headers_batch.last().expect("all batches are non-empty").0.number();
let transaction_params = transaction_params.clone();
log::info!(
target: "bridge",
"Going to submit transaction that updates best {} header at {} to {}",
Source::NAME,
Target::NAME,
expected_number,
);
// prepare `batch_all` call
let mut batch_calls = Vec::with_capacity(headers_batch.len() + 2);
// the first call is always resumes pallet operation
batch_calls.push(set_pallet_operation_mode_call(true));
// followed by submit-finality-proofs calls
for header_and_proof in headers_batch {
batch_calls.push(submit_finality_proof_call(header_and_proof));
}
// if it isn't the last batch, we shall halt pallet again
if !is_last_batch {
batch_calls.push(set_pallet_operation_mode_call(false));
}
let submit_batch_call = batch_all_call(batch_calls);
let batch_transaction_events = target_client
.submit_and_watch_signed_extrinsic(
signer_account_id.clone(),
move |best_block_id, transaction_nonce| {
Ok(Bytes(
Target::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash,
signer: transaction_params.signer.clone(),
era: TransactionEra::new(
best_block_id,
transaction_params.mortality,
),
unsigned: UnsignedTransaction::new(
submit_batch_call.into(),
transaction_nonce,
),
})?
.encode(),
))
},
)
.await?;
wait_until_transaction_is_finalized::<Target>(batch_transaction_events).await?;
// verify that the best finalized header at target has been updated
let current_number =
best_source_block_number_at_target::<Finality>(&target_client).await?;
if current_number != expected_number {
return Err(anyhow::format_err!(
"Transaction has failed to update best {} header at {} to {}. It is {}",
Source::NAME,
Target::NAME,
expected_number,
current_number,
))
}
// verify that the pallet is still halted (or operational if it is the last batch)
ensure_pallet_operating_mode(&finality_target, is_last_batch).await?;
}
if let Some(latest_andatory_header_number) = latest_andatory_header_number {
log::info!(
target: "bridge",
"Successfully updated best {} header at {} to {}. Pallet is now operational",
Source::NAME,
Target::NAME,
latest_andatory_header_number,
);
}
Ok(())
})
}
}
/// Mandatory header and its finality proof.
type HeaderAndProof<P> = (
SyncHeader<HeaderOf<<P as SubstrateFinalitySyncPipeline>::SourceChain>>,
GrandpaJustification<HeaderOf<<P as SubstrateFinalitySyncPipeline>::SourceChain>>,
);
/// Vector of mandatory headers and their finality proofs.
type HeadersAndProofs<P> = Vec<HeaderAndProof<P>>;
/// Returns best finalized source header number known to the bridge GRANDPA pallet at the target
/// chain.
///
/// This function works even if bridge GRANDPA pallet at the target chain is halted.
async fn best_source_block_number_at_target<P: SubstrateFinalitySyncPipeline>(
target_client: &Client<P::TargetChain>,
) -> anyhow::Result<BlockNumberOf<P::SourceChain>> {
Ok(read_client_state::<P::TargetChain, P::SourceChain>(
target_client,
None,
P::SourceChain::BEST_FINALIZED_HEADER_ID_METHOD,
)
.await?
.best_finalized_peer_at_best_self
.0)
}
/// Verify that the bridge GRANDPA pallet at the target chain is either halted, or operational.
async fn ensure_pallet_operating_mode<P: SubstrateFinalitySyncPipeline>(
finality_target: &SubstrateFinalityTarget<P>,
operational: bool,
) -> anyhow::Result<()> {
match (operational, finality_target.ensure_pallet_active().await) {
(true, Ok(())) => Ok(()),
(false, Err(SubstrateError::BridgePalletIsHalted)) => Ok(()),
_ =>
return Err(anyhow::format_err!(
"Bridge GRANDPA pallet at {} is expected to be {}, but it isn't",
P::TargetChain::NAME,
if operational { "operational" } else { "halted" },
)),
}
}
/// Returns list of all mandatory headers in given range.
async fn find_mandatory_headers_in_range<P: SubstrateFinalitySyncPipeline>(
finality_source: &SubstrateFinalitySource<P>,
range: (BlockNumberOf<P::SourceChain>, BlockNumberOf<P::SourceChain>),
) -> anyhow::Result<HeadersAndProofs<P>> {
let mut mandatory_headers = Vec::new();
let mut current = range.0;
while current <= range.1 {
let (header, proof) = finality_source.header_and_finality_proof(current).await?;
if header.is_mandatory() {
match proof {
Some(proof) => mandatory_headers.push((header, proof)),
None =>
return Err(anyhow::format_err!(
"Missing GRANDPA justification for {} header {}",
P::SourceChain::NAME,
current,
)),
}
}
current += One::one();
}
Ok(mandatory_headers)
}
/// Given list of mandatory headers, prepare batches of headers, so that every batch may fit into
/// single transaction.
fn make_mandatory_headers_batches<
P: SubstrateFinalitySyncPipeline,
F: Fn(&HeaderAndProof<P>) -> Weight,
>(
mut headers_to_submit: HeadersAndProofs<P>,
submit_header_weight: F,
) -> Vec<HeadersAndProofs<P>> {
// now that we have all mandatory headers, let's prepare transactions
// (let's keep all our transactions below 2/3 of max tx size/weight to have some reserve
// for utility overhead + for halting transaction)
let maximal_tx_size = P::TargetChain::max_extrinsic_size() * 2 / 3;
let maximal_tx_weight = P::TargetChain::max_extrinsic_weight() * 2 / 3;
let mut current_batch_size: u32 = 0;
let mut current_batch_weight: Weight = 0;
let mut batches = Vec::new();
let mut i = 0;
while i < headers_to_submit.len() {
let header_and_proof_size =
headers_to_submit[i].0.encode().len() + headers_to_submit[i].1.encode().len();
let header_and_proof_weight = submit_header_weight(&headers_to_submit[i]);
let new_batch_size = current_batch_size
.saturating_add(u32::try_from(header_and_proof_size).unwrap_or(u32::MAX));
let new_batch_weight = current_batch_weight.saturating_add(header_and_proof_weight);
let is_exceeding_tx_size = new_batch_size > maximal_tx_size;
let is_exceeding_tx_weight = new_batch_weight > maximal_tx_weight;
let is_new_batch_required = is_exceeding_tx_size || is_exceeding_tx_weight;
if is_new_batch_required {
// if `i` is 0 and we're here, it is a weird situation: even single header submission is
// larger than we've planned for a bunch of headers. Let's be optimistic and hope that
// the tx will still succeed.
let spit_off_index = std::cmp::max(i, 1);
let remaining_headers_to_submit = headers_to_submit.split_off(spit_off_index);
batches.push(headers_to_submit);
// we'll reiterate the same header again => so set `current_*` to zero
current_batch_size = 0;
current_batch_weight = 0;
headers_to_submit = remaining_headers_to_submit;
i = 0;
} else {
current_batch_size = new_batch_size;
current_batch_weight = new_batch_weight;
i += 1;
}
}
if !headers_to_submit.is_empty() {
batches.push(headers_to_submit);
}
batches
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cli::{RuntimeVersionType, SourceRuntimeVersionParams, TargetRuntimeVersionParams};
use bp_test_utils::{make_default_justification, test_header};
use relay_polkadot_client::Polkadot;
use sp_runtime::{traits::Header as _, DigestItem};
fn make_header_and_justification(
i: u32,
size: u32,
) -> (SyncHeader<bp_kusama::Header>, GrandpaJustification<bp_kusama::Header>) {
let size = size as usize;
let mut header: bp_kusama::Header = test_header(i);
let justification = make_default_justification(&header);
let actual_size = header.encode().len() + justification.encode().len();
// additional digest means some additional bytes, so let's decrease `additional_digest_size`
// a bit
let additional_digest_size = size.saturating_sub(actual_size).saturating_sub(100);
header.digest_mut().push(DigestItem::Other(vec![0u8; additional_digest_size]));
let justification = make_default_justification(&header);
println!("{} {}", size, header.encode().len() + justification.encode().len());
(header.into(), justification)
}
#[test]
fn should_parse_cli_options() {
// when
let res = ReinitBridge::from_iter(vec![
"reinit-bridge",
"kusama-to-polkadot",
"--source-host",
"127.0.0.1",
"--source-port",
"42",
"--target-host",
"127.0.0.1",
"--target-port",
"43",
"--target-signer",
"//Alice",
]);
// then
assert_eq!(
res,
ReinitBridge {
bridge: ReinitBridgeName::KusamaToPolkadot,
source: SourceConnectionParams {
source_host: "127.0.0.1".into(),
source_port: 42,
source_secure: false,
source_runtime_version: SourceRuntimeVersionParams {
source_version_mode: RuntimeVersionType::Bundle,
source_spec_version: None,
source_transaction_version: None,
}
},
target: TargetConnectionParams {
target_host: "127.0.0.1".into(),
target_port: 43,
target_secure: false,
target_runtime_version: TargetRuntimeVersionParams {
target_version_mode: RuntimeVersionType::Bundle,
target_spec_version: None,
target_transaction_version: None,
}
},
target_sign: TargetSigningParams {
target_signer: Some("//Alice".into()),
target_signer_password: None,
target_signer_file: None,
target_signer_password_file: None,
target_transactions_mortality: None,
},
}
);
}
#[test]
fn make_mandatory_headers_batches_and_empty_headers() {
let batches = make_mandatory_headers_batches::<KusamaFinalityToPolkadot, _>(vec![], |_| 0);
assert!(batches.is_empty());
}
#[test]
fn make_mandatory_headers_batches_with_single_batch() {
let headers_to_submit =
vec![make_header_and_justification(10, Polkadot::max_extrinsic_size() / 3)];
let batches =
make_mandatory_headers_batches::<KusamaFinalityToPolkadot, _>(headers_to_submit, |_| 0);
assert_eq!(batches.into_iter().map(|x| x.len()).collect::<Vec<_>>(), vec![1],);
}
#[test]
fn make_mandatory_headers_batches_group_by_size() {
let headers_to_submit = vec![
make_header_and_justification(10, Polkadot::max_extrinsic_size() / 3),
make_header_and_justification(20, Polkadot::max_extrinsic_size() / 3),
make_header_and_justification(30, Polkadot::max_extrinsic_size() * 2 / 3),
make_header_and_justification(40, Polkadot::max_extrinsic_size()),
];
let batches =
make_mandatory_headers_batches::<KusamaFinalityToPolkadot, _>(headers_to_submit, |_| 0);
assert_eq!(batches.into_iter().map(|x| x.len()).collect::<Vec<_>>(), vec![2, 1, 1],);
}
#[test]
fn make_mandatory_headers_batches_group_by_weight() {
let headers_to_submit = vec![
make_header_and_justification(10, 0),
make_header_and_justification(20, 0),
make_header_and_justification(30, 0),
make_header_and_justification(40, 0),
];
let batches = make_mandatory_headers_batches::<KusamaFinalityToPolkadot, _>(
headers_to_submit,
|(header, _)| {
if header.number() == 10 || header.number() == 20 {
Polkadot::max_extrinsic_weight() / 3
} else if header.number() == 30 {
Polkadot::max_extrinsic_weight() * 2 / 3
} else {
Polkadot::max_extrinsic_weight()
}
},
);
assert_eq!(batches.into_iter().map(|x| x.len()).collect::<Vec<_>>(), vec![2, 1, 1],);
}
}
@@ -121,18 +121,26 @@ impl RelayHeaders {
let target_client = self.target.to_client::<Target>().await?;
let target_transactions_mortality = self.target_sign.target_transactions_mortality;
let target_sign = self.target_sign.to_keypair::<Target>()?;
let metrics_params = Finality::customize_metrics(self.prometheus_params.into())?;
let metrics_params: relay_utils::metrics::MetricsParams = self.prometheus_params.into();
GlobalMetrics::new()?.register_and_spawn(&metrics_params.registry)?;
let finality = Finality::new(target_client.clone(), target_sign);
finality.start_relay_guards();
let target_transactions_params = substrate_relay_helper::TransactionParams {
signer: target_sign,
mortality: target_transactions_mortality,
};
Finality::start_relay_guards(
&target_client,
&target_transactions_params,
self.target.can_start_version_guard(),
)
.await?;
substrate_relay_helper::finality_pipeline::run(
finality,
substrate_relay_helper::finality_pipeline::run::<Finality>(
source_client,
target_client,
self.only_mandatory_headers,
target_transactions_mortality,
target_transactions_params,
metrics_params,
)
.await
@@ -29,16 +29,18 @@ use strum::VariantNames;
use codec::Encode;
use messages_relay::relay_strategy::MixStrategy;
use relay_substrate_client::{
AccountIdOf, Chain, Client, TransactionSignScheme, UnsignedTransaction,
AccountIdOf, CallOf, Chain, ChainRuntimeVersion, Client, SignParam, TransactionSignScheme,
UnsignedTransaction,
};
use relay_utils::metrics::MetricsParams;
use sp_core::{Bytes, Pair};
use substrate_relay_helper::{
messages_lane::MessagesRelayParams, on_demand_headers::OnDemandHeadersRelay,
finality_pipeline::SubstrateFinalitySyncPipeline, messages_lane::MessagesRelayParams,
on_demand_headers::OnDemandHeadersRelay, TransactionParams,
};
use crate::{
cli::{relay_messages::RelayerMode, CliChain, HexLaneId, PrometheusParams},
cli::{relay_messages::RelayerMode, CliChain, HexLaneId, PrometheusParams, RuntimeVersionType},
declare_chain_options,
};
@@ -48,7 +50,7 @@ use crate::{
/// stored and real conversion rates. If it is large enough (e.g. > than 10 percents, which is 0.1),
/// then rational relayers may stop relaying messages because they were submitted using
/// lesser conversion rate.
const CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO: f64 = 0.05;
pub(crate) const CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO: f64 = 0.05;
/// Start headers+messages relayer process.
#[derive(StructOpt)]
@@ -131,21 +133,9 @@ macro_rules! select_bridge {
type LeftAccountIdConverter = bp_millau::AccountIdConverter;
type RightAccountIdConverter = bp_rialto::AccountIdConverter;
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::{
standalone_metrics as left_to_right_standalone_metrics,
run as left_to_right_messages,
update_rialto_to_millau_conversion_rate as update_right_to_left_conversion_rate,
},
rialto_messages_to_millau::{
run as right_to_left_messages,
update_millau_to_rialto_conversion_rate as update_left_to_right_conversion_rate,
},
millau_messages_to_rialto::MillauMessagesToRialto as LeftToRightMessageLane,
rialto_messages_to_millau::RialtoMessagesToMillau as RightToLeftMessageLane,
};
async fn left_create_account(
@@ -180,51 +170,45 @@ macro_rules! select_bridge {
type LeftAccountIdConverter = bp_rococo::AccountIdConverter;
type RightAccountIdConverter = bp_wococo::AccountIdConverter;
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::{
standalone_metrics as left_to_right_standalone_metrics,
run as left_to_right_messages,
},
wococo_messages_to_rococo::{
run as right_to_left_messages,
},
rococo_messages_to_wococo::RococoMessagesToWococo as LeftToRightMessageLane,
wococo_messages_to_rococo::WococoMessagesToRococo as RightToLeftMessageLane,
};
async fn update_right_to_left_conversion_rate(
_client: Client<Left>,
_signer: <Left as TransactionSignScheme>::AccountKeyPair,
_updated_rate: f64,
) -> anyhow::Result<()> {
Err(anyhow::format_err!("Conversion rate is not supported by this bridge"))
}
async fn update_left_to_right_conversion_rate(
_client: Client<Right>,
_signer: <Right as TransactionSignScheme>::AccountKeyPair,
_updated_rate: f64,
) -> anyhow::Result<()> {
Err(anyhow::format_err!("Conversion rate is not supported by this bridge"))
}
async fn left_create_account(
_left_client: Client<Left>,
_left_sign: <Left as TransactionSignScheme>::AccountKeyPair,
_account_id: AccountIdOf<Left>,
left_client: Client<Left>,
left_sign: <Left as TransactionSignScheme>::AccountKeyPair,
account_id: AccountIdOf<Left>,
) -> anyhow::Result<()> {
Err(anyhow::format_err!("Account creation is not supported by this bridge"))
submit_signed_extrinsic(
left_client,
left_sign,
relay_rococo_client::runtime::Call::Balances(
relay_rococo_client::runtime::BalancesCall::transfer(
bp_rococo::AccountAddress::Id(account_id),
bp_rococo::EXISTENTIAL_DEPOSIT.into(),
),
),
)
.await
}
async fn right_create_account(
_right_client: Client<Right>,
_right_sign: <Right as TransactionSignScheme>::AccountKeyPair,
_account_id: AccountIdOf<Right>,
right_client: Client<Right>,
right_sign: <Right as TransactionSignScheme>::AccountKeyPair,
account_id: AccountIdOf<Right>,
) -> anyhow::Result<()> {
Err(anyhow::format_err!("Account creation is not supported by this bridge"))
submit_signed_extrinsic(
right_client,
right_sign,
relay_wococo_client::runtime::Call::Balances(
relay_wococo_client::runtime::BalancesCall::transfer(
bp_wococo::AccountAddress::Id(account_id),
bp_wococo::EXISTENTIAL_DEPOSIT.into(),
),
),
)
.await
}
$generic
@@ -243,21 +227,9 @@ macro_rules! select_bridge {
type LeftAccountIdConverter = bp_kusama::AccountIdConverter;
type RightAccountIdConverter = bp_polkadot::AccountIdConverter;
const MAX_MISSING_LEFT_HEADERS_AT_RIGHT: bp_kusama::BlockNumber =
bp_kusama::SESSION_LENGTH;
const MAX_MISSING_RIGHT_HEADERS_AT_LEFT: bp_polkadot::BlockNumber =
bp_polkadot::SESSION_LENGTH;
use crate::chains::{
kusama_messages_to_polkadot::{
standalone_metrics as left_to_right_standalone_metrics,
run as left_to_right_messages,
update_polkadot_to_kusama_conversion_rate as update_right_to_left_conversion_rate,
},
polkadot_messages_to_kusama::{
run as right_to_left_messages,
update_kusama_to_polkadot_conversion_rate as update_left_to_right_conversion_rate,
},
kusama_messages_to_polkadot::KusamaMessagesToPolkadot as LeftToRightMessageLane,
polkadot_messages_to_kusama::PolkadotMessagesToKusama as RightToLeftMessageLane,
};
async fn left_create_account(
@@ -265,29 +237,17 @@ macro_rules! select_bridge {
left_sign: <Left as TransactionSignScheme>::AccountKeyPair,
account_id: AccountIdOf<Left>,
) -> anyhow::Result<()> {
let left_genesis_hash = *left_client.genesis_hash();
left_client
.submit_signed_extrinsic(
left_sign.public().into(),
move |_, transaction_nonce| {
Bytes(
Left::sign_transaction(left_genesis_hash, &left_sign, relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
relay_kusama_client::runtime::Call::Balances(
relay_kusama_client::runtime::BalancesCall::transfer(
bp_kusama::AccountAddress::Id(account_id),
bp_kusama::EXISTENTIAL_DEPOSIT.into(),
),
),
transaction_nonce,
),
).encode()
)
},
)
.await
.map(drop)
.map_err(|e| anyhow::format_err!("{}", e))
submit_signed_extrinsic(
left_client,
left_sign,
relay_kusama_client::runtime::Call::Balances(
relay_kusama_client::runtime::BalancesCall::transfer(
bp_kusama::AccountAddress::Id(account_id),
bp_kusama::EXISTENTIAL_DEPOSIT.into(),
),
),
)
.await
}
async fn right_create_account(
@@ -295,29 +255,17 @@ macro_rules! select_bridge {
right_sign: <Right as TransactionSignScheme>::AccountKeyPair,
account_id: AccountIdOf<Right>,
) -> anyhow::Result<()> {
let right_genesis_hash = *right_client.genesis_hash();
right_client
.submit_signed_extrinsic(
right_sign.public().into(),
move |_, transaction_nonce| {
Bytes(
Right::sign_transaction(right_genesis_hash, &right_sign, relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
relay_polkadot_client::runtime::Call::Balances(
relay_polkadot_client::runtime::BalancesCall::transfer(
bp_polkadot::AccountAddress::Id(account_id),
bp_polkadot::EXISTENTIAL_DEPOSIT.into(),
),
),
transaction_nonce,
),
).encode()
)
},
)
.await
.map(drop)
.map_err(|e| anyhow::format_err!("{}", e))
submit_signed_extrinsic(
right_client,
right_sign,
relay_polkadot_client::runtime::Call::Balances(
relay_polkadot_client::runtime::BalancesCall::transfer(
bp_polkadot::AccountAddress::Id(account_id),
bp_polkadot::EXISTENTIAL_DEPOSIT.into(),
),
),
)
.await
}
$generic
@@ -363,11 +311,13 @@ impl RelayHeadersAndMessages {
let metrics_params: MetricsParams = params.shared.prometheus_params.into();
let metrics_params = relay_utils::relay_metrics(metrics_params).into_params();
let left_to_right_metrics =
left_to_right_standalone_metrics(left_client.clone(), right_client.clone())?;
substrate_relay_helper::messages_metrics::standalone_metrics::<
LeftToRightMessageLane,
>(left_client.clone(), right_client.clone())?;
let right_to_left_metrics = left_to_right_metrics.clone().reverse();
// start conversion rate update loops for left/right chains
if let Some(left_messages_pallet_owner) = left_messages_pallet_owner {
if let Some(left_messages_pallet_owner) = left_messages_pallet_owner.clone() {
let left_client = left_client.clone();
let format_err = || {
anyhow::format_err!(
@@ -376,7 +326,15 @@ impl RelayHeadersAndMessages {
Left::NAME
)
};
substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop(
substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop::<
LeftToRightMessageLane,
Left,
>(
left_client.clone(),
TransactionParams {
signer: left_messages_pallet_owner.clone(),
mortality: left_transactions_mortality,
},
left_to_right_metrics
.target_to_source_conversion_rate
.as_ref()
@@ -393,24 +351,9 @@ impl RelayHeadersAndMessages {
.ok_or_else(format_err)?
.shared_value_ref(),
CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
move |new_rate| {
log::info!(
target: "bridge",
"Going to update {} -> {} (on {}) conversion rate to {}.",
Right::NAME,
Left::NAME,
Left::NAME,
new_rate,
);
update_right_to_left_conversion_rate(
left_client.clone(),
left_messages_pallet_owner.clone(),
new_rate,
)
},
);
}
if let Some(right_messages_pallet_owner) = right_messages_pallet_owner {
if let Some(right_messages_pallet_owner) = right_messages_pallet_owner.clone() {
let right_client = right_client.clone();
let format_err = || {
anyhow::format_err!(
@@ -419,38 +362,31 @@ impl RelayHeadersAndMessages {
Right::NAME
)
};
substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop(
substrate_relay_helper::conversion_rate_update::run_conversion_rate_update_loop::<
RightToLeftMessageLane,
Right,
>(
right_client.clone(),
TransactionParams {
signer: right_messages_pallet_owner.clone(),
mortality: right_transactions_mortality,
},
right_to_left_metrics
.target_to_source_conversion_rate
.as_ref()
.ok_or_else(format_err)?
.shared_value_ref(),
left_to_right_metrics
.source_to_base_conversion_rate
.as_ref()
.ok_or_else(format_err)?
.shared_value_ref(),
left_to_right_metrics
right_to_left_metrics
.target_to_base_conversion_rate
.as_ref()
.ok_or_else(format_err)?
.shared_value_ref(),
right_to_left_metrics
.source_to_base_conversion_rate
.as_ref()
.ok_or_else(format_err)?
.shared_value_ref(),
CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO,
move |new_rate| {
log::info!(
target: "bridge",
"Going to update {} -> {} (on {}) conversion rate to {}.",
Left::NAME,
Right::NAME,
Right::NAME,
new_rate,
);
update_left_to_right_conversion_rate(
right_client.clone(),
right_messages_pallet_owner.clone(),
new_rate,
)
},
);
}
@@ -493,21 +429,55 @@ impl RelayHeadersAndMessages {
}
}
// add balance-related metrics
let metrics_params =
substrate_relay_helper::messages_metrics::add_relay_balances_metrics(
left_client.clone(),
metrics_params,
Some(left_sign.public().into()),
left_messages_pallet_owner.map(|kp| kp.public().into()),
)
.await?;
let metrics_params =
substrate_relay_helper::messages_metrics::add_relay_balances_metrics(
right_client.clone(),
metrics_params,
Some(right_sign.public().into()),
right_messages_pallet_owner.map(|kp| kp.public().into()),
)
.await?;
// start on-demand header relays
let left_to_right_on_demand_headers = OnDemandHeadersRelay::new(
let left_to_right_transaction_params = TransactionParams {
mortality: right_transactions_mortality,
signer: right_sign.clone(),
};
let right_to_left_transaction_params = TransactionParams {
mortality: left_transactions_mortality,
signer: left_sign.clone(),
};
LeftToRightFinality::start_relay_guards(
&right_client,
&left_to_right_transaction_params,
params.right.can_start_version_guard(),
)
.await?;
RightToLeftFinality::start_relay_guards(
&left_client,
&right_to_left_transaction_params,
params.left.can_start_version_guard(),
)
.await?;
let left_to_right_on_demand_headers = OnDemandHeadersRelay::new::<LeftToRightFinality>(
left_client.clone(),
right_client.clone(),
right_transactions_mortality,
LeftToRightFinality::new(right_client.clone(), right_sign.clone()),
MAX_MISSING_LEFT_HEADERS_AT_RIGHT,
left_to_right_transaction_params,
params.shared.only_mandatory_headers,
);
let right_to_left_on_demand_headers = OnDemandHeadersRelay::new(
let right_to_left_on_demand_headers = OnDemandHeadersRelay::new::<RightToLeftFinality>(
right_client.clone(),
left_client.clone(),
left_transactions_mortality,
RightToLeftFinality::new(left_client.clone(), left_sign.clone()),
MAX_MISSING_RIGHT_HEADERS_AT_LEFT,
right_to_left_transaction_params,
params.shared.only_mandatory_headers,
);
@@ -515,13 +485,19 @@ impl RelayHeadersAndMessages {
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 {
let left_to_right_messages = substrate_relay_helper::messages_lane::run::<
LeftToRightMessageLane,
>(MessagesRelayParams {
source_client: left_client.clone(),
source_sign: left_sign.clone(),
source_transactions_mortality: left_transactions_mortality,
source_transaction_params: TransactionParams {
signer: left_sign.clone(),
mortality: left_transactions_mortality,
},
target_client: right_client.clone(),
target_sign: right_sign.clone(),
target_transactions_mortality: right_transactions_mortality,
target_transaction_params: TransactionParams {
signer: right_sign.clone(),
mortality: right_transactions_mortality,
},
source_to_target_headers_relay: Some(left_to_right_on_demand_headers.clone()),
target_to_source_headers_relay: Some(right_to_left_on_demand_headers.clone()),
lane_id: lane,
@@ -531,13 +507,19 @@ impl RelayHeadersAndMessages {
})
.map_err(|e| anyhow::format_err!("{}", e))
.boxed();
let right_to_left_messages = right_to_left_messages(MessagesRelayParams {
let right_to_left_messages = substrate_relay_helper::messages_lane::run::<
RightToLeftMessageLane,
>(MessagesRelayParams {
source_client: right_client.clone(),
source_sign: right_sign.clone(),
source_transactions_mortality: right_transactions_mortality,
source_transaction_params: TransactionParams {
signer: right_sign.clone(),
mortality: right_transactions_mortality,
},
target_client: left_client.clone(),
target_sign: left_sign.clone(),
target_transactions_mortality: left_transactions_mortality,
target_transaction_params: TransactionParams {
signer: left_sign.clone(),
mortality: left_transactions_mortality,
},
source_to_target_headers_relay: Some(right_to_left_on_demand_headers.clone()),
target_to_source_headers_relay: Some(left_to_right_on_demand_headers.clone()),
lane_id: lane,
@@ -561,3 +543,34 @@ impl RelayHeadersAndMessages {
})
}
}
/// Sign and submit transaction with given call to the chain.
async fn submit_signed_extrinsic<C: Chain + TransactionSignScheme<Chain = C>>(
client: Client<C>,
sign: C::AccountKeyPair,
call: CallOf<C>,
) -> anyhow::Result<()>
where
AccountIdOf<C>: From<<<C as TransactionSignScheme>::AccountKeyPair as Pair>::Public>,
CallOf<C>: Send,
{
let genesis_hash = *client.genesis_hash();
let (spec_version, transaction_version) = client.simple_runtime_version().await?;
client
.submit_signed_extrinsic(sign.public().into(), move |_, transaction_nonce| {
Ok(Bytes(
C::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash,
signer: sign,
era: relay_substrate_client::TransactionEra::immortal(),
unsigned: UnsignedTransaction::new(call.into(), transaction_nonce),
})?
.encode(),
))
})
.await
.map(drop)
.map_err(|e| anyhow::format_err!("{}", e))
}
@@ -18,7 +18,7 @@ use structopt::StructOpt;
use strum::{EnumString, EnumVariantNames, VariantNames};
use messages_relay::relay_strategy::MixStrategy;
use substrate_relay_helper::messages_lane::MessagesRelayParams;
use substrate_relay_helper::{messages_lane::MessagesRelayParams, TransactionParams};
use crate::{
cli::{
@@ -84,13 +84,17 @@ impl RelayMessages {
let relayer_mode = self.relayer_mode.into();
let relay_strategy = MixStrategy::new(relayer_mode);
relay_messages(MessagesRelayParams {
substrate_relay_helper::messages_lane::run::<MessagesLane>(MessagesRelayParams {
source_client,
source_sign,
source_transactions_mortality,
source_transaction_params: TransactionParams {
signer: source_sign,
mortality: source_transactions_mortality,
},
target_client,
target_sign,
target_transactions_mortality,
target_transaction_params: TransactionParams {
signer: target_sign,
mortality: target_transactions_mortality,
},
source_to_target_headers_relay: None,
target_to_source_headers_relay: None,
lane_id: self.lane.into(),
@@ -19,9 +19,10 @@ use crate::cli::{Balance, TargetConnectionParams, TargetSigningParams};
use codec::{Decode, Encode};
use num_traits::{One, Zero};
use relay_substrate_client::{
BlockWithJustification, Chain, Client, Error as SubstrateError, HeaderOf, TransactionSignScheme,
BlockWithJustification, Chain, Client, Error as SubstrateError, HeaderIdOf, HeaderOf,
SignParam, TransactionSignScheme,
};
use relay_utils::FailedClient;
use relay_utils::{FailedClient, HeaderId};
use sp_core::Bytes;
use sp_runtime::{
traits::{Hash, Header as HeaderT},
@@ -29,6 +30,7 @@ use sp_runtime::{
};
use structopt::StructOpt;
use strum::{EnumString, EnumVariantNames, VariantNames};
use substrate_relay_helper::TransactionParams;
/// Start resubmit transactions process.
#[derive(StructOpt)]
@@ -115,13 +117,16 @@ impl ResubmitTransactions {
select_bridge!(self.chain, {
let relay_loop_name = format!("ResubmitTransactions{}", Target::NAME);
let client = self.target.to_client::<Target>().await?;
let key_pair = self.target_sign.to_keypair::<Target>()?;
let transaction_params = TransactionParams {
signer: self.target_sign.to_keypair::<Target>()?,
mortality: self.target_sign.target_transactions_mortality,
};
relay_utils::relay_loop((), client)
.run(relay_loop_name, move |_, client, _| {
run_until_connection_lost::<Target, TargetSign>(
client,
key_pair.clone(),
transaction_params.clone(),
Context {
strategy: self.strategy,
best_header: HeaderOf::<Target>::new(
@@ -212,13 +217,14 @@ impl<C: Chain> Context<C> {
/// Run resubmit transactions loop.
async fn run_until_connection_lost<C: Chain, S: TransactionSignScheme<Chain = C>>(
client: Client<C>,
key_pair: S::AccountKeyPair,
transaction_params: TransactionParams<S::AccountKeyPair>,
mut context: Context<C>,
) -> Result<(), FailedClient> {
loop {
async_std::task::sleep(C::AVERAGE_BLOCK_INTERVAL).await;
let result = run_loop_iteration::<C, S>(client.clone(), key_pair.clone(), context).await;
let result =
run_loop_iteration::<C, S>(client.clone(), transaction_params.clone(), context).await;
context = match result {
Ok(context) => context,
Err(error) => {
@@ -237,20 +243,21 @@ async fn run_until_connection_lost<C: Chain, S: TransactionSignScheme<Chain = C>
/// Run single loop iteration.
async fn run_loop_iteration<C: Chain, S: TransactionSignScheme<Chain = C>>(
client: Client<C>,
key_pair: S::AccountKeyPair,
transaction_params: TransactionParams<S::AccountKeyPair>,
mut context: Context<C>,
) -> Result<Context<C>, SubstrateError> {
// correct best header is required for all other actions
context.best_header = client.best_header().await?;
// check if there's queued transaction, signed by given author
let original_transaction = match lookup_signer_transaction::<C, S>(&client, &key_pair).await? {
Some(original_transaction) => original_transaction,
None => {
log::trace!(target: "bridge", "No {} transactions from required signer in the txpool", C::NAME);
return Ok(context)
},
};
let original_transaction =
match lookup_signer_transaction::<C, S>(&client, &transaction_params.signer).await? {
Some(original_transaction) => original_transaction,
None => {
log::trace!(target: "bridge", "No {} transactions from required signer in the txpool", C::NAME);
return Ok(context)
},
};
let original_transaction_hash = C::Hasher::hash(&original_transaction.encode());
let context = context.notice_transaction(original_transaction_hash);
@@ -280,8 +287,8 @@ async fn run_loop_iteration<C: Chain, S: TransactionSignScheme<Chain = C>>(
// update transaction tip
let (is_updated, updated_transaction) = update_transaction_tip::<C, S>(
&client,
&key_pair,
context.best_header.hash(),
&transaction_params,
HeaderId(*context.best_header.number(), context.best_header.hash()),
original_transaction,
context.tip_step,
context.tip_limit,
@@ -397,20 +404,21 @@ fn select_transaction_from_queue<C: Chain>(
/// Try to find appropriate tip for transaction so that its priority is larger than given.
async fn update_transaction_tip<C: Chain, S: TransactionSignScheme<Chain = C>>(
client: &Client<C>,
key_pair: &S::AccountKeyPair,
at_block: C::Hash,
transaction_params: &TransactionParams<S::AccountKeyPair>,
at_block: HeaderIdOf<C>,
tx: S::SignedTransaction,
tip_step: C::Balance,
tip_limit: C::Balance,
target_priority: TransactionPriority,
) -> Result<(bool, S::SignedTransaction), SubstrateError> {
let stx = format!("{:?}", tx);
let mut current_priority = client.validate_transaction(at_block, tx.clone()).await??.priority;
let mut current_priority = client.validate_transaction(at_block.1, tx.clone()).await??.priority;
let mut unsigned_tx = S::parse_transaction(tx).ok_or_else(|| {
SubstrateError::Custom(format!("Failed to parse {} transaction {}", C::NAME, stx,))
})?;
let old_tip = unsigned_tx.tip;
let (spec_version, transaction_version) = client.simple_runtime_version().await?;
while current_priority < target_priority {
let next_tip = unsigned_tx.tip + tip_step;
if next_tip > tip_limit {
@@ -429,13 +437,15 @@ async fn update_transaction_tip<C: Chain, S: TransactionSignScheme<Chain = C>>(
unsigned_tx.tip = next_tip;
current_priority = client
.validate_transaction(
at_block,
S::sign_transaction(
*client.genesis_hash(),
key_pair,
relay_substrate_client::TransactionEra::immortal(),
unsigned_tx.clone(),
),
at_block.1,
S::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash: *client.genesis_hash(),
signer: transaction_params.signer.clone(),
era: relay_substrate_client::TransactionEra::immortal(),
unsigned: unsigned_tx.clone(),
})?,
)
.await??
.priority;
@@ -451,12 +461,17 @@ async fn update_transaction_tip<C: Chain, S: TransactionSignScheme<Chain = C>>(
Ok((
old_tip != unsigned_tx.tip,
S::sign_transaction(
*client.genesis_hash(),
key_pair,
relay_substrate_client::TransactionEra::immortal(),
unsigned_tx,
),
S::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash: *client.genesis_hash(),
signer: transaction_params.signer.clone(),
era: relay_substrate_client::TransactionEra::new(
at_block,
transaction_params.mortality,
),
unsigned: unsigned_tx,
})?,
))
}
@@ -17,15 +17,15 @@
use crate::cli::{
bridge::FullBridge,
encode_call::{self, CliEncodeCall},
estimate_fee::estimate_message_delivery_and_dispatch_fee,
Balance, CliChain, ExplicitOrMaximal, HexBytes, HexLaneId, Origins, SourceConnectionParams,
SourceSigningParams, TargetSigningParams,
estimate_fee::{estimate_message_delivery_and_dispatch_fee, ConversionRateOverride},
Balance, ExplicitOrMaximal, HexBytes, HexLaneId, Origins, SourceConnectionParams,
SourceSigningParams, TargetConnectionParams, TargetSigningParams,
};
use bp_message_dispatch::{CallOrigin, MessagePayload};
use bp_runtime::BalanceOf;
use bp_runtime::Chain as _;
use codec::Encode;
use frame_support::weights::Weight;
use relay_substrate_client::{Chain, TransactionSignScheme, UnsignedTransaction};
use relay_substrate_client::{Chain, SignParam, TransactionSignScheme, UnsignedTransaction};
use sp_core::{Bytes, Pair};
use sp_runtime::{traits::IdentifyAccount, AccountId32, MultiSignature, MultiSigner};
use std::fmt::Debug;
@@ -66,6 +66,12 @@ pub struct SendMessage {
/// Hex-encoded lane id. Defaults to `00000000`.
#[structopt(long, default_value = "00000000")]
lane: HexLaneId,
/// A way to override conversion rate between bridge tokens.
///
/// If not specified, conversion rate from runtime storage is used. It may be obsolete and
/// your message won't be relayed.
#[structopt(long)]
conversion_rate_override: Option<ConversionRateOverride>,
/// Where dispatch fee is paid?
#[structopt(
long,
@@ -88,10 +94,16 @@ pub struct SendMessage {
/// `SourceAccount`.
#[structopt(long, possible_values = &Origins::variants(), default_value = "Source")]
origin: Origins,
// Normally we don't need to connect to the target chain to send message. But for testing
// we may want to use **actual** `spec_version` of the target chain when composing a message.
// Then we'll need to read version from the target chain node.
#[structopt(flatten)]
target: TargetConnectionParams,
}
impl SendMessage {
pub fn encode_payload(
pub async fn encode_payload(
&mut self,
) -> anyhow::Result<MessagePayload<AccountId32, MultiSigner, MultiSignature, Vec<u8>>> {
crate::select_full_bridge!(self.bridge, {
@@ -110,18 +122,23 @@ impl SendMessage {
encode_call::preprocess_call::<Source, Target>(message, bridge.bridge_instance_index());
let target_call = Target::encode_call(message)?;
let target_spec_version = self.target.selected_chain_spec_version::<Target>().await?;
let payload = {
let target_call_weight = prepare_call_dispatch_weight(
dispatch_weight,
ExplicitOrMaximal::Explicit(Target::get_dispatch_info(&target_call)?.weight),
|| {
Ok(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_spec_version,
target_call_weight,
match origin {
Origins::Source => CallOrigin::SourceAccount(source_account_id),
@@ -130,7 +147,7 @@ impl SendMessage {
let digest = account_ownership_digest(
&target_call,
source_account_id.clone(),
Target::RUNTIME_VERSION.spec_version,
target_spec_version,
);
let target_origin_public = target_sign.public();
let digest_signature = target_sign.sign(&digest);
@@ -152,17 +169,19 @@ impl SendMessage {
/// Run the command.
pub async fn run(mut self) -> anyhow::Result<()> {
crate::select_full_bridge!(self.bridge, {
let payload = self.encode_payload()?;
let payload = self.encode_payload().await?;
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 conversion_rate_override = self.conversion_rate_override;
let fee = match self.fee {
Some(fee) => fee,
None => Balance(
estimate_message_delivery_and_dispatch_fee::<BalanceOf<Source>, _, _>(
estimate_message_delivery_and_dispatch_fee::<Source, Target, _>(
&source_client,
conversion_rate_override,
ESTIMATE_MESSAGE_FEE_METHOD,
lane,
payload.clone(),
@@ -171,6 +190,7 @@ impl SendMessage {
),
};
let dispatch_weight = payload.weight;
let payload_len = payload.encode().len();
let send_message_call = Source::encode_call(&encode_call::Call::BridgeSendMessage {
bridge_instance_index: self.bridge.bridge_instance_index(),
lane: self.lane,
@@ -179,25 +199,31 @@ impl SendMessage {
})?;
let source_genesis_hash = *source_client.genesis_hash();
let (spec_version, transaction_version) =
source_client.simple_runtime_version().await?;
let estimated_transaction_fee = source_client
.estimate_extrinsic_fee(Bytes(
Source::sign_transaction(
source_genesis_hash,
&source_sign,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(send_message_call.clone(), 0),
)
Source::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash: source_genesis_hash,
signer: source_sign.clone(),
era: relay_substrate_client::TransactionEra::immortal(),
unsigned: UnsignedTransaction::new(send_message_call.clone(), 0),
})?
.encode(),
))
.await?;
source_client
.submit_signed_extrinsic(source_sign.public().into(), move |_, transaction_nonce| {
let signed_source_call = Source::sign_transaction(
source_genesis_hash,
&source_sign,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(send_message_call, transaction_nonce),
)
let signed_source_call = Source::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash: source_genesis_hash,
signer: source_sign.clone(),
era: relay_substrate_client::TransactionEra::immortal(),
unsigned: UnsignedTransaction::new(send_message_call, transaction_nonce),
})?
.encode();
log::info!(
@@ -205,7 +231,7 @@ impl SendMessage {
"Sending message to {}. Lane: {:?}. Size: {}. Dispatch weight: {}. Fee: {}",
Target::NAME,
lane,
signed_source_call.len(),
payload_len,
dispatch_weight,
fee,
);
@@ -225,7 +251,7 @@ impl SendMessage {
HexBytes::encode(&signed_source_call)
);
Bytes(signed_source_call)
Ok(Bytes(signed_source_call))
})
.await?;
});
@@ -236,12 +262,16 @@ impl SendMessage {
fn prepare_call_dispatch_weight(
user_specified_dispatch_weight: &Option<ExplicitOrMaximal<Weight>>,
weight_from_pre_dispatch_call: ExplicitOrMaximal<Weight>,
weight_from_pre_dispatch_call: impl Fn() -> anyhow::Result<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,
) -> anyhow::Result<Weight> {
match user_specified_dispatch_weight
.clone()
.map(Ok)
.unwrap_or_else(weight_from_pre_dispatch_call)?
{
ExplicitOrMaximal::Explicit(weight) => Ok(weight),
ExplicitOrMaximal::Maximal => Ok(maximal_allowed_weight),
}
}
@@ -283,10 +313,11 @@ pub(crate) fn compute_maximal_message_dispatch_weight(maximal_extrinsic_weight:
#[cfg(test)]
mod tests {
use super::*;
use crate::cli::CliChain;
use hex_literal::hex;
#[test]
fn send_remark_rialto_to_millau() {
#[async_std::test]
async fn send_remark_rialto_to_millau() {
// given
let mut send_message = SendMessage::from_iter(vec![
"send-message",
@@ -295,20 +326,22 @@ mod tests {
"1234",
"--source-signer",
"//Alice",
"--conversion-rate-override",
"0.75",
"remark",
"--remark-payload",
"1234",
]);
// when
let payload = send_message.encode_payload().unwrap();
let payload = send_message.encode_payload().await.unwrap();
// then
assert_eq!(
payload,
MessagePayload {
spec_version: relay_millau_client::Millau::RUNTIME_VERSION.spec_version,
weight: 576000,
weight: 0,
origin: CallOrigin::SourceAccount(
sp_keyring::AccountKeyring::Alice.to_account_id()
),
@@ -318,8 +351,8 @@ mod tests {
);
}
#[test]
fn send_remark_millau_to_rialto() {
#[async_std::test]
async fn send_remark_millau_to_rialto() {
// given
let mut send_message = SendMessage::from_iter(vec![
"send-message",
@@ -332,13 +365,15 @@ mod tests {
"Target",
"--target-signer",
"//Bob",
"--conversion-rate-override",
"metric",
"remark",
"--remark-payload",
"1234",
]);
// when
let payload = send_message.encode_payload().unwrap();
let payload = send_message.encode_payload().await.unwrap();
// then
// Since signatures are randomized we extract it from here and only check the rest.
@@ -350,7 +385,7 @@ mod tests {
payload,
MessagePayload {
spec_version: relay_millau_client::Millau::RUNTIME_VERSION.spec_version,
weight: 576000,
weight: 0,
origin: CallOrigin::TargetAccount(
sp_keyring::AccountKeyring::Alice.to_account_id(),
sp_keyring::AccountKeyring::Bob.into(),
@@ -382,8 +417,8 @@ mod tests {
assert!(send_message.is_ok());
}
#[test]
fn accepts_non_default_dispatch_fee_payment() {
#[async_std::test]
async fn accepts_non_default_dispatch_fee_payment() {
// given
let mut send_message = SendMessage::from_iter(vec![
"send-message",
@@ -398,7 +433,7 @@ mod tests {
]);
// when
let payload = send_message.encode_payload().unwrap();
let payload = send_message.encode_payload().await.unwrap();
// then
assert_eq!(
@@ -29,15 +29,15 @@ use strum::{EnumString, EnumVariantNames, VariantNames};
use frame_support::dispatch::GetDispatchInfo;
use relay_substrate_client::{
AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, CallOf, Chain, ChainWithBalances,
Client, Error as SubstrateError, HashOf, SignatureOf, Subscription, TransactionSignScheme,
TransactionStatusOf, UnsignedTransaction,
Client, Error as SubstrateError, HashOf, SignParam, SignatureOf, Subscription,
TransactionSignScheme, TransactionStatusOf, UnsignedTransaction,
};
use sp_core::{blake2_256, storage::StorageKey, Bytes, Pair, H256, U256};
use sp_core::{blake2_256, storage::StorageKey, Bytes, Pair, U256};
use sp_runtime::traits::{Convert, Header as HeaderT};
use crate::cli::{
Balance, CliChain, SourceConnectionParams, SourceSigningParams, TargetConnectionParams,
TargetSigningParams,
estimate_fee::ConversionRateOverride, Balance, CliChain, SourceConnectionParams,
SourceSigningParams, TargetConnectionParams, TargetSigningParams,
};
/// Swap tokens.
@@ -65,6 +65,18 @@ pub struct SwapTokens {
/// Target chain balance that target signer wants to swap.
#[structopt(long)]
target_balance: Balance,
/// A way to override conversion rate from target to source tokens.
///
/// If not specified, conversion rate from runtime storage is used. It may be obsolete and
/// your message won't be relayed.
#[structopt(long)]
target_to_source_conversion_rate_override: Option<ConversionRateOverride>,
/// A way to override conversion rate from source to target tokens.
///
/// If not specified, conversion rate from runtime storage is used. It may be obsolete and
/// your message won't be relayed.
#[structopt(long)]
source_to_target_conversion_rate_override: Option<ConversionRateOverride>,
}
/// Token swap type.
@@ -98,6 +110,8 @@ macro_rules! select_bridge {
SwapTokensBridge::MillauToRialto => {
type Source = relay_millau_client::Millau;
type Target = relay_rialto_client::Rialto;
const SOURCE_SPEC_VERSION: u32 = millau_runtime::VERSION.spec_version;
const TARGET_SPEC_VERSION: u32 = rialto_runtime::VERSION.spec_version;
type FromSwapToThisAccountIdConverter = bp_rialto::AccountIdConverter;
@@ -114,9 +128,6 @@ macro_rules! select_bridge {
const SOURCE_CHAIN_ID: bp_runtime::ChainId = bp_runtime::MILLAU_CHAIN_ID;
const TARGET_CHAIN_ID: bp_runtime::ChainId = bp_runtime::RIALTO_CHAIN_ID;
const SOURCE_SPEC_VERSION: u32 = millau_runtime::VERSION.spec_version;
const TARGET_SPEC_VERSION: u32 = rialto_runtime::VERSION.spec_version;
const SOURCE_TO_TARGET_LANE_ID: bp_messages::LaneId = *b"swap";
const TARGET_TO_SOURCE_LANE_ID: bp_messages::LaneId = [0, 0, 0, 0];
@@ -134,6 +145,10 @@ impl SwapTokens {
let source_sign = self.source_sign.to_keypair::<Target>()?;
let target_client = self.target.to_client::<Target>().await?;
let target_sign = self.target_sign.to_keypair::<Target>()?;
let target_to_source_conversion_rate_override =
self.target_to_source_conversion_rate_override;
let source_to_target_conversion_rate_override =
self.source_to_target_conversion_rate_override;
// names of variables in this function are matching names used by the
// `pallet-bridge-token-swap`
@@ -199,9 +214,14 @@ impl SwapTokens {
// prepare `create_swap` call
let target_public_at_bridged_chain: AccountPublicOf<Target> =
target_sign.public().into();
let swap_delivery_and_dispatch_fee: BalanceOf<Source> =
crate::cli::estimate_fee::estimate_message_delivery_and_dispatch_fee(
let swap_delivery_and_dispatch_fee =
crate::cli::estimate_fee::estimate_message_delivery_and_dispatch_fee::<
Source,
Target,
_,
>(
&source_client,
target_to_source_conversion_rate_override.clone(),
ESTIMATE_SOURCE_TO_TARGET_MESSAGE_FEE_METHOD,
SOURCE_TO_TARGET_LANE_ID,
bp_message_dispatch::MessagePayload {
@@ -234,20 +254,27 @@ impl SwapTokens {
// start tokens swap
let source_genesis_hash = *source_client.genesis_hash();
let create_swap_signer = source_sign.clone();
let (spec_version, transaction_version) =
source_client.simple_runtime_version().await?;
let swap_created_at = wait_until_transaction_is_finalized::<Source>(
source_client
.submit_and_watch_signed_extrinsic(
accounts.source_account_at_this_chain.clone(),
move |_, transaction_nonce| {
Bytes(
Source::sign_transaction(
source_genesis_hash,
&create_swap_signer,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(create_swap_call, transaction_nonce),
)
Ok(Bytes(
Source::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash: source_genesis_hash,
signer: create_swap_signer,
era: relay_substrate_client::TransactionEra::immortal(),
unsigned: UnsignedTransaction::new(
create_swap_call.into(),
transaction_nonce,
),
})?
.encode(),
)
))
},
)
.await?,
@@ -255,11 +282,10 @@ impl SwapTokens {
.await?;
// read state of swap after it has been created
let token_swap_hash: H256 = token_swap.using_encoded(blake2_256).into();
let token_swap_storage_key = bp_runtime::storage_map_final_key_identity(
let token_swap_hash = token_swap.hash();
let token_swap_storage_key = bp_token_swap::storage_keys::pending_swaps_key(
TOKEN_SWAP_PALLET_NAME,
pallet_bridge_token_swap::PENDING_SWAPS_MAP_NAME,
token_swap_hash.as_ref(),
token_swap_hash,
);
match read_token_swap_state(&source_client, swap_created_at, &token_swap_storage_key)
.await?
@@ -337,7 +363,7 @@ impl SwapTokens {
//
if is_transfer_succeeded {
log::info!(target: "bridge", "Claiming the swap swap");
log::info!(target: "bridge", "Claiming the swap");
// prepare `claim_swap` message that will be sent over the bridge
let claim_swap_call: CallOf<Source> =
@@ -351,9 +377,14 @@ impl SwapTokens {
dispatch_fee_payment: bp_runtime::messages::DispatchFeePayment::AtSourceChain,
call: claim_swap_call.encode(),
};
let claim_swap_delivery_and_dispatch_fee: BalanceOf<Target> =
crate::cli::estimate_fee::estimate_message_delivery_and_dispatch_fee(
let claim_swap_delivery_and_dispatch_fee =
crate::cli::estimate_fee::estimate_message_delivery_and_dispatch_fee::<
Target,
Source,
_,
>(
&target_client,
source_to_target_conversion_rate_override.clone(),
ESTIMATE_TARGET_TO_SOURCE_MESSAGE_FEE_METHOD,
TARGET_TO_SOURCE_LANE_ID,
claim_swap_message.clone(),
@@ -369,23 +400,27 @@ impl SwapTokens {
// send `claim_swap` message
let target_genesis_hash = *target_client.genesis_hash();
let (spec_version, transaction_version) =
target_client.simple_runtime_version().await?;
let _ = wait_until_transaction_is_finalized::<Target>(
target_client
.submit_and_watch_signed_extrinsic(
accounts.target_account_at_bridged_chain.clone(),
move |_, transaction_nonce| {
Bytes(
Target::sign_transaction(
target_genesis_hash,
&target_sign,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
send_message_call,
Ok(Bytes(
Target::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash: target_genesis_hash,
signer: target_sign,
era: relay_substrate_client::TransactionEra::immortal(),
unsigned: UnsignedTransaction::new(
send_message_call.into(),
transaction_nonce,
),
)
})?
.encode(),
)
))
},
)
.await?,
@@ -409,23 +444,27 @@ impl SwapTokens {
log::info!(target: "bridge", "Cancelling the swap");
let cancel_swap_call: CallOf<Source> =
pallet_bridge_token_swap::Call::cancel_swap { swap: token_swap.clone() }.into();
let (spec_version, transaction_version) =
source_client.simple_runtime_version().await?;
let _ = wait_until_transaction_is_finalized::<Source>(
source_client
.submit_and_watch_signed_extrinsic(
accounts.source_account_at_this_chain.clone(),
move |_, transaction_nonce| {
Bytes(
Source::sign_transaction(
source_genesis_hash,
&source_sign,
relay_substrate_client::TransactionEra::immortal(),
UnsignedTransaction::new(
cancel_swap_call,
Ok(Bytes(
Source::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash: source_genesis_hash,
signer: source_sign,
era: relay_substrate_client::TransactionEra::immortal(),
unsigned: UnsignedTransaction::new(
cancel_swap_call.into(),
transaction_nonce,
),
)
})?
.encode(),
)
))
},
)
.await?,
@@ -673,6 +712,7 @@ async fn read_token_swap_state<C: Chain>(
#[cfg(test)]
mod tests {
use super::*;
use crate::cli::{RuntimeVersionType, SourceRuntimeVersionParams, TargetRuntimeVersionParams};
#[test]
fn swap_tokens_millau_to_rialto_no_lock() {
@@ -706,6 +746,11 @@ mod tests {
source_host: "127.0.0.1".into(),
source_port: 9000,
source_secure: false,
source_runtime_version: SourceRuntimeVersionParams {
source_version_mode: RuntimeVersionType::Bundle,
source_spec_version: None,
source_transaction_version: None,
}
},
source_sign: SourceSigningParams {
source_signer: Some("//Alice".into()),
@@ -718,6 +763,11 @@ mod tests {
target_host: "127.0.0.1".into(),
target_port: 9001,
target_secure: false,
target_runtime_version: TargetRuntimeVersionParams {
target_version_mode: RuntimeVersionType::Bundle,
target_spec_version: None,
target_transaction_version: None,
}
},
target_sign: TargetSigningParams {
target_signer: Some("//Bob".into()),
@@ -729,6 +779,8 @@ mod tests {
swap_type: TokenSwapType::NoLock,
source_balance: Balance(8000000000),
target_balance: Balance(9000000000),
target_to_source_conversion_rate_override: None,
source_to_target_conversion_rate_override: None,
}
);
}
@@ -754,6 +806,10 @@ mod tests {
"//Bob",
"--target-balance",
"9000000000",
"--target-to-source-conversion-rate-override",
"metric",
"--source-to-target-conversion-rate-override",
"84.56",
"lock-until-block",
"--blocks-before-expire",
"1",
@@ -767,6 +823,11 @@ mod tests {
source_host: "127.0.0.1".into(),
source_port: 9000,
source_secure: false,
source_runtime_version: SourceRuntimeVersionParams {
source_version_mode: RuntimeVersionType::Bundle,
source_spec_version: None,
source_transaction_version: None,
}
},
source_sign: SourceSigningParams {
source_signer: Some("//Alice".into()),
@@ -779,6 +840,11 @@ mod tests {
target_host: "127.0.0.1".into(),
target_port: 9001,
target_secure: false,
target_runtime_version: TargetRuntimeVersionParams {
target_version_mode: RuntimeVersionType::Bundle,
target_spec_version: None,
target_transaction_version: None,
}
},
target_sign: TargetSigningParams {
target_signer: Some("//Bob".into()),
@@ -793,6 +859,10 @@ mod tests {
},
source_balance: Balance(8000000000),
target_balance: Balance(9000000000),
target_to_source_conversion_rate_override: Some(ConversionRateOverride::Metric),
source_to_target_conversion_rate_override: Some(ConversionRateOverride::Explicit(
84.56
)),
}
);
}
@@ -2,14 +2,14 @@
name = "relay-kusama-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.2.0" }
codec = { package = "parity-scale-codec", version = "3.0.0" }
relay-substrate-client = { path = "../client-substrate" }
relay-utils = { path = "../utils" }
scale-info = { version = "1.0", features = ["derive"] }
scale-info = { version = "2.0.1", features = ["derive"] }
# Bridge dependencies
@@ -16,10 +16,12 @@
//! Types used to connect to the Kusama chain.
use bp_messages::MessageNonce;
use codec::Encode;
use frame_support::weights::Weight;
use relay_substrate_client::{
Chain, ChainBase, ChainWithBalances, TransactionEraOf, TransactionSignScheme,
UnsignedTransaction,
Chain, ChainBase, ChainWithBalances, ChainWithGrandpa, ChainWithMessages,
Error as SubstrateError, SignParam, TransactionSignScheme, UnsignedTransaction,
};
use sp_core::{storage::StorageKey, Pair};
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
@@ -44,10 +46,21 @@ impl ChainBase for Kusama {
type Balance = bp_kusama::Balance;
type Index = bp_kusama::Nonce;
type Signature = bp_kusama::Signature;
fn max_extrinsic_size() -> u32 {
bp_kusama::Kusama::max_extrinsic_size()
}
fn max_extrinsic_weight() -> Weight {
bp_kusama::Kusama::max_extrinsic_weight()
}
}
impl Chain for Kusama {
const NAME: &'static str = "Kusama";
const TOKEN_ID: Option<&'static str> = Some("kusama");
const BEST_FINALIZED_HEADER_ID_METHOD: &'static str =
bp_kusama::BEST_FINALIZED_KUSAMA_HEADER_METHOD;
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6);
const STORAGE_PROOF_OVERHEAD: u32 = bp_kusama::EXTRA_STORAGE_PROOF_SIZE;
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_kusama::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
@@ -57,6 +70,24 @@ impl Chain for Kusama {
type WeightToFee = bp_kusama::WeightToFee;
}
impl ChainWithGrandpa for Kusama {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = bp_kusama::WITH_KUSAMA_GRANDPA_PALLET_NAME;
}
impl ChainWithMessages for Kusama {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str =
bp_kusama::WITH_KUSAMA_MESSAGES_PALLET_NAME;
const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str =
bp_kusama::TO_KUSAMA_MESSAGE_DETAILS_METHOD;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_CHAIN: Weight =
bp_kusama::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce =
bp_kusama::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX;
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce =
bp_kusama::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
type WeightInfo = ();
}
impl ChainWithBalances for Kusama {
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
StorageKey(bp_kusama::account_info_storage_key(account_id))
@@ -68,34 +99,30 @@ impl TransactionSignScheme for Kusama {
type AccountKeyPair = sp_core::sr25519::Pair;
type SignedTransaction = crate::runtime::UncheckedExtrinsic;
fn sign_transaction(
genesis_hash: <Self::Chain as ChainBase>::Hash,
signer: &Self::AccountKeyPair,
era: TransactionEraOf<Self::Chain>,
unsigned: UnsignedTransaction<Self::Chain>,
) -> Self::SignedTransaction {
fn sign_transaction(param: SignParam<Self>) -> Result<Self::SignedTransaction, SubstrateError> {
let raw_payload = SignedPayload::new(
unsigned.call,
param.unsigned.call.clone(),
bp_kusama::SignedExtensions::new(
bp_kusama::VERSION,
era,
genesis_hash,
unsigned.nonce,
unsigned.tip,
param.spec_version,
param.transaction_version,
param.era,
param.genesis_hash,
param.unsigned.nonce,
param.unsigned.tip,
),
)
.expect("SignedExtension never fails.");
let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
let signer: sp_runtime::MultiSigner = signer.public().into();
let signature = raw_payload.using_encoded(|payload| param.signer.sign(payload));
let signer: sp_runtime::MultiSigner = param.signer.public().into();
let (call, extra, _) = raw_payload.deconstruct();
bp_kusama::UncheckedExtrinsic::new_signed(
Ok(bp_kusama::UncheckedExtrinsic::new_signed(
call,
sp_runtime::MultiAddress::Id(signer.into_account()),
signature.into(),
extra,
)
))
}
fn is_signed(tx: &Self::SignedTransaction) -> bool {
@@ -70,6 +70,9 @@ pub enum Call {
/// Balances pallet.
#[codec(index = 4)]
Balances(BalancesCall),
/// Utility pallet.
#[codec(index = 24)]
Utility(UtilityCall),
/// Polkadot bridge pallet.
#[codec(index = 110)]
BridgePolkadotGrandpa(BridgePolkadotGrandpaCall),
@@ -102,6 +105,8 @@ pub enum BridgePolkadotGrandpaCall {
),
#[codec(index = 1)]
initialize(bp_header_chain::InitializationData<<PolkadotLike as Chain>::Header>),
#[codec(index = 3)]
set_operational(bool),
}
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
@@ -136,6 +141,13 @@ pub enum BridgePolkadotMessagesCall {
),
}
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
#[allow(non_camel_case_types)]
pub enum UtilityCall {
#[codec(index = 2)]
batch_all(Vec<Call>),
}
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
pub enum BridgePolkadotMessagesParameter {
#[codec(index = 0)]
@@ -2,16 +2,17 @@
name = "relay-millau-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.2.0" }
codec = { package = "parity-scale-codec", version = "3.0.0" }
relay-substrate-client = { path = "../client-substrate" }
relay-utils = { path = "../utils" }
# Supported Chains
bp-messages = { path = "../../primitives/messages" }
bp-millau = { path = "../../primitives/chain-millau" }
millau-runtime = { path = "../../bin/millau/runtime" }
@@ -16,10 +16,12 @@
//! Types used to connect to the Millau-Substrate chain.
use bp_messages::MessageNonce;
use codec::{Compact, Decode, Encode};
use frame_support::weights::Weight;
use relay_substrate_client::{
BalanceOf, Chain, ChainBase, ChainWithBalances, IndexOf, TransactionEraOf,
TransactionSignScheme, UnsignedTransaction,
BalanceOf, Chain, ChainBase, ChainWithBalances, ChainWithGrandpa, ChainWithMessages,
Error as SubstrateError, IndexOf, SignParam, TransactionSignScheme, UnsignedTransaction,
};
use sp_core::{storage::StorageKey, Pair};
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
@@ -29,7 +31,7 @@ use std::time::Duration;
pub type HeaderId = relay_utils::HeaderId<millau_runtime::Hash, millau_runtime::BlockNumber>;
/// Millau chain definition.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Millau;
impl ChainBase for Millau {
@@ -42,10 +44,40 @@ impl ChainBase for Millau {
type Balance = millau_runtime::Balance;
type Index = millau_runtime::Index;
type Signature = millau_runtime::Signature;
fn max_extrinsic_size() -> u32 {
bp_millau::Millau::max_extrinsic_size()
}
fn max_extrinsic_weight() -> Weight {
bp_millau::Millau::max_extrinsic_weight()
}
}
impl ChainWithGrandpa for Millau {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = bp_millau::WITH_MILLAU_GRANDPA_PALLET_NAME;
}
impl ChainWithMessages for Millau {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str =
bp_millau::WITH_MILLAU_MESSAGES_PALLET_NAME;
const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str =
bp_millau::TO_MILLAU_MESSAGE_DETAILS_METHOD;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_CHAIN: Weight =
bp_millau::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce =
bp_millau::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX;
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce =
bp_millau::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
type WeightInfo = ();
}
impl Chain for Millau {
const NAME: &'static str = "Millau";
// Rialto token has no value, but we associate it with KSM token
const TOKEN_ID: Option<&'static str> = Some("kusama");
const BEST_FINALIZED_HEADER_ID_METHOD: &'static str =
bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(5);
const STORAGE_PROOF_OVERHEAD: u32 = bp_millau::EXTRA_STORAGE_PROOF_SIZE;
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_millau::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
@@ -69,43 +101,40 @@ impl TransactionSignScheme for Millau {
type AccountKeyPair = sp_core::sr25519::Pair;
type SignedTransaction = millau_runtime::UncheckedExtrinsic;
fn sign_transaction(
genesis_hash: <Self::Chain as ChainBase>::Hash,
signer: &Self::AccountKeyPair,
era: TransactionEraOf<Self::Chain>,
unsigned: UnsignedTransaction<Self::Chain>,
) -> Self::SignedTransaction {
fn sign_transaction(param: SignParam<Self>) -> Result<Self::SignedTransaction, SubstrateError> {
let raw_payload = SignedPayload::from_raw(
unsigned.call,
param.unsigned.call.clone(),
(
frame_system::CheckNonZeroSender::<millau_runtime::Runtime>::new(),
frame_system::CheckSpecVersion::<millau_runtime::Runtime>::new(),
frame_system::CheckTxVersion::<millau_runtime::Runtime>::new(),
frame_system::CheckGenesis::<millau_runtime::Runtime>::new(),
frame_system::CheckEra::<millau_runtime::Runtime>::from(era.frame_era()),
frame_system::CheckNonce::<millau_runtime::Runtime>::from(unsigned.nonce),
frame_system::CheckEra::<millau_runtime::Runtime>::from(param.era.frame_era()),
frame_system::CheckNonce::<millau_runtime::Runtime>::from(param.unsigned.nonce),
frame_system::CheckWeight::<millau_runtime::Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<millau_runtime::Runtime>::from(unsigned.tip),
pallet_transaction_payment::ChargeTransactionPayment::<millau_runtime::Runtime>::from(param.unsigned.tip),
),
(
millau_runtime::VERSION.spec_version,
millau_runtime::VERSION.transaction_version,
genesis_hash,
era.signed_payload(genesis_hash),
(),
param.spec_version,
param.transaction_version,
param.genesis_hash,
param.era.signed_payload(param.genesis_hash),
(),
(),
(),
),
);
let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
let signer: sp_runtime::MultiSigner = signer.public().into();
let signature = raw_payload.using_encoded(|payload| param.signer.sign(payload));
let signer: sp_runtime::MultiSigner = param.signer.public().into();
let (call, extra, _) = raw_payload.deconstruct();
millau_runtime::UncheckedExtrinsic::new_signed(
call,
Ok(millau_runtime::UncheckedExtrinsic::new_signed(
call.into_decoded()?,
signer.into_account(),
signature.into(),
extra,
)
))
}
fn is_signed(tx: &Self::SignedTransaction) -> bool {
@@ -124,9 +153,9 @@ impl TransactionSignScheme for Millau {
fn parse_transaction(tx: Self::SignedTransaction) -> Option<UnsignedTransaction<Self::Chain>> {
let extra = &tx.signature.as_ref()?.2;
Some(UnsignedTransaction {
call: tx.function,
nonce: Compact::<IndexOf<Self::Chain>>::decode(&mut &extra.4.encode()[..]).ok()?.into(),
tip: Compact::<BalanceOf<Self::Chain>>::decode(&mut &extra.6.encode()[..])
call: tx.function.into(),
nonce: Compact::<IndexOf<Self::Chain>>::decode(&mut &extra.5.encode()[..]).ok()?.into(),
tip: Compact::<BalanceOf<Self::Chain>>::decode(&mut &extra.7.encode()[..])
.ok()?
.into(),
})
@@ -138,3 +167,32 @@ pub type SigningParams = sp_core::sr25519::Pair;
/// Millau header type used in headers sync.
pub type SyncHeader = relay_substrate_client::SyncHeader<millau_runtime::Header>;
#[cfg(test)]
mod tests {
use super::*;
use relay_substrate_client::TransactionEra;
#[test]
fn parse_transaction_works() {
let unsigned = UnsignedTransaction {
call: millau_runtime::Call::System(millau_runtime::SystemCall::remark {
remark: b"Hello world!".to_vec(),
})
.into(),
nonce: 777,
tip: 888,
};
let signed_transaction = Millau::sign_transaction(SignParam {
spec_version: 42,
transaction_version: 50000,
genesis_hash: [42u8; 64].into(),
signer: sp_core::sr25519::Pair::from_seed_slice(&[1u8; 32]).unwrap(),
era: TransactionEra::immortal(),
unsigned: unsigned.clone(),
})
.unwrap();
let parsed_transaction = Millau::parse_transaction(signed_transaction).unwrap();
assert_eq!(parsed_transaction, unsigned);
}
}
@@ -2,14 +2,14 @@
name = "relay-polkadot-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.2.0" }
codec = { package = "parity-scale-codec", version = "3.0.0" }
relay-substrate-client = { path = "../client-substrate" }
relay-utils = { path = "../utils" }
scale-info = { version = "1.0", features = ["derive"] }
scale-info = { version = "2.0.1", features = ["derive"] }
# Bridge dependencies
@@ -16,10 +16,12 @@
//! Types used to connect to the Polkadot chain.
use bp_messages::MessageNonce;
use codec::Encode;
use frame_support::weights::Weight;
use relay_substrate_client::{
Chain, ChainBase, ChainWithBalances, TransactionEraOf, TransactionSignScheme,
UnsignedTransaction,
Chain, ChainBase, ChainWithBalances, ChainWithGrandpa, ChainWithMessages,
Error as SubstrateError, SignParam, TransactionSignScheme, UnsignedTransaction,
};
use sp_core::{storage::StorageKey, Pair};
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
@@ -44,10 +46,21 @@ impl ChainBase for Polkadot {
type Balance = bp_polkadot::Balance;
type Index = bp_polkadot::Nonce;
type Signature = bp_polkadot::Signature;
fn max_extrinsic_size() -> u32 {
bp_polkadot::Polkadot::max_extrinsic_size()
}
fn max_extrinsic_weight() -> Weight {
bp_polkadot::Polkadot::max_extrinsic_weight()
}
}
impl Chain for Polkadot {
const NAME: &'static str = "Polkadot";
const TOKEN_ID: Option<&'static str> = Some("polkadot");
const BEST_FINALIZED_HEADER_ID_METHOD: &'static str =
bp_polkadot::BEST_FINALIZED_POLKADOT_HEADER_METHOD;
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6);
const STORAGE_PROOF_OVERHEAD: u32 = bp_polkadot::EXTRA_STORAGE_PROOF_SIZE;
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_polkadot::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
@@ -57,6 +70,25 @@ impl Chain for Polkadot {
type WeightToFee = bp_polkadot::WeightToFee;
}
impl ChainWithGrandpa for Polkadot {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str =
bp_polkadot::WITH_POLKADOT_GRANDPA_PALLET_NAME;
}
impl ChainWithMessages for Polkadot {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str =
bp_polkadot::WITH_POLKADOT_MESSAGES_PALLET_NAME;
const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str =
bp_polkadot::TO_POLKADOT_MESSAGE_DETAILS_METHOD;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_CHAIN: Weight =
bp_polkadot::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce =
bp_polkadot::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX;
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce =
bp_polkadot::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
type WeightInfo = ();
}
impl ChainWithBalances for Polkadot {
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
StorageKey(bp_polkadot::account_info_storage_key(account_id))
@@ -68,34 +100,30 @@ impl TransactionSignScheme for Polkadot {
type AccountKeyPair = sp_core::sr25519::Pair;
type SignedTransaction = crate::runtime::UncheckedExtrinsic;
fn sign_transaction(
genesis_hash: <Self::Chain as ChainBase>::Hash,
signer: &Self::AccountKeyPair,
era: TransactionEraOf<Self::Chain>,
unsigned: UnsignedTransaction<Self::Chain>,
) -> Self::SignedTransaction {
fn sign_transaction(param: SignParam<Self>) -> Result<Self::SignedTransaction, SubstrateError> {
let raw_payload = SignedPayload::new(
unsigned.call,
param.unsigned.call.clone(),
bp_polkadot::SignedExtensions::new(
bp_polkadot::VERSION,
era,
genesis_hash,
unsigned.nonce,
unsigned.tip,
param.spec_version,
param.transaction_version,
param.era,
param.genesis_hash,
param.unsigned.nonce,
param.unsigned.tip,
),
)
.expect("SignedExtension never fails.");
let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
let signer: sp_runtime::MultiSigner = signer.public().into();
let signature = raw_payload.using_encoded(|payload| param.signer.sign(payload));
let signer: sp_runtime::MultiSigner = param.signer.public().into();
let (call, extra, _) = raw_payload.deconstruct();
bp_polkadot::UncheckedExtrinsic::new_signed(
Ok(bp_polkadot::UncheckedExtrinsic::new_signed(
call,
sp_runtime::MultiAddress::Id(signer.into_account()),
signature.into(),
extra,
)
))
}
fn is_signed(tx: &Self::SignedTransaction) -> bool {
@@ -70,6 +70,9 @@ pub enum Call {
/// Balances pallet.
#[codec(index = 5)]
Balances(BalancesCall),
/// Utility pallet.
#[codec(index = 26)]
Utility(UtilityCall),
/// Kusama bridge pallet.
#[codec(index = 110)]
BridgeKusamaGrandpa(BridgeKusamaGrandpaCall),
@@ -102,6 +105,8 @@ pub enum BridgeKusamaGrandpaCall {
),
#[codec(index = 1)]
initialize(bp_header_chain::InitializationData<<PolkadotLike as Chain>::Header>),
#[codec(index = 3)]
set_operational(bool),
}
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
@@ -136,6 +141,13 @@ pub enum BridgeKusamaMessagesCall {
),
}
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
#[allow(non_camel_case_types)]
pub enum UtilityCall {
#[codec(index = 2)]
batch_all(Vec<Call>),
}
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
pub enum BridgeKusamaMessagesParameter {
#[codec(index = 0)]
@@ -2,7 +2,7 @@
name = "relay-rialto-parachain-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
@@ -16,6 +16,7 @@
//! Types used to connect to the Rialto-Substrate chain.
use frame_support::weights::Weight;
use relay_substrate_client::{Chain, ChainBase};
use std::time::Duration;
@@ -37,10 +38,21 @@ impl ChainBase for RialtoParachain {
type Balance = rialto_parachain_runtime::Balance;
type Index = rialto_parachain_runtime::Index;
type Signature = rialto_parachain_runtime::Signature;
fn max_extrinsic_size() -> u32 {
bp_rialto::Rialto::max_extrinsic_size()
}
fn max_extrinsic_weight() -> Weight {
bp_rialto::Rialto::max_extrinsic_weight()
}
}
impl Chain for RialtoParachain {
const NAME: &'static str = "RialtoParachain";
const TOKEN_ID: Option<&'static str> = None;
// should be fixed/changed in https://github.com/paritytech/parity-bridges-common/pull/1199
const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = "<UNIMPLEMENTED>";
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(5);
const STORAGE_PROOF_OVERHEAD: u32 = bp_rialto::EXTRA_STORAGE_PROOF_SIZE;
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_rialto::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
@@ -2,16 +2,17 @@
name = "relay-rialto-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.2.0" }
codec = { package = "parity-scale-codec", version = "3.0.0" }
relay-substrate-client = { path = "../client-substrate" }
relay-utils = { path = "../utils" }
# Bridge dependencies
bp-messages = { path = "../../primitives/messages" }
bp-rialto = { path = "../../primitives/chain-rialto" }
rialto-runtime = { path = "../../bin/rialto/runtime" }
@@ -16,10 +16,12 @@
//! Types used to connect to the Rialto-Substrate chain.
use bp_messages::MessageNonce;
use codec::{Compact, Decode, Encode};
use frame_support::weights::Weight;
use relay_substrate_client::{
BalanceOf, Chain, ChainBase, ChainWithBalances, IndexOf, TransactionEraOf,
TransactionSignScheme, UnsignedTransaction,
BalanceOf, Chain, ChainBase, ChainWithBalances, ChainWithGrandpa, ChainWithMessages,
Error as SubstrateError, IndexOf, SignParam, TransactionSignScheme, UnsignedTransaction,
};
use sp_core::{storage::StorageKey, Pair};
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
@@ -29,7 +31,7 @@ use std::time::Duration;
pub type HeaderId = relay_utils::HeaderId<rialto_runtime::Hash, rialto_runtime::BlockNumber>;
/// Rialto chain definition
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Rialto;
impl ChainBase for Rialto {
@@ -42,10 +44,22 @@ impl ChainBase for Rialto {
type Balance = rialto_runtime::Balance;
type Index = rialto_runtime::Index;
type Signature = rialto_runtime::Signature;
fn max_extrinsic_size() -> u32 {
bp_rialto::Rialto::max_extrinsic_size()
}
fn max_extrinsic_weight() -> Weight {
bp_rialto::Rialto::max_extrinsic_weight()
}
}
impl Chain for Rialto {
const NAME: &'static str = "Rialto";
// Rialto token has no value, but we associate it with DOT token
const TOKEN_ID: Option<&'static str> = Some("polkadot");
const BEST_FINALIZED_HEADER_ID_METHOD: &'static str =
bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(5);
const STORAGE_PROOF_OVERHEAD: u32 = bp_rialto::EXTRA_STORAGE_PROOF_SIZE;
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_rialto::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
@@ -55,6 +69,24 @@ impl Chain for Rialto {
type WeightToFee = bp_rialto::WeightToFee;
}
impl ChainWithGrandpa for Rialto {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = bp_rialto::WITH_RIALTO_GRANDPA_PALLET_NAME;
}
impl ChainWithMessages for Rialto {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str =
bp_rialto::WITH_RIALTO_MESSAGES_PALLET_NAME;
const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str =
bp_rialto::TO_RIALTO_MESSAGE_DETAILS_METHOD;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_CHAIN: Weight =
bp_rialto::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce =
bp_rialto::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX;
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce =
bp_rialto::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
type WeightInfo = ();
}
impl ChainWithBalances for Rialto {
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
use frame_support::storage::generator::StorageMap;
@@ -69,43 +101,40 @@ impl TransactionSignScheme for Rialto {
type AccountKeyPair = sp_core::sr25519::Pair;
type SignedTransaction = rialto_runtime::UncheckedExtrinsic;
fn sign_transaction(
genesis_hash: <Self::Chain as ChainBase>::Hash,
signer: &Self::AccountKeyPair,
era: TransactionEraOf<Self::Chain>,
unsigned: UnsignedTransaction<Self::Chain>,
) -> Self::SignedTransaction {
fn sign_transaction(param: SignParam<Self>) -> Result<Self::SignedTransaction, SubstrateError> {
let raw_payload = SignedPayload::from_raw(
unsigned.call,
param.unsigned.call.clone(),
(
frame_system::CheckNonZeroSender::<rialto_runtime::Runtime>::new(),
frame_system::CheckSpecVersion::<rialto_runtime::Runtime>::new(),
frame_system::CheckTxVersion::<rialto_runtime::Runtime>::new(),
frame_system::CheckGenesis::<rialto_runtime::Runtime>::new(),
frame_system::CheckEra::<rialto_runtime::Runtime>::from(era.frame_era()),
frame_system::CheckNonce::<rialto_runtime::Runtime>::from(unsigned.nonce),
frame_system::CheckEra::<rialto_runtime::Runtime>::from(param.era.frame_era()),
frame_system::CheckNonce::<rialto_runtime::Runtime>::from(param.unsigned.nonce),
frame_system::CheckWeight::<rialto_runtime::Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<rialto_runtime::Runtime>::from(unsigned.tip),
pallet_transaction_payment::ChargeTransactionPayment::<rialto_runtime::Runtime>::from(param.unsigned.tip),
),
(
rialto_runtime::VERSION.spec_version,
rialto_runtime::VERSION.transaction_version,
genesis_hash,
era.signed_payload(genesis_hash),
(),
param.spec_version,
param.transaction_version,
param.genesis_hash,
param.era.signed_payload(param.genesis_hash),
(),
(),
(),
),
);
let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
let signer: sp_runtime::MultiSigner = signer.public().into();
let signature = raw_payload.using_encoded(|payload| param.signer.sign(payload));
let signer: sp_runtime::MultiSigner = param.signer.public().into();
let (call, extra, _) = raw_payload.deconstruct();
rialto_runtime::UncheckedExtrinsic::new_signed(
call,
Ok(rialto_runtime::UncheckedExtrinsic::new_signed(
call.into_decoded()?,
signer.into_account().into(),
signature.into(),
extra,
)
))
}
fn is_signed(tx: &Self::SignedTransaction) -> bool {
@@ -122,9 +151,9 @@ impl TransactionSignScheme for Rialto {
fn parse_transaction(tx: Self::SignedTransaction) -> Option<UnsignedTransaction<Self::Chain>> {
let extra = &tx.signature.as_ref()?.2;
Some(UnsignedTransaction {
call: tx.function,
nonce: Compact::<IndexOf<Self::Chain>>::decode(&mut &extra.4.encode()[..]).ok()?.into(),
tip: Compact::<BalanceOf<Self::Chain>>::decode(&mut &extra.6.encode()[..])
call: tx.function.into(),
nonce: Compact::<IndexOf<Self::Chain>>::decode(&mut &extra.5.encode()[..]).ok()?.into(),
tip: Compact::<BalanceOf<Self::Chain>>::decode(&mut &extra.7.encode()[..])
.ok()?
.into(),
})
@@ -136,3 +165,32 @@ pub type SigningParams = sp_core::sr25519::Pair;
/// Rialto header type used in headers sync.
pub type SyncHeader = relay_substrate_client::SyncHeader<rialto_runtime::Header>;
#[cfg(test)]
mod tests {
use super::*;
use relay_substrate_client::TransactionEra;
#[test]
fn parse_transaction_works() {
let unsigned = UnsignedTransaction {
call: rialto_runtime::Call::System(rialto_runtime::SystemCall::remark {
remark: b"Hello world!".to_vec(),
})
.into(),
nonce: 777,
tip: 888,
};
let signed_transaction = Rialto::sign_transaction(SignParam {
spec_version: 42,
transaction_version: 50000,
genesis_hash: [42u8; 32].into(),
signer: sp_core::sr25519::Pair::from_seed_slice(&[1u8; 32]).unwrap(),
era: TransactionEra::immortal(),
unsigned: unsigned.clone(),
})
.unwrap();
let parsed_transaction = Rialto::parse_transaction(signed_transaction).unwrap();
assert_eq!(parsed_transaction, unsigned);
}
}
@@ -2,14 +2,14 @@
name = "relay-rococo-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.2.0" }
codec = { package = "parity-scale-codec", version = "3.0.0" }
relay-substrate-client = { path = "../client-substrate" }
relay-utils = { path = "../utils" }
scale-info = { version = "1.0", features = ["derive"] }
scale-info = { version = "2.0.1", features = ["derive"] }
# Bridge dependencies
@@ -16,10 +16,12 @@
//! Types used to connect to the Rococo-Substrate chain.
use bp_messages::MessageNonce;
use codec::Encode;
use frame_support::weights::Weight;
use relay_substrate_client::{
Chain, ChainBase, ChainWithBalances, TransactionEraOf, TransactionSignScheme,
UnsignedTransaction,
Chain, ChainBase, ChainWithBalances, ChainWithGrandpa, ChainWithMessages,
Error as SubstrateError, SignParam, TransactionSignScheme, UnsignedTransaction,
};
use sp_core::{storage::StorageKey, Pair};
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
@@ -47,10 +49,21 @@ impl ChainBase for Rococo {
type Balance = bp_rococo::Balance;
type Index = bp_rococo::Nonce;
type Signature = bp_rococo::Signature;
fn max_extrinsic_size() -> u32 {
bp_rococo::Rococo::max_extrinsic_size()
}
fn max_extrinsic_weight() -> Weight {
bp_rococo::Rococo::max_extrinsic_weight()
}
}
impl Chain for Rococo {
const NAME: &'static str = "Rococo";
const TOKEN_ID: Option<&'static str> = None;
const BEST_FINALIZED_HEADER_ID_METHOD: &'static str =
bp_rococo::BEST_FINALIZED_ROCOCO_HEADER_METHOD;
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6);
const STORAGE_PROOF_OVERHEAD: u32 = bp_rococo::EXTRA_STORAGE_PROOF_SIZE;
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_rococo::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
@@ -60,6 +73,24 @@ impl Chain for Rococo {
type WeightToFee = bp_rococo::WeightToFee;
}
impl ChainWithGrandpa for Rococo {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = bp_rococo::WITH_ROCOCO_GRANDPA_PALLET_NAME;
}
impl ChainWithMessages for Rococo {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str =
bp_rococo::WITH_ROCOCO_MESSAGES_PALLET_NAME;
const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str =
bp_rococo::TO_ROCOCO_MESSAGE_DETAILS_METHOD;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_CHAIN: Weight =
bp_rococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce =
bp_rococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX;
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce =
bp_rococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
type WeightInfo = ();
}
impl ChainWithBalances for Rococo {
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
StorageKey(bp_rococo::account_info_storage_key(account_id))
@@ -71,34 +102,30 @@ impl TransactionSignScheme for Rococo {
type AccountKeyPair = sp_core::sr25519::Pair;
type SignedTransaction = crate::runtime::UncheckedExtrinsic;
fn sign_transaction(
genesis_hash: <Self::Chain as ChainBase>::Hash,
signer: &Self::AccountKeyPair,
era: TransactionEraOf<Self::Chain>,
unsigned: UnsignedTransaction<Self::Chain>,
) -> Self::SignedTransaction {
fn sign_transaction(param: SignParam<Self>) -> Result<Self::SignedTransaction, SubstrateError> {
let raw_payload = SignedPayload::new(
unsigned.call,
param.unsigned.call.clone(),
bp_rococo::SignedExtensions::new(
bp_rococo::VERSION,
era,
genesis_hash,
unsigned.nonce,
unsigned.tip,
param.spec_version,
param.transaction_version,
param.era,
param.genesis_hash,
param.unsigned.nonce,
param.unsigned.tip,
),
)
.expect("SignedExtension never fails.");
let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
let signer: sp_runtime::MultiSigner = signer.public().into();
let signature = raw_payload.using_encoded(|payload| param.signer.sign(payload));
let signer: sp_runtime::MultiSigner = param.signer.public().into();
let (call, extra, _) = raw_payload.deconstruct();
bp_rococo::UncheckedExtrinsic::new_signed(
Ok(bp_rococo::UncheckedExtrinsic::new_signed(
call,
sp_runtime::MultiAddress::Id(signer.into_account()),
signature.into(),
extra,
)
))
}
fn is_signed(tx: &Self::SignedTransaction) -> bool {
@@ -17,9 +17,9 @@
//! Types that are specific to the Rococo runtime.
use bp_messages::{LaneId, UnrewardedRelayersState};
use bp_polkadot_core::PolkadotLike;
use bp_polkadot_core::{AccountAddress, Balance, PolkadotLike};
use bp_runtime::Chain;
use codec::{Decode, Encode};
use codec::{Compact, Decode, Encode};
use frame_support::weights::Weight;
use scale_info::TypeInfo;
@@ -66,12 +66,15 @@ pub enum Call {
/// System pallet.
#[codec(index = 0)]
System(SystemCall),
/// Balances pallet.
#[codec(index = 4)]
Balances(BalancesCall),
/// Wococo bridge pallet.
#[codec(index = 41)]
BridgeGrandpaWococo(BridgeGrandpaWococoCall),
/// Wococo messages pallet.
#[codec(index = 44)]
BridgeMessagesWococo(BridgeMessagesWococoCall),
BridgeWococoMessages(BridgeWococoMessagesCall),
}
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
@@ -81,6 +84,13 @@ pub enum SystemCall {
remark(Vec<u8>),
}
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
#[allow(non_camel_case_types)]
pub enum BalancesCall {
#[codec(index = 0)]
transfer(AccountAddress, Compact<Balance>),
}
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
#[allow(non_camel_case_types)]
pub enum BridgeGrandpaWococoCall {
@@ -95,7 +105,7 @@ pub enum BridgeGrandpaWococoCall {
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
#[allow(non_camel_case_types)]
pub enum BridgeMessagesWococoCall {
pub enum BridgeWococoMessagesCall {
#[codec(index = 3)]
send_message(
LaneId,
@@ -2,25 +2,27 @@
name = "relay-substrate-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
async-std = { version = "1.6.5", features = ["attributes"] }
async-trait = "0.1.40"
codec = { package = "parity-scale-codec", version = "2.2.0" }
jsonrpsee-proc-macros = "0.3.1"
jsonrpsee-ws-client = "0.3.1"
codec = { package = "parity-scale-codec", version = "3.0.0" }
jsonrpsee = { version = "0.8", features = ["macros", "ws-client"] }
log = "0.4.11"
num-traits = "0.2"
rand = "0.7"
tokio = "1.8"
serde = { version = "1.0" }
tokio = { version = "1.8", features = ["rt-multi-thread"] }
thiserror = "1.0.26"
# Bridge dependencies
bp-header-chain = { path = "../../primitives/header-chain" }
bp-messages = { path = "../../primitives/messages" }
bp-runtime = { path = "../../primitives/runtime" }
pallet-bridge-messages = { path = "../../modules/messages" }
finality-relay = { path = "../finality" }
relay-utils = { path = "../utils" }
@@ -31,9 +33,10 @@ frame-system = { git = "https://github.com/paritytech/substrate", branch = "mast
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-chain-spec = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-rpc-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-transaction-pool-api = { 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-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
@@ -14,10 +14,11 @@
// 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 bp_runtime::{Chain as ChainBase, HashOf, TransactionEraOf};
use bp_messages::MessageNonce;
use bp_runtime::{Chain as ChainBase, EncodedOrDecodedCall, HashOf, TransactionEraOf};
use codec::{Codec, Encode};
use frame_support::weights::WeightToFeePolynomial;
use jsonrpsee_ws_client::types::{DeserializeOwned, Serialize};
use frame_support::weights::{Weight, WeightToFeePolynomial};
use jsonrpsee::core::{DeserializeOwned, Serialize};
use num_traits::Zero;
use sc_transaction_pool_api::TransactionStatus;
use sp_core::{storage::StorageKey, Pair};
@@ -32,6 +33,18 @@ use std::{fmt::Debug, time::Duration};
pub trait Chain: ChainBase + Clone {
/// Chain name.
const NAME: &'static str;
/// Identifier of the basic token of the chain (if applicable).
///
/// This identifier is used to fetch token price. In case of testnets, you may either
/// set it to `None`, or associate testnet with one of the existing tokens.
const TOKEN_ID: Option<&'static str>;
/// Name of the runtime API method that is returning best known finalized header number
/// and hash (as tuple).
///
/// Keep in mind that this method is normally provided by the other chain, which is
/// bridged with this chain.
const BEST_FINALIZED_HEADER_ID_METHOD: &'static str;
/// Average block interval.
///
/// How often blocks are produced on that chain. It's suggested to set this value
@@ -45,12 +58,54 @@ pub trait Chain: ChainBase + Clone {
/// Block type.
type SignedBlock: Member + Serialize + DeserializeOwned + BlockWithJustification<Self::Header>;
/// The aggregated `Call` type.
type Call: Clone + Dispatchable + Debug;
type Call: Clone + Codec + Dispatchable + Debug + Send;
/// Type that is used by the chain, to convert from weight to fee.
type WeightToFee: WeightToFeePolynomial<Balance = Self::Balance>;
}
/// Substrate-based chain that is using direct GRANDPA finality from minimal relay-client point of
/// view.
///
/// Keep in mind that parachains are relying on relay chain GRANDPA, so they should not implement
/// this trait.
pub trait ChainWithGrandpa: Chain {
/// Name of the bridge GRANDPA pallet (used in `construct_runtime` macro call) that is deployed
/// at some other chain to bridge with this `ChainWithGrandpa`.
///
/// We assume that all chains that are bridging with this `ChainWithGrandpa` are using
/// the same name.
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str;
}
/// Substrate-based chain with messaging support from minimal relay-client point of view.
pub trait ChainWithMessages: Chain {
/// Name of the bridge messages pallet (used in `construct_runtime` macro call) that is deployed
/// at some other chain to bridge with this `ChainWithMessages`.
///
/// We assume that all chains that are bridging with this `ChainWithMessages` are using
/// the same name.
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str;
/// Name of the `To<ChainWithMessages>OutboundLaneApi::message_details` runtime API method.
/// The method is provided by the runtime that is bridged with this `ChainWithMessages`.
const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str;
/// Additional weight of the dispatch fee payment if dispatch is paid at the target chain
/// and this `ChainWithMessages` is the target chain.
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_CHAIN: Weight;
/// Maximal number of unrewarded relayers in a single confirmation transaction at this
/// `ChainWithMessages`.
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce;
/// Maximal number of unconfirmed messages in a single confirmation transaction at this
/// `ChainWithMessages`.
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce;
/// Weights of message pallet calls.
type WeightInfo: pallet_bridge_messages::WeightInfoExt;
}
/// Call type used by the chain.
pub type CallOf<C> = <C as Chain>::Call;
/// Weight-to-Fee type used by the chain.
@@ -58,7 +113,7 @@ pub type WeightToFeeOf<C> = <C as Chain>::WeightToFee;
/// Transaction status of the chain.
pub type TransactionStatusOf<C> = TransactionStatus<HashOf<C>, HashOf<C>>;
/// Substrate-based chain with `frame_system::Config::AccountData` set to
/// Substrate-based chain with `AccountData` generic argument of `frame_system::AccountInfo` set to
/// the `pallet_balances::AccountData<Balance>`.
pub trait ChainWithBalances: Chain {
/// Return runtime storage key for getting `frame_system::AccountInfo` of given account.
@@ -79,10 +134,10 @@ pub trait BlockWithJustification<Header> {
}
/// Transaction before it is signed.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct UnsignedTransaction<C: Chain> {
/// Runtime call of this transaction.
pub call: C::Call,
pub call: EncodedOrDecodedCall<C::Call>,
/// Transaction nonce.
pub nonce: C::Index,
/// Tip included into transaction.
@@ -91,7 +146,7 @@ pub struct UnsignedTransaction<C: Chain> {
impl<C: Chain> UnsignedTransaction<C> {
/// Create new unsigned transaction with given call, nonce and zero tip.
pub fn new(call: C::Call, nonce: C::Index) -> Self {
pub fn new(call: EncodedOrDecodedCall<C::Call>, nonce: C::Index) -> Self {
Self { call, nonce, tip: Zero::zero() }
}
@@ -102,6 +157,9 @@ impl<C: Chain> UnsignedTransaction<C> {
}
}
/// Account key pair used by transactions signing scheme.
pub type AccountKeyPairOf<S> = <S as TransactionSignScheme>::AccountKeyPair;
/// Substrate-based chain transactions signing scheme.
pub trait TransactionSignScheme {
/// Chain that this scheme is to be used.
@@ -112,12 +170,9 @@ pub trait TransactionSignScheme {
type SignedTransaction: Clone + Debug + Codec + Send + 'static;
/// Create transaction for given runtime call, signed by given account.
fn sign_transaction(
genesis_hash: <Self::Chain as ChainBase>::Hash,
signer: &Self::AccountKeyPair,
era: TransactionEraOf<Self::Chain>,
unsigned: UnsignedTransaction<Self::Chain>,
) -> Self::SignedTransaction;
fn sign_transaction(param: SignParam<Self>) -> Result<Self::SignedTransaction, crate::Error>
where
Self: Sized;
/// Returns true if transaction is signed.
fn is_signed(tx: &Self::SignedTransaction) -> bool;
@@ -131,6 +186,22 @@ pub trait TransactionSignScheme {
fn parse_transaction(tx: Self::SignedTransaction) -> Option<UnsignedTransaction<Self::Chain>>;
}
/// Sign transaction parameters
pub struct SignParam<T: TransactionSignScheme> {
/// Version of the runtime specification.
pub spec_version: u32,
/// Transaction version
pub transaction_version: u32,
/// Hash of the genesis block.
pub genesis_hash: <T::Chain as ChainBase>::Hash,
/// Signer account
pub signer: T::AccountKeyPair,
/// Transaction era used by the chain.
pub era: TransactionEraOf<T::Chain>,
/// Transaction before it is signed.
pub unsigned: UnsignedTransaction<T::Chain>,
}
impl<Block: BlockT> BlockWithJustification<Block::Header> for SignedBlock<Block> {
fn header(&self) -> Block::Header {
self.block.header().clone()
@@ -18,8 +18,9 @@
use crate::{
chain::{Chain, ChainWithBalances, TransactionStatusOf},
rpc::Substrate,
ConnectionParams, Error, HashOf, HeaderIdOf, Result,
rpc::SubstrateClient,
AccountIdOf, BlockNumberOf, ConnectionParams, Error, HashOf, HeaderIdOf, HeaderOf, IndexOf,
Result,
};
use async_std::sync::{Arc, Mutex};
@@ -27,14 +28,12 @@ use async_trait::async_trait;
use codec::{Decode, Encode};
use frame_system::AccountInfo;
use futures::{SinkExt, StreamExt};
use jsonrpsee_ws_client::{
types::{
self as jsonrpsee_types, traits::SubscriptionClient, v2::params::JsonRpcParams,
DeserializeOwned,
},
WsClient as RpcClient, WsClientBuilder as RpcClientBuilder,
use jsonrpsee::{
core::{client::SubscriptionClientT, DeserializeOwned},
types::params::ParamsSer,
ws_client::{WsClient as RpcClient, WsClientBuilder as RpcClientBuilder},
};
use num_traits::{Bounded, Zero};
use num_traits::{Bounded, CheckedSub, One, Zero};
use pallet_balances::AccountData;
use pallet_transaction_payment::InclusionFee;
use relay_utils::{relay_loop::RECONNECT_DELAY, HeaderId};
@@ -60,6 +59,17 @@ pub struct Subscription<T>(Mutex<futures::channel::mpsc::Receiver<Option<T>>>);
/// Opaque GRANDPA authorities set.
pub type OpaqueGrandpaAuthoritiesSet = Vec<u8>;
/// Chain runtime version in client
#[derive(Clone, Debug)]
pub enum ChainRuntimeVersion {
/// Auto query from chain.
Auto,
/// Custom runtime version, defined by user.
/// the first is `spec_version`
/// the second is `transaction_version`
Custom(u32, u32),
}
/// Substrate client type.
///
/// Cloning `Client` is a cheap operation.
@@ -77,6 +87,8 @@ pub struct Client<C: Chain> {
/// transactions will be rejected from the pool. This lock is here to prevent situations like
/// that.
submit_signed_extrinsic_lock: Arc<Mutex<()>>,
/// Saved chain runtime version
chain_runtime_version: ChainRuntimeVersion,
}
#[async_trait]
@@ -99,6 +111,7 @@ impl<C: Chain> Clone for Client<C> {
client: self.client.clone(),
genesis_hash: self.genesis_hash,
submit_signed_extrinsic_lock: self.submit_signed_extrinsic_lock.clone(),
chain_runtime_version: self.chain_runtime_version.clone(),
}
}
}
@@ -140,16 +153,26 @@ impl<C: Chain> Client<C> {
let genesis_hash_client = client.clone();
let genesis_hash = tokio
.spawn(async move {
Substrate::<C>::chain_get_block_hash(&*genesis_hash_client, number).await
SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::chain_get_block_hash(&*genesis_hash_client, Some(number))
.await
})
.await??;
let chain_runtime_version = params.chain_runtime_version.clone();
Ok(Self {
tokio,
params,
client,
genesis_hash,
submit_signed_extrinsic_lock: Arc::new(Mutex::new(())),
chain_runtime_version,
})
}
@@ -178,10 +201,31 @@ impl<C: Chain> Client<C> {
}
impl<C: Chain> Client<C> {
/// Return simple runtime version, only include `spec_version` and `transaction_version`.
pub async fn simple_runtime_version(&self) -> Result<(u32, u32)> {
let (spec_version, transaction_version) = match self.chain_runtime_version {
ChainRuntimeVersion::Auto => {
let runtime_version = self.runtime_version().await?;
(runtime_version.spec_version, runtime_version.transaction_version)
},
ChainRuntimeVersion::Custom(spec_version, transaction_version) =>
(spec_version, transaction_version),
};
Ok((spec_version, transaction_version))
}
/// Returns true if client is connected to at least one peer and is in synced state.
pub async fn ensure_synced(&self) -> Result<()> {
self.jsonrpsee_execute(|client| async move {
let health = Substrate::<C>::system_health(&*client).await?;
let health = SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::system_health(&*client)
.await?;
let is_synced = !health.is_syncing && (!health.should_have_peers || health.peers > 0);
if is_synced {
Ok(())
@@ -200,7 +244,15 @@ impl<C: Chain> Client<C> {
/// Return hash of the best finalized block.
pub async fn best_finalized_header_hash(&self) -> Result<C::Hash> {
self.jsonrpsee_execute(|client| async move {
Ok(Substrate::<C>::chain_get_finalized_head(&*client).await?)
Ok(SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::chain_get_finalized_head(&*client)
.await?)
})
.await
}
@@ -216,7 +268,15 @@ impl<C: Chain> Client<C> {
C::Header: DeserializeOwned,
{
self.jsonrpsee_execute(|client| async move {
Ok(Substrate::<C>::chain_get_header(&*client, None).await?)
Ok(SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::chain_get_header(&*client, None)
.await?)
})
.await
}
@@ -224,7 +284,15 @@ impl<C: Chain> Client<C> {
/// Get a Substrate block from its hash.
pub async fn get_block(&self, block_hash: Option<C::Hash>) -> Result<C::SignedBlock> {
self.jsonrpsee_execute(move |client| async move {
Ok(Substrate::<C>::chain_get_block(&*client, block_hash).await?)
Ok(SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::chain_get_block(&*client, block_hash)
.await?)
})
.await
}
@@ -235,7 +303,15 @@ impl<C: Chain> Client<C> {
C::Header: DeserializeOwned,
{
self.jsonrpsee_execute(move |client| async move {
Ok(Substrate::<C>::chain_get_header(&*client, block_hash).await?)
Ok(SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::chain_get_header(&*client, Some(block_hash))
.await?)
})
.await
}
@@ -243,7 +319,15 @@ impl<C: Chain> Client<C> {
/// Get a Substrate block hash by its number.
pub async fn block_hash_by_number(&self, number: C::BlockNumber) -> Result<C::Hash> {
self.jsonrpsee_execute(move |client| async move {
Ok(Substrate::<C>::chain_get_block_hash(&*client, number).await?)
Ok(SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::chain_get_block_hash(&*client, Some(number))
.await?)
})
.await
}
@@ -261,7 +345,15 @@ impl<C: Chain> Client<C> {
/// Return runtime version.
pub async fn runtime_version(&self) -> Result<RuntimeVersion> {
self.jsonrpsee_execute(move |client| async move {
Ok(Substrate::<C>::state_runtime_version(&*client).await?)
Ok(SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::state_runtime_version(&*client)
.await?)
})
.await
}
@@ -287,7 +379,15 @@ impl<C: Chain> Client<C> {
block_hash: Option<C::Hash>,
) -> Result<Option<StorageData>> {
self.jsonrpsee_execute(move |client| async move {
Ok(Substrate::<C>::state_get_storage(&*client, storage_key, block_hash).await?)
Ok(SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::state_get_storage(&*client, storage_key, block_hash)
.await?)
})
.await
}
@@ -299,10 +399,16 @@ impl<C: Chain> Client<C> {
{
self.jsonrpsee_execute(move |client| async move {
let storage_key = C::account_info_storage_key(&account);
let encoded_account_data =
Substrate::<C>::state_get_storage(&*client, storage_key, None)
.await?
.ok_or(Error::AccountDoesNotExist)?;
let encoded_account_data = SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::state_get_storage(&*client, storage_key, None)
.await?
.ok_or(Error::AccountDoesNotExist)?;
let decoded_account_data = AccountInfo::<C::Index, AccountData<C::Balance>>::decode(
&mut &encoded_account_data.0[..],
)
@@ -317,7 +423,15 @@ impl<C: Chain> Client<C> {
/// Note: It's the caller's responsibility to make sure `account` is a valid SS58 address.
pub async fn next_account_index(&self, account: C::AccountId) -> Result<C::Index> {
self.jsonrpsee_execute(move |client| async move {
Ok(Substrate::<C>::system_account_next_index(&*client, account).await?)
Ok(SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::system_account_next_index(&*client, account)
.await?)
})
.await
}
@@ -327,7 +441,15 @@ impl<C: Chain> Client<C> {
/// Note: The given transaction needs to be SCALE encoded beforehand.
pub async fn submit_unsigned_extrinsic(&self, transaction: Bytes) -> Result<C::Hash> {
self.jsonrpsee_execute(move |client| async move {
let tx_hash = Substrate::<C>::author_submit_extrinsic(&*client, transaction).await?;
let tx_hash = SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::author_submit_extrinsic(&*client, transaction)
.await?;
log::trace!(target: "bridge", "Sent transaction to Substrate node: {:?}", tx_hash);
Ok(tx_hash)
})
@@ -344,15 +466,33 @@ impl<C: Chain> Client<C> {
pub async fn submit_signed_extrinsic(
&self,
extrinsic_signer: C::AccountId,
prepare_extrinsic: impl FnOnce(HeaderIdOf<C>, C::Index) -> Bytes + Send + 'static,
prepare_extrinsic: impl FnOnce(HeaderIdOf<C>, C::Index) -> Result<Bytes> + Send + 'static,
) -> Result<C::Hash> {
let _guard = self.submit_signed_extrinsic_lock.lock().await;
let transaction_nonce = self.next_account_index(extrinsic_signer).await?;
let best_header = self.best_header().await?;
let best_header_id = HeaderId(*best_header.number(), best_header.hash());
// By using parent of best block here, we are protecing again best-block reorganizations.
// E.g. transaction my have been submitted when the best block was `A[num=100]`. Then it has
// been changed to `B[num=100]`. Hash of `A` has been included into transaction signature
// payload. So when signature will be checked, the check will fail and transaction will be
// dropped from the pool.
let best_header_id = match best_header.number().checked_sub(&One::one()) {
Some(parent_block_number) => HeaderId(parent_block_number, *best_header.parent_hash()),
None => HeaderId(*best_header.number(), best_header.hash()),
};
self.jsonrpsee_execute(move |client| async move {
let extrinsic = prepare_extrinsic(best_header_id, transaction_nonce);
let tx_hash = Substrate::<C>::author_submit_extrinsic(&*client, extrinsic).await?;
let extrinsic = prepare_extrinsic(best_header_id, transaction_nonce)?;
let tx_hash = SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::author_submit_extrinsic(&*client, extrinsic)
.await?;
log::trace!(target: "bridge", "Sent transaction to {} node: {:?}", C::NAME, tx_hash);
Ok(tx_hash)
})
@@ -364,7 +504,7 @@ impl<C: Chain> Client<C> {
pub async fn submit_and_watch_signed_extrinsic(
&self,
extrinsic_signer: C::AccountId,
prepare_extrinsic: impl FnOnce(HeaderIdOf<C>, C::Index) -> Bytes + Send + 'static,
prepare_extrinsic: impl FnOnce(HeaderIdOf<C>, C::Index) -> Result<Bytes> + Send + 'static,
) -> Result<Subscription<TransactionStatusOf<C>>> {
let _guard = self.submit_signed_extrinsic_lock.lock().await;
let transaction_nonce = self.next_account_index(extrinsic_signer).await?;
@@ -372,13 +512,13 @@ impl<C: Chain> Client<C> {
let best_header_id = HeaderId(*best_header.number(), best_header.hash());
let subscription = self
.jsonrpsee_execute(move |client| async move {
let extrinsic = prepare_extrinsic(best_header_id, transaction_nonce);
let extrinsic = prepare_extrinsic(best_header_id, transaction_nonce)?;
let tx_hash = C::Hasher::hash(&extrinsic.0);
let subscription = client
.subscribe(
"author_submitAndWatchExtrinsic",
JsonRpcParams::Array(vec![jsonrpsee_types::to_json_value(extrinsic)
.map_err(|e| Error::RpcError(e.into()))?]),
Some(ParamsSer::Array(vec![jsonrpsee::core::to_json_value(extrinsic)
.map_err(|e| Error::RpcError(e.into()))?])),
"author_unwatchExtrinsic",
)
.await?;
@@ -399,7 +539,15 @@ impl<C: Chain> Client<C> {
/// Returns pending extrinsics from transaction pool.
pub async fn pending_extrinsics(&self) -> Result<Vec<Bytes>> {
self.jsonrpsee_execute(move |client| async move {
Ok(Substrate::<C>::author_pending_extrinsics(&*client).await?)
Ok(SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::author_pending_extrinsics(&*client)
.await?)
})
.await
}
@@ -414,8 +562,15 @@ impl<C: Chain> Client<C> {
let call = SUB_API_TXPOOL_VALIDATE_TRANSACTION.to_string();
let data = Bytes((TransactionSource::External, transaction, at_block).encode());
let encoded_response =
Substrate::<C>::state_call(&*client, call, data, Some(at_block)).await?;
let encoded_response = SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::state_call(&*client, call, data, Some(at_block))
.await?;
let validity = TransactionValidity::decode(&mut &encoded_response.0[..])
.map_err(Error::ResponseParseFailed)?;
@@ -430,8 +585,15 @@ impl<C: Chain> Client<C> {
transaction: Bytes,
) -> Result<InclusionFee<C::Balance>> {
self.jsonrpsee_execute(move |client| async move {
let fee_details =
Substrate::<C>::payment_query_fee_details(&*client, transaction, None).await?;
let fee_details = SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::payment_query_fee_details(&*client, transaction, None)
.await?;
let inclusion_fee = fee_details
.inclusion_fee
.map(|inclusion_fee| InclusionFee {
@@ -463,8 +625,15 @@ impl<C: Chain> Client<C> {
let call = SUB_API_GRANDPA_AUTHORITIES.to_string();
let data = Bytes(Vec::new());
let encoded_response =
Substrate::<C>::state_call(&*client, call, data, Some(block)).await?;
let encoded_response = SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::state_call(&*client, call, data, Some(block))
.await?;
let authority_list = encoded_response.0;
Ok(authority_list)
@@ -480,9 +649,16 @@ impl<C: Chain> Client<C> {
at_block: Option<C::Hash>,
) -> Result<Bytes> {
self.jsonrpsee_execute(move |client| async move {
Substrate::<C>::state_call(&*client, method, data, at_block)
.await
.map_err(Into::into)
SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::state_call(&*client, method, data, at_block)
.await
.map_err(Into::into)
})
.await
}
@@ -494,10 +670,36 @@ impl<C: Chain> Client<C> {
at_block: C::Hash,
) -> Result<StorageProof> {
self.jsonrpsee_execute(move |client| async move {
Substrate::<C>::state_prove_storage(&*client, keys, Some(at_block))
.await
.map(|proof| StorageProof::new(proof.proof.into_iter().map(|b| b.0)))
.map_err(Into::into)
SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::state_prove_storage(&*client, keys, Some(at_block))
.await
.map(|proof| {
StorageProof::new(proof.proof.into_iter().map(|b| b.0).collect::<Vec<_>>())
})
.map_err(Into::into)
})
.await
}
/// Return `tokenDecimals` property from the set of chain properties.
pub async fn token_decimals(&self) -> Result<Option<u64>> {
self.jsonrpsee_execute(move |client| async move {
let system_properties = SubstrateClient::<
AccountIdOf<C>,
BlockNumberOf<C>,
HashOf<C>,
HeaderOf<C>,
IndexOf<C>,
C::SignedBlock,
>::system_properties(&*client)
.await?;
Ok(system_properties.get("tokenDecimals").and_then(|v| v.as_u64()))
})
.await
}
@@ -509,7 +711,7 @@ impl<C: Chain> Client<C> {
Ok(client
.subscribe(
"grandpa_subscribeJustifications",
JsonRpcParams::NoParams,
None,
"grandpa_unsubscribeJustifications",
)
.await?)
@@ -549,26 +751,16 @@ impl<T: DeserializeOwned> Subscription<T> {
async fn background_worker(
chain_name: String,
item_type: String,
mut subscription: jsonrpsee_types::Subscription<T>,
mut subscription: jsonrpsee::core::client::Subscription<T>,
mut sender: futures::channel::mpsc::Sender<Option<T>>,
) {
loop {
match subscription.next().await {
Ok(Some(item)) =>
Some(Ok(item)) =>
if sender.send(Some(item)).await.is_err() {
break
},
Ok(None) => {
log::trace!(
target: "bridge",
"{} {} subscription stream has returned None. Stream needs to be restarted.",
chain_name,
item_type,
);
let _ = sender.send(None).await;
break
},
Err(e) => {
Some(Err(e)) => {
log::trace!(
target: "bridge",
"{} {} subscription stream has returned '{:?}'. Stream needs to be restarted.",
@@ -579,6 +771,16 @@ impl<T: DeserializeOwned> Subscription<T> {
let _ = sender.send(None).await;
break
},
None => {
log::trace!(
target: "bridge",
"{} {} subscription stream has returned None. Stream needs to be restarted.",
chain_name,
item_type,
);
let _ = sender.send(None).await;
break
},
}
}
}
@@ -16,7 +16,7 @@
//! Substrate node RPC errors.
use jsonrpsee_ws_client::types::Error as RpcError;
use jsonrpsee::core::Error as RpcError;
use relay_utils::MaybeConnectionError;
use sc_rpc_api::system::Health;
use sp_runtime::transaction_validity::TransactionValidityError;
@@ -51,6 +51,9 @@ pub enum Error {
/// The client we're connected to is not synced, so we can't rely on its state.
#[error("Substrate client is not synced {0}.")]
ClientNotSynced(Health),
/// The bridge pallet is halted and all transactions will be rejected.
#[error("Bridge pallet is halted.")]
BridgePalletIsHalted,
/// An error has happened when we have tried to parse storage proof.
#[error("Error when parsing storage proof: {0:?}.")]
StorageProofError(bp_runtime::StorageProofError),
@@ -64,6 +64,13 @@ pub fn abort_on_spec_version_change<C: ChainWithBalances>(
expected_spec_version: u32,
) {
async_std::task::spawn(async move {
log::info!(
target: "bridge-guard",
"Starting spec_version guard for {}. Expected spec_version: {}",
C::NAME,
expected_spec_version,
);
loop {
let actual_spec_version = env.runtime_version().await;
match actual_spec_version {
@@ -103,6 +110,14 @@ pub fn abort_when_account_balance_decreased<C: ChainWithBalances>(
const DAY: Duration = Duration::from_secs(60 * 60 * 24);
async_std::task::spawn(async move {
log::info!(
target: "bridge-guard",
"Starting balance guard for {}/{:?}. Maximal decrease: {:?}",
C::NAME,
account_id,
maximal_decrease,
);
let mut balances = VecDeque::new();
loop {
@@ -181,7 +196,7 @@ impl<C: ChainWithBalances> Environment<C> for Client<C> {
#[cfg(test)]
mod tests {
use super::*;
use frame_support::weights::IdentityFee;
use frame_support::weights::{IdentityFee, Weight};
use futures::{
channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
future::FutureExt,
@@ -202,10 +217,19 @@ mod tests {
type Balance = u32;
type Index = u32;
type Signature = sp_runtime::testing::TestSignature;
fn max_extrinsic_size() -> u32 {
unreachable!()
}
fn max_extrinsic_weight() -> Weight {
unreachable!()
}
}
impl Chain for TestChain {
const NAME: &'static str = "Test";
const TOKEN_ID: Option<&'static str> = None;
const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = "BestTestHeader";
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_millis(1);
const STORAGE_PROOF_OVERHEAD: u32 = 0;
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = 0;
@@ -24,7 +24,6 @@ mod error;
mod rpc;
mod sync_header;
pub mod finality_source;
pub mod guard;
pub mod metrics;
@@ -32,10 +31,11 @@ use std::time::Duration;
pub use crate::{
chain::{
BlockWithJustification, CallOf, Chain, ChainWithBalances, TransactionSignScheme,
TransactionStatusOf, UnsignedTransaction, WeightToFeeOf,
AccountKeyPairOf, BlockWithJustification, CallOf, Chain, ChainWithBalances,
ChainWithGrandpa, ChainWithMessages, SignParam, TransactionSignScheme, TransactionStatusOf,
UnsignedTransaction, WeightToFeeOf,
},
client::{Client, OpaqueGrandpaAuthoritiesSet, Subscription},
client::{ChainRuntimeVersion, Client, OpaqueGrandpaAuthoritiesSet, Subscription},
error::{Error, Result},
sync_header::SyncHeader,
};
@@ -56,11 +56,18 @@ pub struct ConnectionParams {
pub port: u16,
/// Use secure websocket connection.
pub secure: bool,
/// Defined chain runtime version
pub chain_runtime_version: ChainRuntimeVersion,
}
impl Default for ConnectionParams {
fn default() -> Self {
ConnectionParams { host: "localhost".into(), port: 9944, secure: false }
ConnectionParams {
host: "localhost".into(),
port: 9944,
secure: false,
chain_runtime_version: ChainRuntimeVersion::Auto,
}
}
}
@@ -14,48 +14,84 @@
// 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::{chain::Chain, client::Client};
use crate::{chain::Chain, client::Client, Error as SubstrateError};
use async_std::sync::{Arc, RwLock};
use async_trait::async_trait;
use codec::Decode;
use num_traits::One;
use relay_utils::metrics::{
metric_name, register, F64SharedRef, Gauge, Metric, PrometheusError, Registry,
StandaloneMetric, F64,
};
use sp_core::storage::StorageKey;
use sp_runtime::{traits::UniqueSaturatedInto, FixedPointNumber};
use std::time::Duration;
use sp_core::storage::{StorageData, StorageKey};
use sp_runtime::{traits::UniqueSaturatedInto, FixedPointNumber, FixedU128};
use std::{marker::PhantomData, time::Duration};
/// Storage value update interval (in blocks).
const UPDATE_INTERVAL_IN_BLOCKS: u32 = 5;
/// Metric that represents fixed-point runtime storage value as float gauge.
#[derive(Clone, Debug)]
pub struct FloatStorageValueMetric<C: Chain, T: Clone> {
client: Client<C>,
storage_key: StorageKey,
maybe_default_value: Option<T>,
metric: Gauge<F64>,
shared_value_ref: F64SharedRef,
/// Fied-point storage value and the way it is decoded from the raw storage value.
pub trait FloatStorageValue: 'static + Clone + Send + Sync {
/// Type of the value.
type Value: FixedPointNumber;
/// Try to decode value from the raw storage value.
fn decode(
&self,
maybe_raw_value: Option<StorageData>,
) -> Result<Option<Self::Value>, SubstrateError>;
}
impl<C: Chain, T: Decode + FixedPointNumber> FloatStorageValueMetric<C, T> {
/// Implementation of `FloatStorageValue` that expects encoded `FixedU128` value and returns `1` if
/// value is missing from the storage.
#[derive(Clone, Debug, Default)]
pub struct FixedU128OrOne;
impl FloatStorageValue for FixedU128OrOne {
type Value = FixedU128;
fn decode(
&self,
maybe_raw_value: Option<StorageData>,
) -> Result<Option<Self::Value>, SubstrateError> {
maybe_raw_value
.map(|raw_value| {
FixedU128::decode(&mut &raw_value.0[..])
.map_err(SubstrateError::ResponseParseFailed)
.map(Some)
})
.unwrap_or_else(|| Ok(Some(FixedU128::one())))
}
}
/// Metric that represents fixed-point runtime storage value as float gauge.
#[derive(Clone, Debug)]
pub struct FloatStorageValueMetric<C: Chain, V: FloatStorageValue> {
value_converter: V,
client: Client<C>,
storage_key: StorageKey,
metric: Gauge<F64>,
shared_value_ref: F64SharedRef,
_phantom: PhantomData<V>,
}
impl<C: Chain, V: FloatStorageValue> FloatStorageValueMetric<C, V> {
/// Create new metric.
pub fn new(
value_converter: V,
client: Client<C>,
storage_key: StorageKey,
maybe_default_value: Option<T>,
name: String,
help: String,
) -> Result<Self, PrometheusError> {
let shared_value_ref = Arc::new(RwLock::new(None));
Ok(FloatStorageValueMetric {
value_converter,
client,
storage_key,
maybe_default_value,
metric: Gauge::new(metric_name(None, &name), help)?,
shared_value_ref,
_phantom: Default::default(),
})
}
@@ -65,20 +101,14 @@ impl<C: Chain, T: Decode + FixedPointNumber> FloatStorageValueMetric<C, T> {
}
}
impl<C: Chain, T> Metric for FloatStorageValueMetric<C, T>
where
T: 'static + Decode + Send + Sync + FixedPointNumber,
{
impl<C: Chain, V: FloatStorageValue> Metric for FloatStorageValueMetric<C, V> {
fn register(&self, registry: &Registry) -> Result<(), PrometheusError> {
register(self.metric.clone(), registry).map(drop)
}
}
#[async_trait]
impl<C: Chain, T> StandaloneMetric for FloatStorageValueMetric<C, T>
where
T: 'static + Decode + Send + Sync + FixedPointNumber,
{
impl<C: Chain, V: FloatStorageValue> StandaloneMetric for FloatStorageValueMetric<C, V> {
fn update_interval(&self) -> Duration {
C::AVERAGE_BLOCK_INTERVAL * UPDATE_INTERVAL_IN_BLOCKS
}
@@ -86,16 +116,18 @@ where
async fn update(&self) {
let value = self
.client
.storage_value::<T>(self.storage_key.clone(), None)
.raw_storage_value(self.storage_key.clone(), None)
.await
.map(|maybe_storage_value| {
maybe_storage_value.or(self.maybe_default_value).map(|storage_value| {
storage_value.into_inner().unique_saturated_into() as f64 /
T::DIV.unique_saturated_into() as f64
.and_then(|maybe_storage_value| {
self.value_converter.decode(maybe_storage_value).map(|maybe_fixed_point_value| {
maybe_fixed_point_value.map(|fixed_point_value| {
fixed_point_value.into_inner().unique_saturated_into() as f64 /
V::Value::DIV.unique_saturated_into() as f64
})
})
})
.map_err(drop);
relay_utils::metrics::set_gauge_value(&self.metric, value);
.map_err(|e| e.to_string());
relay_utils::metrics::set_gauge_value(&self.metric, value.clone());
*self.shared_value_ref.write().await = value.ok().and_then(|x| x);
}
}
@@ -16,7 +16,7 @@
//! Contains several Substrate-specific metrics that may be exposed by relay.
pub use float_storage_value::FloatStorageValueMetric;
pub use float_storage_value::{FixedU128OrOne, FloatStorageValue, FloatStorageValueMetric};
pub use storage_proof_overhead::StorageProofOverheadMetric;
mod float_storage_value;
@@ -16,8 +16,7 @@
//! The most generic Substrate node RPC interface.
use crate::chain::Chain;
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use pallet_transaction_payment_rpc_runtime_api::FeeDetails;
use sc_rpc_api::{state::ReadProof, system::Health};
use sp_core::{
@@ -27,33 +26,51 @@ use sp_core::{
use sp_rpc::number::NumberOrHex;
use sp_version::RuntimeVersion;
jsonrpsee_proc_macros::rpc_client_api! {
pub(crate) Substrate<C: Chain> {
#[rpc(method = "system_health", positional_params)]
fn system_health() -> Health;
#[rpc(method = "chain_getHeader", positional_params)]
fn chain_get_header(block_hash: Option<C::Hash>) -> C::Header;
#[rpc(method = "chain_getFinalizedHead", positional_params)]
fn chain_get_finalized_head() -> C::Hash;
#[rpc(method = "chain_getBlock", positional_params)]
fn chain_get_block(block_hash: Option<C::Hash>) -> C::SignedBlock;
#[rpc(method = "chain_getBlockHash", positional_params)]
fn chain_get_block_hash(block_number: Option<C::BlockNumber>) -> C::Hash;
#[rpc(method = "system_accountNextIndex", positional_params)]
fn system_account_next_index(account_id: C::AccountId) -> C::Index;
#[rpc(method = "author_submitExtrinsic", positional_params)]
fn author_submit_extrinsic(extrinsic: Bytes) -> C::Hash;
#[rpc(method = "author_pendingExtrinsics", positional_params)]
fn author_pending_extrinsics() -> Vec<Bytes>;
#[rpc(method = "state_call", positional_params)]
fn state_call(method: String, data: Bytes, at_block: Option<C::Hash>) -> Bytes;
#[rpc(method = "state_getStorage", positional_params)]
fn state_get_storage(key: StorageKey, at_block: Option<C::Hash>) -> Option<StorageData>;
#[rpc(method = "state_getReadProof", positional_params)]
fn state_prove_storage(keys: Vec<StorageKey>, hash: Option<C::Hash>) -> ReadProof<C::Hash>;
#[rpc(method = "state_getRuntimeVersion", positional_params)]
fn state_runtime_version() -> RuntimeVersion;
#[rpc(method = "payment_queryFeeDetails", positional_params)]
fn payment_query_fee_details(extrinsic: Bytes, at_block: Option<C::Hash>) -> FeeDetails<NumberOrHex>;
}
#[rpc(client)]
pub(crate) trait Substrate<AccountId, BlockNumber, Hash, Header, Index, SignedBlock> {
#[method(name = "system_health", param_kind = array)]
async fn system_health(&self) -> RpcResult<Health>;
#[method(name = "system_properties", param_kind = array)]
async fn system_properties(&self) -> RpcResult<sc_chain_spec::Properties>;
#[method(name = "chain_getHeader", param_kind = array)]
async fn chain_get_header(&self, block_hash: Option<Hash>) -> RpcResult<Header>;
#[method(name = "chain_getFinalizedHead", param_kind = array)]
async fn chain_get_finalized_head(&self) -> RpcResult<Hash>;
#[method(name = "chain_getBlock", param_kind = array)]
async fn chain_get_block(&self, block_hash: Option<Hash>) -> RpcResult<SignedBlock>;
#[method(name = "chain_getBlockHash", param_kind = array)]
async fn chain_get_block_hash(&self, block_number: Option<BlockNumber>) -> RpcResult<Hash>;
#[method(name = "system_accountNextIndex", param_kind = array)]
async fn system_account_next_index(&self, account_id: AccountId) -> RpcResult<Index>;
#[method(name = "author_submitExtrinsic", param_kind = array)]
async fn author_submit_extrinsic(&self, extrinsic: Bytes) -> RpcResult<Hash>;
#[method(name = "author_pendingExtrinsics", param_kind = array)]
async fn author_pending_extrinsics(&self) -> RpcResult<Vec<Bytes>>;
#[method(name = "state_call", param_kind = array)]
async fn state_call(
&self,
method: String,
data: Bytes,
at_block: Option<Hash>,
) -> RpcResult<Bytes>;
#[method(name = "state_getStorage", param_kind = array)]
async fn state_get_storage(
&self,
key: StorageKey,
at_block: Option<Hash>,
) -> RpcResult<Option<StorageData>>;
#[method(name = "state_getReadProof", param_kind = array)]
async fn state_prove_storage(
&self,
keys: Vec<StorageKey>,
hash: Option<Hash>,
) -> RpcResult<ReadProof<Hash>>;
#[method(name = "state_getRuntimeVersion", param_kind = array)]
async fn state_runtime_version(&self) -> RpcResult<RuntimeVersion>;
#[method(name = "payment_queryFeeDetails", param_kind = array)]
async fn payment_query_fee_details(
&self,
extrinsic: Bytes,
at_block: Option<Hash>,
) -> RpcResult<FeeDetails<NumberOrHex>>;
}
@@ -44,7 +44,11 @@ impl<Header> From<Header> for SyncHeader<Header> {
}
}
impl<Header: HeaderT> FinalitySourceHeader<Header::Number> for SyncHeader<Header> {
impl<Header: HeaderT> FinalitySourceHeader<Header::Hash, Header::Number> for SyncHeader<Header> {
fn hash(&self) -> Header::Hash {
self.0.hash()
}
fn number(&self) -> Header::Number {
*self.0.number()
}
@@ -2,11 +2,11 @@
name = "relay-westend-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.2.0" }
codec = { package = "parity-scale-codec", version = "3.0.0" }
relay-substrate-client = { path = "../client-substrate" }
relay-utils = { path = "../utils" }
@@ -16,5 +16,6 @@ bp-westend = { path = "../../primitives/chain-westend" }
# Substrate Dependencies
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
@@ -16,7 +16,8 @@
//! Types used to connect to the Westend chain.
use relay_substrate_client::{Chain, ChainBase, ChainWithBalances};
use frame_support::weights::Weight;
use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, ChainWithGrandpa};
use sp_core::storage::StorageKey;
use std::time::Duration;
@@ -40,10 +41,21 @@ impl ChainBase for Westend {
type Balance = bp_westend::Balance;
type Index = bp_westend::Nonce;
type Signature = bp_westend::Signature;
fn max_extrinsic_size() -> u32 {
bp_westend::Westend::max_extrinsic_size()
}
fn max_extrinsic_weight() -> Weight {
bp_westend::Westend::max_extrinsic_weight()
}
}
impl Chain for Westend {
const NAME: &'static str = "Westend";
const TOKEN_ID: Option<&'static str> = None;
const BEST_FINALIZED_HEADER_ID_METHOD: &'static str =
bp_westend::BEST_FINALIZED_WESTEND_HEADER_METHOD;
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6);
const STORAGE_PROOF_OVERHEAD: u32 = bp_westend::EXTRA_STORAGE_PROOF_SIZE;
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_westend::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
@@ -53,6 +65,11 @@ impl Chain for Westend {
type WeightToFee = bp_westend::WeightToFee;
}
impl ChainWithGrandpa for Westend {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str =
bp_westend::WITH_WESTEND_GRANDPA_PALLET_NAME;
}
impl ChainWithBalances for Westend {
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
StorageKey(bp_westend::account_info_storage_key(account_id))
@@ -2,14 +2,14 @@
name = "relay-wococo-client"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.2.0" }
codec = { package = "parity-scale-codec", version = "3.0.0" }
relay-substrate-client = { path = "../client-substrate" }
relay-utils = { path = "../utils" }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
# Bridge dependencies
bridge-runtime-common = { path = "../../bin/runtime-common" }
@@ -16,10 +16,12 @@
//! Types used to connect to the Wococo-Substrate chain.
use bp_messages::MessageNonce;
use codec::Encode;
use frame_support::weights::Weight;
use relay_substrate_client::{
Chain, ChainBase, ChainWithBalances, TransactionEraOf, TransactionSignScheme,
UnsignedTransaction,
Chain, ChainBase, ChainWithBalances, ChainWithGrandpa, ChainWithMessages,
Error as SubstrateError, SignParam, TransactionSignScheme, UnsignedTransaction,
};
use sp_core::{storage::StorageKey, Pair};
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
@@ -47,10 +49,21 @@ impl ChainBase for Wococo {
type Balance = bp_wococo::Balance;
type Index = bp_wococo::Nonce;
type Signature = bp_wococo::Signature;
fn max_extrinsic_size() -> u32 {
bp_wococo::Wococo::max_extrinsic_size()
}
fn max_extrinsic_weight() -> Weight {
bp_wococo::Wococo::max_extrinsic_weight()
}
}
impl Chain for Wococo {
const NAME: &'static str = "Wococo";
const TOKEN_ID: Option<&'static str> = None;
const BEST_FINALIZED_HEADER_ID_METHOD: &'static str =
bp_wococo::BEST_FINALIZED_WOCOCO_HEADER_METHOD;
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6);
const STORAGE_PROOF_OVERHEAD: u32 = bp_wococo::EXTRA_STORAGE_PROOF_SIZE;
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_wococo::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
@@ -60,6 +73,24 @@ impl Chain for Wococo {
type WeightToFee = bp_wococo::WeightToFee;
}
impl ChainWithGrandpa for Wococo {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = bp_wococo::WITH_WOCOCO_GRANDPA_PALLET_NAME;
}
impl ChainWithMessages for Wococo {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str =
bp_wococo::WITH_WOCOCO_MESSAGES_PALLET_NAME;
const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str =
bp_wococo::TO_WOCOCO_MESSAGE_DETAILS_METHOD;
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_CHAIN: Weight =
bp_wococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce =
bp_wococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX;
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce =
bp_wococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
type WeightInfo = ();
}
impl ChainWithBalances for Wococo {
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
StorageKey(bp_wococo::account_info_storage_key(account_id))
@@ -71,34 +102,30 @@ impl TransactionSignScheme for Wococo {
type AccountKeyPair = sp_core::sr25519::Pair;
type SignedTransaction = crate::runtime::UncheckedExtrinsic;
fn sign_transaction(
genesis_hash: <Self::Chain as ChainBase>::Hash,
signer: &Self::AccountKeyPair,
era: TransactionEraOf<Self::Chain>,
unsigned: UnsignedTransaction<Self::Chain>,
) -> Self::SignedTransaction {
fn sign_transaction(param: SignParam<Self>) -> Result<Self::SignedTransaction, SubstrateError> {
let raw_payload = SignedPayload::new(
unsigned.call,
param.unsigned.call.clone(),
bp_wococo::SignedExtensions::new(
bp_wococo::VERSION,
era,
genesis_hash,
unsigned.nonce,
unsigned.tip,
param.spec_version,
param.transaction_version,
param.era,
param.genesis_hash,
param.unsigned.nonce,
param.unsigned.tip,
),
)
.expect("SignedExtension never fails.");
let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
let signer: sp_runtime::MultiSigner = signer.public().into();
let signature = raw_payload.using_encoded(|payload| param.signer.sign(payload));
let signer: sp_runtime::MultiSigner = param.signer.public().into();
let (call, extra, _) = raw_payload.deconstruct();
bp_wococo::UncheckedExtrinsic::new_signed(
Ok(bp_wococo::UncheckedExtrinsic::new_signed(
call,
sp_runtime::MultiAddress::Id(signer.into_account()),
signature.into(),
extra,
)
))
}
fn is_signed(tx: &Self::SignedTransaction) -> bool {
@@ -17,9 +17,9 @@
//! Types that are specific to the Wococo runtime.
use bp_messages::{LaneId, UnrewardedRelayersState};
use bp_polkadot_core::PolkadotLike;
use bp_polkadot_core::{AccountAddress, Balance, PolkadotLike};
use bp_runtime::Chain;
use codec::{Decode, Encode};
use codec::{Compact, Decode, Encode};
use frame_support::weights::Weight;
use scale_info::TypeInfo;
@@ -66,12 +66,15 @@ pub enum Call {
/// System pallet.
#[codec(index = 0)]
System(SystemCall),
/// Balances pallet.
#[codec(index = 4)]
Balances(BalancesCall),
/// Rococo bridge pallet.
#[codec(index = 40)]
BridgeGrandpaRococo(BridgeGrandpaRococoCall),
/// Rococo messages pallet.
#[codec(index = 43)]
BridgeMessagesRococo(BridgeMessagesRococoCall),
BridgeRococoMessages(BridgeRococoMessagesCall),
}
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
@@ -81,6 +84,13 @@ pub enum SystemCall {
remark(Vec<u8>),
}
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
#[allow(non_camel_case_types)]
pub enum BalancesCall {
#[codec(index = 0)]
transfer(AccountAddress, Compact<Balance>),
}
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
#[allow(non_camel_case_types)]
pub enum BridgeGrandpaRococoCall {
@@ -95,7 +105,7 @@ pub enum BridgeGrandpaRococoCall {
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
#[allow(non_camel_case_types)]
pub enum BridgeMessagesRococoCall {
pub enum BridgeRococoMessagesCall {
#[codec(index = 3)]
send_message(
LaneId,
+1 -1
View File
@@ -2,7 +2,7 @@
name = "finality-relay"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
description = "Finality proofs relay"
@@ -29,7 +29,7 @@ use futures::{select, Future, FutureExt, Stream, StreamExt};
use num_traits::{One, Saturating};
use relay_utils::{
metrics::MetricsParams, relay_loop::Client as RelayClient, retry_backoff, FailedClient,
MaybeConnectionError,
HeaderId, MaybeConnectionError,
};
use std::{
pin::Pin,
@@ -87,7 +87,9 @@ pub trait SourceClient<P: FinalitySyncPipeline>: RelayClient {
#[async_trait]
pub trait TargetClient<P: FinalitySyncPipeline>: RelayClient {
/// Get best finalized source block number.
async fn best_finalized_source_block_number(&self) -> Result<P::Number, Self::Error>;
async fn best_finalized_source_block_id(
&self,
) -> Result<HeaderId<P::Hash, P::Number>, Self::Error>;
/// Submit header finality proof.
async fn submit_finality_proof(
@@ -114,7 +116,11 @@ pub async fn run<P: FinalitySyncPipeline>(
let exit_signal = exit_signal.shared();
relay_utils::relay_loop(source_client, target_client)
.with_metrics(metrics_params)
.loop_metric(SyncLoopMetrics::new(Some(&metrics_prefix::<P>()))?)?
.loop_metric(SyncLoopMetrics::new(
Some(&metrics_prefix::<P>()),
"source",
"source_at_target",
)?)?
.expose()
.await?
.run(metrics_prefix::<P>(), move |source_client, target_client, metrics| {
@@ -169,7 +175,7 @@ where
/// Information about transaction that we have submitted.
#[derive(Debug, Clone)]
struct Transaction<Number> {
pub(crate) struct Transaction<Number> {
/// Time when we have submitted this transaction.
pub time: Instant,
/// The number of the header we have submitted.
@@ -181,7 +187,7 @@ pub(crate) struct RestartableFinalityProofsStream<S> {
/// Flag that the stream needs to be restarted.
pub(crate) needs_restart: bool,
/// The stream itself.
stream: Pin<Box<S>>,
pub(crate) stream: Pin<Box<S>>,
}
#[cfg(test)]
@@ -192,15 +198,16 @@ impl<S> From<S> for RestartableFinalityProofsStream<S> {
}
/// Finality synchronization loop state.
struct FinalityLoopState<'a, P: FinalitySyncPipeline, FinalityProofsStream> {
pub(crate) struct FinalityLoopState<'a, P: FinalitySyncPipeline, FinalityProofsStream> {
/// Synchronization loop progress.
progress: &'a mut (Instant, Option<P::Number>),
pub(crate) progress: &'a mut (Instant, Option<P::Number>),
/// Finality proofs stream.
finality_proofs_stream: &'a mut RestartableFinalityProofsStream<FinalityProofsStream>,
pub(crate) finality_proofs_stream:
&'a mut RestartableFinalityProofsStream<FinalityProofsStream>,
/// Recent finality proofs that we have read from the stream.
recent_finality_proofs: &'a mut FinalityProofs<P>,
pub(crate) recent_finality_proofs: &'a mut FinalityProofs<P>,
/// Last transaction that we have submitted to the target node.
last_transaction: Option<Transaction<P::Number>>,
pub(crate) last_transaction: Option<Transaction<P::Number>>,
}
async fn run_until_connection_lost<P: FinalitySyncPipeline>(
@@ -280,7 +287,7 @@ async fn run_until_connection_lost<P: FinalitySyncPipeline>(
}
}
async fn run_loop_iteration<P, SC, TC>(
pub(crate) async fn run_loop_iteration<P, SC, TC>(
source_client: &SC,
target_client: &TC,
state: FinalityLoopState<'_, P, SC::FinalityProofsStream>,
@@ -295,13 +302,31 @@ where
// read best source headers ids from source and target nodes
let best_number_at_source =
source_client.best_finalized_block_number().await.map_err(Error::Source)?;
let best_number_at_target = target_client
.best_finalized_source_block_number()
let best_id_at_target =
target_client.best_finalized_source_block_id().await.map_err(Error::Target)?;
let best_number_at_target = best_id_at_target.0;
let different_hash_at_source = ensure_same_fork::<P, _>(&best_id_at_target, source_client)
.await
.map_err(Error::Target)?;
.map_err(Error::Source)?;
let using_same_fork = different_hash_at_source.is_none();
if let Some(ref different_hash_at_source) = different_hash_at_source {
log::error!(
target: "bridge",
"Source node ({}) and pallet at target node ({}) have different headers at the same height {:?}: \
at-source {:?} vs at-target {:?}",
P::SOURCE_NAME,
P::TARGET_NAME,
best_number_at_target,
different_hash_at_source,
best_id_at_target.1,
);
}
if let Some(ref metrics_sync) = *metrics_sync {
metrics_sync.update_best_block_at_source(best_number_at_source);
metrics_sync.update_best_block_at_target(best_number_at_target);
metrics_sync.update_using_same_fork(using_same_fork);
}
*state.progress =
print_sync_progress::<P>(*state.progress, best_number_at_source, best_number_at_target);
@@ -427,6 +452,22 @@ where
Ok(selected_finality_proof)
}
/// Ensures that both clients are on the same fork.
///
/// Returns `Some(_)` with header has at the source client if headers are different.
async fn ensure_same_fork<P: FinalitySyncPipeline, SC: SourceClient<P>>(
best_id_at_target: &HeaderId<P::Hash, P::Number>,
source_client: &SC,
) -> Result<Option<P::Hash>, SC::Error> {
let header_at_source = source_client.header_and_finality_proof(best_id_at_target.0).await?.0;
let header_hash_at_source = header_at_source.hash();
Ok(if best_id_at_target.1 == header_hash_at_source {
None
} else {
Some(header_hash_at_source)
})
}
/// Finality proof that has been selected by the `read_missing_headers` function.
pub(crate) enum SelectedFinalityProof<Header, FinalityProof> {
/// Mandatory header and its proof has been selected. We shall submit proof for this header.
@@ -20,10 +20,12 @@
use crate::{
finality_loop::{
prune_recent_finality_proofs, read_finality_proofs_from_stream, run,
select_better_recent_finality_proof, select_header_to_submit, FinalityProofs,
FinalitySyncParams, RestartableFinalityProofsStream, SourceClient, TargetClient,
prune_recent_finality_proofs, read_finality_proofs_from_stream, run, run_loop_iteration,
select_better_recent_finality_proof, select_header_to_submit, FinalityLoopState,
FinalityProofs, FinalitySyncParams, RestartableFinalityProofsStream, SourceClient,
TargetClient,
},
sync_loop_metrics::SyncLoopMetrics,
FinalityProof, FinalitySyncPipeline, SourceHeader,
};
@@ -31,12 +33,18 @@ use async_trait::async_trait;
use futures::{FutureExt, Stream, StreamExt};
use parking_lot::Mutex;
use relay_utils::{
metrics::MetricsParams, relay_loop::Client as RelayClient, MaybeConnectionError,
metrics::MetricsParams, relay_loop::Client as RelayClient, HeaderId, MaybeConnectionError,
};
use std::{
collections::HashMap,
pin::Pin,
sync::Arc,
time::{Duration, Instant},
};
use std::{collections::HashMap, pin::Pin, sync::Arc, time::Duration};
type IsMandatory = bool;
type TestNumber = u64;
type TestHash = u64;
#[derive(Debug, Clone)]
enum TestError {
@@ -56,16 +64,20 @@ impl FinalitySyncPipeline for TestFinalitySyncPipeline {
const SOURCE_NAME: &'static str = "TestSource";
const TARGET_NAME: &'static str = "TestTarget";
type Hash = u64;
type Hash = TestHash;
type Number = TestNumber;
type Header = TestSourceHeader;
type FinalityProof = TestFinalityProof;
}
#[derive(Debug, Clone, PartialEq)]
struct TestSourceHeader(IsMandatory, TestNumber);
struct TestSourceHeader(IsMandatory, TestNumber, TestHash);
impl SourceHeader<TestHash, TestNumber> for TestSourceHeader {
fn hash(&self) -> TestHash {
self.2
}
impl SourceHeader<TestNumber> for TestSourceHeader {
fn number(&self) -> TestNumber {
self.1
}
@@ -90,7 +102,7 @@ struct ClientsData {
source_headers: HashMap<TestNumber, (TestSourceHeader, Option<TestFinalityProof>)>,
source_proofs: Vec<TestFinalityProof>,
target_best_block_number: TestNumber,
target_best_block_id: HeaderId<TestHash, TestNumber>,
target_headers: Vec<(TestSourceHeader, TestFinalityProof)>,
}
@@ -152,10 +164,12 @@ impl RelayClient for TestTargetClient {
#[async_trait]
impl TargetClient<TestFinalitySyncPipeline> for TestTargetClient {
async fn best_finalized_source_block_number(&self) -> Result<TestNumber, TestError> {
async fn best_finalized_source_block_id(
&self,
) -> Result<HeaderId<TestHash, TestNumber>, TestError> {
let mut data = self.data.lock();
(self.on_method_call)(&mut *data);
Ok(data.target_best_block_number)
Ok(data.target_best_block_id)
}
async fn submit_finality_proof(
@@ -165,7 +179,7 @@ impl TargetClient<TestFinalitySyncPipeline> for TestTargetClient {
) -> Result<(), TestError> {
let mut data = self.data.lock();
(self.on_method_call)(&mut *data);
data.target_best_block_number = header.number();
data.target_best_block_id = HeaderId(header.number(), header.hash());
data.target_headers.push((header, proof));
Ok(())
}
@@ -187,7 +201,7 @@ fn prepare_test_clients(
source_headers,
source_proofs: vec![TestFinalityProof(12), TestFinalityProof(14)],
target_best_block_number: 5,
target_best_block_id: HeaderId(5, 5),
target_headers: vec![],
}));
(
@@ -199,6 +213,15 @@ fn prepare_test_clients(
)
}
fn test_sync_params() -> FinalitySyncParams {
FinalitySyncParams {
tick: Duration::from_secs(0),
recent_finality_proofs_limit: 1024,
stall_timeout: Duration::from_secs(1),
only_mandatory_headers: false,
}
}
fn run_sync_loop(
state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync + 'static,
) -> ClientsData {
@@ -207,21 +230,17 @@ fn run_sync_loop(
exit_sender,
state_function,
vec![
(6, (TestSourceHeader(false, 6), None)),
(7, (TestSourceHeader(false, 7), Some(TestFinalityProof(7)))),
(8, (TestSourceHeader(true, 8), Some(TestFinalityProof(8)))),
(9, (TestSourceHeader(false, 9), Some(TestFinalityProof(9)))),
(10, (TestSourceHeader(false, 10), None)),
(5, (TestSourceHeader(false, 5, 5), None)),
(6, (TestSourceHeader(false, 6, 6), None)),
(7, (TestSourceHeader(false, 7, 7), Some(TestFinalityProof(7)))),
(8, (TestSourceHeader(true, 8, 8), Some(TestFinalityProof(8)))),
(9, (TestSourceHeader(false, 9, 9), Some(TestFinalityProof(9)))),
(10, (TestSourceHeader(false, 10, 10), None)),
]
.into_iter()
.collect(),
);
let sync_params = FinalitySyncParams {
tick: Duration::from_secs(0),
recent_finality_proofs_limit: 1024,
stall_timeout: Duration::from_secs(1),
only_mandatory_headers: false,
};
let sync_params = test_sync_params();
let clients_data = source_client.data.clone();
let _ = async_std::task::block_on(run(
@@ -246,38 +265,38 @@ fn finality_sync_loop_works() {
//
// once this ^^^ is done, we generate more blocks && read proof for blocks 12 and 14 from
// the stream
if data.target_best_block_number == 9 {
if data.target_best_block_id.0 == 9 {
data.source_best_block_number = 14;
data.source_headers.insert(11, (TestSourceHeader(false, 11), None));
data.source_headers.insert(11, (TestSourceHeader(false, 11, 11), None));
data.source_headers
.insert(12, (TestSourceHeader(false, 12), Some(TestFinalityProof(12))));
data.source_headers.insert(13, (TestSourceHeader(false, 13), None));
.insert(12, (TestSourceHeader(false, 12, 12), Some(TestFinalityProof(12))));
data.source_headers.insert(13, (TestSourceHeader(false, 13, 13), None));
data.source_headers
.insert(14, (TestSourceHeader(false, 14), Some(TestFinalityProof(14))));
.insert(14, (TestSourceHeader(false, 14, 14), Some(TestFinalityProof(14))));
}
// once this ^^^ is done, we generate more blocks && read persistent proof for block 16
if data.target_best_block_number == 14 {
if data.target_best_block_id.0 == 14 {
data.source_best_block_number = 17;
data.source_headers.insert(15, (TestSourceHeader(false, 15), None));
data.source_headers.insert(15, (TestSourceHeader(false, 15, 15), None));
data.source_headers
.insert(16, (TestSourceHeader(false, 16), Some(TestFinalityProof(16))));
data.source_headers.insert(17, (TestSourceHeader(false, 17), None));
.insert(16, (TestSourceHeader(false, 16, 16), Some(TestFinalityProof(16))));
data.source_headers.insert(17, (TestSourceHeader(false, 17, 17), None));
}
data.target_best_block_number == 16
data.target_best_block_id.0 == 16
});
assert_eq!(
client_data.target_headers,
vec![
// before adding 11..14: finality proof for mandatory header#8
(TestSourceHeader(true, 8), TestFinalityProof(8)),
(TestSourceHeader(true, 8, 8), TestFinalityProof(8)),
// before adding 11..14: persistent finality proof for non-mandatory header#9
(TestSourceHeader(false, 9), TestFinalityProof(9)),
(TestSourceHeader(false, 9, 9), TestFinalityProof(9)),
// after adding 11..14: ephemeral finality proof for non-mandatory header#14
(TestSourceHeader(false, 14), TestFinalityProof(14)),
(TestSourceHeader(false, 14, 14), TestFinalityProof(14)),
// after adding 15..17: persistent finality proof for non-mandatory header#16
(TestSourceHeader(false, 16), TestFinalityProof(16)),
(TestSourceHeader(false, 16, 16), TestFinalityProof(16)),
],
);
}
@@ -291,11 +310,11 @@ fn run_only_mandatory_headers_mode_test(
exit_sender,
|_| false,
vec![
(6, (TestSourceHeader(false, 6), Some(TestFinalityProof(6)))),
(7, (TestSourceHeader(false, 7), Some(TestFinalityProof(7)))),
(8, (TestSourceHeader(has_mandatory_headers, 8), Some(TestFinalityProof(8)))),
(9, (TestSourceHeader(false, 9), Some(TestFinalityProof(9)))),
(10, (TestSourceHeader(false, 10), Some(TestFinalityProof(10)))),
(6, (TestSourceHeader(false, 6, 6), Some(TestFinalityProof(6)))),
(7, (TestSourceHeader(false, 7, 7), Some(TestFinalityProof(7)))),
(8, (TestSourceHeader(has_mandatory_headers, 8, 8), Some(TestFinalityProof(8)))),
(9, (TestSourceHeader(false, 9, 9), Some(TestFinalityProof(9)))),
(10, (TestSourceHeader(false, 10, 10), Some(TestFinalityProof(10)))),
]
.into_iter()
.collect(),
@@ -322,7 +341,7 @@ fn select_header_to_submit_skips_non_mandatory_headers_when_only_mandatory_heade
assert_eq!(run_only_mandatory_headers_mode_test(true, false), None);
assert_eq!(
run_only_mandatory_headers_mode_test(false, false),
Some((TestSourceHeader(false, 10), TestFinalityProof(10))),
Some((TestSourceHeader(false, 10, 10), TestFinalityProof(10))),
);
}
@@ -330,11 +349,11 @@ fn select_header_to_submit_skips_non_mandatory_headers_when_only_mandatory_heade
fn select_header_to_submit_selects_mandatory_headers_when_only_mandatory_headers_are_required() {
assert_eq!(
run_only_mandatory_headers_mode_test(true, true),
Some((TestSourceHeader(true, 8), TestFinalityProof(8))),
Some((TestSourceHeader(true, 8, 8), TestFinalityProof(8))),
);
assert_eq!(
run_only_mandatory_headers_mode_test(false, true),
Some((TestSourceHeader(true, 8), TestFinalityProof(8))),
Some((TestSourceHeader(true, 8, 8), TestFinalityProof(8))),
);
}
@@ -345,63 +364,74 @@ fn select_better_recent_finality_proof_works() {
select_better_recent_finality_proof::<TestFinalitySyncPipeline>(
&[(5, TestFinalityProof(5))],
&mut vec![],
Some((TestSourceHeader(false, 2), TestFinalityProof(2))),
Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))),
),
Some((TestSourceHeader(false, 2), TestFinalityProof(2))),
Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))),
);
// if there are no recent finality proofs, nothing is changed
assert_eq!(
select_better_recent_finality_proof::<TestFinalitySyncPipeline>(
&[],
&mut vec![TestSourceHeader(false, 5)],
Some((TestSourceHeader(false, 2), TestFinalityProof(2))),
&mut vec![TestSourceHeader(false, 5, 5)],
Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))),
),
Some((TestSourceHeader(false, 2), TestFinalityProof(2))),
Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))),
);
// if there's no intersection between recent finality proofs and unjustified headers, nothing is
// changed
let mut unjustified_headers = vec![TestSourceHeader(false, 9), TestSourceHeader(false, 10)];
let mut unjustified_headers =
vec![TestSourceHeader(false, 9, 9), TestSourceHeader(false, 10, 10)];
assert_eq!(
select_better_recent_finality_proof::<TestFinalitySyncPipeline>(
&[(1, TestFinalityProof(1)), (4, TestFinalityProof(4))],
&mut unjustified_headers,
Some((TestSourceHeader(false, 2), TestFinalityProof(2))),
Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))),
),
Some((TestSourceHeader(false, 2), TestFinalityProof(2))),
Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))),
);
// if there's intersection between recent finality proofs and unjustified headers, but there are
// no proofs in this intersection, nothing is changed
let mut unjustified_headers =
vec![TestSourceHeader(false, 8), TestSourceHeader(false, 9), TestSourceHeader(false, 10)];
let mut unjustified_headers = vec![
TestSourceHeader(false, 8, 8),
TestSourceHeader(false, 9, 9),
TestSourceHeader(false, 10, 10),
];
assert_eq!(
select_better_recent_finality_proof::<TestFinalitySyncPipeline>(
&[(7, TestFinalityProof(7)), (11, TestFinalityProof(11))],
&mut unjustified_headers,
Some((TestSourceHeader(false, 2), TestFinalityProof(2))),
Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))),
),
Some((TestSourceHeader(false, 2), TestFinalityProof(2))),
Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))),
);
assert_eq!(
unjustified_headers,
vec![TestSourceHeader(false, 8), TestSourceHeader(false, 9), TestSourceHeader(false, 10)]
vec![
TestSourceHeader(false, 8, 8),
TestSourceHeader(false, 9, 9),
TestSourceHeader(false, 10, 10)
]
);
// if there's intersection between recent finality proofs and unjustified headers and there's
// a proof in this intersection:
// - this better (last from intersection) proof is selected;
// - 'obsolete' unjustified headers are pruned.
let mut unjustified_headers =
vec![TestSourceHeader(false, 8), TestSourceHeader(false, 9), TestSourceHeader(false, 10)];
let mut unjustified_headers = vec![
TestSourceHeader(false, 8, 8),
TestSourceHeader(false, 9, 9),
TestSourceHeader(false, 10, 10),
];
assert_eq!(
select_better_recent_finality_proof::<TestFinalitySyncPipeline>(
&[(7, TestFinalityProof(7)), (9, TestFinalityProof(9))],
&mut unjustified_headers,
Some((TestSourceHeader(false, 2), TestFinalityProof(2))),
Some((TestSourceHeader(false, 2, 2), TestFinalityProof(2))),
),
Some((TestSourceHeader(false, 9), TestFinalityProof(9))),
Some((TestSourceHeader(false, 9, 9), TestFinalityProof(9))),
);
}
@@ -475,3 +505,45 @@ fn prune_recent_finality_proofs_works() {
prune_recent_finality_proofs::<TestFinalitySyncPipeline>(20, &mut recent_finality_proofs, 2);
assert_eq!(&original_recent_finality_proofs[5..], recent_finality_proofs);
}
#[test]
fn different_forks_at_source_and_at_target_are_detected() {
let (exit_sender, _exit_receiver) = futures::channel::mpsc::unbounded();
let (source_client, target_client) = prepare_test_clients(
exit_sender,
|_| false,
vec![
(5, (TestSourceHeader(false, 5, 42), None)),
(6, (TestSourceHeader(false, 6, 6), None)),
(7, (TestSourceHeader(false, 7, 7), None)),
(8, (TestSourceHeader(false, 8, 8), None)),
(9, (TestSourceHeader(false, 9, 9), None)),
(10, (TestSourceHeader(false, 10, 10), None)),
]
.into_iter()
.collect(),
);
let mut progress = (Instant::now(), None);
let mut finality_proofs_stream = RestartableFinalityProofsStream {
needs_restart: false,
stream: Box::pin(futures::stream::iter(vec![]).boxed()),
};
let mut recent_finality_proofs = Vec::new();
let metrics_sync = SyncLoopMetrics::new(None, "source", "target").unwrap();
async_std::task::block_on(run_loop_iteration::<TestFinalitySyncPipeline, _, _>(
&source_client,
&target_client,
FinalityLoopState {
progress: &mut progress,
finality_proofs_stream: &mut finality_proofs_stream,
recent_finality_proofs: &mut recent_finality_proofs,
last_transaction: None,
},
&test_sync_params(),
&Some(metrics_sync.clone()),
))
.unwrap();
assert!(!metrics_sync.is_using_same_fork());
}
+7 -4
View File
@@ -19,8 +19,9 @@
//! are still submitted to the target node, but are treated as auxiliary data as we are not trying
//! to submit all source headers to the target node.
pub use crate::finality_loop::{
metrics_prefix, run, FinalitySyncParams, SourceClient, TargetClient,
pub use crate::{
finality_loop::{metrics_prefix, run, FinalitySyncParams, SourceClient, TargetClient},
sync_loop_metrics::SyncLoopMetrics,
};
use bp_header_chain::FinalityProof;
@@ -42,13 +43,15 @@ pub trait FinalitySyncPipeline: 'static + Clone + Debug + Send + Sync {
/// Headers we're syncing are identified by this number.
type Number: relay_utils::BlockNumberBase;
/// Type of header that we're syncing.
type Header: SourceHeader<Self::Number>;
type Header: SourceHeader<Self::Hash, Self::Number>;
/// Finality proof type.
type FinalityProof: FinalityProof<Self::Number>;
}
/// Header that we're receiving from source node.
pub trait SourceHeader<Number>: Clone + Debug + PartialEq + Send + Sync {
pub trait SourceHeader<Hash, Number>: Clone + Debug + PartialEq + Send + Sync {
/// Returns hash of header.
fn hash(&self) -> Hash;
/// Returns number of header.
fn number(&self) -> Number;
/// Returns true if this header needs to be submitted to target node.
@@ -16,49 +16,71 @@
//! Metrics for headers synchronization relay loop.
use relay_utils::metrics::{
metric_name, register, GaugeVec, Metric, Opts, PrometheusError, Registry, U64,
};
use relay_utils::metrics::{metric_name, register, IntGauge, Metric, PrometheusError, Registry};
/// Headers sync metrics.
#[derive(Clone)]
pub struct SyncLoopMetrics {
/// Best syncing headers at "source" and "target" nodes.
best_block_numbers: GaugeVec<U64>,
/// Best syncing header at the source.
best_source_block_number: IntGauge,
/// Best syncing header at the target.
best_target_block_number: IntGauge,
/// Flag that has `0` value when best source headers at the source node and at-target-chain
/// are matching and `1` otherwise.
using_different_forks: IntGauge,
}
impl SyncLoopMetrics {
/// Create and register headers loop metrics.
pub fn new(prefix: Option<&str>) -> Result<Self, PrometheusError> {
pub fn new(
prefix: Option<&str>,
at_source_chain_label: &str,
at_target_chain_label: &str,
) -> Result<Self, PrometheusError> {
Ok(SyncLoopMetrics {
best_block_numbers: GaugeVec::new(
Opts::new(
metric_name(prefix, "best_block_numbers"),
"Best block numbers on source and target nodes",
),
&["node"],
best_source_block_number: IntGauge::new(
metric_name(prefix, &format!("best_{}_block_number", at_source_chain_label)),
format!("Best block number at the {}", at_source_chain_label),
)?,
best_target_block_number: IntGauge::new(
metric_name(prefix, &format!("best_{}_block_number", at_target_chain_label)),
format!("Best block number at the {}", at_target_chain_label),
)?,
using_different_forks: IntGauge::new(
metric_name(prefix, &format!("is_{}_and_{}_using_different_forks", at_source_chain_label, at_target_chain_label)),
"Whether the best finalized source block at target node is different (value 1) from the \
corresponding block at the source node",
)?,
})
}
/// Returns current value of the using-same-fork flag.
#[cfg(test)]
pub(crate) fn is_using_same_fork(&self) -> bool {
self.using_different_forks.get() == 0
}
/// Update best block number at source.
pub fn update_best_block_at_source<Number: Into<u64>>(&self, source_best_number: Number) {
self.best_block_numbers
.with_label_values(&["source"])
.set(source_best_number.into());
self.best_source_block_number.set(source_best_number.into());
}
/// Update best block number at target.
pub fn update_best_block_at_target<Number: Into<u64>>(&self, target_best_number: Number) {
self.best_block_numbers
.with_label_values(&["target"])
.set(target_best_number.into());
self.best_target_block_number.set(target_best_number.into());
}
/// Update using-same-fork flag.
pub fn update_using_same_fork(&self, using_same_fork: bool) {
self.using_different_forks.set(if using_same_fork { 0 } else { 1 })
}
}
impl Metric for SyncLoopMetrics {
fn register(&self, registry: &Registry) -> Result<(), PrometheusError> {
register(self.best_block_numbers.clone(), registry)?;
register(self.best_source_block_number.clone(), registry)?;
register(self.best_target_block_number.clone(), registry)?;
register(self.using_different_forks.clone(), registry)?;
Ok(())
}
}
@@ -2,7 +2,7 @@
name = "substrate-relay-helper"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
@@ -10,23 +10,23 @@ anyhow = "1.0"
thiserror = "1.0.26"
async-std = "1.9.0"
async-trait = "0.1.42"
codec = { package = "parity-scale-codec", version = "2.2.0" }
codec = { package = "parity-scale-codec", version = "3.0.0" }
futures = "0.3.12"
num-traits = "0.2"
log = "0.4.14"
# Bridge dependencies
bp-header-chain = { path = "../../primitives/header-chain" }
bridge-runtime-common = { path = "../../bin/runtime-common" }
finality-grandpa = { version = "0.14.0" }
finality-grandpa = { version = "0.15.0" }
finality-relay = { path = "../finality" }
relay-utils = { path = "../utils" }
messages-relay = { path = "../messages" }
relay-substrate-client = { path = "../client-substrate" }
pallet-bridge-grandpa = { path = "../../modules/grandpa" }
pallet-bridge-messages = { path = "../../modules/messages" }
bp-runtime = { path = "../../primitives/runtime" }
@@ -35,14 +35,18 @@ bp-messages = { path = "../../primitives/messages" }
# Substrate Dependencies
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
[dev-dependencies]
bp-millau = { path = "../../primitives/chain-millau" }
bp-rialto = { path = "../../primitives/chain-rialto" }
bp-rococo = { path = "../../primitives/chain-rococo" }
bp-wococo = { path = "../../primitives/chain-wococo" }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" }
relay-rococo-client = { path = "../client-rococo" }
relay-wococo-client = { path = "../client-wococo" }
rialto-runtime = { path = "../../bin/rialto/runtime" }
@@ -16,39 +16,143 @@
//! Tools for updating conversion rate that is stored in the runtime storage.
use crate::{messages_lane::SubstrateMessageLane, TransactionParams};
use codec::Encode;
use relay_substrate_client::{
transaction_stall_timeout, AccountIdOf, AccountKeyPairOf, CallOf, Chain, Client, SignParam,
TransactionEra, TransactionSignScheme, UnsignedTransaction,
};
use relay_utils::metrics::F64SharedRef;
use std::{future::Future, time::Duration};
use sp_core::{Bytes, Pair};
use std::time::{Duration, Instant};
/// Duration between updater iterations.
const SLEEP_DURATION: Duration = Duration::from_secs(60);
/// Duration which will almost never expire. Since changing conversion rate may require manual
/// intervention (e.g. if call is made through `multisig` pallet), we don't want relayer to
/// resubmit transaction often.
const ALMOST_NEVER_DURATION: Duration = Duration::from_secs(60 * 60 * 24 * 30);
/// Update-conversion-rate transaction status.
#[derive(Debug, Clone, Copy, PartialEq)]
enum TransactionStatus {
/// We have not submitted any transaction recently.
Idle,
/// We have recently submitted transaction that should update conversion rate.
Submitted(f64),
Submitted(Instant, f64),
}
/// Different ways of building 'update conversion rate' calls.
pub trait UpdateConversionRateCallBuilder<C: Chain> {
/// Given conversion rate, build call that updates conversion rate in given chain runtime
/// storage.
fn build_update_conversion_rate_call(conversion_rate: f64) -> anyhow::Result<CallOf<C>>;
}
impl<C: Chain> UpdateConversionRateCallBuilder<C> for () {
fn build_update_conversion_rate_call(_conversion_rate: f64) -> anyhow::Result<CallOf<C>> {
Err(anyhow::format_err!("Conversion rate update is not supported at {}", C::NAME))
}
}
/// Macro that generates `UpdateConversionRateCallBuilder` implementation for the case when
/// you have a direct access to the source chain runtime.
#[rustfmt::skip]
#[macro_export]
macro_rules! generate_direct_update_conversion_rate_call_builder {
(
$source_chain:ident,
$mocked_builder:ident,
$runtime:ty,
$instance:ty,
$parameter:path
) => {
pub struct $mocked_builder;
impl $crate::conversion_rate_update::UpdateConversionRateCallBuilder<$source_chain>
for $mocked_builder
{
fn build_update_conversion_rate_call(
conversion_rate: f64,
) -> anyhow::Result<relay_substrate_client::CallOf<$source_chain>> {
Ok(pallet_bridge_messages::Call::update_pallet_parameter::<$runtime, $instance> {
parameter: $parameter(sp_runtime::FixedU128::from_float(conversion_rate)),
}.into())
}
}
};
}
/// Macro that generates `UpdateConversionRateCallBuilder` implementation for the case when
/// you only have an access to the mocked version of source chain runtime. In this case you
/// should provide "name" of the call variant for the bridge messages calls, the "name" of
/// the variant for the `update_pallet_parameter` call within that first option and the name
/// of the conversion rate parameter itself.
#[rustfmt::skip]
#[macro_export]
macro_rules! generate_mocked_update_conversion_rate_call_builder {
(
$source_chain:ident,
$mocked_builder:ident,
$bridge_messages:path,
$update_pallet_parameter:path,
$parameter:path
) => {
pub struct $mocked_builder;
impl $crate::conversion_rate_update::UpdateConversionRateCallBuilder<$source_chain>
for $mocked_builder
{
fn build_update_conversion_rate_call(
conversion_rate: f64,
) -> anyhow::Result<relay_substrate_client::CallOf<$source_chain>> {
Ok($bridge_messages($update_pallet_parameter($parameter(
sp_runtime::FixedU128::from_float(conversion_rate),
))))
}
}
};
}
/// Run infinite conversion rate updater loop.
///
/// The loop is maintaining the Left -> Right conversion rate, used as `RightTokens = LeftTokens *
/// Rate`.
pub fn run_conversion_rate_update_loop<
SubmitConversionRateFuture: Future<Output = anyhow::Result<()>> + Send + 'static,
>(
pub fn run_conversion_rate_update_loop<Lane, Sign>(
client: Client<Lane::SourceChain>,
transaction_params: TransactionParams<AccountKeyPairOf<Sign>>,
left_to_right_stored_conversion_rate: F64SharedRef,
left_to_base_conversion_rate: F64SharedRef,
right_to_base_conversion_rate: F64SharedRef,
max_difference_ratio: f64,
submit_conversion_rate: impl Fn(f64) -> SubmitConversionRateFuture + Send + 'static,
) {
) where
Lane: SubstrateMessageLane,
Sign: TransactionSignScheme<Chain = Lane::SourceChain>,
AccountIdOf<Lane::SourceChain>: From<<AccountKeyPairOf<Sign> as Pair>::Public>,
{
let stall_timeout = transaction_stall_timeout(
transaction_params.mortality,
Lane::SourceChain::AVERAGE_BLOCK_INTERVAL,
ALMOST_NEVER_DURATION,
);
log::info!(
target: "bridge",
"Starting {} -> {} conversion rate (on {}) update loop. Stall timeout: {}s",
Lane::TargetChain::NAME,
Lane::SourceChain::NAME,
Lane::SourceChain::NAME,
stall_timeout.as_secs(),
);
async_std::task::spawn(async move {
let mut transaction_status = TransactionStatus::Idle;
loop {
async_std::task::sleep(SLEEP_DURATION).await;
let maybe_new_conversion_rate = maybe_select_new_conversion_rate(
stall_timeout,
&mut transaction_status,
&left_to_right_stored_conversion_rate,
&left_to_base_conversion_rate,
@@ -57,13 +161,32 @@ pub fn run_conversion_rate_update_loop<
)
.await;
if let Some((prev_conversion_rate, new_conversion_rate)) = maybe_new_conversion_rate {
let submit_conversion_rate_future = submit_conversion_rate(new_conversion_rate);
match submit_conversion_rate_future.await {
log::info!(
target: "bridge",
"Going to update {} -> {} (on {}) conversion rate to {}.",
Lane::TargetChain::NAME,
Lane::SourceChain::NAME,
Lane::SourceChain::NAME,
new_conversion_rate,
);
let result = update_target_to_source_conversion_rate::<Lane, Sign>(
client.clone(),
transaction_params.clone(),
new_conversion_rate,
)
.await;
match result {
Ok(()) => {
transaction_status = TransactionStatus::Submitted(prev_conversion_rate);
transaction_status =
TransactionStatus::Submitted(Instant::now(), prev_conversion_rate);
},
Err(error) => {
log::trace!(target: "bridge", "Failed to submit conversion rate update transaction: {:?}", error);
log::error!(
target: "bridge",
"Failed to submit conversion rate update transaction: {:?}",
error,
);
},
}
}
@@ -73,6 +196,7 @@ pub fn run_conversion_rate_update_loop<
/// Select new conversion rate to submit to the node.
async fn maybe_select_new_conversion_rate(
stall_timeout: Duration,
transaction_status: &mut TransactionStatus,
left_to_right_stored_conversion_rate: &F64SharedRef,
left_to_base_conversion_rate: &F64SharedRef,
@@ -83,7 +207,18 @@ async fn maybe_select_new_conversion_rate(
(*left_to_right_stored_conversion_rate.read().await)?;
match *transaction_status {
TransactionStatus::Idle => (),
TransactionStatus::Submitted(previous_left_to_right_stored_conversion_rate) => {
TransactionStatus::Submitted(submitted_at, _)
if Instant::now() - submitted_at > stall_timeout =>
{
log::error!(
target: "bridge",
"Conversion rate update transaction has been lost and loop stalled. Restarting",
);
// we assume that our transaction has been lost
*transaction_status = TransactionStatus::Idle;
},
TransactionStatus::Submitted(_, previous_left_to_right_stored_conversion_rate) => {
// we can't compare float values from different sources directly, so we only care
// whether the stored rate has been changed or not. If it has been changed, then we
// assume that our proposal has been accepted.
@@ -106,7 +241,7 @@ async fn maybe_select_new_conversion_rate(
let left_to_base_conversion_rate = (*left_to_base_conversion_rate.read().await)?;
let right_to_base_conversion_rate = (*right_to_base_conversion_rate.read().await)?;
let actual_left_to_right_conversion_rate =
right_to_base_conversion_rate / left_to_base_conversion_rate;
left_to_base_conversion_rate / right_to_base_conversion_rate;
let rate_difference =
(actual_left_to_right_conversion_rate - left_to_right_stored_conversion_rate).abs();
@@ -118,11 +253,50 @@ async fn maybe_select_new_conversion_rate(
Some((left_to_right_stored_conversion_rate, actual_left_to_right_conversion_rate))
}
/// Update Target -> Source tokens conversion rate, stored in the Source runtime storage.
pub async fn update_target_to_source_conversion_rate<Lane, Sign>(
client: Client<Lane::SourceChain>,
transaction_params: TransactionParams<AccountKeyPairOf<Sign>>,
updated_rate: f64,
) -> anyhow::Result<()>
where
Lane: SubstrateMessageLane,
Sign: TransactionSignScheme<Chain = Lane::SourceChain>,
AccountIdOf<Lane::SourceChain>: From<<AccountKeyPairOf<Sign> as Pair>::Public>,
{
let genesis_hash = *client.genesis_hash();
let signer_id = transaction_params.signer.public().into();
let (spec_version, transaction_version) = client.simple_runtime_version().await?;
let call =
Lane::TargetToSourceChainConversionRateUpdateBuilder::build_update_conversion_rate_call(
updated_rate,
)?;
client
.submit_signed_extrinsic(signer_id, move |best_block_id, transaction_nonce| {
Ok(Bytes(
Sign::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash,
signer: transaction_params.signer,
era: TransactionEra::new(best_block_id, transaction_params.mortality),
unsigned: UnsignedTransaction::new(call.into(), transaction_nonce).into(),
})?
.encode(),
))
})
.await
.map(drop)
.map_err(|err| anyhow::format_err!("{:?}", err))
}
#[cfg(test)]
mod tests {
use super::*;
use async_std::sync::{Arc, RwLock};
const TEST_STALL_TIMEOUT: Duration = Duration::from_secs(60);
fn test_maybe_select_new_conversion_rate(
mut transaction_status: TransactionStatus,
stored_conversion_rate: Option<f64>,
@@ -134,6 +308,7 @@ mod tests {
let left_to_base_conversion_rate = Arc::new(RwLock::new(left_to_base_conversion_rate));
let right_to_base_conversion_rate = Arc::new(RwLock::new(right_to_base_conversion_rate));
let result = async_std::task::block_on(maybe_select_new_conversion_rate(
TEST_STALL_TIMEOUT,
&mut transaction_status,
&stored_conversion_rate,
&left_to_base_conversion_rate,
@@ -145,15 +320,10 @@ mod tests {
#[test]
fn rate_is_not_updated_when_transaction_is_submitted() {
let status = TransactionStatus::Submitted(Instant::now(), 10.0);
assert_eq!(
test_maybe_select_new_conversion_rate(
TransactionStatus::Submitted(10.0),
Some(10.0),
Some(1.0),
Some(1.0),
0.0
),
(None, TransactionStatus::Submitted(10.0)),
test_maybe_select_new_conversion_rate(status, Some(10.0), Some(1.0), Some(1.0), 0.0),
(None, status),
);
}
@@ -161,7 +331,7 @@ mod tests {
fn transaction_state_is_changed_to_idle_when_stored_rate_shanges() {
assert_eq!(
test_maybe_select_new_conversion_rate(
TransactionStatus::Submitted(1.0),
TransactionStatus::Submitted(Instant::now(), 1.0),
Some(10.0),
Some(1.0),
Some(1.0),
@@ -229,15 +399,42 @@ mod tests {
#[test]
fn transaction_is_submitted_when_difference_is_above_threshold() {
let left_to_right_stored_conversion_rate = 1.0;
let left_to_base_conversion_rate = 18f64;
let right_to_base_conversion_rate = 180f64;
assert!(left_to_base_conversion_rate < right_to_base_conversion_rate);
assert_eq!(
test_maybe_select_new_conversion_rate(
TransactionStatus::Idle,
Some(1.0),
Some(1.0),
Some(1.03),
Some(left_to_right_stored_conversion_rate),
Some(left_to_base_conversion_rate),
Some(right_to_base_conversion_rate),
0.02
),
(Some((1.0, 1.03)), TransactionStatus::Idle),
(
Some((
left_to_right_stored_conversion_rate,
left_to_base_conversion_rate / right_to_base_conversion_rate,
)),
TransactionStatus::Idle
),
);
}
#[test]
fn transaction_expires() {
let status = TransactionStatus::Submitted(Instant::now() - TEST_STALL_TIMEOUT / 2, 10.0);
assert_eq!(
test_maybe_select_new_conversion_rate(status, Some(10.0), Some(1.0), Some(1.0), 0.0),
(None, status),
);
let status = TransactionStatus::Submitted(Instant::now() - TEST_STALL_TIMEOUT * 2, 10.0);
assert_eq!(
test_maybe_select_new_conversion_rate(status, Some(10.0), Some(1.0), Some(1.0), 0.0),
(Some((10.0, 1.0)), TransactionStatus::Idle),
);
}
}
@@ -55,4 +55,7 @@ pub enum Error<Hash: Debug + MaybeDisplay, HeaderNumber: Debug + MaybeDisplay> {
/// Failed to retrieve header by the hash from the source chain.
#[error("Failed to retrieve {0} header with hash {1}: {:?}")]
RetrieveHeader(&'static str, Hash, client::Error),
/// Failed to retrieve best finalized source header hash from the target chain.
#[error("Failed to retrieve best finalized {0} header from the target chain: {1}")]
RetrieveBestFinalizedHeaderHash(&'static str, client::Error),
}
@@ -0,0 +1,48 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Tools for starting guards of finality relays.
use crate::TransactionParams;
use relay_substrate_client::{
AccountIdOf, AccountKeyPairOf, ChainWithBalances, TransactionSignScheme,
};
use sp_core::Pair;
/// Start finality relay guards.
pub async fn start<C: ChainWithBalances, S: TransactionSignScheme<Chain = C>>(
target_client: &relay_substrate_client::Client<C>,
transaction_params: &TransactionParams<S::AccountKeyPair>,
enable_version_guard: bool,
maximal_balance_decrease_per_day: C::Balance,
) -> relay_substrate_client::Result<()>
where
AccountIdOf<C>: From<<AccountKeyPairOf<S> as Pair>::Public>,
{
if enable_version_guard {
relay_substrate_client::guard::abort_on_spec_version_change(
target_client.clone(),
target_client.simple_runtime_version().await?.0,
);
}
relay_substrate_client::guard::abort_when_account_balance_decreased(
target_client.clone(),
transaction_params.signer.public().into(),
maximal_balance_decrease_per_day,
);
Ok(())
}
@@ -14,18 +14,24 @@
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Substrate-to-Substrate headers sync entrypoint.
//! Types and functions intended to ease adding of new Substrate -> Substrate
//! finality proofs synchronization pipelines.
use crate::{finality_target::SubstrateFinalityTarget, STALL_TIMEOUT};
use bp_header_chain::justification::GrandpaJustification;
use bp_runtime::AccountIdOf;
use finality_relay::{FinalitySyncParams, FinalitySyncPipeline};
use relay_substrate_client::{
finality_source::FinalitySource, BlockNumberOf, Chain, Client, HashOf, SyncHeader,
use crate::{
finality_source::SubstrateFinalitySource, finality_target::SubstrateFinalityTarget,
TransactionParams,
};
use relay_utils::{metrics::MetricsParams, BlockNumberBase};
use sp_core::Bytes;
use async_trait::async_trait;
use bp_header_chain::justification::GrandpaJustification;
use finality_relay::FinalitySyncPipeline;
use pallet_bridge_grandpa::{Call as BridgeGrandpaCall, Config as BridgeGrandpaConfig};
use relay_substrate_client::{
transaction_stall_timeout, AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain,
ChainWithGrandpa, Client, HashOf, HeaderOf, SyncHeader, TransactionSignScheme,
};
use relay_utils::metrics::MetricsParams;
use sp_core::Pair;
use std::{fmt::Debug, marker::PhantomData};
/// Default limit of recent finality proofs.
@@ -34,130 +40,146 @@ use std::{fmt::Debug, marker::PhantomData};
/// Substrate+GRANDPA based chains (good to know).
pub(crate) const RECENT_FINALITY_PROOFS_LIMIT: usize = 4096;
/// Headers sync pipeline for Substrate <-> Substrate relays.
/// Substrate -> Substrate finality proofs synchronization pipeline.
#[async_trait]
pub trait SubstrateFinalitySyncPipeline: 'static + Clone + Debug + Send + Sync {
/// Pipeline for syncing finalized Source chain headers to Target chain.
type FinalitySyncPipeline: FinalitySyncPipeline;
/// Name of the runtime method that returns id of best finalized source header at target chain.
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str;
/// Chain with GRANDPA bridge pallet.
/// Headers of this chain are submitted to the `TargetChain`.
type SourceChain: ChainWithGrandpa;
/// Headers of the `SourceChain` are submitted to this chain.
type TargetChain: Chain;
/// Customize metrics exposed by headers sync loop.
fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
Ok(params)
}
/// How submit finality proof call is built?
type SubmitFinalityProofCallBuilder: SubmitFinalityProofCallBuilder<Self>;
/// Scheme used to sign target chain transactions.
type TransactionSignScheme: TransactionSignScheme;
/// Start finality relay guards.
///
/// Different finality bridges may have different set of guards - e.g. on ephemeral chains we
/// don't need a version guards, on test chains we don't care that much about relayer account
/// balance, ... So the implementation is left to the specific bridges.
fn start_relay_guards(&self) {}
/// Returns id of account that we're using to sign transactions at target chain.
fn transactions_author(&self) -> AccountIdOf<Self::TargetChain>;
/// Make submit header transaction.
fn make_submit_finality_proof_transaction(
&self,
era: bp_runtime::TransactionEraOf<Self::TargetChain>,
transaction_nonce: bp_runtime::IndexOf<Self::TargetChain>,
header: <Self::FinalitySyncPipeline as FinalitySyncPipeline>::Header,
proof: <Self::FinalitySyncPipeline as FinalitySyncPipeline>::FinalityProof,
) -> Bytes;
}
/// Substrate-to-Substrate finality proof pipeline.
#[derive(Clone)]
pub struct SubstrateFinalityToSubstrate<SourceChain, TargetChain: Chain, TargetSign> {
/// Client for the target chain.
pub target_client: Client<TargetChain>,
/// Data required to sign target chain transactions.
pub target_sign: TargetSign,
/// Unused generic arguments dump.
_marker: PhantomData<SourceChain>,
}
impl<SourceChain, TargetChain: Chain, TargetSign> Debug
for SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>
{
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("SubstrateFinalityToSubstrate")
.field("target_client", &self.target_client)
.finish()
/// Add relay guards if required.
async fn start_relay_guards(
_target_client: &Client<Self::TargetChain>,
_transaction_params: &TransactionParams<AccountKeyPairOf<Self::TransactionSignScheme>>,
_enable_version_guard: bool,
) -> relay_substrate_client::Result<()> {
Ok(())
}
}
impl<SourceChain, TargetChain: Chain, TargetSign>
SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>
{
/// Create new Substrate-to-Substrate headers pipeline.
pub fn new(target_client: Client<TargetChain>, target_sign: TargetSign) -> Self {
SubstrateFinalityToSubstrate { target_client, target_sign, _marker: Default::default() }
}
/// Adapter that allows all `SubstrateFinalitySyncPipeline` to act as `FinalitySyncPipeline`.
#[derive(Clone, Debug)]
pub struct FinalitySyncPipelineAdapter<P: SubstrateFinalitySyncPipeline> {
_phantom: PhantomData<P>,
}
impl<SourceChain, TargetChain, TargetSign> FinalitySyncPipeline
for SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>
impl<P: SubstrateFinalitySyncPipeline> FinalitySyncPipeline for FinalitySyncPipelineAdapter<P> {
const SOURCE_NAME: &'static str = P::SourceChain::NAME;
const TARGET_NAME: &'static str = P::TargetChain::NAME;
type Hash = HashOf<P::SourceChain>;
type Number = BlockNumberOf<P::SourceChain>;
type Header = relay_substrate_client::SyncHeader<HeaderOf<P::SourceChain>>;
type FinalityProof = GrandpaJustification<HeaderOf<P::SourceChain>>;
}
/// Different ways of building `submit_finality_proof` calls.
pub trait SubmitFinalityProofCallBuilder<P: SubstrateFinalitySyncPipeline> {
/// Given source chain header and its finality proofs, build call of `submit_finality_proof`
/// function of bridge GRANDPA module at the target chain.
fn build_submit_finality_proof_call(
header: SyncHeader<HeaderOf<P::SourceChain>>,
proof: GrandpaJustification<HeaderOf<P::SourceChain>>,
) -> CallOf<P::TargetChain>;
}
/// Building `submit_finality_proof` call when you have direct access to the target
/// chain runtime.
pub struct DirectSubmitFinalityProofCallBuilder<P, R, I> {
_phantom: PhantomData<(P, R, I)>,
}
impl<P, R, I> SubmitFinalityProofCallBuilder<P> for DirectSubmitFinalityProofCallBuilder<P, R, I>
where
SourceChain: Clone + Chain + Debug,
BlockNumberOf<SourceChain>: BlockNumberBase,
TargetChain: Clone + Chain + Debug,
TargetSign: 'static + Clone + Send + Sync,
P: SubstrateFinalitySyncPipeline,
R: BridgeGrandpaConfig<I>,
I: 'static,
R::BridgedChain: bp_runtime::Chain<Header = HeaderOf<P::SourceChain>>,
CallOf<P::TargetChain>: From<BridgeGrandpaCall<R, I>>,
{
const SOURCE_NAME: &'static str = SourceChain::NAME;
const TARGET_NAME: &'static str = TargetChain::NAME;
type Hash = HashOf<SourceChain>;
type Number = BlockNumberOf<SourceChain>;
type Header = SyncHeader<SourceChain::Header>;
type FinalityProof = GrandpaJustification<SourceChain::Header>;
fn build_submit_finality_proof_call(
header: SyncHeader<HeaderOf<P::SourceChain>>,
proof: GrandpaJustification<HeaderOf<P::SourceChain>>,
) -> CallOf<P::TargetChain> {
BridgeGrandpaCall::<R, I>::submit_finality_proof {
finality_target: Box::new(header.into_inner()),
justification: proof,
}
.into()
}
}
/// Run Substrate-to-Substrate finality sync.
pub async fn run<SourceChain, TargetChain, P>(
pipeline: P,
source_client: Client<SourceChain>,
target_client: Client<TargetChain>,
/// Macro that generates `SubmitFinalityProofCallBuilder` implementation for the case when
/// you only have an access to the mocked version of target chain runtime. In this case you
/// should provide "name" of the call variant for the bridge GRANDPA calls and the "name" of
/// the variant for the `submit_finality_proof` call within that first option.
#[rustfmt::skip]
#[macro_export]
macro_rules! generate_mocked_submit_finality_proof_call_builder {
($pipeline:ident, $mocked_builder:ident, $bridge_grandpa:path, $submit_finality_proof:path) => {
pub struct $mocked_builder;
impl $crate::finality_pipeline::SubmitFinalityProofCallBuilder<$pipeline>
for $mocked_builder
{
fn build_submit_finality_proof_call(
header: relay_substrate_client::SyncHeader<
relay_substrate_client::HeaderOf<
<$pipeline as $crate::finality_pipeline::SubstrateFinalitySyncPipeline>::SourceChain
>
>,
proof: bp_header_chain::justification::GrandpaJustification<
relay_substrate_client::HeaderOf<
<$pipeline as $crate::finality_pipeline::SubstrateFinalitySyncPipeline>::SourceChain
>
>,
) -> relay_substrate_client::CallOf<
<$pipeline as $crate::finality_pipeline::SubstrateFinalitySyncPipeline>::TargetChain
> {
$bridge_grandpa($submit_finality_proof(Box::new(header.into_inner()), proof))
}
}
};
}
/// Run Substrate-to-Substrate finality sync loop.
pub async fn run<P: SubstrateFinalitySyncPipeline>(
source_client: Client<P::SourceChain>,
target_client: Client<P::TargetChain>,
only_mandatory_headers: bool,
transactions_mortality: Option<u32>,
transaction_params: TransactionParams<AccountKeyPairOf<P::TransactionSignScheme>>,
metrics_params: MetricsParams,
) -> anyhow::Result<()>
where
P: SubstrateFinalitySyncPipeline<TargetChain = TargetChain>,
P::FinalitySyncPipeline: FinalitySyncPipeline<
Hash = HashOf<SourceChain>,
Number = BlockNumberOf<SourceChain>,
Header = SyncHeader<SourceChain::Header>,
FinalityProof = GrandpaJustification<SourceChain::Header>,
>,
SourceChain: Clone + Chain,
BlockNumberOf<SourceChain>: BlockNumberBase,
TargetChain: Clone + Chain,
AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TransactionSignScheme> as Pair>::Public>,
P::TransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
{
log::info!(
target: "bridge",
"Starting {} -> {} finality proof relay",
SourceChain::NAME,
TargetChain::NAME,
P::SourceChain::NAME,
P::TargetChain::NAME,
);
finality_relay::run(
FinalitySource::new(source_client, None),
SubstrateFinalityTarget::new(target_client, pipeline, transactions_mortality),
FinalitySyncParams {
SubstrateFinalitySource::<P>::new(source_client, None),
SubstrateFinalityTarget::<P>::new(target_client, transaction_params.clone()),
finality_relay::FinalitySyncParams {
tick: std::cmp::max(
SourceChain::AVERAGE_BLOCK_INTERVAL,
TargetChain::AVERAGE_BLOCK_INTERVAL,
P::SourceChain::AVERAGE_BLOCK_INTERVAL,
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
),
recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT,
stall_timeout: relay_substrate_client::transaction_stall_timeout(
transactions_mortality,
TargetChain::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
stall_timeout: transaction_stall_timeout(
transaction_params.mortality,
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
crate::STALL_TIMEOUT,
),
only_mandatory_headers,
},
@@ -16,49 +16,59 @@
//! Default generic implementation of finality source for basic Substrate client.
use crate::{
chain::{BlockWithJustification, Chain},
client::Client,
error::Error,
sync_header::SyncHeader,
};
use crate::finality_pipeline::{FinalitySyncPipelineAdapter, SubstrateFinalitySyncPipeline};
use async_std::sync::{Arc, Mutex};
use async_trait::async_trait;
use bp_header_chain::justification::GrandpaJustification;
use codec::Decode;
use finality_relay::{FinalitySyncPipeline, SourceClient, SourceHeader};
use finality_relay::SourceClient;
use futures::stream::{unfold, Stream, StreamExt};
use relay_substrate_client::{
BlockNumberOf, BlockWithJustification, Chain, Client, Error, HeaderOf,
};
use relay_utils::relay_loop::Client as RelayClient;
use sp_runtime::traits::Header as HeaderT;
use std::{marker::PhantomData, pin::Pin};
use std::pin::Pin;
/// Shared updatable reference to the maximal header number that we want to sync from the source.
pub type RequiredHeaderNumberRef<C> = Arc<Mutex<<C as bp_runtime::Chain>::BlockNumber>>;
/// Substrate finality proofs stream.
pub type SubstrateFinalityProofsStream<P> = Pin<
Box<
dyn Stream<
Item = GrandpaJustification<
HeaderOf<<P as SubstrateFinalitySyncPipeline>::SourceChain>,
>,
> + Send,
>,
>;
/// Substrate node as finality source.
pub struct FinalitySource<C: Chain, P> {
client: Client<C>,
maximal_header_number: Option<RequiredHeaderNumberRef<C>>,
_phantom: PhantomData<P>,
pub struct SubstrateFinalitySource<P: SubstrateFinalitySyncPipeline> {
client: Client<P::SourceChain>,
maximal_header_number: Option<RequiredHeaderNumberRef<P::SourceChain>>,
}
impl<C: Chain, P> FinalitySource<C, P> {
impl<P: SubstrateFinalitySyncPipeline> SubstrateFinalitySource<P> {
/// Create new headers source using given client.
pub fn new(
client: Client<C>,
maximal_header_number: Option<RequiredHeaderNumberRef<C>>,
client: Client<P::SourceChain>,
maximal_header_number: Option<RequiredHeaderNumberRef<P::SourceChain>>,
) -> Self {
FinalitySource { client, maximal_header_number, _phantom: Default::default() }
SubstrateFinalitySource { client, maximal_header_number }
}
/// Returns reference to the underlying RPC client.
pub fn client(&self) -> &Client<C> {
pub fn client(&self) -> &Client<P::SourceChain> {
&self.client
}
/// Returns best finalized block number.
pub async fn on_chain_best_finalized_block_number(&self) -> Result<C::BlockNumber, Error> {
pub async fn on_chain_best_finalized_block_number(
&self,
) -> Result<BlockNumberOf<P::SourceChain>, Error> {
// we **CAN** continue to relay finality proofs if source node is out of sync, because
// target node may be missing proofs that are already available at the source
let finalized_header_hash = self.client.best_finalized_header_hash().await?;
@@ -67,18 +77,17 @@ impl<C: Chain, P> FinalitySource<C, P> {
}
}
impl<C: Chain, P> Clone for FinalitySource<C, P> {
impl<P: SubstrateFinalitySyncPipeline> Clone for SubstrateFinalitySource<P> {
fn clone(&self) -> Self {
FinalitySource {
SubstrateFinalitySource {
client: self.client.clone(),
maximal_header_number: self.maximal_header_number.clone(),
_phantom: Default::default(),
}
}
}
#[async_trait]
impl<C: Chain, P: FinalitySyncPipeline> RelayClient for FinalitySource<C, P> {
impl<P: SubstrateFinalitySyncPipeline> RelayClient for SubstrateFinalitySource<P> {
type Error = Error;
async fn reconnect(&mut self) -> Result<(), Error> {
@@ -87,21 +96,12 @@ impl<C: Chain, P: FinalitySyncPipeline> RelayClient for FinalitySource<C, P> {
}
#[async_trait]
impl<C, P> SourceClient<P> for FinalitySource<C, P>
where
C: Chain,
C::BlockNumber: relay_utils::BlockNumberBase,
P: FinalitySyncPipeline<
Hash = C::Hash,
Number = C::BlockNumber,
Header = SyncHeader<C::Header>,
FinalityProof = GrandpaJustification<C::Header>,
>,
P::Header: SourceHeader<C::BlockNumber>,
impl<P: SubstrateFinalitySyncPipeline> SourceClient<FinalitySyncPipelineAdapter<P>>
for SubstrateFinalitySource<P>
{
type FinalityProofsStream = Pin<Box<dyn Stream<Item = GrandpaJustification<C::Header>> + Send>>;
type FinalityProofsStream = SubstrateFinalityProofsStream<P>;
async fn best_finalized_block_number(&self) -> Result<P::Number, Error> {
async fn best_finalized_block_number(&self) -> Result<BlockNumberOf<P::SourceChain>, Error> {
let mut finalized_header_number = self.on_chain_best_finalized_block_number().await?;
// never return block number larger than requested. This way we'll never sync headers
// past `maximal_header_number`
@@ -116,15 +116,23 @@ where
async fn header_and_finality_proof(
&self,
number: P::Number,
) -> Result<(P::Header, Option<P::FinalityProof>), Error> {
number: BlockNumberOf<P::SourceChain>,
) -> Result<
(
relay_substrate_client::SyncHeader<HeaderOf<P::SourceChain>>,
Option<GrandpaJustification<HeaderOf<P::SourceChain>>>,
),
Error,
> {
let header_hash = self.client.block_hash_by_number(number).await?;
let signed_block = self.client.get_block(Some(header_hash)).await?;
let justification = signed_block
.justification()
.map(|raw_justification| {
GrandpaJustification::<C::Header>::decode(&mut raw_justification.as_slice())
GrandpaJustification::<HeaderOf<P::SourceChain>>::decode(
&mut raw_justification.as_slice(),
)
})
.transpose()
.map_err(Error::ResponseParseFailed)?;
@@ -141,7 +149,7 @@ where
log::error!(
target: "bridge",
"Failed to read justification target from the {} justifications stream: {:?}",
P::SOURCE_NAME,
P::SourceChain::NAME,
err,
);
};
@@ -153,7 +161,9 @@ where
.ok()??;
let decoded_justification =
GrandpaJustification::<C::Header>::decode(&mut &next_justification[..]);
GrandpaJustification::<HeaderOf<P::SourceChain>>::decode(
&mut &next_justification[..],
);
let justification = match decoded_justification {
Ok(j) => j,
@@ -15,96 +15,122 @@
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Substrate client as Substrate finality proof target. The chain we connect to should have
//! runtime that implements `<BridgedChainName>FinalityApi` to allow bridging with
//! <BridgedName> chain.
//! bridge GRANDPA pallet deployed and provide `<BridgedChainName>FinalityApi` to allow bridging
//! with <BridgedName> chain.
use crate::finality_pipeline::SubstrateFinalitySyncPipeline;
use crate::{
finality_pipeline::{
FinalitySyncPipelineAdapter, SubmitFinalityProofCallBuilder, SubstrateFinalitySyncPipeline,
},
TransactionParams,
};
use async_trait::async_trait;
use codec::Decode;
use finality_relay::{FinalitySyncPipeline, TargetClient};
use relay_substrate_client::{Chain, Client, Error as SubstrateError};
use bp_header_chain::{justification::GrandpaJustification, storage_keys::is_halted_key};
use codec::Encode;
use finality_relay::TargetClient;
use relay_substrate_client::{
AccountIdOf, AccountKeyPairOf, Chain, ChainWithGrandpa, Client, Error, HeaderIdOf, HeaderOf,
SignParam, SyncHeader, TransactionEra, TransactionSignScheme, UnsignedTransaction,
};
use relay_utils::relay_loop::Client as RelayClient;
use sp_core::{Bytes, Pair};
/// Substrate client as Substrate finality target.
pub struct SubstrateFinalityTarget<C: Chain, P> {
client: Client<C>,
pipeline: P,
transactions_mortality: Option<u32>,
pub struct SubstrateFinalityTarget<P: SubstrateFinalitySyncPipeline> {
client: Client<P::TargetChain>,
transaction_params: TransactionParams<AccountKeyPairOf<P::TransactionSignScheme>>,
}
impl<C: Chain, P> SubstrateFinalityTarget<C, P> {
impl<P: SubstrateFinalitySyncPipeline> SubstrateFinalityTarget<P> {
/// Create new Substrate headers target.
pub fn new(client: Client<C>, pipeline: P, transactions_mortality: Option<u32>) -> Self {
SubstrateFinalityTarget { client, pipeline, transactions_mortality }
pub fn new(
client: Client<P::TargetChain>,
transaction_params: TransactionParams<AccountKeyPairOf<P::TransactionSignScheme>>,
) -> Self {
SubstrateFinalityTarget { client, transaction_params }
}
/// Ensure that the GRANDPA pallet at target chain is active.
pub async fn ensure_pallet_active(&self) -> Result<(), Error> {
let is_halted = self
.client
.storage_value(is_halted_key(P::SourceChain::WITH_CHAIN_GRANDPA_PALLET_NAME), None)
.await?;
if is_halted.unwrap_or(false) {
Err(Error::BridgePalletIsHalted)
} else {
Ok(())
}
}
}
impl<C: Chain, P: SubstrateFinalitySyncPipeline> Clone for SubstrateFinalityTarget<C, P> {
impl<P: SubstrateFinalitySyncPipeline> Clone for SubstrateFinalityTarget<P> {
fn clone(&self) -> Self {
SubstrateFinalityTarget {
client: self.client.clone(),
pipeline: self.pipeline.clone(),
transactions_mortality: self.transactions_mortality,
transaction_params: self.transaction_params.clone(),
}
}
}
#[async_trait]
impl<C: Chain, P: SubstrateFinalitySyncPipeline> RelayClient for SubstrateFinalityTarget<C, P> {
type Error = SubstrateError;
impl<P: SubstrateFinalitySyncPipeline> RelayClient for SubstrateFinalityTarget<P> {
type Error = Error;
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
async fn reconnect(&mut self) -> Result<(), Error> {
self.client.reconnect().await
}
}
#[async_trait]
impl<C, P> TargetClient<P::FinalitySyncPipeline> for SubstrateFinalityTarget<C, P>
impl<P: SubstrateFinalitySyncPipeline> TargetClient<FinalitySyncPipelineAdapter<P>>
for SubstrateFinalityTarget<P>
where
C: Chain,
P: SubstrateFinalitySyncPipeline<TargetChain = C>,
<P::FinalitySyncPipeline as FinalitySyncPipeline>::Number: Decode,
<P::FinalitySyncPipeline as FinalitySyncPipeline>::Hash: Decode,
AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TransactionSignScheme> as Pair>::Public>,
P::TransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
{
async fn best_finalized_source_block_number(
&self,
) -> Result<<P::FinalitySyncPipeline as FinalitySyncPipeline>::Number, SubstrateError> {
async fn best_finalized_source_block_id(&self) -> Result<HeaderIdOf<P::SourceChain>, Error> {
// we can't continue to relay finality if target node is out of sync, because
// it may have already received (some of) headers that we're going to relay
self.client.ensure_synced().await?;
// we can't relay finality if GRANDPA pallet at target chain is halted
self.ensure_pallet_active().await?;
Ok(crate::messages_source::read_client_state::<
C,
<P::FinalitySyncPipeline as FinalitySyncPipeline>::Hash,
<P::FinalitySyncPipeline as FinalitySyncPipeline>::Number,
>(&self.client, P::BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET)
Ok(crate::messages_source::read_client_state::<P::TargetChain, P::SourceChain>(
&self.client,
None,
P::SourceChain::BEST_FINALIZED_HEADER_ID_METHOD,
)
.await?
.best_finalized_peer_at_best_self
.0)
.best_finalized_peer_at_best_self)
}
async fn submit_finality_proof(
&self,
header: <P::FinalitySyncPipeline as FinalitySyncPipeline>::Header,
proof: <P::FinalitySyncPipeline as FinalitySyncPipeline>::FinalityProof,
) -> Result<(), SubstrateError> {
let transactions_author = self.pipeline.transactions_author();
let pipeline = self.pipeline.clone();
let transactions_mortality = self.transactions_mortality;
header: SyncHeader<HeaderOf<P::SourceChain>>,
proof: GrandpaJustification<HeaderOf<P::SourceChain>>,
) -> Result<(), Error> {
let genesis_hash = *self.client.genesis_hash();
let transaction_params = self.transaction_params.clone();
let call =
P::SubmitFinalityProofCallBuilder::build_submit_finality_proof_call(header, proof);
let (spec_version, transaction_version) = self.client.simple_runtime_version().await?;
self.client
.submit_signed_extrinsic(
transactions_author,
self.transaction_params.signer.public().into(),
move |best_block_id, transaction_nonce| {
pipeline.make_submit_finality_proof_transaction(
relay_substrate_client::TransactionEra::new(
best_block_id,
transactions_mortality,
),
transaction_nonce,
header,
proof,
)
Ok(Bytes(
P::TransactionSignScheme::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash,
signer: transaction_params.signer.clone(),
era: TransactionEra::new(best_block_id, transaction_params.mortality),
unsigned: UnsignedTransaction::new(call.into(), transaction_nonce),
})?
.encode(),
))
},
)
.await
@@ -31,17 +31,22 @@ use bp_header_chain::{
use codec::Decode;
use finality_grandpa::voter_set::VoterSet;
use num_traits::{One, Zero};
use relay_substrate_client::{Chain, Client};
use relay_substrate_client::{
BlockNumberOf, Chain, ChainWithGrandpa, Client, Error as SubstrateError, HashOf,
};
use sp_core::Bytes;
use sp_finality_grandpa::AuthorityList as GrandpaAuthoritiesSet;
use sp_runtime::traits::Header as HeaderT;
/// Submit headers-bridge initialization transaction.
pub async fn initialize<SourceChain: Chain, TargetChain: Chain>(
pub async fn initialize<SourceChain: ChainWithGrandpa, TargetChain: Chain>(
source_client: Client<SourceChain>,
target_client: Client<TargetChain>,
target_transactions_signer: TargetChain::AccountId,
prepare_initialize_transaction: impl FnOnce(TargetChain::Index, InitializationData<SourceChain::Header>) -> Bytes
prepare_initialize_transaction: impl FnOnce(
TargetChain::Index,
InitializationData<SourceChain::Header>,
) -> Result<Bytes, SubstrateError>
+ Send
+ 'static,
) {
@@ -54,13 +59,14 @@ pub async fn initialize<SourceChain: Chain, TargetChain: Chain>(
.await;
match result {
Ok(tx_hash) => log::info!(
Ok(Some(tx_hash)) => log::info!(
target: "bridge",
"Successfully submitted {}-headers bridge initialization transaction to {}: {:?}",
SourceChain::NAME,
TargetChain::NAME,
tx_hash,
),
Ok(None) => (),
Err(err) => log::error!(
target: "bridge",
"Failed to submit {}-headers bridge initialization transaction to {}: {:?}",
@@ -72,14 +78,31 @@ pub async fn initialize<SourceChain: Chain, TargetChain: Chain>(
}
/// Craft and submit initialization transaction, returning any error that may occur.
async fn do_initialize<SourceChain: Chain, TargetChain: Chain>(
async fn do_initialize<SourceChain: ChainWithGrandpa, TargetChain: Chain>(
source_client: Client<SourceChain>,
target_client: Client<TargetChain>,
target_transactions_signer: TargetChain::AccountId,
prepare_initialize_transaction: impl FnOnce(TargetChain::Index, InitializationData<SourceChain::Header>) -> Bytes
prepare_initialize_transaction: impl FnOnce(
TargetChain::Index,
InitializationData<SourceChain::Header>,
) -> Result<Bytes, SubstrateError>
+ Send
+ 'static,
) -> Result<TargetChain::Hash, Error<SourceChain::Hash, <SourceChain::Header as HeaderT>::Number>> {
) -> Result<
Option<TargetChain::Hash>,
Error<SourceChain::Hash, <SourceChain::Header as HeaderT>::Number>,
> {
let is_initialized = is_initialized::<SourceChain, TargetChain>(&target_client).await?;
if is_initialized {
log::info!(
target: "bridge",
"{}-headers bridge at {} is already initialized. Skipping",
SourceChain::NAME,
TargetChain::NAME,
);
return Ok(None)
}
let initialization_data = prepare_initialization_data(source_client).await?;
log::info!(
target: "bridge",
@@ -95,7 +118,23 @@ async fn do_initialize<SourceChain: Chain, TargetChain: Chain>(
})
.await
.map_err(|err| Error::SubmitTransaction(TargetChain::NAME, err))?;
Ok(initialization_tx_hash)
Ok(Some(initialization_tx_hash))
}
/// Returns `Ok(true)` if bridge has already been initialized.
async fn is_initialized<SourceChain: ChainWithGrandpa, TargetChain: Chain>(
target_client: &Client<TargetChain>,
) -> Result<bool, Error<HashOf<SourceChain>, BlockNumberOf<SourceChain>>> {
Ok(target_client
.raw_storage_value(
bp_header_chain::storage_keys::best_finalized_hash_key(
SourceChain::WITH_CHAIN_GRANDPA_PALLET_NAME,
),
None,
)
.await
.map_err(|err| Error::RetrieveBestFinalizedHeaderHash(SourceChain::NAME, err))?
.is_some())
}
/// Prepare initialization data for the GRANDPA verifier pallet.
@@ -16,14 +16,91 @@
//! Substrate relay helpers
use relay_utils::metrics::{FloatJsonValueMetric, PrometheusError};
use relay_utils::metrics::{FloatJsonValueMetric, PrometheusError, StandaloneMetric};
/// Creates standalone token price metric.
pub fn token_price_metric(token_id: &str) -> Result<FloatJsonValueMetric, PrometheusError> {
FloatJsonValueMetric::new(
format!("https://api.coingecko.com/api/v3/simple/price?ids={}&vs_currencies=btc", token_id),
format!("$.{}.btc", token_id),
format!("{}_to_base_conversion_rate", token_id.replace("-", "_")),
format!("{}_to_base_conversion_rate", token_id.replace('-', "_")),
format!("Rate used to convert from {} to some BASE tokens", token_id.to_uppercase()),
)
}
/// Compute conversion rate between two tokens immediately, without spawning any metrics.
///
/// Returned rate may be used in expression: `from_tokens * rate -> to_tokens`.
pub async fn tokens_conversion_rate_from_metrics(
from_token_id: &str,
to_token_id: &str,
) -> anyhow::Result<f64> {
let from_token_metric = token_price_metric(from_token_id)?;
from_token_metric.update().await;
let to_token_metric = token_price_metric(to_token_id)?;
to_token_metric.update().await;
let from_token_value = *from_token_metric.shared_value_ref().read().await;
let to_token_value = *to_token_metric.shared_value_ref().read().await;
// `FloatJsonValueMetric` guarantees that the value is positive && normal, so no additional
// checks required here
match (from_token_value, to_token_value) {
(Some(from_token_value), Some(to_token_value)) =>
Ok(tokens_conversion_rate(from_token_value, to_token_value)),
_ => Err(anyhow::format_err!(
"Failed to compute conversion rate from {} to {}",
from_token_id,
to_token_id,
)),
}
}
/// Compute conversion rate between two tokens, given token prices.
///
/// Returned rate may be used in expression: `from_tokens * rate -> to_tokens`.
///
/// Both prices are assumed to be normal and non-negative.
pub fn tokens_conversion_rate(from_token_value: f64, to_token_value: f64) -> f64 {
from_token_value / to_token_value
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rialto_to_millau_conversion_rate_is_correct() {
let rialto_price = 18.18;
let millau_price = 136.35;
assert!(rialto_price < millau_price);
let conversion_rate = tokens_conversion_rate(rialto_price, millau_price);
let rialto_amount = 100.0;
let millau_amount = rialto_amount * conversion_rate;
assert!(
rialto_amount > millau_amount,
"{} RLT * {} = {} MLU",
rialto_amount,
conversion_rate,
millau_amount,
);
}
#[test]
fn millau_to_rialto_conversion_rate_is_correct() {
let rialto_price = 18.18;
let millau_price = 136.35;
assert!(rialto_price < millau_price);
let conversion_rate = tokens_conversion_rate(millau_price, rialto_price);
let millau_amount = 100.0;
let rialto_amount = millau_amount * conversion_rate;
assert!(
rialto_amount > millau_amount,
"{} MLU * {} = {} RLT",
millau_amount,
conversion_rate,
rialto_amount,
);
}
}
@@ -22,11 +22,14 @@ use std::time::Duration;
pub mod conversion_rate_update;
pub mod error;
pub mod finality_guards;
pub mod finality_pipeline;
pub mod finality_source;
pub mod finality_target;
pub mod headers_initialize;
pub mod helpers;
pub mod messages_lane;
pub mod messages_metrics;
pub mod messages_source;
pub mod messages_target;
pub mod on_demand_headers;
@@ -39,3 +42,12 @@ pub mod on_demand_headers;
/// blockchains) blocks. So 1 hour seems to be a good guess for (even congested) chains to mine
/// transaction, or remove it from the pool.
pub const STALL_TIMEOUT: Duration = Duration::from_secs(60 * 60);
/// Transaction creation parameters.
#[derive(Clone, Debug)]
pub struct TransactionParams<TS> {
/// Transactions author.
pub signer: TS,
/// Transactions mortality.
pub mortality: Option<u32>,
}
@@ -17,194 +17,434 @@
//! Tools for supporting message lanes between two Substrate-based chains.
use crate::{
messages_source::SubstrateMessagesProof, messages_target::SubstrateMessagesReceivingProof,
conversion_rate_update::UpdateConversionRateCallBuilder,
messages_metrics::StandaloneMessagesMetrics,
messages_source::{SubstrateMessagesProof, SubstrateMessagesSource},
messages_target::{SubstrateMessagesDeliveryProof, SubstrateMessagesTarget},
on_demand_headers::OnDemandHeadersRelay,
TransactionParams, STALL_TIMEOUT,
};
use async_trait::async_trait;
use bp_messages::{LaneId, MessageNonce};
use bp_runtime::{AccountIdOf, IndexOf};
use frame_support::weights::Weight;
use messages_relay::{
message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf},
relay_strategy::RelayStrategy,
use bp_runtime::{AccountIdOf, Chain as _};
use bridge_runtime_common::messages::{
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
};
use codec::Encode;
use frame_support::weights::{GetDispatchInfo, Weight};
use messages_relay::{message_lane::MessageLane, relay_strategy::RelayStrategy};
use pallet_bridge_messages::{Call as BridgeMessagesCall, Config as BridgeMessagesConfig};
use relay_substrate_client::{
metrics::{FloatStorageValueMetric, StorageProofOverheadMetric},
BlockNumberOf, Chain, Client, HashOf,
transaction_stall_timeout, AccountKeyPairOf, BalanceOf, BlockNumberOf, CallOf, Chain,
ChainWithMessages, Client, HashOf, TransactionSignScheme,
};
use relay_utils::{
metrics::{
FloatJsonValueMetric, GlobalMetrics, MetricsParams, PrometheusError, StandaloneMetric,
},
BlockNumberBase,
};
use sp_core::{storage::StorageKey, Bytes};
use sp_runtime::FixedU128;
use std::ops::RangeInclusive;
use relay_utils::metrics::MetricsParams;
use sp_core::Pair;
use std::{convert::TryFrom, fmt::Debug, marker::PhantomData};
/// Substrate -> Substrate messages synchronization pipeline.
pub trait SubstrateMessageLane: 'static + Clone + Debug + Send + Sync {
/// Name of the source -> target tokens conversion rate parameter.
///
/// The parameter is stored at the target chain and the storage key is computed using
/// `bp_runtime::storage_parameter_key` function. If value is unknown, it is assumed
/// to be 1.
const SOURCE_TO_TARGET_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str>;
/// Name of the target -> source tokens conversion rate parameter.
///
/// The parameter is stored at the source chain and the storage key is computed using
/// `bp_runtime::storage_parameter_key` function. If value is unknown, it is assumed
/// to be 1.
const TARGET_TO_SOURCE_CONVERSION_RATE_PARAMETER_NAME: Option<&'static str>;
/// Name of the source chain fee multiplier parameter.
///
/// The parameter is stored at the target chain and the storage key is computed using
/// `bp_runtime::storage_parameter_key` function. If value is unknown, it is assumed
/// to be 1.
const SOURCE_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str>;
/// Name of the target chain fee multiplier parameter.
///
/// The parameter is stored at the source chain and the storage key is computed using
/// `bp_runtime::storage_parameter_key` function. If value is unknown, it is assumed
/// to be 1.
const TARGET_FEE_MULTIPLIER_PARAMETER_NAME: Option<&'static str>;
/// Name of the transaction payment pallet, deployed at the source chain.
const AT_SOURCE_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str>;
/// Name of the transaction payment pallet, deployed at the target chain.
const AT_TARGET_TRANSACTION_PAYMENT_PALLET_NAME: Option<&'static str>;
/// Messages of this chain are relayed to the `TargetChain`.
type SourceChain: ChainWithMessages;
/// Messages from the `SourceChain` are dispatched on this chain.
type TargetChain: ChainWithMessages;
/// Scheme used to sign source chain transactions.
type SourceTransactionSignScheme: TransactionSignScheme;
/// Scheme used to sign target chain transactions.
type TargetTransactionSignScheme: TransactionSignScheme;
/// How receive messages proof call is built?
type ReceiveMessagesProofCallBuilder: ReceiveMessagesProofCallBuilder<Self>;
/// How receive messages delivery proof call is built?
type ReceiveMessagesDeliveryProofCallBuilder: ReceiveMessagesDeliveryProofCallBuilder<Self>;
/// `TargetChain` tokens to `SourceChain` tokens conversion rate update builder.
///
/// If not applicable to this bridge, you may use `()` here.
type TargetToSourceChainConversionRateUpdateBuilder: UpdateConversionRateCallBuilder<
Self::SourceChain,
>;
/// Message relay strategy.
type RelayStrategy: RelayStrategy;
}
/// Adapter that allows all `SubstrateMessageLane` to act as `MessageLane`.
#[derive(Clone, Debug)]
pub(crate) struct MessageLaneAdapter<P: SubstrateMessageLane> {
_phantom: PhantomData<P>,
}
impl<P: SubstrateMessageLane> MessageLane for MessageLaneAdapter<P> {
const SOURCE_NAME: &'static str = P::SourceChain::NAME;
const TARGET_NAME: &'static str = P::TargetChain::NAME;
type MessagesProof = SubstrateMessagesProof<P::SourceChain>;
type MessagesReceivingProof = SubstrateMessagesDeliveryProof<P::TargetChain>;
type SourceChainBalance = BalanceOf<P::SourceChain>;
type SourceHeaderNumber = BlockNumberOf<P::SourceChain>;
type SourceHeaderHash = HashOf<P::SourceChain>;
type TargetHeaderNumber = BlockNumberOf<P::TargetChain>;
type TargetHeaderHash = HashOf<P::TargetChain>;
}
/// Substrate <-> Substrate messages relay parameters.
pub struct MessagesRelayParams<SC: Chain, SS, TC: Chain, TS, Strategy: RelayStrategy> {
pub struct MessagesRelayParams<P: SubstrateMessageLane> {
/// Messages source client.
pub source_client: Client<SC>,
/// Sign parameters for messages source chain.
pub source_sign: SS,
/// Mortality of source transactions.
pub source_transactions_mortality: Option<u32>,
pub source_client: Client<P::SourceChain>,
/// Source transaction params.
pub source_transaction_params:
TransactionParams<AccountKeyPairOf<P::SourceTransactionSignScheme>>,
/// Messages target client.
pub target_client: Client<TC>,
/// Sign parameters for messages target chain.
pub target_sign: TS,
/// Mortality of target transactions.
pub target_transactions_mortality: Option<u32>,
pub target_client: Client<P::TargetChain>,
/// Target transaction params.
pub target_transaction_params:
TransactionParams<AccountKeyPairOf<P::TargetTransactionSignScheme>>,
/// Optional on-demand source to target headers relay.
pub source_to_target_headers_relay: Option<OnDemandHeadersRelay<SC>>,
pub source_to_target_headers_relay: Option<OnDemandHeadersRelay<P::SourceChain>>,
/// Optional on-demand target to source headers relay.
pub target_to_source_headers_relay: Option<OnDemandHeadersRelay<TC>>,
pub target_to_source_headers_relay: Option<OnDemandHeadersRelay<P::TargetChain>>,
/// Identifier of lane that needs to be served.
pub lane_id: LaneId,
/// Metrics parameters.
pub metrics_params: MetricsParams,
/// Pre-registered standalone metrics.
pub standalone_metrics: Option<StandaloneMessagesMetrics<SC, TC>>,
/// Relay strategy
pub relay_strategy: Strategy,
pub standalone_metrics: Option<StandaloneMessagesMetrics<P::SourceChain, P::TargetChain>>,
/// Relay strategy.
pub relay_strategy: P::RelayStrategy,
}
/// Message sync pipeline for Substrate <-> Substrate relays.
#[async_trait]
pub trait SubstrateMessageLane: 'static + Clone + Send + Sync {
/// Underlying generic message lane.
type MessageLane: MessageLane;
/// Name of the runtime method that returns dispatch weight of outbound messages at the source
/// chain.
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str;
/// Name of the runtime method that returns latest generated nonce at the source chain.
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str;
/// Name of the runtime method that returns latest received (confirmed) nonce at the the source
/// chain.
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str;
/// Name of the runtime method that returns latest received nonce at the target chain.
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str;
/// Name of the runtime method that returns the latest confirmed (reward-paid) nonce at the
/// target chain.
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str;
/// Number of the runtime method that returns state of "unrewarded relayers" set at the target
/// chain.
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str;
/// Name of the runtime method that returns id of best finalized source header at target chain.
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str;
/// Name of the runtime method that returns id of best finalized target header at source chain.
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str;
/// Name of the messages pallet as it is declared in the `construct_runtime!()` at source chain.
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str;
/// Name of the messages pallet as it is declared in the `construct_runtime!()` at target chain.
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str;
/// Extra weight of the delivery transaction at the target chain, that is paid to cover
/// dispatch fee payment.
///
/// If dispatch fee is paid at the source chain, then this weight is refunded by the
/// delivery transaction.
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight;
/// Source chain.
type SourceChain: Chain;
/// Target chain.
type TargetChain: Chain;
/// Returns id of account that we're using to sign transactions at target chain (messages
/// proof).
fn target_transactions_author(&self) -> AccountIdOf<Self::TargetChain>;
/// Make messages delivery transaction.
fn make_messages_delivery_transaction(
&self,
best_block_id: TargetHeaderIdOf<Self::MessageLane>,
transaction_nonce: IndexOf<Self::TargetChain>,
generated_at_header: SourceHeaderIdOf<Self::MessageLane>,
nonces: RangeInclusive<MessageNonce>,
proof: <Self::MessageLane as MessageLane>::MessagesProof,
) -> Bytes;
/// Returns id of account that we're using to sign transactions at source chain (delivery
/// proof).
fn source_transactions_author(&self) -> AccountIdOf<Self::SourceChain>;
/// Make messages receiving proof transaction.
fn make_messages_receiving_proof_transaction(
&self,
best_block_id: SourceHeaderIdOf<Self::MessageLane>,
transaction_nonce: IndexOf<Self::SourceChain>,
generated_at_header: TargetHeaderIdOf<Self::MessageLane>,
proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
) -> Bytes;
}
/// Substrate-to-Substrate message lane.
#[derive(Debug)]
pub struct SubstrateMessageLaneToSubstrate<
Source: Chain,
SourceSignParams,
Target: Chain,
TargetSignParams,
> {
/// Client for the source Substrate chain.
pub source_client: Client<Source>,
/// Parameters required to sign transactions for source chain.
pub source_sign: SourceSignParams,
/// Source transactions mortality.
pub source_transactions_mortality: Option<u32>,
/// Client for the target Substrate chain.
pub target_client: Client<Target>,
/// Parameters required to sign transactions for target chain.
pub target_sign: TargetSignParams,
/// Target transactions mortality.
pub target_transactions_mortality: Option<u32>,
/// Account id of relayer at the source chain.
pub relayer_id_at_source: Source::AccountId,
}
impl<Source: Chain, SourceSignParams: Clone, Target: Chain, TargetSignParams: Clone> Clone
for SubstrateMessageLaneToSubstrate<Source, SourceSignParams, Target, TargetSignParams>
/// Run Substrate-to-Substrate messages sync loop.
pub async fn run<P: SubstrateMessageLane>(params: MessagesRelayParams<P>) -> anyhow::Result<()>
where
AccountIdOf<P::SourceChain>:
From<<AccountKeyPairOf<P::SourceTransactionSignScheme> as Pair>::Public>,
AccountIdOf<P::TargetChain>:
From<<AccountKeyPairOf<P::TargetTransactionSignScheme> as Pair>::Public>,
BalanceOf<P::SourceChain>: TryFrom<BalanceOf<P::TargetChain>>,
P::SourceTransactionSignScheme: TransactionSignScheme<Chain = P::SourceChain>,
P::TargetTransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
{
fn clone(&self) -> Self {
Self {
source_client: self.source_client.clone(),
source_sign: self.source_sign.clone(),
source_transactions_mortality: self.source_transactions_mortality,
target_client: self.target_client.clone(),
target_sign: self.target_sign.clone(),
target_transactions_mortality: self.target_transactions_mortality,
relayer_id_at_source: self.relayer_id_at_source.clone(),
let source_client = params.source_client;
let target_client = params.target_client;
let stall_timeout = relay_substrate_client::bidirectional_transaction_stall_timeout(
params.source_transaction_params.mortality,
params.target_transaction_params.mortality,
P::SourceChain::AVERAGE_BLOCK_INTERVAL,
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
);
let relayer_id_at_source: AccountIdOf<P::SourceChain> =
params.source_transaction_params.signer.public().into();
// 2/3 is reserved for proofs and tx overhead
let max_messages_size_in_single_batch = P::TargetChain::max_extrinsic_size() / 3;
// we don't know exact weights of the Polkadot runtime. So to guess weights we'll be using
// weights from Rialto and then simply dividing it by x2.
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
crate::messages_lane::select_delivery_transaction_limits::<
<P::TargetChain as ChainWithMessages>::WeightInfo,
>(
P::TargetChain::max_extrinsic_weight(),
P::SourceChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
);
let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
(max_messages_in_single_batch / 2, max_messages_weight_in_single_batch / 2);
let standalone_metrics = params.standalone_metrics.map(Ok).unwrap_or_else(|| {
crate::messages_metrics::standalone_metrics::<P>(
source_client.clone(),
target_client.clone(),
)
})?;
log::info!(
target: "bridge",
"Starting {} -> {} messages relay.\n\t\
{} relayer account id: {:?}\n\t\
Max messages in single transaction: {}\n\t\
Max messages size in single transaction: {}\n\t\
Max messages weight in single transaction: {}\n\t\
Tx mortality: {:?} (~{}m)/{:?} (~{}m)\n\t\
Stall timeout: {:?}",
P::SourceChain::NAME,
P::TargetChain::NAME,
P::SourceChain::NAME,
relayer_id_at_source,
max_messages_in_single_batch,
max_messages_size_in_single_batch,
max_messages_weight_in_single_batch,
params.source_transaction_params.mortality,
transaction_stall_timeout(
params.source_transaction_params.mortality,
P::SourceChain::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
).as_secs_f64() / 60.0f64,
params.target_transaction_params.mortality,
transaction_stall_timeout(
params.target_transaction_params.mortality,
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
).as_secs_f64() / 60.0f64,
stall_timeout,
);
messages_relay::message_lane_loop::run(
messages_relay::message_lane_loop::Params {
lane: params.lane_id,
source_tick: P::SourceChain::AVERAGE_BLOCK_INTERVAL,
target_tick: P::TargetChain::AVERAGE_BLOCK_INTERVAL,
reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
stall_timeout,
delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
max_unrewarded_relayer_entries_at_target:
P::SourceChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
max_unconfirmed_nonces_at_target:
P::SourceChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
max_messages_in_single_batch,
max_messages_weight_in_single_batch,
max_messages_size_in_single_batch,
relay_strategy: params.relay_strategy,
},
},
SubstrateMessagesSource::<P>::new(
source_client.clone(),
target_client.clone(),
params.lane_id,
params.source_transaction_params,
params.target_to_source_headers_relay,
),
SubstrateMessagesTarget::<P>::new(
target_client,
source_client,
params.lane_id,
relayer_id_at_source,
params.target_transaction_params,
standalone_metrics.clone(),
params.source_to_target_headers_relay,
),
standalone_metrics.register_and_spawn(params.metrics_params)?,
futures::future::pending(),
)
.await
.map_err(Into::into)
}
/// Different ways of building `receive_messages_proof` calls.
pub trait ReceiveMessagesProofCallBuilder<P: SubstrateMessageLane> {
/// Given messages proof, build call of `receive_messages_proof` function of bridge
/// messages module at the target chain.
fn build_receive_messages_proof_call(
relayer_id_at_source: AccountIdOf<P::SourceChain>,
proof: SubstrateMessagesProof<P::SourceChain>,
messages_count: u32,
dispatch_weight: Weight,
trace_call: bool,
) -> CallOf<P::TargetChain>;
}
/// Building `receive_messages_proof` call when you have direct access to the target
/// chain runtime.
pub struct DirectReceiveMessagesProofCallBuilder<P, R, I> {
_phantom: PhantomData<(P, R, I)>,
}
impl<P, R, I> ReceiveMessagesProofCallBuilder<P> for DirectReceiveMessagesProofCallBuilder<P, R, I>
where
P: SubstrateMessageLane,
R: BridgeMessagesConfig<I, InboundRelayer = AccountIdOf<P::SourceChain>>,
I: 'static,
R::SourceHeaderChain: bp_messages::target_chain::SourceHeaderChain<
R::InboundMessageFee,
MessagesProof = FromBridgedChainMessagesProof<HashOf<P::SourceChain>>,
>,
CallOf<P::TargetChain>: From<BridgeMessagesCall<R, I>> + GetDispatchInfo,
{
fn build_receive_messages_proof_call(
relayer_id_at_source: AccountIdOf<P::SourceChain>,
proof: SubstrateMessagesProof<P::SourceChain>,
messages_count: u32,
dispatch_weight: Weight,
trace_call: bool,
) -> CallOf<P::TargetChain> {
let call: CallOf<P::TargetChain> = BridgeMessagesCall::<R, I>::receive_messages_proof {
relayer_id_at_bridged_chain: relayer_id_at_source,
proof: proof.1,
messages_count,
dispatch_weight,
}
.into();
if trace_call {
// this trace isn't super-accurate, because limits are for transactions and we
// have a call here, but it provides required information
log::trace!(
target: "bridge",
"Prepared {} -> {} messages delivery call. Weight: {}/{}, size: {}/{}",
P::SourceChain::NAME,
P::TargetChain::NAME,
call.get_dispatch_info().weight,
P::TargetChain::max_extrinsic_weight(),
call.encode().len(),
P::TargetChain::max_extrinsic_size(),
);
}
call
}
}
impl<Source: Chain, SourceSignParams, Target: Chain, TargetSignParams> MessageLane
for SubstrateMessageLaneToSubstrate<Source, SourceSignParams, Target, TargetSignParams>
/// Macro that generates `ReceiveMessagesProofCallBuilder` implementation for the case when
/// you only have an access to the mocked version of target chain runtime. In this case you
/// should provide "name" of the call variant for the bridge messages calls and the "name" of
/// the variant for the `receive_messages_proof` call within that first option.
#[rustfmt::skip]
#[macro_export]
macro_rules! generate_mocked_receive_message_proof_call_builder {
($pipeline:ident, $mocked_builder:ident, $bridge_messages:path, $receive_messages_proof:path) => {
pub struct $mocked_builder;
impl $crate::messages_lane::ReceiveMessagesProofCallBuilder<$pipeline>
for $mocked_builder
{
fn build_receive_messages_proof_call(
relayer_id_at_source: relay_substrate_client::AccountIdOf<
<$pipeline as $crate::messages_lane::SubstrateMessageLane>::SourceChain
>,
proof: $crate::messages_source::SubstrateMessagesProof<
<$pipeline as $crate::messages_lane::SubstrateMessageLane>::SourceChain
>,
messages_count: u32,
dispatch_weight: Weight,
_trace_call: bool,
) -> relay_substrate_client::CallOf<
<$pipeline as $crate::messages_lane::SubstrateMessageLane>::TargetChain
> {
$bridge_messages($receive_messages_proof(
relayer_id_at_source,
proof.1,
messages_count,
dispatch_weight,
))
}
}
};
}
/// Different ways of building `receive_messages_delivery_proof` calls.
pub trait ReceiveMessagesDeliveryProofCallBuilder<P: SubstrateMessageLane> {
/// Given messages delivery proof, build call of `receive_messages_delivery_proof` function of
/// bridge messages module at the source chain.
fn build_receive_messages_delivery_proof_call(
proof: SubstrateMessagesDeliveryProof<P::TargetChain>,
trace_call: bool,
) -> CallOf<P::SourceChain>;
}
/// Building `receive_messages_delivery_proof` call when you have direct access to the source
/// chain runtime.
pub struct DirectReceiveMessagesDeliveryProofCallBuilder<P, R, I> {
_phantom: PhantomData<(P, R, I)>,
}
impl<P, R, I> ReceiveMessagesDeliveryProofCallBuilder<P>
for DirectReceiveMessagesDeliveryProofCallBuilder<P, R, I>
where
SourceSignParams: Clone + Send + Sync + 'static,
TargetSignParams: Clone + Send + Sync + 'static,
BlockNumberOf<Source>: BlockNumberBase,
BlockNumberOf<Target>: BlockNumberBase,
P: SubstrateMessageLane,
R: BridgeMessagesConfig<I>,
I: 'static,
R::TargetHeaderChain: bp_messages::source_chain::TargetHeaderChain<
R::OutboundPayload,
R::AccountId,
MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof<HashOf<P::TargetChain>>,
>,
CallOf<P::SourceChain>: From<BridgeMessagesCall<R, I>> + GetDispatchInfo,
{
const SOURCE_NAME: &'static str = Source::NAME;
const TARGET_NAME: &'static str = Target::NAME;
fn build_receive_messages_delivery_proof_call(
proof: SubstrateMessagesDeliveryProof<P::TargetChain>,
trace_call: bool,
) -> CallOf<P::SourceChain> {
let call: CallOf<P::SourceChain> =
BridgeMessagesCall::<R, I>::receive_messages_delivery_proof {
proof: proof.1,
relayers_state: proof.0,
}
.into();
if trace_call {
// this trace isn't super-accurate, because limits are for transactions and we
// have a call here, but it provides required information
log::trace!(
target: "bridge",
"Prepared {} -> {} delivery confirmation transaction. Weight: {}/{}, size: {}/{}",
P::TargetChain::NAME,
P::SourceChain::NAME,
call.get_dispatch_info().weight,
P::SourceChain::max_extrinsic_weight(),
call.encode().len(),
P::SourceChain::max_extrinsic_size(),
);
}
call
}
}
type MessagesProof = SubstrateMessagesProof<Source>;
type MessagesReceivingProof = SubstrateMessagesReceivingProof<Target>;
/// Macro that generates `ReceiveMessagesDeliveryProofCallBuilder` implementation for the case when
/// you only have an access to the mocked version of source chain runtime. In this case you
/// should provide "name" of the call variant for the bridge messages calls and the "name" of
/// the variant for the `receive_messages_delivery_proof` call within that first option.
#[rustfmt::skip]
#[macro_export]
macro_rules! generate_mocked_receive_message_delivery_proof_call_builder {
($pipeline:ident, $mocked_builder:ident, $bridge_messages:path, $receive_messages_delivery_proof:path) => {
pub struct $mocked_builder;
type SourceChainBalance = Source::Balance;
type SourceHeaderNumber = BlockNumberOf<Source>;
type SourceHeaderHash = HashOf<Source>;
type TargetHeaderNumber = BlockNumberOf<Target>;
type TargetHeaderHash = HashOf<Target>;
impl $crate::messages_lane::ReceiveMessagesDeliveryProofCallBuilder<$pipeline>
for $mocked_builder
{
fn build_receive_messages_delivery_proof_call(
proof: $crate::messages_target::SubstrateMessagesDeliveryProof<
<$pipeline as $crate::messages_lane::SubstrateMessageLane>::TargetChain
>,
_trace_call: bool,
) -> relay_substrate_client::CallOf<
<$pipeline as $crate::messages_lane::SubstrateMessageLane>::SourceChain
> {
$bridge_messages($receive_messages_delivery_proof(proof.1, proof.0))
}
}
};
}
/// Returns maximal number of messages and their maximal cumulative dispatch weight, based
@@ -245,165 +485,20 @@ pub fn select_delivery_transaction_limits<W: pallet_bridge_messages::WeightInfoE
(max_number_of_messages, weight_for_messages_dispatch)
}
/// Shared references to the standalone metrics of the message lane relay loop.
#[derive(Debug, Clone)]
pub struct StandaloneMessagesMetrics<SC: Chain, TC: Chain> {
/// Global metrics.
pub global: GlobalMetrics,
/// Storage chain proof overhead metric.
pub source_storage_proof_overhead: StorageProofOverheadMetric<SC>,
/// Target chain proof overhead metric.
pub target_storage_proof_overhead: StorageProofOverheadMetric<TC>,
/// Source tokens to base conversion rate metric.
pub source_to_base_conversion_rate: Option<FloatJsonValueMetric>,
/// Target tokens to base conversion rate metric.
pub target_to_base_conversion_rate: Option<FloatJsonValueMetric>,
/// Source tokens to target tokens conversion rate metric. This rate is stored by the target
/// chain.
pub source_to_target_conversion_rate:
Option<FloatStorageValueMetric<TC, sp_runtime::FixedU128>>,
/// Target tokens to source tokens conversion rate metric. This rate is stored by the source
/// chain.
pub target_to_source_conversion_rate:
Option<FloatStorageValueMetric<SC, sp_runtime::FixedU128>>,
}
impl<SC: Chain, TC: Chain> StandaloneMessagesMetrics<SC, TC> {
/// Swap source and target sides.
pub fn reverse(self) -> StandaloneMessagesMetrics<TC, SC> {
StandaloneMessagesMetrics {
global: self.global,
source_storage_proof_overhead: self.target_storage_proof_overhead,
target_storage_proof_overhead: self.source_storage_proof_overhead,
source_to_base_conversion_rate: self.target_to_base_conversion_rate,
target_to_base_conversion_rate: self.source_to_base_conversion_rate,
source_to_target_conversion_rate: self.target_to_source_conversion_rate,
target_to_source_conversion_rate: self.source_to_target_conversion_rate,
}
}
/// Register all metrics in the registry.
pub fn register_and_spawn(
self,
metrics: MetricsParams,
) -> Result<MetricsParams, PrometheusError> {
self.global.register_and_spawn(&metrics.registry)?;
self.source_storage_proof_overhead.register_and_spawn(&metrics.registry)?;
self.target_storage_proof_overhead.register_and_spawn(&metrics.registry)?;
if let Some(m) = self.source_to_base_conversion_rate {
m.register_and_spawn(&metrics.registry)?;
}
if let Some(m) = self.target_to_base_conversion_rate {
m.register_and_spawn(&metrics.registry)?;
}
if let Some(m) = self.target_to_source_conversion_rate {
m.register_and_spawn(&metrics.registry)?;
}
Ok(metrics)
}
/// Return conversion rate from target to source tokens.
pub async fn target_to_source_conversion_rate(&self) -> Option<f64> {
Self::compute_target_to_source_conversion_rate(
*self.target_to_base_conversion_rate.as_ref()?.shared_value_ref().read().await,
*self.source_to_base_conversion_rate.as_ref()?.shared_value_ref().read().await,
)
}
/// Return conversion rate from target to source tokens, given conversion rates from
/// target/source tokens to some base token.
fn compute_target_to_source_conversion_rate(
target_to_base_conversion_rate: Option<f64>,
source_to_base_conversion_rate: Option<f64>,
) -> Option<f64> {
Some(source_to_base_conversion_rate? / target_to_base_conversion_rate?)
}
}
/// Create standalone metrics for the message lane relay loop.
///
/// All metrics returned by this function are exposed by loops that are serving given lane (`P`)
/// and by loops that are serving reverse lane (`P` with swapped `TargetChain` and `SourceChain`).
pub fn standalone_metrics<SC: Chain, TC: Chain>(
source_client: Client<SC>,
target_client: Client<TC>,
source_chain_token_id: Option<&str>,
target_chain_token_id: Option<&str>,
source_to_target_conversion_rate_params: Option<(StorageKey, FixedU128)>,
target_to_source_conversion_rate_params: Option<(StorageKey, FixedU128)>,
) -> anyhow::Result<StandaloneMessagesMetrics<SC, TC>> {
Ok(StandaloneMessagesMetrics {
global: GlobalMetrics::new()?,
source_storage_proof_overhead: StorageProofOverheadMetric::new(
source_client.clone(),
format!("{}_storage_proof_overhead", SC::NAME.to_lowercase()),
format!("{} storage proof overhead", SC::NAME),
)?,
target_storage_proof_overhead: StorageProofOverheadMetric::new(
target_client.clone(),
format!("{}_storage_proof_overhead", TC::NAME.to_lowercase()),
format!("{} storage proof overhead", TC::NAME),
)?,
source_to_base_conversion_rate: source_chain_token_id
.map(|source_chain_token_id| {
crate::helpers::token_price_metric(source_chain_token_id).map(Some)
})
.unwrap_or(Ok(None))?,
target_to_base_conversion_rate: target_chain_token_id
.map(|target_chain_token_id| {
crate::helpers::token_price_metric(target_chain_token_id).map(Some)
})
.unwrap_or(Ok(None))?,
source_to_target_conversion_rate: source_to_target_conversion_rate_params
.map(|(key, rate)| {
FloatStorageValueMetric::<_, sp_runtime::FixedU128>::new(
target_client,
key,
Some(rate),
format!("{}_{}_to_{}_conversion_rate", TC::NAME, SC::NAME, TC::NAME),
format!(
"{} to {} tokens conversion rate (used by {})",
SC::NAME,
TC::NAME,
TC::NAME
),
)
.map(Some)
})
.unwrap_or(Ok(None))?,
target_to_source_conversion_rate: target_to_source_conversion_rate_params
.map(|(key, rate)| {
FloatStorageValueMetric::<_, sp_runtime::FixedU128>::new(
source_client,
key,
Some(rate),
format!("{}_{}_to_{}_conversion_rate", SC::NAME, TC::NAME, SC::NAME),
format!(
"{} to {} tokens conversion rate (used by {})",
TC::NAME,
SC::NAME,
SC::NAME
),
)
.map(Some)
})
.unwrap_or(Ok(None))?,
})
}
#[cfg(test)]
mod tests {
use super::*;
use bp_runtime::Chain;
type RialtoToMillauMessagesWeights =
pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>;
pallet_bridge_messages::weights::MillauWeight<rialto_runtime::Runtime>;
#[test]
fn select_delivery_transaction_limits_works() {
let (max_count, max_weight) =
select_delivery_transaction_limits::<RialtoToMillauMessagesWeights>(
bp_millau::max_extrinsic_weight(),
bp_millau::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
bp_millau::Millau::max_extrinsic_weight(),
bp_rialto::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
);
assert_eq!(
(max_count, max_weight),
@@ -412,15 +507,7 @@ mod tests {
// i.e. weight reserved for messages dispatch allows dispatch of non-trivial messages.
//
// Any significant change in this values should attract additional attention.
(782, 216_583_333_334),
);
}
#[async_std::test]
async fn target_to_source_conversion_rate_works() {
assert_eq!(
StandaloneMessagesMetrics::<relay_rococo_client::Rococo, relay_wococo_client::Wococo>::compute_target_to_source_conversion_rate(Some(183.15), Some(12.32)),
Some(12.32 / 183.15),
(958, 216_583_333_334),
);
}
}
@@ -0,0 +1,389 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Tools for supporting message lanes between two Substrate-based chains.
use crate::{helpers::tokens_conversion_rate, messages_lane::SubstrateMessageLane};
use codec::Decode;
use frame_system::AccountInfo;
use pallet_balances::AccountData;
use relay_substrate_client::{
metrics::{
FixedU128OrOne, FloatStorageValue, FloatStorageValueMetric, StorageProofOverheadMetric,
},
AccountIdOf, BalanceOf, Chain, ChainWithBalances, Client, Error as SubstrateError, IndexOf,
};
use relay_utils::metrics::{
FloatJsonValueMetric, GlobalMetrics, MetricsParams, PrometheusError, StandaloneMetric,
};
use sp_core::storage::StorageData;
use sp_runtime::{FixedPointNumber, FixedU128};
use std::{convert::TryFrom, fmt::Debug, marker::PhantomData};
/// Name of the `NextFeeMultiplier` storage value within the transaction payment pallet.
const NEXT_FEE_MULTIPLIER_VALUE_NAME: &str = "NextFeeMultiplier";
/// Shared references to the standalone metrics of the message lane relay loop.
#[derive(Debug, Clone)]
pub struct StandaloneMessagesMetrics<SC: Chain, TC: Chain> {
/// Global metrics.
pub global: GlobalMetrics,
/// Storage chain proof overhead metric.
pub source_storage_proof_overhead: StorageProofOverheadMetric<SC>,
/// Target chain proof overhead metric.
pub target_storage_proof_overhead: StorageProofOverheadMetric<TC>,
/// Source tokens to base conversion rate metric.
pub source_to_base_conversion_rate: Option<FloatJsonValueMetric>,
/// Target tokens to base conversion rate metric.
pub target_to_base_conversion_rate: Option<FloatJsonValueMetric>,
/// Source tokens to target tokens conversion rate metric. This rate is stored by the target
/// chain.
pub source_to_target_conversion_rate: Option<FloatStorageValueMetric<TC, FixedU128OrOne>>,
/// Target tokens to source tokens conversion rate metric. This rate is stored by the source
/// chain.
pub target_to_source_conversion_rate: Option<FloatStorageValueMetric<SC, FixedU128OrOne>>,
/// Actual source chain fee multiplier.
pub source_fee_multiplier: Option<FloatStorageValueMetric<SC, FixedU128OrOne>>,
/// Source chain fee multiplier, stored at the target chain.
pub source_fee_multiplier_at_target: Option<FloatStorageValueMetric<TC, FixedU128OrOne>>,
/// Actual target chain fee multiplier.
pub target_fee_multiplier: Option<FloatStorageValueMetric<TC, FixedU128OrOne>>,
/// Target chain fee multiplier, stored at the target chain.
pub target_fee_multiplier_at_source: Option<FloatStorageValueMetric<SC, FixedU128OrOne>>,
}
impl<SC: Chain, TC: Chain> StandaloneMessagesMetrics<SC, TC> {
/// Swap source and target sides.
pub fn reverse(self) -> StandaloneMessagesMetrics<TC, SC> {
StandaloneMessagesMetrics {
global: self.global,
source_storage_proof_overhead: self.target_storage_proof_overhead,
target_storage_proof_overhead: self.source_storage_proof_overhead,
source_to_base_conversion_rate: self.target_to_base_conversion_rate,
target_to_base_conversion_rate: self.source_to_base_conversion_rate,
source_to_target_conversion_rate: self.target_to_source_conversion_rate,
target_to_source_conversion_rate: self.source_to_target_conversion_rate,
source_fee_multiplier: self.target_fee_multiplier,
source_fee_multiplier_at_target: self.target_fee_multiplier_at_source,
target_fee_multiplier: self.source_fee_multiplier,
target_fee_multiplier_at_source: self.source_fee_multiplier_at_target,
}
}
/// Register all metrics in the registry.
pub fn register_and_spawn(
self,
metrics: MetricsParams,
) -> Result<MetricsParams, PrometheusError> {
self.global.register_and_spawn(&metrics.registry)?;
self.source_storage_proof_overhead.register_and_spawn(&metrics.registry)?;
self.target_storage_proof_overhead.register_and_spawn(&metrics.registry)?;
if let Some(m) = self.source_to_base_conversion_rate {
m.register_and_spawn(&metrics.registry)?;
}
if let Some(m) = self.target_to_base_conversion_rate {
m.register_and_spawn(&metrics.registry)?;
}
if let Some(m) = self.target_to_source_conversion_rate {
m.register_and_spawn(&metrics.registry)?;
}
if let Some(m) = self.source_fee_multiplier {
m.register_and_spawn(&metrics.registry)?;
}
if let Some(m) = self.source_fee_multiplier_at_target {
m.register_and_spawn(&metrics.registry)?;
}
if let Some(m) = self.target_fee_multiplier {
m.register_and_spawn(&metrics.registry)?;
}
if let Some(m) = self.target_fee_multiplier_at_source {
m.register_and_spawn(&metrics.registry)?;
}
Ok(metrics)
}
/// Return conversion rate from target to source tokens.
pub async fn target_to_source_conversion_rate(&self) -> Option<f64> {
let from_token_value =
(*self.target_to_base_conversion_rate.as_ref()?.shared_value_ref().read().await)?;
let to_token_value =
(*self.source_to_base_conversion_rate.as_ref()?.shared_value_ref().read().await)?;
Some(tokens_conversion_rate(from_token_value, to_token_value))
}
}
/// Create symmetric standalone metrics for the message lane relay loop.
///
/// All metrics returned by this function are exposed by loops that are serving given lane (`P`)
/// and by loops that are serving reverse lane (`P` with swapped `TargetChain` and `SourceChain`).
/// We assume that either conversion rate parameters have values in the storage, or they are
/// initialized with 1:1.
pub fn standalone_metrics<P: SubstrateMessageLane>(
source_client: Client<P::SourceChain>,
target_client: Client<P::TargetChain>,
) -> anyhow::Result<StandaloneMessagesMetrics<P::SourceChain, P::TargetChain>> {
Ok(StandaloneMessagesMetrics {
global: GlobalMetrics::new()?,
source_storage_proof_overhead: StorageProofOverheadMetric::new(
source_client.clone(),
format!("{}_storage_proof_overhead", P::SourceChain::NAME.to_lowercase()),
format!("{} storage proof overhead", P::SourceChain::NAME),
)?,
target_storage_proof_overhead: StorageProofOverheadMetric::new(
target_client.clone(),
format!("{}_storage_proof_overhead", P::TargetChain::NAME.to_lowercase()),
format!("{} storage proof overhead", P::TargetChain::NAME),
)?,
source_to_base_conversion_rate: P::SourceChain::TOKEN_ID
.map(|source_chain_token_id| {
crate::helpers::token_price_metric(source_chain_token_id).map(Some)
})
.unwrap_or(Ok(None))?,
target_to_base_conversion_rate: P::TargetChain::TOKEN_ID
.map(|target_chain_token_id| {
crate::helpers::token_price_metric(target_chain_token_id).map(Some)
})
.unwrap_or(Ok(None))?,
source_to_target_conversion_rate: P::SOURCE_TO_TARGET_CONVERSION_RATE_PARAMETER_NAME
.map(bp_runtime::storage_parameter_key)
.map(|key| {
FloatStorageValueMetric::new(
FixedU128OrOne::default(),
target_client.clone(),
key,
format!(
"{}_{}_to_{}_conversion_rate",
P::TargetChain::NAME,
P::SourceChain::NAME,
P::TargetChain::NAME
),
format!(
"{} to {} tokens conversion rate (used by {})",
P::SourceChain::NAME,
P::TargetChain::NAME,
P::TargetChain::NAME
),
)
.map(Some)
})
.unwrap_or(Ok(None))?,
target_to_source_conversion_rate: P::TARGET_TO_SOURCE_CONVERSION_RATE_PARAMETER_NAME
.map(bp_runtime::storage_parameter_key)
.map(|key| {
FloatStorageValueMetric::new(
FixedU128OrOne::default(),
source_client.clone(),
key,
format!(
"{}_{}_to_{}_conversion_rate",
P::SourceChain::NAME,
P::TargetChain::NAME,
P::SourceChain::NAME
),
format!(
"{} to {} tokens conversion rate (used by {})",
P::TargetChain::NAME,
P::SourceChain::NAME,
P::SourceChain::NAME
),
)
.map(Some)
})
.unwrap_or(Ok(None))?,
source_fee_multiplier: P::AT_SOURCE_TRANSACTION_PAYMENT_PALLET_NAME
.map(|pallet| bp_runtime::storage_value_key(pallet, NEXT_FEE_MULTIPLIER_VALUE_NAME))
.map(|key| {
log::trace!(target: "bridge", "{}_fee_multiplier", P::SourceChain::NAME);
FloatStorageValueMetric::new(
FixedU128OrOne::default(),
source_client.clone(),
key,
format!("{}_fee_multiplier", P::SourceChain::NAME,),
format!("{} fee multiplier", P::SourceChain::NAME,),
)
.map(Some)
})
.unwrap_or(Ok(None))?,
source_fee_multiplier_at_target: P::SOURCE_FEE_MULTIPLIER_PARAMETER_NAME
.map(bp_runtime::storage_parameter_key)
.map(|key| {
FloatStorageValueMetric::new(
FixedU128OrOne::default(),
target_client.clone(),
key,
format!("{}_{}_fee_multiplier", P::TargetChain::NAME, P::SourceChain::NAME,),
format!(
"{} fee multiplier stored at {}",
P::SourceChain::NAME,
P::TargetChain::NAME,
),
)
.map(Some)
})
.unwrap_or(Ok(None))?,
target_fee_multiplier: P::AT_TARGET_TRANSACTION_PAYMENT_PALLET_NAME
.map(|pallet| bp_runtime::storage_value_key(pallet, NEXT_FEE_MULTIPLIER_VALUE_NAME))
.map(|key| {
log::trace!(target: "bridge", "{}_fee_multiplier", P::TargetChain::NAME);
FloatStorageValueMetric::new(
FixedU128OrOne::default(),
target_client,
key,
format!("{}_fee_multiplier", P::TargetChain::NAME,),
format!("{} fee multiplier", P::TargetChain::NAME,),
)
.map(Some)
})
.unwrap_or(Ok(None))?,
target_fee_multiplier_at_source: P::TARGET_FEE_MULTIPLIER_PARAMETER_NAME
.map(bp_runtime::storage_parameter_key)
.map(|key| {
FloatStorageValueMetric::new(
FixedU128OrOne::default(),
source_client,
key,
format!("{}_{}_fee_multiplier", P::SourceChain::NAME, P::TargetChain::NAME,),
format!(
"{} fee multiplier stored at {}",
P::TargetChain::NAME,
P::SourceChain::NAME,
),
)
.map(Some)
})
.unwrap_or(Ok(None))?,
})
}
/// Add relay accounts balance metrics.
pub async fn add_relay_balances_metrics<C: ChainWithBalances>(
client: Client<C>,
metrics: MetricsParams,
relay_account_id: Option<AccountIdOf<C>>,
messages_pallet_owner_account_id: Option<AccountIdOf<C>>,
) -> anyhow::Result<MetricsParams>
where
BalanceOf<C>: Into<u128> + std::fmt::Debug,
{
if relay_account_id.is_none() && messages_pallet_owner_account_id.is_none() {
return Ok(metrics)
}
// if `tokenDecimals` is missing from system properties, we'll be using
let token_decimals = client
.token_decimals()
.await?
.map(|token_decimals| {
log::info!(target: "bridge", "Read `tokenDecimals` for {}: {}", C::NAME, token_decimals);
token_decimals
})
.unwrap_or_else(|| {
// turns out it is normal not to have this property - e.g. when polkadot binary is
// started using `polkadot-local` chain. Let's use minimal nominal here
log::info!(target: "bridge", "Using default (zero) `tokenDecimals` value for {}", C::NAME);
0
});
let token_decimals = u32::try_from(token_decimals).map_err(|e| {
anyhow::format_err!(
"Token decimals value ({}) of {} doesn't fit into u32: {:?}",
token_decimals,
C::NAME,
e,
)
})?;
if let Some(relay_account_id) = relay_account_id {
let relay_account_balance_metric = FloatStorageValueMetric::new(
FreeAccountBalance::<C> { token_decimals, _phantom: Default::default() },
client.clone(),
C::account_info_storage_key(&relay_account_id),
format!("at_{}_relay_balance", C::NAME),
format!("Balance of the relay account at the {}", C::NAME),
)?;
relay_account_balance_metric.register_and_spawn(&metrics.registry)?;
}
if let Some(messages_pallet_owner_account_id) = messages_pallet_owner_account_id {
let pallet_owner_account_balance_metric = FloatStorageValueMetric::new(
FreeAccountBalance::<C> { token_decimals, _phantom: Default::default() },
client.clone(),
C::account_info_storage_key(&messages_pallet_owner_account_id),
format!("at_{}_messages_pallet_owner_balance", C::NAME),
format!("Balance of the messages pallet owner at the {}", C::NAME),
)?;
pallet_owner_account_balance_metric.register_and_spawn(&metrics.registry)?;
}
Ok(metrics)
}
/// Adapter for `FloatStorageValueMetric` to decode account free balance.
#[derive(Clone, Debug)]
struct FreeAccountBalance<C> {
token_decimals: u32,
_phantom: PhantomData<C>,
}
impl<C> FloatStorageValue for FreeAccountBalance<C>
where
C: Chain,
BalanceOf<C>: Into<u128>,
{
type Value = FixedU128;
fn decode(
&self,
maybe_raw_value: Option<StorageData>,
) -> Result<Option<Self::Value>, SubstrateError> {
maybe_raw_value
.map(|raw_value| {
AccountInfo::<IndexOf<C>, AccountData<BalanceOf<C>>>::decode(&mut &raw_value.0[..])
.map_err(SubstrateError::ResponseParseFailed)
.map(|account_data| {
convert_to_token_balance(account_data.data.free.into(), self.token_decimals)
})
})
.transpose()
}
}
/// Convert from raw `u128` balance (nominated in smallest chain token units) to the float regular
/// tokens value.
fn convert_to_token_balance(balance: u128, token_decimals: u32) -> FixedU128 {
FixedU128::from_inner(balance.saturating_mul(FixedU128::DIV / 10u128.pow(token_decimals)))
}
#[cfg(test)]
mod tests {
use super::*;
use frame_support::storage::generator::StorageValue;
use sp_core::storage::StorageKey;
#[test]
fn token_decimals_used_properly() {
let plancks = 425_000_000_000;
let token_decimals = 10;
let dots = convert_to_token_balance(plancks, token_decimals);
assert_eq!(dots, FixedU128::saturating_from_rational(425, 10));
}
#[test]
fn next_fee_multiplier_storage_key_is_correct() {
assert_eq!(
bp_runtime::storage_value_key("TransactionPayment", NEXT_FEE_MULTIPLIER_VALUE_NAME),
StorageKey(pallet_transaction_payment::NextFeeMultiplier::<rialto_runtime::Runtime>::storage_value_final_key().to_vec()),
);
}
}
@@ -19,12 +19,19 @@
//! <BridgedName> chain.
use crate::{
messages_lane::SubstrateMessageLane, messages_target::SubstrateMessagesReceivingProof,
messages_lane::{
MessageLaneAdapter, ReceiveMessagesDeliveryProofCallBuilder, SubstrateMessageLane,
},
messages_target::SubstrateMessagesDeliveryProof,
on_demand_headers::OnDemandHeadersRelay,
TransactionParams,
};
use async_trait::async_trait;
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState};
use bp_messages::{
storage_keys::{operating_mode_key, outbound_lane_data_key},
LaneId, MessageNonce, OperatingMode, OutboundLaneData, UnrewardedRelayersState,
};
use bridge_runtime_common::messages::{
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
};
@@ -39,15 +46,13 @@ use messages_relay::{
};
use num_traits::{Bounded, Zero};
use relay_substrate_client::{
BalanceOf, BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf, HeaderIdOf, HeaderOf,
IndexOf,
};
use relay_utils::{relay_loop::Client as RelayClient, BlockNumberBase, HeaderId};
use sp_core::Bytes;
use sp_runtime::{
traits::{AtLeast32BitUnsigned, Header as HeaderT},
DeserializeOwned,
AccountIdOf, AccountKeyPairOf, BalanceOf, BlockNumberOf, Chain, ChainWithMessages, Client,
Error as SubstrateError, HashOf, HeaderIdOf, IndexOf, SignParam, TransactionEra,
TransactionSignScheme, UnsignedTransaction,
};
use relay_utils::{relay_loop::Client as RelayClient, HeaderId};
use sp_core::{Bytes, Pair};
use sp_runtime::{traits::Header as HeaderT, DeserializeOwned};
use std::ops::RangeInclusive;
/// Intermediate message proof returned by the source Substrate node. Includes everything
@@ -57,30 +62,60 @@ pub type SubstrateMessagesProof<C> = (Weight, FromBridgedChainMessagesProof<Hash
/// Substrate client as Substrate messages source.
pub struct SubstrateMessagesSource<P: SubstrateMessageLane> {
client: Client<P::SourceChain>,
lane: P,
source_client: Client<P::SourceChain>,
target_client: Client<P::TargetChain>,
lane_id: LaneId,
transaction_params: TransactionParams<AccountKeyPairOf<P::SourceTransactionSignScheme>>,
target_to_source_headers_relay: Option<OnDemandHeadersRelay<P::TargetChain>>,
}
impl<P: SubstrateMessageLane> SubstrateMessagesSource<P> {
/// Create new Substrate headers source.
pub fn new(
client: Client<P::SourceChain>,
lane: P,
source_client: Client<P::SourceChain>,
target_client: Client<P::TargetChain>,
lane_id: LaneId,
transaction_params: TransactionParams<AccountKeyPairOf<P::SourceTransactionSignScheme>>,
target_to_source_headers_relay: Option<OnDemandHeadersRelay<P::TargetChain>>,
) -> Self {
SubstrateMessagesSource { client, lane, lane_id, target_to_source_headers_relay }
SubstrateMessagesSource {
source_client,
target_client,
lane_id,
transaction_params,
target_to_source_headers_relay,
}
}
/// Read outbound lane state from the on-chain storage at given block.
async fn outbound_lane_data(
&self,
id: SourceHeaderIdOf<MessageLaneAdapter<P>>,
) -> Result<Option<OutboundLaneData>, SubstrateError> {
self.source_client
.storage_value(
outbound_lane_data_key(
P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
&self.lane_id,
),
Some(id.1),
)
.await
}
/// Ensure that the messages pallet at source chain is active.
async fn ensure_pallet_active(&self) -> Result<(), SubstrateError> {
ensure_messages_pallet_active::<P::SourceChain, P::TargetChain>(&self.source_client).await
}
}
impl<P: SubstrateMessageLane> Clone for SubstrateMessagesSource<P> {
fn clone(&self) -> Self {
Self {
client: self.client.clone(),
lane: self.lane.clone(),
source_client: self.source_client.clone(),
target_client: self.target_client.clone(),
lane_id: self.lane_id,
transaction_params: self.transaction_params.clone(),
target_to_source_headers_relay: self.target_to_source_headers_relay.clone(),
}
}
@@ -91,96 +126,71 @@ impl<P: SubstrateMessageLane> RelayClient for SubstrateMessagesSource<P> {
type Error = SubstrateError;
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
self.client.reconnect().await
self.source_client.reconnect().await?;
self.target_client.reconnect().await
}
}
#[async_trait]
impl<P> SourceClient<P::MessageLane> for SubstrateMessagesSource<P>
impl<P: SubstrateMessageLane> SourceClient<MessageLaneAdapter<P>> for SubstrateMessagesSource<P>
where
P: SubstrateMessageLane,
P::SourceChain: Chain<
Hash = <P::MessageLane as MessageLane>::SourceHeaderHash,
BlockNumber = <P::MessageLane as MessageLane>::SourceHeaderNumber,
Balance = <P::MessageLane as MessageLane>::SourceChainBalance,
>,
BalanceOf<P::SourceChain>: Decode + Bounded,
IndexOf<P::SourceChain>: DeserializeOwned,
HashOf<P::SourceChain>: Copy,
BlockNumberOf<P::SourceChain>: BlockNumberBase + Copy,
HeaderOf<P::SourceChain>: DeserializeOwned,
P::TargetChain: Chain<
Hash = <P::MessageLane as MessageLane>::TargetHeaderHash,
BlockNumber = <P::MessageLane as MessageLane>::TargetHeaderNumber,
>,
P::MessageLane: MessageLane<
MessagesProof = SubstrateMessagesProof<P::SourceChain>,
MessagesReceivingProof = SubstrateMessagesReceivingProof<P::TargetChain>,
>,
<P::MessageLane as MessageLane>::TargetHeaderNumber: Decode,
<P::MessageLane as MessageLane>::TargetHeaderHash: Decode,
<P::MessageLane as MessageLane>::SourceChainBalance: AtLeast32BitUnsigned,
AccountIdOf<P::SourceChain>:
From<<AccountKeyPairOf<P::SourceTransactionSignScheme> as Pair>::Public>,
P::SourceTransactionSignScheme: TransactionSignScheme<Chain = P::SourceChain>,
{
async fn state(&self) -> Result<SourceClientState<P::MessageLane>, SubstrateError> {
async fn state(&self) -> Result<SourceClientState<MessageLaneAdapter<P>>, SubstrateError> {
// we can't continue to deliver confirmations if source node is out of sync, because
// it may have already received confirmations that we're going to deliver
self.client.ensure_synced().await?;
self.source_client.ensure_synced().await?;
// we can't relay confirmations if messages pallet at source chain is halted
self.ensure_pallet_active().await?;
read_client_state::<
_,
<P::MessageLane as MessageLane>::TargetHeaderHash,
<P::MessageLane as MessageLane>::TargetHeaderNumber,
>(&self.client, P::BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE)
read_client_state(
&self.source_client,
Some(&self.target_client),
P::TargetChain::BEST_FINALIZED_HEADER_ID_METHOD,
)
.await
}
async fn latest_generated_nonce(
&self,
id: SourceHeaderIdOf<P::MessageLane>,
) -> Result<(SourceHeaderIdOf<P::MessageLane>, MessageNonce), SubstrateError> {
let encoded_response = self
.client
.state_call(
P::OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD.into(),
Bytes(self.lane_id.encode()),
Some(id.1),
)
.await?;
let latest_generated_nonce: MessageNonce = Decode::decode(&mut &encoded_response.0[..])
.map_err(SubstrateError::ResponseParseFailed)?;
id: SourceHeaderIdOf<MessageLaneAdapter<P>>,
) -> Result<(SourceHeaderIdOf<MessageLaneAdapter<P>>, MessageNonce), SubstrateError> {
// lane data missing from the storage is fine until first message is sent
let latest_generated_nonce = self
.outbound_lane_data(id)
.await?
.map(|data| data.latest_generated_nonce)
.unwrap_or(0);
Ok((id, latest_generated_nonce))
}
async fn latest_confirmed_received_nonce(
&self,
id: SourceHeaderIdOf<P::MessageLane>,
) -> Result<(SourceHeaderIdOf<P::MessageLane>, MessageNonce), SubstrateError> {
let encoded_response = self
.client
.state_call(
P::OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD.into(),
Bytes(self.lane_id.encode()),
Some(id.1),
)
.await?;
let latest_received_nonce: MessageNonce = Decode::decode(&mut &encoded_response.0[..])
.map_err(SubstrateError::ResponseParseFailed)?;
id: SourceHeaderIdOf<MessageLaneAdapter<P>>,
) -> Result<(SourceHeaderIdOf<MessageLaneAdapter<P>>, MessageNonce), SubstrateError> {
// lane data missing from the storage is fine until first message is sent
let latest_received_nonce = self
.outbound_lane_data(id)
.await?
.map(|data| data.latest_received_nonce)
.unwrap_or(0);
Ok((id, latest_received_nonce))
}
async fn generated_message_details(
&self,
id: SourceHeaderIdOf<P::MessageLane>,
id: SourceHeaderIdOf<MessageLaneAdapter<P>>,
nonces: RangeInclusive<MessageNonce>,
) -> Result<
MessageDetailsMap<<P::MessageLane as MessageLane>::SourceChainBalance>,
MessageDetailsMap<<MessageLaneAdapter<P> as MessageLane>::SourceChainBalance>,
SubstrateError,
> {
let encoded_response = self
.client
.source_client
.state_call(
P::OUTBOUND_LANE_MESSAGE_DETAILS_METHOD.into(),
P::TargetChain::TO_CHAIN_MESSAGE_DETAILS_METHOD.into(),
Bytes((self.lane_id, nonces.start(), nonces.end()).encode()),
Some(id.1),
)
@@ -195,14 +205,14 @@ where
async fn prove_messages(
&self,
id: SourceHeaderIdOf<P::MessageLane>,
id: SourceHeaderIdOf<MessageLaneAdapter<P>>,
nonces: RangeInclusive<MessageNonce>,
proof_parameters: MessageProofParameters,
) -> Result<
(
SourceHeaderIdOf<P::MessageLane>,
SourceHeaderIdOf<MessageLaneAdapter<P>>,
RangeInclusive<MessageNonce>,
<P::MessageLane as MessageLane>::MessagesProof,
<MessageLaneAdapter<P> as MessageLane>::MessagesProof,
),
SubstrateError,
> {
@@ -210,8 +220,8 @@ where
Vec::with_capacity(nonces.end().saturating_sub(*nonces.start()) as usize + 1);
let mut message_nonce = *nonces.start();
while message_nonce <= *nonces.end() {
let message_key = pallet_bridge_messages::storage_keys::message_key(
P::MESSAGE_PALLET_NAME_AT_SOURCE,
let message_key = bp_messages::storage_keys::message_key(
P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
&self.lane_id,
message_nonce,
);
@@ -219,13 +229,18 @@ where
message_nonce += 1;
}
if proof_parameters.outbound_state_proof_required {
storage_keys.push(pallet_bridge_messages::storage_keys::outbound_lane_data_key(
P::MESSAGE_PALLET_NAME_AT_SOURCE,
storage_keys.push(bp_messages::storage_keys::outbound_lane_data_key(
P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
&self.lane_id,
));
}
let proof = self.client.prove_storage(storage_keys, id.1).await?.iter_nodes().collect();
let proof = self
.source_client
.prove_storage(storage_keys, id.1)
.await?
.iter_nodes()
.collect();
let proof = FromBridgedChainMessagesProof {
bridged_header_hash: id.1,
storage_proof: proof,
@@ -238,19 +253,26 @@ where
async fn submit_messages_receiving_proof(
&self,
generated_at_block: TargetHeaderIdOf<P::MessageLane>,
proof: <P::MessageLane as MessageLane>::MessagesReceivingProof,
_generated_at_block: TargetHeaderIdOf<MessageLaneAdapter<P>>,
proof: <MessageLaneAdapter<P> as MessageLane>::MessagesReceivingProof,
) -> Result<(), SubstrateError> {
let lane = self.lane.clone();
self.client
let genesis_hash = *self.source_client.genesis_hash();
let transaction_params = self.transaction_params.clone();
let (spec_version, transaction_version) =
self.source_client.simple_runtime_version().await?;
self.source_client
.submit_signed_extrinsic(
self.lane.source_transactions_author(),
self.transaction_params.signer.public().into(),
move |best_block_id, transaction_nonce| {
lane.make_messages_receiving_proof_transaction(
make_messages_delivery_proof_transaction::<P>(
spec_version,
transaction_version,
&genesis_hash,
&transaction_params,
best_block_id,
transaction_nonce,
generated_at_block,
proof,
true,
)
},
)
@@ -258,7 +280,7 @@ where
Ok(())
}
async fn require_target_header_on_source(&self, id: TargetHeaderIdOf<P::MessageLane>) {
async fn require_target_header_on_source(&self, id: TargetHeaderIdOf<MessageLaneAdapter<P>>) {
if let Some(ref target_to_source_headers_relay) = self.target_to_source_headers_relay {
target_to_source_headers_relay.require_finalized_header(id).await;
}
@@ -266,26 +288,89 @@ where
async fn estimate_confirmation_transaction(
&self,
) -> <P::MessageLane as MessageLane>::SourceChainBalance {
self.client
.estimate_extrinsic_fee(self.lane.make_messages_receiving_proof_transaction(
) -> <MessageLaneAdapter<P> as MessageLane>::SourceChainBalance {
let runtime_version = match self.source_client.runtime_version().await {
Ok(v) => v,
Err(_) => return BalanceOf::<P::SourceChain>::max_value(),
};
async {
let dummy_tx = make_messages_delivery_proof_transaction::<P>(
runtime_version.spec_version,
runtime_version.transaction_version,
self.source_client.genesis_hash(),
&self.transaction_params,
HeaderId(Default::default(), Default::default()),
Zero::zero(),
HeaderId(Default::default(), Default::default()),
prepare_dummy_messages_delivery_proof::<P::SourceChain, P::TargetChain>(),
))
.await
.map(|fee| fee.inclusion_fee())
.unwrap_or_else(|_| BalanceOf::<P::SourceChain>::max_value())
false,
)?;
self.source_client
.estimate_extrinsic_fee(dummy_tx)
.await
.map(|fee| fee.inclusion_fee())
}
.await
.unwrap_or_else(|_| BalanceOf::<P::SourceChain>::max_value())
}
}
/// Ensure that the messages pallet at source chain is active.
pub(crate) async fn ensure_messages_pallet_active<AtChain, WithChain>(
client: &Client<AtChain>,
) -> Result<(), SubstrateError>
where
AtChain: ChainWithMessages,
WithChain: ChainWithMessages,
{
let operating_mode = client
.storage_value(operating_mode_key(WithChain::WITH_CHAIN_MESSAGES_PALLET_NAME), None)
.await?;
let is_halted = operating_mode == Some(OperatingMode::Halted);
if is_halted {
Err(SubstrateError::BridgePalletIsHalted)
} else {
Ok(())
}
}
/// Make messages delivery proof transaction from given proof.
#[allow(clippy::too_many_arguments)]
fn make_messages_delivery_proof_transaction<P: SubstrateMessageLane>(
spec_version: u32,
transaction_version: u32,
source_genesis_hash: &HashOf<P::SourceChain>,
source_transaction_params: &TransactionParams<AccountKeyPairOf<P::SourceTransactionSignScheme>>,
source_best_block_id: HeaderIdOf<P::SourceChain>,
transaction_nonce: IndexOf<P::SourceChain>,
proof: SubstrateMessagesDeliveryProof<P::TargetChain>,
trace_call: bool,
) -> Result<Bytes, SubstrateError>
where
P::SourceTransactionSignScheme: TransactionSignScheme<Chain = P::SourceChain>,
{
let call =
P::ReceiveMessagesDeliveryProofCallBuilder::build_receive_messages_delivery_proof_call(
proof, trace_call,
);
Ok(Bytes(
P::SourceTransactionSignScheme::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash: *source_genesis_hash,
signer: source_transaction_params.signer.clone(),
era: TransactionEra::new(source_best_block_id, source_transaction_params.mortality),
unsigned: UnsignedTransaction::new(call.into(), transaction_nonce),
})?
.encode(),
))
}
/// Prepare 'dummy' messages delivery proof that will compose the delivery confirmation transaction.
///
/// We don't care about proof actually being the valid proof, because its validity doesn't
/// affect the call weight - we only care about its size.
fn prepare_dummy_messages_delivery_proof<SC: Chain, TC: Chain>(
) -> SubstrateMessagesReceivingProof<TC> {
) -> SubstrateMessagesDeliveryProof<TC> {
let single_message_confirmation_size = bp_messages::InboundLaneData::<()>::encoded_size_hint(
SC::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE,
1,
@@ -312,19 +397,19 @@ fn prepare_dummy_messages_delivery_proof<SC: Chain, TC: Chain>(
/// This function assumes that the chain that is followed by the `self_client` has
/// bridge GRANDPA pallet deployed and it provides `best_finalized_header_id_method_name`
/// runtime API to read the best finalized Bridged chain header.
pub async fn read_client_state<SelfChain, BridgedHeaderHash, BridgedHeaderNumber>(
///
/// If `peer_client` is `None`, the value of `actual_best_finalized_peer_at_best_self` will
/// always match the `best_finalized_peer_at_best_self`.
pub async fn read_client_state<SelfChain, PeerChain>(
self_client: &Client<SelfChain>,
peer_client: Option<&Client<PeerChain>>,
best_finalized_header_id_method_name: &str,
) -> Result<
ClientState<HeaderIdOf<SelfChain>, HeaderId<BridgedHeaderHash, BridgedHeaderNumber>>,
SubstrateError,
>
) -> Result<ClientState<HeaderIdOf<SelfChain>, HeaderIdOf<PeerChain>>, SubstrateError>
where
SelfChain: Chain,
SelfChain::Header: DeserializeOwned,
SelfChain::Index: DeserializeOwned,
BridgedHeaderHash: Decode,
BridgedHeaderNumber: Decode,
PeerChain: Chain,
{
// let's read our state first: we need best finalized header hash on **this** chain
let self_best_finalized_header_hash = self_client.best_finalized_header_hash().await?;
@@ -346,16 +431,27 @@ where
Some(self_best_hash),
)
.await?;
let decoded_best_finalized_peer_on_self: (BridgedHeaderNumber, BridgedHeaderHash) =
let decoded_best_finalized_peer_on_self: (BlockNumberOf<PeerChain>, HashOf<PeerChain>) =
Decode::decode(&mut &encoded_best_finalized_peer_on_self.0[..])
.map_err(SubstrateError::ResponseParseFailed)?;
let peer_on_self_best_finalized_id =
HeaderId(decoded_best_finalized_peer_on_self.0, decoded_best_finalized_peer_on_self.1);
// read actual header, matching the `peer_on_self_best_finalized_id` from the peer chain
let actual_peer_on_self_best_finalized_id = match peer_client {
Some(peer_client) => {
let actual_peer_on_self_best_finalized =
peer_client.header_by_number(peer_on_self_best_finalized_id.0).await?;
HeaderId(peer_on_self_best_finalized_id.0, actual_peer_on_self_best_finalized.hash())
},
None => peer_on_self_best_finalized_id,
};
Ok(ClientState {
best_self: self_best_id,
best_finalized_self: self_best_finalized_id,
best_finalized_peer_at_best_self: peer_on_self_best_finalized_id,
actual_best_finalized_peer_at_best_self: actual_peer_on_self_best_finalized_id,
})
}
@@ -19,18 +19,22 @@
//! <BridgedName> chain.
use crate::{
messages_lane::{StandaloneMessagesMetrics, SubstrateMessageLane},
messages_source::{read_client_state, SubstrateMessagesProof},
messages_lane::{MessageLaneAdapter, ReceiveMessagesProofCallBuilder, SubstrateMessageLane},
messages_metrics::StandaloneMessagesMetrics,
messages_source::{ensure_messages_pallet_active, read_client_state, SubstrateMessagesProof},
on_demand_headers::OnDemandHeadersRelay,
TransactionParams,
};
use async_trait::async_trait;
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState};
use bp_messages::{
storage_keys::inbound_lane_data_key, total_unrewarded_messages, InboundLaneData, LaneId,
MessageNonce, UnrewardedRelayersState,
};
use bridge_runtime_common::messages::{
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
};
use codec::{Decode, Encode};
use codec::Encode;
use frame_support::weights::{Weight, WeightToFeePolynomial};
use messages_relay::{
message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf},
@@ -38,23 +42,26 @@ use messages_relay::{
};
use num_traits::{Bounded, Zero};
use relay_substrate_client::{
BalanceOf, BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf, HeaderOf, IndexOf,
WeightToFeeOf,
AccountIdOf, AccountKeyPairOf, BalanceOf, Chain, ChainWithMessages, Client,
Error as SubstrateError, HashOf, HeaderIdOf, IndexOf, SignParam, TransactionEra,
TransactionSignScheme, UnsignedTransaction, WeightToFeeOf,
};
use relay_utils::{relay_loop::Client as RelayClient, BlockNumberBase, HeaderId};
use sp_core::Bytes;
use sp_runtime::{traits::Saturating, DeserializeOwned, FixedPointNumber, FixedU128};
use std::{convert::TryFrom, ops::RangeInclusive};
use relay_utils::{relay_loop::Client as RelayClient, HeaderId};
use sp_core::{Bytes, Pair};
use sp_runtime::{traits::Saturating, FixedPointNumber, FixedU128};
use std::{collections::VecDeque, convert::TryFrom, ops::RangeInclusive};
/// Message receiving proof returned by the target Substrate node.
pub type SubstrateMessagesReceivingProof<C> =
pub type SubstrateMessagesDeliveryProof<C> =
(UnrewardedRelayersState, FromBridgedChainMessagesDeliveryProof<HashOf<C>>);
/// Substrate client as Substrate messages target.
pub struct SubstrateMessagesTarget<P: SubstrateMessageLane> {
client: Client<P::TargetChain>,
lane: P,
target_client: Client<P::TargetChain>,
source_client: Client<P::SourceChain>,
lane_id: LaneId,
relayer_id_at_source: AccountIdOf<P::SourceChain>,
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetTransactionSignScheme>>,
metric_values: StandaloneMessagesMetrics<P::SourceChain, P::TargetChain>,
source_to_target_headers_relay: Option<OnDemandHeadersRelay<P::SourceChain>>,
}
@@ -62,28 +69,55 @@ pub struct SubstrateMessagesTarget<P: SubstrateMessageLane> {
impl<P: SubstrateMessageLane> SubstrateMessagesTarget<P> {
/// Create new Substrate headers target.
pub fn new(
client: Client<P::TargetChain>,
lane: P,
target_client: Client<P::TargetChain>,
source_client: Client<P::SourceChain>,
lane_id: LaneId,
relayer_id_at_source: AccountIdOf<P::SourceChain>,
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetTransactionSignScheme>>,
metric_values: StandaloneMessagesMetrics<P::SourceChain, P::TargetChain>,
source_to_target_headers_relay: Option<OnDemandHeadersRelay<P::SourceChain>>,
) -> Self {
SubstrateMessagesTarget {
client,
lane,
target_client,
source_client,
lane_id,
relayer_id_at_source,
transaction_params,
metric_values,
source_to_target_headers_relay,
}
}
/// Read inbound lane state from the on-chain storage at given block.
async fn inbound_lane_data(
&self,
id: TargetHeaderIdOf<MessageLaneAdapter<P>>,
) -> Result<Option<InboundLaneData<AccountIdOf<P::SourceChain>>>, SubstrateError> {
self.target_client
.storage_value(
inbound_lane_data_key(
P::SourceChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
&self.lane_id,
),
Some(id.1),
)
.await
}
/// Ensure that the messages pallet at target chain is active.
async fn ensure_pallet_active(&self) -> Result<(), SubstrateError> {
ensure_messages_pallet_active::<P::TargetChain, P::SourceChain>(&self.target_client).await
}
}
impl<P: SubstrateMessageLane> Clone for SubstrateMessagesTarget<P> {
fn clone(&self) -> Self {
Self {
client: self.client.clone(),
lane: self.lane.clone(),
target_client: self.target_client.clone(),
source_client: self.source_client.clone(),
lane_id: self.lane_id,
relayer_id_at_source: self.relayer_id_at_source.clone(),
transaction_params: self.transaction_params.clone(),
metric_values: self.metric_values.clone(),
source_to_target_headers_relay: self.source_to_target_headers_relay.clone(),
}
@@ -95,115 +129,98 @@ impl<P: SubstrateMessageLane> RelayClient for SubstrateMessagesTarget<P> {
type Error = SubstrateError;
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
self.client.reconnect().await
self.target_client.reconnect().await?;
self.source_client.reconnect().await
}
}
#[async_trait]
impl<P> TargetClient<P::MessageLane> for SubstrateMessagesTarget<P>
impl<P: SubstrateMessageLane> TargetClient<MessageLaneAdapter<P>> for SubstrateMessagesTarget<P>
where
P: SubstrateMessageLane,
P::SourceChain: Chain<
Hash = <P::MessageLane as MessageLane>::SourceHeaderHash,
BlockNumber = <P::MessageLane as MessageLane>::SourceHeaderNumber,
Balance = <P::MessageLane as MessageLane>::SourceChainBalance,
>,
BalanceOf<P::SourceChain>: TryFrom<BalanceOf<P::TargetChain>> + Bounded,
P::TargetChain: Chain<
Hash = <P::MessageLane as MessageLane>::TargetHeaderHash,
BlockNumber = <P::MessageLane as MessageLane>::TargetHeaderNumber,
>,
IndexOf<P::TargetChain>: DeserializeOwned,
HashOf<P::TargetChain>: Copy,
BlockNumberOf<P::TargetChain>: Copy,
HeaderOf<P::TargetChain>: DeserializeOwned,
BlockNumberOf<P::TargetChain>: BlockNumberBase,
P::MessageLane: MessageLane<
MessagesProof = SubstrateMessagesProof<P::SourceChain>,
MessagesReceivingProof = SubstrateMessagesReceivingProof<P::TargetChain>,
>,
<P::MessageLane as MessageLane>::SourceHeaderNumber: Decode,
<P::MessageLane as MessageLane>::SourceHeaderHash: Decode,
AccountIdOf<P::TargetChain>:
From<<AccountKeyPairOf<P::TargetTransactionSignScheme> as Pair>::Public>,
P::TargetTransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
BalanceOf<P::SourceChain>: TryFrom<BalanceOf<P::TargetChain>>,
{
async fn state(&self) -> Result<TargetClientState<P::MessageLane>, SubstrateError> {
async fn state(&self) -> Result<TargetClientState<MessageLaneAdapter<P>>, SubstrateError> {
// we can't continue to deliver messages if target node is out of sync, because
// it may have already received (some of) messages that we're going to deliver
self.client.ensure_synced().await?;
self.target_client.ensure_synced().await?;
// we can't relay messages if messages pallet at target chain is halted
self.ensure_pallet_active().await?;
read_client_state::<
_,
<P::MessageLane as MessageLane>::SourceHeaderHash,
<P::MessageLane as MessageLane>::SourceHeaderNumber,
>(&self.client, P::BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET)
read_client_state(
&self.target_client,
Some(&self.source_client),
P::SourceChain::BEST_FINALIZED_HEADER_ID_METHOD,
)
.await
}
async fn latest_received_nonce(
&self,
id: TargetHeaderIdOf<P::MessageLane>,
) -> Result<(TargetHeaderIdOf<P::MessageLane>, MessageNonce), SubstrateError> {
let encoded_response = self
.client
.state_call(
P::INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD.into(),
Bytes(self.lane_id.encode()),
Some(id.1),
)
.await?;
let latest_received_nonce: MessageNonce = Decode::decode(&mut &encoded_response.0[..])
.map_err(SubstrateError::ResponseParseFailed)?;
id: TargetHeaderIdOf<MessageLaneAdapter<P>>,
) -> Result<(TargetHeaderIdOf<MessageLaneAdapter<P>>, MessageNonce), SubstrateError> {
// lane data missing from the storage is fine until first message is received
let latest_received_nonce = self
.inbound_lane_data(id)
.await?
.map(|data| data.last_delivered_nonce())
.unwrap_or(0);
Ok((id, latest_received_nonce))
}
async fn latest_confirmed_received_nonce(
&self,
id: TargetHeaderIdOf<P::MessageLane>,
) -> Result<(TargetHeaderIdOf<P::MessageLane>, MessageNonce), SubstrateError> {
let encoded_response = self
.client
.state_call(
P::INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD.into(),
Bytes(self.lane_id.encode()),
Some(id.1),
)
.await?;
let latest_received_nonce: MessageNonce = Decode::decode(&mut &encoded_response.0[..])
.map_err(SubstrateError::ResponseParseFailed)?;
Ok((id, latest_received_nonce))
id: TargetHeaderIdOf<MessageLaneAdapter<P>>,
) -> Result<(TargetHeaderIdOf<MessageLaneAdapter<P>>, MessageNonce), SubstrateError> {
// lane data missing from the storage is fine until first message is received
let last_confirmed_nonce = self
.inbound_lane_data(id)
.await?
.map(|data| data.last_confirmed_nonce)
.unwrap_or(0);
Ok((id, last_confirmed_nonce))
}
async fn unrewarded_relayers_state(
&self,
id: TargetHeaderIdOf<P::MessageLane>,
) -> Result<(TargetHeaderIdOf<P::MessageLane>, UnrewardedRelayersState), SubstrateError> {
let encoded_response = self
.client
.state_call(
P::INBOUND_LANE_UNREWARDED_RELAYERS_STATE.into(),
Bytes(self.lane_id.encode()),
Some(id.1),
)
.await?;
let unrewarded_relayers_state: UnrewardedRelayersState =
Decode::decode(&mut &encoded_response.0[..])
.map_err(SubstrateError::ResponseParseFailed)?;
id: TargetHeaderIdOf<MessageLaneAdapter<P>>,
) -> Result<(TargetHeaderIdOf<MessageLaneAdapter<P>>, UnrewardedRelayersState), SubstrateError>
{
let relayers = self
.inbound_lane_data(id)
.await?
.map(|data| data.relayers)
.unwrap_or_else(|| VecDeque::new());
let unrewarded_relayers_state = bp_messages::UnrewardedRelayersState {
unrewarded_relayer_entries: relayers.len() as _,
messages_in_oldest_entry: relayers
.front()
.map(|entry| 1 + entry.messages.end - entry.messages.begin)
.unwrap_or(0),
total_messages: total_unrewarded_messages(&relayers).unwrap_or(MessageNonce::MAX),
};
Ok((id, unrewarded_relayers_state))
}
async fn prove_messages_receiving(
&self,
id: TargetHeaderIdOf<P::MessageLane>,
id: TargetHeaderIdOf<MessageLaneAdapter<P>>,
) -> Result<
(TargetHeaderIdOf<P::MessageLane>, <P::MessageLane as MessageLane>::MessagesReceivingProof),
(
TargetHeaderIdOf<MessageLaneAdapter<P>>,
<MessageLaneAdapter<P> as MessageLane>::MessagesReceivingProof,
),
SubstrateError,
> {
let (id, relayers_state) = self.unrewarded_relayers_state(id).await?;
let inbound_data_key = pallet_bridge_messages::storage_keys::inbound_lane_data_key(
P::MESSAGE_PALLET_NAME_AT_TARGET,
let inbound_data_key = bp_messages::storage_keys::inbound_lane_data_key(
P::SourceChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
&self.lane_id,
);
let proof = self
.client
.target_client
.prove_storage(vec![inbound_data_key], id.1)
.await?
.iter_nodes()
@@ -218,22 +235,31 @@ where
async fn submit_messages_proof(
&self,
generated_at_header: SourceHeaderIdOf<P::MessageLane>,
_generated_at_header: SourceHeaderIdOf<MessageLaneAdapter<P>>,
nonces: RangeInclusive<MessageNonce>,
proof: <P::MessageLane as MessageLane>::MessagesProof,
proof: <MessageLaneAdapter<P> as MessageLane>::MessagesProof,
) -> Result<RangeInclusive<MessageNonce>, SubstrateError> {
let lane = self.lane.clone();
let genesis_hash = *self.target_client.genesis_hash();
let transaction_params = self.transaction_params.clone();
let relayer_id_at_source = self.relayer_id_at_source.clone();
let nonces_clone = nonces.clone();
self.client
let (spec_version, transaction_version) =
self.target_client.simple_runtime_version().await?;
self.target_client
.submit_signed_extrinsic(
self.lane.target_transactions_author(),
self.transaction_params.signer.public().into(),
move |best_block_id, transaction_nonce| {
lane.make_messages_delivery_transaction(
make_messages_delivery_transaction::<P>(
spec_version,
transaction_version,
&genesis_hash,
&transaction_params,
best_block_id,
transaction_nonce,
generated_at_header,
relayer_id_at_source,
nonces_clone,
proof,
true,
)
},
)
@@ -241,7 +267,7 @@ where
Ok(nonces)
}
async fn require_source_header_on_target(&self, id: SourceHeaderIdOf<P::MessageLane>) {
async fn require_source_header_on_target(&self, id: SourceHeaderIdOf<MessageLaneAdapter<P>>) {
if let Some(ref source_to_target_headers_relay) = self.source_to_target_headers_relay {
source_to_target_headers_relay.require_finalized_header(id).await;
}
@@ -253,7 +279,7 @@ where
total_prepaid_nonces: MessageNonce,
total_dispatch_weight: Weight,
total_size: u32,
) -> Result<<P::MessageLane as MessageLane>::SourceChainBalance, SubstrateError> {
) -> Result<<MessageLaneAdapter<P> as MessageLane>::SourceChainBalance, SubstrateError> {
let conversion_rate =
self.metric_values.target_to_source_conversion_rate().await.ok_or_else(|| {
SubstrateError::Custom(format!(
@@ -263,19 +289,26 @@ where
))
})?;
let (spec_version, transaction_version) =
self.target_client.simple_runtime_version().await?;
// Prepare 'dummy' delivery transaction - we only care about its length and dispatch weight.
let delivery_tx = self.lane.make_messages_delivery_transaction(
let delivery_tx = make_messages_delivery_transaction::<P>(
spec_version,
transaction_version,
self.target_client.genesis_hash(),
&self.transaction_params,
HeaderId(Default::default(), Default::default()),
Zero::zero(),
HeaderId(Default::default(), Default::default()),
self.relayer_id_at_source.clone(),
nonces.clone(),
prepare_dummy_messages_proof::<P::SourceChain>(
nonces.clone(),
total_dispatch_weight,
total_size,
),
);
let delivery_tx_fee = self.client.estimate_extrinsic_fee(delivery_tx).await?;
false,
)?;
let delivery_tx_fee = self.target_client.estimate_extrinsic_fee(delivery_tx).await?;
let inclusion_fee_in_target_tokens = delivery_tx_fee.inclusion_fee();
// The pre-dispatch cost of delivery transaction includes additional fee to cover dispatch
@@ -297,23 +330,29 @@ where
let expected_refund_in_target_tokens = if total_prepaid_nonces != 0 {
const WEIGHT_DIFFERENCE: Weight = 100;
let (spec_version, transaction_version) =
self.target_client.simple_runtime_version().await?;
let larger_dispatch_weight = total_dispatch_weight.saturating_add(WEIGHT_DIFFERENCE);
let larger_delivery_tx_fee = self
.client
.estimate_extrinsic_fee(self.lane.make_messages_delivery_transaction(
HeaderId(Default::default(), Default::default()),
Zero::zero(),
HeaderId(Default::default(), Default::default()),
let dummy_tx = make_messages_delivery_transaction::<P>(
spec_version,
transaction_version,
self.target_client.genesis_hash(),
&self.transaction_params,
HeaderId(Default::default(), Default::default()),
Zero::zero(),
self.relayer_id_at_source.clone(),
nonces.clone(),
prepare_dummy_messages_proof::<P::SourceChain>(
nonces.clone(),
prepare_dummy_messages_proof::<P::SourceChain>(
nonces.clone(),
larger_dispatch_weight,
total_size,
),
))
.await?;
larger_dispatch_weight,
total_size,
),
false,
)?;
let larger_delivery_tx_fee =
self.target_client.estimate_extrinsic_fee(dummy_tx).await?;
compute_prepaid_messages_refund::<P>(
compute_prepaid_messages_refund::<P::TargetChain>(
total_prepaid_nonces,
compute_fee_multiplier::<P::TargetChain>(
delivery_tx_fee.adjusted_weight_fee,
@@ -359,6 +398,45 @@ where
}
}
/// Make messages delivery transaction from given proof.
#[allow(clippy::too_many_arguments)]
fn make_messages_delivery_transaction<P: SubstrateMessageLane>(
spec_version: u32,
transaction_version: u32,
target_genesis_hash: &HashOf<P::TargetChain>,
target_transaction_params: &TransactionParams<AccountKeyPairOf<P::TargetTransactionSignScheme>>,
target_best_block_id: HeaderIdOf<P::TargetChain>,
transaction_nonce: IndexOf<P::TargetChain>,
relayer_id_at_source: AccountIdOf<P::SourceChain>,
nonces: RangeInclusive<MessageNonce>,
proof: SubstrateMessagesProof<P::SourceChain>,
trace_call: bool,
) -> Result<Bytes, SubstrateError>
where
P::TargetTransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
{
let messages_count = nonces.end() - nonces.start() + 1;
let dispatch_weight = proof.0;
let call = P::ReceiveMessagesProofCallBuilder::build_receive_messages_proof_call(
relayer_id_at_source,
proof,
messages_count as _,
dispatch_weight,
trace_call,
);
Ok(Bytes(
P::TargetTransactionSignScheme::sign_transaction(SignParam {
spec_version,
transaction_version,
genesis_hash: *target_genesis_hash,
signer: target_transaction_params.signer.clone(),
era: TransactionEra::new(target_best_block_id, target_transaction_params.mortality),
unsigned: UnsignedTransaction::new(call.into(), transaction_nonce),
})?
.encode(),
))
}
/// Prepare 'dummy' messages proof that will compose the delivery transaction.
///
/// We don't care about proof actually being the valid proof, because its validity doesn't
@@ -425,80 +503,20 @@ fn compute_fee_multiplier<C: Chain>(
/// Compute fee that will be refunded to the relayer because dispatch of `total_prepaid_nonces`
/// messages has been paid at the source chain.
fn compute_prepaid_messages_refund<P: SubstrateMessageLane>(
fn compute_prepaid_messages_refund<C: ChainWithMessages>(
total_prepaid_nonces: MessageNonce,
fee_multiplier: FixedU128,
) -> BalanceOf<P::TargetChain> {
fee_multiplier.saturating_mul_int(WeightToFeeOf::<P::TargetChain>::calc(
&P::PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN.saturating_mul(total_prepaid_nonces),
) -> BalanceOf<C> {
fee_multiplier.saturating_mul_int(WeightToFeeOf::<C>::calc(
&C::PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_CHAIN.saturating_mul(total_prepaid_nonces),
))
}
#[cfg(test)]
mod tests {
use super::*;
use relay_rococo_client::{Rococo, SigningParams as RococoSigningParams};
use relay_wococo_client::{SigningParams as WococoSigningParams, Wococo};
#[derive(Clone)]
struct TestSubstrateMessageLane;
impl SubstrateMessageLane for TestSubstrateMessageLane {
type MessageLane = crate::messages_lane::SubstrateMessageLaneToSubstrate<
Rococo,
RococoSigningParams,
Wococo,
WococoSigningParams,
>;
const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str = "";
const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str = "";
const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = "";
const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = "";
const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str = "";
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = "";
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = "";
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = "";
const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = "";
const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = "";
const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight = 100_000;
type SourceChain = Rococo;
type TargetChain = Wococo;
fn source_transactions_author(&self) -> bp_rococo::AccountId {
unreachable!()
}
fn make_messages_receiving_proof_transaction(
&self,
_best_block_id: SourceHeaderIdOf<Self::MessageLane>,
_transaction_nonce: IndexOf<Rococo>,
_generated_at_block: TargetHeaderIdOf<Self::MessageLane>,
_proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
) -> Bytes {
unreachable!()
}
fn target_transactions_author(&self) -> bp_wococo::AccountId {
unreachable!()
}
fn make_messages_delivery_transaction(
&self,
_best_block_id: TargetHeaderIdOf<Self::MessageLane>,
_transaction_nonce: IndexOf<Wococo>,
_generated_at_header: SourceHeaderIdOf<Self::MessageLane>,
_nonces: RangeInclusive<MessageNonce>,
_proof: <Self::MessageLane as MessageLane>::MessagesProof,
) -> Bytes {
unreachable!()
}
}
use relay_rococo_client::Rococo;
use relay_wococo_client::Wococo;
#[test]
fn prepare_dummy_messages_proof_works() {
@@ -556,11 +574,10 @@ mod tests {
#[test]
fn compute_prepaid_messages_refund_returns_sane_results() {
assert!(
compute_prepaid_messages_refund::<TestSubstrateMessageLane>(
compute_prepaid_messages_refund::<Wococo>(
10,
FixedU128::saturating_from_rational(110, 100),
) > (10 * TestSubstrateMessageLane::PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN)
.into()
) > (10 * Wococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_CHAIN).into()
);
}
}
@@ -16,31 +16,24 @@
//! On-demand Substrate -> Substrate headers relay.
use std::fmt::Debug;
use async_std::sync::{Arc, Mutex};
use futures::{select, FutureExt};
use num_traits::{CheckedSub, One, Zero};
use num_traits::{One, Zero};
use finality_relay::{
FinalitySyncParams, FinalitySyncPipeline, SourceClient as FinalitySourceClient, SourceHeader,
TargetClient as FinalityTargetClient,
};
use finality_relay::{FinalitySyncParams, SourceHeader, TargetClient as FinalityTargetClient};
use relay_substrate_client::{
finality_source::{FinalitySource as SubstrateFinalitySource, RequiredHeaderNumberRef},
Chain, Client, HeaderIdOf, SyncHeader,
AccountIdOf, AccountKeyPairOf, BlockNumberOf, Chain, Client, HeaderIdOf, HeaderOf, SyncHeader,
TransactionSignScheme,
};
use relay_utils::{
metrics::MetricsParams, relay_loop::Client as RelayClient, BlockNumberBase, FailedClient,
MaybeConnectionError,
metrics::MetricsParams, relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError,
};
use crate::{
finality_pipeline::{
SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate, RECENT_FINALITY_PROOFS_LIMIT,
},
finality_pipeline::{SubstrateFinalitySyncPipeline, RECENT_FINALITY_PROOFS_LIMIT},
finality_source::{RequiredHeaderNumberRef, SubstrateFinalitySource},
finality_target::SubstrateFinalityTarget,
STALL_TIMEOUT,
TransactionParams, STALL_TIMEOUT,
};
/// On-demand Substrate <-> Substrate headers relay.
@@ -58,41 +51,27 @@ pub struct OnDemandHeadersRelay<SourceChain: Chain> {
impl<SourceChain: Chain> OnDemandHeadersRelay<SourceChain> {
/// Create new on-demand headers relay.
pub fn new<TargetChain: Chain, TargetSign, P>(
source_client: Client<SourceChain>,
target_client: Client<TargetChain>,
target_transactions_mortality: Option<u32>,
pipeline: P,
maximal_headers_difference: SourceChain::BlockNumber,
pub fn new<P: SubstrateFinalitySyncPipeline<SourceChain = SourceChain>>(
source_client: Client<P::SourceChain>,
target_client: Client<P::TargetChain>,
target_transaction_params: TransactionParams<AccountKeyPairOf<P::TransactionSignScheme>>,
only_mandatory_headers: bool,
) -> Self
where
SourceChain: Chain + Debug,
SourceChain::BlockNumber: BlockNumberBase,
TargetChain: Chain + Debug,
TargetChain::BlockNumber: BlockNumberBase,
TargetSign: Clone + Send + Sync + 'static,
P: SubstrateFinalitySyncPipeline<
FinalitySyncPipeline = SubstrateFinalityToSubstrate<
SourceChain,
TargetChain,
TargetSign,
>,
TargetChain = TargetChain,
>,
AccountIdOf<P::TargetChain>:
From<<AccountKeyPairOf<P::TransactionSignScheme> as sp_core::Pair>::Public>,
P::TransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
{
let required_header_number = Arc::new(Mutex::new(Zero::zero()));
let this = OnDemandHeadersRelay {
relay_task_name: on_demand_headers_relay_name::<SourceChain, TargetChain>(),
relay_task_name: on_demand_headers_relay_name::<P::SourceChain, P::TargetChain>(),
required_header_number: required_header_number.clone(),
};
async_std::task::spawn(async move {
background_task(
background_task::<P>(
source_client,
target_client,
target_transactions_mortality,
pipeline,
maximal_headers_difference,
target_transaction_params,
only_mandatory_headers,
required_header_number,
)
@@ -120,35 +99,25 @@ impl<SourceChain: Chain> OnDemandHeadersRelay<SourceChain> {
}
/// Background task that is responsible for starting headers relay.
async fn background_task<SourceChain, TargetChain, TargetSign, P>(
source_client: Client<SourceChain>,
target_client: Client<TargetChain>,
target_transactions_mortality: Option<u32>,
pipeline: P,
maximal_headers_difference: SourceChain::BlockNumber,
async fn background_task<P: SubstrateFinalitySyncPipeline>(
source_client: Client<P::SourceChain>,
target_client: Client<P::TargetChain>,
target_transaction_params: TransactionParams<AccountKeyPairOf<P::TransactionSignScheme>>,
only_mandatory_headers: bool,
required_header_number: RequiredHeaderNumberRef<SourceChain>,
required_header_number: RequiredHeaderNumberRef<P::SourceChain>,
) where
SourceChain: Chain + Debug,
SourceChain::BlockNumber: BlockNumberBase,
TargetChain: Chain + Debug,
TargetChain::BlockNumber: BlockNumberBase,
TargetSign: Clone + Send + Sync + 'static,
P: SubstrateFinalitySyncPipeline<
FinalitySyncPipeline = SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>,
TargetChain = TargetChain,
>,
AccountIdOf<P::TargetChain>:
From<<AccountKeyPairOf<P::TransactionSignScheme> as sp_core::Pair>::Public>,
P::TransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
{
let relay_task_name = on_demand_headers_relay_name::<SourceChain, TargetChain>();
let mut finality_source = SubstrateFinalitySource::<
_,
SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>,
>::new(source_client.clone(), Some(required_header_number.clone()));
let mut finality_target = SubstrateFinalityTarget::new(
target_client.clone(),
pipeline.clone(),
target_transactions_mortality,
let relay_task_name = on_demand_headers_relay_name::<P::SourceChain, P::TargetChain>();
let target_transactions_mortality = target_transaction_params.mortality;
let mut finality_source = SubstrateFinalitySource::<P>::new(
source_client.clone(),
Some(required_header_number.clone()),
);
let mut finality_target =
SubstrateFinalityTarget::new(target_client.clone(), target_transaction_params);
let mut latest_non_mandatory_at_source = Zero::zero();
let mut restart_relay = true;
@@ -157,7 +126,7 @@ async fn background_task<SourceChain, TargetChain, TargetSign, P>(
loop {
select! {
_ = async_std::task::sleep(TargetChain::AVERAGE_BLOCK_INTERVAL).fuse() => {},
_ = async_std::task::sleep(P::TargetChain::AVERAGE_BLOCK_INTERVAL).fuse() => {},
_ = finality_relay_task => {
// this should never happen in practice given the current code
restart_relay = true;
@@ -179,12 +148,8 @@ async fn background_task<SourceChain, TargetChain, TargetSign, P>(
}
// read best finalized source header number from target
let best_finalized_source_header_at_target = best_finalized_source_header_at_target::<
SourceChain,
_,
_,
>(&finality_target, &relay_task_name)
.await;
let best_finalized_source_header_at_target =
best_finalized_source_header_at_target::<P>(&finality_target, &relay_task_name).await;
if matches!(best_finalized_source_header_at_target, Err(ref e) if e.is_connection_error()) {
relay_utils::relay_loop::reconnect_failed_client(
FailedClient::Target,
@@ -197,15 +162,28 @@ async fn background_task<SourceChain, TargetChain, TargetSign, P>(
}
// submit mandatory header if some headers are missing
let best_finalized_source_header_at_source_fmt =
format!("{:?}", best_finalized_source_header_at_source);
let best_finalized_source_header_at_target_fmt =
format!("{:?}", best_finalized_source_header_at_target);
let mandatory_scan_range = mandatory_headers_scan_range::<SourceChain>(
let required_header_number_value = *required_header_number.lock().await;
let mandatory_scan_range = mandatory_headers_scan_range::<P::SourceChain>(
best_finalized_source_header_at_source.ok(),
best_finalized_source_header_at_target.ok(),
maximal_headers_difference,
&required_header_number,
required_header_number_value,
)
.await;
log::trace!(
target: "bridge",
"Mandatory headers scan range in {}: ({:?}, {:?}, {:?}) -> {:?}",
relay_task_name,
required_header_number_value,
best_finalized_source_header_at_source_fmt,
best_finalized_source_header_at_target_fmt,
mandatory_scan_range,
);
if let Some(mandatory_scan_range) = mandatory_scan_range {
let relay_mandatory_header_result = relay_mandatory_header_from_range(
&finality_source,
@@ -224,8 +202,25 @@ async fn background_task<SourceChain, TargetChain, TargetSign, P>(
// there are no (or we don't need to relay them) mandatory headers in the range
// => to avoid scanning the same headers over and over again, remember that
latest_non_mandatory_at_source = mandatory_scan_range.1;
log::trace!(
target: "bridge",
"No mandatory {} headers in the range {:?} of {} relay",
P::SourceChain::NAME,
mandatory_scan_range,
relay_task_name,
);
},
Err(e) =>
Err(e) => {
log::warn!(
target: "bridge",
"Failed to scan mandatory {} headers range in {} relay (range: {:?}): {:?}",
P::SourceChain::NAME,
relay_task_name,
mandatory_scan_range,
e,
);
if e.is_connection_error() {
relay_utils::relay_loop::reconnect_failed_client(
FailedClient::Source,
@@ -235,23 +230,43 @@ async fn background_task<SourceChain, TargetChain, TargetSign, P>(
)
.await;
continue
},
}
},
}
}
// start/restart relay
if restart_relay {
let stall_timeout = relay_substrate_client::transaction_stall_timeout(
target_transactions_mortality,
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
);
log::info!(
target: "bridge",
"Starting {} relay\n\t\
Only mandatory headers: {}\n\t\
Tx mortality: {:?} (~{}m)\n\t\
Stall timeout: {:?}",
relay_task_name,
only_mandatory_headers,
target_transactions_mortality,
stall_timeout.as_secs_f64() / 60.0f64,
stall_timeout,
);
finality_relay_task.set(
finality_relay::run(
finality_source.clone(),
finality_target.clone(),
FinalitySyncParams {
tick: std::cmp::max(
SourceChain::AVERAGE_BLOCK_INTERVAL,
TargetChain::AVERAGE_BLOCK_INTERVAL,
P::SourceChain::AVERAGE_BLOCK_INTERVAL,
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
),
recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT,
stall_timeout: STALL_TIMEOUT,
stall_timeout,
only_mandatory_headers,
},
MetricsParams::disabled(),
@@ -270,11 +285,8 @@ async fn background_task<SourceChain, TargetChain, TargetSign, P>(
async fn mandatory_headers_scan_range<C: Chain>(
best_finalized_source_header_at_source: Option<C::BlockNumber>,
best_finalized_source_header_at_target: Option<C::BlockNumber>,
maximal_headers_difference: C::BlockNumber,
required_header_number: &RequiredHeaderNumberRef<C>,
required_header_number: BlockNumberOf<C>,
) -> Option<(C::BlockNumber, C::BlockNumber)> {
let required_header_number = *required_header_number.lock().await;
// if we have been unable to read header number from the target, then let's assume
// that it is the same as required header number. Otherwise we risk submitting
// unneeded transactions
@@ -286,23 +298,8 @@ async fn mandatory_headers_scan_range<C: Chain>(
let best_finalized_source_header_at_source =
best_finalized_source_header_at_source.unwrap_or(best_finalized_source_header_at_target);
// if there are too many source headers missing from the target node, sync mandatory
// headers to target
//
// why do we need that? When complex headers+messages relay is used, it'll normally only relay
// headers when there are undelivered messages/confirmations. But security model of the
// `pallet-bridge-grandpa` module relies on the fact that headers are synced in real-time and
// that it'll see authorities-change header before unbonding period will end for previous
// authorities set.
let current_headers_difference = best_finalized_source_header_at_source
.checked_sub(&best_finalized_source_header_at_target)
.unwrap_or_else(Zero::zero);
if current_headers_difference <= maximal_headers_difference {
return None
}
// if relay is already asked to sync headers, don't do anything yet
if required_header_number > best_finalized_source_header_at_target {
// if relay is already asked to sync more headers than we have at source, don't do anything yet
if required_header_number >= best_finalized_source_header_at_source {
return None
}
@@ -316,17 +313,13 @@ async fn mandatory_headers_scan_range<C: Chain>(
/// it.
///
/// Returns `true` if header was found and (asked to be) relayed and `false` otherwise.
async fn relay_mandatory_header_from_range<SourceChain: Chain, P>(
finality_source: &SubstrateFinalitySource<SourceChain, P>,
required_header_number: &RequiredHeaderNumberRef<SourceChain>,
async fn relay_mandatory_header_from_range<P: SubstrateFinalitySyncPipeline>(
finality_source: &SubstrateFinalitySource<P>,
required_header_number: &RequiredHeaderNumberRef<P::SourceChain>,
best_finalized_source_header_at_target: String,
range: (SourceChain::BlockNumber, SourceChain::BlockNumber),
range: (BlockNumberOf<P::SourceChain>, BlockNumberOf<P::SourceChain>),
relay_task_name: &str,
) -> Result<bool, relay_substrate_client::Error>
where
SubstrateFinalitySource<SourceChain, P>: FinalitySourceClient<P>,
P: FinalitySyncPipeline<Number = SourceChain::BlockNumber>,
{
) -> Result<bool, relay_substrate_client::Error> {
// search for mandatory header first
let mandatory_source_header_number =
find_mandatory_header_in_range(finality_source, range).await?;
@@ -347,7 +340,7 @@ where
log::trace!(
target: "bridge",
"Too many {} headers missing at target in {} relay ({} vs {}). Going to sync up to the mandatory {}",
SourceChain::NAME,
P::SourceChain::NAME,
relay_task_name,
best_finalized_source_header_at_target,
range.1,
@@ -361,14 +354,10 @@ where
/// Read best finalized source block number from source client.
///
/// Returns `None` if we have failed to read the number.
async fn best_finalized_source_header_at_source<SourceChain: Chain, P>(
finality_source: &SubstrateFinalitySource<SourceChain, P>,
async fn best_finalized_source_header_at_source<P: SubstrateFinalitySyncPipeline>(
finality_source: &SubstrateFinalitySource<P>,
relay_task_name: &str,
) -> Result<SourceChain::BlockNumber, relay_substrate_client::Error>
where
SubstrateFinalitySource<SourceChain, P>: FinalitySourceClient<P>,
P: FinalitySyncPipeline<Number = SourceChain::BlockNumber>,
{
) -> Result<BlockNumberOf<P::SourceChain>, relay_substrate_client::Error> {
finality_source.on_chain_best_finalized_block_number().await.map_err(|error| {
log::error!(
target: "bridge",
@@ -384,41 +373,41 @@ where
/// Read best finalized source block number from target client.
///
/// Returns `None` if we have failed to read the number.
async fn best_finalized_source_header_at_target<SourceChain: Chain, TargetChain: Chain, P>(
finality_target: &SubstrateFinalityTarget<TargetChain, P>,
async fn best_finalized_source_header_at_target<P: SubstrateFinalitySyncPipeline>(
finality_target: &SubstrateFinalityTarget<P>,
relay_task_name: &str,
) -> Result<SourceChain::BlockNumber, <SubstrateFinalityTarget<TargetChain, P> as RelayClient>::Error>
) -> Result<BlockNumberOf<P::SourceChain>, <SubstrateFinalityTarget<P> as RelayClient>::Error>
where
SubstrateFinalityTarget<TargetChain, P>: FinalityTargetClient<P::FinalitySyncPipeline>,
P: SubstrateFinalitySyncPipeline,
P::FinalitySyncPipeline: FinalitySyncPipeline<Number = SourceChain::BlockNumber>,
AccountIdOf<P::TargetChain>:
From<<AccountKeyPairOf<P::TransactionSignScheme> as sp_core::Pair>::Public>,
P::TransactionSignScheme: TransactionSignScheme<Chain = P::TargetChain>,
{
finality_target.best_finalized_source_block_number().await.map_err(|error| {
log::error!(
target: "bridge",
"Failed to read best finalized source header from target in {} relay: {:?}",
relay_task_name,
error,
);
finality_target
.best_finalized_source_block_id()
.await
.map_err(|error| {
log::error!(
target: "bridge",
"Failed to read best finalized source header from target in {} relay: {:?}",
relay_task_name,
error,
);
error
})
error
})
.map(|id| id.0)
}
/// Read first mandatory header in given inclusive range.
///
/// Returns `Ok(None)` if there were no mandatory headers in the range.
async fn find_mandatory_header_in_range<SourceChain: Chain, P>(
finality_source: &SubstrateFinalitySource<SourceChain, P>,
range: (SourceChain::BlockNumber, SourceChain::BlockNumber),
) -> Result<Option<SourceChain::BlockNumber>, relay_substrate_client::Error>
where
SubstrateFinalitySource<SourceChain, P>: FinalitySourceClient<P>,
P: FinalitySyncPipeline<Number = SourceChain::BlockNumber>,
{
async fn find_mandatory_header_in_range<P: SubstrateFinalitySyncPipeline>(
finality_source: &SubstrateFinalitySource<P>,
range: (BlockNumberOf<P::SourceChain>, BlockNumberOf<P::SourceChain>),
) -> Result<Option<BlockNumberOf<P::SourceChain>>, relay_substrate_client::Error> {
let mut current = range.0;
while current <= range.1 {
let header: SyncHeader<SourceChain::Header> =
let header: SyncHeader<HeaderOf<P::SourceChain>> =
finality_source.client().header_by_number(current).await?.into();
if header.is_mandatory() {
return Ok(Some(current))
@@ -445,29 +434,18 @@ mod tests {
const AT_TARGET: Option<bp_rococo::BlockNumber> = Some(1);
#[async_std::test]
async fn mandatory_headers_scan_range_selects_range_if_too_many_headers_are_missing() {
async fn mandatory_headers_scan_range_selects_range_if_some_headers_are_missing() {
assert_eq!(
mandatory_headers_scan_range::<TestChain>(
AT_SOURCE,
AT_TARGET,
5,
&Arc::new(Mutex::new(0))
)
.await,
mandatory_headers_scan_range::<TestChain>(AT_SOURCE, AT_TARGET, 0,).await,
Some((AT_TARGET.unwrap() + 1, AT_SOURCE.unwrap())),
);
}
#[async_std::test]
async fn mandatory_headers_scan_range_selects_nothing_if_enough_headers_are_relayed() {
async fn mandatory_headers_scan_range_selects_nothing_if_already_queued() {
assert_eq!(
mandatory_headers_scan_range::<TestChain>(
AT_SOURCE,
AT_TARGET,
10,
&Arc::new(Mutex::new(0))
)
.await,
mandatory_headers_scan_range::<TestChain>(AT_SOURCE, AT_TARGET, AT_SOURCE.unwrap(),)
.await,
None,
);
}
+2 -1
View File
@@ -2,7 +2,7 @@
name = "messages-relay"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
@@ -18,6 +18,7 @@ parking_lot = "0.11.0"
bp-messages = { path = "../../primitives/messages" }
bp-runtime = { path = "../../primitives/runtime" }
finality-relay = { path = "../finality" }
relay-utils = { path = "../utils" }
sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "master" }
@@ -233,6 +233,9 @@ pub struct ClientState<SelfHeaderId, PeerHeaderId> {
/// Best finalized header id of the peer chain read at the best block of this chain (at
/// `best_finalized_self`).
pub best_finalized_peer_at_best_self: PeerHeaderId,
/// Header id of the peer chain with the number, matching the
/// `best_finalized_peer_at_best_self`.
pub actual_best_finalized_peer_at_best_self: PeerHeaderId,
}
/// State of source client in one-way message lane.
@@ -843,12 +846,14 @@ pub(crate) mod tests {
best_self: HeaderId(0, 0),
best_finalized_self: HeaderId(0, 0),
best_finalized_peer_at_best_self: HeaderId(0, 0),
actual_best_finalized_peer_at_best_self: HeaderId(0, 0),
},
source_latest_generated_nonce: 1,
target_state: ClientState {
best_self: HeaderId(0, 0),
best_finalized_self: HeaderId(0, 0),
best_finalized_peer_at_best_self: HeaderId(0, 0),
actual_best_finalized_peer_at_best_self: HeaderId(0, 0),
},
target_latest_received_nonce: 0,
..Default::default()
@@ -888,12 +893,14 @@ pub(crate) mod tests {
best_self: HeaderId(10, 10),
best_finalized_self: HeaderId(10, 10),
best_finalized_peer_at_best_self: HeaderId(0, 0),
actual_best_finalized_peer_at_best_self: HeaderId(0, 0),
},
source_latest_generated_nonce: 10,
target_state: ClientState {
best_self: HeaderId(0, 0),
best_finalized_self: HeaderId(0, 0),
best_finalized_peer_at_best_self: HeaderId(0, 0),
actual_best_finalized_peer_at_best_self: HeaderId(0, 0),
},
target_latest_received_nonce: 0,
..Default::default()
+33 -20
View File
@@ -22,6 +22,7 @@ use crate::{
};
use bp_messages::MessageNonce;
use finality_relay::SyncLoopMetrics;
use relay_utils::metrics::{
metric_name, register, GaugeVec, Metric, Opts, PrometheusError, Registry, U64,
};
@@ -31,8 +32,10 @@ use relay_utils::metrics::{
/// Cloning only clones references.
#[derive(Clone)]
pub struct MessageLaneLoopMetrics {
/// Best finalized block numbers - "source", "source_at_target", "target_at_source".
source_to_target_finality_metrics: SyncLoopMetrics,
/// Best finalized block numbers - "source", "target", "source_at_target", "target_at_source".
best_block_numbers: GaugeVec<U64>,
target_to_source_finality_metrics: SyncLoopMetrics,
/// Lane state nonces: "source_latest_generated", "source_latest_confirmed",
/// "target_latest_received", "target_latest_confirmed".
lane_state_nonces: GaugeVec<U64>,
@@ -42,12 +45,15 @@ impl MessageLaneLoopMetrics {
/// Create and register messages loop metrics.
pub fn new(prefix: Option<&str>) -> Result<Self, PrometheusError> {
Ok(MessageLaneLoopMetrics {
best_block_numbers: GaugeVec::new(
Opts::new(
metric_name(prefix, "best_block_numbers"),
"Best finalized block numbers",
),
&["type"],
source_to_target_finality_metrics: SyncLoopMetrics::new(
prefix,
"source",
"source_at_target",
)?,
target_to_source_finality_metrics: SyncLoopMetrics::new(
prefix,
"target",
"target_at_source",
)?,
lane_state_nonces: GaugeVec::new(
Opts::new(metric_name(prefix, "lane_state_nonces"), "Nonces of the lane state"),
@@ -58,22 +64,28 @@ impl MessageLaneLoopMetrics {
/// Update source client state metrics.
pub fn update_source_state<P: MessageLane>(&self, source_client_state: SourceClientState<P>) {
self.best_block_numbers
.with_label_values(&["source"])
.set(source_client_state.best_self.0.into());
self.best_block_numbers
.with_label_values(&["target_at_source"])
.set(source_client_state.best_finalized_peer_at_best_self.0.into());
self.source_to_target_finality_metrics
.update_best_block_at_source(source_client_state.best_self.0.into());
self.target_to_source_finality_metrics.update_best_block_at_target(
source_client_state.best_finalized_peer_at_best_self.0.into(),
);
self.target_to_source_finality_metrics.update_using_same_fork(
source_client_state.best_finalized_peer_at_best_self.1 ==
source_client_state.actual_best_finalized_peer_at_best_self.1,
);
}
/// Update target client state metrics.
pub fn update_target_state<P: MessageLane>(&self, target_client_state: TargetClientState<P>) {
self.best_block_numbers
.with_label_values(&["target"])
.set(target_client_state.best_self.0.into());
self.best_block_numbers
.with_label_values(&["source_at_target"])
.set(target_client_state.best_finalized_peer_at_best_self.0.into());
self.target_to_source_finality_metrics
.update_best_block_at_source(target_client_state.best_self.0.into());
self.source_to_target_finality_metrics.update_best_block_at_target(
target_client_state.best_finalized_peer_at_best_self.0.into(),
);
self.source_to_target_finality_metrics.update_using_same_fork(
target_client_state.best_finalized_peer_at_best_self.1 ==
target_client_state.actual_best_finalized_peer_at_best_self.1,
);
}
/// Update latest generated nonce at source.
@@ -119,7 +131,8 @@ impl MessageLaneLoopMetrics {
impl Metric for MessageLaneLoopMetrics {
fn register(&self, registry: &Registry) -> Result<(), PrometheusError> {
register(self.best_block_numbers.clone(), registry)?;
self.source_to_target_finality_metrics.register(registry)?;
self.target_to_source_finality_metrics.register(registry)?;
register(self.lane_state_nonces.clone(), registry)?;
Ok(())
}
+3 -2
View File
@@ -2,7 +2,7 @@
name = "relay-utils"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
edition = "2021"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
@@ -19,7 +19,8 @@ log = "0.4.11"
num-traits = "0.2"
serde_json = "1.0"
sysinfo = "0.15"
time = "0.2"
time = { version = "0.3", features = ["formatting", "local-offset", "std"] }
tokio = { version = "1.8", features = ["rt"] }
thiserror = "1.0.26"
# Bridge dependencies
@@ -29,15 +29,21 @@ pub fn initialize_relay() {
/// Initialize Relay logger instance.
pub fn initialize_logger(with_timestamp: bool) {
let format = time::format_description::parse(
"[year]-[month]-[day] \
[hour repr:24]:[minute]:[second] [offset_hour sign:mandatory]",
)
.expect("static format string is valid");
let mut builder = env_logger::Builder::new();
builder.filter_level(log::LevelFilter::Warn);
builder.filter_module("bridge", log::LevelFilter::Info);
builder.parse_default_env();
if with_timestamp {
builder.format(move |buf, record| {
let timestamp = time::OffsetDateTime::try_now_local()
.unwrap_or_else(|_| time::OffsetDateTime::now_utc())
.format("%Y-%m-%d %H:%M:%S %z");
let timestamp = time::OffsetDateTime::now_local()
.unwrap_or_else(|_| time::OffsetDateTime::now_utc());
let timestamp = timestamp.format(&format).unwrap_or_else(|_| timestamp.to_string());
let log_level = color_level(record.level());
let log_target = color_target(record.target());
+3 -1
View File
@@ -18,7 +18,7 @@ pub use float_json_value::FloatJsonValueMetric;
pub use global::GlobalMetrics;
pub use substrate_prometheus_endpoint::{
prometheus::core::{Atomic, Collector},
register, Counter, CounterVec, Gauge, GaugeVec, Opts, PrometheusError, Registry, F64, U64,
register, Counter, CounterVec, Gauge, GaugeVec, Opts, PrometheusError, Registry, F64, I64, U64,
};
use async_std::sync::{Arc, RwLock};
@@ -30,6 +30,8 @@ mod global;
/// Shared reference to `f64` value that is updated by the metric.
pub type F64SharedRef = Arc<RwLock<Option<f64>>>;
/// Int gauge metric type.
pub type IntGauge = Gauge<U64>;
/// Unparsed address that needs to be used to expose Prometheus metrics.
#[derive(Debug, Clone)]
@@ -187,12 +187,32 @@ impl<SC, TC, LM> LoopMetrics<SC, TC, LM> {
let registry = self.registry;
async_std::task::spawn(async move {
let result = init_prometheus(socket_addr, registry).await;
log::trace!(
target: "bridge-metrics",
"Prometheus endpoint has exited with result: {:?}",
result,
);
let runtime =
match tokio::runtime::Builder::new_current_thread().enable_all().build() {
Ok(runtime) => runtime,
Err(err) => {
log::trace!(
target: "bridge-metrics",
"Failed to create tokio runtime. Prometheus meterics are not available: {:?}",
err,
);
return
},
};
let _ = runtime.block_on(async move {
log::trace!(
target: "bridge-metrics",
"Starting prometheus endpoint at: {:?}",
socket_addr,
);
let result = init_prometheus(socket_addr, registry).await;
log::trace!(
target: "bridge-metrics",
"Prometheus endpoint has exited with result: {:?}",
result,
);
});
});
}