fix: Complete snowbridge pezpallet rebrand and critical bug fixes
- snowbridge-pezpallet-* → pezsnowbridge-pezpallet-* (201 refs) - pallet/ directories → pezpallet/ (4 locations) - Fixed pezpallet.rs self-include recursion bug - Fixed sc-chain-spec hardcoded crate name in derive macro - Reverted .pezpallet_by_name() to .pallet_by_name() (subxt API) - Added BizinikiwiConfig type alias for zombienet tests - Deleted obsolete session state files Verified: pezsnowbridge-pezpallet-*, pezpallet-staking, pezpallet-staking-async, pezframe-benchmarking-cli all pass cargo check
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
[package]
|
||||
name = "bizinikiwi-relay-helper"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
repository.workspace = true
|
||||
publish = false
|
||||
description = "Bizinikiwi utility: relay helper"
|
||||
documentation = "https://docs.rs/bizinikiwi-relay-helper"
|
||||
homepage = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
async-std = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
codec = { workspace = true, default-features = true }
|
||||
futures = { workspace = true }
|
||||
hex = { workspace = true, default-features = true }
|
||||
num-traits = { workspace = true, default-features = true }
|
||||
rbtag = { workspace = true }
|
||||
strum = { features = ["derive"], workspace = true, default-features = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# Bridge dependencies
|
||||
bp-header-pez-chain = { workspace = true, default-features = true }
|
||||
bp-pezkuwi-core = { workspace = true, default-features = true }
|
||||
bp-relayers = { workspace = true, default-features = true }
|
||||
bp-teyrchains = { workspace = true, default-features = true }
|
||||
|
||||
pez-equivocation-detector = { workspace = true }
|
||||
pez-finality-relay = { workspace = true }
|
||||
pez-messages-relay = { workspace = true }
|
||||
relay-bizinikiwi-client = { workspace = true }
|
||||
relay-utils = { workspace = true }
|
||||
teyrchains-relay = { workspace = true }
|
||||
|
||||
pezpallet-bridge-grandpa = { workspace = true, default-features = true }
|
||||
pezpallet-bridge-messages = { workspace = true, default-features = true }
|
||||
pezpallet-bridge-teyrchains = { workspace = true, default-features = true }
|
||||
|
||||
bp-messages = { workspace = true, default-features = true }
|
||||
pezbp-runtime = { workspace = true, default-features = true }
|
||||
|
||||
# Bizinikiwi Dependencies
|
||||
pezframe-support = { workspace = true, default-features = true }
|
||||
pezframe-system = { workspace = true, default-features = true }
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezpallet-grandpa = { workspace = true, default-features = true }
|
||||
pezsp-consensus-grandpa = { workspace = true, default-features = true }
|
||||
pezsp-core = { workspace = true, default-features = true }
|
||||
pezsp-runtime = { workspace = true, default-features = true }
|
||||
pezsp-trie = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
relay-bizinikiwi-client = { features = ["test-helpers"], workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
|
||||
[features]
|
||||
runtime-benchmarks = [
|
||||
"bp-header-pez-chain/runtime-benchmarks",
|
||||
"bp-messages/runtime-benchmarks",
|
||||
"bp-pezkuwi-core/runtime-benchmarks",
|
||||
"bp-relayers/runtime-benchmarks",
|
||||
"pezbp-runtime/runtime-benchmarks",
|
||||
"bp-teyrchains/runtime-benchmarks",
|
||||
"pez-equivocation-detector/runtime-benchmarks",
|
||||
"pez-finality-relay/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pez-messages-relay/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-bridge-grandpa/runtime-benchmarks",
|
||||
"pezpallet-bridge-messages/runtime-benchmarks",
|
||||
"pezpallet-bridge-teyrchains/runtime-benchmarks",
|
||||
"pezpallet-grandpa/runtime-benchmarks",
|
||||
"relay-bizinikiwi-client/runtime-benchmarks",
|
||||
"relay-utils/runtime-benchmarks",
|
||||
"pezsp-consensus-grandpa/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-trie/runtime-benchmarks",
|
||||
"teyrchains-relay/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,114 @@
|
||||
// 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/>.
|
||||
|
||||
//! Basic traits for exposing bridges in the CLI.
|
||||
|
||||
use crate::{
|
||||
equivocation::BizinikiwiEquivocationDetectionPipeline,
|
||||
finality::BizinikiwiFinalitySyncPipeline,
|
||||
messages::{MessagesRelayLimits, BizinikiwiMessageLane},
|
||||
teyrchains::BizinikiwiTeyrchainsPipeline,
|
||||
};
|
||||
use bp_teyrchains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
|
||||
use relay_bizinikiwi_client::{
|
||||
Chain, ChainWithRuntimeVersion, ChainWithTransactions, RelayChain, Teyrchain,
|
||||
};
|
||||
|
||||
/// Minimal bridge representation that can be used from the CLI.
|
||||
/// It connects a source chain to a target chain.
|
||||
pub trait CliBridgeBase: Sized {
|
||||
/// The source chain.
|
||||
type Source: Chain + ChainWithRuntimeVersion;
|
||||
/// The target chain.
|
||||
type Target: ChainWithTransactions + ChainWithRuntimeVersion;
|
||||
}
|
||||
|
||||
/// Bridge representation that can be used from the CLI for relaying headers
|
||||
/// from a relay chain to a relay chain.
|
||||
pub trait RelayToRelayHeadersCliBridge: CliBridgeBase {
|
||||
/// Finality proofs synchronization pipeline.
|
||||
type Finality: BizinikiwiFinalitySyncPipeline<
|
||||
SourceChain = Self::Source,
|
||||
TargetChain = Self::Target,
|
||||
>;
|
||||
}
|
||||
|
||||
/// Convenience trait that adds bounds to `CliBridgeBase`.
|
||||
pub trait RelayToRelayEquivocationDetectionCliBridgeBase: CliBridgeBase {
|
||||
/// The source chain with extra bounds.
|
||||
type BoundedSource: ChainWithTransactions;
|
||||
}
|
||||
|
||||
impl<T> RelayToRelayEquivocationDetectionCliBridgeBase for T
|
||||
where
|
||||
T: CliBridgeBase,
|
||||
T::Source: ChainWithTransactions,
|
||||
{
|
||||
type BoundedSource = T::Source;
|
||||
}
|
||||
|
||||
/// Bridge representation that can be used from the CLI for detecting equivocations
|
||||
/// in the headers synchronized from a relay chain to a relay chain.
|
||||
pub trait RelayToRelayEquivocationDetectionCliBridge:
|
||||
RelayToRelayEquivocationDetectionCliBridgeBase
|
||||
{
|
||||
/// Equivocation detection pipeline.
|
||||
type Equivocation: BizinikiwiEquivocationDetectionPipeline<
|
||||
SourceChain = Self::Source,
|
||||
TargetChain = Self::Target,
|
||||
>;
|
||||
}
|
||||
|
||||
/// Bridge representation that can be used from the CLI for relaying headers
|
||||
/// from a teyrchain to a relay chain.
|
||||
pub trait TeyrchainToRelayHeadersCliBridge: CliBridgeBase
|
||||
where
|
||||
Self::Source: Teyrchain,
|
||||
{
|
||||
/// The `CliBridgeBase` type represents the teyrchain in this situation.
|
||||
/// We need to add an extra type for the relay chain.
|
||||
type SourceRelay: Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash, Hasher = RelayBlockHasher>
|
||||
+ ChainWithRuntimeVersion
|
||||
+ RelayChain;
|
||||
/// Finality proofs synchronization pipeline (source teyrchain -> target).
|
||||
type TeyrchainFinality: BizinikiwiTeyrchainsPipeline<
|
||||
SourceRelayChain = Self::SourceRelay,
|
||||
SourceTeyrchain = Self::Source,
|
||||
TargetChain = Self::Target,
|
||||
>;
|
||||
/// Finality proofs synchronization pipeline (source relay chain -> target).
|
||||
type RelayFinality: BizinikiwiFinalitySyncPipeline<
|
||||
SourceChain = Self::SourceRelay,
|
||||
TargetChain = Self::Target,
|
||||
>;
|
||||
}
|
||||
|
||||
/// Bridge representation that can be used from the CLI for relaying messages.
|
||||
pub trait MessagesCliBridge: CliBridgeBase {
|
||||
/// The Source -> Destination messages synchronization pipeline.
|
||||
type MessagesLane: BizinikiwiMessageLane<SourceChain = Self::Source, TargetChain = Self::Target>;
|
||||
|
||||
/// Optional messages delivery transaction limits that the messages relay is going
|
||||
/// to use. If it returns `None`, limits are estimated using `TransactionPayment` API
|
||||
/// at the target chain.
|
||||
fn maybe_messages_limits() -> Option<MessagesRelayLimits> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// An alias for lane identifier type.
|
||||
pub type MessagesLaneIdOf<B> =
|
||||
<<B as MessagesCliBridge>::MessagesLane as BizinikiwiMessageLane>::LaneId;
|
||||
@@ -0,0 +1,245 @@
|
||||
// Copyright 2019-2022 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/>.
|
||||
|
||||
//! Primitives related to chain CLI options.
|
||||
|
||||
use clap::Parser;
|
||||
use relay_bizinikiwi_client::{AccountKeyPairOf, ChainWithTransactions};
|
||||
use strum::{EnumString, VariantNames};
|
||||
|
||||
use relay_bizinikiwi_client::{ChainRuntimeVersion, ChainWithRuntimeVersion, SimpleRuntimeVersion};
|
||||
|
||||
use crate::TransactionParams;
|
||||
|
||||
#[doc = "Runtime version params."]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Parser, EnumString, VariantNames)]
|
||||
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 runtime version parameters.
|
||||
#[macro_export]
|
||||
macro_rules! declare_chain_runtime_version_params_cli_schema {
|
||||
($chain:ident, $chain_prefix:ident) => {
|
||||
pezbp_runtime::paste::item! {
|
||||
#[doc = $chain " runtime version params."]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Parser)]
|
||||
pub struct [<$chain RuntimeVersionParams>] {
|
||||
#[doc = "The type of runtime version for chain " $chain]
|
||||
#[arg(long, default_value = "Bundle")]
|
||||
pub [<$chain_prefix _version_mode>]: RuntimeVersionType,
|
||||
#[doc = "The custom sepc_version for chain " $chain]
|
||||
#[arg(long)]
|
||||
pub [<$chain_prefix _spec_version>]: Option<u32>,
|
||||
#[doc = "The custom transaction_version for chain " $chain]
|
||||
#[arg(long)]
|
||||
pub [<$chain_prefix _transaction_version>]: Option<u32>,
|
||||
}
|
||||
|
||||
impl [<$chain RuntimeVersionParams>] {
|
||||
/// Converts self into `ChainRuntimeVersion`.
|
||||
pub fn into_runtime_version(
|
||||
self,
|
||||
bundle_runtime_version: Option<SimpleRuntimeVersion>,
|
||||
) -> anyhow::Result<ChainRuntimeVersion> {
|
||||
Ok(match self.[<$chain_prefix _version_mode>] {
|
||||
RuntimeVersionType::Auto => ChainRuntimeVersion::Auto,
|
||||
RuntimeVersionType::Custom => {
|
||||
let custom_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 custom_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(
|
||||
SimpleRuntimeVersion {
|
||||
spec_version: custom_spec_version,
|
||||
transaction_version: custom_transaction_version
|
||||
}
|
||||
)
|
||||
},
|
||||
RuntimeVersionType::Bundle => match bundle_runtime_version {
|
||||
Some(runtime_version) => ChainRuntimeVersion::Custom(runtime_version),
|
||||
None => {
|
||||
return Err(anyhow::format_err!("Cannot use bundled runtime version of {}: it is not known to the relay", stringify!($chain_prefix)));
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Create chain-specific set of runtime version parameters.
|
||||
#[macro_export]
|
||||
macro_rules! declare_chain_connection_params_cli_schema {
|
||||
($chain:ident, $chain_prefix:ident) => {
|
||||
pezbp_runtime::paste::item! {
|
||||
// TODO: https://github.com/pezkuwichain/kurdistan-sdk/issues/86
|
||||
// remove all obsolete arguments (separate URI components)
|
||||
|
||||
#[doc = $chain " connection params."]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Parser)]
|
||||
pub struct [<$chain ConnectionParams>] {
|
||||
#[doc = "WS endpoint of " $chain ": full URI."]
|
||||
#[arg(long)]
|
||||
pub [<$chain_prefix _uri>]: String,
|
||||
#[doc = "Custom runtime version"]
|
||||
#[command(flatten)]
|
||||
pub [<$chain_prefix _runtime_version>]: [<$chain RuntimeVersionParams>],
|
||||
}
|
||||
|
||||
impl [<$chain ConnectionParams>] {
|
||||
/// Convert connection params into Bizinikiwi client.
|
||||
#[allow(dead_code)]
|
||||
pub async fn into_client<Chain: ChainWithRuntimeVersion>(
|
||||
self,
|
||||
) -> anyhow::Result<$crate::cli::DefaultClient<Chain>> {
|
||||
let chain_runtime_version = self
|
||||
.[<$chain_prefix _runtime_version>]
|
||||
.into_runtime_version(Chain::RUNTIME_VERSION)?;
|
||||
Ok(relay_bizinikiwi_client::new(relay_bizinikiwi_client::ConnectionParams {
|
||||
uri: self.[<$chain_prefix _uri>],
|
||||
chain_runtime_version,
|
||||
})
|
||||
.await
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Create chain-specific set of signing parameters.
|
||||
#[macro_export]
|
||||
macro_rules! declare_chain_signing_params_cli_schema {
|
||||
($chain:ident, $chain_prefix:ident) => {
|
||||
pezbp_runtime::paste::item! {
|
||||
#[doc = $chain " signing params."]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Parser)]
|
||||
pub struct [<$chain SigningParams>] {
|
||||
#[doc = "The SURI of secret key to use when transactions are submitted to the " $chain " node."]
|
||||
#[arg(long)]
|
||||
pub [<$chain_prefix _signer>]: Option<String>,
|
||||
#[doc = "The password for the SURI of secret key to use when transactions are submitted to the " $chain " node."]
|
||||
#[arg(long)]
|
||||
pub [<$chain_prefix _signer_password>]: Option<String>,
|
||||
|
||||
#[doc = "Path to the file, that contains SURI of secret key to use when transactions are submitted to the " $chain " node. Can be overridden with " $chain_prefix "_signer option."]
|
||||
#[arg(long)]
|
||||
pub [<$chain_prefix _signer_file>]: Option<std::path::PathBuf>,
|
||||
#[doc = "Path to the file, that password for the SURI of secret key to use when transactions are submitted to the " $chain " node. Can be overridden with " $chain_prefix "_signer_password option."]
|
||||
#[arg(long)]
|
||||
pub [<$chain_prefix _signer_password_file>]: Option<std::path::PathBuf>,
|
||||
|
||||
#[doc = "Transactions mortality period, in blocks. MUST be a power of two in [4; 65536] range. MAY NOT be larger than `BlockHashCount` parameter of the chain system module."]
|
||||
#[arg(long)]
|
||||
pub [<$chain_prefix _transactions_mortality>]: Option<u32>,
|
||||
}
|
||||
|
||||
impl [<$chain SigningParams>] {
|
||||
/// Return transactions mortality.
|
||||
#[allow(dead_code)]
|
||||
pub fn transactions_mortality(&self) -> anyhow::Result<Option<u32>> {
|
||||
self.[<$chain_prefix _transactions_mortality>]
|
||||
.map(|transactions_mortality| {
|
||||
if !(4..=65536).contains(&transactions_mortality)
|
||||
|| !transactions_mortality.is_power_of_two()
|
||||
{
|
||||
Err(anyhow::format_err!(
|
||||
"Transactions mortality {} is not a power of two in a [4; 65536] range",
|
||||
transactions_mortality,
|
||||
))
|
||||
} else {
|
||||
Ok(transactions_mortality)
|
||||
}
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Parse signing params into chain-specific KeyPair.
|
||||
#[allow(dead_code)]
|
||||
pub fn to_keypair<Chain: ChainWithTransactions>(&self) -> anyhow::Result<AccountKeyPairOf<Chain>> {
|
||||
let suri = match (self.[<$chain_prefix _signer>].as_ref(), self.[<$chain_prefix _signer_file>].as_ref()) {
|
||||
(Some(suri), _) => suri.to_owned(),
|
||||
(None, Some(suri_file)) => std::fs::read_to_string(suri_file)
|
||||
.map_err(|err| anyhow::format_err!(
|
||||
"Failed to read SURI from file {:?}: {}",
|
||||
suri_file,
|
||||
err,
|
||||
))?,
|
||||
(None, None) => return Err(anyhow::format_err!(
|
||||
"One of options must be specified: '{}' or '{}'",
|
||||
stringify!([<$chain_prefix _signer>]),
|
||||
stringify!([<$chain_prefix _signer_file>]),
|
||||
)),
|
||||
};
|
||||
|
||||
let suri_password = match (
|
||||
self.[<$chain_prefix _signer_password>].as_ref(),
|
||||
self.[<$chain_prefix _signer_password_file>].as_ref(),
|
||||
) {
|
||||
(Some(suri_password), _) => Some(suri_password.to_owned()),
|
||||
(None, Some(suri_password_file)) => std::fs::read_to_string(suri_password_file)
|
||||
.map(Some)
|
||||
.map_err(|err| anyhow::format_err!(
|
||||
"Failed to read SURI password from file {:?}: {}",
|
||||
suri_password_file,
|
||||
err,
|
||||
))?,
|
||||
_ => None,
|
||||
};
|
||||
|
||||
use pezsp_core::crypto::Pair;
|
||||
|
||||
AccountKeyPairOf::<Chain>::from_string(
|
||||
&suri,
|
||||
suri_password.as_deref()
|
||||
).map_err(|e| anyhow::format_err!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Return transaction parameters.
|
||||
#[allow(dead_code)]
|
||||
pub fn transaction_params<Chain: ChainWithTransactions>(
|
||||
&self,
|
||||
) -> anyhow::Result<TransactionParams<AccountKeyPairOf<Chain>>> {
|
||||
Ok(TransactionParams {
|
||||
mortality: self.transactions_mortality()?,
|
||||
signer: self.to_keypair::<Chain>()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Create chain-specific set of configuration objects: connection parameters,
|
||||
/// signing parameters and bridge initialization parameters.
|
||||
#[macro_export]
|
||||
macro_rules! declare_chain_cli_schema {
|
||||
($chain:ident, $chain_prefix:ident) => {
|
||||
$crate::declare_chain_runtime_version_params_cli_schema!($chain, $chain_prefix);
|
||||
$crate::declare_chain_connection_params_cli_schema!($chain, $chain_prefix);
|
||||
$crate::declare_chain_signing_params_cli_schema!($chain, $chain_prefix);
|
||||
};
|
||||
}
|
||||
|
||||
declare_chain_cli_schema!(Source, source);
|
||||
declare_chain_cli_schema!(Target, target);
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2019-2023 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/>.
|
||||
|
||||
//! Primitives for exposing the equivocation detection functionality in the CLI.
|
||||
|
||||
use crate::{
|
||||
cli::{bridge::*, chain_schema::*, PrometheusParams},
|
||||
equivocation,
|
||||
equivocation::BizinikiwiEquivocationDetectionPipeline,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use clap::Parser;
|
||||
use relay_bizinikiwi_client::{ChainWithTransactions, Client};
|
||||
|
||||
/// Start equivocation detection loop.
|
||||
#[derive(Parser)]
|
||||
pub struct DetectEquivocationsParams {
|
||||
#[command(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[command(flatten)]
|
||||
source_sign: SourceSigningParams,
|
||||
#[command(flatten)]
|
||||
target: TargetConnectionParams,
|
||||
#[command(flatten)]
|
||||
prometheus_params: PrometheusParams,
|
||||
}
|
||||
|
||||
/// Trait used for starting the equivocation detection loop between 2 chains.
|
||||
#[async_trait]
|
||||
pub trait EquivocationsDetector: RelayToRelayEquivocationDetectionCliBridge
|
||||
where
|
||||
Self::Source: ChainWithTransactions,
|
||||
{
|
||||
/// Start the equivocation detection loop.
|
||||
async fn start(data: DetectEquivocationsParams) -> anyhow::Result<()> {
|
||||
let source_client = data.source.into_client::<Self::Source>().await?;
|
||||
Self::Equivocation::start_relay_guards(
|
||||
&source_client,
|
||||
source_client.can_start_version_guard(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
equivocation::run::<Self::Equivocation>(
|
||||
source_client,
|
||||
data.target.into_client::<Self::Target>().await?,
|
||||
data.source_sign.transaction_params::<Self::Source>()?,
|
||||
data.prometheus_params.into_metrics_params()?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// 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/>.
|
||||
|
||||
//! Primitives for exposing the bridge initialization functionality in the CLI.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codec::Encode;
|
||||
|
||||
use crate::{
|
||||
cli::{bridge::CliBridgeBase, chain_schema::*},
|
||||
finality_base::engine::Engine,
|
||||
};
|
||||
use pezbp_runtime::Chain as ChainBase;
|
||||
use clap::Parser;
|
||||
use relay_bizinikiwi_client::{AccountKeyPairOf, Chain, UnsignedTransaction};
|
||||
use pezsp_core::Pair;
|
||||
|
||||
/// Bridge initialization params.
|
||||
#[derive(Parser)]
|
||||
pub struct InitBridgeParams {
|
||||
#[command(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[command(flatten)]
|
||||
target: TargetConnectionParams,
|
||||
#[command(flatten)]
|
||||
target_sign: TargetSigningParams,
|
||||
/// Generates all required data, but does not submit extrinsic
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
}
|
||||
|
||||
/// Trait used for bridge initializing.
|
||||
#[async_trait]
|
||||
pub trait BridgeInitializer: CliBridgeBase
|
||||
where
|
||||
<Self::Target as ChainBase>::AccountId: From<<AccountKeyPairOf<Self::Target> as Pair>::Public>,
|
||||
{
|
||||
/// The finality engine used by the source chain.
|
||||
type Engine: Engine<Self::Source>;
|
||||
|
||||
/// Get the encoded call to init the bridge.
|
||||
fn encode_init_bridge(
|
||||
init_data: <Self::Engine as Engine<Self::Source>>::InitializationData,
|
||||
) -> <Self::Target as Chain>::Call;
|
||||
|
||||
/// Initialize the bridge.
|
||||
async fn init_bridge(data: InitBridgeParams) -> anyhow::Result<()> {
|
||||
let source_client = data.source.into_client::<Self::Source>().await?;
|
||||
let target_client = data.target.into_client::<Self::Target>().await?;
|
||||
let target_sign = data.target_sign.to_keypair::<Self::Target>()?;
|
||||
let dry_run = data.dry_run;
|
||||
|
||||
crate::finality::initialize::initialize::<Self::Engine, _, _, _>(
|
||||
source_client,
|
||||
target_client.clone(),
|
||||
target_sign,
|
||||
move |transaction_nonce, initialization_data| {
|
||||
let call = Self::encode_init_bridge(initialization_data);
|
||||
tracing::info!(
|
||||
target: "bridge",
|
||||
hex_string=?format!("0x{}", hex::encode(call.encode())),
|
||||
"Initialize bridge call encoded"
|
||||
);
|
||||
Ok(UnsignedTransaction::new(call.into(), transaction_nonce))
|
||||
},
|
||||
dry_run,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Deal with CLI args of bizinikiwi-to-bizinikiwi relay.
|
||||
|
||||
use clap::Parser;
|
||||
use rbtag::BuildInfo;
|
||||
use pezsp_runtime::traits::TryConvert;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub mod bridge;
|
||||
pub mod chain_schema;
|
||||
pub mod detect_equivocations;
|
||||
pub mod init_bridge;
|
||||
pub mod relay_headers;
|
||||
pub mod relay_headers_and_messages;
|
||||
pub mod relay_messages;
|
||||
pub mod relay_teyrchains;
|
||||
|
||||
/// The target that will be used when publishing logs related to this pezpallet.
|
||||
pub const LOG_TARGET: &str = "bridge";
|
||||
|
||||
/// Default Bizinikiwi client type that we are using. We'll use it all over the glue CLI code
|
||||
/// to avoid multiple level generic arguments and constraints. We still allow usage of other
|
||||
/// clients in the **core logic code**.
|
||||
pub type DefaultClient<C> = relay_bizinikiwi_client::RpcWithCachingClient<C>;
|
||||
|
||||
/// Lane id.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct HexLaneId(Vec<u8>);
|
||||
|
||||
impl<T: TryFrom<Vec<u8>>> TryConvert<HexLaneId, T> for HexLaneId {
|
||||
fn try_convert(lane_id: HexLaneId) -> Result<T, HexLaneId> {
|
||||
T::try_from(lane_id.0.clone()).map_err(|_| lane_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for HexLaneId {
|
||||
type Err = hex::FromHexError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
hex::decode(s).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Prometheus metrics params.
|
||||
#[derive(Clone, Debug, PartialEq, Parser)]
|
||||
pub struct PrometheusParams {
|
||||
/// Do not expose a Prometheus metric endpoint.
|
||||
#[arg(long)]
|
||||
pub no_prometheus: bool,
|
||||
/// Expose Prometheus endpoint at given interface.
|
||||
#[arg(long, default_value = "127.0.0.1")]
|
||||
pub prometheus_host: String,
|
||||
/// Expose Prometheus endpoint at given port.
|
||||
#[arg(long, default_value = "9616")]
|
||||
pub prometheus_port: u16,
|
||||
}
|
||||
|
||||
/// Struct to get git commit info and build time.
|
||||
#[derive(BuildInfo)]
|
||||
struct BizinikiwiRelayBuildInfo;
|
||||
|
||||
impl BizinikiwiRelayBuildInfo {
|
||||
/// Get git commit in form `<short-sha-(clean|dirty)>`.
|
||||
pub fn get_git_commit() -> String {
|
||||
// on gitlab we use images without git installed, so we can't use `rbtag` there
|
||||
// locally we don't have `CI_*` env variables, so we can't rely on them
|
||||
// => we are using `CI_*` env variables or else `rbtag`
|
||||
let maybe_sha_from_ci = option_env!("CI_COMMIT_SHORT_SHA");
|
||||
maybe_sha_from_ci
|
||||
.map(|short_sha| {
|
||||
// we assume that on CI the copy is always clean
|
||||
format!("{short_sha}-clean")
|
||||
})
|
||||
.unwrap_or_else(|| BizinikiwiRelayBuildInfo.get_build_commit().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl PrometheusParams {
|
||||
/// Tries to convert CLI metrics params into metrics params, used by the relay.
|
||||
pub fn into_metrics_params(self) -> anyhow::Result<relay_utils::metrics::MetricsParams> {
|
||||
let metrics_address = if !self.no_prometheus {
|
||||
Some(relay_utils::metrics::MetricsAddress {
|
||||
host: self.prometheus_host,
|
||||
port: self.prometheus_port,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let relay_version = relay_utils::initialize::RELAYER_VERSION
|
||||
.lock()
|
||||
.clone()
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let relay_commit = BizinikiwiRelayBuildInfo::get_git_commit();
|
||||
relay_utils::metrics::MetricsParams::new(metrics_address, relay_version, relay_commit)
|
||||
.map_err(|e| anyhow::format_err!("{:?}", e))
|
||||
}
|
||||
}
|
||||
|
||||
/// Either explicit or maximal allowed value.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ExplicitOrMaximal<V> {
|
||||
/// User has explicitly specified argument value.
|
||||
Explicit(V),
|
||||
/// Maximal allowed value for this argument.
|
||||
Maximal,
|
||||
}
|
||||
|
||||
impl<V: std::str::FromStr> std::str::FromStr for ExplicitOrMaximal<V>
|
||||
where
|
||||
V::Err: std::fmt::Debug,
|
||||
{
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.to_lowercase() == "max" {
|
||||
return Ok(ExplicitOrMaximal::Maximal);
|
||||
}
|
||||
|
||||
V::from_str(s)
|
||||
.map(ExplicitOrMaximal::Explicit)
|
||||
.map_err(|e| format!("Failed to parse '{e:?}'. Expected 'max' or explicit value"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bp_messages::{HashedLaneId, LegacyLaneId};
|
||||
use pezsp_core::H256;
|
||||
|
||||
#[test]
|
||||
fn hex_lane_id_from_str_works() {
|
||||
// hash variant
|
||||
assert!(HexLaneId::from_str(
|
||||
"101010101010101010101010101010101010101010101010101010101010101"
|
||||
)
|
||||
.is_err());
|
||||
assert!(HexLaneId::from_str(
|
||||
"00101010101010101010101010101010101010101010101010101010101010101"
|
||||
)
|
||||
.is_err());
|
||||
assert_eq!(
|
||||
HexLaneId::try_convert(
|
||||
HexLaneId::from_str(
|
||||
"0101010101010101010101010101010101010101010101010101010101010101"
|
||||
)
|
||||
.unwrap()
|
||||
),
|
||||
Ok(HashedLaneId::from_inner(H256::from([1u8; 32])))
|
||||
);
|
||||
|
||||
// array variant
|
||||
assert!(HexLaneId::from_str("0000001").is_err());
|
||||
assert!(HexLaneId::from_str("000000001").is_err());
|
||||
assert_eq!(
|
||||
HexLaneId::try_convert(HexLaneId::from_str("00000001").unwrap()),
|
||||
Ok(LegacyLaneId([0, 0, 0, 1]))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// 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/>.
|
||||
|
||||
//! Primitives for exposing the headers relaying functionality in the CLI.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use clap::Parser;
|
||||
|
||||
use relay_utils::{
|
||||
metrics::{GlobalMetrics, StandaloneMetric},
|
||||
UniqueSaturatedInto,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
cli::{bridge::*, chain_schema::*, PrometheusParams},
|
||||
finality::BizinikiwiFinalitySyncPipeline,
|
||||
HeadersToRelay,
|
||||
};
|
||||
use relay_bizinikiwi_client::Client;
|
||||
|
||||
/// Chain headers relaying params.
|
||||
#[derive(Parser)]
|
||||
pub struct RelayHeadersParams {
|
||||
/// If passed, only mandatory headers (headers that are changing the GRANDPA authorities set)
|
||||
/// are relayed.
|
||||
#[arg(long)]
|
||||
only_mandatory_headers: bool,
|
||||
/// If passed, only free headers (mandatory and every Nth header, if configured in runtime)
|
||||
/// are relayed. Overrides `only_mandatory_headers`.
|
||||
#[arg(long)]
|
||||
only_free_headers: bool,
|
||||
#[command(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[command(flatten)]
|
||||
target: TargetConnectionParams,
|
||||
#[command(flatten)]
|
||||
target_sign: TargetSigningParams,
|
||||
#[command(flatten)]
|
||||
prometheus_params: PrometheusParams,
|
||||
}
|
||||
|
||||
/// Single header relaying params.
|
||||
#[derive(Parser)]
|
||||
pub struct RelayHeaderParams {
|
||||
#[command(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[command(flatten)]
|
||||
target: TargetConnectionParams,
|
||||
#[command(flatten)]
|
||||
target_sign: TargetSigningParams,
|
||||
/// Number of the source chain header that we want to relay. It must have a persistent
|
||||
/// storage proof at the [`Self::source`] node, otherwise the command will fail.
|
||||
#[arg(long)]
|
||||
number: u128,
|
||||
}
|
||||
|
||||
impl RelayHeadersParams {
|
||||
fn headers_to_relay(&self) -> HeadersToRelay {
|
||||
match (self.only_mandatory_headers, self.only_free_headers) {
|
||||
(_, true) => HeadersToRelay::Free,
|
||||
(true, false) => HeadersToRelay::Mandatory,
|
||||
_ => HeadersToRelay::All,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait used for relaying headers between 2 chains.
|
||||
#[async_trait]
|
||||
pub trait HeadersRelayer: RelayToRelayHeadersCliBridge {
|
||||
/// Relay headers.
|
||||
async fn relay_headers(data: RelayHeadersParams) -> anyhow::Result<()> {
|
||||
let headers_to_relay = data.headers_to_relay();
|
||||
let source_client = data.source.into_client::<Self::Source>().await?;
|
||||
let target_client = data.target.into_client::<Self::Target>().await?;
|
||||
let target_transactions_mortality = data.target_sign.target_transactions_mortality;
|
||||
let target_sign = data.target_sign.to_keypair::<Self::Target>()?;
|
||||
|
||||
let metrics_params: relay_utils::metrics::MetricsParams =
|
||||
data.prometheus_params.into_metrics_params()?;
|
||||
GlobalMetrics::new()?.register_and_spawn(&metrics_params.registry)?;
|
||||
|
||||
let target_transactions_params = crate::TransactionParams {
|
||||
signer: target_sign,
|
||||
mortality: target_transactions_mortality,
|
||||
};
|
||||
|
||||
Self::Finality::start_relay_guards(&target_client, target_client.can_start_version_guard())
|
||||
.await?;
|
||||
|
||||
crate::finality::run::<Self::Finality>(
|
||||
source_client,
|
||||
target_client,
|
||||
headers_to_relay,
|
||||
target_transactions_params,
|
||||
metrics_params,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Relay single header. No checks are made to ensure that transaction will succeed.
|
||||
async fn relay_header(data: RelayHeaderParams) -> anyhow::Result<()> {
|
||||
let source_client = data.source.into_client::<Self::Source>().await?;
|
||||
let target_client = data.target.into_client::<Self::Target>().await?;
|
||||
let target_transactions_mortality = data.target_sign.target_transactions_mortality;
|
||||
let target_sign = data.target_sign.to_keypair::<Self::Target>()?;
|
||||
|
||||
crate::finality::relay_single_header::<Self::Finality>(
|
||||
source_client,
|
||||
target_client,
|
||||
crate::TransactionParams {
|
||||
signer: target_sign,
|
||||
mortality: target_transactions_mortality,
|
||||
},
|
||||
data.number.unique_saturated_into(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Complex 2-ways headers+messages relays support.
|
||||
//!
|
||||
//! To add new complex relay between `ChainA` and `ChainB`, you must:
|
||||
//!
|
||||
//! 1) ensure that there's a `declare_chain_cli_schema!(...)` for both chains.
|
||||
//! 2) add `declare_chain_to_chain_bridge_schema!(...)` or
|
||||
//! `declare_chain_to_teyrchain_bridge_schema` for the bridge.
|
||||
//! 3) declare a new struct for the added bridge and implement the `Full2WayBridge` trait for it.
|
||||
|
||||
#[macro_use]
|
||||
pub mod teyrchain_to_teyrchain;
|
||||
#[macro_use]
|
||||
pub mod relay_to_relay;
|
||||
#[macro_use]
|
||||
pub mod relay_to_teyrchain;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use clap::Parser;
|
||||
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
||||
|
||||
use futures::{FutureExt, TryFutureExt};
|
||||
|
||||
use crate::{
|
||||
cli::{
|
||||
bridge::{MessagesCliBridge, MessagesLaneIdOf},
|
||||
DefaultClient, HexLaneId, PrometheusParams,
|
||||
},
|
||||
messages::{MessagesRelayLimits, MessagesRelayParams},
|
||||
on_demand::OnDemandRelay,
|
||||
HeadersToRelay, TaggedAccount, TransactionParams,
|
||||
};
|
||||
use pezbp_runtime::BalanceOf;
|
||||
use relay_bizinikiwi_client::{
|
||||
AccountIdOf, AccountKeyPairOf, Chain, ChainWithBalances, ChainWithMessages,
|
||||
ChainWithRuntimeVersion, ChainWithTransactions,
|
||||
};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use pezsp_core::Pair;
|
||||
use pezsp_runtime::traits::TryConvert;
|
||||
|
||||
/// Parameters that have the same names across all bridges.
|
||||
#[derive(Debug, PartialEq, Parser)]
|
||||
pub struct HeadersAndMessagesSharedParams {
|
||||
/// Hex-encoded lane identifiers that should be served by the complex relay.
|
||||
#[arg(long)]
|
||||
pub lane: Vec<HexLaneId>,
|
||||
/// If passed, only mandatory headers (headers that are changing the GRANDPA authorities set)
|
||||
/// are relayed.
|
||||
#[arg(long)]
|
||||
pub only_mandatory_headers: bool,
|
||||
/// If passed, only free headers (mandatory and every Nth header, if configured in runtime)
|
||||
/// are relayed. Overrides `only_mandatory_headers`.
|
||||
#[arg(long)]
|
||||
pub only_free_headers: bool,
|
||||
#[command(flatten)]
|
||||
/// Prometheus metrics params.
|
||||
pub prometheus_params: PrometheusParams,
|
||||
}
|
||||
|
||||
impl HeadersAndMessagesSharedParams {
|
||||
fn headers_to_relay(&self) -> HeadersToRelay {
|
||||
match (self.only_mandatory_headers, self.only_free_headers) {
|
||||
(_, true) => HeadersToRelay::Free,
|
||||
(true, false) => HeadersToRelay::Mandatory,
|
||||
_ => HeadersToRelay::All,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bridge parameters, shared by all bridge types.
|
||||
pub struct Full2WayBridgeCommonParams<
|
||||
Left: ChainWithTransactions + ChainWithRuntimeVersion,
|
||||
Right: ChainWithTransactions + ChainWithRuntimeVersion,
|
||||
> {
|
||||
/// Shared parameters.
|
||||
pub shared: HeadersAndMessagesSharedParams,
|
||||
/// Parameters of the left chain.
|
||||
pub left: BridgeEndCommonParams<Left>,
|
||||
/// Parameters of the right chain.
|
||||
pub right: BridgeEndCommonParams<Right>,
|
||||
|
||||
/// Common metric parameters.
|
||||
pub metrics_params: MetricsParams,
|
||||
}
|
||||
|
||||
impl<
|
||||
Left: ChainWithTransactions + ChainWithRuntimeVersion,
|
||||
Right: ChainWithTransactions + ChainWithRuntimeVersion,
|
||||
> Full2WayBridgeCommonParams<Left, Right>
|
||||
{
|
||||
/// Creates new bridge parameters from its components.
|
||||
pub fn new<L2R: MessagesCliBridge<Source = Left, Target = Right>>(
|
||||
shared: HeadersAndMessagesSharedParams,
|
||||
left: BridgeEndCommonParams<Left>,
|
||||
right: BridgeEndCommonParams<Right>,
|
||||
) -> anyhow::Result<Self> {
|
||||
// Create metrics registry.
|
||||
let metrics_params = shared.prometheus_params.clone().into_metrics_params()?;
|
||||
let metrics_params = relay_utils::relay_metrics(metrics_params).into_params();
|
||||
|
||||
Ok(Self { shared, left, right, metrics_params })
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameters that are associated with one side of the bridge.
|
||||
pub struct BridgeEndCommonParams<Chain: ChainWithTransactions + ChainWithRuntimeVersion> {
|
||||
/// Chain client.
|
||||
pub client: DefaultClient<Chain>,
|
||||
/// Params used for sending transactions to the chain.
|
||||
pub tx_params: TransactionParams<AccountKeyPairOf<Chain>>,
|
||||
/// Accounts, which balances are exposed as metrics by the relay process.
|
||||
pub accounts: Vec<TaggedAccount<AccountIdOf<Chain>>>,
|
||||
}
|
||||
|
||||
/// All data of the bidirectional complex relay.
|
||||
pub struct FullBridge<
|
||||
'a,
|
||||
Source: ChainWithTransactions + ChainWithRuntimeVersion,
|
||||
Target: ChainWithTransactions + ChainWithRuntimeVersion,
|
||||
Bridge: MessagesCliBridge<Source = Source, Target = Target>,
|
||||
> {
|
||||
source: &'a mut BridgeEndCommonParams<Source>,
|
||||
target: &'a mut BridgeEndCommonParams<Target>,
|
||||
metrics_params: &'a MetricsParams,
|
||||
_phantom_data: PhantomData<Bridge>,
|
||||
}
|
||||
|
||||
impl<
|
||||
'a,
|
||||
Source: ChainWithTransactions + ChainWithRuntimeVersion,
|
||||
Target: ChainWithTransactions + ChainWithRuntimeVersion,
|
||||
Bridge: MessagesCliBridge<Source = Source, Target = Target>,
|
||||
> FullBridge<'a, Source, Target, Bridge>
|
||||
where
|
||||
AccountIdOf<Source>: From<<AccountKeyPairOf<Source> as Pair>::Public>,
|
||||
AccountIdOf<Target>: From<<AccountKeyPairOf<Target> as Pair>::Public>,
|
||||
BalanceOf<Source>: TryFrom<BalanceOf<Target>> + Into<u128>,
|
||||
{
|
||||
/// Construct complex relay given it components.
|
||||
fn new(
|
||||
source: &'a mut BridgeEndCommonParams<Source>,
|
||||
target: &'a mut BridgeEndCommonParams<Target>,
|
||||
metrics_params: &'a MetricsParams,
|
||||
) -> Self {
|
||||
Self { source, target, metrics_params, _phantom_data: Default::default() }
|
||||
}
|
||||
|
||||
/// Returns message relay parameters.
|
||||
fn pez_messages_relay_params(
|
||||
&self,
|
||||
source_to_target_headers_relay: Arc<dyn OnDemandRelay<Source, Target>>,
|
||||
target_to_source_headers_relay: Arc<dyn OnDemandRelay<Target, Source>>,
|
||||
lane_id: MessagesLaneIdOf<Bridge>,
|
||||
maybe_limits: Option<MessagesRelayLimits>,
|
||||
) -> MessagesRelayParams<Bridge::MessagesLane, DefaultClient<Source>, DefaultClient<Target>> {
|
||||
MessagesRelayParams {
|
||||
source_client: self.source.client.clone(),
|
||||
source_transaction_params: self.source.tx_params.clone(),
|
||||
target_client: self.target.client.clone(),
|
||||
target_transaction_params: self.target.tx_params.clone(),
|
||||
source_to_target_headers_relay: Some(source_to_target_headers_relay),
|
||||
target_to_source_headers_relay: Some(target_to_source_headers_relay),
|
||||
lane_id,
|
||||
limits: maybe_limits,
|
||||
metrics_params: self.metrics_params.clone().disable(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Base portion of the bidirectional complex relay.
|
||||
///
|
||||
/// This main purpose of extracting this trait is that in different relays the implementation
|
||||
/// of `start_on_demand_headers_relayers` method will be different. But the number of
|
||||
/// implementations is limited to relay <> relay, teyrchain <> relay and teyrchain <> teyrchain.
|
||||
/// This trait allows us to reuse these implementations in different bridges.
|
||||
#[async_trait]
|
||||
pub trait Full2WayBridgeBase: Sized + Send + Sync {
|
||||
/// The CLI params for the bridge.
|
||||
type Params;
|
||||
/// The left relay chain.
|
||||
type Left: ChainWithTransactions + ChainWithRuntimeVersion;
|
||||
/// The right destination chain (it can be a relay or a teyrchain).
|
||||
type Right: ChainWithTransactions + ChainWithRuntimeVersion;
|
||||
|
||||
/// Reference to common relay parameters.
|
||||
fn common(&self) -> &Full2WayBridgeCommonParams<Self::Left, Self::Right>;
|
||||
|
||||
/// Mutable reference to common relay parameters.
|
||||
fn mut_common(&mut self) -> &mut Full2WayBridgeCommonParams<Self::Left, Self::Right>;
|
||||
|
||||
/// Start on-demand headers relays.
|
||||
async fn start_on_demand_headers_relayers(
|
||||
&mut self,
|
||||
) -> anyhow::Result<(
|
||||
Arc<dyn OnDemandRelay<Self::Left, Self::Right>>,
|
||||
Arc<dyn OnDemandRelay<Self::Right, Self::Left>>,
|
||||
)>;
|
||||
}
|
||||
|
||||
/// Bidirectional complex relay.
|
||||
#[async_trait]
|
||||
pub trait Full2WayBridge: Sized + Sync
|
||||
where
|
||||
AccountIdOf<Self::Left>: From<<AccountKeyPairOf<Self::Left> as Pair>::Public>,
|
||||
AccountIdOf<Self::Right>: From<<AccountKeyPairOf<Self::Right> as Pair>::Public>,
|
||||
BalanceOf<Self::Left>: TryFrom<BalanceOf<Self::Right>> + Into<u128>,
|
||||
BalanceOf<Self::Right>: TryFrom<BalanceOf<Self::Left>> + Into<u128>,
|
||||
{
|
||||
/// Base portion of the bidirectional complex relay.
|
||||
type Base: Full2WayBridgeBase<Left = Self::Left, Right = Self::Right>;
|
||||
|
||||
/// The left relay chain.
|
||||
type Left: ChainWithTransactions
|
||||
+ ChainWithBalances
|
||||
+ ChainWithMessages
|
||||
+ ChainWithRuntimeVersion;
|
||||
/// The right relay chain.
|
||||
type Right: ChainWithTransactions
|
||||
+ ChainWithBalances
|
||||
+ ChainWithMessages
|
||||
+ ChainWithRuntimeVersion;
|
||||
|
||||
/// Left to Right bridge.
|
||||
type L2R: MessagesCliBridge<Source = Self::Left, Target = Self::Right>;
|
||||
/// Right to Left bridge
|
||||
type R2L: MessagesCliBridge<Source = Self::Right, Target = Self::Left>;
|
||||
|
||||
/// Construct new bridge.
|
||||
fn new(params: <Self::Base as Full2WayBridgeBase>::Params) -> anyhow::Result<Self>;
|
||||
|
||||
/// Reference to the base relay portion.
|
||||
fn base(&self) -> &Self::Base;
|
||||
|
||||
/// Mutable reference to the base relay portion.
|
||||
fn mut_base(&mut self) -> &mut Self::Base;
|
||||
|
||||
/// Creates and returns Left to Right complex relay.
|
||||
fn left_to_right(&mut self) -> FullBridge<'_, Self::Left, Self::Right, Self::L2R> {
|
||||
let common = self.mut_base().mut_common();
|
||||
FullBridge::<_, _, Self::L2R>::new(
|
||||
&mut common.left,
|
||||
&mut common.right,
|
||||
&common.metrics_params,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates and returns Right to Left complex relay.
|
||||
fn right_to_left(&mut self) -> FullBridge<'_, Self::Right, Self::Left, Self::R2L> {
|
||||
let common = self.mut_base().mut_common();
|
||||
FullBridge::<_, _, Self::R2L>::new(
|
||||
&mut common.right,
|
||||
&mut common.left,
|
||||
&common.metrics_params,
|
||||
)
|
||||
}
|
||||
|
||||
/// Start complex relay.
|
||||
async fn run(&mut self) -> anyhow::Result<()> {
|
||||
// Register standalone metrics.
|
||||
{
|
||||
let common = self.mut_base().mut_common();
|
||||
common.left.accounts.push(TaggedAccount::Messages {
|
||||
id: common.left.tx_params.signer.public().into(),
|
||||
bridged_chain: Self::Right::NAME.to_string(),
|
||||
});
|
||||
common.right.accounts.push(TaggedAccount::Messages {
|
||||
id: common.right.tx_params.signer.public().into(),
|
||||
bridged_chain: Self::Left::NAME.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// start on-demand header relays
|
||||
let (left_to_right_on_demand_headers, right_to_left_on_demand_headers) =
|
||||
self.mut_base().start_on_demand_headers_relayers().await?;
|
||||
|
||||
// add balance-related metrics
|
||||
let lanes_l2r: Vec<MessagesLaneIdOf<Self::L2R>> = self
|
||||
.base()
|
||||
.common()
|
||||
.shared
|
||||
.lane
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(HexLaneId::try_convert)
|
||||
.collect::<Result<Vec<_>, HexLaneId>>()
|
||||
.map_err(|e| {
|
||||
anyhow::format_err!("Conversion failed for L2R lanes with error: {:?}!", e)
|
||||
})?;
|
||||
let lanes_r2l: Vec<MessagesLaneIdOf<Self::R2L>> = self
|
||||
.base()
|
||||
.common()
|
||||
.shared
|
||||
.lane
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(HexLaneId::try_convert)
|
||||
.collect::<Result<Vec<_>, HexLaneId>>()
|
||||
.map_err(|e| {
|
||||
anyhow::format_err!("Conversion failed for R2L lanes with error: {:?}!", e)
|
||||
})?;
|
||||
{
|
||||
let common = self.mut_base().mut_common();
|
||||
crate::messages::metrics::add_relay_balances_metrics::<_>(
|
||||
common.left.client.clone(),
|
||||
&common.metrics_params,
|
||||
&common.left.accounts,
|
||||
)
|
||||
.await?;
|
||||
crate::messages::metrics::add_relay_balances_metrics::<_>(
|
||||
common.right.client.clone(),
|
||||
&common.metrics_params,
|
||||
&common.right.accounts,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Need 2x capacity since we consider both directions for each lane
|
||||
let mut message_relays =
|
||||
Vec::with_capacity(lanes_l2r.len().saturating_add(lanes_r2l.len()));
|
||||
for lane in lanes_l2r {
|
||||
let left_to_right_messages =
|
||||
crate::messages::run::<<Self::L2R as MessagesCliBridge>::MessagesLane, _, _>(
|
||||
self.left_to_right().pez_messages_relay_params(
|
||||
left_to_right_on_demand_headers.clone(),
|
||||
right_to_left_on_demand_headers.clone(),
|
||||
lane,
|
||||
Self::L2R::maybe_messages_limits(),
|
||||
),
|
||||
)
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
.boxed();
|
||||
message_relays.push(left_to_right_messages);
|
||||
}
|
||||
for lane in lanes_r2l {
|
||||
let right_to_left_messages =
|
||||
crate::messages::run::<<Self::R2L as MessagesCliBridge>::MessagesLane, _, _>(
|
||||
self.right_to_left().pez_messages_relay_params(
|
||||
right_to_left_on_demand_headers.clone(),
|
||||
left_to_right_on_demand_headers.clone(),
|
||||
lane,
|
||||
Self::R2L::maybe_messages_limits(),
|
||||
),
|
||||
)
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
.boxed();
|
||||
message_relays.push(right_to_left_messages);
|
||||
}
|
||||
|
||||
relay_utils::relay_metrics(self.base().common().metrics_params.clone())
|
||||
.expose()
|
||||
.await
|
||||
.map_err(|e| anyhow::format_err!("{}", e))?;
|
||||
|
||||
futures::future::select_all(message_relays).await.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{cli::chain_schema::RuntimeVersionType, declare_chain_cli_schema};
|
||||
|
||||
use relay_bizinikiwi_client::{ChainRuntimeVersion, SimpleRuntimeVersion, Teyrchain};
|
||||
|
||||
#[test]
|
||||
// We need `#[allow(dead_code)]` because some of the methods generated by the macros
|
||||
// are not used.
|
||||
#[allow(dead_code)]
|
||||
fn should_parse_teyrchain_to_teyrchain_options() {
|
||||
// Chains.
|
||||
declare_chain_cli_schema!(Kusama, kusama);
|
||||
declare_chain_cli_schema!(BridgeHubKusama, bridge_hub_kusama);
|
||||
declare_chain_cli_schema!(Pezkuwi, pezkuwi);
|
||||
declare_chain_cli_schema!(BridgeHubPezkuwi, bridge_hub_pezkuwi);
|
||||
// Means to override signers of different layer transactions.
|
||||
declare_chain_cli_schema!(
|
||||
KusamaHeadersToBridgeHubPezkuwi,
|
||||
kusama_headers_to_bridge_hub_pezkuwi
|
||||
);
|
||||
declare_chain_cli_schema!(
|
||||
KusamaTeyrchainsToBridgeHubPezkuwi,
|
||||
kusama_teyrchains_to_bridge_hub_pezkuwi
|
||||
);
|
||||
declare_chain_cli_schema!(
|
||||
PezkuwiHeadersToBridgeHubKusama,
|
||||
pezkuwi_headers_to_bridge_hub_kusama
|
||||
);
|
||||
declare_chain_cli_schema!(
|
||||
PezkuwiTeyrchainsToBridgeHubKusama,
|
||||
pezkuwi_teyrchains_to_bridge_hub_kusama
|
||||
);
|
||||
// Bridges.
|
||||
declare_teyrchain_to_teyrchain_bridge_schema!(
|
||||
BridgeHubKusama,
|
||||
Kusama,
|
||||
BridgeHubPezkuwi,
|
||||
Pezkuwi
|
||||
);
|
||||
|
||||
let res = BridgeHubKusamaBridgeHubPezkuwiHeadersAndMessages::parse_from(vec![
|
||||
"bridge-hub-kusama-bridge-hub-pezkuwi-headers-and-messages",
|
||||
"--bridge-hub-kusama-uri",
|
||||
"ws://bridge-hub-zagros-collator1:9944",
|
||||
"--bridge-hub-kusama-signer",
|
||||
"//Iden",
|
||||
"--bridge-hub-kusama-transactions-mortality",
|
||||
"64",
|
||||
"--kusama-uri",
|
||||
"ws://zagros-alice:9944",
|
||||
"--bridge-hub-pezkuwi-uri",
|
||||
"ws://bridge-hub-pezkuwichain-collator1:9944",
|
||||
"--bridge-hub-pezkuwi-signer",
|
||||
"//George",
|
||||
"--bridge-hub-pezkuwi-transactions-mortality",
|
||||
"64",
|
||||
"--pezkuwi-uri",
|
||||
"ws://pezkuwichain-alice:9944",
|
||||
"--lane",
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"--prometheus-host",
|
||||
"0.0.0.0",
|
||||
]);
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
res,
|
||||
BridgeHubKusamaBridgeHubPezkuwiHeadersAndMessages {
|
||||
shared: HeadersAndMessagesSharedParams {
|
||||
lane: vec![HexLaneId(vec![0x00u8; 32])],
|
||||
only_mandatory_headers: false,
|
||||
only_free_headers: false,
|
||||
prometheus_params: PrometheusParams {
|
||||
no_prometheus: false,
|
||||
prometheus_host: "0.0.0.0".into(),
|
||||
prometheus_port: 9616,
|
||||
},
|
||||
},
|
||||
left: BridgeHubKusamaConnectionParams {
|
||||
bridge_hub_kusama_uri: "ws://bridge-hub-zagros-collator1:9944".into(),
|
||||
bridge_hub_kusama_runtime_version: BridgeHubKusamaRuntimeVersionParams {
|
||||
bridge_hub_kusama_version_mode: RuntimeVersionType::Bundle,
|
||||
bridge_hub_kusama_spec_version: None,
|
||||
bridge_hub_kusama_transaction_version: None,
|
||||
},
|
||||
},
|
||||
left_sign: BridgeHubKusamaSigningParams {
|
||||
bridge_hub_kusama_signer: Some("//Iden".into()),
|
||||
bridge_hub_kusama_signer_password: None,
|
||||
bridge_hub_kusama_signer_file: None,
|
||||
bridge_hub_kusama_signer_password_file: None,
|
||||
bridge_hub_kusama_transactions_mortality: Some(64),
|
||||
},
|
||||
left_relay: KusamaConnectionParams {
|
||||
kusama_uri: "ws://zagros-alice:9944".into(),
|
||||
kusama_runtime_version: KusamaRuntimeVersionParams {
|
||||
kusama_version_mode: RuntimeVersionType::Bundle,
|
||||
kusama_spec_version: None,
|
||||
kusama_transaction_version: None,
|
||||
},
|
||||
},
|
||||
right: BridgeHubPezkuwiConnectionParams {
|
||||
bridge_hub_pezkuwi_uri: "ws://bridge-hub-pezkuwichain-collator1:9944".into(),
|
||||
bridge_hub_pezkuwi_runtime_version: BridgeHubPezkuwiRuntimeVersionParams {
|
||||
bridge_hub_pezkuwi_version_mode: RuntimeVersionType::Bundle,
|
||||
bridge_hub_pezkuwi_spec_version: None,
|
||||
bridge_hub_pezkuwi_transaction_version: None,
|
||||
},
|
||||
},
|
||||
right_sign: BridgeHubPezkuwiSigningParams {
|
||||
bridge_hub_pezkuwi_signer: Some("//George".into()),
|
||||
bridge_hub_pezkuwi_signer_password: None,
|
||||
bridge_hub_pezkuwi_signer_file: None,
|
||||
bridge_hub_pezkuwi_signer_password_file: None,
|
||||
bridge_hub_pezkuwi_transactions_mortality: Some(64),
|
||||
},
|
||||
right_relay: PezkuwiConnectionParams {
|
||||
pezkuwi_uri: "ws://pezkuwichain-alice:9944".into(),
|
||||
pezkuwi_runtime_version: PezkuwiRuntimeVersionParams {
|
||||
pezkuwi_version_mode: RuntimeVersionType::Bundle,
|
||||
pezkuwi_spec_version: None,
|
||||
pezkuwi_transaction_version: None,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
// Copyright 2019-2022 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/>.
|
||||
|
||||
// we don't have any relay/standalone <> relay/standalone chain bridges, but we may need it in a
|
||||
// future
|
||||
#![allow(unused_macros)]
|
||||
|
||||
//! Relay chain to Relay chain relayer CLI primitives.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
cli::{
|
||||
bridge::{CliBridgeBase, MessagesCliBridge, RelayToRelayHeadersCliBridge},
|
||||
relay_headers_and_messages::{Full2WayBridgeBase, Full2WayBridgeCommonParams},
|
||||
},
|
||||
finality::BizinikiwiFinalitySyncPipeline,
|
||||
on_demand::{headers::OnDemandHeadersRelay, OnDemandRelay},
|
||||
};
|
||||
use relay_bizinikiwi_client::{
|
||||
AccountIdOf, AccountKeyPairOf, ChainWithRuntimeVersion, ChainWithTransactions, Client,
|
||||
};
|
||||
use pezsp_core::Pair;
|
||||
|
||||
/// A base relay between two standalone (relay) chains.
|
||||
///
|
||||
/// Such relay starts 2 messages relay and 2 on-demand header relays.
|
||||
pub struct RelayToRelayBridge<
|
||||
L2R: MessagesCliBridge + RelayToRelayHeadersCliBridge,
|
||||
R2L: MessagesCliBridge + RelayToRelayHeadersCliBridge,
|
||||
> {
|
||||
/// Parameters that are shared by all bridge types.
|
||||
pub common:
|
||||
Full2WayBridgeCommonParams<<R2L as CliBridgeBase>::Target, <L2R as CliBridgeBase>::Target>,
|
||||
}
|
||||
|
||||
/// Create set of configuration objects specific to relay-to-relay relayer.
|
||||
macro_rules! declare_relay_to_relay_bridge_schema {
|
||||
($left_chain:ident, $right_chain:ident) => {
|
||||
pezbp_runtime::paste::item! {
|
||||
#[doc = $left_chain " and " $right_chain " headers+messages relay params."]
|
||||
#[derive(Debug, PartialEq, Parser)]
|
||||
pub struct [<$left_chain $right_chain HeadersAndMessages>] {
|
||||
#[command(flatten)]
|
||||
shared: HeadersAndMessagesSharedParams,
|
||||
|
||||
#[command(flatten)]
|
||||
left: [<$left_chain ConnectionParams>],
|
||||
// default signer, which is always used to sign messages relay transactions on the left chain
|
||||
#[command(flatten)]
|
||||
left_sign: [<$left_chain SigningParams>],
|
||||
|
||||
#[command(flatten)]
|
||||
right: [<$right_chain ConnectionParams>],
|
||||
#[command(flatten)]
|
||||
// default signer, which is always used to sign messages relay transactions on the right chain
|
||||
right_sign: [<$right_chain SigningParams>],
|
||||
}
|
||||
|
||||
impl [<$left_chain $right_chain HeadersAndMessages>] {
|
||||
async fn into_bridge<
|
||||
Left: ChainWithTransactions + CliChain,
|
||||
Right: ChainWithTransactions + CliChain,
|
||||
L2R: CliBridgeBase<Source = Left, Target = Right> + MessagesCliBridge + RelayToRelayHeadersCliBridge,
|
||||
R2L: CliBridgeBase<Source = Right, Target = Left> + MessagesCliBridge + RelayToRelayHeadersCliBridge,
|
||||
>(
|
||||
self,
|
||||
) -> anyhow::Result<RelayToRelayBridge<L2R, R2L>> {
|
||||
Ok(RelayToRelayBridge {
|
||||
common: Full2WayBridgeCommonParams::new::<L2R>(
|
||||
self.shared,
|
||||
BridgeEndCommonParams {
|
||||
client: self.left.into_client::<Left>().await?,
|
||||
tx_params: self.left_sign.transaction_params::<Left>()?,
|
||||
accounts: vec![],
|
||||
},
|
||||
BridgeEndCommonParams {
|
||||
client: self.right.into_client::<Right>().await?,
|
||||
tx_params: self.right_sign.transaction_params::<Right>()?,
|
||||
accounts: vec![],
|
||||
},
|
||||
)?,
|
||||
right_to_left_transaction_params: self.left_sign.transaction_params::<Left>(),
|
||||
left_to_right_transaction_params: self.right_sign.transaction_params::<Right>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<
|
||||
Left: ChainWithTransactions + ChainWithRuntimeVersion,
|
||||
Right: ChainWithTransactions + ChainWithRuntimeVersion,
|
||||
L2R: CliBridgeBase<Source = Left, Target = Right>
|
||||
+ MessagesCliBridge
|
||||
+ RelayToRelayHeadersCliBridge,
|
||||
R2L: CliBridgeBase<Source = Right, Target = Left>
|
||||
+ MessagesCliBridge
|
||||
+ RelayToRelayHeadersCliBridge,
|
||||
> Full2WayBridgeBase for RelayToRelayBridge<L2R, R2L>
|
||||
where
|
||||
AccountIdOf<Left>: From<<AccountKeyPairOf<Left> as Pair>::Public>,
|
||||
AccountIdOf<Right>: From<<AccountKeyPairOf<Right> as Pair>::Public>,
|
||||
{
|
||||
type Params = RelayToRelayBridge<L2R, R2L>;
|
||||
type Left = Left;
|
||||
type Right = Right;
|
||||
|
||||
fn common(&self) -> &Full2WayBridgeCommonParams<Left, Right> {
|
||||
&self.common
|
||||
}
|
||||
|
||||
fn mut_common(&mut self) -> &mut Full2WayBridgeCommonParams<Self::Left, Self::Right> {
|
||||
&mut self.common
|
||||
}
|
||||
|
||||
async fn start_on_demand_headers_relayers(
|
||||
&mut self,
|
||||
) -> anyhow::Result<(
|
||||
Arc<dyn OnDemandRelay<Self::Left, Self::Right>>,
|
||||
Arc<dyn OnDemandRelay<Self::Right, Self::Left>>,
|
||||
)> {
|
||||
<L2R as RelayToRelayHeadersCliBridge>::Finality::start_relay_guards(
|
||||
&self.common.right.client,
|
||||
self.common.right.client.can_start_version_guard(),
|
||||
)
|
||||
.await?;
|
||||
<R2L as RelayToRelayHeadersCliBridge>::Finality::start_relay_guards(
|
||||
&self.common.left.client,
|
||||
self.common.left.client.can_start_version_guard(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let left_to_right_on_demand_headers =
|
||||
OnDemandHeadersRelay::<<L2R as RelayToRelayHeadersCliBridge>::Finality, _, _>::new(
|
||||
self.common.left.client.clone(),
|
||||
self.common.right.client.clone(),
|
||||
self.common.right.tx_params.clone(),
|
||||
self.common.shared.headers_to_relay(),
|
||||
None,
|
||||
);
|
||||
let right_to_left_on_demand_headers =
|
||||
OnDemandHeadersRelay::<<R2L as RelayToRelayHeadersCliBridge>::Finality, _, _>::new(
|
||||
self.common.right.client.clone(),
|
||||
self.common.left.client.clone(),
|
||||
self.common.left.tx_params.clone(),
|
||||
self.common.shared.headers_to_relay(),
|
||||
None,
|
||||
);
|
||||
|
||||
Ok((Arc::new(left_to_right_on_demand_headers), Arc::new(right_to_left_on_demand_headers)))
|
||||
}
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
// Copyright 2019-2022 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/>.
|
||||
|
||||
//! Relay chain to teyrchain relayer CLI primitives.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
cli::{
|
||||
bridge::{
|
||||
CliBridgeBase, MessagesCliBridge, RelayToRelayHeadersCliBridge,
|
||||
TeyrchainToRelayHeadersCliBridge,
|
||||
},
|
||||
relay_headers_and_messages::{Full2WayBridgeBase, Full2WayBridgeCommonParams},
|
||||
DefaultClient,
|
||||
},
|
||||
finality::BizinikiwiFinalitySyncPipeline,
|
||||
on_demand::{
|
||||
headers::OnDemandHeadersRelay, teyrchains::OnDemandTeyrchainsRelay, OnDemandRelay,
|
||||
},
|
||||
};
|
||||
use bp_pezkuwi_core::teyrchains::ParaHash;
|
||||
use bp_teyrchains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
|
||||
use relay_bizinikiwi_client::{
|
||||
AccountIdOf, AccountKeyPairOf, Chain, ChainWithRuntimeVersion, ChainWithTransactions, Client,
|
||||
Teyrchain,
|
||||
};
|
||||
use pezsp_core::Pair;
|
||||
|
||||
/// A base relay between standalone (relay) chain and a teyrchain from another consensus system.
|
||||
///
|
||||
/// Such relay starts 2 messages relay. It also starts 2 on-demand header relays and 1 on-demand
|
||||
/// teyrchain heads relay.
|
||||
pub struct RelayToTeyrchainBridge<
|
||||
L2R: MessagesCliBridge + RelayToRelayHeadersCliBridge,
|
||||
R2L: MessagesCliBridge + TeyrchainToRelayHeadersCliBridge,
|
||||
> where
|
||||
<R2L as CliBridgeBase>::Source: Teyrchain,
|
||||
{
|
||||
/// Parameters that are shared by all bridge types.
|
||||
pub common:
|
||||
Full2WayBridgeCommonParams<<R2L as CliBridgeBase>::Target, <L2R as CliBridgeBase>::Target>,
|
||||
/// Client of the right relay chain.
|
||||
pub right_relay: DefaultClient<<R2L as TeyrchainToRelayHeadersCliBridge>::SourceRelay>,
|
||||
}
|
||||
|
||||
/// Create set of configuration objects specific to relay-to-teyrchain relayer.
|
||||
#[macro_export]
|
||||
macro_rules! declare_relay_to_teyrchain_bridge_schema {
|
||||
// chain, teyrchain, relay-chain-of-teyrchain
|
||||
($left_chain:ident, $right_teyrchain:ident, $right_chain:ident) => {
|
||||
pezbp_runtime::paste::item! {
|
||||
#[doc = $left_chain ", " $right_teyrchain " and " $right_chain " headers+teyrchains+messages relay params."]
|
||||
#[derive(Debug, PartialEq, Parser)]
|
||||
pub struct [<$left_chain $right_teyrchain HeadersAndMessages>] {
|
||||
// shared parameters
|
||||
#[command(flatten)]
|
||||
shared: HeadersAndMessagesSharedParams,
|
||||
|
||||
#[command(flatten)]
|
||||
left: [<$left_chain ConnectionParams>],
|
||||
// default signer, which is always used to sign messages relay transactions on the left chain
|
||||
#[command(flatten)]
|
||||
left_sign: [<$left_chain SigningParams>],
|
||||
|
||||
#[command(flatten)]
|
||||
right: [<$right_teyrchain ConnectionParams>],
|
||||
// default signer, which is always used to sign messages relay transactions on the right chain
|
||||
#[command(flatten)]
|
||||
right_sign: [<$right_teyrchain SigningParams>],
|
||||
|
||||
#[command(flatten)]
|
||||
right_relay: [<$right_chain ConnectionParams>],
|
||||
}
|
||||
|
||||
impl [<$left_chain $right_teyrchain HeadersAndMessages>] {
|
||||
async fn into_bridge<
|
||||
Left: ChainWithTransactions + ChainWithRuntimeVersion,
|
||||
Right: ChainWithTransactions + ChainWithRuntimeVersion + Teyrchain,
|
||||
RightRelay: ChainWithRuntimeVersion,
|
||||
L2R: CliBridgeBase<Source = Left, Target = Right> + MessagesCliBridge + RelayToRelayHeadersCliBridge,
|
||||
R2L: CliBridgeBase<Source = Right, Target = Left>
|
||||
+ MessagesCliBridge
|
||||
+ TeyrchainToRelayHeadersCliBridge<SourceRelay = RightRelay>,
|
||||
>(
|
||||
self,
|
||||
) -> anyhow::Result<RelayToTeyrchainBridge<L2R, R2L>> {
|
||||
Ok(RelayToTeyrchainBridge {
|
||||
common: Full2WayBridgeCommonParams::new::<L2R>(
|
||||
self.shared,
|
||||
BridgeEndCommonParams {
|
||||
client: self.left.into_client::<Left>().await?,
|
||||
tx_params: self.left_sign.transaction_params::<Left>()?,
|
||||
accounts: vec![],
|
||||
},
|
||||
BridgeEndCommonParams {
|
||||
client: self.right.into_client::<Right>().await?,
|
||||
tx_params: self.right_sign.transaction_params::<Right>()?,
|
||||
accounts: vec![],
|
||||
},
|
||||
)?,
|
||||
right_relay: self.right_relay.into_client::<RightRelay>().await?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<
|
||||
Left: ChainWithTransactions + ChainWithRuntimeVersion,
|
||||
Right: Chain<Hash = ParaHash> + ChainWithTransactions + ChainWithRuntimeVersion + Teyrchain,
|
||||
RightRelay: Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash, Hasher = RelayBlockHasher>
|
||||
+ ChainWithRuntimeVersion,
|
||||
L2R: CliBridgeBase<Source = Left, Target = Right>
|
||||
+ MessagesCliBridge
|
||||
+ RelayToRelayHeadersCliBridge,
|
||||
R2L: CliBridgeBase<Source = Right, Target = Left>
|
||||
+ MessagesCliBridge
|
||||
+ TeyrchainToRelayHeadersCliBridge<SourceRelay = RightRelay>,
|
||||
> Full2WayBridgeBase for RelayToTeyrchainBridge<L2R, R2L>
|
||||
where
|
||||
AccountIdOf<Left>: From<<AccountKeyPairOf<Left> as Pair>::Public>,
|
||||
AccountIdOf<Right>: From<<AccountKeyPairOf<Right> as Pair>::Public>,
|
||||
{
|
||||
type Params = RelayToTeyrchainBridge<L2R, R2L>;
|
||||
type Left = Left;
|
||||
type Right = Right;
|
||||
|
||||
fn common(&self) -> &Full2WayBridgeCommonParams<Left, Right> {
|
||||
&self.common
|
||||
}
|
||||
|
||||
fn mut_common(&mut self) -> &mut Full2WayBridgeCommonParams<Self::Left, Self::Right> {
|
||||
&mut self.common
|
||||
}
|
||||
|
||||
async fn start_on_demand_headers_relayers(
|
||||
&mut self,
|
||||
) -> anyhow::Result<(
|
||||
Arc<dyn OnDemandRelay<Self::Left, Self::Right>>,
|
||||
Arc<dyn OnDemandRelay<Self::Right, Self::Left>>,
|
||||
)> {
|
||||
<L2R as RelayToRelayHeadersCliBridge>::Finality::start_relay_guards(
|
||||
&self.common.right.client,
|
||||
self.common.right.client.can_start_version_guard(),
|
||||
)
|
||||
.await?;
|
||||
<R2L as TeyrchainToRelayHeadersCliBridge>::RelayFinality::start_relay_guards(
|
||||
&self.common.left.client,
|
||||
self.common.left.client.can_start_version_guard(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let left_to_right_on_demand_headers =
|
||||
OnDemandHeadersRelay::<<L2R as RelayToRelayHeadersCliBridge>::Finality, _, _>::new(
|
||||
self.common.left.client.clone(),
|
||||
self.common.right.client.clone(),
|
||||
self.common.right.tx_params.clone(),
|
||||
self.common.shared.headers_to_relay(),
|
||||
None,
|
||||
);
|
||||
let right_relay_to_left_on_demand_headers = OnDemandHeadersRelay::<
|
||||
<R2L as TeyrchainToRelayHeadersCliBridge>::RelayFinality,
|
||||
_,
|
||||
_,
|
||||
>::new(
|
||||
self.right_relay.clone(),
|
||||
self.common.left.client.clone(),
|
||||
self.common.left.tx_params.clone(),
|
||||
self.common.shared.headers_to_relay(),
|
||||
Some(self.common.metrics_params.clone()),
|
||||
);
|
||||
let right_to_left_on_demand_teyrchains = OnDemandTeyrchainsRelay::<
|
||||
<R2L as TeyrchainToRelayHeadersCliBridge>::TeyrchainFinality,
|
||||
_,
|
||||
_,
|
||||
>::new(
|
||||
self.right_relay.clone(),
|
||||
self.common.left.client.clone(),
|
||||
self.common.left.tx_params.clone(),
|
||||
Arc::new(right_relay_to_left_on_demand_headers),
|
||||
);
|
||||
|
||||
Ok((
|
||||
Arc::new(left_to_right_on_demand_headers),
|
||||
Arc::new(right_to_left_on_demand_teyrchains),
|
||||
))
|
||||
}
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
// Copyright 2019-2022 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/>.
|
||||
|
||||
//! Teyrchain to teyrchain relayer CLI primitives.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
cli::{
|
||||
bridge::{CliBridgeBase, MessagesCliBridge, TeyrchainToRelayHeadersCliBridge},
|
||||
relay_headers_and_messages::{Full2WayBridgeBase, Full2WayBridgeCommonParams},
|
||||
DefaultClient,
|
||||
},
|
||||
finality::BizinikiwiFinalitySyncPipeline,
|
||||
on_demand::{
|
||||
headers::OnDemandHeadersRelay, teyrchains::OnDemandTeyrchainsRelay, OnDemandRelay,
|
||||
},
|
||||
};
|
||||
use bp_pezkuwi_core::teyrchains::ParaHash;
|
||||
use bp_teyrchains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
|
||||
use relay_bizinikiwi_client::{
|
||||
AccountIdOf, AccountKeyPairOf, Chain, ChainWithRuntimeVersion, ChainWithTransactions, Client,
|
||||
Teyrchain,
|
||||
};
|
||||
use pezsp_core::Pair;
|
||||
|
||||
/// A base relay between two teyrchain from different consensus systems.
|
||||
///
|
||||
/// Such relay starts 2 messages relay. It also starts 2 on-demand header relays and 2 on-demand
|
||||
/// teyrchain heads relay.
|
||||
pub struct TeyrchainToTeyrchainBridge<
|
||||
L2R: MessagesCliBridge + TeyrchainToRelayHeadersCliBridge,
|
||||
R2L: MessagesCliBridge + TeyrchainToRelayHeadersCliBridge,
|
||||
> where
|
||||
<L2R as CliBridgeBase>::Source: Teyrchain,
|
||||
<R2L as CliBridgeBase>::Source: Teyrchain,
|
||||
{
|
||||
/// Parameters that are shared by all bridge types.
|
||||
pub common:
|
||||
Full2WayBridgeCommonParams<<R2L as CliBridgeBase>::Target, <L2R as CliBridgeBase>::Target>,
|
||||
/// Client of the left relay chain.
|
||||
pub left_relay: DefaultClient<<L2R as TeyrchainToRelayHeadersCliBridge>::SourceRelay>,
|
||||
/// Client of the right relay chain.
|
||||
pub right_relay: DefaultClient<<R2L as TeyrchainToRelayHeadersCliBridge>::SourceRelay>,
|
||||
}
|
||||
|
||||
/// Create set of configuration objects specific to teyrchain-to-teyrchain relayer.
|
||||
#[macro_export]
|
||||
macro_rules! declare_teyrchain_to_teyrchain_bridge_schema {
|
||||
// left-teyrchain, relay-chain-of-left-teyrchain, right-teyrchain, relay-chain-of-right-teyrchain
|
||||
($left_teyrchain:ident, $left_chain:ident, $right_teyrchain:ident, $right_chain:ident) => {
|
||||
pezbp_runtime::paste::item! {
|
||||
#[doc = $left_teyrchain ", " $left_chain ", " $right_teyrchain " and " $right_chain " headers+teyrchains+messages relay params."]
|
||||
#[derive(Debug, PartialEq, Parser)]
|
||||
pub struct [<$left_teyrchain $right_teyrchain HeadersAndMessages>] {
|
||||
// shared parameters
|
||||
#[command(flatten)]
|
||||
shared: HeadersAndMessagesSharedParams,
|
||||
|
||||
#[command(flatten)]
|
||||
left: [<$left_teyrchain ConnectionParams>],
|
||||
// default signer, which is always used to sign messages relay transactions on the left chain
|
||||
#[command(flatten)]
|
||||
left_sign: [<$left_teyrchain SigningParams>],
|
||||
|
||||
#[command(flatten)]
|
||||
left_relay: [<$left_chain ConnectionParams>],
|
||||
|
||||
#[command(flatten)]
|
||||
right: [<$right_teyrchain ConnectionParams>],
|
||||
// default signer, which is always used to sign messages relay transactions on the right chain
|
||||
#[command(flatten)]
|
||||
right_sign: [<$right_teyrchain SigningParams>],
|
||||
|
||||
#[command(flatten)]
|
||||
right_relay: [<$right_chain ConnectionParams>],
|
||||
}
|
||||
|
||||
impl [<$left_teyrchain $right_teyrchain HeadersAndMessages>] {
|
||||
async fn into_bridge<
|
||||
Left: ChainWithTransactions + ChainWithRuntimeVersion + Teyrchain,
|
||||
LeftRelay: ChainWithRuntimeVersion,
|
||||
Right: ChainWithTransactions + ChainWithRuntimeVersion + Teyrchain,
|
||||
RightRelay: ChainWithRuntimeVersion,
|
||||
L2R: $crate::cli::bridge::CliBridgeBase<Source = Left, Target = Right>
|
||||
+ MessagesCliBridge
|
||||
+ $crate::cli::bridge::TeyrchainToRelayHeadersCliBridge<SourceRelay = LeftRelay>,
|
||||
R2L: $crate::cli::bridge::CliBridgeBase<Source = Right, Target = Left>
|
||||
+ MessagesCliBridge
|
||||
+ $crate::cli::bridge::TeyrchainToRelayHeadersCliBridge<SourceRelay = RightRelay>,
|
||||
>(
|
||||
self,
|
||||
) -> anyhow::Result<$crate::cli::relay_headers_and_messages::teyrchain_to_teyrchain::TeyrchainToTeyrchainBridge<L2R, R2L>> {
|
||||
Ok($crate::cli::relay_headers_and_messages::teyrchain_to_teyrchain::TeyrchainToTeyrchainBridge {
|
||||
common: Full2WayBridgeCommonParams::new::<L2R>(
|
||||
self.shared,
|
||||
BridgeEndCommonParams {
|
||||
client: self.left.into_client::<Left>().await?,
|
||||
tx_params: self.left_sign.transaction_params::<Left>()?,
|
||||
accounts: vec![],
|
||||
},
|
||||
BridgeEndCommonParams {
|
||||
client: self.right.into_client::<Right>().await?,
|
||||
tx_params: self.right_sign.transaction_params::<Right>()?,
|
||||
accounts: vec![],
|
||||
},
|
||||
)?,
|
||||
left_relay: self.left_relay.into_client::<LeftRelay>().await?,
|
||||
right_relay: self.right_relay.into_client::<RightRelay>().await?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<
|
||||
Left: Chain<Hash = ParaHash> + ChainWithTransactions + ChainWithRuntimeVersion + Teyrchain,
|
||||
Right: Chain<Hash = ParaHash> + ChainWithTransactions + ChainWithRuntimeVersion + Teyrchain,
|
||||
LeftRelay: Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash, Hasher = RelayBlockHasher>
|
||||
+ ChainWithRuntimeVersion,
|
||||
RightRelay: Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash, Hasher = RelayBlockHasher>
|
||||
+ ChainWithRuntimeVersion,
|
||||
L2R: CliBridgeBase<Source = Left, Target = Right>
|
||||
+ MessagesCliBridge
|
||||
+ TeyrchainToRelayHeadersCliBridge<SourceRelay = LeftRelay>,
|
||||
R2L: CliBridgeBase<Source = Right, Target = Left>
|
||||
+ MessagesCliBridge
|
||||
+ TeyrchainToRelayHeadersCliBridge<SourceRelay = RightRelay>,
|
||||
> Full2WayBridgeBase for TeyrchainToTeyrchainBridge<L2R, R2L>
|
||||
where
|
||||
AccountIdOf<Left>: From<<AccountKeyPairOf<Left> as Pair>::Public>,
|
||||
AccountIdOf<Right>: From<<AccountKeyPairOf<Right> as Pair>::Public>,
|
||||
{
|
||||
type Params = TeyrchainToTeyrchainBridge<L2R, R2L>;
|
||||
type Left = Left;
|
||||
type Right = Right;
|
||||
|
||||
fn common(&self) -> &Full2WayBridgeCommonParams<Left, Right> {
|
||||
&self.common
|
||||
}
|
||||
|
||||
fn mut_common(&mut self) -> &mut Full2WayBridgeCommonParams<Self::Left, Self::Right> {
|
||||
&mut self.common
|
||||
}
|
||||
|
||||
async fn start_on_demand_headers_relayers(
|
||||
&mut self,
|
||||
) -> anyhow::Result<(
|
||||
Arc<dyn OnDemandRelay<Self::Left, Self::Right>>,
|
||||
Arc<dyn OnDemandRelay<Self::Right, Self::Left>>,
|
||||
)> {
|
||||
<L2R as TeyrchainToRelayHeadersCliBridge>::RelayFinality::start_relay_guards(
|
||||
&self.common.right.client,
|
||||
self.common.right.client.can_start_version_guard(),
|
||||
)
|
||||
.await?;
|
||||
<R2L as TeyrchainToRelayHeadersCliBridge>::RelayFinality::start_relay_guards(
|
||||
&self.common.left.client,
|
||||
self.common.left.client.can_start_version_guard(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let left_relay_to_right_on_demand_headers = OnDemandHeadersRelay::<
|
||||
<L2R as TeyrchainToRelayHeadersCliBridge>::RelayFinality,
|
||||
_,
|
||||
_,
|
||||
>::new(
|
||||
self.left_relay.clone(),
|
||||
self.common.right.client.clone(),
|
||||
self.common.right.tx_params.clone(),
|
||||
self.common.shared.headers_to_relay(),
|
||||
Some(self.common.metrics_params.clone()),
|
||||
);
|
||||
let right_relay_to_left_on_demand_headers = OnDemandHeadersRelay::<
|
||||
<R2L as TeyrchainToRelayHeadersCliBridge>::RelayFinality,
|
||||
_,
|
||||
_,
|
||||
>::new(
|
||||
self.right_relay.clone(),
|
||||
self.common.left.client.clone(),
|
||||
self.common.left.tx_params.clone(),
|
||||
self.common.shared.headers_to_relay(),
|
||||
Some(self.common.metrics_params.clone()),
|
||||
);
|
||||
|
||||
let left_to_right_on_demand_teyrchains = OnDemandTeyrchainsRelay::<
|
||||
<L2R as TeyrchainToRelayHeadersCliBridge>::TeyrchainFinality,
|
||||
_,
|
||||
_,
|
||||
>::new(
|
||||
self.left_relay.clone(),
|
||||
self.common.right.client.clone(),
|
||||
self.common.right.tx_params.clone(),
|
||||
Arc::new(left_relay_to_right_on_demand_headers),
|
||||
);
|
||||
let right_to_left_on_demand_teyrchains = OnDemandTeyrchainsRelay::<
|
||||
<R2L as TeyrchainToRelayHeadersCliBridge>::TeyrchainFinality,
|
||||
_,
|
||||
_,
|
||||
>::new(
|
||||
self.right_relay.clone(),
|
||||
self.common.left.client.clone(),
|
||||
self.common.left.tx_params.clone(),
|
||||
Arc::new(right_relay_to_left_on_demand_headers),
|
||||
);
|
||||
|
||||
Ok((
|
||||
Arc::new(left_to_right_on_demand_teyrchains),
|
||||
Arc::new(right_to_left_on_demand_teyrchains),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
// 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/>.
|
||||
|
||||
//! Primitives for exposing the messages relaying functionality in the CLI.
|
||||
|
||||
use crate::{
|
||||
cli::{bridge::*, chain_schema::*, HexLaneId, PrometheusParams},
|
||||
messages::MessagesRelayParams,
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use clap::Parser;
|
||||
use pezsp_core::Pair;
|
||||
|
||||
use bp_messages::MessageNonce;
|
||||
use pezbp_runtime::HeaderIdProvider;
|
||||
use relay_bizinikiwi_client::{
|
||||
AccountIdOf, AccountKeyPairOf, BalanceOf, Chain, ChainWithRuntimeVersion,
|
||||
ChainWithTransactions, Client,
|
||||
};
|
||||
use relay_utils::UniqueSaturatedInto;
|
||||
use pezsp_runtime::traits::TryConvert;
|
||||
|
||||
/// Messages relaying params.
|
||||
#[derive(Parser)]
|
||||
pub struct RelayMessagesParams {
|
||||
/// Hex-encoded lane id that should be served by the relay.
|
||||
#[arg(long)]
|
||||
lane: HexLaneId,
|
||||
#[command(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[command(flatten)]
|
||||
source_sign: SourceSigningParams,
|
||||
#[command(flatten)]
|
||||
target: TargetConnectionParams,
|
||||
#[command(flatten)]
|
||||
target_sign: TargetSigningParams,
|
||||
#[command(flatten)]
|
||||
prometheus_params: PrometheusParams,
|
||||
}
|
||||
|
||||
/// Messages range relaying params.
|
||||
#[derive(Parser)]
|
||||
pub struct RelayMessagesRangeParams {
|
||||
/// Number of the source chain header that we will use to prepare a messages proof.
|
||||
/// This header must be previously proved to the target chain.
|
||||
#[arg(long)]
|
||||
at_source_block: u128,
|
||||
/// Hex-encoded lane id that should be served by the relay.
|
||||
#[arg(long)]
|
||||
lane: HexLaneId,
|
||||
/// Nonce (inclusive) of the first message to relay.
|
||||
#[arg(long)]
|
||||
messages_start: MessageNonce,
|
||||
/// Nonce (inclusive) of the last message to relay.
|
||||
#[arg(long)]
|
||||
messages_end: MessageNonce,
|
||||
/// Whether the outbound lane state proof should be included into transaction.
|
||||
#[arg(long)]
|
||||
outbound_state_proof_required: bool,
|
||||
#[command(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[command(flatten)]
|
||||
source_sign: SourceSigningParams,
|
||||
#[command(flatten)]
|
||||
target: TargetConnectionParams,
|
||||
#[command(flatten)]
|
||||
target_sign: TargetSigningParams,
|
||||
}
|
||||
|
||||
/// Messages delivery confirmation relaying params.
|
||||
#[derive(Parser)]
|
||||
pub struct RelayMessagesDeliveryConfirmationParams {
|
||||
/// Number of the target chain header that we will use to prepare a messages
|
||||
/// delivery proof. This header must be previously proved to the source chain.
|
||||
#[arg(long)]
|
||||
at_target_block: u128,
|
||||
/// Hex-encoded lane id that should be served by the relay.
|
||||
#[arg(long)]
|
||||
lane: HexLaneId,
|
||||
#[command(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[command(flatten)]
|
||||
source_sign: SourceSigningParams,
|
||||
#[command(flatten)]
|
||||
target: TargetConnectionParams,
|
||||
}
|
||||
|
||||
/// Trait used for relaying messages between 2 chains.
|
||||
#[async_trait]
|
||||
pub trait MessagesRelayer: MessagesCliBridge
|
||||
where
|
||||
Self::Source: ChainWithTransactions + ChainWithRuntimeVersion,
|
||||
AccountIdOf<Self::Source>: From<<AccountKeyPairOf<Self::Source> as Pair>::Public>,
|
||||
AccountIdOf<Self::Target>: From<<AccountKeyPairOf<Self::Target> as Pair>::Public>,
|
||||
BalanceOf<Self::Source>: TryFrom<BalanceOf<Self::Target>>,
|
||||
{
|
||||
/// Start relaying messages.
|
||||
async fn relay_messages(data: RelayMessagesParams) -> anyhow::Result<()> {
|
||||
let source_client = data.source.into_client::<Self::Source>().await?;
|
||||
let source_sign = data.source_sign.to_keypair::<Self::Source>()?;
|
||||
let source_transactions_mortality = data.source_sign.transactions_mortality()?;
|
||||
let target_client = data.target.into_client::<Self::Target>().await?;
|
||||
let target_sign = data.target_sign.to_keypair::<Self::Target>()?;
|
||||
let target_transactions_mortality = data.target_sign.transactions_mortality()?;
|
||||
let lane_id = HexLaneId::try_convert(data.lane).map_err(|invalid_lane_id| {
|
||||
anyhow::format_err!("Invalid laneId: {:?}!", invalid_lane_id)
|
||||
})?;
|
||||
|
||||
Self::start_relay_guards(&target_client, target_client.can_start_version_guard()).await?;
|
||||
|
||||
crate::messages::run::<Self::MessagesLane, _, _>(MessagesRelayParams {
|
||||
source_client,
|
||||
source_transaction_params: TransactionParams {
|
||||
signer: source_sign,
|
||||
mortality: source_transactions_mortality,
|
||||
},
|
||||
target_client,
|
||||
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,
|
||||
limits: Self::maybe_messages_limits(),
|
||||
metrics_params: data.prometheus_params.into_metrics_params()?,
|
||||
})
|
||||
.await
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
}
|
||||
|
||||
/// Relay a consequitive range of messages.
|
||||
async fn relay_messages_range(data: RelayMessagesRangeParams) -> anyhow::Result<()> {
|
||||
let source_client = data.source.into_client::<Self::Source>().await?;
|
||||
let target_client = data.target.into_client::<Self::Target>().await?;
|
||||
let source_sign = data.source_sign.to_keypair::<Self::Source>()?;
|
||||
let source_transactions_mortality = data.source_sign.transactions_mortality()?;
|
||||
let target_sign = data.target_sign.to_keypair::<Self::Target>()?;
|
||||
let target_transactions_mortality = data.target_sign.transactions_mortality()?;
|
||||
let lane_id = HexLaneId::try_convert(data.lane).map_err(|invalid_lane_id| {
|
||||
anyhow::format_err!("Invalid laneId: {:?}!", invalid_lane_id)
|
||||
})?;
|
||||
|
||||
let at_source_block = source_client
|
||||
.header_by_number(data.at_source_block.unique_saturated_into())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::trace!(
|
||||
target: "bridge",
|
||||
error=?e,
|
||||
source=%Self::Source::NAME,
|
||||
at_source_block=%data.at_source_block,
|
||||
"Failed to read header"
|
||||
);
|
||||
anyhow::format_err!("The command has failed")
|
||||
})?
|
||||
.id();
|
||||
|
||||
crate::messages::relay_messages_range::<Self::MessagesLane>(
|
||||
source_client,
|
||||
target_client,
|
||||
TransactionParams { signer: source_sign, mortality: source_transactions_mortality },
|
||||
TransactionParams { signer: target_sign, mortality: target_transactions_mortality },
|
||||
at_source_block,
|
||||
lane_id,
|
||||
data.messages_start..=data.messages_end,
|
||||
data.outbound_state_proof_required,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Relay a messages delivery confirmation.
|
||||
async fn relay_messages_delivery_confirmation(
|
||||
data: RelayMessagesDeliveryConfirmationParams,
|
||||
) -> anyhow::Result<()> {
|
||||
let source_client = data.source.into_client::<Self::Source>().await?;
|
||||
let target_client = data.target.into_client::<Self::Target>().await?;
|
||||
let source_sign = data.source_sign.to_keypair::<Self::Source>()?;
|
||||
let source_transactions_mortality = data.source_sign.transactions_mortality()?;
|
||||
let lane_id = HexLaneId::try_convert(data.lane).map_err(|invalid_lane_id| {
|
||||
anyhow::format_err!("Invalid laneId: {:?}!", invalid_lane_id)
|
||||
})?;
|
||||
|
||||
let at_target_block = target_client
|
||||
.header_by_number(data.at_target_block.unique_saturated_into())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::trace!(
|
||||
target: "bridge",
|
||||
error=?e,
|
||||
target=%Self::Target::NAME,
|
||||
at_target_block=%data.at_target_block,
|
||||
"Failed to read header"
|
||||
);
|
||||
anyhow::format_err!("The command has failed")
|
||||
})?
|
||||
.id();
|
||||
|
||||
crate::messages::relay_messages_delivery_confirmation::<Self::MessagesLane>(
|
||||
source_client,
|
||||
target_client,
|
||||
TransactionParams { signer: source_sign, mortality: source_transactions_mortality },
|
||||
at_target_block,
|
||||
lane_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Add relay guards if required.
|
||||
async fn start_relay_guards(
|
||||
target_client: &impl Client<Self::Target>,
|
||||
enable_version_guard: bool,
|
||||
) -> relay_bizinikiwi_client::Result<()> {
|
||||
if enable_version_guard {
|
||||
relay_bizinikiwi_client::guard::abort_on_spec_version_change(
|
||||
target_client.clone(),
|
||||
target_client.simple_runtime_version().await?.spec_version,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
// 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/>.
|
||||
|
||||
//! Primitives for exposing the teyrchains finality relaying functionality in the CLI.
|
||||
|
||||
use async_std::sync::Mutex;
|
||||
use async_trait::async_trait;
|
||||
use bp_pezkuwi_core::BlockNumber as RelayBlockNumber;
|
||||
use pezbp_runtime::HeaderIdProvider;
|
||||
use clap::Parser;
|
||||
use relay_bizinikiwi_client::{Client, Teyrchain};
|
||||
use relay_utils::metrics::{GlobalMetrics, StandaloneMetric};
|
||||
use std::sync::Arc;
|
||||
use teyrchains_relay::teyrchains_loop::{AvailableHeader, SourceClient, TargetClient};
|
||||
|
||||
use crate::{
|
||||
cli::{
|
||||
bridge::{CliBridgeBase, TeyrchainToRelayHeadersCliBridge},
|
||||
chain_schema::*,
|
||||
DefaultClient, PrometheusParams,
|
||||
},
|
||||
finality::BizinikiwiFinalitySyncPipeline,
|
||||
teyrchains::{source::TeyrchainsSource, target::TeyrchainsTarget, TeyrchainsPipelineAdapter},
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
/// Teyrchains heads relaying params.
|
||||
#[derive(Parser)]
|
||||
pub struct RelayTeyrchainsParams {
|
||||
#[command(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[command(flatten)]
|
||||
target: TargetConnectionParams,
|
||||
#[command(flatten)]
|
||||
target_sign: TargetSigningParams,
|
||||
/// If passed, only free headers (those, available at "free" relay chain headers)
|
||||
/// are relayed.
|
||||
#[arg(long)]
|
||||
only_free_headers: bool,
|
||||
#[command(flatten)]
|
||||
prometheus_params: PrometheusParams,
|
||||
}
|
||||
|
||||
/// Single teyrchains head relaying params.
|
||||
#[derive(Parser)]
|
||||
pub struct RelayTeyrchainHeadParams {
|
||||
#[command(flatten)]
|
||||
source: SourceConnectionParams,
|
||||
#[command(flatten)]
|
||||
target: TargetConnectionParams,
|
||||
#[command(flatten)]
|
||||
target_sign: TargetSigningParams,
|
||||
/// Prove teyrchain head at that relay block number. This relay header must be previously
|
||||
/// proved to the target chain.
|
||||
#[arg(long)]
|
||||
at_relay_block: RelayBlockNumber,
|
||||
}
|
||||
|
||||
/// Trait used for relaying teyrchains finality between 2 chains.
|
||||
#[async_trait]
|
||||
pub trait TeyrchainsRelayer: TeyrchainToRelayHeadersCliBridge
|
||||
where
|
||||
TeyrchainsSource<Self::TeyrchainFinality, DefaultClient<Self::SourceRelay>>:
|
||||
SourceClient<TeyrchainsPipelineAdapter<Self::TeyrchainFinality>>,
|
||||
TeyrchainsTarget<
|
||||
Self::TeyrchainFinality,
|
||||
DefaultClient<Self::SourceRelay>,
|
||||
DefaultClient<Self::Target>,
|
||||
>: TargetClient<TeyrchainsPipelineAdapter<Self::TeyrchainFinality>>,
|
||||
<Self as CliBridgeBase>::Source: Teyrchain,
|
||||
{
|
||||
/// Start relaying teyrchains finality.
|
||||
async fn relay_teyrchains(data: RelayTeyrchainsParams) -> anyhow::Result<()> {
|
||||
let source_chain_client = data.source.into_client::<Self::SourceRelay>().await?;
|
||||
let source_client = TeyrchainsSource::<Self::TeyrchainFinality, _>::new(
|
||||
source_chain_client.clone(),
|
||||
Arc::new(Mutex::new(AvailableHeader::Missing)),
|
||||
);
|
||||
|
||||
let target_transaction_params = TransactionParams {
|
||||
signer: data.target_sign.to_keypair::<Self::Target>()?,
|
||||
mortality: data.target_sign.target_transactions_mortality,
|
||||
};
|
||||
let target_chain_client = data.target.into_client::<Self::Target>().await?;
|
||||
let target_client = TeyrchainsTarget::<Self::TeyrchainFinality, _, _>::new(
|
||||
source_chain_client,
|
||||
target_chain_client,
|
||||
target_transaction_params,
|
||||
);
|
||||
|
||||
let metrics_params: relay_utils::metrics::MetricsParams =
|
||||
data.prometheus_params.into_metrics_params()?;
|
||||
GlobalMetrics::new()?.register_and_spawn(&metrics_params.registry)?;
|
||||
|
||||
Self::RelayFinality::start_relay_guards(
|
||||
target_client.target_client(),
|
||||
target_client.target_client().can_start_version_guard(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
teyrchains_relay::teyrchains_loop::run(
|
||||
source_client,
|
||||
target_client,
|
||||
metrics_params,
|
||||
data.only_free_headers,
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
}
|
||||
|
||||
/// Relay single teyrchain head. No checks are made to ensure that transaction will succeed.
|
||||
async fn relay_teyrchain_head(data: RelayTeyrchainHeadParams) -> anyhow::Result<()> {
|
||||
let source_chain_client = data.source.into_client::<Self::SourceRelay>().await?;
|
||||
let at_relay_block = source_chain_client
|
||||
.header_by_number(data.at_relay_block)
|
||||
.await
|
||||
.map_err(|e| anyhow::format_err!("{}", e))?
|
||||
.id();
|
||||
|
||||
let source_client = TeyrchainsSource::<Self::TeyrchainFinality, _>::new(
|
||||
source_chain_client.clone(),
|
||||
Arc::new(Mutex::new(AvailableHeader::Missing)),
|
||||
);
|
||||
|
||||
let target_transaction_params = TransactionParams {
|
||||
signer: data.target_sign.to_keypair::<Self::Target>()?,
|
||||
mortality: data.target_sign.target_transactions_mortality,
|
||||
};
|
||||
let target_chain_client = data.target.into_client::<Self::Target>().await?;
|
||||
let target_client = TeyrchainsTarget::<Self::TeyrchainFinality, _, _>::new(
|
||||
source_chain_client,
|
||||
target_chain_client,
|
||||
target_transaction_params,
|
||||
);
|
||||
|
||||
teyrchains_relay::teyrchains_loop::relay_single_head(
|
||||
source_client,
|
||||
target_client,
|
||||
at_relay_block,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| anyhow::format_err!("The command has failed"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
// Copyright 2019-2023 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/>.
|
||||
|
||||
//! Types and functions intended to ease adding of new Bizinikiwi -> Bizinikiwi
|
||||
//! equivocation detection pipelines.
|
||||
|
||||
mod source;
|
||||
mod target;
|
||||
|
||||
use crate::{
|
||||
equivocation::{source::BizinikiwiEquivocationSource, target::BizinikiwiEquivocationTarget},
|
||||
finality_base::{engine::Engine, BizinikiwiFinalityPipeline, BizinikiwiFinalityProof},
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use pezbp_runtime::{AccountIdOf, BlockNumberOf, HashOf};
|
||||
use pez_equivocation_detector::EquivocationDetectionPipeline;
|
||||
use pez_finality_relay::FinalityPipeline;
|
||||
use pezpallet_grandpa::{Call as GrandpaCall, Config as GrandpaConfig};
|
||||
use relay_bizinikiwi_client::{AccountKeyPairOf, CallOf, Chain, ChainWithTransactions, Client};
|
||||
use relay_utils::metrics::MetricsParams;
|
||||
use pezsp_core::Pair;
|
||||
use pezsp_runtime::traits::{Block, Header};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Convenience trait that adds bounds to `BizinikiwiEquivocationDetectionPipeline`.
|
||||
pub trait BaseBizinikiwiEquivocationDetectionPipeline:
|
||||
BizinikiwiFinalityPipeline<SourceChain = Self::BoundedSourceChain>
|
||||
{
|
||||
/// Bounded `BizinikiwiFinalityPipeline::SourceChain`.
|
||||
type BoundedSourceChain: ChainWithTransactions<AccountId = Self::BoundedSourceChainAccountId>;
|
||||
|
||||
/// Bounded `AccountIdOf<BizinikiwiFinalityPipeline::SourceChain>`.
|
||||
type BoundedSourceChainAccountId: From<<AccountKeyPairOf<Self::BoundedSourceChain> as Pair>::Public>
|
||||
+ Send;
|
||||
}
|
||||
|
||||
impl<T> BaseBizinikiwiEquivocationDetectionPipeline for T
|
||||
where
|
||||
T: BizinikiwiFinalityPipeline,
|
||||
T::SourceChain: ChainWithTransactions,
|
||||
AccountIdOf<T::SourceChain>: From<<AccountKeyPairOf<Self::SourceChain> as Pair>::Public>,
|
||||
{
|
||||
type BoundedSourceChain = T::SourceChain;
|
||||
type BoundedSourceChainAccountId = AccountIdOf<T::SourceChain>;
|
||||
}
|
||||
|
||||
/// Bizinikiwi -> Bizinikiwi equivocation detection pipeline.
|
||||
#[async_trait]
|
||||
pub trait BizinikiwiEquivocationDetectionPipeline:
|
||||
BaseBizinikiwiEquivocationDetectionPipeline
|
||||
{
|
||||
/// How the `report_equivocation` call is built ?
|
||||
type ReportEquivocationCallBuilder: ReportEquivocationCallBuilder<Self>;
|
||||
|
||||
/// Add relay guards if required.
|
||||
async fn start_relay_guards(
|
||||
source_client: &impl Client<Self::SourceChain>,
|
||||
enable_version_guard: bool,
|
||||
) -> relay_bizinikiwi_client::Result<()> {
|
||||
if enable_version_guard {
|
||||
relay_bizinikiwi_client::guard::abort_on_spec_version_change(
|
||||
source_client.clone(),
|
||||
source_client.simple_runtime_version().await?.spec_version,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
type FinalityProoffOf<P> = <<P as BizinikiwiFinalityPipeline>::FinalityEngine as Engine<
|
||||
<P as BizinikiwiFinalityPipeline>::SourceChain,
|
||||
>>::FinalityProof;
|
||||
type FinalityVerificationContextfOf<P> =
|
||||
<<P as BizinikiwiFinalityPipeline>::FinalityEngine as Engine<
|
||||
<P as BizinikiwiFinalityPipeline>::SourceChain,
|
||||
>>::FinalityVerificationContext;
|
||||
/// The type of the equivocation proof used by the `BizinikiwiEquivocationDetectionPipeline`
|
||||
pub type EquivocationProofOf<P> = <<P as BizinikiwiFinalityPipeline>::FinalityEngine as Engine<
|
||||
<P as BizinikiwiFinalityPipeline>::SourceChain,
|
||||
>>::EquivocationProof;
|
||||
type EquivocationsFinderOf<P> = <<P as BizinikiwiFinalityPipeline>::FinalityEngine as Engine<
|
||||
<P as BizinikiwiFinalityPipeline>::SourceChain,
|
||||
>>::EquivocationsFinder;
|
||||
/// The type of the key owner proof used by the `BizinikiwiEquivocationDetectionPipeline`
|
||||
pub type KeyOwnerProofOf<P> = <<P as BizinikiwiFinalityPipeline>::FinalityEngine as Engine<
|
||||
<P as BizinikiwiFinalityPipeline>::SourceChain,
|
||||
>>::KeyOwnerProof;
|
||||
|
||||
/// Adapter that allows a `BizinikiwiEquivocationDetectionPipeline` to act as an
|
||||
/// `EquivocationDetectionPipeline`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EquivocationDetectionPipelineAdapter<P: BizinikiwiEquivocationDetectionPipeline> {
|
||||
_phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiEquivocationDetectionPipeline> FinalityPipeline
|
||||
for EquivocationDetectionPipelineAdapter<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 FinalityProof = BizinikiwiFinalityProof<P>;
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiEquivocationDetectionPipeline> EquivocationDetectionPipeline
|
||||
for EquivocationDetectionPipelineAdapter<P>
|
||||
{
|
||||
type TargetNumber = BlockNumberOf<P::TargetChain>;
|
||||
type FinalityVerificationContext = FinalityVerificationContextfOf<P>;
|
||||
type EquivocationProof = EquivocationProofOf<P>;
|
||||
type EquivocationsFinder = EquivocationsFinderOf<P>;
|
||||
}
|
||||
|
||||
/// Different ways of building `report_equivocation` calls.
|
||||
pub trait ReportEquivocationCallBuilder<P: BizinikiwiEquivocationDetectionPipeline> {
|
||||
/// Build a `report_equivocation` call to be executed on the source chain.
|
||||
fn build_report_equivocation_call(
|
||||
equivocation_proof: EquivocationProofOf<P>,
|
||||
key_owner_proof: KeyOwnerProofOf<P>,
|
||||
) -> CallOf<P::SourceChain>;
|
||||
}
|
||||
|
||||
/// Building the `report_equivocation` call when having direct access to the target chain runtime.
|
||||
pub struct DirectReportGrandpaEquivocationCallBuilder<P, R> {
|
||||
_phantom: PhantomData<(P, R)>,
|
||||
}
|
||||
|
||||
impl<P, R> ReportEquivocationCallBuilder<P> for DirectReportGrandpaEquivocationCallBuilder<P, R>
|
||||
where
|
||||
P: BizinikiwiEquivocationDetectionPipeline,
|
||||
P::FinalityEngine: Engine<
|
||||
P::SourceChain,
|
||||
EquivocationProof = pezsp_consensus_grandpa::EquivocationProof<
|
||||
HashOf<P::SourceChain>,
|
||||
BlockNumberOf<P::SourceChain>,
|
||||
>,
|
||||
>,
|
||||
R: pezframe_system::Config<Hash = HashOf<P::SourceChain>>
|
||||
+ GrandpaConfig<KeyOwnerProof = KeyOwnerProofOf<P>>,
|
||||
<R::Block as Block>::Header: Header<Number = BlockNumberOf<P::SourceChain>>,
|
||||
CallOf<P::SourceChain>: From<GrandpaCall<R>>,
|
||||
{
|
||||
fn build_report_equivocation_call(
|
||||
equivocation_proof: EquivocationProofOf<P>,
|
||||
key_owner_proof: KeyOwnerProofOf<P>,
|
||||
) -> CallOf<P::SourceChain> {
|
||||
GrandpaCall::<R>::report_equivocation {
|
||||
equivocation_proof: Box::new(equivocation_proof),
|
||||
key_owner_proof,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro that generates `ReportEquivocationCallBuilder` implementation for the case where
|
||||
/// we only have access to the mocked version of the source chain runtime.
|
||||
#[rustfmt::skip]
|
||||
#[macro_export]
|
||||
macro_rules! generate_report_equivocation_call_builder {
|
||||
($pipeline:ident, $mocked_builder:ident, $grandpa:path, $report_equivocation:path) => {
|
||||
pub struct $mocked_builder;
|
||||
|
||||
impl $crate::equivocation::ReportEquivocationCallBuilder<$pipeline>
|
||||
for $mocked_builder
|
||||
{
|
||||
fn build_report_equivocation_call(
|
||||
equivocation_proof: $crate::equivocation::EquivocationProofOf<$pipeline>,
|
||||
key_owner_proof: $crate::equivocation::KeyOwnerProofOf<$pipeline>,
|
||||
) -> relay_bizinikiwi_client::CallOf<
|
||||
<$pipeline as $crate::finality_base::BizinikiwiFinalityPipeline>::SourceChain
|
||||
> {
|
||||
pezbp_runtime::paste::item! {
|
||||
$grandpa($report_equivocation {
|
||||
equivocation_proof: Box::new(equivocation_proof),
|
||||
key_owner_proof: key_owner_proof
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Run Bizinikiwi-to-Bizinikiwi equivocations detection loop.
|
||||
pub async fn run<P: BizinikiwiEquivocationDetectionPipeline>(
|
||||
source_client: impl Client<P::SourceChain>,
|
||||
target_client: impl Client<P::TargetChain>,
|
||||
source_transaction_params: TransactionParams<AccountKeyPairOf<P::SourceChain>>,
|
||||
metrics_params: MetricsParams,
|
||||
) -> anyhow::Result<()> {
|
||||
tracing::info!(
|
||||
target: "bridge",
|
||||
source=%P::SourceChain::NAME,
|
||||
target=%P::TargetChain::NAME,
|
||||
"Starting equivocations detection loop"
|
||||
);
|
||||
|
||||
pez_equivocation_detector::run(
|
||||
BizinikiwiEquivocationSource::<P, _>::new(source_client, source_transaction_params),
|
||||
BizinikiwiEquivocationTarget::<P, _>::new(target_client),
|
||||
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
metrics_params,
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Default generic implementation of equivocation source for basic Bizinikiwi client.
|
||||
|
||||
use crate::{
|
||||
equivocation::{
|
||||
EquivocationDetectionPipelineAdapter, EquivocationProofOf, ReportEquivocationCallBuilder,
|
||||
BizinikiwiEquivocationDetectionPipeline,
|
||||
},
|
||||
finality_base::{engine::Engine, finality_proofs, BizinikiwiFinalityProofsStream},
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use pezbp_runtime::{HashOf, TransactionEra};
|
||||
use pez_equivocation_detector::SourceClient;
|
||||
use pez_finality_relay::SourceClientBase;
|
||||
use relay_bizinikiwi_client::{
|
||||
AccountKeyPairOf, Client, Error, TransactionTracker, UnsignedTransaction,
|
||||
};
|
||||
use relay_utils::relay_loop::Client as RelayClient;
|
||||
|
||||
/// Bizinikiwi node as equivocation source.
|
||||
pub struct BizinikiwiEquivocationSource<P: BizinikiwiEquivocationDetectionPipeline, SourceClnt> {
|
||||
client: SourceClnt,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::SourceChain>>,
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiEquivocationDetectionPipeline, SourceClnt: Client<P::SourceChain>>
|
||||
BizinikiwiEquivocationSource<P, SourceClnt>
|
||||
{
|
||||
/// Create new instance of `BizinikiwiEquivocationSource`.
|
||||
pub fn new(
|
||||
client: SourceClnt,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::SourceChain>>,
|
||||
) -> Self {
|
||||
Self { client, transaction_params }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiEquivocationDetectionPipeline, SourceClnt: Client<P::SourceChain>> Clone
|
||||
for BizinikiwiEquivocationSource<P, SourceClnt>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self { client: self.client.clone(), transaction_params: self.transaction_params.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: BizinikiwiEquivocationDetectionPipeline, SourceClnt: Client<P::SourceChain>> RelayClient
|
||||
for BizinikiwiEquivocationSource<P, SourceClnt>
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), Error> {
|
||||
self.client.reconnect().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: BizinikiwiEquivocationDetectionPipeline, SourceClnt: Client<P::SourceChain>>
|
||||
SourceClientBase<EquivocationDetectionPipelineAdapter<P>>
|
||||
for BizinikiwiEquivocationSource<P, SourceClnt>
|
||||
{
|
||||
type FinalityProofsStream = BizinikiwiFinalityProofsStream<P>;
|
||||
|
||||
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, Error> {
|
||||
finality_proofs::<P>(&self.client).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: BizinikiwiEquivocationDetectionPipeline, SourceClnt: Client<P::SourceChain>>
|
||||
SourceClient<EquivocationDetectionPipelineAdapter<P>>
|
||||
for BizinikiwiEquivocationSource<P, SourceClnt>
|
||||
{
|
||||
type TransactionTracker = TransactionTracker<P::SourceChain, SourceClnt>;
|
||||
|
||||
async fn report_equivocation(
|
||||
&self,
|
||||
at: HashOf<P::SourceChain>,
|
||||
equivocation: EquivocationProofOf<P>,
|
||||
) -> Result<Self::TransactionTracker, Self::Error> {
|
||||
let key_owner_proof =
|
||||
P::FinalityEngine::generate_source_key_ownership_proof(&self.client, at, &equivocation)
|
||||
.await?;
|
||||
|
||||
let mortality = self.transaction_params.mortality;
|
||||
let call = P::ReportEquivocationCallBuilder::build_report_equivocation_call(
|
||||
equivocation,
|
||||
key_owner_proof,
|
||||
);
|
||||
self.client
|
||||
.submit_and_watch_signed_extrinsic(
|
||||
&self.transaction_params.signer,
|
||||
move |best_block_id, transaction_nonce| {
|
||||
Ok(UnsignedTransaction::new(call.into(), transaction_nonce)
|
||||
.era(TransactionEra::new(best_block_id, mortality)))
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Default generic implementation of equivocation source for basic Bizinikiwi client.
|
||||
|
||||
use crate::{
|
||||
equivocation::{
|
||||
EquivocationDetectionPipelineAdapter, FinalityProoffOf, FinalityVerificationContextfOf,
|
||||
BizinikiwiEquivocationDetectionPipeline,
|
||||
},
|
||||
finality_base::{best_synced_header_id, engine::Engine},
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_header_pez_chain::HeaderFinalityInfo;
|
||||
use pezbp_runtime::{BlockNumberOf, HashOf};
|
||||
use pez_equivocation_detector::TargetClient;
|
||||
use relay_bizinikiwi_client::{Client, Error};
|
||||
use relay_utils::relay_loop::Client as RelayClient;
|
||||
use pezsp_runtime::traits::Header;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Bizinikiwi node as equivocation source.
|
||||
pub struct BizinikiwiEquivocationTarget<P: BizinikiwiEquivocationDetectionPipeline, TargetClnt> {
|
||||
client: TargetClnt,
|
||||
|
||||
_phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiEquivocationDetectionPipeline, TargetClnt: Client<P::TargetChain>>
|
||||
BizinikiwiEquivocationTarget<P, TargetClnt>
|
||||
{
|
||||
/// Create new instance of `BizinikiwiEquivocationTarget`.
|
||||
pub fn new(client: TargetClnt) -> Self {
|
||||
Self { client, _phantom: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiEquivocationDetectionPipeline, TargetClnt: Client<P::TargetChain>> Clone
|
||||
for BizinikiwiEquivocationTarget<P, TargetClnt>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self { client: self.client.clone(), _phantom: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: BizinikiwiEquivocationDetectionPipeline, TargetClnt: Client<P::TargetChain>> RelayClient
|
||||
for BizinikiwiEquivocationTarget<P, TargetClnt>
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), Error> {
|
||||
self.client.reconnect().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: BizinikiwiEquivocationDetectionPipeline, TargetClnt: Client<P::TargetChain>>
|
||||
TargetClient<EquivocationDetectionPipelineAdapter<P>>
|
||||
for BizinikiwiEquivocationTarget<P, TargetClnt>
|
||||
{
|
||||
async fn best_finalized_header_number(
|
||||
&self,
|
||||
) -> Result<BlockNumberOf<P::TargetChain>, Self::Error> {
|
||||
self.client.best_finalized_header_number().await
|
||||
}
|
||||
|
||||
async fn best_synced_header_hash(
|
||||
&self,
|
||||
at: BlockNumberOf<P::TargetChain>,
|
||||
) -> Result<Option<HashOf<P::SourceChain>>, Self::Error> {
|
||||
Ok(best_synced_header_id::<P::SourceChain, P::TargetChain>(
|
||||
&self.client,
|
||||
self.client.header_by_number(at).await?.hash(),
|
||||
)
|
||||
.await?
|
||||
.map(|id| id.hash()))
|
||||
}
|
||||
|
||||
async fn finality_verification_context(
|
||||
&self,
|
||||
at: BlockNumberOf<P::TargetChain>,
|
||||
) -> Result<FinalityVerificationContextfOf<P>, Self::Error> {
|
||||
P::FinalityEngine::finality_verification_context(
|
||||
&self.client,
|
||||
self.client.header_by_number(at).await?.hash(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn synced_headers_finality_info(
|
||||
&self,
|
||||
at: BlockNumberOf<P::TargetChain>,
|
||||
) -> Result<
|
||||
Vec<HeaderFinalityInfo<FinalityProoffOf<P>, FinalityVerificationContextfOf<P>>>,
|
||||
Self::Error,
|
||||
> {
|
||||
P::FinalityEngine::synced_headers_finality_info(
|
||||
&self.client,
|
||||
self.client.header_by_number(at).await?.hash(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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/>.
|
||||
|
||||
//! Relay errors.
|
||||
|
||||
use relay_bizinikiwi_client as client;
|
||||
use pezsp_consensus_grandpa::AuthorityList;
|
||||
use pezsp_runtime::traits::MaybeDisplay;
|
||||
use std::fmt::Debug;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Relay errors.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error<Hash: Debug + MaybeDisplay, HeaderNumber: Debug + MaybeDisplay> {
|
||||
/// Failed to submit signed extrinsic from to the target chain.
|
||||
#[error("Failed to submit {0} transaction: {1:?}")]
|
||||
SubmitTransaction(&'static str, client::Error),
|
||||
/// Failed subscribe to justification stream of the source chain.
|
||||
#[error("Failed to subscribe to {0} justifications: {1:?}")]
|
||||
Subscribe(&'static str, client::Error),
|
||||
/// Failed subscribe to read justification from the source chain (client error).
|
||||
#[error("Failed to read {0} justification from the stream: {1}")]
|
||||
ReadJustification(&'static str, client::Error),
|
||||
/// Failed subscribe to read justification from the source chain (stream ended).
|
||||
#[error("Failed to read {0} justification from the stream: stream has ended unexpectedly")]
|
||||
ReadJustificationStreamEnded(&'static str),
|
||||
/// Failed subscribe to decode justification from the source chain.
|
||||
#[error("Failed to decode {0} justification: {1:?}")]
|
||||
DecodeJustification(&'static str, codec::Error),
|
||||
/// GRANDPA authorities read from the source chain are invalid.
|
||||
#[error("Read invalid {0} authorities set: {1:?}")]
|
||||
ReadInvalidAuthorities(&'static str, AuthorityList),
|
||||
/// Failed to guess initial GRANDPA authorities at the given header of the source chain.
|
||||
#[error("Failed to guess initial {0} GRANDPA authorities set id: checked all possible ids in range [0; {1}]")]
|
||||
GuessInitialAuthorities(&'static str, HeaderNumber),
|
||||
/// Failed to retrieve GRANDPA authorities at the given header from the source chain.
|
||||
#[error("Failed to retrieve {0} GRANDPA authorities set at header {1}: {2:?}")]
|
||||
RetrieveAuthorities(&'static str, Hash, client::Error),
|
||||
/// Failed to decode GRANDPA authorities at the given header of the source chain.
|
||||
#[error("Failed to decode {0} GRANDPA authorities set at header {1}: {2:?}")]
|
||||
DecodeAuthorities(&'static str, Hash, codec::Error),
|
||||
/// Failed to retrieve header by the hash from the source chain.
|
||||
#[error("Failed to retrieve {0} header with hash {1}: {2:?}")]
|
||||
RetrieveHeader(&'static str, Hash, client::Error),
|
||||
/// Failed to submit signed extrinsic from to the target chain.
|
||||
#[error(
|
||||
"Failed to retrieve `is_initialized` flag of the with-{0} finality pezpallet at {1}: {2:?}"
|
||||
)]
|
||||
IsInitializedRetrieve(&'static str, &'static str, client::Error),
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Initialize Bizinikiwi -> Bizinikiwi finality bridge.
|
||||
//!
|
||||
//! Initialization is a transaction that calls `initialize()` function of the
|
||||
//! finality pezpallet (GRANDPA/BEEFY/...). This transaction brings initial header
|
||||
//! and authorities set from source to target chain. The finality sync starts
|
||||
//! with this header.
|
||||
|
||||
use crate::{error::Error, finality_base::engine::Engine};
|
||||
use pezsp_core::Pair;
|
||||
|
||||
use pezbp_runtime::HeaderIdOf;
|
||||
use relay_bizinikiwi_client::{
|
||||
AccountKeyPairOf, Chain, ChainWithTransactions, Client, Error as BizinikiwiError,
|
||||
UnsignedTransaction,
|
||||
};
|
||||
use relay_utils::{TrackedTransactionStatus, TransactionTracker};
|
||||
use pezsp_runtime::traits::Header as HeaderT;
|
||||
|
||||
/// Submit headers-bridge initialization transaction.
|
||||
pub async fn initialize<
|
||||
E: Engine<SourceChain>,
|
||||
SourceChain: Chain,
|
||||
TargetChain: ChainWithTransactions,
|
||||
F,
|
||||
>(
|
||||
source_client: impl Client<SourceChain>,
|
||||
target_client: impl Client<TargetChain>,
|
||||
target_signer: AccountKeyPairOf<TargetChain>,
|
||||
prepare_initialize_transaction: F,
|
||||
dry_run: bool,
|
||||
) where
|
||||
F: FnOnce(
|
||||
TargetChain::Nonce,
|
||||
E::InitializationData,
|
||||
) -> Result<UnsignedTransaction<TargetChain>, BizinikiwiError>
|
||||
+ Send
|
||||
+ 'static,
|
||||
TargetChain::AccountId: From<<TargetChain::AccountKeyPair as Pair>::Public>,
|
||||
{
|
||||
let result = do_initialize::<E, _, _, _>(
|
||||
source_client,
|
||||
target_client,
|
||||
target_signer,
|
||||
prepare_initialize_transaction,
|
||||
dry_run,
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(Some(tx_status)) => match tx_status {
|
||||
TrackedTransactionStatus::Lost => {
|
||||
tracing::error!(
|
||||
target: "bridge",
|
||||
source=%SourceChain::NAME,
|
||||
target=%TargetChain::NAME,
|
||||
?tx_status,
|
||||
"Failed to execute headers bridge initialization transaction."
|
||||
)
|
||||
},
|
||||
TrackedTransactionStatus::Finalized(_) => {
|
||||
tracing::info!(
|
||||
target: "bridge",
|
||||
source=%SourceChain::NAME,
|
||||
target=%TargetChain::NAME,
|
||||
?tx_status,
|
||||
"Successfully executed headers bridge initialization transaction."
|
||||
)
|
||||
},
|
||||
},
|
||||
Ok(None) => (),
|
||||
Err(err) => tracing::error!(
|
||||
target: "bridge",
|
||||
error=?err,
|
||||
source=%SourceChain::NAME,
|
||||
target=%TargetChain::NAME,
|
||||
"Failed to submit headers bridge initialization transaction"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Craft and submit initialization transaction, returning any error that may occur.
|
||||
async fn do_initialize<
|
||||
E: Engine<SourceChain>,
|
||||
SourceChain: Chain,
|
||||
TargetChain: ChainWithTransactions,
|
||||
F,
|
||||
>(
|
||||
source_client: impl Client<SourceChain>,
|
||||
target_client: impl Client<TargetChain>,
|
||||
target_signer: AccountKeyPairOf<TargetChain>,
|
||||
prepare_initialize_transaction: F,
|
||||
dry_run: bool,
|
||||
) -> Result<
|
||||
Option<TrackedTransactionStatus<HeaderIdOf<TargetChain>>>,
|
||||
Error<SourceChain::Hash, <SourceChain::Header as HeaderT>::Number>,
|
||||
>
|
||||
where
|
||||
F: FnOnce(
|
||||
TargetChain::Nonce,
|
||||
E::InitializationData,
|
||||
) -> Result<UnsignedTransaction<TargetChain>, BizinikiwiError>
|
||||
+ Send
|
||||
+ 'static,
|
||||
TargetChain::AccountId: From<<TargetChain::AccountKeyPair as Pair>::Public>,
|
||||
{
|
||||
let is_initialized = E::is_initialized(&target_client)
|
||||
.await
|
||||
.map_err(|e| Error::IsInitializedRetrieve(SourceChain::NAME, TargetChain::NAME, e))?;
|
||||
if is_initialized {
|
||||
tracing::info!(
|
||||
target: "bridge",
|
||||
source=%SourceChain::NAME,
|
||||
target=%TargetChain::NAME,
|
||||
"Headers bridge is already initialized. Skipping"
|
||||
);
|
||||
if !dry_run {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
let initialization_data = E::prepare_initialization_data(source_client).await?;
|
||||
tracing::info!(
|
||||
target: "bridge",
|
||||
source=%SourceChain::NAME,
|
||||
target=%TargetChain::NAME,
|
||||
?initialization_data,
|
||||
"Prepared initialization data for headers bridge"
|
||||
);
|
||||
|
||||
let tx_status = target_client
|
||||
.submit_and_watch_signed_extrinsic(&target_signer, move |_, transaction_nonce| {
|
||||
let tx = prepare_initialize_transaction(transaction_nonce, initialization_data);
|
||||
if dry_run {
|
||||
Err(BizinikiwiError::Custom(
|
||||
"Not submitting extrinsic in `dry-run` mode!".to_string(),
|
||||
))
|
||||
} else {
|
||||
tx
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|err| Error::SubmitTransaction(TargetChain::NAME, err))?
|
||||
.wait()
|
||||
.await;
|
||||
|
||||
Ok(Some(tx_status))
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
// 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/>.
|
||||
|
||||
//! Types and functions intended to ease adding of new Bizinikiwi -> Bizinikiwi
|
||||
//! finality proofs synchronization pipelines.
|
||||
|
||||
use crate::{
|
||||
finality::{source::BizinikiwiFinalitySource, target::BizinikiwiFinalityTarget},
|
||||
finality_base::{engine::Engine, BizinikiwiFinalityPipeline, BizinikiwiFinalityProof},
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_header_pez_chain::justification::{GrandpaJustification, JustificationVerificationContext};
|
||||
use pez_finality_relay::{
|
||||
FinalityPipeline, FinalitySyncPipeline, HeadersToRelay, SourceClient, TargetClient,
|
||||
};
|
||||
use pezpallet_bridge_grandpa::{Call as BridgeGrandpaCall, Config as BridgeGrandpaConfig};
|
||||
use relay_bizinikiwi_client::{
|
||||
transaction_stall_timeout, AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain,
|
||||
ChainWithTransactions, Client, HashOf, HeaderOf, SyncHeader,
|
||||
};
|
||||
use relay_utils::{metrics::MetricsParams, TrackedTransactionStatus, TransactionTracker};
|
||||
use pezsp_core::Pair;
|
||||
use std::{fmt::Debug, marker::PhantomData};
|
||||
|
||||
pub mod initialize;
|
||||
pub mod source;
|
||||
pub mod target;
|
||||
|
||||
/// Default limit of recent finality proofs.
|
||||
///
|
||||
/// Finality delay of 4096 blocks is unlikely to happen in practice in
|
||||
/// Bizinikiwi+GRANDPA based chains (good to know).
|
||||
pub(crate) const RECENT_FINALITY_PROOFS_LIMIT: usize = 4096;
|
||||
|
||||
/// Convenience trait that adds bounds to `BizinikiwiFinalitySyncPipeline`.
|
||||
pub trait BaseBizinikiwiFinalitySyncPipeline:
|
||||
BizinikiwiFinalityPipeline<TargetChain = Self::BoundedTargetChain>
|
||||
{
|
||||
/// Bounded `BizinikiwiFinalityPipeline::TargetChain`.
|
||||
type BoundedTargetChain: ChainWithTransactions<AccountId = Self::BoundedTargetChainAccountId>;
|
||||
|
||||
/// Bounded `AccountIdOf<BizinikiwiFinalityPipeline::TargetChain>`.
|
||||
type BoundedTargetChainAccountId: From<<AccountKeyPairOf<Self::BoundedTargetChain> as Pair>::Public>
|
||||
+ Send;
|
||||
}
|
||||
|
||||
impl<T> BaseBizinikiwiFinalitySyncPipeline for T
|
||||
where
|
||||
T: BizinikiwiFinalityPipeline,
|
||||
T::TargetChain: ChainWithTransactions,
|
||||
AccountIdOf<T::TargetChain>: From<<AccountKeyPairOf<Self::TargetChain> as Pair>::Public>,
|
||||
{
|
||||
type BoundedTargetChain = T::TargetChain;
|
||||
type BoundedTargetChainAccountId = AccountIdOf<T::TargetChain>;
|
||||
}
|
||||
|
||||
/// Bizinikiwi -> Bizinikiwi finality proofs synchronization pipeline.
|
||||
#[async_trait]
|
||||
pub trait BizinikiwiFinalitySyncPipeline: BaseBizinikiwiFinalitySyncPipeline {
|
||||
/// How submit finality proof call is built?
|
||||
type SubmitFinalityProofCallBuilder: SubmitFinalityProofCallBuilder<Self>;
|
||||
|
||||
/// Add relay guards if required.
|
||||
async fn start_relay_guards(
|
||||
target_client: &impl Client<Self::TargetChain>,
|
||||
enable_version_guard: bool,
|
||||
) -> relay_bizinikiwi_client::Result<()> {
|
||||
if enable_version_guard {
|
||||
relay_bizinikiwi_client::guard::abort_on_spec_version_change(
|
||||
target_client.clone(),
|
||||
target_client.simple_runtime_version().await?.spec_version,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Adapter that allows all `BizinikiwiFinalitySyncPipeline` to act as `FinalitySyncPipeline`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FinalitySyncPipelineAdapter<P: BizinikiwiFinalitySyncPipeline> {
|
||||
_phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiFinalitySyncPipeline> FinalityPipeline 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 FinalityProof = BizinikiwiFinalityProof<P>;
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiFinalitySyncPipeline> FinalitySyncPipeline for FinalitySyncPipelineAdapter<P> {
|
||||
type ConsensusLogReader = <P::FinalityEngine as Engine<P::SourceChain>>::ConsensusLogReader;
|
||||
type Header = SyncHeader<HeaderOf<P::SourceChain>>;
|
||||
}
|
||||
|
||||
/// Different ways of building `submit_finality_proof` calls.
|
||||
pub trait SubmitFinalityProofCallBuilder<P: BizinikiwiFinalitySyncPipeline> {
|
||||
/// Given source chain header, its finality proof and the current authority set id, 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: BizinikiwiFinalityProof<P>,
|
||||
is_free_execution_expected: bool,
|
||||
context: <<P as BizinikiwiFinalityPipeline>::FinalityEngine as Engine<P::SourceChain>>::FinalityVerificationContext,
|
||||
) -> CallOf<P::TargetChain>;
|
||||
}
|
||||
|
||||
/// Building `submit_finality_proof` call when you have direct access to the target
|
||||
/// chain runtime.
|
||||
pub struct DirectSubmitGrandpaFinalityProofCallBuilder<P, R, I> {
|
||||
_phantom: PhantomData<(P, R, I)>,
|
||||
}
|
||||
|
||||
impl<P, R, I> SubmitFinalityProofCallBuilder<P>
|
||||
for DirectSubmitGrandpaFinalityProofCallBuilder<P, R, I>
|
||||
where
|
||||
P: BizinikiwiFinalitySyncPipeline,
|
||||
R: BridgeGrandpaConfig<I>,
|
||||
I: 'static,
|
||||
R::BridgedChain: pezbp_runtime::Chain<Header = HeaderOf<P::SourceChain>>,
|
||||
CallOf<P::TargetChain>: From<BridgeGrandpaCall<R, I>>,
|
||||
P::FinalityEngine: Engine<
|
||||
P::SourceChain,
|
||||
FinalityProof = GrandpaJustification<HeaderOf<P::SourceChain>>,
|
||||
FinalityVerificationContext = JustificationVerificationContext,
|
||||
>,
|
||||
{
|
||||
fn build_submit_finality_proof_call(
|
||||
header: SyncHeader<HeaderOf<P::SourceChain>>,
|
||||
proof: GrandpaJustification<HeaderOf<P::SourceChain>>,
|
||||
_is_free_execution_expected: bool,
|
||||
_context: JustificationVerificationContext,
|
||||
) -> CallOf<P::TargetChain> {
|
||||
BridgeGrandpaCall::<R, I>::submit_finality_proof {
|
||||
finality_target: Box::new(header.into_inner()),
|
||||
justification: proof,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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_submit_finality_proof_call_builder {
|
||||
($pipeline:ident, $mocked_builder:ident, $bridge_grandpa:path, $submit_finality_proof:path) => {
|
||||
pub struct $mocked_builder;
|
||||
|
||||
impl $crate::finality::SubmitFinalityProofCallBuilder<$pipeline>
|
||||
for $mocked_builder
|
||||
{
|
||||
fn build_submit_finality_proof_call(
|
||||
header: relay_bizinikiwi_client::SyncHeader<
|
||||
relay_bizinikiwi_client::HeaderOf<
|
||||
<$pipeline as $crate::finality_base::BizinikiwiFinalityPipeline>::SourceChain
|
||||
>
|
||||
>,
|
||||
proof: bp_header_pez_chain::justification::GrandpaJustification<
|
||||
relay_bizinikiwi_client::HeaderOf<
|
||||
<$pipeline as $crate::finality_base::BizinikiwiFinalityPipeline>::SourceChain
|
||||
>
|
||||
>,
|
||||
_is_free_execution_expected: bool,
|
||||
_context: bp_header_pez_chain::justification::JustificationVerificationContext,
|
||||
) -> relay_bizinikiwi_client::CallOf<
|
||||
<$pipeline as $crate::finality_base::BizinikiwiFinalityPipeline>::TargetChain
|
||||
> {
|
||||
pezbp_runtime::paste::item! {
|
||||
$bridge_grandpa($submit_finality_proof {
|
||||
finality_target: Box::new(header.into_inner()),
|
||||
justification: proof
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// 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_ex` call within that first option.
|
||||
#[rustfmt::skip]
|
||||
#[macro_export]
|
||||
macro_rules! generate_submit_finality_proof_ex_call_builder {
|
||||
($pipeline:ident, $mocked_builder:ident, $bridge_grandpa:path, $submit_finality_proof:path) => {
|
||||
pub struct $mocked_builder;
|
||||
|
||||
impl $crate::finality::SubmitFinalityProofCallBuilder<$pipeline>
|
||||
for $mocked_builder
|
||||
{
|
||||
fn build_submit_finality_proof_call(
|
||||
header: relay_bizinikiwi_client::SyncHeader<
|
||||
relay_bizinikiwi_client::HeaderOf<
|
||||
<$pipeline as $crate::finality_base::BizinikiwiFinalityPipeline>::SourceChain
|
||||
>
|
||||
>,
|
||||
proof: bp_header_pez_chain::justification::GrandpaJustification<
|
||||
relay_bizinikiwi_client::HeaderOf<
|
||||
<$pipeline as $crate::finality_base::BizinikiwiFinalityPipeline>::SourceChain
|
||||
>
|
||||
>,
|
||||
is_free_execution_expected: bool,
|
||||
context: bp_header_pez_chain::justification::JustificationVerificationContext,
|
||||
) -> relay_bizinikiwi_client::CallOf<
|
||||
<$pipeline as $crate::finality_base::BizinikiwiFinalityPipeline>::TargetChain
|
||||
> {
|
||||
pezbp_runtime::paste::item! {
|
||||
$bridge_grandpa($submit_finality_proof {
|
||||
finality_target: Box::new(header.into_inner()),
|
||||
justification: proof,
|
||||
current_set_id: context.authority_set_id,
|
||||
is_free_execution_expected,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Run Bizinikiwi-to-Bizinikiwi finality sync loop.
|
||||
pub async fn run<P: BizinikiwiFinalitySyncPipeline>(
|
||||
source_client: impl Client<P::SourceChain>,
|
||||
target_client: impl Client<P::TargetChain>,
|
||||
headers_to_relay: HeadersToRelay,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
|
||||
metrics_params: MetricsParams,
|
||||
) -> anyhow::Result<()> {
|
||||
tracing::info!(
|
||||
target: "bridge",
|
||||
source=%P::SourceChain::NAME,
|
||||
target=%P::TargetChain::NAME,
|
||||
?headers_to_relay,
|
||||
"Starting source -> target finality proof relay"
|
||||
);
|
||||
|
||||
pez_finality_relay::run(
|
||||
BizinikiwiFinalitySource::<P, _>::new(source_client, None),
|
||||
BizinikiwiFinalityTarget::<P, _>::new(target_client, transaction_params.clone()),
|
||||
pez_finality_relay::FinalitySyncParams {
|
||||
tick: std::cmp::max(
|
||||
P::SourceChain::AVERAGE_BLOCK_INTERVAL,
|
||||
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
),
|
||||
recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT,
|
||||
stall_timeout: transaction_stall_timeout(
|
||||
transaction_params.mortality,
|
||||
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
relay_utils::STALL_TIMEOUT,
|
||||
),
|
||||
headers_to_relay,
|
||||
},
|
||||
metrics_params,
|
||||
futures::future::pending(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow::format_err!("{}", e))
|
||||
}
|
||||
|
||||
/// Relay single header. No checks are made to ensure that transaction will succeed.
|
||||
pub async fn relay_single_header<P: BizinikiwiFinalitySyncPipeline>(
|
||||
source_client: impl Client<P::SourceChain>,
|
||||
target_client: impl Client<P::TargetChain>,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
|
||||
header_number: BlockNumberOf<P::SourceChain>,
|
||||
) -> anyhow::Result<()> {
|
||||
let finality_source = BizinikiwiFinalitySource::<P, _>::new(source_client, None);
|
||||
let (header, proof) = finality_source.header_and_finality_proof(header_number).await?;
|
||||
let Some(proof) = proof else {
|
||||
return Err(anyhow::format_err!(
|
||||
"Unable to submit {} header #{} to {}: no finality proof",
|
||||
P::SourceChain::NAME,
|
||||
header_number,
|
||||
P::TargetChain::NAME,
|
||||
));
|
||||
};
|
||||
|
||||
let finality_target = BizinikiwiFinalityTarget::<P, _>::new(target_client, transaction_params);
|
||||
let tx_tracker = finality_target.submit_finality_proof(header, proof, false).await?;
|
||||
match tx_tracker.wait().await {
|
||||
TrackedTransactionStatus::Finalized(_) => Ok(()),
|
||||
TrackedTransactionStatus::Lost => Err(anyhow::format_err!(
|
||||
"Transaction with {} header #{} is considered lost at {}",
|
||||
P::SourceChain::NAME,
|
||||
header_number,
|
||||
P::TargetChain::NAME,
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Default generic implementation of finality source for basic Bizinikiwi client.
|
||||
|
||||
use crate::{
|
||||
finality::{FinalitySyncPipelineAdapter, BizinikiwiFinalitySyncPipeline},
|
||||
finality_base::{
|
||||
engine::Engine, finality_proofs, BizinikiwiFinalityProof, BizinikiwiFinalityProofsStream,
|
||||
},
|
||||
};
|
||||
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use async_trait::async_trait;
|
||||
use bp_header_pez_chain::FinalityProof;
|
||||
use codec::Decode;
|
||||
use pez_finality_relay::{SourceClient, SourceClientBase};
|
||||
use futures::{
|
||||
select,
|
||||
stream::{try_unfold, Stream, StreamExt, TryStreamExt},
|
||||
};
|
||||
use num_traits::One;
|
||||
use relay_bizinikiwi_client::{BlockNumberOf, BlockWithJustification, Client, Error, HeaderOf};
|
||||
use relay_utils::{relay_loop::Client as RelayClient, UniqueSaturatedInto};
|
||||
|
||||
/// Shared updatable reference to the maximal header number that we want to sync from the source.
|
||||
pub type RequiredHeaderNumberRef<C> = Arc<Mutex<<C as pezbp_runtime::Chain>::BlockNumber>>;
|
||||
|
||||
/// Bizinikiwi node as finality source.
|
||||
pub struct BizinikiwiFinalitySource<P: BizinikiwiFinalitySyncPipeline, SourceClnt> {
|
||||
client: SourceClnt,
|
||||
maximal_header_number: Option<RequiredHeaderNumberRef<P::SourceChain>>,
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiFinalitySyncPipeline, SourceClnt: Client<P::SourceChain>>
|
||||
BizinikiwiFinalitySource<P, SourceClnt>
|
||||
{
|
||||
/// Create new headers source using given client.
|
||||
pub fn new(
|
||||
client: SourceClnt,
|
||||
maximal_header_number: Option<RequiredHeaderNumberRef<P::SourceChain>>,
|
||||
) -> Self {
|
||||
BizinikiwiFinalitySource { client, maximal_header_number }
|
||||
}
|
||||
|
||||
/// Returns reference to the underlying RPC client.
|
||||
pub fn client(&self) -> &SourceClnt {
|
||||
&self.client
|
||||
}
|
||||
|
||||
/// Returns best finalized block number.
|
||||
pub async fn on_chain_best_finalized_block_number(
|
||||
&self,
|
||||
) -> Result<BlockNumberOf<P::SourceChain>, Error> {
|
||||
// we **CAN** continue to relay finality proofs if source node is out of sync, because
|
||||
// target node may be missing proofs that are already available at the source
|
||||
self.client.best_finalized_header_number().await
|
||||
}
|
||||
|
||||
/// Return header and its justification of the given block or its descendant that
|
||||
/// has a GRANDPA justification.
|
||||
///
|
||||
/// This method is optimized for cases when `block_number` is close to the best finalized
|
||||
/// chain block.
|
||||
pub async fn prove_block_finality(
|
||||
&self,
|
||||
block_number: BlockNumberOf<P::SourceChain>,
|
||||
) -> Result<
|
||||
(relay_bizinikiwi_client::SyncHeader<HeaderOf<P::SourceChain>>, BizinikiwiFinalityProof<P>),
|
||||
Error,
|
||||
> {
|
||||
// first, subscribe to proofs
|
||||
let next_persistent_proof =
|
||||
self.persistent_proofs_stream(block_number + One::one()).await?.fuse();
|
||||
let next_ephemeral_proof = self.ephemeral_proofs_stream(block_number).await?.fuse();
|
||||
|
||||
// in perfect world we'll need to return justfication for the requested `block_number`
|
||||
let (header, maybe_proof) = self.header_and_finality_proof(block_number).await?;
|
||||
if let Some(proof) = maybe_proof {
|
||||
return Ok((header, proof));
|
||||
}
|
||||
|
||||
// otherwise we don't care which header to return, so let's select first
|
||||
futures::pin_mut!(next_persistent_proof, next_ephemeral_proof);
|
||||
loop {
|
||||
select! {
|
||||
maybe_header_and_proof = next_persistent_proof.next() => match maybe_header_and_proof {
|
||||
Some(header_and_proof) => return header_and_proof,
|
||||
None => continue,
|
||||
},
|
||||
maybe_header_and_proof = next_ephemeral_proof.next() => match maybe_header_and_proof {
|
||||
Some(header_and_proof) => return header_and_proof,
|
||||
None => continue,
|
||||
},
|
||||
complete => return Err(Error::FinalityProofNotFound(block_number.unique_saturated_into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns stream of headers and their persistent proofs, starting from given block.
|
||||
async fn persistent_proofs_stream(
|
||||
&self,
|
||||
block_number: BlockNumberOf<P::SourceChain>,
|
||||
) -> Result<
|
||||
impl Stream<
|
||||
Item = Result<
|
||||
(
|
||||
relay_bizinikiwi_client::SyncHeader<HeaderOf<P::SourceChain>>,
|
||||
BizinikiwiFinalityProof<P>,
|
||||
),
|
||||
Error,
|
||||
>,
|
||||
>,
|
||||
Error,
|
||||
> {
|
||||
let client = self.client.clone();
|
||||
let best_finalized_block_number = client.best_finalized_header_number().await?;
|
||||
Ok(try_unfold((client, block_number), move |(client, current_block_number)| async move {
|
||||
// if we've passed the `best_finalized_block_number`, we no longer need persistent
|
||||
// justifications
|
||||
if current_block_number > best_finalized_block_number {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let (header, maybe_proof) =
|
||||
header_and_finality_proof::<P>(&client, current_block_number).await?;
|
||||
let next_block_number = current_block_number + One::one();
|
||||
let next_state = (client, next_block_number);
|
||||
|
||||
Ok(Some((maybe_proof.map(|proof| (header, proof)), next_state)))
|
||||
})
|
||||
.try_filter_map(|maybe_result| async { Ok(maybe_result) }))
|
||||
}
|
||||
|
||||
/// Returns stream of headers and their ephemeral proofs, starting from given block.
|
||||
async fn ephemeral_proofs_stream(
|
||||
&self,
|
||||
block_number: BlockNumberOf<P::SourceChain>,
|
||||
) -> Result<
|
||||
impl Stream<
|
||||
Item = Result<
|
||||
(
|
||||
relay_bizinikiwi_client::SyncHeader<HeaderOf<P::SourceChain>>,
|
||||
BizinikiwiFinalityProof<P>,
|
||||
),
|
||||
Error,
|
||||
>,
|
||||
>,
|
||||
Error,
|
||||
> {
|
||||
let client = self.client.clone();
|
||||
Ok(self.finality_proofs().await?.map(Ok).try_filter_map(move |proof| {
|
||||
let client = client.clone();
|
||||
async move {
|
||||
if proof.target_header_number() < block_number {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let header = client.header_by_number(proof.target_header_number()).await?;
|
||||
Ok(Some((header.into(), proof)))
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiFinalitySyncPipeline, SourceClnt: Clone> Clone
|
||||
for BizinikiwiFinalitySource<P, SourceClnt>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
BizinikiwiFinalitySource {
|
||||
client: self.client.clone(),
|
||||
maximal_header_number: self.maximal_header_number.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: BizinikiwiFinalitySyncPipeline, SourceClnt: Client<P::SourceChain>> RelayClient
|
||||
for BizinikiwiFinalitySource<P, SourceClnt>
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), Error> {
|
||||
self.client.reconnect().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: BizinikiwiFinalitySyncPipeline, SourceClnt: Client<P::SourceChain>>
|
||||
SourceClientBase<FinalitySyncPipelineAdapter<P>> for BizinikiwiFinalitySource<P, SourceClnt>
|
||||
{
|
||||
type FinalityProofsStream = BizinikiwiFinalityProofsStream<P>;
|
||||
|
||||
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, Error> {
|
||||
finality_proofs::<P>(&self.client).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: BizinikiwiFinalitySyncPipeline, SourceClnt: Client<P::SourceChain>>
|
||||
SourceClient<FinalitySyncPipelineAdapter<P>> for BizinikiwiFinalitySource<P, SourceClnt>
|
||||
{
|
||||
async fn best_finalized_block_number(&self) -> Result<BlockNumberOf<P::SourceChain>, Error> {
|
||||
let mut finalized_header_number = self.on_chain_best_finalized_block_number().await?;
|
||||
// never return block number larger than requested. This way we'll never sync headers
|
||||
// past `maximal_header_number`
|
||||
if let Some(ref maximal_header_number) = self.maximal_header_number {
|
||||
let maximal_header_number = *maximal_header_number.lock().await;
|
||||
if finalized_header_number > maximal_header_number {
|
||||
finalized_header_number = maximal_header_number;
|
||||
}
|
||||
}
|
||||
Ok(finalized_header_number)
|
||||
}
|
||||
|
||||
async fn header_and_finality_proof(
|
||||
&self,
|
||||
number: BlockNumberOf<P::SourceChain>,
|
||||
) -> Result<
|
||||
(
|
||||
relay_bizinikiwi_client::SyncHeader<HeaderOf<P::SourceChain>>,
|
||||
Option<BizinikiwiFinalityProof<P>>,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
header_and_finality_proof::<P>(&self.client, number).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn header_and_finality_proof<P: BizinikiwiFinalitySyncPipeline>(
|
||||
client: &impl Client<P::SourceChain>,
|
||||
number: BlockNumberOf<P::SourceChain>,
|
||||
) -> Result<
|
||||
(
|
||||
relay_bizinikiwi_client::SyncHeader<HeaderOf<P::SourceChain>>,
|
||||
Option<BizinikiwiFinalityProof<P>>,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let header_hash = client.header_hash_by_number(number).await?;
|
||||
let signed_block = client.block_by_hash(header_hash).await?;
|
||||
|
||||
let justification = signed_block
|
||||
.justification(P::FinalityEngine::ID)
|
||||
.map(|raw_justification| {
|
||||
BizinikiwiFinalityProof::<P>::decode(&mut raw_justification.as_slice())
|
||||
})
|
||||
.transpose()
|
||||
.map_err(Error::ResponseParseFailed)?;
|
||||
|
||||
Ok((signed_block.header().into(), justification))
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// 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/>.
|
||||
|
||||
//! Bizinikiwi client as Bizinikiwi finality proof target.
|
||||
|
||||
use crate::{
|
||||
finality::{
|
||||
FinalitySyncPipelineAdapter, SubmitFinalityProofCallBuilder, BizinikiwiFinalitySyncPipeline,
|
||||
},
|
||||
finality_base::{best_synced_header_id, engine::Engine, BizinikiwiFinalityProof},
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use pezbp_runtime::BlockNumberOf;
|
||||
use pez_finality_relay::TargetClient;
|
||||
use relay_bizinikiwi_client::{
|
||||
AccountIdOf, AccountKeyPairOf, Chain, Client, Error, HeaderIdOf, HeaderOf, SyncHeader,
|
||||
TransactionEra, TransactionTracker, UnsignedTransaction,
|
||||
};
|
||||
use relay_utils::relay_loop::Client as RelayClient;
|
||||
use pezsp_core::Pair;
|
||||
use pezsp_runtime::traits::Header;
|
||||
|
||||
/// Bizinikiwi client as Bizinikiwi finality target.
|
||||
pub struct BizinikiwiFinalityTarget<P: BizinikiwiFinalitySyncPipeline, TargetClnt> {
|
||||
client: TargetClnt,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiFinalitySyncPipeline, TargetClnt: Client<P::TargetChain>>
|
||||
BizinikiwiFinalityTarget<P, TargetClnt>
|
||||
{
|
||||
/// Create new Bizinikiwi headers target.
|
||||
pub fn new(
|
||||
client: TargetClnt,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
|
||||
) -> Self {
|
||||
BizinikiwiFinalityTarget { client, transaction_params }
|
||||
}
|
||||
|
||||
/// Ensure that the bridge pezpallet at target chain is active.
|
||||
pub async fn ensure_pallet_active(&self) -> Result<(), Error> {
|
||||
let is_halted = P::FinalityEngine::is_halted(&self.client).await?;
|
||||
if is_halted {
|
||||
return Err(Error::BridgePalletIsHalted);
|
||||
}
|
||||
|
||||
let is_initialized = P::FinalityEngine::is_initialized(&self.client).await?;
|
||||
if !is_initialized {
|
||||
return Err(Error::BridgePalletIsNotInitialized);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiFinalitySyncPipeline, TargetClnt: Clone> Clone
|
||||
for BizinikiwiFinalityTarget<P, TargetClnt>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
BizinikiwiFinalityTarget {
|
||||
client: self.client.clone(),
|
||||
transaction_params: self.transaction_params.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: BizinikiwiFinalitySyncPipeline, TargetClnt: Client<P::TargetChain>> RelayClient
|
||||
for BizinikiwiFinalityTarget<P, TargetClnt>
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), Error> {
|
||||
self.client.reconnect().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: BizinikiwiFinalitySyncPipeline, TargetClnt: Client<P::TargetChain>>
|
||||
TargetClient<FinalitySyncPipelineAdapter<P>> for BizinikiwiFinalityTarget<P, TargetClnt>
|
||||
where
|
||||
AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TargetChain> as Pair>::Public>,
|
||||
{
|
||||
type TransactionTracker = TransactionTracker<P::TargetChain, TargetClnt>;
|
||||
|
||||
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 bridge pezpallet at target chain is halted
|
||||
self.ensure_pallet_active().await?;
|
||||
|
||||
Ok(best_synced_header_id::<P::SourceChain, P::TargetChain>(
|
||||
&self.client,
|
||||
self.client.best_header().await?.hash(),
|
||||
)
|
||||
.await?
|
||||
.ok_or(Error::BridgePalletIsNotInitialized)?)
|
||||
}
|
||||
|
||||
async fn free_source_headers_interval(
|
||||
&self,
|
||||
) -> Result<Option<BlockNumberOf<P::SourceChain>>, Self::Error> {
|
||||
Ok(self
|
||||
.client
|
||||
.state_call(
|
||||
self.client.best_header().await?.hash(),
|
||||
P::SourceChain::FREE_HEADERS_INTERVAL_METHOD.into(),
|
||||
(),
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
tracing::info!(
|
||||
target: "bridge",
|
||||
error=?e,
|
||||
method=%P::SourceChain::FREE_HEADERS_INTERVAL_METHOD,
|
||||
target=%P::TargetChain::NAME,
|
||||
"Call has failed. Treating as `None`"
|
||||
);
|
||||
None
|
||||
}))
|
||||
}
|
||||
|
||||
async fn submit_finality_proof(
|
||||
&self,
|
||||
header: SyncHeader<HeaderOf<P::SourceChain>>,
|
||||
mut proof: BizinikiwiFinalityProof<P>,
|
||||
is_free_execution_expected: bool,
|
||||
) -> Result<Self::TransactionTracker, Error> {
|
||||
// verify and runtime module at target chain may require optimized finality proof
|
||||
let context =
|
||||
P::FinalityEngine::verify_and_optimize_proof(&self.client, &header, &mut proof).await?;
|
||||
|
||||
// if free execution is expected, but the call size/weight exceeds hardcoded limits, the
|
||||
// runtime may still accept the proof, but it may have some cost for relayer. Let's check
|
||||
// it here to avoid losing relayer funds
|
||||
if is_free_execution_expected {
|
||||
let extras = P::FinalityEngine::check_max_expected_call_limits(&header, &proof);
|
||||
if extras.is_weight_limit_exceeded || extras.extra_size != 0 {
|
||||
return Err(Error::FinalityProofWeightLimitExceeded { extras });
|
||||
}
|
||||
}
|
||||
|
||||
// now we may submit optimized finality proof
|
||||
let mortality = self.transaction_params.mortality;
|
||||
let call = P::SubmitFinalityProofCallBuilder::build_submit_finality_proof_call(
|
||||
header,
|
||||
proof,
|
||||
is_free_execution_expected,
|
||||
context,
|
||||
);
|
||||
self.client
|
||||
.submit_and_watch_signed_extrinsic(
|
||||
&self.transaction_params.signer,
|
||||
move |best_block_id, transaction_nonce| {
|
||||
Ok(UnsignedTransaction::new(call.into(), transaction_nonce)
|
||||
.era(TransactionEra::new(best_block_id, mortality)))
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
// 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/>.
|
||||
|
||||
//! Support of different finality engines, available in Bizinikiwi.
|
||||
|
||||
use crate::error::Error;
|
||||
use async_trait::async_trait;
|
||||
use bp_header_pez_chain::{
|
||||
justification::{
|
||||
verify_and_optimize_justification, GrandpaEquivocationsFinder, GrandpaJustification,
|
||||
JustificationVerificationContext,
|
||||
},
|
||||
AuthoritySet, ConsensusLogReader, FinalityProof, FindEquivocations, GrandpaConsensusLogReader,
|
||||
HeaderFinalityInfo, HeaderGrandpaInfo, StoredHeaderGrandpaInfo, SubmitFinalityProofCallExtras,
|
||||
};
|
||||
use pezbp_runtime::{BasicOperatingMode, HeaderIdProvider, OperatingMode};
|
||||
use codec::{Decode, Encode};
|
||||
use futures::stream::StreamExt;
|
||||
use num_traits::{One, Zero};
|
||||
use relay_bizinikiwi_client::{
|
||||
BlockNumberOf, Chain, ChainWithGrandpa, Client, Error as BizinikiwiError, HashOf, HeaderOf,
|
||||
Subscription,
|
||||
};
|
||||
use pezsp_consensus_grandpa::{AuthorityList as GrandpaAuthoritiesSet, GRANDPA_ENGINE_ID};
|
||||
use pezsp_core::{storage::StorageKey, Bytes};
|
||||
use pezsp_runtime::{scale_info::TypeInfo, traits::Header, ConsensusEngineId};
|
||||
use std::{fmt::Debug, marker::PhantomData};
|
||||
|
||||
/// Finality engine, used by the Bizinikiwi chain.
|
||||
#[async_trait]
|
||||
pub trait Engine<C: Chain>: Send {
|
||||
/// Unique consensus engine identifier.
|
||||
const ID: ConsensusEngineId;
|
||||
/// A reader that can extract the consensus log from the header digest and interpret it.
|
||||
type ConsensusLogReader: ConsensusLogReader;
|
||||
/// Type of finality proofs, used by consensus engine.
|
||||
type FinalityProof: FinalityProof<HashOf<C>, BlockNumberOf<C>> + Decode + Encode;
|
||||
/// The context needed for verifying finality proofs.
|
||||
type FinalityVerificationContext: Debug + Send;
|
||||
/// The type of the equivocation proof used by the consensus engine.
|
||||
type EquivocationProof: Clone + Debug + Send + Sync;
|
||||
/// The equivocations finder.
|
||||
type EquivocationsFinder: FindEquivocations<
|
||||
Self::FinalityProof,
|
||||
Self::FinalityVerificationContext,
|
||||
Self::EquivocationProof,
|
||||
>;
|
||||
/// The type of the key owner proof used by the consensus engine.
|
||||
type KeyOwnerProof: Send;
|
||||
/// Type of bridge pezpallet initialization data.
|
||||
type InitializationData: Debug + Send + Sync + 'static;
|
||||
/// Type of bridge pezpallet operating mode.
|
||||
type OperatingMode: OperatingMode + 'static;
|
||||
|
||||
/// Returns storage at the bridged (target) chain that corresponds to some value that is
|
||||
/// missing from the storage until bridge pezpallet is initialized.
|
||||
///
|
||||
/// Note that we don't care about type of the value - just if it present or not.
|
||||
fn is_initialized_key() -> StorageKey;
|
||||
|
||||
/// Returns `Ok(true)` if finality pezpallet at the bridged chain has already been initialized.
|
||||
async fn is_initialized<TargetChain: Chain>(
|
||||
target_client: &impl Client<TargetChain>,
|
||||
) -> Result<bool, BizinikiwiError> {
|
||||
Ok(target_client
|
||||
.raw_storage_value(target_client.best_header_hash().await?, Self::is_initialized_key())
|
||||
.await?
|
||||
.is_some())
|
||||
}
|
||||
|
||||
/// Returns storage key at the bridged (target) chain that corresponds to the variable
|
||||
/// that holds the operating mode of the pezpallet.
|
||||
fn pezpallet_operating_mode_key() -> StorageKey;
|
||||
|
||||
/// Returns `Ok(true)` if finality pezpallet at the bridged chain is halted.
|
||||
async fn is_halted<TargetChain: Chain>(
|
||||
target_client: &impl Client<TargetChain>,
|
||||
) -> Result<bool, BizinikiwiError> {
|
||||
Ok(target_client
|
||||
.storage_value::<Self::OperatingMode>(
|
||||
target_client.best_header_hash().await?,
|
||||
Self::pezpallet_operating_mode_key(),
|
||||
)
|
||||
.await?
|
||||
.map(|operating_mode| operating_mode.is_halted())
|
||||
.unwrap_or(false))
|
||||
}
|
||||
|
||||
/// A method to subscribe to encoded finality proofs, given source client.
|
||||
async fn source_finality_proofs(
|
||||
source_client: &impl Client<C>,
|
||||
) -> Result<Subscription<Bytes>, BizinikiwiError>;
|
||||
|
||||
/// Verify and optimize finality proof before sending it to the target node.
|
||||
///
|
||||
/// Apart from optimization, we expect this method to perform all required checks
|
||||
/// that the `header` and `proof` are valid at the current state of the target chain.
|
||||
async fn verify_and_optimize_proof<TargetChain: Chain>(
|
||||
target_client: &impl Client<TargetChain>,
|
||||
header: &C::Header,
|
||||
proof: &mut Self::FinalityProof,
|
||||
) -> Result<Self::FinalityVerificationContext, BizinikiwiError>;
|
||||
|
||||
/// Checks whether the given `header` and its finality `proof` fit the maximal expected
|
||||
/// call limits (size and weight).
|
||||
fn check_max_expected_call_limits(
|
||||
header: &C::Header,
|
||||
proof: &Self::FinalityProof,
|
||||
) -> SubmitFinalityProofCallExtras;
|
||||
|
||||
/// Prepare initialization data for the finality bridge pezpallet.
|
||||
async fn prepare_initialization_data(
|
||||
client: impl Client<C>,
|
||||
) -> Result<Self::InitializationData, Error<HashOf<C>, BlockNumberOf<C>>>;
|
||||
|
||||
/// Get the context needed for validating a finality proof.
|
||||
async fn finality_verification_context<TargetChain: Chain>(
|
||||
target_client: &impl Client<TargetChain>,
|
||||
at: HashOf<TargetChain>,
|
||||
) -> Result<Self::FinalityVerificationContext, BizinikiwiError>;
|
||||
|
||||
/// Returns the finality info associated to the source headers synced with the target
|
||||
/// at the provided block.
|
||||
async fn synced_headers_finality_info<TargetChain: Chain>(
|
||||
target_client: &impl Client<TargetChain>,
|
||||
at: TargetChain::Hash,
|
||||
) -> Result<
|
||||
Vec<HeaderFinalityInfo<Self::FinalityProof, Self::FinalityVerificationContext>>,
|
||||
BizinikiwiError,
|
||||
>;
|
||||
|
||||
/// Generate key ownership proof for the provided equivocation.
|
||||
async fn generate_source_key_ownership_proof(
|
||||
source_client: &impl Client<C>,
|
||||
at: C::Hash,
|
||||
equivocation: &Self::EquivocationProof,
|
||||
) -> Result<Self::KeyOwnerProof, BizinikiwiError>;
|
||||
}
|
||||
|
||||
/// GRANDPA finality engine.
|
||||
pub struct Grandpa<C>(PhantomData<C>);
|
||||
|
||||
impl<C: ChainWithGrandpa> Grandpa<C> {
|
||||
/// Read header by hash from the source client.
|
||||
async fn source_header(
|
||||
source_client: &impl Client<C>,
|
||||
header_hash: C::Hash,
|
||||
) -> Result<C::Header, Error<HashOf<C>, BlockNumberOf<C>>> {
|
||||
source_client
|
||||
.header_by_hash(header_hash)
|
||||
.await
|
||||
.map_err(|err| Error::RetrieveHeader(C::NAME, header_hash, err))
|
||||
}
|
||||
|
||||
/// Read GRANDPA authorities set at given header.
|
||||
async fn source_authorities_set(
|
||||
source_client: &impl Client<C>,
|
||||
header_hash: C::Hash,
|
||||
) -> Result<GrandpaAuthoritiesSet, Error<HashOf<C>, BlockNumberOf<C>>> {
|
||||
const SUB_API_GRANDPA_AUTHORITIES: &str = "GrandpaApi_grandpa_authorities";
|
||||
|
||||
source_client
|
||||
.state_call(header_hash, SUB_API_GRANDPA_AUTHORITIES.to_string(), ())
|
||||
.await
|
||||
.map_err(|err| Error::RetrieveAuthorities(C::NAME, header_hash, err))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C: ChainWithGrandpa> Engine<C> for Grandpa<C> {
|
||||
const ID: ConsensusEngineId = GRANDPA_ENGINE_ID;
|
||||
type ConsensusLogReader = GrandpaConsensusLogReader<<C::Header as Header>::Number>;
|
||||
type FinalityProof = GrandpaJustification<HeaderOf<C>>;
|
||||
type FinalityVerificationContext = JustificationVerificationContext;
|
||||
type EquivocationProof = pezsp_consensus_grandpa::EquivocationProof<HashOf<C>, BlockNumberOf<C>>;
|
||||
type EquivocationsFinder = GrandpaEquivocationsFinder<C>;
|
||||
type KeyOwnerProof = C::KeyOwnerProof;
|
||||
type InitializationData = bp_header_pez_chain::InitializationData<C::Header>;
|
||||
type OperatingMode = BasicOperatingMode;
|
||||
|
||||
fn is_initialized_key() -> StorageKey {
|
||||
bp_header_pez_chain::storage_keys::best_finalized_key(C::WITH_CHAIN_GRANDPA_PALLET_NAME)
|
||||
}
|
||||
|
||||
fn pezpallet_operating_mode_key() -> StorageKey {
|
||||
bp_header_pez_chain::storage_keys::pezpallet_operating_mode_key(C::WITH_CHAIN_GRANDPA_PALLET_NAME)
|
||||
}
|
||||
|
||||
async fn source_finality_proofs(
|
||||
client: &impl Client<C>,
|
||||
) -> Result<Subscription<Bytes>, BizinikiwiError> {
|
||||
client.subscribe_grandpa_finality_justifications().await
|
||||
}
|
||||
|
||||
async fn verify_and_optimize_proof<TargetChain: Chain>(
|
||||
target_client: &impl Client<TargetChain>,
|
||||
header: &C::Header,
|
||||
proof: &mut Self::FinalityProof,
|
||||
) -> Result<Self::FinalityVerificationContext, BizinikiwiError> {
|
||||
let verification_context = Grandpa::<C>::finality_verification_context(
|
||||
target_client,
|
||||
target_client.best_header().await?.hash(),
|
||||
)
|
||||
.await?;
|
||||
// we're risking with race here - we have decided to submit justification some time ago and
|
||||
// actual authorities set (which we have read now) may have changed, so this
|
||||
// `optimize_justification` may fail. But if target chain is configured properly, it'll fail
|
||||
// anyway, after we submit transaction and failing earlier is better. So - it is fine
|
||||
verify_and_optimize_justification(
|
||||
(header.hash(), *header.number()),
|
||||
&verification_context,
|
||||
proof,
|
||||
)
|
||||
.map(|_| verification_context)
|
||||
.map_err(|e| {
|
||||
BizinikiwiError::Custom(format!(
|
||||
"Failed to optimize {} GRANDPA jutification for header {:?}: {:?}",
|
||||
C::NAME,
|
||||
header.id(),
|
||||
e,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn check_max_expected_call_limits(
|
||||
header: &C::Header,
|
||||
proof: &Self::FinalityProof,
|
||||
) -> SubmitFinalityProofCallExtras {
|
||||
bp_header_pez_chain::submit_finality_proof_limits_extras::<C>(header, proof)
|
||||
}
|
||||
|
||||
/// Prepare initialization data for the GRANDPA verifier pezpallet.
|
||||
async fn prepare_initialization_data(
|
||||
source_client: impl Client<C>,
|
||||
) -> Result<Self::InitializationData, Error<HashOf<C>, BlockNumberOf<C>>> {
|
||||
// In ideal world we just need to get best finalized header and then to read GRANDPA
|
||||
// authorities set (`pezpallet_grandpa::CurrentSetId` + `GrandpaApi::grandpa_authorities()`) at
|
||||
// this header.
|
||||
//
|
||||
// But now there are problems with this approach - `CurrentSetId` may return invalid value.
|
||||
// So here we're waiting for the next justification, read the authorities set and then try
|
||||
// to figure out the set id with bruteforce.
|
||||
let mut justifications = Self::source_finality_proofs(&source_client)
|
||||
.await
|
||||
.map_err(|err| Error::Subscribe(C::NAME, err))?;
|
||||
// Read next justification - the header that it finalizes will be used as initial header.
|
||||
let justification = justifications
|
||||
.next()
|
||||
.await
|
||||
.ok_or(Error::ReadJustificationStreamEnded(C::NAME))?;
|
||||
|
||||
// Read initial header.
|
||||
let justification: GrandpaJustification<C::Header> =
|
||||
Decode::decode(&mut &justification.0[..])
|
||||
.map_err(|err| Error::DecodeJustification(C::NAME, err))?;
|
||||
|
||||
let (initial_header_hash, initial_header_number) =
|
||||
(justification.commit.target_hash, justification.commit.target_number);
|
||||
|
||||
let initial_header = Self::source_header(&source_client, initial_header_hash).await?;
|
||||
tracing::trace!(
|
||||
target: "bridge",
|
||||
node=%C::NAME,
|
||||
%initial_header_number,
|
||||
%initial_header_hash,
|
||||
"Selected initial header"
|
||||
);
|
||||
|
||||
// Read GRANDPA authorities set at initial header.
|
||||
let initial_authorities_set =
|
||||
Self::source_authorities_set(&source_client, initial_header_hash).await?;
|
||||
tracing::trace!(
|
||||
target: "bridge",
|
||||
node=%C::NAME,
|
||||
?initial_authorities_set,
|
||||
"Selected"
|
||||
);
|
||||
|
||||
// If initial header changes the GRANDPA authorities set, then we need previous authorities
|
||||
// to verify justification.
|
||||
let mut authorities_for_verification = initial_authorities_set.clone();
|
||||
let scheduled_change = GrandpaConsensusLogReader::<BlockNumberOf<C>>::find_scheduled_change(
|
||||
initial_header.digest(),
|
||||
);
|
||||
assert!(
|
||||
scheduled_change.as_ref().map(|c| c.delay.is_zero()).unwrap_or(true),
|
||||
"GRANDPA authorities change at {} scheduled to happen in {:?} blocks. We expect \
|
||||
regular change to have zero delay",
|
||||
initial_header_hash,
|
||||
scheduled_change.as_ref().map(|c| c.delay),
|
||||
);
|
||||
let schedules_change = scheduled_change.is_some();
|
||||
if schedules_change {
|
||||
authorities_for_verification =
|
||||
Self::source_authorities_set(&source_client, *initial_header.parent_hash()).await?;
|
||||
tracing::trace!(
|
||||
target: "bridge",
|
||||
node=%C::NAME,
|
||||
previous_set=?authorities_for_verification,
|
||||
"Selected header is scheduling GRANDPA authorities set changes."
|
||||
);
|
||||
}
|
||||
|
||||
// Now let's try to guess authorities set id by verifying justification.
|
||||
let mut initial_authorities_set_id = 0;
|
||||
let mut min_possible_block_number = C::BlockNumber::zero();
|
||||
loop {
|
||||
tracing::trace!(
|
||||
target: "bridge",
|
||||
node=%C::NAME,
|
||||
authorities_set_id=%initial_authorities_set_id,
|
||||
"Trying GRANDPA authorities set id"
|
||||
);
|
||||
|
||||
let is_valid_set_id = verify_and_optimize_justification(
|
||||
(initial_header_hash, initial_header_number),
|
||||
&AuthoritySet {
|
||||
authorities: authorities_for_verification.clone(),
|
||||
set_id: initial_authorities_set_id,
|
||||
}
|
||||
.try_into()
|
||||
.map_err(|_| {
|
||||
Error::ReadInvalidAuthorities(C::NAME, authorities_for_verification.clone())
|
||||
})?,
|
||||
&mut justification.clone(),
|
||||
)
|
||||
.is_ok();
|
||||
|
||||
if is_valid_set_id {
|
||||
break;
|
||||
}
|
||||
|
||||
initial_authorities_set_id += 1;
|
||||
min_possible_block_number += One::one();
|
||||
if min_possible_block_number > initial_header_number {
|
||||
// there can't be more authorities set changes than headers => if we have reached
|
||||
// `initial_block_number` and still have not found correct value of
|
||||
// `initial_authorities_set_id`, then something else is broken => fail
|
||||
return Err(Error::GuessInitialAuthorities(C::NAME, initial_header_number));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bp_header_pez_chain::InitializationData {
|
||||
header: Box::new(initial_header),
|
||||
authority_list: initial_authorities_set,
|
||||
set_id: if schedules_change {
|
||||
initial_authorities_set_id + 1
|
||||
} else {
|
||||
initial_authorities_set_id
|
||||
},
|
||||
operating_mode: BasicOperatingMode::Normal,
|
||||
})
|
||||
}
|
||||
|
||||
async fn finality_verification_context<TargetChain: Chain>(
|
||||
target_client: &impl Client<TargetChain>,
|
||||
at: HashOf<TargetChain>,
|
||||
) -> Result<Self::FinalityVerificationContext, BizinikiwiError> {
|
||||
let current_authority_set_key = bp_header_pez_chain::storage_keys::current_authority_set_key(
|
||||
C::WITH_CHAIN_GRANDPA_PALLET_NAME,
|
||||
);
|
||||
let authority_set: AuthoritySet = target_client
|
||||
.storage_value(at, current_authority_set_key)
|
||||
.await?
|
||||
.map(Ok)
|
||||
.unwrap_or(Err(BizinikiwiError::Custom(format!(
|
||||
"{} `CurrentAuthoritySet` is missing from the {} storage",
|
||||
C::NAME,
|
||||
TargetChain::NAME,
|
||||
))))?;
|
||||
|
||||
authority_set.try_into().map_err(|e| {
|
||||
BizinikiwiError::Custom(format!(
|
||||
"{} `CurrentAuthoritySet` from the {} storage is invalid: {e:?}",
|
||||
C::NAME,
|
||||
TargetChain::NAME,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
async fn synced_headers_finality_info<TargetChain: Chain>(
|
||||
target_client: &impl Client<TargetChain>,
|
||||
at: TargetChain::Hash,
|
||||
) -> Result<Vec<HeaderGrandpaInfo<HeaderOf<C>>>, BizinikiwiError> {
|
||||
let stored_headers_grandpa_info: Vec<StoredHeaderGrandpaInfo<HeaderOf<C>>> = target_client
|
||||
.state_call(at, C::SYNCED_HEADERS_GRANDPA_INFO_METHOD.to_string(), ())
|
||||
.await?;
|
||||
|
||||
let mut headers_grandpa_info = vec![];
|
||||
for stored_header_grandpa_info in stored_headers_grandpa_info {
|
||||
headers_grandpa_info.push(stored_header_grandpa_info.try_into().map_err(|e| {
|
||||
BizinikiwiError::Custom(format!(
|
||||
"{} `AuthoritySet` synced to {} is invalid: {e:?} ",
|
||||
C::NAME,
|
||||
TargetChain::NAME,
|
||||
))
|
||||
})?);
|
||||
}
|
||||
|
||||
Ok(headers_grandpa_info)
|
||||
}
|
||||
|
||||
async fn generate_source_key_ownership_proof(
|
||||
source_client: &impl Client<C>,
|
||||
at: C::Hash,
|
||||
equivocation: &Self::EquivocationProof,
|
||||
) -> Result<Self::KeyOwnerProof, BizinikiwiError> {
|
||||
let set_id = equivocation.set_id();
|
||||
let offender = equivocation.offender();
|
||||
|
||||
let opaque_key_owner_proof = source_client
|
||||
.generate_grandpa_key_ownership_proof(at, set_id, offender.clone())
|
||||
.await?
|
||||
.ok_or(BizinikiwiError::Custom(format!(
|
||||
"Couldn't get GRANDPA key ownership proof from {} at block: {at} \
|
||||
for offender: {:?}, set_id: {set_id} ",
|
||||
C::NAME,
|
||||
offender.clone(),
|
||||
)))?;
|
||||
|
||||
let key_owner_proof =
|
||||
opaque_key_owner_proof.decode().ok_or(BizinikiwiError::Custom(format!(
|
||||
"Couldn't decode GRANDPA `OpaqueKeyOwnnershipProof` from {} at block: {at}
|
||||
to `{:?}` for offender: {:?}, set_id: {set_id}, at block: {at}",
|
||||
C::NAME,
|
||||
<C::KeyOwnerProof as TypeInfo>::type_info().path,
|
||||
offender.clone(),
|
||||
)))?;
|
||||
|
||||
Ok(key_owner_proof)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright 2019-2023 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/>.
|
||||
|
||||
//! Types and functions intended to ease adding of new Bizinikiwi -> Bizinikiwi
|
||||
//! finality pipelines.
|
||||
|
||||
pub mod engine;
|
||||
|
||||
use crate::finality_base::engine::Engine;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use pezbp_runtime::{HashOf, HeaderIdOf};
|
||||
use codec::Decode;
|
||||
use futures::{stream::unfold, Stream, StreamExt};
|
||||
use relay_bizinikiwi_client::{Chain, Client, Error};
|
||||
use std::{fmt::Debug, pin::Pin};
|
||||
|
||||
/// Bizinikiwi -> Bizinikiwi finality related pipeline.
|
||||
#[async_trait]
|
||||
pub trait BizinikiwiFinalityPipeline: 'static + Clone + Debug + Send + Sync {
|
||||
/// Headers of this chain are submitted to the `TargetChain`.
|
||||
type SourceChain: Chain;
|
||||
/// Headers of the `SourceChain` are submitted to this chain.
|
||||
type TargetChain: Chain;
|
||||
/// Finality engine.
|
||||
type FinalityEngine: Engine<Self::SourceChain>;
|
||||
}
|
||||
|
||||
/// Bizinikiwi finality proof. Specific to the used `FinalityEngine`.
|
||||
pub type BizinikiwiFinalityProof<P> = <<P as BizinikiwiFinalityPipeline>::FinalityEngine as Engine<
|
||||
<P as BizinikiwiFinalityPipeline>::SourceChain,
|
||||
>>::FinalityProof;
|
||||
|
||||
/// Bizinikiwi finality proofs stream.
|
||||
pub type BizinikiwiFinalityProofsStream<P> =
|
||||
Pin<Box<dyn Stream<Item = BizinikiwiFinalityProof<P>> + Send>>;
|
||||
|
||||
/// Subscribe to new finality proofs.
|
||||
pub async fn finality_proofs<P: BizinikiwiFinalityPipeline>(
|
||||
client: &impl Client<P::SourceChain>,
|
||||
) -> Result<BizinikiwiFinalityProofsStream<P>, Error> {
|
||||
Ok(unfold(
|
||||
P::FinalityEngine::source_finality_proofs(client).await?,
|
||||
move |mut subscription| async move {
|
||||
loop {
|
||||
let log_error = |err| {
|
||||
tracing::error!(
|
||||
target: "bridge",
|
||||
error=?err,
|
||||
source=%P::SourceChain::NAME,
|
||||
"Failed to read justification target from the justifications stream"
|
||||
);
|
||||
};
|
||||
|
||||
let next_justification = subscription.next().await?;
|
||||
|
||||
let decoded_justification =
|
||||
<P::FinalityEngine as Engine<P::SourceChain>>::FinalityProof::decode(
|
||||
&mut &next_justification[..],
|
||||
);
|
||||
|
||||
let justification = match decoded_justification {
|
||||
Ok(j) => j,
|
||||
Err(err) => {
|
||||
log_error(format!("decode failed with error {err:?}"));
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
return Some((justification, subscription));
|
||||
}
|
||||
},
|
||||
)
|
||||
.boxed())
|
||||
}
|
||||
|
||||
/// Get the id of the best `SourceChain` header known to the `TargetChain` at the provided
|
||||
/// target block using the exposed runtime API method.
|
||||
///
|
||||
/// The runtime API method should be `<TargetChain>FinalityApi::best_finalized()`.
|
||||
pub async fn best_synced_header_id<SourceChain, TargetChain>(
|
||||
target_client: &impl Client<TargetChain>,
|
||||
at: HashOf<TargetChain>,
|
||||
) -> Result<Option<HeaderIdOf<SourceChain>>, Error>
|
||||
where
|
||||
SourceChain: Chain,
|
||||
TargetChain: Chain,
|
||||
{
|
||||
// now let's read id of best finalized peer header at our best finalized block
|
||||
target_client
|
||||
.state_call(at, SourceChain::BEST_FINALIZED_HEADER_ID_METHOD.into(), ())
|
||||
.await
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// 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/>.
|
||||
|
||||
//! The library of bizinikiwi relay. contains some public codes to provide to bizinikiwi relay.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use relay_bizinikiwi_client::{Chain, ChainWithUtilityPallet, UtilityPallet};
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
// to avoid `pez_finality_relay` dependency in other crates
|
||||
pub use pez_finality_relay::HeadersToRelay;
|
||||
|
||||
pub mod cli;
|
||||
pub mod equivocation;
|
||||
pub mod error;
|
||||
pub mod finality;
|
||||
pub mod finality_base;
|
||||
pub mod messages;
|
||||
pub mod on_demand;
|
||||
pub mod teyrchains;
|
||||
|
||||
/// Transaction creation parameters.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TransactionParams<TS> {
|
||||
/// Transactions author.
|
||||
pub signer: TS,
|
||||
/// Transactions mortality.
|
||||
pub mortality: Option<u32>,
|
||||
}
|
||||
|
||||
/// Tagged relay account, which balance may be exposed as metrics by the relay.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TaggedAccount<AccountId> {
|
||||
/// Account, used to sign message (also headers and teyrchains) relay transactions from given
|
||||
/// bridged chain.
|
||||
Messages {
|
||||
/// Account id.
|
||||
id: AccountId,
|
||||
/// Name of the bridged chain, which sends us messages or delivery confirmations.
|
||||
bridged_chain: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl<AccountId> TaggedAccount<AccountId> {
|
||||
/// Returns reference to the account id.
|
||||
pub fn id(&self) -> &AccountId {
|
||||
match *self {
|
||||
TaggedAccount::Messages { ref id, .. } => id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns stringified account tag.
|
||||
pub fn tag(&self) -> String {
|
||||
match *self {
|
||||
TaggedAccount::Messages { ref bridged_chain, .. } => {
|
||||
format!("{bridged_chain}Messages")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Batch call builder.
|
||||
pub trait BatchCallBuilder<Call>: Clone + Send + Sync {
|
||||
/// Create batch call from given calls vector.
|
||||
fn build_batch_call(&self, _calls: Vec<Call>) -> Call;
|
||||
}
|
||||
|
||||
/// Batch call builder constructor.
|
||||
pub trait BatchCallBuilderConstructor<Call>: Clone {
|
||||
/// Call builder, used by this constructor.
|
||||
type CallBuilder: BatchCallBuilder<Call>;
|
||||
/// Create a new instance of a batch call builder.
|
||||
fn new_builder() -> Option<Self::CallBuilder>;
|
||||
}
|
||||
|
||||
/// Batch call builder based on `pezpallet-utility`.
|
||||
#[derive(Clone)]
|
||||
pub struct UtilityPalletBatchCallBuilder<C: Chain>(PhantomData<C>);
|
||||
|
||||
impl<C: Chain> BatchCallBuilder<C::Call> for UtilityPalletBatchCallBuilder<C>
|
||||
where
|
||||
C: ChainWithUtilityPallet,
|
||||
{
|
||||
fn build_batch_call(&self, calls: Vec<C::Call>) -> C::Call {
|
||||
C::UtilityPallet::build_batch_call(calls)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Chain> BatchCallBuilderConstructor<C::Call> for UtilityPalletBatchCallBuilder<C>
|
||||
where
|
||||
C: ChainWithUtilityPallet,
|
||||
{
|
||||
type CallBuilder = Self;
|
||||
|
||||
fn new_builder() -> Option<Self::CallBuilder> {
|
||||
Some(Self(Default::default()))
|
||||
}
|
||||
}
|
||||
|
||||
// A `BatchCallBuilderConstructor` that always returns `None`.
|
||||
impl<Call> BatchCallBuilderConstructor<Call> for () {
|
||||
type CallBuilder = ();
|
||||
fn new_builder() -> Option<Self::CallBuilder> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Dummy `BatchCallBuilder` implementation that must never be used outside
|
||||
// of the `impl BatchCallBuilderConstructor for ()` code.
|
||||
impl<Call> BatchCallBuilder<Call> for () {
|
||||
fn build_batch_call(&self, _calls: Vec<Call>) -> Call {
|
||||
unreachable!("never called, because ()::new_builder() returns None; qed")
|
||||
}
|
||||
}
|
||||
|
||||
/// Module for handling storage proofs compatibility.
|
||||
pub mod proofs {
|
||||
use pezbp_runtime::{HashOf, RawStorageProof};
|
||||
use relay_bizinikiwi_client::Chain;
|
||||
use pezsp_trie::StorageProof;
|
||||
|
||||
/// Converts proof to `RawStorageProof` type.
|
||||
pub fn to_raw_storage_proof<SourceChain: Chain>(
|
||||
proof: (StorageProof, HashOf<SourceChain>),
|
||||
) -> RawStorageProof {
|
||||
proof.0.into_iter_nodes().collect()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
// 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 Bizinikiwi-based chains.
|
||||
|
||||
use crate::TaggedAccount;
|
||||
|
||||
use bp_relayers::{RewardsAccountOwner, RewardsAccountParams};
|
||||
use codec::{Decode, EncodeLike};
|
||||
use pezframe_system::AccountInfo;
|
||||
use pez_messages_relay::Labeled;
|
||||
use pezpallet_balances::AccountData;
|
||||
use relay_bizinikiwi_client::{
|
||||
metrics::{FixedU128OrOne, FloatStorageValue, FloatStorageValueMetric},
|
||||
AccountIdOf, BalanceOf, Chain, ChainWithBalances, ChainWithMessages, ChainWithRewards, Client,
|
||||
Error as BizinikiwiError, NonceOf,
|
||||
};
|
||||
use relay_utils::metrics::{MetricsParams, StandaloneMetric};
|
||||
use pezsp_core::storage::StorageData;
|
||||
use pezsp_runtime::{FixedPointNumber, FixedU128};
|
||||
use std::{fmt::Debug, marker::PhantomData};
|
||||
|
||||
/// Add relay accounts balance metrics.
|
||||
pub async fn add_relay_balances_metrics<C: ChainWithBalances>(
|
||||
client: impl Client<C>,
|
||||
metrics: &MetricsParams,
|
||||
relay_accounts: &Vec<TaggedAccount<AccountIdOf<C>>>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
BalanceOf<C>: Into<u128> + std::fmt::Debug,
|
||||
{
|
||||
if relay_accounts.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// if `tokenDecimals` is missing from system properties, we'll be using
|
||||
let token_decimals = client
|
||||
.token_decimals()
|
||||
.await?
|
||||
.inspect(|token_decimals| {
|
||||
tracing::info!(target: "bridge", node=%C::NAME, %token_decimals, "Read `tokenDecimals`");
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
// turns out it is normal not to have this property - e.g. when pezkuwi binary is
|
||||
// started using `pezkuwi-local` chain. Let's use minimal nominal here
|
||||
tracing::info!(target: "bridge", node=%C::NAME, "Using default (zero) `tokenDecimals`");
|
||||
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,
|
||||
)
|
||||
})?;
|
||||
|
||||
for account in relay_accounts {
|
||||
let relay_account_balance_metric = FloatStorageValueMetric::new(
|
||||
AccountBalanceFromAccountInfo::<C> { token_decimals, _phantom: Default::default() },
|
||||
client.clone(),
|
||||
C::account_info_storage_key(account.id()),
|
||||
format!("at_{}_relay_{}_balance", C::NAME, account.tag()),
|
||||
format!("Balance of the {} relay account at the {}", account.tag(), C::NAME),
|
||||
)?;
|
||||
relay_account_balance_metric.register_and_spawn(&metrics.registry)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add relay accounts rewards metrics.
|
||||
pub async fn add_relay_rewards_metrics<C: ChainWithRewards, BC: ChainWithMessages, LaneId>(
|
||||
client: impl Client<C>,
|
||||
metrics: &MetricsParams,
|
||||
relay_accounts: &Vec<TaggedAccount<AccountIdOf<C>>>,
|
||||
lanes: &[LaneId],
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
C::RewardBalance: Into<u128> + std::fmt::Debug,
|
||||
C::Reward: From<RewardsAccountParams<LaneId>>,
|
||||
LaneId: Clone + Copy + Decode + EncodeLike + Send + Sync + Labeled,
|
||||
{
|
||||
if relay_accounts.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for account in relay_accounts {
|
||||
if let Some(_) = C::WITH_CHAIN_RELAYERS_PALLET_NAME {
|
||||
for lane in lanes {
|
||||
FloatStorageValueMetric::new(
|
||||
FixedU128OrOne,
|
||||
client.clone(),
|
||||
C::account_reward_storage_key(account.id(), RewardsAccountParams::new(*lane, BC::ID, RewardsAccountOwner::ThisChain)),
|
||||
format!("at_{}_relay_{}_reward_for_msgs_from_{}_on_lane_{}", C::NAME, account.tag(), BC::NAME, lane.label()),
|
||||
format!("Reward of the {} relay account at {} for delivering messages from {} on lane {:?}", account.tag(), C::NAME, BC::NAME, lane.label()),
|
||||
)?.register_and_spawn(&metrics.registry)?;
|
||||
|
||||
FloatStorageValueMetric::new(
|
||||
FixedU128OrOne,
|
||||
client.clone(),
|
||||
C::account_reward_storage_key(account.id(), RewardsAccountParams::new(*lane, BC::ID, RewardsAccountOwner::BridgedChain)),
|
||||
format!("at_{}_relay_{}_reward_for_msgs_to_{}_on_lane_{}", C::NAME, account.tag(), BC::NAME, lane.label()),
|
||||
format!("Reward of the {} relay account at {} for delivering messages confirmations from {} on lane {:?}", account.tag(), C::NAME, BC::NAME, lane.label()),
|
||||
)?.register_and_spawn(&metrics.registry)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adapter for `FloatStorageValueMetric` to decode account free balance.
|
||||
#[derive(Clone, Debug)]
|
||||
struct AccountBalanceFromAccountInfo<C> {
|
||||
token_decimals: u32,
|
||||
_phantom: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<C> FloatStorageValue for AccountBalanceFromAccountInfo<C>
|
||||
where
|
||||
C: Chain,
|
||||
BalanceOf<C>: Into<u128>,
|
||||
{
|
||||
type Value = FixedU128;
|
||||
|
||||
fn decode(
|
||||
&self,
|
||||
maybe_raw_value: Option<StorageData>,
|
||||
) -> Result<Option<Self::Value>, BizinikiwiError> {
|
||||
maybe_raw_value
|
||||
.map(|raw_value| {
|
||||
AccountInfo::<NonceOf<C>, AccountData<BalanceOf<C>>>::decode(&mut &raw_value.0[..])
|
||||
.map_err(BizinikiwiError::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::*;
|
||||
#[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));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,792 @@
|
||||
// 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/>.
|
||||
|
||||
//! Bizinikiwi client as Bizinikiwi messages source. The chain we connect to should have
|
||||
//! runtime that implements `<BridgedChainName>HeaderApi` to allow bridging with
|
||||
//! `<BridgedName>` chain.
|
||||
|
||||
use crate::{
|
||||
finality_base::best_synced_header_id,
|
||||
messages::{
|
||||
BatchProofTransaction, MessageLaneAdapter, ReceiveMessagesDeliveryProofCallBuilder,
|
||||
BizinikiwiMessageLane,
|
||||
},
|
||||
on_demand::OnDemandRelay,
|
||||
proofs::to_raw_storage_proof,
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
use async_std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
use bp_messages::{
|
||||
storage_keys::{operating_mode_key, outbound_lane_data_key},
|
||||
target_chain::FromBridgedChainMessagesProof,
|
||||
ChainWithMessages as _, InboundMessageDetails, MessageNonce, MessagePayload,
|
||||
MessagesOperatingMode, OutboundMessageDetails,
|
||||
};
|
||||
use pezbp_runtime::{BasicOperatingMode, HeaderIdProvider, RangeInclusiveExt};
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::weights::Weight;
|
||||
use pez_messages_relay::{
|
||||
message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf},
|
||||
message_lane_loop::{
|
||||
ClientState, MessageDetails, MessageDetailsMap, MessageProofParameters, SourceClient,
|
||||
SourceClientState,
|
||||
},
|
||||
};
|
||||
use num_traits::Zero;
|
||||
use relay_bizinikiwi_client::{
|
||||
AccountIdOf, AccountKeyPairOf, BalanceOf, Chain, ChainWithMessages, Client,
|
||||
Error as BizinikiwiError, HashOf, HeaderIdOf, TransactionEra, TransactionTracker,
|
||||
UnsignedTransaction,
|
||||
};
|
||||
use relay_utils::relay_loop::Client as RelayClient;
|
||||
use pezsp_core::Pair;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// Intermediate message proof returned by the source Bizinikiwi node. Includes everything
|
||||
/// required to submit to the target node: cumulative dispatch weight of bundled messages and
|
||||
/// the proof itself.
|
||||
pub type BizinikiwiMessagesProof<C, L> = (Weight, FromBridgedChainMessagesProof<HashOf<C>, L>);
|
||||
type MessagesToRefine<'a> = Vec<(MessagePayload, &'a mut OutboundMessageDetails)>;
|
||||
|
||||
/// Outbound lane data - for backwards compatibility with `bp_messages::OutboundLaneData` which has
|
||||
/// additional `lane_state` attribute.
|
||||
///
|
||||
/// TODO: remove - https://github.com/pezkuwichain/pezkuwi-sdk/issues/22
|
||||
#[derive(Decode)]
|
||||
struct LegacyOutboundLaneData {
|
||||
#[allow(unused)]
|
||||
oldest_unpruned_nonce: MessageNonce,
|
||||
latest_received_nonce: MessageNonce,
|
||||
latest_generated_nonce: MessageNonce,
|
||||
}
|
||||
|
||||
/// Bizinikiwi client as Bizinikiwi messages source.
|
||||
pub struct BizinikiwiMessagesSource<P: BizinikiwiMessageLane, SourceClnt, TargetClnt> {
|
||||
source_client: SourceClnt,
|
||||
target_client: TargetClnt,
|
||||
lane_id: P::LaneId,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::SourceChain>>,
|
||||
target_to_source_headers_relay: Option<Arc<dyn OnDemandRelay<P::TargetChain, P::SourceChain>>>,
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiMessageLane, SourceClnt: Client<P::SourceChain>, TargetClnt>
|
||||
BizinikiwiMessagesSource<P, SourceClnt, TargetClnt>
|
||||
{
|
||||
/// Create new Bizinikiwi headers source.
|
||||
pub fn new(
|
||||
source_client: SourceClnt,
|
||||
target_client: TargetClnt,
|
||||
lane_id: P::LaneId,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::SourceChain>>,
|
||||
target_to_source_headers_relay: Option<
|
||||
Arc<dyn OnDemandRelay<P::TargetChain, P::SourceChain>>,
|
||||
>,
|
||||
) -> Self {
|
||||
BizinikiwiMessagesSource {
|
||||
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<LegacyOutboundLaneData>, BizinikiwiError> {
|
||||
self.source_client
|
||||
.storage_value(
|
||||
id.hash(),
|
||||
outbound_lane_data_key(
|
||||
P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&self.lane_id,
|
||||
),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Ensure that the messages pezpallet at source chain is active.
|
||||
async fn ensure_pallet_active(&self) -> Result<(), BizinikiwiError> {
|
||||
ensure_messages_pallet_active::<P::SourceChain, P::TargetChain, _>(&self.source_client)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiMessageLane, SourceClnt: Clone, TargetClnt: Clone> Clone
|
||||
for BizinikiwiMessagesSource<P, SourceClnt, TargetClnt>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<
|
||||
P: BizinikiwiMessageLane,
|
||||
SourceClnt: Client<P::SourceChain>,
|
||||
TargetClnt: Client<P::TargetChain>,
|
||||
> RelayClient for BizinikiwiMessagesSource<P, SourceClnt, TargetClnt>
|
||||
{
|
||||
type Error = BizinikiwiError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), BizinikiwiError> {
|
||||
// since the client calls RPC methods on both sides, we need to reconnect both
|
||||
self.source_client.reconnect().await?;
|
||||
self.target_client.reconnect().await?;
|
||||
|
||||
// call reconnect on on-demand headers relay, because we may use different chains there
|
||||
// and the error that has lead to reconnect may have came from those other chains
|
||||
// (see `require_target_header_on_source`)
|
||||
//
|
||||
// this may lead to multiple reconnects to the same node during the same call and it
|
||||
// needs to be addressed in the future
|
||||
// TODO: https://github.com/pezkuwichain/kurdistan-sdk/issues/82
|
||||
if let Some(ref mut target_to_source_headers_relay) = self.target_to_source_headers_relay {
|
||||
target_to_source_headers_relay.reconnect().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<
|
||||
P: BizinikiwiMessageLane,
|
||||
SourceClnt: Client<P::SourceChain>,
|
||||
TargetClnt: Client<P::TargetChain>,
|
||||
> SourceClient<MessageLaneAdapter<P>> for BizinikiwiMessagesSource<P, SourceClnt, TargetClnt>
|
||||
where
|
||||
AccountIdOf<P::SourceChain>: From<<AccountKeyPairOf<P::SourceChain> as Pair>::Public>,
|
||||
{
|
||||
type BatchTransaction =
|
||||
BatchProofTransaction<P::SourceChain, P::TargetChain, P::SourceBatchCallBuilder>;
|
||||
type TransactionTracker = TransactionTracker<P::SourceChain, SourceClnt>;
|
||||
|
||||
async fn state(&self) -> Result<SourceClientState<MessageLaneAdapter<P>>, BizinikiwiError> {
|
||||
// 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
|
||||
//
|
||||
// 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.source_client.ensure_synced().await?;
|
||||
self.target_client.ensure_synced().await?;
|
||||
// we can't relay confirmations if messages pezpallet at source chain is halted
|
||||
self.ensure_pallet_active().await?;
|
||||
|
||||
read_client_state_from_both_chains(&self.source_client, &self.target_client).await
|
||||
}
|
||||
|
||||
async fn latest_generated_nonce(
|
||||
&self,
|
||||
id: SourceHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
) -> Result<(SourceHeaderIdOf<MessageLaneAdapter<P>>, MessageNonce), BizinikiwiError> {
|
||||
// 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<MessageLaneAdapter<P>>,
|
||||
) -> Result<(SourceHeaderIdOf<MessageLaneAdapter<P>>, MessageNonce), BizinikiwiError> {
|
||||
// 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<MessageLaneAdapter<P>>,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
) -> Result<MessageDetailsMap<BalanceOf<P::SourceChain>>, BizinikiwiError> {
|
||||
let mut out_msgs_details: Vec<_> = self
|
||||
.source_client
|
||||
.state_call::<_, Vec<_>>(
|
||||
id.hash(),
|
||||
P::TargetChain::TO_CHAIN_MESSAGE_DETAILS_METHOD.into(),
|
||||
(self.lane_id, *nonces.start(), *nonces.end()),
|
||||
)
|
||||
.await?;
|
||||
validate_out_msgs_details::<P::SourceChain>(&out_msgs_details, nonces)?;
|
||||
|
||||
// prepare arguments of the inbound message details call (if we need it)
|
||||
let mut msgs_to_refine = vec![];
|
||||
for out_msg_details in out_msgs_details.iter_mut() {
|
||||
// in our current strategy all messages are supposed to be paid at the target chain
|
||||
|
||||
// for pay-at-target messages we may want to ask target chain for
|
||||
// refined dispatch weight
|
||||
let msg_key = bp_messages::storage_keys::message_key(
|
||||
P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&self.lane_id,
|
||||
out_msg_details.nonce,
|
||||
);
|
||||
let msg_payload: MessagePayload =
|
||||
self.source_client.storage_value(id.hash(), msg_key).await?.ok_or_else(|| {
|
||||
BizinikiwiError::Custom(format!(
|
||||
"Message to {} {:?}/{} is missing from runtime the storage of {} at {:?}",
|
||||
P::TargetChain::NAME,
|
||||
self.lane_id,
|
||||
out_msg_details.nonce,
|
||||
P::SourceChain::NAME,
|
||||
id,
|
||||
))
|
||||
})?;
|
||||
|
||||
msgs_to_refine.push((msg_payload, out_msg_details));
|
||||
}
|
||||
|
||||
let best_target_header_hash = self.target_client.best_header_hash().await?;
|
||||
for mut msgs_to_refine_batch in split_msgs_to_refine::<
|
||||
P::SourceChain,
|
||||
P::TargetChain,
|
||||
P::LaneId,
|
||||
>(self.lane_id, msgs_to_refine)?
|
||||
{
|
||||
let in_msgs_details = self
|
||||
.target_client
|
||||
.state_call::<_, Vec<InboundMessageDetails>>(
|
||||
best_target_header_hash,
|
||||
P::SourceChain::FROM_CHAIN_MESSAGE_DETAILS_METHOD.into(),
|
||||
(self.lane_id, &msgs_to_refine_batch),
|
||||
)
|
||||
.await?;
|
||||
if in_msgs_details.len() != msgs_to_refine_batch.len() {
|
||||
return Err(BizinikiwiError::Custom(format!(
|
||||
"Call of {} at {} has returned {} entries instead of expected {}",
|
||||
P::SourceChain::FROM_CHAIN_MESSAGE_DETAILS_METHOD,
|
||||
P::TargetChain::NAME,
|
||||
in_msgs_details.len(),
|
||||
msgs_to_refine_batch.len(),
|
||||
)));
|
||||
}
|
||||
for ((_, out_msg_details), in_msg_details) in
|
||||
msgs_to_refine_batch.iter_mut().zip(in_msgs_details)
|
||||
{
|
||||
tracing::trace!(
|
||||
target: "bridge",
|
||||
source=%P::SourceChain::NAME,
|
||||
target=%P::TargetChain::NAME,
|
||||
lane_id=?self.lane_id,
|
||||
nonce=%out_msg_details.nonce,
|
||||
at_source=%out_msg_details.dispatch_weight,
|
||||
at_target=%in_msg_details.dispatch_weight,
|
||||
"Refined weight of source->target message"
|
||||
);
|
||||
out_msg_details.dispatch_weight = in_msg_details.dispatch_weight;
|
||||
}
|
||||
}
|
||||
|
||||
let mut msgs_details_map = MessageDetailsMap::new();
|
||||
for out_msg_details in out_msgs_details {
|
||||
msgs_details_map.insert(
|
||||
out_msg_details.nonce,
|
||||
MessageDetails {
|
||||
dispatch_weight: out_msg_details.dispatch_weight,
|
||||
size: out_msg_details.size as _,
|
||||
reward: Zero::zero(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok(msgs_details_map)
|
||||
}
|
||||
|
||||
async fn prove_messages(
|
||||
&self,
|
||||
id: SourceHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
proof_parameters: MessageProofParameters,
|
||||
) -> Result<
|
||||
(
|
||||
SourceHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
RangeInclusive<MessageNonce>,
|
||||
<MessageLaneAdapter<P> as MessageLane>::MessagesProof,
|
||||
),
|
||||
BizinikiwiError,
|
||||
> {
|
||||
let mut storage_keys = Vec::with_capacity(nonces.saturating_len() as usize);
|
||||
for message_nonce in nonces.clone() {
|
||||
let message_key = bp_messages::storage_keys::message_key(
|
||||
P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&self.lane_id,
|
||||
message_nonce,
|
||||
);
|
||||
storage_keys.push(message_key);
|
||||
}
|
||||
if proof_parameters.outbound_state_proof_required {
|
||||
storage_keys.push(outbound_lane_data_key(
|
||||
P::TargetChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&self.lane_id,
|
||||
));
|
||||
}
|
||||
|
||||
let storage_proof =
|
||||
self.source_client.prove_storage(id.hash(), storage_keys.clone()).await?;
|
||||
let proof = FromBridgedChainMessagesProof {
|
||||
bridged_header_hash: id.1,
|
||||
storage_proof: to_raw_storage_proof::<P::SourceChain>(storage_proof),
|
||||
lane: self.lane_id,
|
||||
nonces_start: *nonces.start(),
|
||||
nonces_end: *nonces.end(),
|
||||
};
|
||||
Ok((id, nonces, (proof_parameters.dispatch_weight, proof)))
|
||||
}
|
||||
|
||||
async fn submit_messages_receiving_proof(
|
||||
&self,
|
||||
maybe_batch_tx: Option<Self::BatchTransaction>,
|
||||
_generated_at_block: TargetHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
proof: <MessageLaneAdapter<P> as MessageLane>::MessagesReceivingProof,
|
||||
) -> Result<Self::TransactionTracker, BizinikiwiError> {
|
||||
let messages_proof_call =
|
||||
P::ReceiveMessagesDeliveryProofCallBuilder::build_receive_messages_delivery_proof_call(
|
||||
proof,
|
||||
maybe_batch_tx.is_none(),
|
||||
);
|
||||
let final_call = match maybe_batch_tx {
|
||||
Some(batch_tx) => batch_tx.append_call_and_build(messages_proof_call),
|
||||
None => messages_proof_call,
|
||||
};
|
||||
|
||||
let transaction_params = self.transaction_params.clone();
|
||||
self.source_client
|
||||
.submit_and_watch_signed_extrinsic(
|
||||
&self.transaction_params.signer,
|
||||
move |best_block_id, transaction_nonce| {
|
||||
Ok(UnsignedTransaction::new(final_call.into(), transaction_nonce)
|
||||
.era(TransactionEra::new(best_block_id, transaction_params.mortality)))
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn require_target_header_on_source(
|
||||
&self,
|
||||
id: TargetHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
) -> Result<Option<Self::BatchTransaction>, BizinikiwiError> {
|
||||
if let Some(ref target_to_source_headers_relay) = self.target_to_source_headers_relay {
|
||||
if let Some(batch_tx) =
|
||||
BatchProofTransaction::new(target_to_source_headers_relay.clone(), id.0).await?
|
||||
{
|
||||
return Ok(Some(batch_tx));
|
||||
}
|
||||
|
||||
target_to_source_headers_relay.require_more_headers(id.0).await;
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure that the messages pezpallet at source chain is active.
|
||||
pub(crate) async fn ensure_messages_pallet_active<AtChain, WithChain, AtChainClient>(
|
||||
client: &AtChainClient,
|
||||
) -> Result<(), BizinikiwiError>
|
||||
where
|
||||
AtChain: ChainWithMessages,
|
||||
WithChain: ChainWithMessages,
|
||||
AtChainClient: Client<AtChain>,
|
||||
{
|
||||
let operating_mode = client
|
||||
.storage_value(
|
||||
client.best_header_hash().await?,
|
||||
operating_mode_key(WithChain::WITH_CHAIN_MESSAGES_PALLET_NAME),
|
||||
)
|
||||
.await?;
|
||||
let is_halted =
|
||||
operating_mode == Some(MessagesOperatingMode::Basic(BasicOperatingMode::Halted));
|
||||
if is_halted {
|
||||
Err(BizinikiwiError::BridgePalletIsHalted)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Read best blocks from given client.
|
||||
///
|
||||
/// This function assumes that the chain that is followed by the `self_client` has
|
||||
/// bridge GRANDPA pezpallet deployed and it provides `best_finalized_header_id_method_name`
|
||||
/// runtime API to read the best finalized Bridged chain header.
|
||||
///
|
||||
/// 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: &impl Client<SelfChain>,
|
||||
) -> Result<ClientState<HeaderIdOf<SelfChain>, HeaderIdOf<PeerChain>>, BizinikiwiError>
|
||||
where
|
||||
SelfChain: Chain,
|
||||
PeerChain: Chain,
|
||||
{
|
||||
// let's read our state first: we need best finalized header hash on **this** chain
|
||||
let self_best_finalized_id = self_client.best_finalized_header().await?.id();
|
||||
// now let's read our best header on **this** chain
|
||||
let self_best_id = self_client.best_header().await?.id();
|
||||
|
||||
// now let's read id of best finalized peer header at our best finalized block
|
||||
let peer_on_self_best_finalized_id =
|
||||
best_synced_header_id::<PeerChain, SelfChain>(self_client, self_best_id.hash()).await?;
|
||||
|
||||
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: peer_on_self_best_finalized_id,
|
||||
})
|
||||
}
|
||||
|
||||
/// Does the same stuff as `read_client_state`, but properly fills the
|
||||
/// `actual_best_finalized_peer_at_best_self` field of the result.
|
||||
pub async fn read_client_state_from_both_chains<SelfChain, PeerChain>(
|
||||
self_client: &impl Client<SelfChain>,
|
||||
peer_client: &impl Client<PeerChain>,
|
||||
) -> Result<ClientState<HeaderIdOf<SelfChain>, HeaderIdOf<PeerChain>>, BizinikiwiError>
|
||||
where
|
||||
SelfChain: Chain,
|
||||
PeerChain: Chain,
|
||||
{
|
||||
let mut client_state = read_client_state::<SelfChain, PeerChain>(self_client).await?;
|
||||
client_state.actual_best_finalized_peer_at_best_self =
|
||||
match client_state.best_finalized_peer_at_best_self.as_ref() {
|
||||
Some(peer_on_self_best_finalized_id) => {
|
||||
let actual_peer_on_self_best_finalized =
|
||||
peer_client.header_by_number(peer_on_self_best_finalized_id.number()).await?;
|
||||
Some(actual_peer_on_self_best_finalized.id())
|
||||
},
|
||||
_ => client_state.best_finalized_peer_at_best_self,
|
||||
};
|
||||
Ok(client_state)
|
||||
}
|
||||
|
||||
/// Reads best `PeerChain` header known to the `SelfChain` using provided runtime API method.
|
||||
///
|
||||
/// Method is supposed to be the `<PeerChain>FinalityApi::best_finalized()` method.
|
||||
pub async fn best_finalized_peer_header_at_self<SelfChain, PeerChain>(
|
||||
self_client: &impl Client<SelfChain>,
|
||||
at_self_hash: HashOf<SelfChain>,
|
||||
) -> Result<Option<HeaderIdOf<PeerChain>>, BizinikiwiError>
|
||||
where
|
||||
SelfChain: Chain,
|
||||
PeerChain: Chain,
|
||||
{
|
||||
// now let's read id of best finalized peer header at our best finalized block
|
||||
self_client
|
||||
.state_call::<_, Option<_>>(
|
||||
at_self_hash,
|
||||
PeerChain::BEST_FINALIZED_HEADER_ID_METHOD.into(),
|
||||
(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn validate_out_msgs_details<C: Chain>(
|
||||
out_msgs_details: &[OutboundMessageDetails],
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
) -> Result<(), BizinikiwiError> {
|
||||
let make_missing_nonce_error = |expected_nonce| {
|
||||
Err(BizinikiwiError::Custom(format!(
|
||||
"Missing nonce {expected_nonce} in message_details call result. Expected all nonces from {nonces:?}",
|
||||
)))
|
||||
};
|
||||
|
||||
if out_msgs_details.len() > nonces.clone().count() {
|
||||
return Err(BizinikiwiError::Custom(
|
||||
"More messages than requested returned by the message_details call.".into(),
|
||||
));
|
||||
}
|
||||
|
||||
// Check if last nonce is missing. The loop below is not checking this.
|
||||
if out_msgs_details.is_empty() && !nonces.is_empty() {
|
||||
return make_missing_nonce_error(*nonces.end());
|
||||
}
|
||||
|
||||
let mut nonces_iter = nonces.clone().rev().peekable();
|
||||
let mut out_msgs_details_iter = out_msgs_details.iter().rev();
|
||||
while let Some((out_msg_details, &nonce)) = out_msgs_details_iter.next().zip(nonces_iter.peek())
|
||||
{
|
||||
nonces_iter.next();
|
||||
if out_msg_details.nonce != nonce {
|
||||
// Some nonces are missing from the middle/tail of the range. This is critical error.
|
||||
return make_missing_nonce_error(nonce);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if some nonces from the beginning of the range are missing. This may happen if
|
||||
// some messages were already pruned from the source node. This is not a critical error
|
||||
// and will be auto-resolved by messages lane (and target node).
|
||||
if nonces_iter.peek().is_some() {
|
||||
tracing::info!(
|
||||
target: "bridge",
|
||||
node=%C::NAME,
|
||||
missing=?nonces_iter.rev().collect::<Vec<_>>(),
|
||||
"Some messages are missing. Target node may be out of sync?"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn split_msgs_to_refine<Source: Chain + ChainWithMessages, Target: Chain, LaneId: Encode + Copy>(
|
||||
lane_id: LaneId,
|
||||
msgs_to_refine: MessagesToRefine,
|
||||
) -> Result<Vec<MessagesToRefine>, BizinikiwiError> {
|
||||
let max_batch_size = Target::max_extrinsic_size() as usize;
|
||||
let mut batches = vec![];
|
||||
|
||||
let mut current_msgs_batch = msgs_to_refine;
|
||||
while !current_msgs_batch.is_empty() {
|
||||
let mut next_msgs_batch = vec![];
|
||||
while (lane_id, ¤t_msgs_batch).encoded_size() > max_batch_size {
|
||||
if current_msgs_batch.len() <= 1 {
|
||||
return Err(BizinikiwiError::Custom(format!(
|
||||
"Call of {} at {} can't be executed even if only one message is supplied. \
|
||||
max_extrinsic_size(): {}",
|
||||
Source::FROM_CHAIN_MESSAGE_DETAILS_METHOD,
|
||||
Target::NAME,
|
||||
Target::max_extrinsic_size(),
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(msg) = current_msgs_batch.pop() {
|
||||
next_msgs_batch.insert(0, msg);
|
||||
}
|
||||
}
|
||||
|
||||
batches.push(current_msgs_batch);
|
||||
current_msgs_batch = next_msgs_batch;
|
||||
}
|
||||
|
||||
Ok(batches)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bp_messages::{HashedLaneId, LaneIdType};
|
||||
use relay_bizinikiwi_client::test_chain::TestChain;
|
||||
|
||||
/// Lane identifier type used for tests.
|
||||
type TestLaneIdType = HashedLaneId;
|
||||
|
||||
fn message_details_from_rpc(
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
) -> Vec<OutboundMessageDetails> {
|
||||
nonces
|
||||
.into_iter()
|
||||
.map(|nonce| bp_messages::OutboundMessageDetails {
|
||||
nonce,
|
||||
dispatch_weight: Weight::zero(),
|
||||
size: 0,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_out_msgs_details_succeeds_if_no_messages_are_missing() {
|
||||
assert!(validate_out_msgs_details::<TestChain>(&message_details_from_rpc(1..=3), 1..=3,)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_out_msgs_details_succeeds_if_head_messages_are_missing() {
|
||||
assert!(validate_out_msgs_details::<TestChain>(&message_details_from_rpc(2..=3), 1..=3,)
|
||||
.is_ok())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_out_msgs_details_fails_if_mid_messages_are_missing() {
|
||||
let mut message_details_from_rpc = message_details_from_rpc(1..=3);
|
||||
message_details_from_rpc.remove(1);
|
||||
assert!(matches!(
|
||||
validate_out_msgs_details::<TestChain>(&message_details_from_rpc, 1..=3,),
|
||||
Err(BizinikiwiError::Custom(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_out_msgs_details_map_fails_if_tail_messages_are_missing() {
|
||||
assert!(matches!(
|
||||
validate_out_msgs_details::<TestChain>(&message_details_from_rpc(1..=2), 1..=3,),
|
||||
Err(BizinikiwiError::Custom(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_out_msgs_details_fails_if_all_messages_are_missing() {
|
||||
assert!(matches!(
|
||||
validate_out_msgs_details::<TestChain>(&[], 1..=3),
|
||||
Err(BizinikiwiError::Custom(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_out_msgs_details_fails_if_more_messages_than_nonces() {
|
||||
assert!(matches!(
|
||||
validate_out_msgs_details::<TestChain>(&message_details_from_rpc(1..=5), 2..=5,),
|
||||
Err(BizinikiwiError::Custom(_))
|
||||
));
|
||||
}
|
||||
|
||||
fn check_split_msgs_to_refine(
|
||||
payload_sizes: Vec<usize>,
|
||||
expected_batches: Result<Vec<usize>, ()>,
|
||||
) {
|
||||
let mut out_msgs_details = vec![];
|
||||
for (idx, _) in payload_sizes.iter().enumerate() {
|
||||
out_msgs_details.push(OutboundMessageDetails {
|
||||
nonce: idx as MessageNonce,
|
||||
dispatch_weight: Weight::zero(),
|
||||
size: 0,
|
||||
});
|
||||
}
|
||||
|
||||
let mut msgs_to_refine = vec![];
|
||||
for (&payload_size, out_msg_details) in
|
||||
payload_sizes.iter().zip(out_msgs_details.iter_mut())
|
||||
{
|
||||
let payload = vec![1u8; payload_size];
|
||||
msgs_to_refine.push((payload, out_msg_details));
|
||||
}
|
||||
|
||||
let maybe_batches = split_msgs_to_refine::<TestChain, TestChain, TestLaneIdType>(
|
||||
TestLaneIdType::try_new(1, 2).unwrap(),
|
||||
msgs_to_refine,
|
||||
);
|
||||
match expected_batches {
|
||||
Ok(expected_batches) => {
|
||||
let batches = maybe_batches.unwrap();
|
||||
let mut idx = 0;
|
||||
assert_eq!(batches.len(), expected_batches.len());
|
||||
for (batch, &expected_batch_size) in batches.iter().zip(expected_batches.iter()) {
|
||||
assert_eq!(batch.len(), expected_batch_size);
|
||||
for msg_to_refine in batch {
|
||||
assert_eq!(msg_to_refine.0.len(), payload_sizes[idx]);
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
matches!(maybe_batches, Err(BizinikiwiError::Custom(_)));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_msgs_to_refine() {
|
||||
let max_extrinsic_size = 100000;
|
||||
|
||||
// Check that an error is returned when one of the messages is too big.
|
||||
check_split_msgs_to_refine(vec![max_extrinsic_size], Err(()));
|
||||
check_split_msgs_to_refine(vec![50, 100, max_extrinsic_size, 200], Err(()));
|
||||
|
||||
// Otherwise check that the split is valid.
|
||||
check_split_msgs_to_refine(vec![100, 200, 300, 400], Ok(vec![4]));
|
||||
check_split_msgs_to_refine(
|
||||
vec![
|
||||
50,
|
||||
100,
|
||||
max_extrinsic_size - 500,
|
||||
500,
|
||||
1000,
|
||||
1500,
|
||||
max_extrinsic_size - 3500,
|
||||
5000,
|
||||
10000,
|
||||
],
|
||||
Ok(vec![3, 4, 2]),
|
||||
);
|
||||
check_split_msgs_to_refine(
|
||||
vec![
|
||||
50,
|
||||
100,
|
||||
max_extrinsic_size - 150,
|
||||
500,
|
||||
1000,
|
||||
1500,
|
||||
max_extrinsic_size - 3000,
|
||||
5000,
|
||||
10000,
|
||||
],
|
||||
Ok(vec![2, 1, 3, 1, 2]),
|
||||
);
|
||||
check_split_msgs_to_refine(
|
||||
vec![
|
||||
5000,
|
||||
10000,
|
||||
max_extrinsic_size - 3500,
|
||||
500,
|
||||
1000,
|
||||
1500,
|
||||
max_extrinsic_size - 500,
|
||||
50,
|
||||
100,
|
||||
],
|
||||
Ok(vec![2, 4, 3]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outbound_lane_data_wrapper_is_compatible() {
|
||||
let bytes_without_state =
|
||||
vec![1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0];
|
||||
let bytes_with_state = {
|
||||
// add state byte `bp_messages::LaneState::Opened`
|
||||
let mut b = bytes_without_state.clone();
|
||||
b.push(0);
|
||||
b
|
||||
};
|
||||
|
||||
let full = bp_messages::OutboundLaneData {
|
||||
oldest_unpruned_nonce: 1,
|
||||
latest_received_nonce: 2,
|
||||
latest_generated_nonce: 3,
|
||||
state: bp_messages::LaneState::Opened,
|
||||
};
|
||||
assert_eq!(full.encode(), bytes_with_state);
|
||||
assert_ne!(full.encode(), bytes_without_state);
|
||||
|
||||
// decode from `bytes_with_state`
|
||||
let decoded: LegacyOutboundLaneData = Decode::decode(&mut &bytes_with_state[..]).unwrap();
|
||||
assert_eq!(full.oldest_unpruned_nonce, decoded.oldest_unpruned_nonce);
|
||||
assert_eq!(full.latest_received_nonce, decoded.latest_received_nonce);
|
||||
assert_eq!(full.latest_generated_nonce, decoded.latest_generated_nonce);
|
||||
|
||||
// decode from `bytes_without_state`
|
||||
let decoded: LegacyOutboundLaneData =
|
||||
Decode::decode(&mut &bytes_without_state[..]).unwrap();
|
||||
assert_eq!(full.oldest_unpruned_nonce, decoded.oldest_unpruned_nonce);
|
||||
assert_eq!(full.latest_received_nonce, decoded.latest_received_nonce);
|
||||
assert_eq!(full.latest_generated_nonce, decoded.latest_generated_nonce);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
// 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/>.
|
||||
|
||||
//! Bizinikiwi client as Bizinikiwi messages target. The chain we connect to should have
|
||||
//! runtime that implements `<BridgedChainName>HeaderApi` to allow bridging with
|
||||
//! `<BridgedName>` chain.
|
||||
|
||||
use crate::{
|
||||
messages::{
|
||||
source::{
|
||||
ensure_messages_pallet_active, read_client_state_from_both_chains,
|
||||
BizinikiwiMessagesProof,
|
||||
},
|
||||
BatchProofTransaction, MessageLaneAdapter, ReceiveMessagesProofCallBuilder,
|
||||
BizinikiwiMessageLane,
|
||||
},
|
||||
on_demand::OnDemandRelay,
|
||||
proofs::to_raw_storage_proof,
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
use async_std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
use bp_messages::{
|
||||
source_chain::FromBridgedChainMessagesDeliveryProof, storage_keys::inbound_lane_data_key,
|
||||
ChainWithMessages as _, LaneState, MessageNonce, UnrewardedRelayer, UnrewardedRelayersState,
|
||||
};
|
||||
use codec::Decode;
|
||||
use pez_messages_relay::{
|
||||
message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf},
|
||||
message_lane_loop::{NoncesSubmitArtifacts, TargetClient, TargetClientState},
|
||||
};
|
||||
use relay_bizinikiwi_client::{
|
||||
AccountIdOf, AccountKeyPairOf, BalanceOf, CallOf, Chain, Client, Error as BizinikiwiError,
|
||||
HashOf, TransactionEra, TransactionTracker, UnsignedTransaction,
|
||||
};
|
||||
use relay_utils::relay_loop::Client as RelayClient;
|
||||
use pezsp_core::Pair;
|
||||
use std::{collections::VecDeque, convert::TryFrom, ops::RangeInclusive};
|
||||
|
||||
/// Message receiving proof returned by the target Bizinikiwi node.
|
||||
pub type BizinikiwiMessagesDeliveryProof<C, L> =
|
||||
(UnrewardedRelayersState, FromBridgedChainMessagesDeliveryProof<HashOf<C>, L>);
|
||||
|
||||
/// Inbound lane data - for backwards compatibility with `bp_messages::InboundLaneData` which has
|
||||
/// additional `lane_state` attribute.
|
||||
///
|
||||
/// TODO: remove - https://github.com/pezkuwichain/pezkuwi-sdk/issues/22
|
||||
#[derive(Decode)]
|
||||
struct LegacyInboundLaneData<RelayerId> {
|
||||
relayers: VecDeque<UnrewardedRelayer<RelayerId>>,
|
||||
last_confirmed_nonce: MessageNonce,
|
||||
}
|
||||
impl<RelayerId> Default for LegacyInboundLaneData<RelayerId> {
|
||||
fn default() -> Self {
|
||||
let full = bp_messages::InboundLaneData::default();
|
||||
Self { relayers: full.relayers, last_confirmed_nonce: full.last_confirmed_nonce }
|
||||
}
|
||||
}
|
||||
|
||||
impl<RelayerId> LegacyInboundLaneData<RelayerId> {
|
||||
pub fn last_delivered_nonce(self) -> MessageNonce {
|
||||
bp_messages::InboundLaneData {
|
||||
relayers: self.relayers,
|
||||
last_confirmed_nonce: self.last_confirmed_nonce,
|
||||
// we don't care about the state here
|
||||
state: LaneState::Opened,
|
||||
}
|
||||
.last_delivered_nonce()
|
||||
}
|
||||
}
|
||||
|
||||
impl<RelayerId> From<LegacyInboundLaneData<RelayerId>> for UnrewardedRelayersState {
|
||||
fn from(value: LegacyInboundLaneData<RelayerId>) -> Self {
|
||||
(&bp_messages::InboundLaneData {
|
||||
relayers: value.relayers,
|
||||
last_confirmed_nonce: value.last_confirmed_nonce,
|
||||
// we don't care about the state here
|
||||
state: LaneState::Opened,
|
||||
})
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Bizinikiwi client as Bizinikiwi messages target.
|
||||
pub struct BizinikiwiMessagesTarget<P: BizinikiwiMessageLane, SourceClnt, TargetClnt> {
|
||||
target_client: TargetClnt,
|
||||
source_client: SourceClnt,
|
||||
lane_id: P::LaneId,
|
||||
relayer_id_at_source: AccountIdOf<P::SourceChain>,
|
||||
transaction_params: Option<TransactionParams<AccountKeyPairOf<P::TargetChain>>>,
|
||||
source_to_target_headers_relay: Option<Arc<dyn OnDemandRelay<P::SourceChain, P::TargetChain>>>,
|
||||
}
|
||||
|
||||
impl<P, SourceClnt, TargetClnt> BizinikiwiMessagesTarget<P, SourceClnt, TargetClnt>
|
||||
where
|
||||
P: BizinikiwiMessageLane,
|
||||
TargetClnt: Client<P::TargetChain>,
|
||||
{
|
||||
/// Create new Bizinikiwi headers target.
|
||||
pub fn new(
|
||||
target_client: TargetClnt,
|
||||
source_client: SourceClnt,
|
||||
lane_id: P::LaneId,
|
||||
relayer_id_at_source: AccountIdOf<P::SourceChain>,
|
||||
transaction_params: Option<TransactionParams<AccountKeyPairOf<P::TargetChain>>>,
|
||||
source_to_target_headers_relay: Option<
|
||||
Arc<dyn OnDemandRelay<P::SourceChain, P::TargetChain>>,
|
||||
>,
|
||||
) -> Self {
|
||||
BizinikiwiMessagesTarget {
|
||||
target_client,
|
||||
source_client,
|
||||
lane_id,
|
||||
relayer_id_at_source,
|
||||
transaction_params,
|
||||
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<LegacyInboundLaneData<AccountIdOf<P::SourceChain>>>, BizinikiwiError> {
|
||||
self.target_client
|
||||
.storage_value(
|
||||
id.hash(),
|
||||
inbound_lane_data_key(
|
||||
P::SourceChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&self.lane_id,
|
||||
),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Ensure that the messages pezpallet at target chain is active.
|
||||
async fn ensure_pallet_active(&self) -> Result<(), BizinikiwiError> {
|
||||
ensure_messages_pallet_active::<P::TargetChain, P::SourceChain, _>(&self.target_client)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiMessageLane, SourceClnt: Clone, TargetClnt: Clone> Clone
|
||||
for BizinikiwiMessagesTarget<P, SourceClnt, TargetClnt>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
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(),
|
||||
source_to_target_headers_relay: self.source_to_target_headers_relay.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<
|
||||
P: BizinikiwiMessageLane,
|
||||
SourceClnt: Client<P::SourceChain>,
|
||||
TargetClnt: Client<P::TargetChain>,
|
||||
> RelayClient for BizinikiwiMessagesTarget<P, SourceClnt, TargetClnt>
|
||||
{
|
||||
type Error = BizinikiwiError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), BizinikiwiError> {
|
||||
// since the client calls RPC methods on both sides, we need to reconnect both
|
||||
self.target_client.reconnect().await?;
|
||||
self.source_client.reconnect().await?;
|
||||
|
||||
// call reconnect on on-demand headers relay, because we may use different chains there
|
||||
// and the error that has lead to reconnect may have came from those other chains
|
||||
// (see `require_source_header_on_target`)
|
||||
//
|
||||
// this may lead to multiple reconnects to the same node during the same call and it
|
||||
// needs to be addressed in the future
|
||||
// TODO: https://github.com/pezkuwichain/kurdistan-sdk/issues/82
|
||||
if let Some(ref mut source_to_target_headers_relay) = self.source_to_target_headers_relay {
|
||||
source_to_target_headers_relay.reconnect().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<
|
||||
P: BizinikiwiMessageLane,
|
||||
SourceClnt: Client<P::SourceChain>,
|
||||
TargetClnt: Client<P::TargetChain>,
|
||||
> TargetClient<MessageLaneAdapter<P>> for BizinikiwiMessagesTarget<P, SourceClnt, TargetClnt>
|
||||
where
|
||||
AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TargetChain> as Pair>::Public>,
|
||||
BalanceOf<P::SourceChain>: TryFrom<BalanceOf<P::TargetChain>>,
|
||||
{
|
||||
type BatchTransaction =
|
||||
BatchProofTransaction<P::TargetChain, P::SourceChain, P::TargetBatchCallBuilder>;
|
||||
type TransactionTracker = TransactionTracker<P::TargetChain, TargetClnt>;
|
||||
|
||||
async fn state(&self) -> Result<TargetClientState<MessageLaneAdapter<P>>, BizinikiwiError> {
|
||||
// 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
|
||||
//
|
||||
// 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.source_client.ensure_synced().await?;
|
||||
self.target_client.ensure_synced().await?;
|
||||
// we can't relay messages if messages pezpallet at target chain is halted
|
||||
self.ensure_pallet_active().await?;
|
||||
|
||||
read_client_state_from_both_chains(&self.target_client, &self.source_client).await
|
||||
}
|
||||
|
||||
async fn latest_received_nonce(
|
||||
&self,
|
||||
id: TargetHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
) -> Result<(TargetHeaderIdOf<MessageLaneAdapter<P>>, MessageNonce), BizinikiwiError> {
|
||||
// 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<MessageLaneAdapter<P>>,
|
||||
) -> Result<(TargetHeaderIdOf<MessageLaneAdapter<P>>, MessageNonce), BizinikiwiError> {
|
||||
// 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<MessageLaneAdapter<P>>,
|
||||
) -> Result<(TargetHeaderIdOf<MessageLaneAdapter<P>>, UnrewardedRelayersState), BizinikiwiError>
|
||||
{
|
||||
let inbound_lane_data =
|
||||
self.inbound_lane_data(id).await?.unwrap_or(LegacyInboundLaneData::default());
|
||||
Ok((id, inbound_lane_data.into()))
|
||||
}
|
||||
|
||||
async fn prove_messages_receiving(
|
||||
&self,
|
||||
id: TargetHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
) -> Result<
|
||||
(
|
||||
TargetHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
<MessageLaneAdapter<P> as MessageLane>::MessagesReceivingProof,
|
||||
),
|
||||
BizinikiwiError,
|
||||
> {
|
||||
let (id, relayers_state) = self.unrewarded_relayers_state(id).await?;
|
||||
let storage_keys = vec![inbound_lane_data_key(
|
||||
P::SourceChain::WITH_CHAIN_MESSAGES_PALLET_NAME,
|
||||
&self.lane_id,
|
||||
)];
|
||||
|
||||
let storage_proof =
|
||||
self.target_client.prove_storage(id.hash(), storage_keys.clone()).await?;
|
||||
let proof = FromBridgedChainMessagesDeliveryProof {
|
||||
bridged_header_hash: id.1,
|
||||
storage_proof: to_raw_storage_proof::<P::TargetChain>(storage_proof),
|
||||
lane: self.lane_id,
|
||||
};
|
||||
Ok((id, (relayers_state, proof)))
|
||||
}
|
||||
|
||||
async fn submit_messages_proof(
|
||||
&self,
|
||||
maybe_batch_tx: Option<Self::BatchTransaction>,
|
||||
_generated_at_header: SourceHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
proof: <MessageLaneAdapter<P> as MessageLane>::MessagesProof,
|
||||
) -> Result<NoncesSubmitArtifacts<Self::TransactionTracker>, BizinikiwiError> {
|
||||
let messages_proof_call = make_messages_delivery_call::<P>(
|
||||
self.relayer_id_at_source.clone(),
|
||||
proof.1.nonces_start..=proof.1.nonces_end,
|
||||
proof,
|
||||
maybe_batch_tx.is_none(),
|
||||
);
|
||||
let final_call = match maybe_batch_tx {
|
||||
Some(batch_tx) => batch_tx.append_call_and_build(messages_proof_call),
|
||||
None => messages_proof_call,
|
||||
};
|
||||
|
||||
let transaction_params = self.transaction_params.clone().map(Ok).unwrap_or_else(|| {
|
||||
// this error shall never happen in practice, so it not deserves
|
||||
// a separate error variant
|
||||
Err(BizinikiwiError::Custom(format!(
|
||||
"Cannot sign transaction of {} chain",
|
||||
P::TargetChain::NAME,
|
||||
)))
|
||||
})?;
|
||||
let tx_tracker = self
|
||||
.target_client
|
||||
.submit_and_watch_signed_extrinsic(
|
||||
&transaction_params.signer,
|
||||
move |best_block_id, transaction_nonce| {
|
||||
Ok(UnsignedTransaction::new(final_call.into(), transaction_nonce)
|
||||
.era(TransactionEra::new(best_block_id, transaction_params.mortality)))
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(NoncesSubmitArtifacts { nonces, tx_tracker })
|
||||
}
|
||||
|
||||
async fn require_source_header_on_target(
|
||||
&self,
|
||||
id: SourceHeaderIdOf<MessageLaneAdapter<P>>,
|
||||
) -> Result<Option<Self::BatchTransaction>, BizinikiwiError> {
|
||||
if let Some(ref source_to_target_headers_relay) = self.source_to_target_headers_relay {
|
||||
if let Some(batch_tx) =
|
||||
BatchProofTransaction::new(source_to_target_headers_relay.clone(), id.0).await?
|
||||
{
|
||||
return Ok(Some(batch_tx));
|
||||
}
|
||||
|
||||
source_to_target_headers_relay.require_more_headers(id.0).await;
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Make messages delivery call from given proof.
|
||||
fn make_messages_delivery_call<P: BizinikiwiMessageLane>(
|
||||
relayer_id_at_source: AccountIdOf<P::SourceChain>,
|
||||
nonces: RangeInclusive<MessageNonce>,
|
||||
proof: BizinikiwiMessagesProof<P::SourceChain, P::LaneId>,
|
||||
trace_call: bool,
|
||||
) -> CallOf<P::TargetChain> {
|
||||
let messages_count = nonces.end() - nonces.start() + 1;
|
||||
let dispatch_weight = proof.0;
|
||||
P::ReceiveMessagesProofCallBuilder::build_receive_messages_proof_call(
|
||||
relayer_id_at_source,
|
||||
proof,
|
||||
messages_count as _,
|
||||
dispatch_weight,
|
||||
trace_call,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bp_messages::{DeliveredMessages, UnrewardedRelayer};
|
||||
use codec::Encode;
|
||||
|
||||
#[test]
|
||||
fn inbound_lane_data_wrapper_is_compatible() {
|
||||
let bytes_without_state =
|
||||
vec![4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0];
|
||||
let bytes_with_state = {
|
||||
// add state byte `bp_messages::LaneState::Opened`
|
||||
let mut b = bytes_without_state.clone();
|
||||
b.push(0);
|
||||
b
|
||||
};
|
||||
|
||||
let full = bp_messages::InboundLaneData::<u8> {
|
||||
relayers: vec![UnrewardedRelayer {
|
||||
relayer: Default::default(),
|
||||
messages: DeliveredMessages { begin: 2, end: 5 },
|
||||
}]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
last_confirmed_nonce: 6,
|
||||
state: bp_messages::LaneState::Opened,
|
||||
};
|
||||
assert_eq!(full.encode(), bytes_with_state);
|
||||
assert_ne!(full.encode(), bytes_without_state);
|
||||
|
||||
// decode from `bytes_with_state`
|
||||
let decoded: LegacyInboundLaneData<u8> =
|
||||
Decode::decode(&mut &bytes_with_state[..]).unwrap();
|
||||
assert_eq!(full.relayers, decoded.relayers);
|
||||
assert_eq!(full.last_confirmed_nonce, decoded.last_confirmed_nonce);
|
||||
assert_eq!(full.last_delivered_nonce(), decoded.last_delivered_nonce());
|
||||
|
||||
// decode from `bytes_without_state`
|
||||
let decoded: LegacyInboundLaneData<u8> =
|
||||
Decode::decode(&mut &bytes_without_state[..]).unwrap();
|
||||
assert_eq!(full.relayers, decoded.relayers);
|
||||
assert_eq!(full.last_confirmed_nonce, decoded.last_confirmed_nonce);
|
||||
assert_eq!(full.last_delivered_nonce(), decoded.last_delivered_nonce());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,569 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! On-demand Bizinikiwi -> Bizinikiwi header finality relay.
|
||||
|
||||
use crate::finality::SubmitFinalityProofCallBuilder;
|
||||
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use async_trait::async_trait;
|
||||
use bp_header_pez_chain::ConsensusLogReader;
|
||||
use pezbp_runtime::HeaderIdProvider;
|
||||
use futures::{select, FutureExt};
|
||||
use num_traits::{One, Saturating, Zero};
|
||||
use pezsp_runtime::traits::Header;
|
||||
|
||||
use pez_finality_relay::{FinalitySyncParams, HeadersToRelay, TargetClient as FinalityTargetClient};
|
||||
use relay_bizinikiwi_client::{
|
||||
AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain, Client, Error as BizinikiwiError,
|
||||
HeaderIdOf,
|
||||
};
|
||||
use relay_utils::{
|
||||
metrics::MetricsParams, relay_loop::Client as RelayClient, FailedClient, MaybeConnectionError,
|
||||
STALL_TIMEOUT,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
finality::{
|
||||
source::{RequiredHeaderNumberRef, BizinikiwiFinalitySource},
|
||||
target::BizinikiwiFinalityTarget,
|
||||
BizinikiwiFinalitySyncPipeline, RECENT_FINALITY_PROOFS_LIMIT,
|
||||
},
|
||||
finality_base::engine::Engine,
|
||||
on_demand::OnDemandRelay,
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
/// On-demand Bizinikiwi <-> Bizinikiwi header finality relay.
|
||||
///
|
||||
/// This relay may be requested to sync more headers, whenever some other relay (e.g. messages
|
||||
/// relay) needs it to continue its regular work. When enough headers are relayed, on-demand stops
|
||||
/// syncing headers.
|
||||
#[derive(Clone)]
|
||||
pub struct OnDemandHeadersRelay<P: BizinikiwiFinalitySyncPipeline, SourceClnt, TargetClnt> {
|
||||
/// Relay task name.
|
||||
relay_task_name: String,
|
||||
/// Shared reference to maximal required finalized header number.
|
||||
required_header_number: RequiredHeaderNumberRef<P::SourceChain>,
|
||||
/// Client of the source chain.
|
||||
source_client: SourceClnt,
|
||||
/// Client of the target chain.
|
||||
target_client: TargetClnt,
|
||||
}
|
||||
|
||||
impl<
|
||||
P: BizinikiwiFinalitySyncPipeline,
|
||||
SourceClnt: Client<P::SourceChain>,
|
||||
TargetClnt: Client<P::TargetChain>,
|
||||
> OnDemandHeadersRelay<P, SourceClnt, TargetClnt>
|
||||
{
|
||||
/// Create new on-demand headers relay.
|
||||
///
|
||||
/// If `metrics_params` is `Some(_)`, the metrics of the finality relay are registered.
|
||||
/// Otherwise, all required metrics must be exposed outside of this method.
|
||||
pub fn new(
|
||||
source_client: SourceClnt,
|
||||
target_client: TargetClnt,
|
||||
target_transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
|
||||
headers_to_relay: HeadersToRelay,
|
||||
metrics_params: Option<MetricsParams>,
|
||||
) -> Self
|
||||
where
|
||||
AccountIdOf<P::TargetChain>:
|
||||
From<<AccountKeyPairOf<P::TargetChain> as pezsp_core::Pair>::Public>,
|
||||
{
|
||||
let required_header_number = Arc::new(Mutex::new(Zero::zero()));
|
||||
let this = OnDemandHeadersRelay {
|
||||
relay_task_name: on_demand_headers_relay_name::<P::SourceChain, P::TargetChain>(),
|
||||
required_header_number: required_header_number.clone(),
|
||||
source_client: source_client.clone(),
|
||||
target_client: target_client.clone(),
|
||||
};
|
||||
async_std::task::spawn(async move {
|
||||
background_task::<P>(
|
||||
source_client,
|
||||
target_client,
|
||||
target_transaction_params,
|
||||
headers_to_relay,
|
||||
required_header_number,
|
||||
metrics_params,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<
|
||||
P: BizinikiwiFinalitySyncPipeline,
|
||||
SourceClnt: Client<P::SourceChain>,
|
||||
TargetClnt: Client<P::TargetChain>,
|
||||
> OnDemandRelay<P::SourceChain, P::TargetChain>
|
||||
for OnDemandHeadersRelay<P, SourceClnt, TargetClnt>
|
||||
{
|
||||
async fn reconnect(&self) -> Result<(), BizinikiwiError> {
|
||||
// using clone is fine here (to avoid mut requirement), because clone on Client clones
|
||||
// internal references
|
||||
self.source_client.clone().reconnect().await?;
|
||||
self.target_client.clone().reconnect().await
|
||||
}
|
||||
|
||||
async fn require_more_headers(&self, required_header: BlockNumberOf<P::SourceChain>) {
|
||||
let mut required_header_number = self.required_header_number.lock().await;
|
||||
if required_header > *required_header_number {
|
||||
tracing::trace!(
|
||||
target: "bridge",
|
||||
relay_task_name=%self.relay_task_name,
|
||||
source=%P::SourceChain::NAME,
|
||||
%required_header,
|
||||
"More headers required. Going to sync up"
|
||||
);
|
||||
|
||||
*required_header_number = required_header;
|
||||
}
|
||||
}
|
||||
|
||||
async fn prove_header(
|
||||
&self,
|
||||
required_header: BlockNumberOf<P::SourceChain>,
|
||||
) -> Result<(HeaderIdOf<P::SourceChain>, Vec<CallOf<P::TargetChain>>), BizinikiwiError> {
|
||||
const MAX_ITERATIONS: u32 = 4;
|
||||
let mut iterations = 0;
|
||||
let mut current_required_header = required_header;
|
||||
loop {
|
||||
// first find proper header (either `current_required_header`) or its descendant
|
||||
let finality_source =
|
||||
BizinikiwiFinalitySource::<P, _>::new(self.source_client.clone(), None);
|
||||
let (header, mut proof) =
|
||||
finality_source.prove_block_finality(current_required_header).await?;
|
||||
let header_id = header.id();
|
||||
|
||||
// verify and optimize justification before including it into the call
|
||||
let context = P::FinalityEngine::verify_and_optimize_proof(
|
||||
&self.target_client,
|
||||
&header,
|
||||
&mut proof,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// now we have the header and its proof, but we want to minimize our losses, so let's
|
||||
// check if we'll get the full refund for submitting this header
|
||||
let check_result = P::FinalityEngine::check_max_expected_call_limits(&header, &proof);
|
||||
if check_result.is_weight_limit_exceeded || check_result.extra_size != 0 {
|
||||
iterations += 1;
|
||||
current_required_header = header_id.number().saturating_add(One::one());
|
||||
if iterations < MAX_ITERATIONS {
|
||||
tracing::debug!(
|
||||
target: "bridge",
|
||||
relay_task_name=%self.relay_task_name,
|
||||
source=%P::SourceChain::NAME,
|
||||
%required_header,
|
||||
?header_id,
|
||||
?check_result,
|
||||
"Requested to prove head. Selected to prove head. But it exceeds limits. \
|
||||
Going to select next header"
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
target: "bridge",
|
||||
relay_task_name=%self.relay_task_name,
|
||||
source=%P::SourceChain::NAME,
|
||||
%required_header,
|
||||
?header_id,
|
||||
%iterations,
|
||||
"Requested to prove head. Selected to prove head (after iterations)",
|
||||
);
|
||||
|
||||
// and then craft the submit-proof call
|
||||
let call = P::SubmitFinalityProofCallBuilder::build_submit_finality_proof_call(
|
||||
header, proof, false, context,
|
||||
);
|
||||
|
||||
return Ok((header_id, vec![call]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Background task that is responsible for starting headers relay.
|
||||
async fn background_task<P: BizinikiwiFinalitySyncPipeline>(
|
||||
source_client: impl Client<P::SourceChain>,
|
||||
target_client: impl Client<P::TargetChain>,
|
||||
target_transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
|
||||
headers_to_relay: HeadersToRelay,
|
||||
required_header_number: RequiredHeaderNumberRef<P::SourceChain>,
|
||||
metrics_params: Option<MetricsParams>,
|
||||
) where
|
||||
AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TargetChain> as pezsp_core::Pair>::Public>,
|
||||
{
|
||||
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 = BizinikiwiFinalitySource::<P, _>::new(
|
||||
source_client.clone(),
|
||||
Some(required_header_number.clone()),
|
||||
);
|
||||
let mut finality_target =
|
||||
BizinikiwiFinalityTarget::new(target_client.clone(), target_transaction_params);
|
||||
let mut latest_non_mandatory_at_source = Zero::zero();
|
||||
|
||||
let mut restart_relay = true;
|
||||
let pez_finality_relay_task = futures::future::Fuse::terminated();
|
||||
futures::pin_mut!(pez_finality_relay_task);
|
||||
|
||||
loop {
|
||||
select! {
|
||||
_ = async_std::task::sleep(P::TargetChain::AVERAGE_BLOCK_INTERVAL).fuse() => {},
|
||||
_ = pez_finality_relay_task => {
|
||||
// this should never happen in practice given the current code
|
||||
restart_relay = true;
|
||||
},
|
||||
}
|
||||
|
||||
// read best finalized source header number from source
|
||||
let best_finalized_source_header_at_source =
|
||||
best_finalized_source_header_at_source(&finality_source, &relay_task_name).await;
|
||||
if matches!(best_finalized_source_header_at_source, Err(ref e) if e.is_connection_error()) {
|
||||
relay_utils::relay_loop::reconnect_failed_client(
|
||||
FailedClient::Source,
|
||||
relay_utils::relay_loop::RECONNECT_DELAY,
|
||||
&mut finality_source,
|
||||
&mut finality_target,
|
||||
)
|
||||
.await;
|
||||
continue;
|
||||
}
|
||||
|
||||
// read best finalized source header number from target
|
||||
let best_finalized_source_header_at_target =
|
||||
best_finalized_source_header_at_target::<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,
|
||||
relay_utils::relay_loop::RECONNECT_DELAY,
|
||||
&mut finality_source,
|
||||
&mut finality_target,
|
||||
)
|
||||
.await;
|
||||
continue;
|
||||
}
|
||||
|
||||
// submit mandatory header if some headers are missing
|
||||
let best_finalized_source_header_at_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 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(),
|
||||
required_header_number_value,
|
||||
)
|
||||
.await;
|
||||
|
||||
tracing::trace!(
|
||||
target: "bridge",
|
||||
%relay_task_name,
|
||||
?required_header_number_value,
|
||||
?best_finalized_source_header_at_source_fmt,
|
||||
?best_finalized_source_header_at_target_fmt,
|
||||
?mandatory_scan_range,
|
||||
"Mandatory headers scan range"
|
||||
);
|
||||
|
||||
if let Some(mandatory_scan_range) = mandatory_scan_range {
|
||||
let relay_mandatory_header_result = relay_mandatory_header_from_range(
|
||||
&finality_source,
|
||||
&required_header_number,
|
||||
best_finalized_source_header_at_target_fmt,
|
||||
(
|
||||
std::cmp::max(mandatory_scan_range.0, latest_non_mandatory_at_source),
|
||||
mandatory_scan_range.1,
|
||||
),
|
||||
&relay_task_name,
|
||||
)
|
||||
.await;
|
||||
match relay_mandatory_header_result {
|
||||
Ok(true) => (),
|
||||
Ok(false) => {
|
||||
// there are no (or we don't need to relay them) mandatory headers in the range
|
||||
// => to avoid scanning the same headers over and over again, remember that
|
||||
latest_non_mandatory_at_source = mandatory_scan_range.1;
|
||||
|
||||
tracing::trace!(
|
||||
target: "bridge",
|
||||
%relay_task_name,
|
||||
source=%P::SourceChain::NAME,
|
||||
?mandatory_scan_range,
|
||||
"No mandatory headers in the range"
|
||||
);
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
target: "bridge",
|
||||
error=?e,
|
||||
%relay_task_name,
|
||||
source=%P::SourceChain::NAME,
|
||||
?mandatory_scan_range,
|
||||
"Failed to scan mandatory headers range"
|
||||
);
|
||||
|
||||
if e.is_connection_error() {
|
||||
relay_utils::relay_loop::reconnect_failed_client(
|
||||
FailedClient::Source,
|
||||
relay_utils::relay_loop::RECONNECT_DELAY,
|
||||
&mut finality_source,
|
||||
&mut finality_target,
|
||||
)
|
||||
.await;
|
||||
continue;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// start/restart relay
|
||||
if restart_relay {
|
||||
let stall_timeout = relay_bizinikiwi_client::transaction_stall_timeout(
|
||||
target_transactions_mortality,
|
||||
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
STALL_TIMEOUT,
|
||||
);
|
||||
|
||||
tracing::info!(
|
||||
target: "bridge",
|
||||
%relay_task_name,
|
||||
?headers_to_relay,
|
||||
?target_transactions_mortality,
|
||||
stall_timeout_as_mins=%stall_timeout.as_secs_f64() / 60.0f64,
|
||||
?stall_timeout,
|
||||
"Starting on-demand headers relay task"
|
||||
);
|
||||
|
||||
pez_finality_relay_task.set(
|
||||
pez_finality_relay::run(
|
||||
finality_source.clone(),
|
||||
finality_target.clone(),
|
||||
FinalitySyncParams {
|
||||
tick: std::cmp::max(
|
||||
P::SourceChain::AVERAGE_BLOCK_INTERVAL,
|
||||
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
),
|
||||
recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT,
|
||||
stall_timeout,
|
||||
headers_to_relay,
|
||||
},
|
||||
metrics_params.clone().unwrap_or_else(MetricsParams::disabled),
|
||||
futures::future::pending(),
|
||||
)
|
||||
.fuse(),
|
||||
);
|
||||
|
||||
restart_relay = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Some()` with inclusive range of headers which must be scanned for mandatory headers
|
||||
/// and the first of such headers must be submitted to the target node.
|
||||
async fn mandatory_headers_scan_range<C: Chain>(
|
||||
best_finalized_source_header_at_source: Option<C::BlockNumber>,
|
||||
best_finalized_source_header_at_target: Option<C::BlockNumber>,
|
||||
required_header_number: BlockNumberOf<C>,
|
||||
) -> Option<(C::BlockNumber, C::BlockNumber)> {
|
||||
// if we have been unable to read header number from the target, then let's assume
|
||||
// that it is the same as required header number. Otherwise we risk submitting
|
||||
// unneeded transactions
|
||||
let best_finalized_source_header_at_target =
|
||||
best_finalized_source_header_at_target.unwrap_or(required_header_number);
|
||||
|
||||
// if we have been unable to read header number from the source, then let's assume
|
||||
// that it is the same as at the target
|
||||
let best_finalized_source_header_at_source =
|
||||
best_finalized_source_header_at_source.unwrap_or(best_finalized_source_header_at_target);
|
||||
|
||||
// if 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;
|
||||
}
|
||||
|
||||
Some((
|
||||
best_finalized_source_header_at_target + One::one(),
|
||||
best_finalized_source_header_at_source,
|
||||
))
|
||||
}
|
||||
|
||||
/// Try to find mandatory header in the inclusive headers range and, if one is found, ask to relay
|
||||
/// it.
|
||||
///
|
||||
/// Returns `true` if header was found and (asked to be) relayed and `false` otherwise.
|
||||
async fn relay_mandatory_header_from_range<P, SourceClnt>(
|
||||
finality_source: &BizinikiwiFinalitySource<P, SourceClnt>,
|
||||
required_header_number: &RequiredHeaderNumberRef<P::SourceChain>,
|
||||
best_finalized_source_header_at_target: String,
|
||||
range: (BlockNumberOf<P::SourceChain>, BlockNumberOf<P::SourceChain>),
|
||||
relay_task_name: &str,
|
||||
) -> Result<bool, relay_bizinikiwi_client::Error>
|
||||
where
|
||||
P: BizinikiwiFinalitySyncPipeline,
|
||||
SourceClnt: Client<P::SourceChain>,
|
||||
{
|
||||
// search for mandatory header first
|
||||
let mandatory_source_header_number =
|
||||
find_mandatory_header_in_range(finality_source, range).await?;
|
||||
|
||||
// if there are no mandatory headers - we have nothing to do
|
||||
let mandatory_source_header_number = match mandatory_source_header_number {
|
||||
Some(mandatory_source_header_number) => mandatory_source_header_number,
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
// `find_mandatory_header` call may take a while => check if `required_header_number` is still
|
||||
// less than our `mandatory_source_header_number` before logging anything
|
||||
let mut required_header_number = required_header_number.lock().await;
|
||||
if *required_header_number >= mandatory_source_header_number {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
tracing::trace!(
|
||||
target: "bridge",
|
||||
%relay_task_name,
|
||||
source=%P::SourceChain::NAME,
|
||||
%best_finalized_source_header_at_target,
|
||||
at_source=%range.1,
|
||||
%mandatory_source_header_number,
|
||||
"Too many headers missing at target. Going to sync up to the mandatory"
|
||||
);
|
||||
|
||||
*required_header_number = mandatory_source_header_number;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Read best finalized source block number from source client.
|
||||
///
|
||||
/// Returns `None` if we have failed to read the number.
|
||||
async fn best_finalized_source_header_at_source<P, SourceClnt>(
|
||||
finality_source: &BizinikiwiFinalitySource<P, SourceClnt>,
|
||||
relay_task_name: &str,
|
||||
) -> Result<BlockNumberOf<P::SourceChain>, relay_bizinikiwi_client::Error>
|
||||
where
|
||||
P: BizinikiwiFinalitySyncPipeline,
|
||||
SourceClnt: Client<P::SourceChain>,
|
||||
{
|
||||
finality_source.on_chain_best_finalized_block_number().await.map_err(|error| {
|
||||
tracing::error!(
|
||||
target: "bridge",
|
||||
?error,
|
||||
%relay_task_name,
|
||||
"Failed to read best finalized source header from source"
|
||||
);
|
||||
|
||||
error
|
||||
})
|
||||
}
|
||||
|
||||
/// Read best finalized source block number from target client.
|
||||
///
|
||||
/// Returns `None` if we have failed to read the number.
|
||||
async fn best_finalized_source_header_at_target<P, TargetClnt>(
|
||||
finality_target: &BizinikiwiFinalityTarget<P, TargetClnt>,
|
||||
relay_task_name: &str,
|
||||
) -> Result<
|
||||
BlockNumberOf<P::SourceChain>,
|
||||
<BizinikiwiFinalityTarget<P, TargetClnt> as RelayClient>::Error,
|
||||
>
|
||||
where
|
||||
P: BizinikiwiFinalitySyncPipeline,
|
||||
TargetClnt: Client<P::TargetChain>,
|
||||
AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TargetChain> as pezsp_core::Pair>::Public>,
|
||||
{
|
||||
finality_target
|
||||
.best_finalized_source_block_id()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
tracing::error!(
|
||||
target: "bridge",
|
||||
?error,
|
||||
%relay_task_name,
|
||||
"Failed to read best finalized source header from target"
|
||||
);
|
||||
|
||||
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<P, SourceClnt>(
|
||||
finality_source: &BizinikiwiFinalitySource<P, SourceClnt>,
|
||||
range: (BlockNumberOf<P::SourceChain>, BlockNumberOf<P::SourceChain>),
|
||||
) -> Result<Option<BlockNumberOf<P::SourceChain>>, relay_bizinikiwi_client::Error>
|
||||
where
|
||||
P: BizinikiwiFinalitySyncPipeline,
|
||||
SourceClnt: Client<P::SourceChain>,
|
||||
{
|
||||
let mut current = range.0;
|
||||
while current <= range.1 {
|
||||
let header = finality_source.client().header_by_number(current).await?;
|
||||
if <P::FinalityEngine as Engine<P::SourceChain>>::ConsensusLogReader::schedules_authorities_change(
|
||||
header.digest(),
|
||||
) {
|
||||
return Ok(Some(current))
|
||||
}
|
||||
|
||||
current += One::one();
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// On-demand headers relay task name.
|
||||
fn on_demand_headers_relay_name<SourceChain: Chain, TargetChain: Chain>() -> String {
|
||||
format!("{}-to-{}-on-demand-headers", SourceChain::NAME, TargetChain::NAME)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use relay_bizinikiwi_client::test_chain::TestChain;
|
||||
|
||||
const AT_SOURCE: Option<BlockNumberOf<TestChain>> = Some(10);
|
||||
const AT_TARGET: Option<BlockNumberOf<TestChain>> = Some(1);
|
||||
|
||||
#[async_std::test]
|
||||
async fn mandatory_headers_scan_range_selects_range_if_some_headers_are_missing() {
|
||||
assert_eq!(
|
||||
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_already_queued() {
|
||||
assert_eq!(
|
||||
mandatory_headers_scan_range::<TestChain>(AT_SOURCE, AT_TARGET, AT_SOURCE.unwrap(),)
|
||||
.await,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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/>.
|
||||
|
||||
//! Types and functions intended to ease adding of new Bizinikiwi -> Bizinikiwi
|
||||
//! on-demand pipelines.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use relay_bizinikiwi_client::{BlockNumberOf, CallOf, Chain, Error as BizinikiwiError, HeaderIdOf};
|
||||
|
||||
pub mod headers;
|
||||
pub mod teyrchains;
|
||||
|
||||
/// On-demand headers relay that is relaying finalizing headers only when requested.
|
||||
#[async_trait]
|
||||
pub trait OnDemandRelay<SourceChain: Chain, TargetChain: Chain>: Send + Sync {
|
||||
/// Reconnect to source and target nodes.
|
||||
async fn reconnect(&self) -> Result<(), BizinikiwiError>;
|
||||
|
||||
/// Ask relay to relay source header with given number to the target chain.
|
||||
///
|
||||
/// Depending on implementation, on-demand relay may also relay `required_header` ancestors
|
||||
/// (e.g. if they're mandatory), or its descendants. The request is considered complete if
|
||||
/// the best avbailable header at the target chain has number that is larger than or equal
|
||||
/// to the `required_header`.
|
||||
async fn require_more_headers(&self, required_header: BlockNumberOf<SourceChain>);
|
||||
|
||||
/// Ask relay to prove source `required_header` to the `TargetChain`.
|
||||
///
|
||||
/// Returns number of header that is proved (it may be the `required_header` or one of its
|
||||
/// descendants) and calls for delivering the proof.
|
||||
async fn prove_header(
|
||||
&self,
|
||||
required_header: BlockNumberOf<SourceChain>,
|
||||
) -> Result<(HeaderIdOf<SourceChain>, Vec<CallOf<TargetChain>>), BizinikiwiError>;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,108 @@
|
||||
// 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/>.
|
||||
|
||||
//! Types and functions intended to ease adding of new Bizinikiwi -> Bizinikiwi
|
||||
//! teyrchain finality proofs synchronization pipelines.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_pezkuwi_core::teyrchains::{ParaHash, ParaHeadsProof, ParaId};
|
||||
use bp_teyrchains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
|
||||
use pezpallet_bridge_teyrchains::{Call as BridgeTeyrchainsCall, Config as BridgeTeyrchainsConfig};
|
||||
use relay_bizinikiwi_client::{
|
||||
CallOf, Chain, ChainWithTransactions, HeaderIdOf, RelayChain, Teyrchain,
|
||||
};
|
||||
use std::{fmt::Debug, marker::PhantomData};
|
||||
use teyrchains_relay::TeyrchainsPipeline;
|
||||
|
||||
pub mod source;
|
||||
pub mod target;
|
||||
|
||||
/// Bizinikiwi -> Bizinikiwi teyrchain finality proofs synchronization pipeline.
|
||||
///
|
||||
/// This is currently restricted to the single teyrchain, because it is how it
|
||||
/// will be used (at least) initially.
|
||||
#[async_trait]
|
||||
pub trait BizinikiwiTeyrchainsPipeline: 'static + Clone + Debug + Send + Sync {
|
||||
/// Headers of this teyrchain are submitted to the `Self::TargetChain`.
|
||||
type SourceTeyrchain: Teyrchain;
|
||||
/// Relay chain that is storing headers of `Self::SourceTeyrchain`.
|
||||
type SourceRelayChain: RelayChain;
|
||||
/// Target chain where `Self::SourceTeyrchain` headers are submitted.
|
||||
type TargetChain: ChainWithTransactions;
|
||||
|
||||
/// How submit teyrchains heads call is built?
|
||||
type SubmitTeyrchainHeadsCallBuilder: SubmitTeyrchainHeadsCallBuilder<Self>;
|
||||
}
|
||||
|
||||
/// Adapter that allows all `BizinikiwiTeyrchainsPipeline` to act as `TeyrchainsPipeline`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TeyrchainsPipelineAdapter<P: BizinikiwiTeyrchainsPipeline> {
|
||||
_phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiTeyrchainsPipeline> TeyrchainsPipeline for TeyrchainsPipelineAdapter<P> {
|
||||
type SourceTeyrchain = P::SourceTeyrchain;
|
||||
type SourceRelayChain = P::SourceRelayChain;
|
||||
type TargetChain = P::TargetChain;
|
||||
}
|
||||
|
||||
/// Different ways of building `submit_teyrchain_heads` calls.
|
||||
pub trait SubmitTeyrchainHeadsCallBuilder<P: BizinikiwiTeyrchainsPipeline>:
|
||||
'static + Send + Sync
|
||||
{
|
||||
/// Given teyrchains and their heads proof, build call of `submit_teyrchain_heads`
|
||||
/// function of bridge teyrchains module at the target chain.
|
||||
fn build_submit_teyrchain_heads_call(
|
||||
at_relay_block: HeaderIdOf<P::SourceRelayChain>,
|
||||
teyrchains: Vec<(ParaId, ParaHash)>,
|
||||
teyrchain_heads_proof: ParaHeadsProof,
|
||||
is_free_execution_expected: bool,
|
||||
) -> CallOf<P::TargetChain>;
|
||||
}
|
||||
|
||||
/// Building `submit_teyrchain_heads` call when you have direct access to the target
|
||||
/// chain runtime.
|
||||
pub struct DirectSubmitTeyrchainHeadsCallBuilder<P, R, I> {
|
||||
_phantom: PhantomData<(P, R, I)>,
|
||||
}
|
||||
|
||||
impl<P, R, I> SubmitTeyrchainHeadsCallBuilder<P> for DirectSubmitTeyrchainHeadsCallBuilder<P, R, I>
|
||||
where
|
||||
P: BizinikiwiTeyrchainsPipeline,
|
||||
P::SourceRelayChain: Chain<Hash = RelayBlockHash, BlockNumber = RelayBlockNumber>,
|
||||
R: BridgeTeyrchainsConfig<I> + Send + Sync,
|
||||
I: 'static + Send + Sync,
|
||||
R::BridgedChain: pezbp_runtime::Chain<
|
||||
BlockNumber = RelayBlockNumber,
|
||||
Hash = RelayBlockHash,
|
||||
Hasher = RelayBlockHasher,
|
||||
>,
|
||||
CallOf<P::TargetChain>: From<BridgeTeyrchainsCall<R, I>>,
|
||||
{
|
||||
fn build_submit_teyrchain_heads_call(
|
||||
at_relay_block: HeaderIdOf<P::SourceRelayChain>,
|
||||
teyrchains: Vec<(ParaId, ParaHash)>,
|
||||
teyrchain_heads_proof: ParaHeadsProof,
|
||||
_is_free_execution_expected: bool,
|
||||
) -> CallOf<P::TargetChain> {
|
||||
BridgeTeyrchainsCall::<R, I>::submit_teyrchain_heads {
|
||||
at_relay_block: (at_relay_block.0, at_relay_block.1),
|
||||
teyrchains,
|
||||
teyrchain_heads_proof,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// 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/>.
|
||||
|
||||
//! Teyrchain heads source.
|
||||
|
||||
use crate::{
|
||||
proofs::to_raw_storage_proof,
|
||||
teyrchains::{BizinikiwiTeyrchainsPipeline, TeyrchainsPipelineAdapter},
|
||||
};
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use async_trait::async_trait;
|
||||
use bp_pezkuwi_core::teyrchains::{ParaHash, ParaHead, ParaHeadsProof, ParaId};
|
||||
use pezbp_runtime::HeaderIdProvider;
|
||||
use bp_teyrchains::teyrchain_head_storage_key_at_source;
|
||||
use codec::Decode;
|
||||
use relay_bizinikiwi_client::{
|
||||
is_ancient_block, Chain, Client, Error as BizinikiwiError, HeaderIdOf, HeaderOf, RelayChain,
|
||||
TeyrchainBase,
|
||||
};
|
||||
use relay_utils::relay_loop::Client as RelayClient;
|
||||
use teyrchains_relay::teyrchains_loop::{AvailableHeader, SourceClient};
|
||||
|
||||
/// Shared updatable reference to the maximal teyrchain header id that we want to sync from the
|
||||
/// source.
|
||||
pub type RequiredHeaderIdRef<C> = Arc<Mutex<AvailableHeader<HeaderIdOf<C>>>>;
|
||||
|
||||
/// Bizinikiwi client as teyrchain heads source.
|
||||
#[derive(Clone)]
|
||||
pub struct TeyrchainsSource<P: BizinikiwiTeyrchainsPipeline, SourceRelayClnt> {
|
||||
client: SourceRelayClnt,
|
||||
max_head_id: RequiredHeaderIdRef<P::SourceTeyrchain>,
|
||||
}
|
||||
|
||||
impl<P: BizinikiwiTeyrchainsPipeline, SourceRelayClnt: Client<P::SourceRelayChain>>
|
||||
TeyrchainsSource<P, SourceRelayClnt>
|
||||
{
|
||||
/// Creates new teyrchains source client.
|
||||
pub fn new(
|
||||
client: SourceRelayClnt,
|
||||
max_head_id: RequiredHeaderIdRef<P::SourceTeyrchain>,
|
||||
) -> Self {
|
||||
TeyrchainsSource { client, max_head_id }
|
||||
}
|
||||
|
||||
/// Returns reference to the underlying RPC client.
|
||||
pub fn client(&self) -> &SourceRelayClnt {
|
||||
&self.client
|
||||
}
|
||||
|
||||
/// Return decoded head of given teyrchain.
|
||||
pub async fn on_chain_para_head_id(
|
||||
&self,
|
||||
at_block: HeaderIdOf<P::SourceRelayChain>,
|
||||
) -> Result<Option<HeaderIdOf<P::SourceTeyrchain>>, BizinikiwiError> {
|
||||
let para_id = ParaId(P::SourceTeyrchain::TEYRCHAIN_ID);
|
||||
let storage_key =
|
||||
teyrchain_head_storage_key_at_source(P::SourceRelayChain::PARAS_PALLET_NAME, para_id);
|
||||
let para_head: Option<ParaHead> =
|
||||
self.client.storage_value(at_block.hash(), storage_key).await?;
|
||||
let para_head = match para_head {
|
||||
Some(para_head) => para_head,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let para_head: HeaderOf<P::SourceTeyrchain> = Decode::decode(&mut ¶_head.0[..])?;
|
||||
Ok(Some(para_head.id()))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: BizinikiwiTeyrchainsPipeline, SourceRelayClnt: Client<P::SourceRelayChain>> RelayClient
|
||||
for TeyrchainsSource<P, SourceRelayClnt>
|
||||
{
|
||||
type Error = BizinikiwiError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), BizinikiwiError> {
|
||||
self.client.reconnect().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: BizinikiwiTeyrchainsPipeline, SourceRelayClnt: Client<P::SourceRelayChain>>
|
||||
SourceClient<TeyrchainsPipelineAdapter<P>> for TeyrchainsSource<P, SourceRelayClnt>
|
||||
where
|
||||
P::SourceTeyrchain: Chain<Hash = ParaHash>,
|
||||
{
|
||||
async fn ensure_synced(&self) -> Result<bool, Self::Error> {
|
||||
match self.client.ensure_synced().await {
|
||||
Ok(_) => Ok(true),
|
||||
Err(BizinikiwiError::ClientNotSynced(_)) => Ok(false),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
async fn teyrchain_head(
|
||||
&self,
|
||||
at_block: HeaderIdOf<P::SourceRelayChain>,
|
||||
) -> Result<AvailableHeader<HeaderIdOf<P::SourceTeyrchain>>, Self::Error> {
|
||||
// if requested relay header is ancient, then we don't even want to try to read the
|
||||
// teyrchain head - we simply return `Unavailable`
|
||||
let best_block_number = self.client.best_finalized_header_number().await?;
|
||||
if is_ancient_block(at_block.number(), best_block_number) {
|
||||
tracing::trace!(
|
||||
target: "bridge",
|
||||
source_relay_chain=%P::SourceRelayChain::NAME,
|
||||
?at_block,
|
||||
source=%P::SourceTeyrchain::NAME,
|
||||
"Block is ancient. Cannot prove the header there"
|
||||
);
|
||||
return Ok(AvailableHeader::Unavailable);
|
||||
}
|
||||
|
||||
// else - try to read head from the source client
|
||||
let mut para_head_id = AvailableHeader::Missing;
|
||||
if let Some(on_chain_para_head_id) = self.on_chain_para_head_id(at_block).await? {
|
||||
// Never return head that is larger than requested. This way we'll never sync
|
||||
// headers past `max_header_id`.
|
||||
para_head_id = match *self.max_head_id.lock().await {
|
||||
AvailableHeader::Unavailable => AvailableHeader::Unavailable,
|
||||
AvailableHeader::Missing => {
|
||||
// `max_header_id` is not set. There is no limit.
|
||||
AvailableHeader::Available(on_chain_para_head_id)
|
||||
},
|
||||
AvailableHeader::Available(max_head_id) if on_chain_para_head_id >= max_head_id => {
|
||||
// We report at most `max_header_id`.
|
||||
AvailableHeader::Available(std::cmp::min(on_chain_para_head_id, max_head_id))
|
||||
},
|
||||
AvailableHeader::Available(_) => {
|
||||
// the `max_head_id` is not yet available at the source chain => wait and avoid
|
||||
// syncing extra headers
|
||||
AvailableHeader::Unavailable
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(para_head_id)
|
||||
}
|
||||
|
||||
async fn prove_teyrchain_head(
|
||||
&self,
|
||||
at_block: HeaderIdOf<P::SourceRelayChain>,
|
||||
) -> Result<(ParaHeadsProof, ParaHash), Self::Error> {
|
||||
let teyrchain = ParaId(P::SourceTeyrchain::TEYRCHAIN_ID);
|
||||
let storage_key =
|
||||
teyrchain_head_storage_key_at_source(P::SourceRelayChain::PARAS_PALLET_NAME, teyrchain);
|
||||
|
||||
let storage_proof =
|
||||
self.client.prove_storage(at_block.hash(), vec![storage_key.clone()]).await?;
|
||||
|
||||
// why we're reading teyrchain head here once again (it has already been read at the
|
||||
// `teyrchain_head`)? that's because `teyrchain_head` sometimes returns obsolete teyrchain
|
||||
// head and loop sometimes asks to prove this obsolete head and gets other (actual) head
|
||||
// instead
|
||||
//
|
||||
// => since we want to provide proper hashes in our `submit_teyrchain_heads` call, we're
|
||||
// rereading actual value here
|
||||
let teyrchain_head = self
|
||||
.client
|
||||
.storage_value::<ParaHead>(at_block.hash(), storage_key)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
BizinikiwiError::Custom(format!(
|
||||
"Failed to read expected teyrchain {teyrchain:?} head at {at_block:?}"
|
||||
))
|
||||
})?;
|
||||
let teyrchain_head_hash = teyrchain_head.hash();
|
||||
|
||||
Ok((
|
||||
ParaHeadsProof {
|
||||
storage_proof: to_raw_storage_proof::<P::SourceRelayChain>(storage_proof),
|
||||
},
|
||||
teyrchain_head_hash,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
// 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/>.
|
||||
|
||||
//! Teyrchain heads target.
|
||||
|
||||
use crate::{
|
||||
teyrchains::{
|
||||
SubmitTeyrchainHeadsCallBuilder, BizinikiwiTeyrchainsPipeline, TeyrchainsPipelineAdapter,
|
||||
},
|
||||
TransactionParams,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_pezkuwi_core::{
|
||||
teyrchains::{ParaHash, ParaHeadsProof, ParaId},
|
||||
BlockNumber as RelayBlockNumber,
|
||||
};
|
||||
use pezbp_runtime::{
|
||||
Chain as ChainBase, HeaderId, HeaderIdProvider, StorageDoubleMapKeyProvider,
|
||||
StorageMapKeyProvider,
|
||||
};
|
||||
use bp_teyrchains::{
|
||||
ImportedParaHeadsKeyProvider, ParaInfo, ParaStoredHeaderData, ParasInfoKeyProvider,
|
||||
};
|
||||
use relay_bizinikiwi_client::{
|
||||
AccountIdOf, AccountKeyPairOf, BlockNumberOf, Chain, Client, Error as BizinikiwiError,
|
||||
HeaderIdOf, RelayChain, TeyrchainBase, TransactionEra, TransactionTracker, UnsignedTransaction,
|
||||
};
|
||||
use relay_utils::relay_loop::Client as RelayClient;
|
||||
use pezsp_core::Pair;
|
||||
use pezsp_runtime::traits::Header;
|
||||
use teyrchains_relay::teyrchains_loop::TargetClient;
|
||||
|
||||
/// Bizinikiwi client as teyrchain heads source.
|
||||
pub struct TeyrchainsTarget<P: BizinikiwiTeyrchainsPipeline, SourceClnt, TargetClnt> {
|
||||
source_client: SourceClnt,
|
||||
target_client: TargetClnt,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
|
||||
}
|
||||
|
||||
impl<
|
||||
P: BizinikiwiTeyrchainsPipeline,
|
||||
SourceClnt: Client<P::SourceRelayChain>,
|
||||
TargetClnt: Client<P::TargetChain>,
|
||||
> TeyrchainsTarget<P, SourceClnt, TargetClnt>
|
||||
{
|
||||
/// Creates new teyrchains target client.
|
||||
pub fn new(
|
||||
source_client: SourceClnt,
|
||||
target_client: TargetClnt,
|
||||
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
|
||||
) -> Self {
|
||||
TeyrchainsTarget { source_client, target_client, transaction_params }
|
||||
}
|
||||
|
||||
/// Returns reference to the underlying RPC client.
|
||||
pub fn target_client(&self) -> &TargetClnt {
|
||||
&self.target_client
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
P: BizinikiwiTeyrchainsPipeline,
|
||||
SourceClnt: Client<P::SourceRelayChain>,
|
||||
TargetClnt: Clone,
|
||||
> Clone for TeyrchainsTarget<P, SourceClnt, TargetClnt>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
TeyrchainsTarget {
|
||||
source_client: self.source_client.clone(),
|
||||
target_client: self.target_client.clone(),
|
||||
transaction_params: self.transaction_params.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<
|
||||
P: BizinikiwiTeyrchainsPipeline,
|
||||
SourceClnt: Client<P::SourceRelayChain>,
|
||||
TargetClnt: Client<P::TargetChain>,
|
||||
> RelayClient for TeyrchainsTarget<P, SourceClnt, TargetClnt>
|
||||
{
|
||||
type Error = BizinikiwiError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), BizinikiwiError> {
|
||||
self.target_client.reconnect().await?;
|
||||
self.source_client.reconnect().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P, SourceClnt, TargetClnt> TargetClient<TeyrchainsPipelineAdapter<P>>
|
||||
for TeyrchainsTarget<P, SourceClnt, TargetClnt>
|
||||
where
|
||||
P: BizinikiwiTeyrchainsPipeline,
|
||||
SourceClnt: Client<P::SourceRelayChain>,
|
||||
TargetClnt: Client<P::TargetChain>,
|
||||
AccountIdOf<P::TargetChain>: From<<AccountKeyPairOf<P::TargetChain> as Pair>::Public>,
|
||||
P::SourceTeyrchain: ChainBase<Hash = ParaHash>,
|
||||
P::SourceRelayChain: ChainBase<BlockNumber = RelayBlockNumber>,
|
||||
{
|
||||
type TransactionTracker = TransactionTracker<P::TargetChain, TargetClnt>;
|
||||
|
||||
async fn best_block(&self) -> Result<HeaderIdOf<P::TargetChain>, Self::Error> {
|
||||
let best_header = self.target_client.best_header().await?;
|
||||
let best_id = best_header.id();
|
||||
|
||||
Ok(best_id)
|
||||
}
|
||||
|
||||
async fn best_finalized_source_relay_chain_block(
|
||||
&self,
|
||||
at_block: &HeaderIdOf<P::TargetChain>,
|
||||
) -> Result<HeaderIdOf<P::SourceRelayChain>, Self::Error> {
|
||||
self.target_client
|
||||
.state_call::<_, Option<HeaderIdOf<P::SourceRelayChain>>>(
|
||||
at_block.hash(),
|
||||
P::SourceRelayChain::BEST_FINALIZED_HEADER_ID_METHOD.into(),
|
||||
(),
|
||||
)
|
||||
.await?
|
||||
.map(Ok)
|
||||
.unwrap_or(Err(BizinikiwiError::BridgePalletIsNotInitialized))
|
||||
}
|
||||
|
||||
async fn free_source_relay_headers_interval(
|
||||
&self,
|
||||
) -> Result<Option<BlockNumberOf<P::SourceRelayChain>>, Self::Error> {
|
||||
Ok(self
|
||||
.target_client
|
||||
.state_call(
|
||||
self.target_client.best_header().await?.hash(),
|
||||
P::SourceRelayChain::FREE_HEADERS_INTERVAL_METHOD.into(),
|
||||
(),
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
tracing::info!(
|
||||
target: "bridge",
|
||||
error=?e,
|
||||
methpd=%P::SourceRelayChain::FREE_HEADERS_INTERVAL_METHOD,
|
||||
target=%P::TargetChain::NAME,
|
||||
"Call has failed. Treating as `None`"
|
||||
);
|
||||
None
|
||||
}))
|
||||
}
|
||||
|
||||
async fn teyrchain_head(
|
||||
&self,
|
||||
at_block: HeaderIdOf<P::TargetChain>,
|
||||
) -> Result<
|
||||
Option<(HeaderIdOf<P::SourceRelayChain>, HeaderIdOf<P::SourceTeyrchain>)>,
|
||||
Self::Error,
|
||||
> {
|
||||
// read best teyrchain head from the target bridge-teyrchains pezpallet
|
||||
let storage_key = ParasInfoKeyProvider::final_key(
|
||||
P::SourceRelayChain::WITH_CHAIN_BRIDGE_TEYRCHAINS_PALLET_NAME,
|
||||
&P::SourceTeyrchain::TEYRCHAIN_ID.into(),
|
||||
);
|
||||
let storage_value: Option<ParaInfo> =
|
||||
self.target_client.storage_value(at_block.hash(), storage_key).await?;
|
||||
let para_info = match storage_value {
|
||||
Some(para_info) => para_info,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
// now we need to get full header ids. For source relay chain it is simple, because we
|
||||
// are connected
|
||||
let relay_header_id = self
|
||||
.source_client
|
||||
.header_by_number(para_info.best_head_hash.at_relay_block_number)
|
||||
.await?
|
||||
.id();
|
||||
|
||||
// for teyrchain, we need to read from the target chain runtime storage
|
||||
let storage_key = ImportedParaHeadsKeyProvider::final_key(
|
||||
P::SourceRelayChain::WITH_CHAIN_BRIDGE_TEYRCHAINS_PALLET_NAME,
|
||||
&P::SourceTeyrchain::TEYRCHAIN_ID.into(),
|
||||
¶_info.best_head_hash.head_hash,
|
||||
);
|
||||
let storage_value: Option<ParaStoredHeaderData> =
|
||||
self.target_client.storage_value(at_block.hash(), storage_key).await?;
|
||||
let para_head_number = match storage_value {
|
||||
Some(para_head_data) =>
|
||||
para_head_data.decode_teyrchain_head_data::<P::SourceTeyrchain>()?.number,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let para_head_id = HeaderId(para_head_number, para_info.best_head_hash.head_hash);
|
||||
Ok(Some((relay_header_id, para_head_id)))
|
||||
}
|
||||
|
||||
async fn submit_teyrchain_head_proof(
|
||||
&self,
|
||||
at_relay_block: HeaderIdOf<P::SourceRelayChain>,
|
||||
updated_head_hash: ParaHash,
|
||||
proof: ParaHeadsProof,
|
||||
is_free_execution_expected: bool,
|
||||
) -> Result<Self::TransactionTracker, Self::Error> {
|
||||
let transaction_params = self.transaction_params.clone();
|
||||
let call = P::SubmitTeyrchainHeadsCallBuilder::build_submit_teyrchain_heads_call(
|
||||
at_relay_block,
|
||||
vec![(ParaId(P::SourceTeyrchain::TEYRCHAIN_ID), updated_head_hash)],
|
||||
proof,
|
||||
is_free_execution_expected,
|
||||
);
|
||||
self.target_client
|
||||
.submit_and_watch_signed_extrinsic(
|
||||
&transaction_params.signer,
|
||||
move |best_block_id, transaction_nonce| {
|
||||
Ok(UnsignedTransaction::new(call.into(), transaction_nonce)
|
||||
.era(TransactionEra::new(best_block_id, transaction_params.mortality)))
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user