Support Pallet Instances in Relay (#238)

* Sketch out how to support different bridge pallet instances

* Create a common interface for using pallet instances

* Start introducing generic instance parameter

Attemps to make the compiler happy, but I'm having second thoughts about
this approach. Commiting now as a way to have a checkpoint, but I think
I'm going to need to re-consider my approach here.

Ideally I want a change which introduces minimal changes, but this seems
to be propagating around the codebase in ways I don't want.

* Use trait objects instead of generics

* Implement traits for Boxed trait objects

This is done in order to statisfy trait bounds by types
which use these new trait objects

* Remove Clone usage for sync parameters

* Remove implementation of Default for sync params

* Require that BridgeInstance implements Debug

* Ensure that BridgeInstance trait implements Send/Sync

* Add documentation related to instances

* Rust Fmt

* Remove needless format

* Make instance CLI option case insensitive

* Replace `with_*` constructors with `new`

* Clean up usage of instance structs

* Enforce a default instance in the CLI params

* Build sync params as we process input from CLI

* Remove case insensitivity from sub-tx-mode

I think this should happen, but maybe as part of a different PR

* Process default Eth contract deployment config in CLI

* Build EthereumExchangeParams in CLI

* Process EthereumExchangeSubmitParams params in CLI
This commit is contained in:
Hernando Castano
2020-08-04 12:03:21 -04:00
committed by Bastian Köcher
parent 6fd1651b21
commit 7f8360d8ab
10 changed files with 467 additions and 286 deletions
+11
View File
@@ -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.
@@ -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<u8>,
/// Substrate connection params.
pub sub: SubstrateConnectionParams,
pub sub_params: SubstrateConnectionParams,
/// Initial authorities set id.
pub sub_initial_authorities_set_id: Option<u64>,
/// Initial authorities set.
pub sub_initial_authorities_set: Option<Vec<u8>>,
/// Initial header.
pub sub_initial_header: Option<Vec<u8>>,
}
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<dyn BridgeInstance>,
}
/// 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(
&eth_client,
&params.eth_sign,
params.eth_contract_code,
&eth_sign,
eth_contract_code,
initial_header,
initial_set_id,
initial_set,
@@ -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<MetricsParams>,
/// Instance of the bridge pallet being synchronized.
pub instance: Box<dyn BridgeInstance>,
}
/// Ethereum to Substrate exchange pipeline.
@@ -253,18 +256,6 @@ impl TargetClient<EthereumToSubstrateExchange> 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<u64>) {
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(),
);
@@ -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)
@@ -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<MetricsParams>,
}
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<dyn BridgeInstance>,
}
/// Ethereum client as headers source.
@@ -190,26 +179,33 @@ impl TargetClient<EthereumHeadersSyncPipeline> 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(),
);
+115
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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<QueuedEthereumHeader>) -> 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<QueuedEthereumHeader>) -> 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<QueuedEthereumHeader>) -> 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)
}
}
+131 -76
View File
@@ -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<SubstrateSigni
}
fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result<EthereumSyncParams, String> {
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<SubstrateSyncParams, String> {
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<ethereum_deploy_contract::EthereumDeployContractParams, String> {
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(&eth_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<ethereum_exchange_submit::EthereumExchangeSubmitParams, String> {
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(&eth_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<EthereumDeployContractParams, String> {
let eth_contract_code = if let Some(eth_contract_code) = matches.value_of("eth-contract-code") {
hex::decode(&eth_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<EthereumExchangeSubmitParams, String> {
let eth_nonce = if let Some(eth_nonce) = matches.value_of("eth-nonce") {
Some(ethereum_types::U256::from_dec_str(&eth_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<ethereum_exchange::EthereumExchangeParams, String> {
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<EthereumExchangeParams, String> {
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<ethereum_excha
}),
};
let params = EthereumExchangeParams {
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)?,
mode,
};
log::debug!(target: "bridge", "Ethereum exchange params: {:?}", params);
Ok(params)
@@ -352,3 +393,17 @@ fn metrics_params(matches: &clap::ArgMatches) -> Result<Option<metrics::MetricsP
Ok(Some(metrics_params))
}
fn instance_params(matches: &clap::ArgMatches) -> Result<Box<dyn BridgeInstance>, String> {
let instance: Box<dyn BridgeInstance> = 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)
}
+24 -44
View File
@@ -15,12 +15,10 @@
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
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<dyn BridgeInstance>,
}
impl SubstrateRpcClient {
/// Returns client that is able to call RPCs on Substrate node.
pub async fn new(params: SubstrateConnectionParams) -> Result<Self> {
pub async fn new(params: SubstrateConnectionParams, instance: Box<dyn BridgeInstance>) -> Result<Self> {
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, &params.signer, nonce, self.genesis_hash);
let call = self.instance.build_signed_header_call(headers);
let transaction = create_signed_submit_transaction(call, &params.signer, nonce, self.genesis_hash);
let _ = self.submit_extrinsic(Bytes(transaction.encode())).await?;
Ok(())
}
@@ -276,9 +281,13 @@ impl SubmitEthereumHeaders for SubstrateRpcClient {
) -> SubmittedHeaders<EthereumHeaderId, RpcError> {
let mut ids = headers.iter().map(|header| header.id()).collect::<VecDeque<_>>();
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),
),
&params.signer,
nonce,
self.genesis_hash,
);
let call = self.instance.build_currency_exchange_call(proof);
let transaction = create_signed_transaction(call, &params.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<QueuedEthereumHeader>,
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.
@@ -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<MetricsParams>,
}
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<dyn BridgeInstance>,
}
/// Substrate client as headers source.
@@ -199,21 +181,29 @@ impl TargetClient<SubstrateHeadersSyncPipeline> 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(),
);
+32
View File
@@ -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 {