diff --git a/bridges/bin/millau/runtime/src/lib.rs b/bridges/bin/millau/runtime/src/lib.rs index 42251b94b0..70134602da 100644 --- a/bridges/bin/millau/runtime/src/lib.rs +++ b/bridges/bin/millau/runtime/src/lib.rs @@ -58,6 +58,7 @@ pub use frame_support::{ pub use pallet_balances::Call as BalancesCall; pub use pallet_message_lane::Call as MessageLaneCall; pub use pallet_substrate_bridge::Call as BridgeRialtoCall; +pub use pallet_sudo::Call as SudoCall; pub use pallet_timestamp::Call as TimestampCall; #[cfg(any(feature = "std", test))] diff --git a/bridges/bin/rialto/runtime/src/lib.rs b/bridges/bin/rialto/runtime/src/lib.rs index ea73fd2d1f..020be14458 100644 --- a/bridges/bin/rialto/runtime/src/lib.rs +++ b/bridges/bin/rialto/runtime/src/lib.rs @@ -67,6 +67,7 @@ pub use pallet_bridge_currency_exchange::Call as BridgeCurrencyExchangeCall; pub use pallet_bridge_eth_poa::Call as BridgeEthPoACall; pub use pallet_message_lane::Call as MessageLaneCall; pub use pallet_substrate_bridge::Call as BridgeMillauCall; +pub use pallet_sudo::Call as SudoCall; pub use pallet_timestamp::Call as TimestampCall; #[cfg(any(feature = "std", test))] diff --git a/bridges/relays/substrate/Cargo.toml b/bridges/relays/substrate/Cargo.toml index a147eb2bc2..f0d6c1e218 100644 --- a/bridges/relays/substrate/Cargo.toml +++ b/bridges/relays/substrate/Cargo.toml @@ -37,5 +37,6 @@ rialto-runtime = { path = "../../bin/rialto/runtime" } frame-support = "2.0" sp-core = "2.0" +sp-finality-grandpa = "2.0" sp-runtime = "2.0" sp-trie = "2.0" diff --git a/bridges/relays/substrate/src/cli.rs b/bridges/relays/substrate/src/cli.rs index d3ad78c3a1..334ebd0d5c 100644 --- a/bridges/relays/substrate/src/cli.rs +++ b/bridges/relays/substrate/src/cli.rs @@ -17,6 +17,8 @@ //! Deal with CLI args of substrate-to-substrate relay. use bp_message_lane::LaneId; +use sp_core::Bytes; +use sp_finality_grandpa::SetId as GrandpaAuthoritiesSetId; use structopt::{clap::arg_enum, StructOpt}; /// Parse relay CLI args. @@ -28,6 +30,17 @@ pub fn parse_args() -> Command { #[derive(StructOpt)] #[structopt(about = "Substrate-to-Substrate relay")] pub enum Command { + /// Initialize Millau headers bridge in Rialto. + InitializeMillauHeadersBridgeInRialto { + #[structopt(flatten)] + millau: MillauConnectionParams, + #[structopt(flatten)] + rialto: RialtoConnectionParams, + #[structopt(flatten)] + rialto_sign: RialtoSigningParams, + #[structopt(flatten)] + millau_bridge_params: MillauBridgeInitializationParams, + }, /// Relay Millau headers to Rialto. MillauHeadersToRialto { #[structopt(flatten)] @@ -39,6 +52,17 @@ pub enum Command { #[structopt(flatten)] prometheus_params: PrometheusParams, }, + /// Initialize Rialto headers bridge in Millau. + InitializeRialtoHeadersBridgeInMillau { + #[structopt(flatten)] + rialto: RialtoConnectionParams, + #[structopt(flatten)] + millau: MillauConnectionParams, + #[structopt(flatten)] + millau_sign: MillauSigningParams, + #[structopt(flatten)] + rialto_bridge_params: RialtoBridgeInitializationParams, + }, /// Relay Rialto headers to Millau. RialtoHeadersToMillau { #[structopt(flatten)] @@ -165,6 +189,20 @@ macro_rules! declare_chain_options { #[structopt(long)] pub [<$chain_prefix _signer_password>]: Option, } + + #[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, + #[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, + #[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, + } } }; } diff --git a/bridges/relays/substrate/src/headers_initialize.rs b/bridges/relays/substrate/src/headers_initialize.rs new file mode 100644 index 0000000000..6b66a2e9bd --- /dev/null +++ b/bridges/relays/substrate/src/headers_initialize.rs @@ -0,0 +1,141 @@ +// 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 . + +//! Initialize Substrate -> Substrate headers bridge. +//! +//! Initialization is a transaction that calls `initialize()` function of the +//! `pallet-substrate-bridge` 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 relay_substrate_client::{Chain, Client}; +use sp_core::Bytes; +use sp_finality_grandpa::{AuthorityList as GrandpaAuthoritiesSet, SetId as GrandpaAuthoritiesSetId}; + +/// Submit headers-bridge initialization transaction. +pub async fn initialize( + source_client: Client, + target_client: Client, + raw_initial_header: Option, + raw_initial_authorities_set: Option, + initial_authorities_set_id: Option, + prepare_initialize_transaction: impl FnOnce(InitializationData) -> Result, +) { + let result = do_initialize( + source_client, + target_client, + raw_initial_header, + raw_initial_authorities_set, + initial_authorities_set_id, + prepare_initialize_transaction, + ) + .await; + + match result { + Ok(tx_hash) => log::info!( + target: "bridge", + "Successfully submitted {}-headers bridge initialization transaction to {}: {:?}", + SourceChain::NAME, + TargetChain::NAME, + tx_hash, + ), + Err(err) => log::error!( + target: "bridge", + "Failed to submit {}-headers bridge initialization transaction to {}: {:?}", + SourceChain::NAME, + TargetChain::NAME, + err, + ), + } +} + +/// Craft and submit initialization transaction, returning any error that may occur. +async fn do_initialize( + source_client: Client, + target_client: Client, + raw_initial_header: Option, + raw_initial_authorities_set: Option, + initial_authorities_set_id: Option, + prepare_initialize_transaction: impl FnOnce(InitializationData) -> Result, +) -> Result { + let initialization_data = prepare_initialization_data( + source_client, + raw_initial_header, + raw_initial_authorities_set, + initial_authorities_set_id, + ) + .await?; + let initialization_tx = prepare_initialize_transaction(initialization_data)?; + let initialization_tx_hash = target_client + .submit_extrinsic(initialization_tx) + .await + .map_err(|err| format!("Failed to submit {} transaction: {:?}", TargetChain::NAME, err))?; + Ok(initialization_tx_hash) +} + +/// Prepare initialization data for the headers-bridge pallet. +async fn prepare_initialization_data( + source_client: Client, + raw_initial_header: Option, + raw_initial_authorities_set: Option, + initial_authorities_set_id: Option, +) -> Result, String> { + let source_genesis_hash = *source_client.genesis_hash(); + + 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))?, + }; + + 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: {:?}", + SourceChain::NAME, + err + ) + })?; + + Ok(InitializationData { + 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, + }) +} diff --git a/bridges/relays/substrate/src/main.rs b/bridges/relays/substrate/src/main.rs index 98c30ff0be..727e4eb243 100644 --- a/bridges/relays/substrate/src/main.rs +++ b/bridges/relays/substrate/src/main.rs @@ -33,6 +33,7 @@ pub type MillauClient = relay_substrate_client::Client; pub type RialtoClient = relay_substrate_client::Client; mod cli; +mod headers_initialize; mod headers_maintain; mod headers_pipeline; mod headers_target; @@ -53,6 +54,54 @@ fn main() { async fn run_command(command: cli::Command) -> Result<(), String> { match command { + cli::Command::InitializeMillauHeadersBridgeInRialto { + millau, + rialto, + rialto_sign, + millau_bridge_params, + } => { + let millau_client = MillauClient::new(ConnectionParams { + host: millau.millau_host, + port: millau.millau_port, + }) + .await?; + let rialto_client = RialtoClient::new(ConnectionParams { + host: rialto.rialto_host, + port: rialto.rialto_port, + }) + .await?; + let rialto_sign = RialtoSigningParams::from_suri( + &rialto_sign.rialto_signer, + rialto_sign.rialto_signer_password.as_deref(), + ) + .map_err(|e| format!("Failed to parse rialto-signer: {:?}", e))?; + let rialto_signer_next_index = rialto_client + .next_account_index(rialto_sign.signer.public().into()) + .await?; + + 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, + &rialto_sign.signer, + rialto_signer_next_index, + millau_runtime::SudoCall::sudo(Box::new( + rialto_runtime::BridgeMillauCall::initialize(initialization_data).into(), + )) + .into(), + ) + .encode(), + )) + }, + ) + .await; + } cli::Command::MillauHeadersToRialto { millau, rialto, @@ -76,6 +125,54 @@ async fn run_command(command: cli::Command) -> Result<(), String> { .map_err(|e| format!("Failed to parse rialto-signer: {:?}", e))?; millau_headers_to_rialto::run(millau_client, rialto_client, rialto_sign, prometheus_params.into()).await; } + cli::Command::InitializeRialtoHeadersBridgeInMillau { + rialto, + millau, + millau_sign, + rialto_bridge_params, + } => { + let rialto_client = RialtoClient::new(ConnectionParams { + host: rialto.rialto_host, + port: rialto.rialto_port, + }) + .await?; + let millau_client = MillauClient::new(ConnectionParams { + host: millau.millau_host, + port: millau.millau_port, + }) + .await?; + let millau_sign = MillauSigningParams::from_suri( + &millau_sign.millau_signer, + millau_sign.millau_signer_password.as_deref(), + ) + .map_err(|e| format!("Failed to parse millau-signer: {:?}", e))?; + let millau_signer_next_index = millau_client + .next_account_index(millau_sign.signer.public().into()) + .await?; + + 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, + &millau_sign.signer, + millau_signer_next_index, + millau_runtime::SudoCall::sudo(Box::new( + millau_runtime::BridgeRialtoCall::initialize(initialization_data).into(), + )) + .into(), + ) + .encode(), + )) + }, + ) + .await; + } cli::Command::RialtoHeadersToMillau { rialto, millau,