Newer
Older
Svyatoslav Nikolsky
committed
// 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 Substrate -> Substrate
//! finality proofs synchronization pipelines.
Svyatoslav Nikolsky
committed
finality::{source::SubstrateFinalitySource, target::SubstrateFinalityTarget},
finality_base::{engine::Engine, SubstrateFinalityPipeline, SubstrateFinalityProof},
Svyatoslav Nikolsky
committed
Svyatoslav Nikolsky
committed
use async_trait::async_trait;
use bp_header_chain::justification::{GrandpaJustification, JustificationVerificationContext};
Svyatoslav Nikolsky
committed
use finality_relay::{
FinalityPipeline, FinalitySyncPipeline, HeadersToRelay, SourceClient, TargetClient,
};
use pallet_bridge_grandpa::{Call as BridgeGrandpaCall, Config as BridgeGrandpaConfig};
transaction_stall_timeout, AccountIdOf, AccountKeyPairOf, BlockNumberOf, CallOf, Chain,
ChainWithTransactions, Client, HashOf, HeaderOf, SyncHeader,
Svyatoslav Nikolsky
committed
use relay_utils::{metrics::MetricsParams, TrackedTransactionStatus, TransactionTracker};
use std::{fmt::Debug, marker::PhantomData};
Svyatoslav Nikolsky
committed
pub mod initialize;
pub mod source;
pub mod target;
Svyatoslav Nikolsky
committed
/// Default limit of recent finality proofs.
///
/// Finality delay of 4096 blocks is unlikely to happen in practice in
/// Substrate+GRANDPA based chains (good to know).
pub(crate) const RECENT_FINALITY_PROOFS_LIMIT: usize = 4096;
Svyatoslav Nikolsky
committed
/// Convenience trait that adds bounds to `SubstrateFinalitySyncPipeline`.
pub trait BaseSubstrateFinalitySyncPipeline:
SubstrateFinalityPipeline<TargetChain = Self::BoundedTargetChain>
{
/// Bounded `SubstrateFinalityPipeline::TargetChain`.
type BoundedTargetChain: ChainWithTransactions<AccountId = Self::BoundedTargetChainAccountId>;
/// Bounded `AccountIdOf<SubstrateFinalityPipeline::TargetChain>`.
type BoundedTargetChainAccountId: From<<AccountKeyPairOf<Self::BoundedTargetChain> as Pair>::Public>
+ Send;
}
impl<T> BaseSubstrateFinalitySyncPipeline for T
where
T: SubstrateFinalityPipeline,
T::TargetChain: ChainWithTransactions,
AccountIdOf<T::TargetChain>: From<<AccountKeyPairOf<Self::TargetChain> as Pair>::Public>,
{
type BoundedTargetChain = T::TargetChain;
type BoundedTargetChainAccountId = AccountIdOf<T::TargetChain>;
}
/// Substrate -> Substrate finality proofs synchronization pipeline.
Svyatoslav Nikolsky
committed
#[async_trait]
pub trait SubstrateFinalitySyncPipeline: BaseSubstrateFinalitySyncPipeline {
/// How submit finality proof call is built?
type SubmitFinalityProofCallBuilder: SubmitFinalityProofCallBuilder<Self>;
/// Add relay guards if required.
Svyatoslav Nikolsky
committed
async fn start_relay_guards(
target_client: &Client<Self::TargetChain>,
enable_version_guard: bool,
Svyatoslav Nikolsky
committed
) -> relay_substrate_client::Result<()> {
if enable_version_guard {
relay_substrate_client::guard::abort_on_spec_version_change(
target_client.clone(),
target_client.simple_runtime_version().await?.spec_version,
);
}
Svyatoslav Nikolsky
committed
Ok(())
Svyatoslav Nikolsky
committed
}
Svyatoslav Nikolsky
committed
/// Adapter that allows all `SubstrateFinalitySyncPipeline` to act as `FinalitySyncPipeline`.
#[derive(Clone, Debug)]
pub struct FinalitySyncPipelineAdapter<P: SubstrateFinalitySyncPipeline> {
Svyatoslav Nikolsky
committed
}
impl<P: SubstrateFinalitySyncPipeline> 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 = SubstrateFinalityProof<P>;
}
impl<P: SubstrateFinalitySyncPipeline> FinalitySyncPipeline for FinalitySyncPipelineAdapter<P> {
Serban Iorga
committed
type ConsensusLogReader = <P::FinalityEngine as Engine<P::SourceChain>>::ConsensusLogReader;
type Header = SyncHeader<HeaderOf<P::SourceChain>>;
Svyatoslav Nikolsky
committed
}
/// Different ways of building `submit_finality_proof` calls.
pub trait SubmitFinalityProofCallBuilder<P: SubstrateFinalitySyncPipeline> {
/// 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: SubstrateFinalityProof<P>,
Svyatoslav Nikolsky
committed
is_free_execution_expected: bool,
context: <<P as SubstrateFinalityPipeline>::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)>,
Svyatoslav Nikolsky
committed
}
impl<P, R, I> SubmitFinalityProofCallBuilder<P>
for DirectSubmitGrandpaFinalityProofCallBuilder<P, R, I>
Svyatoslav Nikolsky
committed
where
P: SubstrateFinalitySyncPipeline,
R: BridgeGrandpaConfig<I>,
I: 'static,
R::BridgedChain: bp_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,
>,
Svyatoslav Nikolsky
committed
{
fn build_submit_finality_proof_call(
header: SyncHeader<HeaderOf<P::SourceChain>>,
proof: GrandpaJustification<HeaderOf<P::SourceChain>>,
Svyatoslav Nikolsky
committed
_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()
}
}
Svyatoslav Nikolsky
committed
/// 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_substrate_client::SyncHeader<
relay_substrate_client::HeaderOf<
<$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain
>
>,
proof: bp_header_chain::justification::GrandpaJustification<
relay_substrate_client::HeaderOf<
<$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain
Svyatoslav Nikolsky
committed
_is_free_execution_expected: bool,
_context: bp_header_chain::justification::JustificationVerificationContext,
) -> relay_substrate_client::CallOf<
<$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::TargetChain
bp_runtime::paste::item! {
$bridge_grandpa($submit_finality_proof {
finality_target: Box::new(header.into_inner()),
justification: proof
})
}
Svyatoslav Nikolsky
committed
}
Svyatoslav Nikolsky
committed
/// 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_substrate_client::SyncHeader<
relay_substrate_client::HeaderOf<
<$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain
>
>,
proof: bp_header_chain::justification::GrandpaJustification<
relay_substrate_client::HeaderOf<
<$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::SourceChain
>
>,
Svyatoslav Nikolsky
committed
is_free_execution_expected: bool,
Svyatoslav Nikolsky
committed
context: bp_header_chain::justification::JustificationVerificationContext,
) -> relay_substrate_client::CallOf<
<$pipeline as $crate::finality_base::SubstrateFinalityPipeline>::TargetChain
> {
bp_runtime::paste::item! {
$bridge_grandpa($submit_finality_proof {
finality_target: Box::new(header.into_inner()),
justification: proof,
Svyatoslav Nikolsky
committed
current_set_id: context.authority_set_id,
is_free_execution_expected,
Svyatoslav Nikolsky
committed
})
}
}
}
};
}
/// Run Substrate-to-Substrate finality sync loop.
pub async fn run<P: SubstrateFinalitySyncPipeline>(
source_client: Client<P::SourceChain>,
target_client: Client<P::TargetChain>,
Svyatoslav Nikolsky
committed
headers_to_relay: HeadersToRelay,
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
Svyatoslav Nikolsky
committed
metrics_params: MetricsParams,
) -> anyhow::Result<()> {
Svyatoslav Nikolsky
committed
log::info!(
target: "bridge",
Svyatoslav Nikolsky
committed
"Starting {} -> {} finality proof relay: relaying {:?} headers",
P::SourceChain::NAME,
P::TargetChain::NAME,
Svyatoslav Nikolsky
committed
headers_to_relay,
Svyatoslav Nikolsky
committed
);
finality_relay::run(
SubstrateFinalitySource::<P>::new(source_client, None),
SubstrateFinalityTarget::<P>::new(target_client, transaction_params.clone()),
finality_relay::FinalitySyncParams {
P::SourceChain::AVERAGE_BLOCK_INTERVAL,
P::TargetChain::AVERAGE_BLOCK_INTERVAL,
Svyatoslav Nikolsky
committed
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,
Svyatoslav Nikolsky
committed
headers_to_relay,
Svyatoslav Nikolsky
committed
},
metrics_params,
futures::future::pending(),
.map_err(|e| anyhow::format_err!("{}", e))
Svyatoslav Nikolsky
committed
}
Svyatoslav Nikolsky
committed
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/// Relay single header. No checks are made to ensure that transaction will succeed.
pub async fn relay_single_header<P: SubstrateFinalitySyncPipeline>(
source_client: Client<P::SourceChain>,
target_client: Client<P::TargetChain>,
transaction_params: TransactionParams<AccountKeyPairOf<P::TargetChain>>,
header_number: BlockNumberOf<P::SourceChain>,
) -> anyhow::Result<()> {
let finality_source = SubstrateFinalitySource::<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 = SubstrateFinalityTarget::<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,
)),
}
}