From ec34870eabebf450292e026c57bebd49edf58481 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Tue, 22 Sep 2020 11:43:04 +0300 Subject: [PATCH] Extract minimal ethereum client (#359) * relay-ethereum-client * use relay-ethereum-client from ethereum-poa-relay * cargo fmt --all * #![warn(missing_docs)] * EthereumRpcClient -> EthereumClient * make EthereumHeadersSyncPipeline private * return concrete type from crate::new * cleanup dependencies * *self -> self * remove trait Client * sort deps --- bridges/relays/ethereum-client/Cargo.toml | 17 ++ bridges/relays/ethereum-client/src/client.rs | 142 ++++++++++++ bridges/relays/ethereum-client/src/error.rs | 71 ++++++ bridges/relays/ethereum-client/src/lib.rs | 48 ++++ bridges/relays/ethereum-client/src/rpc.rs | 53 +++++ bridges/relays/ethereum-client/src/sign.rs | 85 ++++++++ .../src/types.rs} | 54 ++--- bridges/relays/ethereum/Cargo.toml | 3 +- .../relays/ethereum/src/ethereum_client.rs | 206 ++---------------- .../ethereum/src/ethereum_deploy_contract.rs | 11 +- .../relays/ethereum/src/ethereum_exchange.rs | 26 ++- .../ethereum/src/ethereum_exchange_submit.rs | 10 +- .../relays/ethereum/src/ethereum_sync_loop.rs | 65 ++++-- bridges/relays/ethereum/src/instances.rs | 2 +- bridges/relays/ethereum/src/main.rs | 23 +- bridges/relays/ethereum/src/rpc.rs | 60 +---- bridges/relays/ethereum/src/rpc_errors.rs | 39 +--- .../relays/ethereum/src/substrate_client.rs | 3 +- .../ethereum/src/substrate_sync_loop.rs | 15 +- .../relays/ethereum/src/substrate_types.rs | 7 +- 20 files changed, 556 insertions(+), 384 deletions(-) create mode 100644 bridges/relays/ethereum-client/Cargo.toml create mode 100644 bridges/relays/ethereum-client/src/client.rs create mode 100644 bridges/relays/ethereum-client/src/error.rs create mode 100644 bridges/relays/ethereum-client/src/lib.rs create mode 100644 bridges/relays/ethereum-client/src/rpc.rs create mode 100644 bridges/relays/ethereum-client/src/sign.rs rename bridges/relays/{ethereum/src/ethereum_types.rs => ethereum-client/src/types.rs} (57%) diff --git a/bridges/relays/ethereum-client/Cargo.toml b/bridges/relays/ethereum-client/Cargo.toml new file mode 100644 index 00000000000..4923b8a111f --- /dev/null +++ b/bridges/relays/ethereum-client/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "relay-ethereum-client" +version = "0.1.0" +authors = ["Parity Technologies <admin@parity.io>"] +edition = "2018" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "1.3.4" } +ethereum-tx-sign = "3.0" +headers-relay = { path = "../headers-relay" } +hex = "0.4" +jsonrpsee = { git = "https://github.com/svyatonik/jsonrpsee.git", branch = "shared-client-in-rpc-api", default-features = false, features = ["http"] } +log = "0.4.11" +parity-crypto = { version = "0.6", features = ["publickey"] } +relay-utils = { path = "../utils" } +web3 = "0.13" diff --git a/bridges/relays/ethereum-client/src/client.rs b/bridges/relays/ethereum-client/src/client.rs new file mode 100644 index 00000000000..0042b13c6ef --- /dev/null +++ b/bridges/relays/ethereum-client/src/client.rs @@ -0,0 +1,142 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. + +use crate::rpc::Ethereum; +use crate::types::{ + Address, Bytes, CallRequest, Header, HeaderWithTransactions, Receipt, SignedRawTx, Transaction, TransactionHash, + H256, U256, +}; +use crate::{ConnectionParams, Error, Result}; + +use jsonrpsee::raw::RawClient; +use jsonrpsee::transport::http::HttpTransportClient; +use jsonrpsee::Client as RpcClient; + +/// The client used to interact with an Ethereum node through RPC. +#[derive(Clone)] +pub struct Client { + client: RpcClient, +} + +impl Client { + /// Create a new Ethereum RPC Client. + pub fn new(params: ConnectionParams) -> Self { + let uri = format!("http://{}:{}", params.host, params.port); + let transport = HttpTransportClient::new(&uri); + let raw_client = RawClient::new(transport); + let client: RpcClient = raw_client.into(); + + Self { client } + } +} + +impl Client { + /// Estimate gas usage for the given call. + pub async fn estimate_gas(&self, call_request: CallRequest) -> Result<U256> { + Ok(Ethereum::estimate_gas(&self.client, call_request).await?) + } + + /// Retrieve number of the best known block from the Ethereum node. + pub async fn best_block_number(&self) -> Result<u64> { + Ok(Ethereum::block_number(&self.client).await?.as_u64()) + } + + /// Retrieve number of the best known block from the Ethereum node. + pub async fn header_by_number(&self, block_number: u64) -> Result<Header> { + let get_full_tx_objects = false; + let header = Ethereum::get_block_by_number(&self.client, block_number, get_full_tx_objects).await?; + match header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some() { + true => Ok(header), + false => Err(Error::IncompleteHeader), + } + } + + /// Retrieve block header by its hash from Ethereum node. + pub async fn header_by_hash(&self, hash: H256) -> Result<Header> { + let get_full_tx_objects = false; + let header = Ethereum::get_block_by_hash(&self.client, hash, get_full_tx_objects).await?; + match header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some() { + true => Ok(header), + false => Err(Error::IncompleteHeader), + } + } + + /// Retrieve block header and its transactions by its number from Ethereum node. + pub async fn header_by_number_with_transactions(&self, number: u64) -> Result<HeaderWithTransactions> { + let get_full_tx_objects = true; + let header = Ethereum::get_block_by_number_with_transactions(&self.client, number, get_full_tx_objects).await?; + + let is_complete_header = header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some(); + if !is_complete_header { + return Err(Error::IncompleteHeader); + } + + let is_complete_transactions = header.transactions.iter().all(|tx| tx.raw.is_some()); + if !is_complete_transactions { + return Err(Error::IncompleteTransaction); + } + + Ok(header) + } + + /// Retrieve block header and its transactions by its hash from Ethereum node. + pub async fn header_by_hash_with_transactions(&self, hash: H256) -> Result<HeaderWithTransactions> { + let get_full_tx_objects = true; + let header = Ethereum::get_block_by_hash_with_transactions(&self.client, hash, get_full_tx_objects).await?; + + let is_complete_header = header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some(); + if !is_complete_header { + return Err(Error::IncompleteHeader); + } + + let is_complete_transactions = header.transactions.iter().all(|tx| tx.raw.is_some()); + if !is_complete_transactions { + return Err(Error::IncompleteTransaction); + } + + Ok(header) + } + + /// Retrieve transaction by its hash from Ethereum node. + pub async fn transaction_by_hash(&self, hash: H256) -> Result<Option<Transaction>> { + Ok(Ethereum::transaction_by_hash(&self.client, hash).await?) + } + + /// Retrieve transaction receipt by transaction hash. + pub async fn transaction_receipt(&self, transaction_hash: H256) -> Result<Receipt> { + Ok(Ethereum::get_transaction_receipt(&self.client, transaction_hash).await?) + } + + /// Get the nonce of the given account. + pub async fn account_nonce(&self, address: Address) -> Result<U256> { + Ok(Ethereum::get_transaction_count(&self.client, address).await?) + } + + /// Submit an Ethereum transaction. + /// + /// The transaction must already be signed before sending it through this method. + pub async fn submit_transaction(&self, signed_raw_tx: SignedRawTx) -> Result<TransactionHash> { + let transaction = Bytes(signed_raw_tx); + let tx_hash = Ethereum::submit_transaction(&self.client, transaction).await?; + log::trace!(target: "bridge", "Sent transaction to Ethereum node: {:?}", tx_hash); + Ok(tx_hash) + } + + /// Call Ethereum smart contract. + pub async fn eth_call(&self, call_transaction: CallRequest) -> Result<Bytes> { + Ok(Ethereum::call(&self.client, call_transaction).await?) + } +} diff --git a/bridges/relays/ethereum-client/src/error.rs b/bridges/relays/ethereum-client/src/error.rs new file mode 100644 index 00000000000..b02e5fecf58 --- /dev/null +++ b/bridges/relays/ethereum-client/src/error.rs @@ -0,0 +1,71 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. + +//! Ethereum node RPC errors. + +use jsonrpsee::client::RequestError; +use relay_utils::MaybeConnectionError; + +/// Result type used by Ethereum client. +pub type Result<T> = std::result::Result<T, Error>; + +/// Errors that can occur only when interacting with +/// an Ethereum node through RPC. +#[derive(Debug)] +pub enum Error { + /// An error that can occur when making an HTTP request to + /// an JSON-RPC client. + Request(RequestError), + /// Failed to parse response. + ResponseParseFailed(String), + /// We have received a header with missing fields. + IncompleteHeader, + /// We have received a transaction missing a `raw` field. + IncompleteTransaction, + /// An invalid Substrate block number was received from + /// an Ethereum node. + InvalidSubstrateBlockNumber, + /// An invalid index has been received from an Ethereum node. + InvalidIncompleteIndex, +} + +impl From<RequestError> for Error { + fn from(error: RequestError) -> Self { + Error::Request(error) + } +} + +impl MaybeConnectionError for Error { + fn is_connection_error(&self) -> bool { + matches!(*self, Error::Request(RequestError::TransportError(_))) + } +} + +impl ToString for Error { + fn to_string(&self) -> String { + match self { + Self::Request(e) => e.to_string(), + Self::ResponseParseFailed(e) => e.to_string(), + Self::IncompleteHeader => { + "Incomplete Ethereum Header Received (missing some of required fields - hash, number, logs_bloom)" + .to_string() + } + Self::IncompleteTransaction => "Incomplete Ethereum Transaction (missing required field - raw)".to_string(), + Self::InvalidSubstrateBlockNumber => "Received an invalid Substrate block from Ethereum Node".to_string(), + Self::InvalidIncompleteIndex => "Received an invalid incomplete index from Ethereum Node".to_string(), + } + } +} diff --git a/bridges/relays/ethereum-client/src/lib.rs b/bridges/relays/ethereum-client/src/lib.rs new file mode 100644 index 00000000000..8c5a00e01b4 --- /dev/null +++ b/bridges/relays/ethereum-client/src/lib.rs @@ -0,0 +1,48 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. + +//! Tools to interact with (Open) Ethereum node using RPC methods. + +#![warn(missing_docs)] + +mod client; +mod error; +mod rpc; +mod sign; + +pub use crate::client::Client; +pub use crate::error::{Error, Result}; +pub use crate::sign::{sign_and_submit_transaction, SigningParams}; + +pub mod types; + +/// Ethereum connection params. +#[derive(Debug, Clone)] +pub struct ConnectionParams { + /// Ethereum RPC host. + pub host: String, + /// Ethereum RPC port. + pub port: u16, +} + +impl Default for ConnectionParams { + fn default() -> Self { + ConnectionParams { + host: "localhost".into(), + port: 8545, + } + } +} diff --git a/bridges/relays/ethereum-client/src/rpc.rs b/bridges/relays/ethereum-client/src/rpc.rs new file mode 100644 index 00000000000..9739d3edbe2 --- /dev/null +++ b/bridges/relays/ethereum-client/src/rpc.rs @@ -0,0 +1,53 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. + +//! Ethereum node RPC interface. + +// The compiler doesn't think we're using the +// code from rpc_api! +#![allow(dead_code)] +#![allow(unused_variables)] + +use crate::types::{ + Address, Bytes, CallRequest, Header, HeaderWithTransactions, Receipt, Transaction, TransactionHash, H256, U256, U64, +}; + +jsonrpsee::rpc_api! { + pub(crate) Ethereum { + #[rpc(method = "eth_estimateGas", positional_params)] + fn estimate_gas(call_request: CallRequest) -> U256; + #[rpc(method = "eth_blockNumber", positional_params)] + fn block_number() -> U64; + #[rpc(method = "eth_getBlockByNumber", positional_params)] + fn get_block_by_number(block_number: U64, full_tx_objs: bool) -> Header; + #[rpc(method = "eth_getBlockByHash", positional_params)] + fn get_block_by_hash(hash: H256, full_tx_objs: bool) -> Header; + #[rpc(method = "eth_getBlockByNumber", positional_params)] + fn get_block_by_number_with_transactions(number: U64, full_tx_objs: bool) -> HeaderWithTransactions; + #[rpc(method = "eth_getBlockByHash", positional_params)] + fn get_block_by_hash_with_transactions(hash: H256, full_tx_objs: bool) -> HeaderWithTransactions; + #[rpc(method = "eth_getTransactionByHash", positional_params)] + fn transaction_by_hash(hash: H256) -> Option<Transaction>; + #[rpc(method = "eth_getTransactionReceipt", positional_params)] + fn get_transaction_receipt(transaction_hash: H256) -> Receipt; + #[rpc(method = "eth_getTransactionCount", positional_params)] + fn get_transaction_count(address: Address) -> U256; + #[rpc(method = "eth_submitTransaction", positional_params)] + fn submit_transaction(transaction: Bytes) -> TransactionHash; + #[rpc(method = "eth_call", positional_params)] + fn call(transaction_call: CallRequest) -> Bytes; + } +} diff --git a/bridges/relays/ethereum-client/src/sign.rs b/bridges/relays/ethereum-client/src/sign.rs new file mode 100644 index 00000000000..f5b80a34e5a --- /dev/null +++ b/bridges/relays/ethereum-client/src/sign.rs @@ -0,0 +1,85 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. + +use crate::types::{Address, CallRequest, U256}; +use crate::{Client, Result}; + +use parity_crypto::publickey::KeyPair; + +/// Ethereum signing params. +#[derive(Clone, Debug)] +pub struct SigningParams { + /// Ethereum chain id. + pub chain_id: u64, + /// Ethereum transactions signer. + pub signer: KeyPair, + /// Gas price we agree to pay. + pub gas_price: U256, +} + +impl Default for SigningParams { + fn default() -> Self { + SigningParams { + chain_id: 0x11, // Parity dev chain + // account that has a lot of ether when we run instant seal engine + // address: 0x00a329c0648769a73afac7f9381e08fb43dbea72 + // secret: 0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7 + signer: KeyPair::from_secret_slice( + &hex::decode("4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7") + .expect("secret is hardcoded, thus valid; qed"), + ) + .expect("secret is hardcoded, thus valid; qed"), + gas_price: 8_000_000_000u64.into(), // 8 Gwei + } + } +} + +/// Sign and submit tranaction using given Ethereum client. +pub async fn sign_and_submit_transaction( + client: &Client, + params: &SigningParams, + contract_address: Option<Address>, + nonce: Option<U256>, + double_gas: bool, + encoded_call: Vec<u8>, +) -> Result<()> { + let nonce = if let Some(n) = nonce { + n + } else { + let address: Address = params.signer.address().as_fixed_bytes().into(); + client.account_nonce(address).await? + }; + + let call_request = CallRequest { + to: contract_address, + data: Some(encoded_call.clone().into()), + ..Default::default() + }; + let gas = client.estimate_gas(call_request).await?; + + let raw_transaction = ethereum_tx_sign::RawTransaction { + nonce, + to: contract_address, + value: U256::zero(), + gas: if double_gas { gas.saturating_mul(2.into()) } else { gas }, + gas_price: params.gas_price, + data: encoded_call, + } + .sign(¶ms.signer.secret().as_fixed_bytes().into(), ¶ms.chain_id); + + let _ = client.submit_transaction(raw_transaction).await?; + Ok(()) +} diff --git a/bridges/relays/ethereum/src/ethereum_types.rs b/bridges/relays/ethereum-client/src/types.rs similarity index 57% rename from bridges/relays/ethereum/src/ethereum_types.rs rename to bridges/relays/ethereum-client/src/types.rs index bb780d69680..f64362ade0e 100644 --- a/bridges/relays/ethereum/src/ethereum_types.rs +++ b/bridges/relays/ethereum-client/src/types.rs @@ -14,11 +14,9 @@ // 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/>. -use crate::substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts}; +//! Common types that are used in relay <-> Ethereum node communications. -use codec::Encode; -use headers_relay::sync_types::{HeadersSyncPipeline, QueuedHeader, SourceHeader}; -use relay_utils::HeaderId; +use headers_relay::sync_types::SourceHeader; pub use web3::types::{Address, Bytes, CallRequest, H256, U128, U256, U64}; @@ -26,6 +24,9 @@ pub use web3::types::{Address, Bytes, CallRequest, H256, U128, U256, U64}; /// both number and hash fields filled. pub const HEADER_ID_PROOF: &str = "checked on retrieval; qed"; +/// Ethereum transaction hash type. +pub type HeaderHash = H256; + /// Ethereum transaction hash type. pub type TransactionHash = H256; @@ -37,10 +38,11 @@ pub type Header = web3::types::Block<H256>; /// Ethereum header type used in headers sync. #[derive(Clone, Debug, PartialEq)] -pub struct EthereumSyncHeader(Header); +pub struct SyncHeader(Header); -impl std::ops::Deref for EthereumSyncHeader { +impl std::ops::Deref for SyncHeader { type Target = Header; + fn deref(&self) -> &Self::Target { &self.0 } @@ -53,52 +55,26 @@ pub type HeaderWithTransactions = web3::types::Block<Transaction>; pub type Receipt = web3::types::TransactionReceipt; /// Ethereum header ID. -pub type EthereumHeaderId = HeaderId<H256, u64>; - -/// Queued ethereum header ID. -pub type QueuedEthereumHeader = QueuedHeader<EthereumHeadersSyncPipeline>; +pub type HeaderId = relay_utils::HeaderId<H256, u64>; /// A raw Ethereum transaction that's been signed. pub type SignedRawTx = Vec<u8>; -/// Ethereum synchronization pipeline. -#[derive(Clone, Copy, Debug)] -#[cfg_attr(test, derive(PartialEq))] -pub struct EthereumHeadersSyncPipeline; - -impl HeadersSyncPipeline for EthereumHeadersSyncPipeline { - const SOURCE_NAME: &'static str = "Ethereum"; - const TARGET_NAME: &'static str = "Substrate"; - - type Hash = H256; - type Number = u64; - type Header = EthereumSyncHeader; - type Extra = Vec<Receipt>; - type Completion = (); - - fn estimate_size(source: &QueuedHeader<Self>) -> usize { - into_substrate_ethereum_header(source.header()).encode().len() - + into_substrate_ethereum_receipts(source.extra()) - .map(|extra| extra.encode().len()) - .unwrap_or(0) - } -} - -impl From<Header> for EthereumSyncHeader { +impl From<Header> for SyncHeader { fn from(header: Header) -> Self { Self(header) } } -impl SourceHeader<H256, u64> for EthereumSyncHeader { - fn id(&self) -> EthereumHeaderId { - HeaderId( +impl SourceHeader<H256, u64> for SyncHeader { + fn id(&self) -> HeaderId { + relay_utils::HeaderId( self.number.expect(HEADER_ID_PROOF).as_u64(), self.hash.expect(HEADER_ID_PROOF), ) } - fn parent_id(&self) -> EthereumHeaderId { - HeaderId(self.number.expect(HEADER_ID_PROOF).as_u64() - 1, self.parent_hash) + fn parent_id(&self) -> HeaderId { + relay_utils::HeaderId(self.number.expect(HEADER_ID_PROOF).as_u64() - 1, self.parent_hash) } } diff --git a/bridges/relays/ethereum/Cargo.toml b/bridges/relays/ethereum/Cargo.toml index 1bedea7aeda..ab6872dabf2 100644 --- a/bridges/relays/ethereum/Cargo.toml +++ b/bridges/relays/ethereum/Cargo.toml @@ -17,7 +17,6 @@ env_logger = "0.7.0" ethabi = "12.0" ethabi-contract = "11.0" ethabi-derive = "12.0" -ethereum-tx-sign = "3.0" exchange-relay = { path = "../exchange-relay" } futures = "0.3.5" headers-relay = { path = "../headers-relay" } @@ -27,11 +26,11 @@ log = "0.4.11" messages-relay = { path = "../messages-relay" } num-traits = "0.2" parity-crypto = { version = "0.6", features = ["publickey"] } +relay-ethereum-client = { path = "../ethereum-client" } relay-utils = { path = "../utils" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.57" time = "0.2" -web3 = "0.13" [dependencies.jsonrpsee] git = "https://github.com/svyatonik/jsonrpsee.git" diff --git a/bridges/relays/ethereum/src/ethereum_client.rs b/bridges/relays/ethereum/src/ethereum_client.rs index 5635197d640..d9357c560b0 100644 --- a/bridges/relays/ethereum/src/ethereum_client.rs +++ b/bridges/relays/ethereum/src/ethereum_client.rs @@ -14,22 +14,18 @@ // 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/>. -use crate::ethereum_types::{ - Address, Bytes, CallRequest, EthereumHeaderId, Header, HeaderWithTransactions, Receipt, SignedRawTx, Transaction, - TransactionHash, H256, U256, -}; -use crate::rpc::{Ethereum, EthereumRpc}; -use crate::rpc_errors::{EthereumNodeError, RpcError}; +use crate::rpc_errors::RpcError; use crate::substrate_types::{GrandpaJustification, Hash as SubstrateHash, QueuedSubstrateHeader, SubstrateHeaderId}; use async_trait::async_trait; use codec::{Decode, Encode}; use ethabi::FunctionOutputDecoder; use headers_relay::sync_types::SubmittedHeaders; -use jsonrpsee::raw::RawClient; -use jsonrpsee::transport::http::HttpTransportClient; -use jsonrpsee::Client; -use parity_crypto::publickey::KeyPair; +use relay_ethereum_client::{ + sign_and_submit_transaction, + types::{Address, CallRequest, HeaderId as EthereumHeaderId, Receipt, H256, U256}, + Client as EthereumClient, Error as EthereumNodeError, SigningParams as EthereumSigningParams, +}; use relay_utils::{HeaderId, MaybeConnectionError}; use std::collections::HashSet; @@ -38,159 +34,10 @@ ethabi_contract::use_contract!(bridge_contract, "res/substrate-bridge-abi.json") type RpcResult<T> = std::result::Result<T, RpcError>; -/// Ethereum connection params. -#[derive(Debug, Clone)] -pub struct EthereumConnectionParams { - /// Ethereum RPC host. - pub host: String, - /// Ethereum RPC port. - pub port: u16, -} - -impl Default for EthereumConnectionParams { - fn default() -> Self { - EthereumConnectionParams { - host: "localhost".into(), - port: 8545, - } - } -} - -/// Ethereum signing params. -#[derive(Clone, Debug)] -pub struct EthereumSigningParams { - /// Ethereum chain id. - pub chain_id: u64, - /// Ethereum transactions signer. - pub signer: KeyPair, - /// Gas price we agree to pay. - pub gas_price: U256, -} - -impl Default for EthereumSigningParams { - fn default() -> Self { - EthereumSigningParams { - chain_id: 0x11, // Parity dev chain - // account that has a lot of ether when we run instant seal engine - // address: 0x00a329c0648769a73afac7f9381e08fb43dbea72 - // secret: 0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7 - signer: KeyPair::from_secret_slice( - &hex::decode("4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7") - .expect("secret is hardcoded, thus valid; qed"), - ) - .expect("secret is hardcoded, thus valid; qed"), - gas_price: 8_000_000_000u64.into(), // 8 Gwei - } - } -} - -/// The client used to interact with an Ethereum node through RPC. -pub struct EthereumRpcClient { - client: Client, -} - -impl EthereumRpcClient { - /// Create a new Ethereum RPC Client. - pub fn new(params: EthereumConnectionParams) -> Self { - let uri = format!("http://{}:{}", params.host, params.port); - let transport = HttpTransportClient::new(&uri); - let raw_client = RawClient::new(transport); - let client: Client = raw_client.into(); - - Self { client } - } -} - -#[async_trait] -impl EthereumRpc for EthereumRpcClient { - async fn estimate_gas(&self, call_request: CallRequest) -> RpcResult<U256> { - Ok(Ethereum::estimate_gas(&self.client, call_request).await?) - } - - async fn best_block_number(&self) -> RpcResult<u64> { - Ok(Ethereum::block_number(&self.client).await?.as_u64()) - } - - async fn header_by_number(&self, block_number: u64) -> RpcResult<Header> { - let get_full_tx_objects = false; - let header = Ethereum::get_block_by_number(&self.client, block_number, get_full_tx_objects).await?; - match header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some() { - true => Ok(header), - false => Err(RpcError::Ethereum(EthereumNodeError::IncompleteHeader)), - } - } - - async fn header_by_hash(&self, hash: H256) -> RpcResult<Header> { - let get_full_tx_objects = false; - let header = Ethereum::get_block_by_hash(&self.client, hash, get_full_tx_objects).await?; - match header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some() { - true => Ok(header), - false => Err(RpcError::Ethereum(EthereumNodeError::IncompleteHeader)), - } - } - - async fn header_by_number_with_transactions(&self, number: u64) -> RpcResult<HeaderWithTransactions> { - let get_full_tx_objects = true; - let header = Ethereum::get_block_by_number_with_transactions(&self.client, number, get_full_tx_objects).await?; - - let is_complete_header = header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some(); - if !is_complete_header { - return Err(RpcError::Ethereum(EthereumNodeError::IncompleteHeader)); - } - - let is_complete_transactions = header.transactions.iter().all(|tx| tx.raw.is_some()); - if !is_complete_transactions { - return Err(RpcError::Ethereum(EthereumNodeError::IncompleteTransaction)); - } - - Ok(header) - } - - async fn header_by_hash_with_transactions(&self, hash: H256) -> RpcResult<HeaderWithTransactions> { - let get_full_tx_objects = true; - let header = Ethereum::get_block_by_hash_with_transactions(&self.client, hash, get_full_tx_objects).await?; - - let is_complete_header = header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some(); - if !is_complete_header { - return Err(RpcError::Ethereum(EthereumNodeError::IncompleteHeader)); - } - - let is_complete_transactions = header.transactions.iter().all(|tx| tx.raw.is_some()); - if !is_complete_transactions { - return Err(RpcError::Ethereum(EthereumNodeError::IncompleteTransaction)); - } - - Ok(header) - } - - async fn transaction_by_hash(&self, hash: H256) -> RpcResult<Option<Transaction>> { - Ok(Ethereum::transaction_by_hash(&self.client, hash).await?) - } - - async fn transaction_receipt(&self, transaction_hash: H256) -> RpcResult<Receipt> { - Ok(Ethereum::get_transaction_receipt(&self.client, transaction_hash).await?) - } - - async fn account_nonce(&self, address: Address) -> RpcResult<U256> { - Ok(Ethereum::get_transaction_count(&self.client, address).await?) - } - - async fn submit_transaction(&self, signed_raw_tx: SignedRawTx) -> RpcResult<TransactionHash> { - let transaction = Bytes(signed_raw_tx); - let tx_hash = Ethereum::submit_transaction(&self.client, transaction).await?; - log::trace!(target: "bridge", "Sent transaction to Ethereum node: {:?}", tx_hash); - Ok(tx_hash) - } - - async fn eth_call(&self, call_transaction: CallRequest) -> RpcResult<Bytes> { - Ok(Ethereum::call(&self.client, call_transaction).await?) - } -} - /// A trait which contains methods that work by using multiple low-level RPCs, or more complicated /// interactions involving, for example, an Ethereum contract. #[async_trait] -pub trait EthereumHighLevelRpc: EthereumRpc { +pub trait EthereumHighLevelRpc { /// Returns best Substrate block that PoA chain knows of. async fn best_substrate_block(&self, contract_address: Address) -> RpcResult<SubstrateHeaderId>; @@ -240,7 +87,7 @@ pub trait EthereumHighLevelRpc: EthereumRpc { } #[async_trait] -impl EthereumHighLevelRpc for EthereumRpcClient { +impl EthereumHighLevelRpc for EthereumClient { async fn best_substrate_block(&self, contract_address: Address) -> RpcResult<SubstrateHeaderId> { let (encoded_call, call_decoder) = bridge_contract::functions::best_known_header::call(); let call_request = CallRequest { @@ -293,7 +140,7 @@ impl EthereumHighLevelRpc for EthereumRpcClient { submitted: Vec::new(), incomplete: Vec::new(), rejected: headers.iter().rev().map(|header| header.id()).collect(), - fatal_error: Some(error), + fatal_error: Some(error.into()), } } }; @@ -302,9 +149,7 @@ impl EthereumHighLevelRpc for EthereumRpcClient { // cloning `jsonrpsee::Client` only clones reference to background threads submit_substrate_headers( EthereumHeadersSubmitter { - client: EthereumRpcClient { - client: self.client.clone(), - }, + client: self.clone(), params, contract_address, nonce, @@ -369,32 +214,9 @@ impl EthereumHighLevelRpc for EthereumRpcClient { double_gas: bool, encoded_call: Vec<u8>, ) -> RpcResult<()> { - let nonce = if let Some(n) = nonce { - n - } else { - let address: Address = params.signer.address().as_fixed_bytes().into(); - self.account_nonce(address).await? - }; - - let call_request = CallRequest { - to: contract_address, - data: Some(encoded_call.clone().into()), - ..Default::default() - }; - let gas = self.estimate_gas(call_request).await?; - - let raw_transaction = ethereum_tx_sign::RawTransaction { - nonce, - to: contract_address, - value: U256::zero(), - gas: if double_gas { gas.saturating_mul(2.into()) } else { gas }, - gas_price: params.gas_price, - data: encoded_call, - } - .sign(¶ms.signer.secret().as_fixed_bytes().into(), ¶ms.chain_id); - - let _ = self.submit_transaction(raw_transaction).await?; - Ok(()) + sign_and_submit_transaction(self, params, contract_address, nonce, double_gas, encoded_call) + .await + .map_err(Into::into) } async fn transaction_receipts( @@ -524,7 +346,7 @@ trait HeadersSubmitter { /// Implementation of Substrate headers submitter that sends headers to running Ethereum node. struct EthereumHeadersSubmitter { - client: EthereumRpcClient, + client: EthereumClient, params: EthereumSigningParams, contract_address: Address, nonce: U256, diff --git a/bridges/relays/ethereum/src/ethereum_deploy_contract.rs b/bridges/relays/ethereum/src/ethereum_deploy_contract.rs index b04632fc7ea..17f12ba45eb 100644 --- a/bridges/relays/ethereum/src/ethereum_deploy_contract.rs +++ b/bridges/relays/ethereum/src/ethereum_deploy_contract.rs @@ -14,9 +14,7 @@ // 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/>. -use crate::ethereum_client::{ - bridge_contract, EthereumConnectionParams, EthereumHighLevelRpc, EthereumRpcClient, EthereumSigningParams, -}; +use crate::ethereum_client::{bridge_contract, EthereumHighLevelRpc}; use crate::instances::BridgeInstance; use crate::rpc::SubstrateRpc; use crate::substrate_client::{SubstrateConnectionParams, SubstrateRpcClient}; @@ -24,6 +22,9 @@ use crate::substrate_types::{Hash as SubstrateHash, Header as SubstrateHeader, S use codec::{Decode, Encode}; use num_traits::Zero; +use relay_ethereum_client::{ + Client as EthereumClient, ConnectionParams as EthereumConnectionParams, SigningParams as EthereumSigningParams, +}; use relay_utils::HeaderId; /// Ethereum synchronization parameters. @@ -63,7 +64,7 @@ pub fn run(params: EthereumDeployContractParams) { } = params; let result = local_pool.run_until(async move { - let eth_client = EthereumRpcClient::new(eth_params); + let eth_client = EthereumClient::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?; @@ -137,7 +138,7 @@ async fn prepare_initial_authorities_set( /// Deploy bridge contract to Ethereum chain. async fn deploy_bridge_contract( - eth_client: &EthereumRpcClient, + eth_client: &EthereumClient, params: &EthereumSigningParams, contract_code: Vec<u8>, initial_header: Vec<u8>, diff --git a/bridges/relays/ethereum/src/ethereum_exchange.rs b/bridges/relays/ethereum/src/ethereum_exchange.rs index e5a821bf3a0..59c9a42ca68 100644 --- a/bridges/relays/ethereum/src/ethereum_exchange.rs +++ b/bridges/relays/ethereum/src/ethereum_exchange.rs @@ -16,13 +16,8 @@ //! Relaying proofs of PoA -> Substrate exchange transactions. -use crate::ethereum_client::{EthereumConnectionParams, EthereumRpcClient}; -use crate::ethereum_types::{ - EthereumHeaderId, HeaderWithTransactions as EthereumHeaderWithTransactions, Transaction as EthereumTransaction, - TransactionHash as EthereumTransactionHash, H256, -}; use crate::instances::BridgeInstance; -use crate::rpc::{EthereumRpc, SubstrateRpc}; +use crate::rpc::SubstrateRpc; use crate::rpc_errors::RpcError; use crate::substrate_client::{ SubmitEthereumExchangeTransactionProof, SubstrateConnectionParams, SubstrateRpcClient, SubstrateSigningParams, @@ -36,6 +31,13 @@ use exchange_relay::exchange::{ TransactionProofPipeline, }; use exchange_relay::exchange_loop::{run as run_loop, InMemoryStorage}; +use relay_ethereum_client::{ + types::{ + HeaderId as EthereumHeaderId, HeaderWithTransactions as EthereumHeaderWithTransactions, + Transaction as EthereumTransaction, TransactionHash as EthereumTransactionHash, H256, HEADER_ID_PROOF, + }, + Client as EthereumClient, ConnectionParams as EthereumConnectionParams, +}; use relay_utils::{metrics::MetricsParams, HeaderId}; use rialto_runtime::exchange::EthereumTransactionInclusionProof; use std::time::Duration; @@ -92,8 +94,8 @@ impl SourceBlock for EthereumSourceBlock { fn id(&self) -> EthereumHeaderId { HeaderId( - self.0.number.expect(crate::ethereum_types::HEADER_ID_PROOF).as_u64(), - self.0.hash.expect(crate::ethereum_types::HEADER_ID_PROOF), + self.0.number.expect(HEADER_ID_PROOF).as_u64(), + self.0.hash.expect(HEADER_ID_PROOF), ) } @@ -120,7 +122,7 @@ impl SourceTransaction for EthereumSourceTransaction { /// Ethereum node as transactions proof source. struct EthereumTransactionsSource { - client: EthereumRpcClient, + client: EthereumClient, } #[async_trait] @@ -136,6 +138,7 @@ impl SourceClient<EthereumToSubstrateExchange> for EthereumTransactionsSource { .header_by_hash_with_transactions(hash) .await .map(EthereumSourceBlock) + .map_err(Into::into) } async fn block_by_number(&self, number: u64) -> Result<EthereumSourceBlock, Self::Error> { @@ -143,6 +146,7 @@ impl SourceClient<EthereumToSubstrateExchange> for EthereumTransactionsSource { .header_by_number_with_transactions(number) .await .map(EthereumSourceBlock) + .map_err(Into::into) } async fn transaction_block( @@ -278,7 +282,7 @@ fn run_single_transaction_relay(params: EthereumExchangeParams, eth_tx_hash: H25 } = params; let result = local_pool.run_until(async move { - let eth_client = EthereumRpcClient::new(eth_params); + let eth_client = EthereumClient::new(eth_params); let sub_client = SubstrateRpcClient::new(sub_params, instance).await?; let source = EthereumTransactionsSource { client: eth_client }; @@ -321,7 +325,7 @@ fn run_auto_transactions_relay_loop(params: EthereumExchangeParams, eth_start_wi } = params; let do_run_loop = move || -> Result<(), String> { - let eth_client = EthereumRpcClient::new(eth_params); + let eth_client = EthereumClient::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))?; diff --git a/bridges/relays/ethereum/src/ethereum_exchange_submit.rs b/bridges/relays/ethereum/src/ethereum_exchange_submit.rs index 4309b6e894d..519fdba8cb9 100644 --- a/bridges/relays/ethereum/src/ethereum_exchange_submit.rs +++ b/bridges/relays/ethereum/src/ethereum_exchange_submit.rs @@ -16,14 +16,14 @@ //! Submitting Ethereum -> Substrate exchange transactions. -use crate::ethereum_client::{EthereumConnectionParams, EthereumRpcClient, EthereumSigningParams}; -use crate::ethereum_types::{CallRequest, U256}; -use crate::rpc::EthereumRpc; - use bp_eth_poa::{ signatures::{SecretKey, SignTransaction}, UnsignedTransaction, }; +use relay_ethereum_client::{ + types::{CallRequest, U256}, + Client as EthereumClient, ConnectionParams as EthereumConnectionParams, SigningParams as EthereumSigningParams, +}; use rialto_runtime::exchange::LOCK_FUNDS_ADDRESS; /// Ethereum exchange transaction params. @@ -54,7 +54,7 @@ pub fn run(params: EthereumExchangeSubmitParams) { } = params; let result: Result<_, String> = local_pool.run_until(async move { - let eth_client = EthereumRpcClient::new(eth_params); + let eth_client = EthereumClient::new(eth_params); let eth_signer_address = eth_sign.signer.address(); let sub_recipient_encoded = sub_recipient; diff --git a/bridges/relays/ethereum/src/ethereum_sync_loop.rs b/bridges/relays/ethereum/src/ethereum_sync_loop.rs index 10779845cbf..d5a4ec9b8e1 100644 --- a/bridges/relays/ethereum/src/ethereum_sync_loop.rs +++ b/bridges/relays/ethereum/src/ethereum_sync_loop.rs @@ -16,26 +16,27 @@ //! Ethereum PoA -> Substrate synchronization. -use crate::ethereum_client::{EthereumConnectionParams, EthereumHighLevelRpc, EthereumRpcClient}; -use crate::ethereum_types::{ - EthereumHeaderId, EthereumHeadersSyncPipeline, EthereumSyncHeader as Header, QueuedEthereumHeader, Receipt, -}; +use crate::ethereum_client::EthereumHighLevelRpc; use crate::instances::BridgeInstance; -use crate::rpc::{EthereumRpc, SubstrateRpc}; +use crate::rpc::SubstrateRpc; use crate::rpc_errors::RpcError; use crate::substrate_client::{ SubmitEthereumHeaders, SubstrateConnectionParams, SubstrateRpcClient, SubstrateSigningParams, }; -use crate::substrate_types::into_substrate_ethereum_header; +use crate::substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts}; use async_trait::async_trait; +use codec::Encode; use headers_relay::{ sync::{HeadersSyncParams, TargetTransactionMode}, sync_loop::{SourceClient, TargetClient}, - sync_types::{SourceHeader, SubmittedHeaders}, + sync_types::{HeadersSyncPipeline, QueuedHeader, SourceHeader, SubmittedHeaders}, +}; +use relay_ethereum_client::{ + types::{HeaderHash, HeaderId as EthereumHeaderId, Receipt, SyncHeader as Header}, + Client as EthereumClient, ConnectionParams as EthereumConnectionParams, }; use relay_utils::metrics::MetricsParams; -use web3::types::H256; use std::fmt::Debug; use std::{collections::HashSet, time::Duration}; @@ -78,14 +79,40 @@ pub struct EthereumSyncParams { pub instance: Box<dyn BridgeInstance>, } +/// Ethereum synchronization pipeline. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(test, derive(PartialEq))] +pub struct EthereumHeadersSyncPipeline; + +impl HeadersSyncPipeline for EthereumHeadersSyncPipeline { + const SOURCE_NAME: &'static str = "Ethereum"; + const TARGET_NAME: &'static str = "Substrate"; + + type Hash = HeaderHash; + type Number = u64; + type Header = Header; + type Extra = Vec<Receipt>; + type Completion = (); + + fn estimate_size(source: &QueuedHeader<Self>) -> usize { + into_substrate_ethereum_header(source.header()).encode().len() + + into_substrate_ethereum_receipts(source.extra()) + .map(|extra| extra.encode().len()) + .unwrap_or(0) + } +} + +/// Queued ethereum header ID. +pub type QueuedEthereumHeader = QueuedHeader<EthereumHeadersSyncPipeline>; + /// Ethereum client as headers source. struct EthereumHeadersSource { /// Ethereum node client. - client: EthereumRpcClient, + client: EthereumClient, } impl EthereumHeadersSource { - fn new(client: EthereumRpcClient) -> Self { + fn new(client: EthereumClient) -> Self { Self { client } } } @@ -95,15 +122,23 @@ impl SourceClient<EthereumHeadersSyncPipeline> for EthereumHeadersSource { type Error = RpcError; async fn best_block_number(&self) -> Result<u64, Self::Error> { - self.client.best_block_number().await + self.client.best_block_number().await.map_err(Into::into) } - async fn header_by_hash(&self, hash: H256) -> Result<Header, Self::Error> { - self.client.header_by_hash(hash).await.map(Into::into) + async fn header_by_hash(&self, hash: HeaderHash) -> Result<Header, Self::Error> { + self.client + .header_by_hash(hash) + .await + .map(Into::into) + .map_err(Into::into) } async fn header_by_number(&self, number: u64) -> Result<Header, Self::Error> { - self.client.header_by_number(number).await.map(Into::into) + self.client + .header_by_number(number) + .await + .map(Into::into) + .map_err(Into::into) } async fn header_completion(&self, id: EthereumHeaderId) -> Result<(EthereumHeaderId, Option<()>), Self::Error> { @@ -192,7 +227,7 @@ pub fn run(params: EthereumSyncParams) -> Result<(), RpcError> { instance, } = params; - let eth_client = EthereumRpcClient::new(eth_params); + let eth_client = EthereumClient::new(eth_params); let sub_client = async_std::task::block_on(async { SubstrateRpcClient::new(sub_params, instance).await })?; let sign_sub_transactions = match sync_params.target_tx_mode { diff --git a/bridges/relays/ethereum/src/instances.rs b/bridges/relays/ethereum/src/instances.rs index 6fa44740e89..d2f06c25043 100644 --- a/bridges/relays/ethereum/src/instances.rs +++ b/bridges/relays/ethereum/src/instances.rs @@ -23,7 +23,7 @@ //! //! This module helps by preparing the correct `Call`s for each of the different pallet instances. -use crate::ethereum_types::QueuedEthereumHeader; +use crate::ethereum_sync_loop::QueuedEthereumHeader; use crate::substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts}; use rialto_runtime::exchange::EthereumTransactionInclusionProof as Proof; diff --git a/bridges/relays/ethereum/src/main.rs b/bridges/relays/ethereum/src/main.rs index 85deab4f1ad..14400c630f9 100644 --- a/bridges/relays/ethereum/src/main.rs +++ b/bridges/relays/ethereum/src/main.rs @@ -21,7 +21,6 @@ mod ethereum_deploy_contract; mod ethereum_exchange; mod ethereum_exchange_submit; mod ethereum_sync_loop; -mod ethereum_types; mod instances; mod rpc; mod rpc_errors; @@ -29,7 +28,6 @@ mod substrate_client; mod substrate_sync_loop; mod substrate_types; -use ethereum_client::{EthereumConnectionParams, EthereumSigningParams}; use ethereum_deploy_contract::EthereumDeployContractParams; use ethereum_exchange::EthereumExchangeParams; use ethereum_exchange_submit::EthereumExchangeSubmitParams; @@ -44,6 +42,7 @@ use substrate_client::{SubstrateConnectionParams, SubstrateSigningParams}; use substrate_sync_loop::SubstrateSyncParams; use headers_relay::sync::HeadersSyncParams; +use relay_ethereum_client::{ConnectionParams as EthereumConnectionParams, SigningParams as EthereumSigningParams}; use std::io::Write; fn main() { @@ -250,13 +249,14 @@ fn ethereum_sync_params(matches: &clap::ArgMatches) -> Result<EthereumSyncParams fn substrate_sync_params(matches: &clap::ArgMatches) -> Result<SubstrateSyncParams, String> { use crate::substrate_sync_loop::consts::*; - 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() - .expect("address is hardcoded, thus valid; qed") - }; + let eth_contract_address: relay_ethereum_client::types::Address = + if let Some(eth_contract) = matches.value_of("eth-contract") { + eth_contract.parse().map_err(|e| format!("{}", e))? + } else { + "731a10897d267e19b34503ad902d0a29173ba4b1" + .parse() + .expect("address is hardcoded, thus valid; qed") + }; let params = SubstrateSyncParams { sub_params: substrate_connection_params(matches)?, @@ -313,7 +313,10 @@ fn ethereum_deploy_contract_params(matches: &clap::ArgMatches) -> Result<Ethereu 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(ð_nonce).map_err(|e| format!("Failed to parse eth-nonce: {}", e))?) + Some( + relay_ethereum_client::types::U256::from_dec_str(ð_nonce) + .map_err(|e| format!("Failed to parse eth-nonce: {}", e))?, + ) } else { None }; diff --git a/bridges/relays/ethereum/src/rpc.rs b/bridges/relays/ethereum/src/rpc.rs index 64b70093f12..ee463e38cec 100644 --- a/bridges/relays/ethereum/src/rpc.rs +++ b/bridges/relays/ethereum/src/rpc.rs @@ -23,11 +23,6 @@ #![allow(unused_variables)] use std::result; -use crate::ethereum_types::{ - Address as EthAddress, Bytes, CallRequest, EthereumHeaderId, Header as EthereumHeader, - HeaderWithTransactions as EthereumHeaderWithTransactions, Receipt, SignedRawTx, Transaction as EthereumTransaction, - TransactionHash as EthereumTxHash, H256, U256, U64, -}; use crate::rpc_errors::RpcError; use crate::substrate_types::{ Hash as SubstrateHash, Header as SubstrateHeader, Number as SubBlockNumber, SignedBlock as SubstrateBlock, @@ -35,36 +30,12 @@ use crate::substrate_types::{ use async_trait::async_trait; use bp_eth_poa::AuraHeader as SubstrateEthereumHeader; +use relay_ethereum_client::types::{Bytes, HeaderId as EthereumHeaderId}; type Result<T> = result::Result<T, RpcError>; type GrandpaAuthorityList = Vec<u8>; jsonrpsee::rpc_api! { - pub(crate) Ethereum { - #[rpc(method = "eth_estimateGas", positional_params)] - fn estimate_gas(call_request: CallRequest) -> U256; - #[rpc(method = "eth_blockNumber", positional_params)] - fn block_number() -> U64; - #[rpc(method = "eth_getBlockByNumber", positional_params)] - fn get_block_by_number(block_number: U64, full_tx_objs: bool) -> EthereumHeader; - #[rpc(method = "eth_getBlockByHash", positional_params)] - fn get_block_by_hash(hash: H256, full_tx_objs: bool) -> EthereumHeader; - #[rpc(method = "eth_getBlockByNumber", positional_params)] - fn get_block_by_number_with_transactions(number: U64, full_tx_objs: bool) -> EthereumHeaderWithTransactions; - #[rpc(method = "eth_getBlockByHash", positional_params)] - fn get_block_by_hash_with_transactions(hash: H256, full_tx_objs: bool) -> EthereumHeaderWithTransactions; - #[rpc(method = "eth_getTransactionByHash", positional_params)] - fn transaction_by_hash(hash: H256) -> Option<EthereumTransaction>; - #[rpc(method = "eth_getTransactionReceipt", positional_params)] - fn get_transaction_receipt(transaction_hash: H256) -> Receipt; - #[rpc(method = "eth_getTransactionCount", positional_params)] - fn get_transaction_count(address: EthAddress) -> U256; - #[rpc(method = "eth_submitTransaction", positional_params)] - fn submit_transaction(transaction: Bytes) -> EthereumTxHash; - #[rpc(method = "eth_call", positional_params)] - fn call(transaction_call: CallRequest) -> Bytes; - } - pub(crate) Substrate { #[rpc(method = "chain_getHeader", positional_params)] fn chain_get_header(block_hash: Option<SubstrateHash>) -> SubstrateHeader; @@ -81,35 +52,6 @@ jsonrpsee::rpc_api! { } } -/// The API for the supported Ethereum RPC methods. -#[async_trait] -pub trait EthereumRpc { - /// Estimate gas usage for the given call. - async fn estimate_gas(&self, call_request: CallRequest) -> Result<U256>; - /// Retrieve number of the best known block from the Ethereum node. - async fn best_block_number(&self) -> Result<u64>; - /// Retrieve block header by its number from Ethereum node. - async fn header_by_number(&self, block_number: u64) -> Result<EthereumHeader>; - /// Retrieve block header by its hash from Ethereum node. - async fn header_by_hash(&self, hash: H256) -> Result<EthereumHeader>; - /// Retrieve block header and its transactions by its number from Ethereum node. - async fn header_by_number_with_transactions(&self, block_number: u64) -> Result<EthereumHeaderWithTransactions>; - /// Retrieve block header and its transactions by its hash from Ethereum node. - async fn header_by_hash_with_transactions(&self, hash: H256) -> Result<EthereumHeaderWithTransactions>; - /// Retrieve transaction by its hash from Ethereum node. - async fn transaction_by_hash(&self, hash: H256) -> Result<Option<EthereumTransaction>>; - /// Retrieve transaction receipt by transaction hash. - async fn transaction_receipt(&self, transaction_hash: H256) -> Result<Receipt>; - /// Get the nonce of the given account. - async fn account_nonce(&self, address: EthAddress) -> Result<U256>; - /// Submit an Ethereum transaction. - /// - /// The transaction must already be signed before sending it through this method. - async fn submit_transaction(&self, signed_raw_tx: SignedRawTx) -> Result<EthereumTxHash>; - /// Submit a call to an Ethereum smart contract. - async fn eth_call(&self, call_transaction: CallRequest) -> Result<Bytes>; -} - /// The API for the supported Substrate RPC methods. #[async_trait] pub trait SubstrateRpc { diff --git a/bridges/relays/ethereum/src/rpc_errors.rs b/bridges/relays/ethereum/src/rpc_errors.rs index 747ec151f1e..5e01031968f 100644 --- a/bridges/relays/ethereum/src/rpc_errors.rs +++ b/bridges/relays/ethereum/src/rpc_errors.rs @@ -15,6 +15,7 @@ // along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>. use jsonrpsee::client::RequestError; +use relay_ethereum_client::Error as EthereumNodeError; use relay_utils::MaybeConnectionError; /// Contains common errors that can occur when @@ -76,7 +77,11 @@ impl From<ethabi::Error> for RpcError { impl MaybeConnectionError for RpcError { fn is_connection_error(&self) -> bool { - matches!(*self, RpcError::Request(RequestError::TransportError(_))) + match self { + RpcError::Request(RequestError::TransportError(_)) => true, + RpcError::Ethereum(ref error) => error.is_connection_error(), + _ => false, + } } } @@ -86,38 +91,6 @@ impl From<codec::Error> for RpcError { } } -/// Errors that can occur only when interacting with -/// an Ethereum node through RPC. -#[derive(Debug)] -pub enum EthereumNodeError { - /// Failed to parse response. - ResponseParseFailed(String), - /// We have received a header with missing fields. - IncompleteHeader, - /// We have received a transaction missing a `raw` field. - IncompleteTransaction, - /// An invalid Substrate block number was received from - /// an Ethereum node. - InvalidSubstrateBlockNumber, - /// An invalid index has been received from an Ethereum node. - InvalidIncompleteIndex, -} - -impl ToString for EthereumNodeError { - fn to_string(&self) -> String { - match self { - Self::ResponseParseFailed(e) => e.to_string(), - Self::IncompleteHeader => { - "Incomplete Ethereum Header Received (missing some of required fields - hash, number, logs_bloom)" - .to_string() - } - Self::IncompleteTransaction => "Incomplete Ethereum Transaction (missing required field - raw)".to_string(), - Self::InvalidSubstrateBlockNumber => "Received an invalid Substrate block from Ethereum Node".to_string(), - Self::InvalidIncompleteIndex => "Received an invalid incomplete index from Ethereum Node".to_string(), - } - } -} - /// Errors that can occur only when interacting with /// a Substrate node through RPC. #[derive(Debug)] diff --git a/bridges/relays/ethereum/src/substrate_client.rs b/bridges/relays/ethereum/src/substrate_client.rs index aadde0ec14f..4b1765aa2be 100644 --- a/bridges/relays/ethereum/src/substrate_client.rs +++ b/bridges/relays/ethereum/src/substrate_client.rs @@ -14,7 +14,7 @@ // 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/>. -use crate::ethereum_types::{Bytes, EthereumHeaderId, QueuedEthereumHeader, H256}; +use crate::ethereum_sync_loop::QueuedEthereumHeader; use crate::instances::BridgeInstance; use crate::rpc::{Substrate, SubstrateRpc}; use crate::rpc_errors::RpcError; @@ -28,6 +28,7 @@ use jsonrpsee::raw::RawClient; use jsonrpsee::transport::http::HttpTransportClient; use jsonrpsee::Client; use num_traits::Zero; +use relay_ethereum_client::types::{Bytes, HeaderId as EthereumHeaderId, H256}; use relay_utils::HeaderId; use sp_core::crypto::Pair; use sp_runtime::traits::IdentifyAccount; diff --git a/bridges/relays/ethereum/src/substrate_sync_loop.rs b/bridges/relays/ethereum/src/substrate_sync_loop.rs index ce3a5ae4d31..8c3e0fb91d1 100644 --- a/bridges/relays/ethereum/src/substrate_sync_loop.rs +++ b/bridges/relays/ethereum/src/substrate_sync_loop.rs @@ -16,10 +16,7 @@ //! Substrate -> Ethereum synchronization. -use crate::ethereum_client::{ - EthereumConnectionParams, EthereumHighLevelRpc, EthereumRpcClient, EthereumSigningParams, -}; -use crate::ethereum_types::Address; +use crate::ethereum_client::EthereumHighLevelRpc; use crate::instances::BridgeInstance; use crate::rpc::SubstrateRpc; use crate::rpc_errors::RpcError; @@ -35,6 +32,10 @@ use headers_relay::{ sync_loop::{SourceClient, TargetClient}, sync_types::{SourceHeader, SubmittedHeaders}, }; +use relay_ethereum_client::{ + types::Address, Client as EthereumClient, ConnectionParams as EthereumConnectionParams, + SigningParams as EthereumSigningParams, +}; use relay_utils::metrics::MetricsParams; use std::fmt::Debug; @@ -125,7 +126,7 @@ impl SourceClient<SubstrateHeadersSyncPipeline> for SubstrateHeadersSource { /// Ethereum client as Substrate headers target. struct EthereumHeadersTarget { /// Ethereum node client. - client: EthereumRpcClient, + client: EthereumClient, /// Bridge contract address. contract: Address, /// Ethereum signing params. @@ -133,7 +134,7 @@ struct EthereumHeadersTarget { } impl EthereumHeadersTarget { - fn new(client: EthereumRpcClient, contract: Address, sign_params: EthereumSigningParams) -> Self { + fn new(client: EthereumClient, contract: Address, sign_params: EthereumSigningParams) -> Self { Self { client, contract, @@ -194,7 +195,7 @@ pub fn run(params: SubstrateSyncParams) -> Result<(), RpcError> { instance, } = params; - let eth_client = EthereumRpcClient::new(eth_params); + let eth_client = EthereumClient::new(eth_params); let sub_client = async_std::task::block_on(async { SubstrateRpcClient::new(sub_params, instance).await })?; let target = EthereumHeadersTarget::new(eth_client, eth_contract_address, eth_sign); diff --git a/bridges/relays/ethereum/src/substrate_types.rs b/bridges/relays/ethereum/src/substrate_types.rs index 4f5328b7787..793358be03a 100644 --- a/bridges/relays/ethereum/src/substrate_types.rs +++ b/bridges/relays/ethereum/src/substrate_types.rs @@ -14,10 +14,6 @@ // 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/>. -use crate::ethereum_types::{ - Header as EthereumHeader, Receipt as EthereumReceipt, HEADER_ID_PROOF as ETHEREUM_HEADER_ID_PROOF, -}; - use codec::Encode; use headers_relay::sync_types::{HeadersSyncPipeline, QueuedHeader, SourceHeader}; use relay_utils::HeaderId; @@ -26,6 +22,9 @@ pub use bp_eth_poa::{ Address, AuraHeader as SubstrateEthereumHeader, Bloom, Bytes, LogEntry as SubstrateEthereumLogEntry, Receipt as SubstrateEthereumReceipt, TransactionOutcome as SubstrateEthereumTransactionOutcome, H256, U256, }; +use relay_ethereum_client::types::{ + Header as EthereumHeader, Receipt as EthereumReceipt, HEADER_ID_PROOF as ETHEREUM_HEADER_ID_PROOF, +}; /// Substrate header hash. pub type Hash = rialto_runtime::Hash; -- GitLab