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
This commit is contained in:
Svyatoslav Nikolsky
2020-09-22 11:43:04 +03:00
committed by Bastian Köcher
parent d614cdaba8
commit ec34870eab
20 changed files with 556 additions and 384 deletions
@@ -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?)
}
}
@@ -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(),
}
}
}
+48
View File
@@ -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,
}
}
}
+53
View File
@@ -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;
}
}
@@ -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(&params.signer.secret().as_fixed_bytes().into(), &params.chain_id);
let _ = client.submit_transaction(raw_transaction).await?;
Ok(())
}
@@ -0,0 +1,80 @@
// 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/>.
//! Common types that are used in relay <-> Ethereum node communications.
use headers_relay::sync_types::SourceHeader;
pub use web3::types::{Address, Bytes, CallRequest, H256, U128, U256, U64};
/// When header is just received from the Ethereum node, we check that it has
/// 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;
/// Ethereum transaction type.
pub type Transaction = web3::types::Transaction;
/// Ethereum header type.
pub type Header = web3::types::Block<H256>;
/// Ethereum header type used in headers sync.
#[derive(Clone, Debug, PartialEq)]
pub struct SyncHeader(Header);
impl std::ops::Deref for SyncHeader {
type Target = Header;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Ethereum header with transactions type.
pub type HeaderWithTransactions = web3::types::Block<Transaction>;
/// Ethereum transaction receipt type.
pub type Receipt = web3::types::TransactionReceipt;
/// Ethereum header ID.
pub type HeaderId = relay_utils::HeaderId<H256, u64>;
/// A raw Ethereum transaction that's been signed.
pub type SignedRawTx = Vec<u8>;
impl From<Header> for SyncHeader {
fn from(header: Header) -> Self {
Self(header)
}
}
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) -> HeaderId {
relay_utils::HeaderId(self.number.expect(HEADER_ID_PROOF).as_u64() - 1, self.parent_hash)
}
}