Sync Westend to Millau (#824)

* make finality verifier pallet instantiable

* add second instance of finality verifier pallet to the Millau runtime

* add Westend -> Millau headers relay

* use wss to connect to public westend nodes

* initialize with best_finalized_block

* typo

* Revert "initialize with best_finalized_block"

This reverts commit 954ed2832372d67618abc1a06d47e66faa93f674.

* pass VoterSet by ref

* new bridge initialization code

* loop upper bound

* Polkadot -> Westend

* fixed tests compilation

* default-features

* assert
This commit is contained in:
Svyatoslav Nikolsky
2021-03-16 10:09:02 +03:00
committed by Bastian Köcher
parent 249a8f73ff
commit d749bc3a96
18 changed files with 474 additions and 136 deletions
+3
View File
@@ -26,7 +26,9 @@ bp-millau = { path = "../../primitives/chains/millau" }
bp-polkadot = { path = "../../primitives/chains/polkadot" }
bp-runtime = { path = "../../primitives/runtime" }
bp-rialto = { path = "../../primitives/chains/rialto" }
bp-westend = { path = "../../primitives/chains/westend" }
bridge-runtime-common = { path = "../../bin/runtime-common" }
finality-grandpa = { version = "0.14.0" }
finality-relay = { path = "../generic/finality" }
headers-relay = { path = "../generic/headers" }
messages-relay = { path = "../generic/messages" }
@@ -39,6 +41,7 @@ relay-millau-client = { path = "../clients/millau" }
relay-polkadot-client = { path = "../clients/polkadot" }
relay-rialto-client = { path = "../clients/rialto" }
relay-substrate-client = { path = "../clients/substrate" }
relay-westend-client = { path = "../clients/westend" }
relay-utils = { path = "../generic/utils" }
rialto-runtime = { path = "../../bin/rialto/runtime" }
+3 -14
View File
@@ -396,6 +396,9 @@ macro_rules! declare_chain_options {
#[doc = "Connect to " $chain " node websocket server at given port."]
#[structopt(long)]
pub [<$chain_prefix _port>]: u16,
#[doc = "Use secure websocket connection."]
#[structopt(long)]
pub [<$chain_prefix _secure>]: bool,
}
#[doc = $chain " signing params."]
@@ -408,20 +411,6 @@ macro_rules! declare_chain_options {
#[structopt(long)]
pub [<$chain_prefix _signer_password>]: Option<String>,
}
#[doc = $chain " headers bridge initialization params."]
#[derive(StructOpt)]
pub struct [<$chain BridgeInitializationParams>] {
#[doc = "Hex-encoded " $chain " header to initialize bridge with. If not specified, genesis header is used."]
#[structopt(long)]
pub [<$chain_prefix _initial_header>]: Option<sp_core::Bytes>,
#[doc = "Hex-encoded " $chain " GRANDPA authorities set to initialize bridge with. If not specified, set from genesis block is used."]
#[structopt(long)]
pub [<$chain_prefix _initial_authorities>]: Option<sp_core::Bytes>,
#[doc = "Id of the " $chain " GRANDPA authorities set to initialize bridge with. If not specified, zero is used."]
#[structopt(long)]
pub [<$chain_prefix _initial_authorities_set_id>]: Option<sp_finality_grandpa::SetId>,
}
}
};
}
@@ -21,30 +21,26 @@
//! and authorities set from source to target chain. The headers sync starts
//! with this header.
use bp_header_chain::{
find_grandpa_authorities_scheduled_change,
justification::{decode_justification_target, verify_justification},
};
use codec::Decode;
use finality_grandpa::voter_set::VoterSet;
use num_traits::{One, Zero};
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};
use sp_finality_grandpa::AuthorityList as GrandpaAuthoritiesSet;
use sp_runtime::traits::Header as HeaderT;
/// Submit headers-bridge initialization transaction.
pub async fn initialize<SourceChain: Chain, TargetChain: Chain>(
source_client: Client<SourceChain>,
target_client: Client<TargetChain>,
raw_initial_header: Option<Bytes>,
raw_initial_authorities_set: Option<Bytes>,
initial_authorities_set_id: Option<GrandpaAuthoritiesSetId>,
prepare_initialize_transaction: impl FnOnce(InitializationData<SourceChain::Header>) -> Result<Bytes, String>,
) {
let result = do_initialize(
source_client,
target_client,
raw_initial_header,
raw_initial_authorities_set,
initial_authorities_set_id,
prepare_initialize_transaction,
)
.await;
let result = do_initialize(source_client, target_client, prepare_initialize_transaction).await;
match result {
Ok(tx_hash) => log::info!(
@@ -68,18 +64,17 @@ pub async fn initialize<SourceChain: Chain, TargetChain: Chain>(
async fn do_initialize<SourceChain: Chain, TargetChain: Chain>(
source_client: Client<SourceChain>,
target_client: Client<TargetChain>,
raw_initial_header: Option<Bytes>,
raw_initial_authorities_set: Option<Bytes>,
initial_authorities_set_id: Option<GrandpaAuthoritiesSetId>,
prepare_initialize_transaction: impl FnOnce(InitializationData<SourceChain::Header>) -> Result<Bytes, String>,
) -> Result<TargetChain::Hash, String> {
let initialization_data = prepare_initialization_data(
source_client,
raw_initial_header,
raw_initial_authorities_set,
initial_authorities_set_id,
)
.await?;
let initialization_data = prepare_initialization_data(source_client).await?;
log::info!(
target: "bridge",
"Prepared initialization data for {}-headers bridge at {}: {:?}",
SourceChain::NAME,
TargetChain::NAME,
initialization_data,
);
let initialization_tx = prepare_initialize_transaction(initialization_data)?;
let initialization_tx_hash = target_client
.submit_extrinsic(initialization_tx)
@@ -88,50 +83,161 @@ async fn do_initialize<SourceChain: Chain, TargetChain: Chain>(
Ok(initialization_tx_hash)
}
/// Prepare initialization data for the headers-bridge pallet.
/// Prepare initialization data for the finality-verifier pallet.
async fn prepare_initialization_data<SourceChain: Chain>(
source_client: Client<SourceChain>,
raw_initial_header: Option<Bytes>,
raw_initial_authorities_set: Option<Bytes>,
initial_authorities_set_id: Option<GrandpaAuthoritiesSetId>,
) -> Result<InitializationData<SourceChain::Header>, String> {
let source_genesis_hash = *source_client.genesis_hash();
// In ideal world we just need to get best finalized header and then to read GRANDPA authorities
// set (`pallet_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 = source_client
.subscribe_justifications()
.await
.map_err(|err| format!("Failed to subscribe to {} justifications: {:?}", SourceChain::NAME, err))?;
let initial_header = match raw_initial_header {
Some(raw_initial_header) => SourceChain::Header::decode(&mut &raw_initial_header.0[..])
.map_err(|err| format!("Failed to decode {} initial header: {:?}", SourceChain::NAME, err))?,
None => source_client
.header_by_hash(source_genesis_hash)
.await
.map_err(|err| format!("Failed to retrive {} genesis header: {:?}", SourceChain::NAME, err))?,
};
// Read next justification - the header that it finalizes will be used as initial header.
let justification = justifications.next().await.ok_or_else(|| {
format!(
"Failed to read {} justification from the stream: stream has ended unexpectedly",
SourceChain::NAME,
)
})?;
let raw_initial_authorities_set = match raw_initial_authorities_set {
Some(raw_initial_authorities_set) => raw_initial_authorities_set.0,
None => source_client
.grandpa_authorities_set(source_genesis_hash)
.await
.map_err(|err| {
format!(
"Failed to retrive {} authorities set at genesis header: {:?}",
SourceChain::NAME,
err
)
})?,
};
let initial_authorities_set =
GrandpaAuthoritiesSet::decode(&mut &raw_initial_authorities_set[..]).map_err(|err| {
format!(
"Failed to decode {} initial authorities set: {:?}",
// Read initial header.
let (initial_header_hash, initial_header_number) =
decode_justification_target::<SourceChain::Header>(&justification.0)
.map_err(|err| format!("Failed to decode {} justification: {:?}", SourceChain::NAME, err))?;
let initial_header = source_header(&source_client, initial_header_hash).await?;
log::trace!(target: "bridge", "Selected {} initial header: {}/{}",
SourceChain::NAME,
initial_header_number,
initial_header_hash,
);
// Read GRANDPA authorities set at initial header.
let initial_authorities_set = source_authorities_set(&source_client, initial_header_hash).await?;
log::trace!(target: "bridge", "Selected {} initial authorities set: {:?}",
SourceChain::NAME,
initial_authorities_set,
);
// 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 = find_grandpa_authorities_scheduled_change(&initial_header);
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 hange 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 = source_authorities_set(&source_client, *initial_header.parent_hash()).await?;
log::trace!(
target: "bridge",
"Selected {} header is scheduling GRANDPA authorities set changes. Using previous set: {:?}",
SourceChain::NAME,
authorities_for_verification,
);
}
// 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 = SourceChain::BlockNumber::zero();
let authorities_for_verification = VoterSet::new(authorities_for_verification.clone()).ok_or_else(|| {
format!(
"Read invalid {} authorities set: {:?}",
SourceChain::NAME,
authorities_for_verification,
)
})?;
loop {
log::trace!(
target: "bridge", "Trying {} GRANDPA authorities set id: {}",
SourceChain::NAME,
initial_authorities_set_id,
);
let is_valid_set_id = verify_justification::<SourceChain::Header>(
(initial_header_hash, initial_header_number),
initial_authorities_set_id,
&authorities_for_verification,
&justification.0,
)
.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(format!(
"Failed to guess initial {} GRANDPA authorities set id: checked all\
possible ids in range [0; {}]",
SourceChain::NAME,
err
)
})?;
initial_header_number
));
}
}
Ok(InitializationData {
header: initial_header,
authority_list: initial_authorities_set,
set_id: initial_authorities_set_id.unwrap_or(0),
set_id: if schedules_change {
initial_authorities_set_id + 1
} else {
initial_authorities_set_id
},
is_halted: false,
})
}
/// Read header by hash from the source client.
async fn source_header<SourceChain: Chain>(
source_client: &Client<SourceChain>,
header_hash: SourceChain::Hash,
) -> Result<SourceChain::Header, String> {
source_client.header_by_hash(header_hash).await.map_err(|err| {
format!(
"Failed to retrive {} header with hash {}: {:?}",
SourceChain::NAME,
header_hash,
err,
)
})
}
/// Read GRANDPA authorities set at given header.
async fn source_authorities_set<SourceChain: Chain>(
source_client: &Client<SourceChain>,
header_hash: SourceChain::Hash,
) -> Result<GrandpaAuthoritiesSet, String> {
let raw_authorities_set = source_client
.grandpa_authorities_set(header_hash)
.await
.map_err(|err| {
format!(
"Failed to retrive {} GRANDPA authorities set at header {}: {:?}",
SourceChain::NAME,
header_hash,
err,
)
})?;
GrandpaAuthoritiesSet::decode(&mut &raw_authorities_set[..]).map_err(|err| {
format!(
"Failed to decode {} GRANDPA authorities set at header {}: {:?}",
SourceChain::NAME,
header_hash,
err,
)
})
}
@@ -47,6 +47,17 @@ pub enum RelayHeaders {
#[structopt(flatten)]
prometheus_params: PrometheusParams,
},
/// Relay Westend headers to Millau.
WestendToMillau {
#[structopt(flatten)]
westend: WestendConnectionParams,
#[structopt(flatten)]
millau: MillauConnectionParams,
#[structopt(flatten)]
millau_sign: MillauSigningParams,
#[structopt(flatten)]
prometheus_params: PrometheusParams,
},
}
impl RelayHeaders {
@@ -113,8 +124,6 @@ pub enum InitBridge {
rialto: RialtoConnectionParams,
#[structopt(flatten)]
rialto_sign: RialtoSigningParams,
#[structopt(flatten)]
millau_bridge_params: MillauBridgeInitializationParams,
},
/// Initialize Rialto headers bridge in Millau.
RialtoToMillau {
@@ -124,8 +133,15 @@ pub enum InitBridge {
millau: MillauConnectionParams,
#[structopt(flatten)]
millau_sign: MillauSigningParams,
},
/// Initialize Westend headers bridge in Millau.
WestendToMillau {
#[structopt(flatten)]
rialto_bridge_params: RialtoBridgeInitializationParams,
westend: WestendConnectionParams,
#[structopt(flatten)]
millau: MillauConnectionParams,
#[structopt(flatten)]
millau_sign: MillauSigningParams,
},
}
@@ -421,3 +437,4 @@ pub enum ToMillauMessage {
declare_chain_options!(Rialto, rialto);
declare_chain_options!(Millau, millau);
declare_chain_options!(Westend, westend);
@@ -21,11 +21,14 @@ pub mod millau_headers_to_rialto;
pub mod millau_messages_to_rialto;
pub mod rialto_headers_to_millau;
pub mod rialto_messages_to_millau;
pub mod westend_headers_to_millau;
/// Millau node client.
pub type MillauClient = relay_substrate_client::Client<Millau>;
/// Rialto node client.
pub type RialtoClient = relay_substrate_client::Client<Rialto>;
/// Westend node client.
pub type WestendClient = relay_substrate_client::Client<Westend>;
use crate::cli::{ExplicitOrMaximal, HexBytes, Origins};
use codec::{Decode, Encode};
@@ -34,6 +37,7 @@ use pallet_bridge_call_dispatch::{CallOrigin, MessagePayload};
use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
use relay_substrate_client::{Chain, ConnectionParams, TransactionSignScheme};
use relay_westend_client::Westend;
use sp_core::{Bytes, Pair};
use sp_runtime::traits::IdentifyAccount;
use std::fmt::Debug;
@@ -44,7 +48,6 @@ async fn run_init_bridge(command: cli::InitBridge) -> Result<(), String> {
millau,
rialto,
rialto_sign,
millau_bridge_params,
} => {
let millau_client = millau.into_client().await?;
let rialto_client = rialto.into_client().await?;
@@ -54,34 +57,26 @@ async fn run_init_bridge(command: cli::InitBridge) -> Result<(), String> {
.next_account_index(rialto_sign.signer.public().into())
.await?;
crate::headers_initialize::initialize(
millau_client,
rialto_client.clone(),
millau_bridge_params.millau_initial_header,
millau_bridge_params.millau_initial_authorities,
millau_bridge_params.millau_initial_authorities_set_id,
move |initialization_data| {
Ok(Bytes(
Rialto::sign_transaction(
*rialto_client.genesis_hash(),
&rialto_sign.signer,
rialto_signer_next_index,
rialto_runtime::SudoCall::sudo(Box::new(
rialto_runtime::FinalityBridgeMillauCall::initialize(initialization_data).into(),
))
.into(),
)
.encode(),
))
},
)
crate::headers_initialize::initialize(millau_client, rialto_client.clone(), move |initialization_data| {
Ok(Bytes(
Rialto::sign_transaction(
*rialto_client.genesis_hash(),
&rialto_sign.signer,
rialto_signer_next_index,
rialto_runtime::SudoCall::sudo(Box::new(
rialto_runtime::FinalityBridgeMillauCall::initialize(initialization_data).into(),
))
.into(),
)
.encode(),
))
})
.await;
}
cli::InitBridge::RialtoToMillau {
rialto,
millau,
millau_sign,
rialto_bridge_params,
} => {
let rialto_client = rialto.into_client().await?;
let millau_client = millau.into_client().await?;
@@ -90,27 +85,52 @@ async fn run_init_bridge(command: cli::InitBridge) -> Result<(), String> {
.next_account_index(millau_sign.signer.public().into())
.await?;
crate::headers_initialize::initialize(
rialto_client,
millau_client.clone(),
rialto_bridge_params.rialto_initial_header,
rialto_bridge_params.rialto_initial_authorities,
rialto_bridge_params.rialto_initial_authorities_set_id,
move |initialization_data| {
Ok(Bytes(
Millau::sign_transaction(
*millau_client.genesis_hash(),
&millau_sign.signer,
millau_signer_next_index,
millau_runtime::SudoCall::sudo(Box::new(
millau_runtime::FinalityBridgeRialtoCall::initialize(initialization_data).into(),
))
.into(),
)
.encode(),
))
},
)
crate::headers_initialize::initialize(rialto_client, millau_client.clone(), move |initialization_data| {
let initialize_call = millau_runtime::FinalityBridgeRialtoCall::<
millau_runtime::Runtime,
millau_runtime::RialtoFinalityVerifierInstance,
>::initialize(initialization_data);
Ok(Bytes(
Millau::sign_transaction(
*millau_client.genesis_hash(),
&millau_sign.signer,
millau_signer_next_index,
millau_runtime::SudoCall::sudo(Box::new(initialize_call.into())).into(),
)
.encode(),
))
})
.await;
}
cli::InitBridge::WestendToMillau {
westend,
millau,
millau_sign,
} => {
let westend_client = westend.into_client().await?;
let millau_client = millau.into_client().await?;
let millau_sign = millau_sign.parse()?;
let millau_signer_next_index = millau_client
.next_account_index(millau_sign.signer.public().into())
.await?;
crate::headers_initialize::initialize(westend_client, millau_client.clone(), move |initialization_data| {
let initialize_call = millau_runtime::FinalityBridgeWestendCall::<
millau_runtime::Runtime,
millau_runtime::WestendFinalityVerifierInstance,
>::initialize(initialization_data);
Ok(Bytes(
Millau::sign_transaction(
*millau_client.genesis_hash(),
&millau_sign.signer,
millau_signer_next_index,
millau_runtime::SudoCall::sudo(Box::new(initialize_call.into())).into(),
)
.encode(),
))
})
.await;
}
}
@@ -141,6 +161,17 @@ async fn run_relay_headers(command: cli::RelayHeaders) -> Result<(), String> {
let millau_sign = millau_sign.parse()?;
rialto_headers_to_millau::run(rialto_client, millau_client, millau_sign, prometheus_params.into()).await;
}
cli::RelayHeaders::WestendToMillau {
westend,
millau,
millau_sign,
prometheus_params,
} => {
let westend_client = westend.into_client().await?;
let millau_client = millau.into_client().await?;
let millau_sign = millau_sign.parse()?;
westend_headers_to_millau::run(westend_client, millau_client, millau_sign, prometheus_params.into()).await;
}
}
Ok(())
}
@@ -661,16 +692,31 @@ impl cli::MillauConnectionParams {
MillauClient::new(ConnectionParams {
host: self.millau_host,
port: self.millau_port,
secure: self.millau_secure,
})
.await
}
}
impl cli::RialtoConnectionParams {
/// Convert CLI connection parameters into Rialto RPC Client.
pub async fn into_client(self) -> relay_substrate_client::Result<RialtoClient> {
RialtoClient::new(ConnectionParams {
host: self.rialto_host,
port: self.rialto_port,
secure: self.rialto_secure,
})
.await
}
}
impl cli::WestendConnectionParams {
/// Convert CLI connection parameters into Westend RPC Client.
pub async fn into_client(self) -> relay_substrate_client::Result<WestendClient> {
WestendClient::new(ConnectionParams {
host: self.westend_host,
port: self.westend_port,
secure: self.westend_secure,
})
.await
}
@@ -41,9 +41,12 @@ impl SubstrateFinalitySyncPipeline for RialtoFinalityToMillau {
) -> 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 =
millau_runtime::FinalityBridgeRialtoCall::submit_finality_proof(header.into_inner(), proof.into_inner())
.into();
let call = millau_runtime::FinalityBridgeRialtoCall::<
millau_runtime::Runtime,
millau_runtime::RialtoFinalityVerifierInstance,
>::submit_finality_proof(header.into_inner(), proof.into_inner())
.into();
let genesis_hash = *self.target_client.genesis_hash();
let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign.signer, nonce, call);
@@ -0,0 +1,72 @@
// 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/>.
//! Westend-to-Millau headers sync entrypoint.
use super::{MillauClient, WestendClient};
use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
use async_trait::async_trait;
use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
use relay_substrate_client::{finality_source::Justification, Error as SubstrateError, TransactionSignScheme};
use relay_westend_client::{SyncHeader as WestendSyncHeader, Westend};
use sp_core::Pair;
/// Westend-to-Millau finality sync pipeline.
pub(crate) type WestendFinalityToMillau = SubstrateFinalityToSubstrate<Westend, Millau, MillauSigningParams>;
#[async_trait]
impl SubstrateFinalitySyncPipeline for WestendFinalityToMillau {
const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_westend::BEST_FINALIZED_WESTEND_HEADER_METHOD;
type SignedTransaction = <Millau as TransactionSignScheme>::SignedTransaction;
async fn make_submit_finality_proof_transaction(
&self,
header: WestendSyncHeader,
proof: Justification<bp_westend::BlockNumber>,
) -> 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 = millau_runtime::FinalityBridgeWestendCall::<
millau_runtime::Runtime,
millau_runtime::WestendFinalityVerifierInstance,
>::submit_finality_proof(header.into_inner(), proof.into_inner())
.into();
let genesis_hash = *self.target_client.genesis_hash();
let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign.signer, nonce, call);
Ok(transaction)
}
}
/// Run Westend-to-Millau finality sync.
pub async fn run(
westend_client: WestendClient,
millau_client: MillauClient,
millau_sign: MillauSigningParams,
metrics_params: Option<relay_utils::metrics::MetricsParams>,
) {
crate::finality_pipeline::run(
WestendFinalityToMillau::new(millau_client.clone(), millau_sign),
westend_client,
millau_client,
metrics_params,
)
.await;
}