// 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 . use crate::ethereum_types::{ Address, Bytes, CallRequest, EthereumHeaderId, Header, Receipt, TransactionHash, H256, U256, U64, }; use crate::substrate_types::{GrandpaJustification, Hash as SubstrateHash, QueuedSubstrateHeader, SubstrateHeaderId}; use crate::sync_types::{HeaderId, MaybeConnectionError}; use crate::{bail_on_arg_error, bail_on_error}; use codec::{Decode, Encode}; use ethabi::FunctionOutputDecoder; use jsonrpsee::common::Params; use jsonrpsee::raw::{RawClient, RawClientError}; use jsonrpsee::transport::http::{HttpTransportClient, RequestError}; use parity_crypto::publickey::KeyPair; use serde::de::DeserializeOwned; use serde_json::{from_value, to_value}; use std::collections::HashSet; // to encode/decode contract calls ethabi_contract::use_contract!(bridge_contract, "res/substrate-bridge-abi.json"); /// Proof of hash serialization success. const HASH_SERIALIZATION_PROOF: &'static str = "hash serialization never fails; qed"; /// Proof of integer serialization success. const INT_SERIALIZATION_PROOF: &'static str = "integer serialization never fails; qed"; /// Proof of bool serialization success. const BOOL_SERIALIZATION_PROOF: &'static str = "bool serialization never fails; qed"; /// Ethereum connection params. #[derive(Debug)] 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 } } } /// Ethereum client type. pub type Client = RawClient; /// All possible errors that can occur during interacting with Ethereum node. #[derive(Debug)] pub enum Error { /// Request start failed. StartRequestFailed(RequestError), /// Error serializing request. RequestSerialization(serde_json::Error), /// Request not found (should never occur?). RequestNotFound, /// Failed to receive response. ResponseRetrievalFailed(RawClientError), /// Failed to parse response. ResponseParseFailed(String), /// We have received header with missing number and hash fields. IncompleteHeader, /// We have received receipt with missing gas_used field. IncompleteReceipt, /// Invalid Substrate block number received from Ethereum node. InvalidSubstrateBlockNumber, } impl MaybeConnectionError for Error { fn is_connection_error(&self) -> bool { match *self { Error::StartRequestFailed(_) | Error::ResponseRetrievalFailed(_) => true, _ => false, } } } /// Returns client that is able to call RPCs on Ethereum node. pub fn client(params: EthereumConnectionParams) -> Client { let uri = format!("http://{}:{}", params.host, params.port); let transport = HttpTransportClient::new(&uri); RawClient::new(transport) } /// Retrieve best known block number from Ethereum node. pub async fn best_block_number(client: Client) -> (Client, Result) { let (client, result) = call_rpc::(client, "eth_blockNumber", Params::None).await; (client, result.map(|x| x.as_u64())) } /// Retrieve block header by its number from Ethereum node. pub async fn header_by_number(client: Client, number: u64) -> (Client, Result) { let (client, header) = call_rpc( client, "eth_getBlockByNumber", Params::Array(vec![ to_value(U64::from(number)).expect(INT_SERIALIZATION_PROOF), to_value(false).expect(BOOL_SERIALIZATION_PROOF), ]), ) .await; ( client, header.and_then(|header: Header| { 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(client: Client, hash: H256) -> (Client, Result) { let (client, header) = call_rpc( client, "eth_getBlockByHash", Params::Array(vec![ to_value(hash).expect(HASH_SERIALIZATION_PROOF), to_value(false).expect(BOOL_SERIALIZATION_PROOF), ]), ) .await; ( client, header.and_then(|header: Header| { match header.number.is_some() && header.hash.is_some() && header.logs_bloom.is_some() { true => Ok(header), false => Err(Error::IncompleteHeader), } }), ) } /// Retrieve transactions receipts for given block. pub async fn transactions_receipts( mut client: Client, id: EthereumHeaderId, transactions: Vec, ) -> (Client, Result<(EthereumHeaderId, Vec), Error>) { let mut transactions_receipts = Vec::with_capacity(transactions.len()); for transaction in transactions { let (next_client, transaction_receipt) = bail_on_error!(transaction_receipt(client, transaction).await); transactions_receipts.push(transaction_receipt); client = next_client; } (client, Ok((id, transactions_receipts))) } /// Retrieve transaction receipt by transaction hash. async fn transaction_receipt(client: Client, hash: H256) -> (Client, Result) { let (client, receipt) = call_rpc::( client, "eth_getTransactionReceipt", Params::Array(vec![to_value(hash).expect(HASH_SERIALIZATION_PROOF)]), ) .await; ( client, receipt.and_then(|receipt| match receipt.gas_used.is_some() { true => Ok(receipt), false => Err(Error::IncompleteReceipt), }), ) } /// Returns best Substrate block that PoA chain knows of. pub async fn best_substrate_block( client: Client, contract_address: Address, ) -> (Client, Result) { let (encoded_call, call_decoder) = bridge_contract::functions::best_known_header::call(); let call_request = bail_on_arg_error!( to_value(CallRequest { to: Some(contract_address), data: Some(encoded_call.into()), ..Default::default() }) .map_err(|e| Error::RequestSerialization(e)), client ); let (client, call_result) = bail_on_error!(call_rpc::(client, "eth_call", Params::Array(vec![call_request]),).await); let (number, raw_hash) = match call_decoder.decode(&call_result.0) { Ok((raw_number, raw_hash)) => (raw_number, raw_hash), Err(error) => return (client, Err(Error::ResponseParseFailed(format!("{}", error)))), }; let hash = match SubstrateHash::decode(&mut &raw_hash[..]) { Ok(hash) => hash, Err(error) => return (client, Err(Error::ResponseParseFailed(format!("{}", error)))), }; if number != number.low_u32().into() { return (client, Err(Error::InvalidSubstrateBlockNumber)); } (client, Ok(HeaderId(number.low_u32(), hash))) } /// Returns true if Substrate header is known to Ethereum node. pub async fn substrate_header_known( client: Client, contract_address: Address, id: SubstrateHeaderId, ) -> (Client, Result<(SubstrateHeaderId, bool), Error>) { let (encoded_call, call_decoder) = bridge_contract::functions::is_known_header::call(id.1); let call_request = bail_on_arg_error!( to_value(CallRequest { to: Some(contract_address), data: Some(encoded_call.into()), ..Default::default() }) .map_err(|e| Error::RequestSerialization(e)), client ); let (client, call_result) = bail_on_error!(call_rpc::(client, "eth_call", Params::Array(vec![call_request]),).await); match call_decoder.decode(&call_result.0) { Ok(is_known_block) => (client, Ok((id, is_known_block))), Err(error) => (client, Err(Error::ResponseParseFailed(format!("{}", error)))), } } /// Submits Substrate headers to Ethereum contract. pub async fn submit_substrate_headers( client: Client, params: EthereumSigningParams, contract_address: Address, headers: Vec, ) -> (Client, Result, Error>) { let (mut client, mut nonce) = bail_on_error!(account_nonce(client, params.signer.address().as_fixed_bytes().into()).await); let ids = headers.iter().map(|header| header.id()).collect(); for header in headers { client = bail_on_error!( submit_ethereum_transaction( client, ¶ms, Some(contract_address), Some(nonce), false, bridge_contract::functions::import_header::encode_input(header.header().encode(),), ) .await ) .0; nonce += 1.into(); } (client, Ok(ids)) } /// Returns ids of incomplete Substrate headers. pub async fn incomplete_substrate_headers( client: Client, contract_address: Address, ) -> (Client, Result, Error>) { let (encoded_call, call_decoder) = bridge_contract::functions::incomplete_headers::call(); let call_request = bail_on_arg_error!( to_value(CallRequest { to: Some(contract_address), data: Some(encoded_call.into()), ..Default::default() }) .map_err(|e| Error::RequestSerialization(e)), client ); let (client, call_result) = bail_on_error!(call_rpc::(client, "eth_call", Params::Array(vec![call_request]),).await); match call_decoder.decode(&call_result.0) { Ok((incomplete_headers_numbers, incomplete_headers_hashes)) => ( client, Ok(incomplete_headers_numbers .into_iter() .zip(incomplete_headers_hashes) .filter_map(|(number, hash)| { if number != number.low_u32().into() { return None; } Some(HeaderId(number.low_u32(), hash)) }) .collect()), ), Err(error) => (client, Err(Error::ResponseParseFailed(format!("{}", error)))), } } /// Complete Substrate header. pub async fn complete_substrate_header( client: Client, params: EthereumSigningParams, contract_address: Address, id: SubstrateHeaderId, justification: GrandpaJustification, ) -> (Client, Result) { let (client, _) = bail_on_error!( submit_ethereum_transaction( client, ¶ms, Some(contract_address), None, false, bridge_contract::functions::import_finality_proof::encode_input(id.0, id.1, justification,), ) .await ); (client, Ok(id)) } /// Deploy bridge contract. pub async fn deploy_bridge_contract( client: Client, params: &EthereumSigningParams, contract_code: Vec, initial_header: Vec, initial_set_id: u64, initial_authorities: Vec, ) -> (Client, Result<(), Error>) { submit_ethereum_transaction( client, params, None, None, false, bridge_contract::constructor(contract_code, initial_header, initial_set_id, initial_authorities), ) .await } /// Submit ethereum transaction. async fn submit_ethereum_transaction( client: Client, params: &EthereumSigningParams, contract_address: Option
, nonce: Option, double_gas: bool, encoded_call: Vec, ) -> (Client, Result<(), Error>) { let (client, nonce) = match nonce { Some(nonce) => (client, nonce), None => bail_on_error!(account_nonce(client, params.signer.address().as_fixed_bytes().into()).await), }; let (client, gas) = bail_on_error!( estimate_gas( client, CallRequest { to: contract_address, data: Some(encoded_call.clone().into()), ..Default::default() } ) .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 transaction = bail_on_arg_error!( to_value(Bytes(raw_transaction)).map_err(|e| Error::RequestSerialization(e)), client ); let (client, _) = bail_on_error!( call_rpc::(client, "eth_submitTransaction", Params::Array(vec![transaction])).await ); (client, Ok(())) } /// Get account nonce. async fn account_nonce(client: Client, caller_address: Address) -> (Client, Result) { let caller_address = bail_on_arg_error!( to_value(caller_address).map_err(|e| Error::RequestSerialization(e)), client ); call_rpc(client, "eth_getTransactionCount", Params::Array(vec![caller_address])).await } /// Estimate gas usage for call. async fn estimate_gas(client: Client, call_request: CallRequest) -> (Client, Result) { let call_request = bail_on_arg_error!( to_value(call_request).map_err(|e| Error::RequestSerialization(e)), client ); call_rpc(client, "eth_estimateGas", Params::Array(vec![call_request])).await } /// Calls RPC on Ethereum node. async fn call_rpc( mut client: Client, method: &'static str, params: Params, ) -> (Client, Result) { async fn do_call_rpc( client: &mut Client, method: &'static str, params: Params, ) -> Result { let request_id = client .start_request(method, params) .await .map_err(Error::StartRequestFailed)?; // WARN: if there'll be need for executing >1 request at a time, we should avoid // calling request_by_id let response = client .request_by_id(request_id) .ok_or(Error::RequestNotFound)? .await .map_err(Error::ResponseRetrievalFailed)?; from_value(response).map_err(|e| Error::ResponseParseFailed(format!("{}", e))) } let result = do_call_rpc(&mut client, method, params).await; (client, result) }