mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 05:11:02 +00:00
Relay Millau && Rialto headers using (future) finality verifier API (#761)
* finality proofs relay * SyncHeader::is_mandatory * empty ancestry proof * logs * fixed submit condition * fixed wrong split index * tick comment * recent_finality_proofs * basic finality loop tests * removed obsolete files * rename files in substrate relay * fmt * clippy * fixed TODOs * clippy * stop syncing if target node is out of sync * more clippy * more clippy * Update relays/finality-relay/src/finality_loop.rs Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com> * Update relays/finality-relay/src/finality_loop.rs Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com> * Update relays/finality-relay/src/finality_loop.rs Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com> * docs * moved doc * typo * Update relays/finality-relay/src/finality_loop_tests.rs Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com> * Update relays/finality-relay/src/finality_loop_tests.rs Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com> * header_and_finality_proof_by_number -> header_and_finality_proof * VecDeque isn't required (because of make_contiguous) * fixed wrong expect * Update relays/finality-relay/src/finality_loop.rs Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com> * Update relays/substrate/src/rialto_headers_to_millau.rs Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com> * Update relays/substrate/src/rialto_headers_to_millau.rs Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com> * RialtoSyncHeader * Update relays/finality-relay/src/finality_loop.rs Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com> * Update relays/finality-relay/src/finality_loop.rs Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com> * removed wrong comment * Update relays/finality-relay/src/finality_loop.rs Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com> * fix used runtime methods names * fix for new jsonrpsee * fix comment * initialize finality verifier pallet * fmt Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com> Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>
This commit is contained in:
committed by
Bastian Köcher
parent
e13ff320ea
commit
f87053c1cb
@@ -26,12 +26,13 @@ bp-polkadot = { path = "../../primitives/polkadot" }
|
||||
bp-runtime = { path = "../../primitives/runtime" }
|
||||
bp-rialto = { path = "../../primitives/rialto" }
|
||||
bridge-runtime-common = { path = "../../bin/runtime-common" }
|
||||
finality-relay = { path = "../finality-relay" }
|
||||
headers-relay = { path = "../headers-relay" }
|
||||
messages-relay = { path = "../messages-relay" }
|
||||
millau-runtime = { path = "../../bin/millau/runtime" }
|
||||
pallet-bridge-call-dispatch = { path = "../../modules/call-dispatch" }
|
||||
pallet-finality-verifier = { path = "../../modules/finality-verifier" }
|
||||
pallet-message-lane = { path = "../../modules/message-lane" }
|
||||
pallet-substrate-bridge = { path = "../../modules/substrate" }
|
||||
relay-kusama-client = { path = "../kusama-client" }
|
||||
relay-millau-client = { path = "../millau-client" }
|
||||
relay-polkadot-client = { path = "../polkadot-client" }
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// 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/>.
|
||||
|
||||
//! Substrate-to-Substrate headers sync entrypoint.
|
||||
|
||||
use crate::finality_target::SubstrateFinalityTarget;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codec::Encode;
|
||||
use finality_relay::{FinalitySyncParams, FinalitySyncPipeline};
|
||||
use relay_substrate_client::{
|
||||
finality_source::{FinalitySource, Justification},
|
||||
BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf, SyncHeader,
|
||||
};
|
||||
use relay_utils::BlockNumberBase;
|
||||
use std::{fmt::Debug, marker::PhantomData, time::Duration};
|
||||
|
||||
/// Default synchronization loop timeout.
|
||||
const STALL_TIMEOUT: Duration = Duration::from_secs(120);
|
||||
/// 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).
|
||||
const RECENT_FINALITY_PROOFS_LIMIT: usize = 4096;
|
||||
|
||||
/// Headers sync pipeline for Substrate <-> Substrate relays.
|
||||
#[async_trait]
|
||||
pub trait SubstrateFinalitySyncPipeline: FinalitySyncPipeline {
|
||||
/// Name of the runtime method that returns id of best finalized source header at target chain.
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str;
|
||||
|
||||
/// Signed transaction type.
|
||||
type SignedTransaction: Send + Sync + Encode;
|
||||
|
||||
/// Make submit header transaction.
|
||||
async fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
header: Self::Header,
|
||||
proof: Self::FinalityProof,
|
||||
) -> Result<Self::SignedTransaction, SubstrateError>;
|
||||
}
|
||||
|
||||
/// Substrate-to-Substrate finality proof pipeline.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SubstrateFinalityToSubstrate<SourceChain, TargetChain: Chain, TargetSign> {
|
||||
/// Client for the target chain.
|
||||
pub(crate) target_client: Client<TargetChain>,
|
||||
/// Data required to sign target chain transactions.
|
||||
pub(crate) target_sign: TargetSign,
|
||||
/// Unused generic arguments dump.
|
||||
_marker: PhantomData<SourceChain>,
|
||||
}
|
||||
|
||||
impl<SourceChain, TargetChain: Chain, TargetSign> SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign> {
|
||||
/// Create new Substrate-to-Substrate headers pipeline.
|
||||
pub fn new(target_client: Client<TargetChain>, target_sign: TargetSign) -> Self {
|
||||
SubstrateFinalityToSubstrate {
|
||||
target_client,
|
||||
target_sign,
|
||||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<SourceChain, TargetChain, TargetSign> FinalitySyncPipeline
|
||||
for SubstrateFinalityToSubstrate<SourceChain, TargetChain, TargetSign>
|
||||
where
|
||||
SourceChain: Clone + Chain + Debug,
|
||||
BlockNumberOf<SourceChain>: BlockNumberBase,
|
||||
TargetChain: Clone + Chain + Debug,
|
||||
TargetSign: Clone + Send + Sync + Debug,
|
||||
{
|
||||
const SOURCE_NAME: &'static str = SourceChain::NAME;
|
||||
const TARGET_NAME: &'static str = TargetChain::NAME;
|
||||
|
||||
type Hash = HashOf<SourceChain>;
|
||||
type Number = BlockNumberOf<SourceChain>;
|
||||
type Header = SyncHeader<SourceChain::Header>;
|
||||
type FinalityProof = Justification<SourceChain::Header>;
|
||||
}
|
||||
|
||||
/// Run Substrate-to-Substrate finality sync.
|
||||
pub async fn run<SourceChain, TargetChain, P>(
|
||||
pipeline: P,
|
||||
source_client: Client<SourceChain>,
|
||||
target_client: Client<TargetChain>,
|
||||
metrics_params: Option<relay_utils::metrics::MetricsParams>,
|
||||
) where
|
||||
P: SubstrateFinalitySyncPipeline<
|
||||
Hash = HashOf<SourceChain>,
|
||||
Number = BlockNumberOf<SourceChain>,
|
||||
Header = SyncHeader<SourceChain::Header>,
|
||||
FinalityProof = Justification<SourceChain::Header>,
|
||||
>,
|
||||
SourceChain: Clone + Chain,
|
||||
BlockNumberOf<SourceChain>: BlockNumberBase,
|
||||
TargetChain: Clone + Chain,
|
||||
{
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Starting {} -> {} finality proof relay",
|
||||
SourceChain::NAME,
|
||||
TargetChain::NAME,
|
||||
);
|
||||
|
||||
finality_relay::run(
|
||||
FinalitySource::new(source_client),
|
||||
SubstrateFinalityTarget::new(target_client, pipeline),
|
||||
FinalitySyncParams {
|
||||
tick: std::cmp::max(SourceChain::AVERAGE_BLOCK_INTERVAL, TargetChain::AVERAGE_BLOCK_INTERVAL),
|
||||
recent_finality_proofs_limit: RECENT_FINALITY_PROOFS_LIMIT,
|
||||
stall_timeout: STALL_TIMEOUT,
|
||||
},
|
||||
metrics_params,
|
||||
futures::future::pending(),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright 2019-2020 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/>.
|
||||
|
||||
//! Substrate client as Substrate finality proof target. The chain we connect to should have
|
||||
//! runtime that implements `<BridgedChainName>FinalityApi` to allow bridging with
|
||||
//! <BridgedName> chain.
|
||||
|
||||
use crate::finality_pipeline::SubstrateFinalitySyncPipeline;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codec::{Decode, Encode};
|
||||
use finality_relay::TargetClient;
|
||||
use futures::TryFutureExt;
|
||||
use relay_substrate_client::{Chain, Client, Error as SubstrateError};
|
||||
use relay_utils::relay_loop::Client as RelayClient;
|
||||
use sp_core::Bytes;
|
||||
|
||||
/// Substrate client as Substrate finality target.
|
||||
pub struct SubstrateFinalityTarget<C: Chain, P> {
|
||||
client: Client<C>,
|
||||
pipeline: P,
|
||||
}
|
||||
|
||||
impl<C: Chain, P> SubstrateFinalityTarget<C, P> {
|
||||
/// Create new Substrate headers target.
|
||||
pub fn new(client: Client<C>, pipeline: P) -> Self {
|
||||
SubstrateFinalityTarget { client, pipeline }
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Chain, P: SubstrateFinalitySyncPipeline> Clone for SubstrateFinalityTarget<C, P> {
|
||||
fn clone(&self) -> Self {
|
||||
SubstrateFinalityTarget {
|
||||
client: self.client.clone(),
|
||||
pipeline: self.pipeline.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C: Chain, P: SubstrateFinalitySyncPipeline> RelayClient for SubstrateFinalityTarget<C, P> {
|
||||
type Error = SubstrateError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
|
||||
self.client.reconnect().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, P> TargetClient<P> for SubstrateFinalityTarget<C, P>
|
||||
where
|
||||
C: Chain,
|
||||
P::Number: Decode,
|
||||
P::Hash: Decode,
|
||||
P: SubstrateFinalitySyncPipeline,
|
||||
{
|
||||
async fn best_finalized_source_block_number(&self) -> Result<P::Number, SubstrateError> {
|
||||
// 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?;
|
||||
|
||||
Ok(crate::messages_source::read_client_state::<C, P::Hash, P::Number>(
|
||||
&self.client,
|
||||
P::BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET,
|
||||
)
|
||||
.await?
|
||||
.best_finalized_peer_at_best_self
|
||||
.0)
|
||||
}
|
||||
|
||||
async fn submit_finality_proof(&self, header: P::Header, proof: P::FinalityProof) -> Result<(), SubstrateError> {
|
||||
self.pipeline
|
||||
.make_submit_finality_proof_transaction(header, proof)
|
||||
.and_then(|tx| self.client.submit_extrinsic(Bytes(tx.encode())))
|
||||
.await
|
||||
.map(drop)
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,12 @@
|
||||
//! Initialize Substrate -> Substrate headers bridge.
|
||||
//!
|
||||
//! Initialization is a transaction that calls `initialize()` function of the
|
||||
//! `pallet-substrate-bridge` pallet. This transaction brings initial header
|
||||
//! `pallet-finality-verifier` pallet. This transaction brings initial header
|
||||
//! and authorities set from source to target chain. The headers sync starts
|
||||
//! with this header.
|
||||
|
||||
use codec::Decode;
|
||||
use pallet_substrate_bridge::InitializationData;
|
||||
use pallet_finality_verifier::InitializationData;
|
||||
use relay_substrate_client::{Chain, Client};
|
||||
use sp_core::Bytes;
|
||||
use sp_finality_grandpa::{AuthorityList as GrandpaAuthoritiesSet, SetId as GrandpaAuthoritiesSetId};
|
||||
@@ -132,10 +132,6 @@ async fn prepare_initialization_data<SourceChain: Chain>(
|
||||
header: initial_header,
|
||||
authority_list: initial_authorities_set,
|
||||
set_id: initial_authorities_set_id.unwrap_or(0),
|
||||
// There may be multiple scheduled changes, so on real chains we should select proper
|
||||
// moment, when there's nothing scheduled. On ephemeral (temporary) chains, it is ok to
|
||||
// start with genesis.
|
||||
scheduled_change: None,
|
||||
is_halted: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,458 +0,0 @@
|
||||
// Copyright 2019-2020 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/>.
|
||||
|
||||
//! Substrate-to-Substrate headers synchronization maintain procedure.
|
||||
//!
|
||||
//! Regular headers synchronization only depends on persistent justifications
|
||||
//! that are generated when authorities set changes. This happens rarely on
|
||||
//! real-word chains. So some other way to finalize headers is required.
|
||||
//!
|
||||
//! Full nodes are listening to GRANDPA messages, so they may have track authorities
|
||||
//! votes on their own. They're returning both persistent and ephemeral justifications
|
||||
//! (justifications that are not stored in the database and not broadcasted over network)
|
||||
//! throught `grandpa_subscribeJustifications` RPC subscription.
|
||||
//!
|
||||
//! The idea of this maintain procedure is that when we see justification that 'improves'
|
||||
//! best finalized header on the target chain, we submit this justification to the target
|
||||
//! node.
|
||||
|
||||
use crate::headers_pipeline::SubstrateHeadersSyncPipeline;
|
||||
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use async_trait::async_trait;
|
||||
use codec::{Decode, Encode};
|
||||
use futures::future::{poll_fn, FutureExt, TryFutureExt};
|
||||
use headers_relay::{
|
||||
sync::HeadersSync,
|
||||
sync_loop::SyncMaintain,
|
||||
sync_types::{HeaderIdOf, HeaderStatus},
|
||||
};
|
||||
use relay_substrate_client::{Chain, Client, Error as SubstrateError, JustificationsSubscription};
|
||||
use relay_utils::HeaderId;
|
||||
use sp_core::Bytes;
|
||||
use sp_runtime::{traits::Header as HeaderT, Justification};
|
||||
use std::{collections::VecDeque, marker::PhantomData, task::Poll};
|
||||
|
||||
/// Substrate-to-Substrate headers synchronization maintain procedure.
|
||||
pub struct SubstrateHeadersToSubstrateMaintain<P: SubstrateHeadersSyncPipeline, SourceChain: Chain, TargetChain: Chain>
|
||||
{
|
||||
pipeline: P,
|
||||
source_client: Client<SourceChain>,
|
||||
target_client: Client<TargetChain>,
|
||||
justifications: Arc<Mutex<Justifications<P>>>,
|
||||
_marker: PhantomData<SourceChain>,
|
||||
}
|
||||
|
||||
/// Future and already received justifications from the source chain.
|
||||
struct Justifications<P: SubstrateHeadersSyncPipeline> {
|
||||
/// Justifications stream. None if it hasn't been initialized yet, or it has been dropped
|
||||
/// by the rpc library.
|
||||
stream: Option<JustificationsSubscription>,
|
||||
/// Justifications that we have read from the stream but have not sent to the
|
||||
/// target node, because their targets were still not synced.
|
||||
queue: VecDeque<(HeaderIdOf<P>, Justification)>,
|
||||
}
|
||||
|
||||
impl<P: SubstrateHeadersSyncPipeline, SourceChain: Chain, TargetChain: Chain>
|
||||
SubstrateHeadersToSubstrateMaintain<P, SourceChain, TargetChain>
|
||||
{
|
||||
/// Create new maintain procedure.
|
||||
pub async fn new(pipeline: P, source_client: Client<SourceChain>, target_client: Client<TargetChain>) -> Self {
|
||||
let justifications = subscribe_justifications(&source_client).await;
|
||||
SubstrateHeadersToSubstrateMaintain {
|
||||
pipeline,
|
||||
source_client,
|
||||
target_client,
|
||||
justifications: Arc::new(Mutex::new(Justifications {
|
||||
stream: justifications,
|
||||
queue: VecDeque::new(),
|
||||
})),
|
||||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P: SubstrateHeadersSyncPipeline, SourceChain: Chain, TargetChain: Chain> Clone
|
||||
for SubstrateHeadersToSubstrateMaintain<P, SourceChain, TargetChain>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
SubstrateHeadersToSubstrateMaintain {
|
||||
pipeline: self.pipeline.clone(),
|
||||
source_client: self.source_client.clone(),
|
||||
target_client: self.target_client.clone(),
|
||||
justifications: self.justifications.clone(),
|
||||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P, SourceChain, TargetChain> SyncMaintain<P> for SubstrateHeadersToSubstrateMaintain<P, SourceChain, TargetChain>
|
||||
where
|
||||
SourceChain: Chain,
|
||||
<SourceChain::Header as HeaderT>::Number: Into<P::Number>,
|
||||
<SourceChain::Header as HeaderT>::Hash: Into<P::Hash>,
|
||||
TargetChain: Chain,
|
||||
P::Number: Decode,
|
||||
P::Hash: Decode,
|
||||
P: SubstrateHeadersSyncPipeline<Completion = Justification, Extra = ()>,
|
||||
{
|
||||
async fn maintain(&self, sync: &mut HeadersSync<P>) {
|
||||
// lock justifications before doing anything else
|
||||
let mut justifications = match self.justifications.try_lock() {
|
||||
Some(justifications) => justifications,
|
||||
None => {
|
||||
// this should never happen, as we use single-thread executor
|
||||
log::warn!(target: "bridge", "Failed to acquire {} justifications lock", P::SOURCE_NAME);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// we need to read best finalized header from the target node to be able to
|
||||
// choose justification to submit
|
||||
let best_finalized = match best_finalized_header_id::<P, _>(&self.target_client).await {
|
||||
Ok(best_finalized) => best_finalized,
|
||||
Err(error) => {
|
||||
log::warn!(
|
||||
target: "bridge",
|
||||
"Failed to read best finalized {} block from maintain: {:?}",
|
||||
P::SOURCE_NAME,
|
||||
error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
log::debug!(
|
||||
target: "bridge",
|
||||
"Read best finalized {} block from {}: {:?}",
|
||||
P::SOURCE_NAME,
|
||||
P::TARGET_NAME,
|
||||
best_finalized,
|
||||
);
|
||||
|
||||
// Select justification to submit to the target node. We're submitting at most one justification
|
||||
// on every maintain call. So maintain rate directly affects finalization rate.
|
||||
let (resubscribe, justification_to_submit) = poll_fn(|context| {
|
||||
// read justifications from the stream and push to the queue
|
||||
let resubscribe = !justifications.read_from_stream::<SourceChain::Header>(context);
|
||||
|
||||
// remove all obsolete justifications from the queue
|
||||
remove_obsolete::<P>(&mut justifications.queue, best_finalized);
|
||||
|
||||
// select justification to submit
|
||||
Poll::Ready((resubscribe, select_justification(&mut justifications.queue, sync)))
|
||||
})
|
||||
.await;
|
||||
|
||||
// if justifications subscription has been dropped, resubscribe
|
||||
if resubscribe {
|
||||
justifications.stream = subscribe_justifications(&self.source_client).await;
|
||||
}
|
||||
|
||||
// finally - submit selected justification
|
||||
if let Some((target, justification)) = justification_to_submit {
|
||||
let submit_result = self
|
||||
.pipeline
|
||||
.make_complete_header_transaction(target, justification)
|
||||
.and_then(|tx| self.target_client.submit_extrinsic(Bytes(tx.encode())))
|
||||
.await;
|
||||
|
||||
match submit_result {
|
||||
Ok(_) => log::debug!(
|
||||
target: "bridge",
|
||||
"Submitted justification received over {} subscription. Target: {:?}",
|
||||
P::SOURCE_NAME,
|
||||
target,
|
||||
),
|
||||
Err(error) => log::warn!(
|
||||
target: "bridge",
|
||||
"Failed to submit justification received over {} subscription for {:?}: {:?}",
|
||||
P::SOURCE_NAME,
|
||||
target,
|
||||
error,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Justifications<P>
|
||||
where
|
||||
P::Number: Decode,
|
||||
P::Hash: Decode,
|
||||
P: SubstrateHeadersSyncPipeline<Completion = Justification, Extra = ()>,
|
||||
{
|
||||
/// Read justifications from the subscription stream without blocking.
|
||||
///
|
||||
/// Returns `true` if justifications stream is still readable and `false` if it has been
|
||||
/// dropped by the RPC crate && we need to resubscribe.
|
||||
#[must_use]
|
||||
fn read_from_stream<'a, SourceHeader>(&mut self, context: &mut std::task::Context<'a>) -> bool
|
||||
where
|
||||
SourceHeader: HeaderT,
|
||||
SourceHeader::Number: Into<P::Number>,
|
||||
SourceHeader::Hash: Into<P::Hash>,
|
||||
{
|
||||
let stream = match self.stream.as_mut() {
|
||||
Some(stream) => stream,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
loop {
|
||||
let maybe_next_justification = stream.next();
|
||||
futures::pin_mut!(maybe_next_justification);
|
||||
|
||||
let maybe_next_justification = maybe_next_justification.poll_unpin(context);
|
||||
let justification = match maybe_next_justification {
|
||||
Poll::Ready(justification) => justification,
|
||||
Poll::Pending => return true,
|
||||
};
|
||||
|
||||
let justification = match justification {
|
||||
Some(justification) => justification,
|
||||
None => {
|
||||
log::warn!(
|
||||
target: "bridge",
|
||||
"{} justifications stream has been dropped. Will be trying to resubscribe",
|
||||
P::SOURCE_NAME,
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// decode justification target
|
||||
let target = bp_header_chain::justification::decode_justification_target::<SourceHeader>(&justification);
|
||||
let target = match target {
|
||||
Ok((target_hash, target_number)) => HeaderId(target_number.into(), target_hash.into()),
|
||||
Err(error) => {
|
||||
log::warn!(
|
||||
target: "bridge",
|
||||
"Failed to decode justification from {} subscription: {:?}",
|
||||
P::SOURCE_NAME,
|
||||
error,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
log::debug!(
|
||||
target: "bridge",
|
||||
"Received {} justification over subscription. Target: {:?}",
|
||||
P::SOURCE_NAME,
|
||||
target,
|
||||
);
|
||||
|
||||
self.queue.push_back((target, justification.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clean queue of all justifications that are justifying already finalized blocks.
|
||||
fn remove_obsolete<P: SubstrateHeadersSyncPipeline>(
|
||||
queue: &mut VecDeque<(HeaderIdOf<P>, Justification)>,
|
||||
best_finalized: HeaderIdOf<P>,
|
||||
) {
|
||||
while queue
|
||||
.front()
|
||||
.map(|(target, _)| target.0 <= best_finalized.0)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
queue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
/// Select appropriate justification that would improve best finalized block on target node.
|
||||
///
|
||||
/// It is assumed that the selected justification will be submitted to the target node. The
|
||||
/// justification itself and all preceeding justifications are removed from the queue.
|
||||
fn select_justification<P>(
|
||||
queue: &mut VecDeque<(HeaderIdOf<P>, Justification)>,
|
||||
sync: &mut HeadersSync<P>,
|
||||
) -> Option<(HeaderIdOf<P>, Justification)>
|
||||
where
|
||||
P: SubstrateHeadersSyncPipeline<Completion = Justification>,
|
||||
{
|
||||
let mut selected_justification = None;
|
||||
while let Some((target, justification)) = queue.pop_front() {
|
||||
// if we're waiting for this justification, report it
|
||||
if sync.headers().requires_completion_data(&target) {
|
||||
sync.headers_mut().completion_response(&target, Some(justification));
|
||||
// we won't submit previous justifications as we going to submit justification for
|
||||
// next header
|
||||
selected_justification = None;
|
||||
// we won't submit next justifications as we need to submit previous justifications
|
||||
// first
|
||||
break;
|
||||
}
|
||||
|
||||
// if we know that the header is already synced (it is known to the target node), let's
|
||||
// select it for submission. We still may select better justification on the next iteration.
|
||||
if sync.headers().status(&target) == HeaderStatus::Synced {
|
||||
selected_justification = Some((target, justification));
|
||||
continue;
|
||||
}
|
||||
|
||||
// finally - return justification back to the queue
|
||||
queue.push_back((target, justification));
|
||||
break;
|
||||
}
|
||||
|
||||
selected_justification
|
||||
}
|
||||
|
||||
/// Returns best finalized source header on the target chain.
|
||||
async fn best_finalized_header_id<P, C>(client: &Client<C>) -> Result<HeaderIdOf<P>, SubstrateError>
|
||||
where
|
||||
P: SubstrateHeadersSyncPipeline,
|
||||
P::Number: Decode,
|
||||
P::Hash: Decode,
|
||||
C: Chain,
|
||||
{
|
||||
let call = P::FINALIZED_BLOCK_METHOD.into();
|
||||
let data = Bytes(Vec::new());
|
||||
|
||||
let encoded_response = client.state_call(call, data, None).await?;
|
||||
let decoded_response: (P::Number, P::Hash) =
|
||||
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
|
||||
|
||||
let best_header_id = HeaderId(decoded_response.0, decoded_response.1);
|
||||
Ok(best_header_id)
|
||||
}
|
||||
|
||||
/// Subscribe to justifications stream at source node.
|
||||
async fn subscribe_justifications<C: Chain>(client: &Client<C>) -> Option<JustificationsSubscription> {
|
||||
match client.subscribe_justifications().await {
|
||||
Ok(source_justifications) => {
|
||||
log::debug!(
|
||||
target: "bridge",
|
||||
"Successfully (re)subscribed to {} justifications",
|
||||
C::NAME,
|
||||
);
|
||||
|
||||
Some(source_justifications)
|
||||
}
|
||||
Err(error) => {
|
||||
log::warn!(
|
||||
target: "bridge",
|
||||
"Failed to subscribe to {} justifications: {:?}",
|
||||
C::NAME,
|
||||
error,
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::headers_pipeline::sync_params;
|
||||
use crate::millau_headers_to_rialto::MillauHeadersToRialto;
|
||||
|
||||
fn parent_hash(index: u8) -> bp_millau::Hash {
|
||||
if index == 1 {
|
||||
Default::default()
|
||||
} else {
|
||||
header(index - 1).hash()
|
||||
}
|
||||
}
|
||||
|
||||
fn header_hash(index: u8) -> bp_millau::Hash {
|
||||
header(index).hash()
|
||||
}
|
||||
|
||||
fn header(index: u8) -> bp_millau::Header {
|
||||
bp_millau::Header::new(
|
||||
index as _,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
parent_hash(index),
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn obsolete_justifications_are_removed() {
|
||||
let mut queue = vec![
|
||||
(HeaderId(1, header_hash(1)), vec![1]),
|
||||
(HeaderId(2, header_hash(2)), vec![2]),
|
||||
(HeaderId(3, header_hash(3)), vec![3]),
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
remove_obsolete::<MillauHeadersToRialto>(&mut queue, HeaderId(2, header_hash(2)));
|
||||
|
||||
assert_eq!(
|
||||
queue,
|
||||
vec![(HeaderId(3, header_hash(3)), vec![3])]
|
||||
.into_iter()
|
||||
.collect::<VecDeque<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn latest_justification_is_selected() {
|
||||
let mut queue = vec![
|
||||
(HeaderId(1, header_hash(1)), vec![1]),
|
||||
(HeaderId(2, header_hash(2)), vec![2]),
|
||||
(HeaderId(3, header_hash(3)), vec![3]),
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let mut sync = HeadersSync::<MillauHeadersToRialto>::new(sync_params());
|
||||
sync.headers_mut().header_response(header(1).into());
|
||||
sync.headers_mut().header_response(header(2).into());
|
||||
sync.headers_mut().header_response(header(3).into());
|
||||
sync.target_best_header_response(HeaderId(2, header_hash(2)));
|
||||
|
||||
assert_eq!(
|
||||
select_justification(&mut queue, &mut sync),
|
||||
Some((HeaderId(2, header_hash(2)), vec![2])),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_justification_is_reported() {
|
||||
let mut queue = vec![
|
||||
(HeaderId(1, header_hash(1)), vec![1]),
|
||||
(HeaderId(2, header_hash(2)), vec![2]),
|
||||
(HeaderId(3, header_hash(3)), vec![3]),
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let mut sync = HeadersSync::<MillauHeadersToRialto>::new(sync_params());
|
||||
sync.headers_mut().header_response(header(1).into());
|
||||
sync.headers_mut().header_response(header(2).into());
|
||||
sync.headers_mut().header_response(header(3).into());
|
||||
sync.headers_mut()
|
||||
.incomplete_headers_response(vec![HeaderId(2, header_hash(2))].into_iter().collect());
|
||||
sync.target_best_header_response(HeaderId(2, header_hash(2)));
|
||||
|
||||
assert_eq!(sync.headers_mut().header_to_complete(), None,);
|
||||
|
||||
assert_eq!(select_justification(&mut queue, &mut sync), None,);
|
||||
|
||||
assert_eq!(
|
||||
sync.headers_mut().header_to_complete(),
|
||||
Some((HeaderId(2, header_hash(2)), &vec![2])),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
// Copyright 2019-2020 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/>.
|
||||
|
||||
//! Substrate-to-Substrate headers sync entrypoint.
|
||||
|
||||
use crate::{headers_maintain::SubstrateHeadersToSubstrateMaintain, headers_target::SubstrateHeadersTarget};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codec::Encode;
|
||||
use headers_relay::{
|
||||
sync::{HeadersSyncParams, TargetTransactionMode},
|
||||
sync_types::{HeaderIdOf, HeadersSyncPipeline, QueuedHeader, SourceHeader},
|
||||
};
|
||||
use relay_substrate_client::{
|
||||
headers_source::HeadersSource, BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf,
|
||||
};
|
||||
use relay_utils::BlockNumberBase;
|
||||
use sp_runtime::Justification;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Headers sync pipeline for Substrate <-> Substrate relays.
|
||||
#[async_trait]
|
||||
pub trait SubstrateHeadersSyncPipeline: HeadersSyncPipeline {
|
||||
/// Name of the `best_block` runtime method.
|
||||
const BEST_BLOCK_METHOD: &'static str;
|
||||
/// Name of the `finalized_block` runtime method.
|
||||
const FINALIZED_BLOCK_METHOD: &'static str;
|
||||
/// Name of the `is_known_block` runtime method.
|
||||
const IS_KNOWN_BLOCK_METHOD: &'static str;
|
||||
/// Name of the `incomplete_headers` runtime method.
|
||||
const INCOMPLETE_HEADERS_METHOD: &'static str;
|
||||
|
||||
/// Signed transaction type.
|
||||
type SignedTransaction: Send + Sync + Encode;
|
||||
|
||||
/// Make submit header transaction.
|
||||
async fn make_submit_header_transaction(
|
||||
&self,
|
||||
header: QueuedHeader<Self>,
|
||||
) -> Result<Self::SignedTransaction, SubstrateError>;
|
||||
|
||||
/// Make completion transaction for the header.
|
||||
async fn make_complete_header_transaction(
|
||||
&self,
|
||||
id: HeaderIdOf<Self>,
|
||||
completion: Justification,
|
||||
) -> Result<Self::SignedTransaction, SubstrateError>;
|
||||
}
|
||||
|
||||
/// Substrate-to-Substrate headers pipeline.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SubstrateHeadersToSubstrate<SourceChain, SourceSyncHeader, TargetChain: Chain, TargetSign> {
|
||||
/// Client for the target chain.
|
||||
pub(crate) target_client: Client<TargetChain>,
|
||||
/// Data required to sign target chain transactions.
|
||||
pub(crate) target_sign: TargetSign,
|
||||
/// Unused generic arguments dump.
|
||||
_marker: PhantomData<(SourceChain, SourceSyncHeader)>,
|
||||
}
|
||||
|
||||
impl<SourceChain, SourceSyncHeader, TargetChain: Chain, TargetSign>
|
||||
SubstrateHeadersToSubstrate<SourceChain, SourceSyncHeader, TargetChain, TargetSign>
|
||||
{
|
||||
/// Create new Substrate-to-Substrate headers pipeline.
|
||||
pub fn new(target_client: Client<TargetChain>, target_sign: TargetSign) -> Self {
|
||||
SubstrateHeadersToSubstrate {
|
||||
target_client,
|
||||
target_sign,
|
||||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<SourceChain, SourceSyncHeader, TargetChain, TargetSign> HeadersSyncPipeline
|
||||
for SubstrateHeadersToSubstrate<SourceChain, SourceSyncHeader, TargetChain, TargetSign>
|
||||
where
|
||||
SourceChain: Clone + Chain,
|
||||
BlockNumberOf<SourceChain>: BlockNumberBase,
|
||||
SourceSyncHeader:
|
||||
SourceHeader<HashOf<SourceChain>, BlockNumberOf<SourceChain>> + std::ops::Deref<Target = SourceChain::Header>,
|
||||
TargetChain: Clone + Chain,
|
||||
TargetSign: Clone + Send + Sync,
|
||||
{
|
||||
const SOURCE_NAME: &'static str = SourceChain::NAME;
|
||||
const TARGET_NAME: &'static str = TargetChain::NAME;
|
||||
|
||||
type Hash = HashOf<SourceChain>;
|
||||
type Number = BlockNumberOf<SourceChain>;
|
||||
type Header = SourceSyncHeader;
|
||||
type Extra = ();
|
||||
type Completion = Justification;
|
||||
|
||||
fn estimate_size(source: &QueuedHeader<Self>) -> usize {
|
||||
source.header().encode().len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Return sync parameters for Substrate-to-Substrate headers sync.
|
||||
pub fn sync_params() -> HeadersSyncParams {
|
||||
HeadersSyncParams {
|
||||
max_future_headers_to_download: 32,
|
||||
max_headers_in_submitted_status: 8,
|
||||
max_headers_in_single_submit: 1,
|
||||
max_headers_size_in_single_submit: 1024 * 1024,
|
||||
prune_depth: 256,
|
||||
target_tx_mode: TargetTransactionMode::Signed,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run Substrate-to-Substrate headers sync.
|
||||
pub async fn run<SourceChain, TargetChain, P>(
|
||||
pipeline: P,
|
||||
source_client: Client<SourceChain>,
|
||||
target_client: Client<TargetChain>,
|
||||
metrics_params: Option<relay_utils::metrics::MetricsParams>,
|
||||
) where
|
||||
P: SubstrateHeadersSyncPipeline<
|
||||
Hash = HashOf<SourceChain>,
|
||||
Number = BlockNumberOf<SourceChain>,
|
||||
Completion = Justification,
|
||||
Extra = (),
|
||||
>,
|
||||
P::Header: SourceHeader<HashOf<SourceChain>, BlockNumberOf<SourceChain>>,
|
||||
SourceChain: Clone + Chain,
|
||||
SourceChain::Header: Into<P::Header>,
|
||||
BlockNumberOf<SourceChain>: BlockNumberBase,
|
||||
TargetChain: Clone + Chain,
|
||||
{
|
||||
let sync_maintain = SubstrateHeadersToSubstrateMaintain::<_, SourceChain, _>::new(
|
||||
pipeline.clone(),
|
||||
source_client.clone(),
|
||||
target_client.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
log::info!(
|
||||
target: "bridge",
|
||||
"Starting {} -> {} headers relay",
|
||||
SourceChain::NAME,
|
||||
TargetChain::NAME,
|
||||
);
|
||||
|
||||
headers_relay::sync_loop::run(
|
||||
HeadersSource::new(source_client),
|
||||
SourceChain::AVERAGE_BLOCK_INTERVAL,
|
||||
SubstrateHeadersTarget::new(target_client, pipeline),
|
||||
TargetChain::AVERAGE_BLOCK_INTERVAL,
|
||||
sync_maintain,
|
||||
sync_params(),
|
||||
metrics_params,
|
||||
futures::future::pending(),
|
||||
);
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
// Copyright 2019-2020 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/>.
|
||||
|
||||
//! Substrate client as Substrate headers target. The chain we connect to should have
|
||||
//! runtime that implements `<BridgedChainName>HeaderApi` to allow bridging with
|
||||
//! <BridgedName> chain.
|
||||
|
||||
use crate::headers_pipeline::SubstrateHeadersSyncPipeline;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codec::{Decode, Encode};
|
||||
use futures::TryFutureExt;
|
||||
use headers_relay::{
|
||||
sync_loop::TargetClient,
|
||||
sync_types::{HeaderIdOf, QueuedHeader, SubmittedHeaders},
|
||||
};
|
||||
use relay_substrate_client::{Chain, Client, Error as SubstrateError};
|
||||
use relay_utils::{relay_loop::Client as RelayClient, HeaderId};
|
||||
use sp_core::Bytes;
|
||||
use sp_runtime::Justification;
|
||||
use std::collections::HashSet;
|
||||
|
||||
/// Substrate client as Substrate headers target.
|
||||
pub struct SubstrateHeadersTarget<C: Chain, P> {
|
||||
client: Client<C>,
|
||||
pipeline: P,
|
||||
}
|
||||
|
||||
impl<C: Chain, P> SubstrateHeadersTarget<C, P> {
|
||||
/// Create new Substrate headers target.
|
||||
pub fn new(client: Client<C>, pipeline: P) -> Self {
|
||||
SubstrateHeadersTarget { client, pipeline }
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Chain, P: SubstrateHeadersSyncPipeline> Clone for SubstrateHeadersTarget<C, P> {
|
||||
fn clone(&self) -> Self {
|
||||
SubstrateHeadersTarget {
|
||||
client: self.client.clone(),
|
||||
pipeline: self.pipeline.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C: Chain, P: SubstrateHeadersSyncPipeline> RelayClient for SubstrateHeadersTarget<C, P> {
|
||||
type Error = SubstrateError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
|
||||
self.client.reconnect().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, P> TargetClient<P> for SubstrateHeadersTarget<C, P>
|
||||
where
|
||||
C: Chain,
|
||||
P::Number: Decode,
|
||||
P::Hash: Decode + Encode,
|
||||
P: SubstrateHeadersSyncPipeline<Completion = Justification, Extra = ()>,
|
||||
{
|
||||
async fn best_header_id(&self) -> Result<HeaderIdOf<P>, SubstrateError> {
|
||||
// we can't continue to relay headers 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?;
|
||||
|
||||
let call = P::BEST_BLOCK_METHOD.into();
|
||||
let data = Bytes(Vec::new());
|
||||
|
||||
let encoded_response = self.client.state_call(call, data, None).await?;
|
||||
let decoded_response: Vec<(P::Number, P::Hash)> =
|
||||
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
|
||||
|
||||
// If we parse an empty list of headers it means that bridge pallet has not been initalized
|
||||
// yet. Otherwise we expect to always have at least one header.
|
||||
decoded_response
|
||||
.last()
|
||||
.ok_or(SubstrateError::UninitializedBridgePallet)
|
||||
.map(|(num, hash)| HeaderId(*num, *hash))
|
||||
}
|
||||
|
||||
async fn is_known_header(&self, id: HeaderIdOf<P>) -> Result<(HeaderIdOf<P>, bool), SubstrateError> {
|
||||
let call = P::IS_KNOWN_BLOCK_METHOD.into();
|
||||
let data = Bytes(id.1.encode());
|
||||
|
||||
let encoded_response = self.client.state_call(call, data, None).await?;
|
||||
let is_known_block: bool =
|
||||
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
|
||||
|
||||
Ok((id, is_known_block))
|
||||
}
|
||||
|
||||
async fn submit_headers(
|
||||
&self,
|
||||
mut headers: Vec<QueuedHeader<P>>,
|
||||
) -> SubmittedHeaders<HeaderIdOf<P>, SubstrateError> {
|
||||
debug_assert_eq!(
|
||||
headers.len(),
|
||||
1,
|
||||
"Substrate pallet only supports single header / transaction"
|
||||
);
|
||||
|
||||
let header = headers.remove(0);
|
||||
let id = header.id();
|
||||
let submit_transaction_result = self
|
||||
.pipeline
|
||||
.make_submit_header_transaction(header)
|
||||
.and_then(|tx| self.client.submit_extrinsic(Bytes(tx.encode())))
|
||||
.await;
|
||||
|
||||
match submit_transaction_result {
|
||||
Ok(_) => SubmittedHeaders {
|
||||
submitted: vec![id],
|
||||
incomplete: Vec::new(),
|
||||
rejected: Vec::new(),
|
||||
fatal_error: None,
|
||||
},
|
||||
Err(error) => SubmittedHeaders {
|
||||
submitted: Vec::new(),
|
||||
incomplete: Vec::new(),
|
||||
rejected: vec![id],
|
||||
fatal_error: Some(error),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn incomplete_headers_ids(&self) -> Result<HashSet<HeaderIdOf<P>>, SubstrateError> {
|
||||
let call = P::INCOMPLETE_HEADERS_METHOD.into();
|
||||
let data = Bytes(Vec::new());
|
||||
|
||||
let encoded_response = self.client.state_call(call, data, None).await?;
|
||||
let decoded_response: Vec<(P::Number, P::Hash)> =
|
||||
Decode::decode(&mut &encoded_response.0[..]).map_err(SubstrateError::ResponseParseFailed)?;
|
||||
|
||||
let incomplete_headers = decoded_response
|
||||
.into_iter()
|
||||
.map(|(number, hash)| HeaderId(number, hash))
|
||||
.collect();
|
||||
Ok(incomplete_headers)
|
||||
}
|
||||
|
||||
async fn complete_header(
|
||||
&self,
|
||||
id: HeaderIdOf<P>,
|
||||
completion: Justification,
|
||||
) -> Result<HeaderIdOf<P>, SubstrateError> {
|
||||
let tx = self.pipeline.make_complete_header_transaction(id, completion).await?;
|
||||
self.client.submit_extrinsic(Bytes(tx.encode())).await?;
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
async fn requires_extra(&self, header: QueuedHeader<P>) -> Result<(HeaderIdOf<P>, bool), SubstrateError> {
|
||||
Ok((header.id(), false))
|
||||
}
|
||||
}
|
||||
@@ -38,10 +38,9 @@ pub type MillauClient = relay_substrate_client::Client<Millau>;
|
||||
pub type RialtoClient = relay_substrate_client::Client<Rialto>;
|
||||
|
||||
mod cli;
|
||||
mod finality_pipeline;
|
||||
mod finality_target;
|
||||
mod headers_initialize;
|
||||
mod headers_maintain;
|
||||
mod headers_pipeline;
|
||||
mod headers_target;
|
||||
mod messages_lane;
|
||||
mod messages_source;
|
||||
mod messages_target;
|
||||
@@ -101,7 +100,7 @@ async fn run_init_bridge(command: cli::InitBridge) -> Result<(), String> {
|
||||
&rialto_sign.signer,
|
||||
rialto_signer_next_index,
|
||||
rialto_runtime::SudoCall::sudo(Box::new(
|
||||
rialto_runtime::BridgeMillauCall::initialize(initialization_data).into(),
|
||||
rialto_runtime::FinalityBridgeMillauCall::initialize(initialization_data).into(),
|
||||
))
|
||||
.into(),
|
||||
)
|
||||
@@ -137,7 +136,7 @@ async fn run_init_bridge(command: cli::InitBridge) -> Result<(), String> {
|
||||
&millau_sign.signer,
|
||||
millau_signer_next_index,
|
||||
millau_runtime::SudoCall::sudo(Box::new(
|
||||
millau_runtime::BridgeRialtoCall::initialize(initialization_data).into(),
|
||||
millau_runtime::FinalityBridgeRialtoCall::initialize(initialization_data).into(),
|
||||
))
|
||||
.into(),
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// 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
|
||||
|
||||
@@ -17,70 +17,52 @@
|
||||
//! Millau-to-Rialto headers sync entrypoint.
|
||||
|
||||
use crate::{
|
||||
headers_pipeline::{SubstrateHeadersSyncPipeline, SubstrateHeadersToSubstrate},
|
||||
finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate},
|
||||
MillauClient, RialtoClient,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_millau::{
|
||||
BEST_MILLAU_BLOCKS_METHOD, FINALIZED_MILLAU_BLOCK_METHOD, INCOMPLETE_MILLAU_HEADERS_METHOD,
|
||||
IS_KNOWN_MILLAU_BLOCK_METHOD,
|
||||
};
|
||||
use headers_relay::sync_types::QueuedHeader;
|
||||
use relay_millau_client::{HeaderId as MillauHeaderId, Millau, SyncHeader as MillauSyncHeader};
|
||||
use relay_rialto_client::{BridgeMillauCall, Rialto, SigningParams as RialtoSigningParams};
|
||||
use relay_substrate_client::{Error as SubstrateError, TransactionSignScheme};
|
||||
use relay_millau_client::{Millau, SyncHeader as MillauSyncHeader};
|
||||
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
|
||||
use relay_substrate_client::{finality_source::Justification, Error as SubstrateError, TransactionSignScheme};
|
||||
use sp_core::Pair;
|
||||
use sp_runtime::Justification;
|
||||
|
||||
/// Millau-to-Rialto headers sync pipeline.
|
||||
pub(crate) type MillauHeadersToRialto =
|
||||
SubstrateHeadersToSubstrate<Millau, MillauSyncHeader, Rialto, RialtoSigningParams>;
|
||||
/// Millau header in-the-queue.
|
||||
type QueuedMillauHeader = QueuedHeader<MillauHeadersToRialto>;
|
||||
/// Millau-to-Rialto finality sync pipeline.
|
||||
pub(crate) type MillauFinalityToRialto = SubstrateFinalityToSubstrate<Millau, Rialto, RialtoSigningParams>;
|
||||
|
||||
#[async_trait]
|
||||
impl SubstrateHeadersSyncPipeline for MillauHeadersToRialto {
|
||||
const BEST_BLOCK_METHOD: &'static str = BEST_MILLAU_BLOCKS_METHOD;
|
||||
const FINALIZED_BLOCK_METHOD: &'static str = FINALIZED_MILLAU_BLOCK_METHOD;
|
||||
const IS_KNOWN_BLOCK_METHOD: &'static str = IS_KNOWN_MILLAU_BLOCK_METHOD;
|
||||
const INCOMPLETE_HEADERS_METHOD: &'static str = INCOMPLETE_MILLAU_HEADERS_METHOD;
|
||||
impl SubstrateFinalitySyncPipeline for MillauFinalityToRialto {
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
|
||||
|
||||
type SignedTransaction = <Rialto as TransactionSignScheme>::SignedTransaction;
|
||||
|
||||
async fn make_submit_header_transaction(
|
||||
async fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
header: QueuedMillauHeader,
|
||||
header: MillauSyncHeader,
|
||||
proof: Justification<bp_millau::Header>,
|
||||
) -> Result<Self::SignedTransaction, SubstrateError> {
|
||||
let account_id = self.target_sign.signer.public().as_array_ref().clone().into();
|
||||
let nonce = self.target_client.next_account_index(account_id).await?;
|
||||
let call = BridgeMillauCall::import_signed_header(header.header().clone().into_inner()).into();
|
||||
let transaction = Rialto::sign_transaction(&self.target_client, &self.target_sign.signer, nonce, call);
|
||||
Ok(transaction)
|
||||
}
|
||||
|
||||
async fn make_complete_header_transaction(
|
||||
&self,
|
||||
id: MillauHeaderId,
|
||||
completion: Justification,
|
||||
) -> Result<Self::SignedTransaction, SubstrateError> {
|
||||
let account_id = self.target_sign.signer.public().as_array_ref().clone().into();
|
||||
let nonce = self.target_client.next_account_index(account_id).await?;
|
||||
let call = BridgeMillauCall::finalize_header(id.1, completion).into();
|
||||
let call = rialto_runtime::FinalityBridgeMillauCall::submit_finality_proof(
|
||||
header.into_inner(),
|
||||
proof.into_inner(),
|
||||
(),
|
||||
)
|
||||
.into();
|
||||
let transaction = Rialto::sign_transaction(&self.target_client, &self.target_sign.signer, nonce, call);
|
||||
Ok(transaction)
|
||||
}
|
||||
}
|
||||
|
||||
/// Run Millau-to-Rialto headers sync.
|
||||
/// Run Millau-to-Rialto finality sync.
|
||||
pub async fn run(
|
||||
millau_client: MillauClient,
|
||||
rialto_client: RialtoClient,
|
||||
rialto_sign: RialtoSigningParams,
|
||||
metrics_params: Option<relay_utils::metrics::MetricsParams>,
|
||||
) {
|
||||
crate::headers_pipeline::run(
|
||||
MillauHeadersToRialto::new(rialto_client.clone(), rialto_sign),
|
||||
crate::finality_pipeline::run(
|
||||
MillauFinalityToRialto::new(rialto_client.clone(), rialto_sign),
|
||||
millau_client,
|
||||
rialto_client,
|
||||
metrics_params,
|
||||
|
||||
@@ -51,8 +51,8 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
|
||||
bp_millau::FROM_MILLAU_LATEST_CONFIRMED_NONCE_METHOD;
|
||||
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = bp_millau::FROM_MILLAU_UNREWARDED_RELAYERS_STATE;
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_millau::FINALIZED_MILLAU_BLOCK_METHOD;
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_rialto::FINALIZED_RIALTO_BLOCK_METHOD;
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
|
||||
|
||||
type SourceSignedTransaction = <Millau as TransactionSignScheme>::SignedTransaction;
|
||||
type TargetSignedTransaction = <Rialto as TransactionSignScheme>::SignedTransaction;
|
||||
|
||||
@@ -17,69 +17,52 @@
|
||||
//! Rialto-to-Millau headers sync entrypoint.
|
||||
|
||||
use crate::{
|
||||
headers_pipeline::{SubstrateHeadersSyncPipeline, SubstrateHeadersToSubstrate},
|
||||
finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate},
|
||||
MillauClient, RialtoClient,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_rialto::{
|
||||
BEST_RIALTO_BLOCKS_METHOD, FINALIZED_RIALTO_BLOCK_METHOD, INCOMPLETE_RIALTO_HEADERS_METHOD,
|
||||
IS_KNOWN_RIALTO_BLOCK_METHOD,
|
||||
};
|
||||
use headers_relay::sync_types::QueuedHeader;
|
||||
use relay_millau_client::{BridgeRialtoCall, Millau, SigningParams as MillauSigningParams};
|
||||
use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SyncHeader as RialtoSyncHeader};
|
||||
use relay_substrate_client::{Error as SubstrateError, TransactionSignScheme};
|
||||
use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
|
||||
use relay_rialto_client::{Rialto, SyncHeader as RialtoSyncHeader};
|
||||
use relay_substrate_client::{finality_source::Justification, Error as SubstrateError, TransactionSignScheme};
|
||||
use sp_core::Pair;
|
||||
use sp_runtime::Justification;
|
||||
|
||||
/// Rialto-to-Millau headers sync pipeline.
|
||||
type RialtoHeadersToMillau = SubstrateHeadersToSubstrate<Rialto, RialtoSyncHeader, Millau, MillauSigningParams>;
|
||||
/// Rialto header in-the-queue.
|
||||
type QueuedRialtoHeader = QueuedHeader<RialtoHeadersToMillau>;
|
||||
/// Rialto-to-Millau finality sync pipeline.
|
||||
pub(crate) type RialtoFinalityToMillau = SubstrateFinalityToSubstrate<Rialto, Millau, MillauSigningParams>;
|
||||
|
||||
#[async_trait]
|
||||
impl SubstrateHeadersSyncPipeline for RialtoHeadersToMillau {
|
||||
const BEST_BLOCK_METHOD: &'static str = BEST_RIALTO_BLOCKS_METHOD;
|
||||
const FINALIZED_BLOCK_METHOD: &'static str = FINALIZED_RIALTO_BLOCK_METHOD;
|
||||
const IS_KNOWN_BLOCK_METHOD: &'static str = IS_KNOWN_RIALTO_BLOCK_METHOD;
|
||||
const INCOMPLETE_HEADERS_METHOD: &'static str = INCOMPLETE_RIALTO_HEADERS_METHOD;
|
||||
impl SubstrateFinalitySyncPipeline for RialtoFinalityToMillau {
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
|
||||
|
||||
type SignedTransaction = <Millau as TransactionSignScheme>::SignedTransaction;
|
||||
|
||||
async fn make_submit_header_transaction(
|
||||
async fn make_submit_finality_proof_transaction(
|
||||
&self,
|
||||
header: QueuedRialtoHeader,
|
||||
header: RialtoSyncHeader,
|
||||
proof: Justification<bp_rialto::Header>,
|
||||
) -> Result<Self::SignedTransaction, SubstrateError> {
|
||||
let account_id = self.target_sign.signer.public().as_array_ref().clone().into();
|
||||
let nonce = self.target_client.next_account_index(account_id).await?;
|
||||
let call = BridgeRialtoCall::import_signed_header(header.header().clone().into_inner()).into();
|
||||
let transaction = Millau::sign_transaction(&self.target_client, &self.target_sign.signer, nonce, call);
|
||||
Ok(transaction)
|
||||
}
|
||||
|
||||
async fn make_complete_header_transaction(
|
||||
&self,
|
||||
id: RialtoHeaderId,
|
||||
completion: Justification,
|
||||
) -> Result<Self::SignedTransaction, SubstrateError> {
|
||||
let account_id = self.target_sign.signer.public().as_array_ref().clone().into();
|
||||
let nonce = self.target_client.next_account_index(account_id).await?;
|
||||
let call = BridgeRialtoCall::finalize_header(id.1, completion).into();
|
||||
let call = millau_runtime::FinalityBridgeRialtoCall::submit_finality_proof(
|
||||
header.into_inner(),
|
||||
proof.into_inner(),
|
||||
(),
|
||||
)
|
||||
.into();
|
||||
let transaction = Millau::sign_transaction(&self.target_client, &self.target_sign.signer, nonce, call);
|
||||
Ok(transaction)
|
||||
}
|
||||
}
|
||||
|
||||
/// Run Rialto-to-Millau headers sync.
|
||||
/// Run Rialto-to-Millau finality sync.
|
||||
pub async fn run(
|
||||
rialto_client: RialtoClient,
|
||||
millau_client: MillauClient,
|
||||
millau_sign: MillauSigningParams,
|
||||
metrics_params: Option<relay_utils::metrics::MetricsParams>,
|
||||
) {
|
||||
crate::headers_pipeline::run(
|
||||
RialtoHeadersToMillau::new(millau_client.clone(), millau_sign),
|
||||
crate::finality_pipeline::run(
|
||||
RialtoFinalityToMillau::new(millau_client.clone(), millau_sign),
|
||||
rialto_client,
|
||||
millau_client,
|
||||
metrics_params,
|
||||
|
||||
@@ -51,8 +51,8 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
|
||||
bp_rialto::FROM_RIALTO_LATEST_CONFIRMED_NONCE_METHOD;
|
||||
const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = bp_rialto::FROM_RIALTO_UNREWARDED_RELAYERS_STATE;
|
||||
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_rialto::FINALIZED_RIALTO_BLOCK_METHOD;
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_millau::FINALIZED_MILLAU_BLOCK_METHOD;
|
||||
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
|
||||
const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
|
||||
|
||||
type SourceSignedTransaction = <Rialto as TransactionSignScheme>::SignedTransaction;
|
||||
type TargetSignedTransaction = <Millau as TransactionSignScheme>::SignedTransaction;
|
||||
|
||||
Reference in New Issue
Block a user