diff --git a/bridges/bin/millau/runtime/Cargo.toml b/bridges/bin/millau/runtime/Cargo.toml index 058598e5c1..92193b1153 100644 --- a/bridges/bin/millau/runtime/Cargo.toml +++ b/bridges/bin/millau/runtime/Cargo.toml @@ -19,6 +19,7 @@ bp-message-lane = { path = "../../../primitives/message-lane", default-features bp-millau = { path = "../../../primitives/chains/millau", default-features = false } bp-rialto = { path = "../../../primitives/chains/rialto", default-features = false } bp-runtime = { path = "../../../primitives/runtime", default-features = false } +bp-westend = { path = "../../../primitives/chains/westend", default-features = false } bridge-runtime-common = { path = "../../runtime-common", default-features = false } pallet-bridge-call-dispatch = { path = "../../../modules/call-dispatch", default-features = false } pallet-finality-verifier = { path = "../../../modules/finality-verifier", default-features = false } @@ -66,6 +67,7 @@ std = [ "bp-millau/std", "bp-rialto/std", "bp-runtime/std", + "bp-westend/std", "bridge-runtime-common/std", "codec/std", "frame-executive/std", diff --git a/bridges/bin/millau/runtime/src/lib.rs b/bridges/bin/millau/runtime/src/lib.rs index 8e0a2f3bd7..87d5ab8714 100644 --- a/bridges/bin/millau/runtime/src/lib.rs +++ b/bridges/bin/millau/runtime/src/lib.rs @@ -63,6 +63,7 @@ pub use frame_support::{ pub use frame_system::Call as SystemCall; pub use pallet_balances::Call as BalancesCall; pub use pallet_finality_verifier::Call as FinalityBridgeRialtoCall; +pub use pallet_finality_verifier::Call as FinalityBridgeWestendCall; pub use pallet_message_lane::Call as MessageLaneCall; pub use pallet_substrate_bridge::Call as BridgeRialtoCall; pub use pallet_sudo::Call as SudoCall; @@ -312,11 +313,18 @@ parameter_types! { pub const MaxRequests: u32 = 50; } +pub type RialtoFinalityVerifierInstance = (); impl pallet_finality_verifier::Config for Runtime { type BridgedChain = bp_rialto::Rialto; type MaxRequests = MaxRequests; } +pub type WestendFinalityVerifierInstance = pallet_finality_verifier::Instance1; +impl pallet_finality_verifier::Config for Runtime { + type BridgedChain = bp_westend::Westend; + type MaxRequests = MaxRequests; +} + impl pallet_shift_session_manager::Config for Runtime {} parameter_types! { @@ -371,7 +379,8 @@ construct_runtime!( BridgeRialto: pallet_substrate_bridge::{Module, Call, Storage, Config}, BridgeRialtoMessageLane: pallet_message_lane::{Module, Call, Storage, Event}, BridgeCallDispatch: pallet_bridge_call_dispatch::{Module, Event}, - BridgeFinalityVerifier: pallet_finality_verifier::{Module, Call}, + BridgeRialtoFinalityVerifier: pallet_finality_verifier::{Module, Call}, + BridgeWestendFinalityVerifier: pallet_finality_verifier::::{Module, Call}, System: frame_system::{Module, Call, Config, Storage, Event}, RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Module, Call, Storage}, Timestamp: pallet_timestamp::{Module, Call, Storage, Inherent}, @@ -572,12 +581,23 @@ impl_runtime_apis! { impl bp_rialto::RialtoFinalityApi for Runtime { fn best_finalized() -> (bp_rialto::BlockNumber, bp_rialto::Hash) { - let header = BridgeFinalityVerifier::best_finalized(); + let header = BridgeRialtoFinalityVerifier::best_finalized(); (header.number, header.hash()) } fn is_known_header(hash: bp_rialto::Hash) -> bool { - BridgeFinalityVerifier::is_known_header(hash) + BridgeRialtoFinalityVerifier::is_known_header(hash) + } + } + + impl bp_westend::WestendFinalityApi for Runtime { + fn best_finalized() -> (bp_westend::BlockNumber, bp_westend::Hash) { + let header = BridgeWestendFinalityVerifier::best_finalized(); + (header.number, header.hash()) + } + + fn is_known_header(hash: bp_westend::Hash) -> bool { + BridgeWestendFinalityVerifier::is_known_header(hash) } } diff --git a/bridges/modules/finality-verifier/src/lib.rs b/bridges/modules/finality-verifier/src/lib.rs index 8127daba3e..0f450273a1 100644 --- a/bridges/modules/finality-verifier/src/lib.rs +++ b/bridges/modules/finality-verifier/src/lib.rs @@ -366,7 +366,7 @@ pub mod pallet { let set_id = authority_set.set_id; Ok( - verify_justification::>((hash, number), set_id, voter_set, &justification).map_err( + verify_justification::>((hash, number), set_id, &voter_set, &justification).map_err( |e| { log::error!("Received invalid justification for {:?}: {:?}", hash, e); >::InvalidJustification diff --git a/bridges/modules/substrate/src/verifier.rs b/bridges/modules/substrate/src/verifier.rs index 11d54fade7..5ae3ed366b 100644 --- a/bridges/modules/substrate/src/verifier.rs +++ b/bridges/modules/substrate/src/verifier.rs @@ -236,7 +236,7 @@ where verify_justification::( (hash, *header.number()), current_authority_set.set_id, - voter_set, + &voter_set, &proof.0, ) .map_err(|_| FinalizationError::InvalidJustification)?; diff --git a/bridges/primitives/chains/westend/src/lib.rs b/bridges/primitives/chains/westend/src/lib.rs index a2345e3b76..5b43eeeae5 100644 --- a/bridges/primitives/chains/westend/src/lib.rs +++ b/bridges/primitives/chains/westend/src/lib.rs @@ -20,7 +20,7 @@ // Runtime-generated DecodeLimit::decode_all_with_depth_limit #![allow(clippy::unnecessary_mut_passed)] -use bp_message_lane::{LaneId, Weight, MessageNonce, UnrewardedRelayersState}; +use bp_message_lane::{LaneId, MessageNonce, UnrewardedRelayersState, Weight}; use sp_std::prelude::*; pub use bp_polkadot_core::*; diff --git a/bridges/primitives/header-chain/src/justification.rs b/bridges/primitives/header-chain/src/justification.rs index fef9aedac9..67968258a5 100644 --- a/bridges/primitives/header-chain/src/justification.rs +++ b/bridges/primitives/header-chain/src/justification.rs @@ -57,7 +57,7 @@ pub fn decode_justification_target( pub fn verify_justification( finalized_target: (Header::Hash, Header::Number), authorities_set_id: SetId, - authorities_set: VoterSet, + authorities_set: &VoterSet, raw_justification: &[u8], ) -> Result<(), Error> where @@ -76,7 +76,7 @@ where // signatures are valid. We'll check the validity of the signatures later since they're more // resource intensive to verify. let ancestry_chain = AncestryChain::new(&justification.votes_ancestries); - match finality_grandpa::validate_commit(&justification.commit, &authorities_set, &ancestry_chain) { + match finality_grandpa::validate_commit(&justification.commit, authorities_set, &ancestry_chain) { Ok(ref result) if result.ghost().is_some() => {} _ => return Err(Error::InvalidJustificationCommit), } diff --git a/bridges/primitives/header-chain/tests/justification.rs b/bridges/primitives/header-chain/tests/justification.rs index 81bd83b1ad..e71db25e41 100644 --- a/bridges/primitives/header-chain/tests/justification.rs +++ b/bridges/primitives/header-chain/tests/justification.rs @@ -34,7 +34,7 @@ fn make_justification_for_header_1() -> GrandpaJustification { #[test] fn justification_with_invalid_encoding_rejected() { assert_eq!( - verify_justification::(header_id::(1), TEST_GRANDPA_SET_ID, voter_set(), &[],), + verify_justification::(header_id::(1), TEST_GRANDPA_SET_ID, &voter_set(), &[],), Err(Error::JustificationDecode), ); } @@ -45,7 +45,7 @@ fn justification_with_invalid_target_rejected() { verify_justification::( header_id::(2), TEST_GRANDPA_SET_ID, - voter_set(), + &voter_set(), &make_justification_for_header_1().encode(), ), Err(Error::InvalidJustificationTarget), @@ -61,7 +61,7 @@ fn justification_with_invalid_commit_rejected() { verify_justification::( header_id::(1), TEST_GRANDPA_SET_ID, - voter_set(), + &voter_set(), &justification.encode(), ), Err(Error::InvalidJustificationCommit), @@ -77,7 +77,7 @@ fn justification_with_invalid_authority_signature_rejected() { verify_justification::( header_id::(1), TEST_GRANDPA_SET_ID, - voter_set(), + &voter_set(), &justification.encode(), ), Err(Error::InvalidAuthoritySignature), @@ -93,7 +93,7 @@ fn justification_with_invalid_precommit_ancestry() { verify_justification::( header_id::(1), TEST_GRANDPA_SET_ID, - voter_set(), + &voter_set(), &justification.encode(), ), Err(Error::InvalidPrecommitAncestries), @@ -106,7 +106,7 @@ fn valid_justification_accepted() { verify_justification::( header_id::(1), TEST_GRANDPA_SET_ID, - voter_set(), + &voter_set(), &make_justification_for_header_1().encode(), ), Ok(()), diff --git a/bridges/relays/clients/substrate/src/client.rs b/bridges/relays/clients/substrate/src/client.rs index 813dda6940..ec18e9ba23 100644 --- a/bridges/relays/clients/substrate/src/client.rs +++ b/bridges/relays/clients/substrate/src/client.rs @@ -95,7 +95,12 @@ impl Client { /// Build client to use in connection. async fn build_client(params: ConnectionParams) -> Result { - let uri = format!("ws://{}:{}", params.host, params.port); + let uri = format!( + "{}://{}:{}", + if params.secure { "wss" } else { "ws" }, + params.host, + params.port, + ); let mut config = RpcConfig::with_url(&uri); config.max_subscription_capacity = MAX_SUBSCRIPTION_CAPACITY; let client = RpcClient::new(config).await?; diff --git a/bridges/relays/clients/substrate/src/lib.rs b/bridges/relays/clients/substrate/src/lib.rs index 6a21e3bffa..d18189647f 100644 --- a/bridges/relays/clients/substrate/src/lib.rs +++ b/bridges/relays/clients/substrate/src/lib.rs @@ -44,6 +44,8 @@ pub struct ConnectionParams { pub host: String, /// Websocket server TCP port. pub port: u16, + /// Use secure websocket connection. + pub secure: bool, } impl Default for ConnectionParams { @@ -51,6 +53,7 @@ impl Default for ConnectionParams { ConnectionParams { host: "localhost".into(), port: 9944, + secure: false, } } } diff --git a/bridges/relays/clients/westend/Cargo.toml b/bridges/relays/clients/westend/Cargo.toml new file mode 100644 index 0000000000..d5d56154f3 --- /dev/null +++ b/bridges/relays/clients/westend/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "relay-westend-client" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "2.0.0" } +headers-relay = { path = "../../generic/headers" } +relay-substrate-client = { path = "../substrate" } +relay-utils = { path = "../../generic/utils" } + +# Bridge dependencies + +bp-westend = { path = "../../../primitives/chains/westend" } + +# Substrate Dependencies + +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/bridges/relays/clients/westend/src/lib.rs b/bridges/relays/clients/westend/src/lib.rs new file mode 100644 index 0000000000..0abbbb100c --- /dev/null +++ b/bridges/relays/clients/westend/src/lib.rs @@ -0,0 +1,47 @@ +// 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 . + +//! Types used to connect to the Westend chain. + +use relay_substrate_client::{Chain, ChainBase}; +use std::time::Duration; + +/// Westend header id. +pub type HeaderId = relay_utils::HeaderId; + +/// Westend chain definition +#[derive(Debug, Clone, Copy)] +pub struct Westend; + +impl ChainBase for Westend { + type BlockNumber = bp_westend::BlockNumber; + type Hash = bp_westend::Hash; + type Hasher = bp_westend::Hasher; + type Header = bp_westend::Header; +} + +impl Chain for Westend { + const NAME: &'static str = "Westend"; + const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_secs(6); + + type AccountId = bp_westend::AccountId; + type Index = bp_westend::Nonce; + type SignedBlock = bp_westend::SignedBlock; + type Call = (); +} + +/// Westend header type used in headers sync. +pub type SyncHeader = relay_substrate_client::SyncHeader; diff --git a/bridges/relays/substrate/Cargo.toml b/bridges/relays/substrate/Cargo.toml index d71cfa2f1e..e9e969930d 100644 --- a/bridges/relays/substrate/Cargo.toml +++ b/bridges/relays/substrate/Cargo.toml @@ -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" } diff --git a/bridges/relays/substrate/src/cli.rs b/bridges/relays/substrate/src/cli.rs index bfe5f6f549..a4296226e2 100644 --- a/bridges/relays/substrate/src/cli.rs +++ b/bridges/relays/substrate/src/cli.rs @@ -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, } - - #[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 index 2386f28f1b..0fb05ad4a9 100644 --- a/bridges/relays/substrate/src/headers_initialize.rs +++ b/bridges/relays/substrate/src/headers_initialize.rs @@ -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( 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; + 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( 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_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( 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( 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(); + // 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::(&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::( + (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( + source_client: &Client, + header_hash: SourceChain::Hash, +) -> Result { + 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( + source_client: &Client, + header_hash: SourceChain::Hash, +) -> Result { + 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, + ) + }) +} diff --git a/bridges/relays/substrate/src/rialto_millau/cli.rs b/bridges/relays/substrate/src/rialto_millau/cli.rs index 6ef5625921..86693331de 100644 --- a/bridges/relays/substrate/src/rialto_millau/cli.rs +++ b/bridges/relays/substrate/src/rialto_millau/cli.rs @@ -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); diff --git a/bridges/relays/substrate/src/rialto_millau/mod.rs b/bridges/relays/substrate/src/rialto_millau/mod.rs index 756a034974..72919ccb24 100644 --- a/bridges/relays/substrate/src/rialto_millau/mod.rs +++ b/bridges/relays/substrate/src/rialto_millau/mod.rs @@ -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; /// Rialto node client. pub type RialtoClient = relay_substrate_client::Client; +/// Westend node client. +pub type WestendClient = relay_substrate_client::Client; 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::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::new(ConnectionParams { + host: self.westend_host, + port: self.westend_port, + secure: self.westend_secure, }) .await } diff --git a/bridges/relays/substrate/src/rialto_millau/rialto_headers_to_millau.rs b/bridges/relays/substrate/src/rialto_millau/rialto_headers_to_millau.rs index 7e9b855afd..f16b37ad54 100644 --- a/bridges/relays/substrate/src/rialto_millau/rialto_headers_to_millau.rs +++ b/bridges/relays/substrate/src/rialto_millau/rialto_headers_to_millau.rs @@ -41,9 +41,12 @@ impl SubstrateFinalitySyncPipeline for RialtoFinalityToMillau { ) -> Result { 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); diff --git a/bridges/relays/substrate/src/rialto_millau/westend_headers_to_millau.rs b/bridges/relays/substrate/src/rialto_millau/westend_headers_to_millau.rs new file mode 100644 index 0000000000..c32f262218 --- /dev/null +++ b/bridges/relays/substrate/src/rialto_millau/westend_headers_to_millau.rs @@ -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 . + +//! 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; + +#[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 = ::SignedTransaction; + + async fn make_submit_finality_proof_transaction( + &self, + header: WestendSyncHeader, + proof: Justification, + ) -> Result { + 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, +) { + crate::finality_pipeline::run( + WestendFinalityToMillau::new(millau_client.clone(), millau_sign), + westend_client, + millau_client, + metrics_params, + ) + .await; +}