// 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 . // Copyright 2019 Parity Technologies (UK) Ltd. // This file is part of Parity-Bridge. // Parity-Bridge 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-Bridge 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-Bridge. If not, see . use jsonrpsee_core::{client::ClientError, common::Params}; use jsonrpsee_http::{HttpClient, RequestError, http_client}; use serde::de::DeserializeOwned; use serde_json::{from_value, to_value}; use crate::ethereum_sync_loop::MaybeConnectionError; use crate::ethereum_types::{H256, Header, HeaderId, Receipt, U64}; /// 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 client type. pub type Client = HttpClient; /// All possible errors that can occur during interacting with Ethereum node. #[derive(Debug)] pub enum Error { /// Request start failed. StartRequestFailed(RequestError), /// Request not found (should never occur?). RequestNotFound, /// Failed to receive response. ResponseRetrievalFailed(ClientError), /// Failed to parse response. ResponseParseFailed(serde_json::Error), /// We have received header with missing number and hash fields. IncompleteHeader, /// We have received receipt with missing gas_used field. IncompleteReceipt, } 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(uri: &str) -> Client { http_client(uri) } /// 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() { 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_none() && header.hash.is_none() { true => Ok(header), false => Err(Error::IncompleteHeader), })) } /// Retrieve transactions receipts for given block. pub async fn transactions_receipts( mut client: Client, id: HeaderId, transacactions: Vec, ) -> (Client, Result<(HeaderId, Vec), Error>) { let mut transactions_receipts = Vec::with_capacity(transacactions.len()); for transacaction in transacactions { let (next_client, transaction_receipt) = transaction_receipt(client, transacaction).await; let transaction_receipt = match transaction_receipt { Ok(transaction_receipt) => transaction_receipt, Err(error) => return (next_client, Err(error)), }; 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), } })) } /// 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(Error::ResponseParseFailed) } let result = do_call_rpc(&mut client, method, params).await; (client, result) }