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:
2025-12-16 09:57:23 +03:00
parent eea003e14d
commit 3139ffa25e
3022 changed files with 42157 additions and 23579 deletions
@@ -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,
},
},
}
);
}
}
@@ -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)))
}
}
@@ -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),
))
}
}
@@ -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, &current_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 &para_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(),
&para_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
}
}