diff --git a/bridges/relays/ethereum/src/cli.yml b/bridges/relays/ethereum/src/cli.yml index 135e8da29a..0fbf866166 100644 --- a/bridges/relays/ethereum/src/cli.yml +++ b/bridges/relays/ethereum/src/cli.yml @@ -43,6 +43,17 @@ subcommands: long: sub-signer-password value_name: SUB_SIGNER_PASSWORD help: The password for the SURI of secret key to use when transactions are submitted to the Substrate node. + - sub-pallet-instance: &sub-pallet-instance + long: instance + short: i + value_name: PALLET_INSTANCE + help: The instance of the bridge pallet the relay should follow. + takes_value: true + case_insensitive: true + possible_values: + - Rialto + - Kovan + default_value: Rialto - no-prometheus: &no-prometheus long: no-prometheus help: Do not expose a Prometheus metric endpoint. diff --git a/bridges/relays/ethereum/src/ethereum_deploy_contract.rs b/bridges/relays/ethereum/src/ethereum_deploy_contract.rs index d46bd87944..cce91a5519 100644 --- a/bridges/relays/ethereum/src/ethereum_deploy_contract.rs +++ b/bridges/relays/ethereum/src/ethereum_deploy_contract.rs @@ -17,6 +17,7 @@ use crate::ethereum_client::{ bridge_contract, EthereumConnectionParams, EthereumHighLevelRpc, EthereumRpcClient, EthereumSigningParams, }; +use crate::instances::BridgeInstance; use crate::rpc::SubstrateRpc; use crate::substrate_client::{SubstrateConnectionParams, SubstrateRpcClient}; use crate::substrate_types::{Hash as SubstrateHash, Header as SubstrateHeader, SubstrateHeaderId}; @@ -29,50 +30,48 @@ use num_traits::Zero; #[derive(Debug)] pub struct EthereumDeployContractParams { /// Ethereum connection params. - pub eth: EthereumConnectionParams, + pub eth_params: EthereumConnectionParams, /// Ethereum signing params. pub eth_sign: EthereumSigningParams, /// Ethereum contract bytecode. pub eth_contract_code: Vec, /// Substrate connection params. - pub sub: SubstrateConnectionParams, + pub sub_params: SubstrateConnectionParams, /// Initial authorities set id. pub sub_initial_authorities_set_id: Option, /// Initial authorities set. pub sub_initial_authorities_set: Option>, /// Initial header. pub sub_initial_header: Option>, -} - -impl Default for EthereumDeployContractParams { - fn default() -> Self { - EthereumDeployContractParams { - eth: Default::default(), - eth_sign: Default::default(), - eth_contract_code: hex::decode(include_str!("../res/substrate-bridge-bytecode.hex")) - .expect("code is hardcoded, thus valid; qed"), - sub: Default::default(), - sub_initial_authorities_set_id: None, - sub_initial_authorities_set: None, - sub_initial_header: None, - } - } + /// Instance of the bridge pallet being synchronized. + pub instance: Box, } /// Deploy Bridge contract on Ethereum chain. pub fn run(params: EthereumDeployContractParams) { let mut local_pool = futures::executor::LocalPool::new(); - let result = local_pool.run_until(async move { - let eth_client = EthereumRpcClient::new(params.eth); - let sub_client = SubstrateRpcClient::new(params.sub).await?; + let EthereumDeployContractParams { + eth_params, + eth_sign, + sub_params, + instance, + sub_initial_authorities_set_id, + sub_initial_authorities_set, + sub_initial_header, + eth_contract_code, + } = params; - let (initial_header_id, initial_header) = prepare_initial_header(&sub_client, params.sub_initial_header).await?; - let initial_set_id = params.sub_initial_authorities_set_id.unwrap_or(0); + let result = local_pool.run_until(async move { + let eth_client = EthereumRpcClient::new(eth_params); + let sub_client = SubstrateRpcClient::new(sub_params, instance).await?; + + let (initial_header_id, initial_header) = prepare_initial_header(&sub_client, sub_initial_header).await?; + let initial_set_id = sub_initial_authorities_set_id.unwrap_or(0); let initial_set = prepare_initial_authorities_set( &sub_client, initial_header_id.1, - params.sub_initial_authorities_set, + sub_initial_authorities_set, ).await?; log::info!( @@ -87,8 +86,8 @@ pub fn run(params: EthereumDeployContractParams) { deploy_bridge_contract( ð_client, - ¶ms.eth_sign, - params.eth_contract_code, + ð_sign, + eth_contract_code, initial_header, initial_set_id, initial_set, diff --git a/bridges/relays/ethereum/src/ethereum_exchange.rs b/bridges/relays/ethereum/src/ethereum_exchange.rs index 9661f28f19..d094d9bae3 100644 --- a/bridges/relays/ethereum/src/ethereum_exchange.rs +++ b/bridges/relays/ethereum/src/ethereum_exchange.rs @@ -26,6 +26,7 @@ use crate::exchange::{ TransactionProofPipeline, }; use crate::exchange_loop::{run as run_loop, InMemoryStorage}; +use crate::instances::BridgeInstance; use crate::metrics::MetricsParams; use crate::rpc::{EthereumRpc, SubstrateRpc}; use crate::rpc_errors::RpcError; @@ -58,15 +59,17 @@ pub enum ExchangeRelayMode { #[derive(Debug)] pub struct EthereumExchangeParams { /// Ethereum connection params. - pub eth: EthereumConnectionParams, + pub eth_params: EthereumConnectionParams, /// Substrate connection params. - pub sub: SubstrateConnectionParams, + pub sub_params: SubstrateConnectionParams, /// Substrate signing params. pub sub_sign: SubstrateSigningParams, /// Relay working mode. pub mode: ExchangeRelayMode, /// Metrics parameters. pub metrics_params: Option, + /// Instance of the bridge pallet being synchronized. + pub instance: Box, } /// Ethereum to Substrate exchange pipeline. @@ -253,18 +256,6 @@ impl TargetClient for SubstrateTransactionsTarget { } } -impl Default for EthereumExchangeParams { - fn default() -> Self { - EthereumExchangeParams { - eth: Default::default(), - sub: Default::default(), - sub_sign: Default::default(), - mode: ExchangeRelayMode::Auto(None), - metrics_params: Some(Default::default()), - } - } -} - /// Relay exchange transaction proof(s) to Substrate node. pub fn run(params: EthereumExchangeParams) { match params.mode { @@ -279,14 +270,22 @@ pub fn run(params: EthereumExchangeParams) { fn run_single_transaction_relay(params: EthereumExchangeParams, eth_tx_hash: H256) { let mut local_pool = futures::executor::LocalPool::new(); + let EthereumExchangeParams { + eth_params, + sub_params, + sub_sign, + instance, + .. + } = params; + let result = local_pool.run_until(async move { - let eth_client = EthereumRpcClient::new(params.eth); - let sub_client = SubstrateRpcClient::new(params.sub).await?; + let eth_client = EthereumRpcClient::new(eth_params); + let sub_client = SubstrateRpcClient::new(sub_params, instance).await?; let source = EthereumTransactionsSource { client: eth_client }; let target = SubstrateTransactionsTarget { client: sub_client, - sign_params: params.sub_sign, + sign_params: sub_sign, }; relay_single_transaction_proof(&source, &target, eth_tx_hash).await @@ -313,9 +312,18 @@ fn run_single_transaction_relay(params: EthereumExchangeParams, eth_tx_hash: H25 /// Run auto-relay loop. fn run_auto_transactions_relay_loop(params: EthereumExchangeParams, eth_start_with_block_number: Option) { + let EthereumExchangeParams { + eth_params, + sub_params, + sub_sign, + metrics_params, + instance, + .. + } = params; + let do_run_loop = move || -> Result<(), String> { - let eth_client = EthereumRpcClient::new(params.eth); - let sub_client = async_std::task::block_on(SubstrateRpcClient::new(params.sub)) + let eth_client = EthereumRpcClient::new(eth_params); + let sub_client = async_std::task::block_on(SubstrateRpcClient::new(sub_params, instance)) .map_err(|err| format!("Error starting Substrate client: {:?}", err))?; let eth_start_with_block_number = match eth_start_with_block_number { @@ -337,9 +345,9 @@ fn run_auto_transactions_relay_loop(params: EthereumExchangeParams, eth_start_wi EthereumTransactionsSource { client: eth_client }, SubstrateTransactionsTarget { client: sub_client, - sign_params: params.sub_sign, + sign_params: sub_sign, }, - params.metrics_params, + metrics_params, futures::future::pending(), ); diff --git a/bridges/relays/ethereum/src/ethereum_exchange_submit.rs b/bridges/relays/ethereum/src/ethereum_exchange_submit.rs index 201b251db2..96a0fc68d0 100644 --- a/bridges/relays/ethereum/src/ethereum_exchange_submit.rs +++ b/bridges/relays/ethereum/src/ethereum_exchange_submit.rs @@ -21,7 +21,6 @@ use crate::ethereum_types::{CallRequest, U256}; use crate::rpc::EthereumRpc; use bridge_node_runtime::exchange::LOCK_FUNDS_ADDRESS; -use hex_literal::hex; use sp_bridge_eth_poa::{ signatures::{SecretKey, SignTransaction}, UnsignedTransaction, @@ -31,7 +30,7 @@ use sp_bridge_eth_poa::{ #[derive(Debug)] pub struct EthereumExchangeSubmitParams { /// Ethereum connection params. - pub eth: EthereumConnectionParams, + pub eth_params: EthereumConnectionParams, /// Ethereum signing params. pub eth_sign: EthereumSigningParams, /// Ethereum signer nonce. @@ -42,28 +41,24 @@ pub struct EthereumExchangeSubmitParams { pub sub_recipient: [u8; 32], } -impl Default for EthereumExchangeSubmitParams { - fn default() -> Self { - EthereumExchangeSubmitParams { - eth: Default::default(), - eth_sign: Default::default(), - eth_nonce: None, - eth_amount: 1_000_000_000_000_000_000_u64.into(), // 1 ETH - sub_recipient: hex!("1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c"), // ferdie - } - } -} - /// Submit single Ethereum -> Substrate exchange transaction. pub fn run(params: EthereumExchangeSubmitParams) { let mut local_pool = futures::executor::LocalPool::new(); - let result: Result<_, String> = local_pool.run_until(async move { - let eth_client = EthereumRpcClient::new(params.eth); + let EthereumExchangeSubmitParams { + eth_params, + eth_sign, + eth_nonce, + eth_amount, + sub_recipient, + } = params; - let eth_signer_address = params.eth_sign.signer.address(); - let sub_recipient_encoded = params.sub_recipient; - let nonce = match params.eth_nonce { + let result: Result<_, String> = local_pool.run_until(async move { + let eth_client = EthereumRpcClient::new(eth_params); + + let eth_signer_address = eth_sign.signer.address(); + let sub_recipient_encoded = sub_recipient; + let nonce = match eth_nonce { Some(eth_nonce) => eth_nonce, None => eth_client .account_nonce(eth_signer_address) @@ -74,7 +69,7 @@ pub fn run(params: EthereumExchangeSubmitParams) { .estimate_gas(CallRequest { from: Some(eth_signer_address), to: Some(LOCK_FUNDS_ADDRESS.into()), - value: Some(params.eth_amount), + value: Some(eth_amount), data: Some(sub_recipient_encoded.to_vec().into()), ..Default::default() }) @@ -82,16 +77,16 @@ pub fn run(params: EthereumExchangeSubmitParams) { .map_err(|err| format!("error estimating gas requirements: {:?}", err))?; let eth_tx_unsigned = UnsignedTransaction { nonce, - gas_price: params.eth_sign.gas_price, + gas_price: eth_sign.gas_price, gas, to: Some(LOCK_FUNDS_ADDRESS.into()), - value: params.eth_amount, + value: eth_amount, payload: sub_recipient_encoded.to_vec(), }; let eth_tx_signed = eth_tx_unsigned.clone().sign_by( - &SecretKey::parse(params.eth_sign.signer.secret().as_fixed_bytes()) + &SecretKey::parse(eth_sign.signer.secret().as_fixed_bytes()) .expect("key is accepted by secp256k1::KeyPair and thus is valid; qed"), - Some(params.eth_sign.chain_id), + Some(eth_sign.chain_id), ); eth_client .submit_transaction(eth_tx_signed) diff --git a/bridges/relays/ethereum/src/ethereum_sync_loop.rs b/bridges/relays/ethereum/src/ethereum_sync_loop.rs index 4a13fa6a1d..939cad5666 100644 --- a/bridges/relays/ethereum/src/ethereum_sync_loop.rs +++ b/bridges/relays/ethereum/src/ethereum_sync_loop.rs @@ -18,6 +18,7 @@ use crate::ethereum_client::{EthereumConnectionParams, EthereumHighLevelRpc, EthereumRpcClient}; use crate::ethereum_types::{EthereumHeaderId, EthereumHeadersSyncPipeline, Header, QueuedEthereumHeader, Receipt}; +use crate::instances::BridgeInstance; use crate::metrics::MetricsParams; use crate::rpc::{EthereumRpc, SubstrateRpc}; use crate::rpc_errors::RpcError; @@ -32,57 +33,45 @@ use crate::sync_types::{SourceHeader, SubmittedHeaders}; use async_trait::async_trait; use web3::types::H256; +use std::fmt::Debug; use std::{collections::HashSet, time::Duration}; -/// Interval at which we check new Ethereum headers when we are synced/almost synced. -const ETHEREUM_TICK_INTERVAL: Duration = Duration::from_secs(10); -/// Interval at which we check new Substrate blocks. -const SUBSTRATE_TICK_INTERVAL: Duration = Duration::from_secs(5); -/// Max number of headers in single submit transaction. -const MAX_HEADERS_IN_SINGLE_SUBMIT: usize = 32; -/// Max total size of headers in single submit transaction. This only affects signed -/// submissions, when several headers are submitted at once. 4096 is the maximal **expected** -/// size of the Ethereum header + transactions receipts (if they're required). -const MAX_HEADERS_SIZE_IN_SINGLE_SUBMIT: usize = MAX_HEADERS_IN_SINGLE_SUBMIT * 4096; -/// Max Ethereum headers we want to have in all 'before-submitted' states. -const MAX_FUTURE_HEADERS_TO_DOWNLOAD: usize = 128; -/// Max Ethereum headers count we want to have in 'submitted' state. -const MAX_SUBMITTED_HEADERS: usize = 128; -/// Max depth of in-memory headers in all states. Past this depth they will be forgotten (pruned). -const PRUNE_DEPTH: u32 = 4096; +pub mod consts { + use super::*; + + /// Interval at which we check new Ethereum headers when we are synced/almost synced. + pub const ETHEREUM_TICK_INTERVAL: Duration = Duration::from_secs(10); + /// Interval at which we check new Substrate blocks. + pub const SUBSTRATE_TICK_INTERVAL: Duration = Duration::from_secs(5); + /// Max number of headers in single submit transaction. + pub const MAX_HEADERS_IN_SINGLE_SUBMIT: usize = 32; + /// Max total size of headers in single submit transaction. This only affects signed + /// submissions, when several headers are submitted at once. 4096 is the maximal **expected** + /// size of the Ethereum header + transactions receipts (if they're required). + pub const MAX_HEADERS_SIZE_IN_SINGLE_SUBMIT: usize = MAX_HEADERS_IN_SINGLE_SUBMIT * 4096; + /// Max Ethereum headers we want to have in all 'before-submitted' states. + pub const MAX_FUTURE_HEADERS_TO_DOWNLOAD: usize = 128; + /// Max Ethereum headers count we want to have in 'submitted' state. + pub const MAX_SUBMITTED_HEADERS: usize = 128; + /// Max depth of in-memory headers in all states. Past this depth they will be forgotten (pruned). + pub const PRUNE_DEPTH: u32 = 4096; +} /// Ethereum synchronization parameters. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct EthereumSyncParams { /// Ethereum connection params. - pub eth: EthereumConnectionParams, + pub eth_params: EthereumConnectionParams, /// Substrate connection params. - pub sub: SubstrateConnectionParams, + pub sub_params: SubstrateConnectionParams, /// Substrate signing params. pub sub_sign: SubstrateSigningParams, /// Synchronization parameters. pub sync_params: HeadersSyncParams, /// Metrics parameters. pub metrics_params: Option, -} - -impl Default for EthereumSyncParams { - fn default() -> Self { - EthereumSyncParams { - eth: Default::default(), - sub: Default::default(), - sub_sign: Default::default(), - sync_params: HeadersSyncParams { - max_future_headers_to_download: MAX_FUTURE_HEADERS_TO_DOWNLOAD, - max_headers_in_submitted_status: MAX_SUBMITTED_HEADERS, - max_headers_in_single_submit: MAX_HEADERS_IN_SINGLE_SUBMIT, - max_headers_size_in_single_submit: MAX_HEADERS_SIZE_IN_SINGLE_SUBMIT, - prune_depth: PRUNE_DEPTH, - target_tx_mode: TargetTransactionMode::Signed, - }, - metrics_params: Some(Default::default()), - } - } + /// Instance of the bridge pallet being synchronized. + pub instance: Box, } /// Ethereum client as headers source. @@ -190,26 +179,33 @@ impl TargetClient for SubstrateHeadersTarget { /// Run Ethereum headers synchronization. pub fn run(params: EthereumSyncParams) -> Result<(), RpcError> { - let sub_params = params.clone(); + let EthereumSyncParams { + eth_params, + sub_params, + sub_sign, + sync_params, + metrics_params, + instance, + } = params; - let eth_client = EthereumRpcClient::new(params.eth); - let sub_client = async_std::task::block_on(async { SubstrateRpcClient::new(sub_params.sub).await })?; + let eth_client = EthereumRpcClient::new(eth_params); + let sub_client = async_std::task::block_on(async { SubstrateRpcClient::new(sub_params, instance).await })?; - let sign_sub_transactions = match params.sync_params.target_tx_mode { + let sign_sub_transactions = match sync_params.target_tx_mode { TargetTransactionMode::Signed | TargetTransactionMode::Backup => true, TargetTransactionMode::Unsigned => false, }; let source = EthereumHeadersSource::new(eth_client); - let target = SubstrateHeadersTarget::new(sub_client, sign_sub_transactions, params.sub_sign); + let target = SubstrateHeadersTarget::new(sub_client, sign_sub_transactions, sub_sign); crate::sync_loop::run( source, - ETHEREUM_TICK_INTERVAL, + consts::ETHEREUM_TICK_INTERVAL, target, - SUBSTRATE_TICK_INTERVAL, - params.sync_params, - params.metrics_params, + consts::SUBSTRATE_TICK_INTERVAL, + sync_params, + metrics_params, futures::future::pending(), ); diff --git a/bridges/relays/ethereum/src/instances.rs b/bridges/relays/ethereum/src/instances.rs new file mode 100644 index 0000000000..fa6963430b --- /dev/null +++ b/bridges/relays/ethereum/src/instances.rs @@ -0,0 +1,115 @@ +// Copyright 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 . + +//! The PoA Bridge Pallet provides a way to include multiple instances of itself in a runtime. When +//! synchronizing a Substrate chain which can include multiple instances of the bridge pallet we +//! must somehow decide which of the instances to sync. +//! +//! Note that each instance of the bridge pallet is coupled with an instance of the currency exchange +//! pallet. We must also have a way to create `Call`s for the correct currency exchange instance. +//! +//! This module helps by preparing the correct `Call`s for each of the different pallet instances. + +use crate::ethereum_types::QueuedEthereumHeader; +use crate::substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts}; + +use bridge_node_runtime::exchange::EthereumTransactionInclusionProof as Proof; +use bridge_node_runtime::Call; + +/// Interface for `Calls` which are needed to correctly sync the bridge. +/// +/// Each instance of the bridge and currency exchange pallets in the bridge runtime requires similar +/// but slightly different `Call` in order to be synchronized. +pub trait BridgeInstance: Send + Sync + std::fmt::Debug { + /// Used to build a `Call` for importing signed headers to a Substrate runtime. + fn build_signed_header_call(&self, headers: Vec) -> Call; + /// Used to build a `Call` for importing an unsigned header to a Substrate runtime. + fn build_unsigned_header_call(&self, header: QueuedEthereumHeader) -> Call; + /// Used to build a `Call` for importing peer transactions to a Substrate runtime. + fn build_currency_exchange_call(&self, proof: Proof) -> Call; +} + +/// Corresponds to the Rialto instance used in the bridge runtime. +#[derive(Default, Clone, Debug)] +pub struct Rialto; + +impl BridgeInstance for Rialto { + fn build_signed_header_call(&self, headers: Vec) -> Call { + let pallet_call = bridge_node_runtime::BridgeEthPoACall::import_signed_headers( + headers + .into_iter() + .map(|header| { + ( + into_substrate_ethereum_header(header.header()), + into_substrate_ethereum_receipts(header.extra()), + ) + }) + .collect(), + ); + + bridge_node_runtime::Call::BridgeRialto(pallet_call) + } + + fn build_unsigned_header_call(&self, header: QueuedEthereumHeader) -> Call { + let pallet_call = bridge_node_runtime::BridgeEthPoACall::import_unsigned_header( + into_substrate_ethereum_header(header.header()), + into_substrate_ethereum_receipts(header.extra()), + ); + + bridge_node_runtime::Call::BridgeRialto(pallet_call) + } + + fn build_currency_exchange_call(&self, proof: Proof) -> Call { + let pallet_call = bridge_node_runtime::BridgeCurrencyExchangeCall::import_peer_transaction(proof); + bridge_node_runtime::Call::BridgeRialtoCurrencyExchange(pallet_call) + } +} + +/// Corresponds to the Kovan instance used in the bridge runtime. +#[derive(Default, Clone, Debug)] +pub struct Kovan; + +impl BridgeInstance for Kovan { + fn build_signed_header_call(&self, headers: Vec) -> Call { + let pallet_call = bridge_node_runtime::BridgeEthPoACall::import_signed_headers( + headers + .into_iter() + .map(|header| { + ( + into_substrate_ethereum_header(header.header()), + into_substrate_ethereum_receipts(header.extra()), + ) + }) + .collect(), + ); + + bridge_node_runtime::Call::BridgeKovan(pallet_call) + } + + fn build_unsigned_header_call(&self, header: QueuedEthereumHeader) -> Call { + let pallet_call = bridge_node_runtime::BridgeEthPoACall::import_unsigned_header( + into_substrate_ethereum_header(header.header()), + into_substrate_ethereum_receipts(header.extra()), + ); + + bridge_node_runtime::Call::BridgeKovan(pallet_call) + } + + fn build_currency_exchange_call(&self, proof: Proof) -> Call { + let pallet_call = bridge_node_runtime::BridgeCurrencyExchangeCall::import_peer_transaction(proof); + bridge_node_runtime::Call::BridgeKovanCurrencyExchange(pallet_call) + } +} diff --git a/bridges/relays/ethereum/src/main.rs b/bridges/relays/ethereum/src/main.rs index 94d88f6e93..5a1cf23ab1 100644 --- a/bridges/relays/ethereum/src/main.rs +++ b/bridges/relays/ethereum/src/main.rs @@ -26,6 +26,7 @@ mod exchange; mod exchange_loop; mod exchange_loop_metrics; mod headers; +mod instances; mod metrics; mod rpc; mod rpc_errors; @@ -40,12 +41,19 @@ mod sync_types; mod utils; use ethereum_client::{EthereumConnectionParams, EthereumSigningParams}; +use ethereum_deploy_contract::EthereumDeployContractParams; +use ethereum_exchange::EthereumExchangeParams; +use ethereum_exchange_submit::EthereumExchangeSubmitParams; use ethereum_sync_loop::EthereumSyncParams; +use hex_literal::hex; +use instances::{BridgeInstance, Kovan, Rialto}; use parity_crypto::publickey::{KeyPair, Secret}; use sp_core::crypto::Pair; -use std::io::Write; use substrate_client::{SubstrateConnectionParams, SubstrateSigningParams}; use substrate_sync_loop::SubstrateSyncParams; +use sync::HeadersSyncParams; + +use std::io::Write; fn main() { initialize(); @@ -210,110 +218,134 @@ fn substrate_signing_params(matches: &clap::ArgMatches) -> Result Result { - let mut eth_sync_params = EthereumSyncParams::default(); - eth_sync_params.eth = ethereum_connection_params(matches)?; - eth_sync_params.sub = substrate_connection_params(matches)?; - eth_sync_params.sub_sign = substrate_signing_params(matches)?; - eth_sync_params.metrics_params = metrics_params(matches)?; + let mut sync_params = HeadersSyncParams::ethereum_sync_default(); match matches.value_of("sub-tx-mode") { - Some("signed") => eth_sync_params.sync_params.target_tx_mode = sync::TargetTransactionMode::Signed, + Some("signed") => sync_params.target_tx_mode = sync::TargetTransactionMode::Signed, Some("unsigned") => { - eth_sync_params.sync_params.target_tx_mode = sync::TargetTransactionMode::Unsigned; + sync_params.target_tx_mode = sync::TargetTransactionMode::Unsigned; // tx pool won't accept too much unsigned transactions - eth_sync_params.sync_params.max_headers_in_submitted_status = 10; + sync_params.max_headers_in_submitted_status = 10; } - Some("backup") => eth_sync_params.sync_params.target_tx_mode = sync::TargetTransactionMode::Backup, + Some("backup") => sync_params.target_tx_mode = sync::TargetTransactionMode::Backup, Some(mode) => return Err(format!("Invalid sub-tx-mode: {}", mode)), - None => eth_sync_params.sync_params.target_tx_mode = sync::TargetTransactionMode::Signed, + None => sync_params.target_tx_mode = sync::TargetTransactionMode::Signed, } - log::debug!(target: "bridge", "Ethereum sync params: {:?}", eth_sync_params); + let params = EthereumSyncParams { + eth_params: ethereum_connection_params(matches)?, + sub_params: substrate_connection_params(matches)?, + sub_sign: substrate_signing_params(matches)?, + metrics_params: metrics_params(matches)?, + instance: instance_params(matches)?, + sync_params, + }; - Ok(eth_sync_params) + log::debug!(target: "bridge", "Ethereum sync params: {:?}", params); + + Ok(params) } fn substrate_sync_params(matches: &clap::ArgMatches) -> Result { - let mut sub_sync_params = SubstrateSyncParams::default(); - sub_sync_params.eth = ethereum_connection_params(matches)?; - sub_sync_params.eth_sign = ethereum_signing_params(matches)?; - sub_sync_params.sub = substrate_connection_params(matches)?; - sub_sync_params.metrics_params = metrics_params(matches)?; - - if let Some(eth_contract) = matches.value_of("eth-contract") { - sub_sync_params.eth_contract_address = eth_contract.parse().map_err(|e| format!("{}", e))?; - } - - log::debug!(target: "bridge", "Substrate sync params: {:?}", sub_sync_params); - - Ok(sub_sync_params) -} - -fn ethereum_deploy_contract_params( - matches: &clap::ArgMatches, -) -> Result { - let mut eth_deploy_params = ethereum_deploy_contract::EthereumDeployContractParams::default(); - eth_deploy_params.eth = ethereum_connection_params(matches)?; - eth_deploy_params.eth_sign = ethereum_signing_params(matches)?; - eth_deploy_params.sub = substrate_connection_params(matches)?; - - if let Some(eth_contract_code) = matches.value_of("eth-contract-code") { - eth_deploy_params.eth_contract_code = - hex::decode(ð_contract_code).map_err(|e| format!("Failed to parse eth-contract-code: {}", e))?; - } - - log::debug!(target: "bridge", "Deploy params: {:?}", eth_deploy_params); - - Ok(eth_deploy_params) -} - -fn ethereum_exchange_submit_params( - matches: &clap::ArgMatches, -) -> Result { - let mut params = ethereum_exchange_submit::EthereumExchangeSubmitParams::default(); - params.eth = ethereum_connection_params(matches)?; - params.eth_sign = ethereum_signing_params(matches)?; - - if let Some(eth_nonce) = matches.value_of("eth-nonce") { - params.eth_nonce = Some( - ethereum_types::U256::from_dec_str(ð_nonce).map_err(|e| format!("Failed to parse eth-nonce: {}", e))?, - ); - } - if let Some(eth_amount) = matches.value_of("eth-amount") { - params.eth_amount = eth_amount + let eth_contract_address: ethereum_types::Address = if let Some(eth_contract) = matches.value_of("eth-contract") { + eth_contract.parse().map_err(|e| format!("{}", e))? + } else { + "731a10897d267e19b34503ad902d0a29173ba4b1" .parse() - .map_err(|e| format!("Failed to parse eth-amount: {}", e))?; - } - if let Some(sub_recipient) = matches.value_of("sub-recipient") { - params.sub_recipient = hex::decode(&sub_recipient) + .expect("address is hardcoded, thus valid; qed") + }; + + let params = SubstrateSyncParams { + sub_params: substrate_connection_params(matches)?, + eth_params: ethereum_connection_params(matches)?, + eth_sign: ethereum_signing_params(matches)?, + metrics_params: metrics_params(matches)?, + instance: instance_params(matches)?, + sync_params: HeadersSyncParams::substrate_sync_default(), + eth_contract_address, + }; + + log::debug!(target: "bridge", "Substrate sync params: {:?}", params); + + Ok(params) +} + +fn ethereum_deploy_contract_params(matches: &clap::ArgMatches) -> Result { + let eth_contract_code = if let Some(eth_contract_code) = matches.value_of("eth-contract-code") { + hex::decode(ð_contract_code).map_err(|e| format!("Failed to parse eth-contract-code: {}", e))? + } else { + hex::decode(include_str!("../res/substrate-bridge-bytecode.hex")).expect("code is hardcoded, thus valid; qed") + }; + + let params = EthereumDeployContractParams { + eth_params: ethereum_connection_params(matches)?, + eth_sign: ethereum_signing_params(matches)?, + sub_params: substrate_connection_params(matches)?, + instance: instance_params(matches)?, + sub_initial_authorities_set_id: None, + sub_initial_authorities_set: None, + sub_initial_header: None, + eth_contract_code, + }; + + log::debug!(target: "bridge", "Deploy params: {:?}", params); + + Ok(params) +} + +fn ethereum_exchange_submit_params(matches: &clap::ArgMatches) -> Result { + let eth_nonce = if let Some(eth_nonce) = matches.value_of("eth-nonce") { + Some(ethereum_types::U256::from_dec_str(ð_nonce).map_err(|e| format!("Failed to parse eth-nonce: {}", e))?) + } else { + None + }; + + let eth_amount = if let Some(eth_amount) = matches.value_of("eth-amount") { + eth_amount + .parse() + .map_err(|e| format!("Failed to parse eth-amount: {}", e))? + } else { + // This is in Wei, represents 1 ETH + 1_000_000_000_000_000_000_u64.into() + }; + + // This is the well-known Substrate account of Ferdie + let default_recepient = hex!("1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c"); + + let sub_recipient = if let Some(sub_recipient) = matches.value_of("sub-recipient") { + hex::decode(&sub_recipient) .map_err(|err| err.to_string()) .and_then(|vsub_recipient| { - let expected_len = params.sub_recipient.len(); + let expected_len = default_recepient.len(); if expected_len != vsub_recipient.len() { Err(format!("invalid length. Expected {} bytes", expected_len)) } else { - let mut sub_recipient = params.sub_recipient; + let mut sub_recipient = default_recepient; sub_recipient.copy_from_slice(&vsub_recipient[..expected_len]); Ok(sub_recipient) } }) - .map_err(|e| format!("Failed to parse sub-recipient: {}", e))?; - } + .map_err(|e| format!("Failed to parse sub-recipient: {}", e))? + } else { + default_recepient + }; + + let params = EthereumExchangeSubmitParams { + eth_params: ethereum_connection_params(matches)?, + eth_sign: ethereum_signing_params(matches)?, + eth_nonce, + eth_amount, + sub_recipient, + }; log::debug!(target: "bridge", "Submit Ethereum exchange tx params: {:?}", params); Ok(params) } -fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result { - let mut params = ethereum_exchange::EthereumExchangeParams::default(); - params.eth = ethereum_connection_params(matches)?; - params.sub = substrate_connection_params(matches)?; - params.sub_sign = substrate_signing_params(matches)?; - params.metrics_params = metrics_params(matches)?; - - params.mode = match matches.value_of("eth-tx-hash") { +fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result { + let mode = match matches.value_of("eth-tx-hash") { Some(eth_tx_hash) => ethereum_exchange::ExchangeRelayMode::Single( eth_tx_hash .parse() @@ -329,6 +361,15 @@ fn ethereum_exchange_params(matches: &clap::ArgMatches) -> Result Result Result, String> { + let instance: Box = if let Some(instance) = matches.value_of("sub-pallet-instance") { + match instance.to_lowercase().as_str() { + "rialto" => Box::new(Rialto::default()), + "kovan" => Box::new(Kovan::default()), + _ => return Err("Unsupported bridge pallet instance".to_string()), + } + } else { + unreachable!("CLI config enforces a default instance, can never be None") + }; + + Ok(instance) +} diff --git a/bridges/relays/ethereum/src/substrate_client.rs b/bridges/relays/ethereum/src/substrate_client.rs index bfb6c3c279..f40d3a5472 100644 --- a/bridges/relays/ethereum/src/substrate_client.rs +++ b/bridges/relays/ethereum/src/substrate_client.rs @@ -15,12 +15,10 @@ // along with Parity Bridges Common. If not, see . use crate::ethereum_types::{Bytes, EthereumHeaderId, QueuedEthereumHeader, H256}; +use crate::instances::BridgeInstance; use crate::rpc::{Substrate, SubstrateRpc}; use crate::rpc_errors::RpcError; -use crate::substrate_types::{ - into_substrate_ethereum_header, into_substrate_ethereum_receipts, Hash, Header as SubstrateHeader, Number, - SignedBlock as SignedSubstrateBlock, -}; +use crate::substrate_types::{Hash, Header as SubstrateHeader, Number, SignedBlock as SignedSubstrateBlock}; use crate::sync_types::{HeaderId, SubmittedHeaders}; use async_trait::async_trait; @@ -89,11 +87,13 @@ pub struct SubstrateRpcClient { client: Client, /// Genesis block hash. genesis_hash: H256, + /// Instance of the bridge pallet being synchronized. + instance: Box, } impl SubstrateRpcClient { /// Returns client that is able to call RPCs on Substrate node. - pub async fn new(params: SubstrateConnectionParams) -> Result { + pub async fn new(params: SubstrateConnectionParams, instance: Box) -> Result { let uri = format!("http://{}:{}", params.host, params.port); let transport = HttpTransportClient::new(&uri); let raw_client = RawClient::new(transport); @@ -102,7 +102,11 @@ impl SubstrateRpcClient { let number: Number = Zero::zero(); let genesis_hash = Substrate::chain_get_block_hash(&client, number).await?; - Ok(Self { client, genesis_hash }) + Ok(Self { + client, + genesis_hash, + instance, + }) } } @@ -248,7 +252,8 @@ impl SubmitEthereumHeaders for SubstrateRpcClient { let account_id = params.signer.public().as_array_ref().clone().into(); let nonce = self.next_account_index(account_id).await?; - let transaction = create_signed_submit_transaction(headers, ¶ms.signer, nonce, self.genesis_hash); + let call = self.instance.build_signed_header_call(headers); + let transaction = create_signed_submit_transaction(call, ¶ms.signer, nonce, self.genesis_hash); let _ = self.submit_extrinsic(Bytes(transaction.encode())).await?; Ok(()) } @@ -276,9 +281,13 @@ impl SubmitEthereumHeaders for SubstrateRpcClient { ) -> SubmittedHeaders { let mut ids = headers.iter().map(|header| header.id()).collect::>(); let mut submitted_headers = SubmittedHeaders::default(); + for header in headers { let id = ids.pop_front().expect("both collections have same size; qed"); - let transaction = create_unsigned_submit_transaction(header); + + let call = self.instance.build_unsigned_header_call(header); + let transaction = create_unsigned_submit_transaction(call); + match self.submit_extrinsic(Bytes(transaction.encode())).await { Ok(_) => submitted_headers.submitted.push(id), Err(error) => { @@ -335,15 +344,9 @@ impl SubmitEthereumExchangeTransactionProof for SubstrateRpcClient { let account_id = params.signer.public().as_array_ref().clone().into(); let nonce = self.next_account_index(account_id).await?; - let transaction = create_signed_transaction( - // TODO [#209]: Change so that that it's dynamic - bridge_node_runtime::Call::BridgeRialtoCurrencyExchange( - bridge_node_runtime::BridgeCurrencyExchangeCall::import_peer_transaction(proof), - ), - ¶ms.signer, - nonce, - self.genesis_hash, - ); + let call = self.instance.build_currency_exchange_call(proof); + let transaction = create_signed_transaction(call, ¶ms.signer, nonce, self.genesis_hash); + let _ = self.submit_extrinsic(Bytes(transaction.encode())).await?; Ok(()) } @@ -351,40 +354,17 @@ impl SubmitEthereumExchangeTransactionProof for SubstrateRpcClient { /// Create signed Substrate transaction for submitting Ethereum headers. fn create_signed_submit_transaction( - headers: Vec, + signed_call: bridge_node_runtime::Call, signer: &sp_core::sr25519::Pair, index: node_primitives::Index, genesis_hash: H256, ) -> bridge_node_runtime::UncheckedExtrinsic { - create_signed_transaction( - // TODO [#209]: Change so that that it's dynamic - bridge_node_runtime::Call::BridgeRialto(bridge_node_runtime::BridgeEthPoACall::import_signed_headers( - headers - .into_iter() - .map(|header| { - ( - into_substrate_ethereum_header(header.header()), - into_substrate_ethereum_receipts(header.extra()), - ) - }) - .collect(), - )), - signer, - index, - genesis_hash, - ) + create_signed_transaction(signed_call, signer, index, genesis_hash) } /// Create unsigned Substrate transaction for submitting Ethereum header. -fn create_unsigned_submit_transaction(header: QueuedEthereumHeader) -> bridge_node_runtime::UncheckedExtrinsic { - let function = - // TODO [#209]: Change so that that it's dynamic - bridge_node_runtime::Call::BridgeRialto(bridge_node_runtime::BridgeEthPoACall::import_unsigned_header( - into_substrate_ethereum_header(header.header()), - into_substrate_ethereum_receipts(header.extra()), - )); - - bridge_node_runtime::UncheckedExtrinsic::new_unsigned(function) +fn create_unsigned_submit_transaction(call: bridge_node_runtime::Call) -> bridge_node_runtime::UncheckedExtrinsic { + bridge_node_runtime::UncheckedExtrinsic::new_unsigned(call) } /// Create signed Substrate transaction. diff --git a/bridges/relays/ethereum/src/substrate_sync_loop.rs b/bridges/relays/ethereum/src/substrate_sync_loop.rs index 16afb163d4..0ed7913327 100644 --- a/bridges/relays/ethereum/src/substrate_sync_loop.rs +++ b/bridges/relays/ethereum/src/substrate_sync_loop.rs @@ -20,6 +20,7 @@ use crate::ethereum_client::{ EthereumConnectionParams, EthereumHighLevelRpc, EthereumRpcClient, EthereumSigningParams, }; use crate::ethereum_types::Address; +use crate::instances::BridgeInstance; use crate::metrics::MetricsParams; use crate::rpc::SubstrateRpc; use crate::rpc_errors::RpcError; @@ -27,66 +28,47 @@ use crate::substrate_client::{SubstrateConnectionParams, SubstrateRpcClient}; use crate::substrate_types::{ GrandpaJustification, Hash, Header, Number, QueuedSubstrateHeader, SubstrateHeaderId, SubstrateHeadersSyncPipeline, }; -use crate::sync::{HeadersSyncParams, TargetTransactionMode}; +use crate::sync::HeadersSyncParams; use crate::sync_loop::{SourceClient, TargetClient}; use crate::sync_types::{SourceHeader, SubmittedHeaders}; use async_trait::async_trait; +use std::fmt::Debug; use std::{collections::HashSet, time::Duration}; -/// Interval at which we check new Substrate headers when we are synced/almost synced. -const SUBSTRATE_TICK_INTERVAL: Duration = Duration::from_secs(10); -/// Interval at which we check new Ethereum blocks. -const ETHEREUM_TICK_INTERVAL: Duration = Duration::from_secs(5); -/// Max Ethereum headers we want to have in all 'before-submitted' states. -const MAX_FUTURE_HEADERS_TO_DOWNLOAD: usize = 8; -/// Max Ethereum headers count we want to have in 'submitted' state. -const MAX_SUBMITTED_HEADERS: usize = 1; -/// Max depth of in-memory headers in all states. Past this depth they will be forgotten (pruned). -const PRUNE_DEPTH: u32 = 256; +pub mod consts { + use super::*; + + /// Interval at which we check new Substrate headers when we are synced/almost synced. + pub const SUBSTRATE_TICK_INTERVAL: Duration = Duration::from_secs(10); + /// Interval at which we check new Ethereum blocks. + pub const ETHEREUM_TICK_INTERVAL: Duration = Duration::from_secs(5); + /// Max Ethereum headers we want to have in all 'before-submitted' states. + pub const MAX_FUTURE_HEADERS_TO_DOWNLOAD: usize = 8; + /// Max Ethereum headers count we want to have in 'submitted' state. + pub const MAX_SUBMITTED_HEADERS: usize = 1; + /// Max depth of in-memory headers in all states. Past this depth they will be forgotten (pruned). + pub const PRUNE_DEPTH: u32 = 256; +} /// Substrate synchronization parameters. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct SubstrateSyncParams { + /// Substrate connection params. + pub sub_params: SubstrateConnectionParams, /// Ethereum connection params. - pub eth: EthereumConnectionParams, + pub eth_params: EthereumConnectionParams, /// Ethereum signing params. pub eth_sign: EthereumSigningParams, /// Ethereum bridge contract address. pub eth_contract_address: Address, - /// Substrate connection params. - pub sub: SubstrateConnectionParams, /// Synchronization parameters. pub sync_params: HeadersSyncParams, /// Metrics parameters. pub metrics_params: Option, -} - -impl Default for SubstrateSyncParams { - fn default() -> Self { - SubstrateSyncParams { - eth: Default::default(), - eth_sign: Default::default(), - // the address 0x731a10897d267e19b34503ad902d0a29173ba4b1 is the address - // of the contract that is deployed by default signer and 0 nonce - eth_contract_address: "731a10897d267e19b34503ad902d0a29173ba4b1" - .parse() - .expect("address is hardcoded, thus valid; qed"), - sub: Default::default(), - sync_params: HeadersSyncParams { - max_future_headers_to_download: MAX_FUTURE_HEADERS_TO_DOWNLOAD, - max_headers_in_submitted_status: MAX_SUBMITTED_HEADERS, - // since we always have single Substrate header in separate Ethereum transaction, - // all max_**_in_single_submit aren't important here - max_headers_in_single_submit: 4, - max_headers_size_in_single_submit: std::usize::MAX, - prune_depth: PRUNE_DEPTH, - target_tx_mode: TargetTransactionMode::Signed, - }, - metrics_params: Some(Default::default()), - } - } + /// Instance of the bridge pallet being synchronized. + pub instance: Box, } /// Substrate client as headers source. @@ -199,21 +181,29 @@ impl TargetClient for EthereumHeadersTarget { /// Run Substrate headers synchronization. pub fn run(params: SubstrateSyncParams) -> Result<(), RpcError> { - let sub_params = params.clone(); + let SubstrateSyncParams { + sub_params, + eth_params, + eth_sign, + eth_contract_address, + sync_params, + metrics_params, + instance, + } = params; - let eth_client = EthereumRpcClient::new(params.eth); - let sub_client = async_std::task::block_on(async { SubstrateRpcClient::new(sub_params.sub).await })?; + let eth_client = EthereumRpcClient::new(eth_params); + let sub_client = async_std::task::block_on(async { SubstrateRpcClient::new(sub_params, instance).await })?; - let target = EthereumHeadersTarget::new(eth_client, params.eth_contract_address, params.eth_sign); + let target = EthereumHeadersTarget::new(eth_client, eth_contract_address, eth_sign); let source = SubstrateHeadersSource::new(sub_client); crate::sync_loop::run( source, - SUBSTRATE_TICK_INTERVAL, + consts::SUBSTRATE_TICK_INTERVAL, target, - ETHEREUM_TICK_INTERVAL, - params.sync_params, - params.metrics_params, + consts::ETHEREUM_TICK_INTERVAL, + sync_params, + metrics_params, futures::future::pending(), ); diff --git a/bridges/relays/ethereum/src/sync.rs b/bridges/relays/ethereum/src/sync.rs index b1799bf2d2..9ff7a0733f 100644 --- a/bridges/relays/ethereum/src/sync.rs +++ b/bridges/relays/ethereum/src/sync.rs @@ -36,6 +36,38 @@ pub struct HeadersSyncParams { pub target_tx_mode: TargetTransactionMode, } +impl HeadersSyncParams { + /// Default parameters for syncing Ethereum headers. + pub fn ethereum_sync_default() -> Self { + use crate::ethereum_sync_loop::consts::*; + + Self { + max_future_headers_to_download: MAX_FUTURE_HEADERS_TO_DOWNLOAD, + max_headers_in_submitted_status: MAX_SUBMITTED_HEADERS, + max_headers_in_single_submit: MAX_HEADERS_IN_SINGLE_SUBMIT, + max_headers_size_in_single_submit: MAX_HEADERS_SIZE_IN_SINGLE_SUBMIT, + prune_depth: PRUNE_DEPTH, + target_tx_mode: TargetTransactionMode::Signed, + } + } + + /// Default parameters for syncing Substrate headers. + pub fn substrate_sync_default() -> Self { + use crate::substrate_sync_loop::consts::*; + + Self { + max_future_headers_to_download: MAX_FUTURE_HEADERS_TO_DOWNLOAD, + max_headers_in_submitted_status: MAX_SUBMITTED_HEADERS, + // since we always have single Substrate header in separate Ethereum transaction, + // all max_**_in_single_submit aren't important here + max_headers_in_single_submit: 4, + max_headers_size_in_single_submit: std::usize::MAX, + prune_depth: PRUNE_DEPTH, + target_tx_mode: TargetTransactionMode::Signed, + } + } +} + /// Target transaction mode. #[derive(Debug, PartialEq, Clone)] pub enum TargetTransactionMode {