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