diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index c791caed1..f340b5bcf 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -68,6 +68,13 @@ pub trait LightChainClient: Send + Sync { /// Get the signing network ID. fn signing_network_id(&self) -> Option; + /// Get environment info for execution at a given block. + /// Fails if that block's header is not stored. + fn env_info(&self, id: BlockId) -> Option; + + /// Get a handle to the consensus engine. + fn engine(&self) -> &Arc; + /// Query whether a block is known. fn is_known(&self, hash: &H256) -> bool; @@ -295,6 +302,14 @@ impl LightChainClient for Client { Client::signing_network_id(self) } + fn env_info(&self, id: BlockId) -> Option { + Client::env_info(self, id) + } + + fn engine(&self) -> &Arc { + Client::engine(self) + } + fn is_known(&self, hash: &H256) -> bool { self.status(hash) == BlockStatus::InChain } diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 7a7e940b8..aae6a97f3 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -346,3 +346,8 @@ pub fn deprecated>>(message: T) -> Error { data: message.into().map(Value::String), } } + +// on-demand sender cancelled. +pub fn on_demand_cancel(_cancel: ::futures::sync::oneshot::Canceled) -> Error { + internal("on-demand sender cancelled", "") +} diff --git a/rpc/src/v1/helpers/light_fetch.rs b/rpc/src/v1/helpers/light_fetch.rs new file mode 100644 index 000000000..e2ce6880f --- /dev/null +++ b/rpc/src/v1/helpers/light_fetch.rs @@ -0,0 +1,200 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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 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. If not, see . + +//! Helpers for fetching blockchain data either from the light client or the network. + +use std::sync::Arc; + +use ethcore::basic_account::BasicAccount; +use ethcore::encoded; +use ethcore::executed::{Executed, ExecutionError}; +use ethcore::ids::BlockId; +use ethcore::transaction::{Action, Transaction as EthTransaction}; + +use futures::{future, Future, BoxFuture}; +use jsonrpc_core::Error; +use jsonrpc_macros::Trailing; + +use light::cache::Cache; +use light::client::LightChainClient; +use light::cht; +use light::on_demand::{OnDemand, request}; + +use ethsync::LightSync; +use util::{Address, Mutex, Uint, U256}; + +use v1::helpers::{CallRequest as CRequest, errors, dispatch}; +use v1::types::{BlockNumber, CallRequest}; + +/// Helper for fetching blockchain data either from the light client or the network +/// as necessary. +pub struct LightFetch { + /// The light client. + pub client: Arc, + /// The on-demand request service. + pub on_demand: Arc, + /// Handle to the network. + pub sync: Arc, + /// The light data cache. + pub cache: Arc>, +} + +/// Type alias for convenience. +pub type ExecutionResult = Result; + +impl LightFetch { + /// Get a block header from the on demand service or client, or error. + pub fn header(&self, id: BlockId) -> BoxFuture, Error> { + if let Some(h) = self.client.block_header(id) { + return future::ok(Some(h)).boxed() + } + + let maybe_future = match id { + BlockId::Number(n) => { + let cht_root = cht::block_to_cht_number(n).and_then(|cn| self.client.cht_root(cn as usize)); + match cht_root { + None => return future::ok(None).boxed(), + Some(root) => { + let req = request::HeaderProof::new(n, root) + .expect("only fails for 0; client always stores genesis; client already queried; qed"); + + let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); + self.sync.with_context(|ctx| { + let fut = self.on_demand.hash_by_number(ctx, req) + .map(request::HeaderByHash) + .map_err(errors::on_demand_cancel); + + fut.and_then(move |req| { + match sync.with_context(|ctx| on_demand.header_by_hash(ctx, req)) { + Some(fut) => fut.map_err(errors::on_demand_cancel).boxed(), + None => future::err(errors::network_disabled()).boxed(), + } + }).map(Some).boxed() + }) + } + } + } + BlockId::Hash(h) => { + self.sync.with_context(|ctx| + self.on_demand.header_by_hash(ctx, request::HeaderByHash(h)) + .then(|res| future::done(match res { + Ok(h) => Ok(Some(h)), + Err(e) => Err(errors::on_demand_cancel(e)), + })) + .boxed() + ) + } + _ => None, // latest, earliest, and pending will have all already returned. + }; + + match maybe_future { + Some(recv) => recv, + None => future::err(errors::network_disabled()).boxed() + } + } + + // Get account info at a given block. `None` signifies no such account existing. + pub fn account(&self, address: Address, id: BlockId) -> BoxFuture, Error> { + let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); + + self.header(id).and_then(move |header| { + let header = match header { + None => return future::ok(None).boxed(), + Some(hdr) => hdr, + }; + + sync.with_context(|ctx| on_demand.account(ctx, request::Account { + header: header, + address: address, + })) + .map(|x| x.map_err(errors::on_demand_cancel).boxed()) + .unwrap_or_else(|| future::err(errors::network_disabled()).boxed()) + }).boxed() + } + + /// helper for getting proved execution. + pub fn proved_execution(&self, req: CallRequest, num: Trailing) -> BoxFuture { + const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]); + + let (sync, on_demand, client) = (self.sync.clone(), self.on_demand.clone(), self.client.clone()); + let req: CRequest = req.into(); + let id = num.0.into(); + + let from = req.from.unwrap_or(Address::zero()); + let nonce_fut = match req.nonce { + Some(nonce) => future::ok(Some(nonce)).boxed(), + None => self.account(from, id).map(|acc| acc.map(|a| a.nonce)).boxed(), + }; + + let gas_price_fut = match req.gas_price { + Some(price) => future::ok(price).boxed(), + None => dispatch::fetch_gas_price_corpus( + self.sync.clone(), + self.client.clone(), + self.on_demand.clone(), + self.cache.clone(), + ).map(|corp| match corp.median() { + Some(median) => *median, + None => DEFAULT_GAS_PRICE, + }).boxed() + }; + + // if nonce resolves, this should too since it'll be in the LRU-cache. + let header_fut = self.header(id); + + // fetch missing transaction fields from the network. + nonce_fut.join(gas_price_fut).and_then(move |(nonce, gas_price)| { + let action = req.to.map_or(Action::Create, Action::Call); + let gas = req.gas.unwrap_or(U256::from(10_000_000)); // better gas amount? + let value = req.value.unwrap_or_else(U256::zero); + let data = req.data.map_or_else(Vec::new, |d| d.to_vec()); + + future::done(match nonce { + Some(n) => Ok(EthTransaction { + nonce: n, + action: action, + gas: gas, + gas_price: gas_price, + value: value, + data: data, + }.fake_sign(from)), + None => Err(errors::unknown_block()), + }) + }).join(header_fut).and_then(move |(tx, hdr)| { + // then request proved execution. + // TODO: get last-hashes from network. + let (env_info, hdr) = match (client.env_info(id), hdr) { + (Some(env_info), Some(hdr)) => (env_info, hdr), + _ => return future::err(errors::unknown_block()).boxed(), + }; + let request = request::TransactionProof { + tx: tx, + header: hdr, + env_info: env_info, + engine: client.engine().clone(), + }; + + let proved_future = sync.with_context(move |ctx| { + on_demand.transaction_proof(ctx, request).map_err(errors::on_demand_cancel).boxed() + }); + + match proved_future { + Some(fut) => fut.boxed(), + None => future::err(errors::network_disabled()).boxed(), + } + }).boxed() + } +} diff --git a/rpc/src/v1/helpers/mod.rs b/rpc/src/v1/helpers/mod.rs index 9d2064aa8..839d6b090 100644 --- a/rpc/src/v1/helpers/mod.rs +++ b/rpc/src/v1/helpers/mod.rs @@ -21,6 +21,7 @@ pub mod accounts; pub mod block_import; pub mod dispatch; pub mod fake_sign; +pub mod light_fetch; pub mod informant; pub mod oneshot; diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 73d4d9f9b..d4265233a 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -146,7 +146,7 @@ impl EthClient where (Some(block), Some(total_difficulty)) => { let view = block.header_view(); Ok(Some(RichBlock { - block: Block { + inner: Block { hash: Some(view.sha3().into()), size: Some(block.rlp().as_raw().len().into()), parent_hash: view.parent_hash().into(), @@ -202,7 +202,7 @@ impl EthClient where .map(Into::into); let block = RichBlock { - block: Block { + inner: Block { hash: Some(uncle.hash().into()), size: size, parent_hash: uncle.parent_hash().clone().into(), diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index 251daf90d..6f283af02 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -45,6 +45,7 @@ use futures::sync::oneshot; use v1::helpers::{CallRequest as CRequest, errors, limit_logs, dispatch}; use v1::helpers::block_import::is_major_importing; +use v1::helpers::light_fetch::LightFetch; use v1::traits::Eth; use v1::types::{ RichBlock, Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, @@ -65,12 +66,6 @@ pub struct EthClient { cache: Arc>, } -// helper for internal error: on demand sender cancelled. -fn err_premature_cancel(_cancel: oneshot::Canceled) -> Error { - errors::internal("on-demand sender prematurely cancelled", "") -} - -type ExecutionResult = Result; impl EthClient { /// Create a new `EthClient` with a handle to the light sync instance, client, @@ -93,147 +88,15 @@ impl EthClient { } } - /// Get a block header from the on demand service or client, or error. - fn header(&self, id: BlockId) -> BoxFuture, Error> { - if let Some(h) = self.client.block_header(id) { - return future::ok(Some(h)).boxed() + /// Create a light data fetcher instance. + fn fetcher(&self) -> LightFetch { + LightFetch { + client: self.client.clone(), + on_demand: self.on_demand.clone(), + sync: self.sync.clone(), + cache: self.cache.clone(), + } - - let maybe_future = match id { - BlockId::Number(n) => { - let cht_root = cht::block_to_cht_number(n).and_then(|cn| self.client.cht_root(cn as usize)); - match cht_root { - None => return future::ok(None).boxed(), - Some(root) => { - let req = request::HeaderProof::new(n, root) - .expect("only fails for 0; client always stores genesis; client already queried; qed"); - - let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); - self.sync.with_context(|ctx| { - let fut = self.on_demand.hash_by_number(ctx, req) - .map(request::HeaderByHash) - .map_err(err_premature_cancel); - - fut.and_then(move |req| { - match sync.with_context(|ctx| on_demand.header_by_hash(ctx, req)) { - Some(fut) => fut.map_err(err_premature_cancel).boxed(), - None => future::err(errors::network_disabled()).boxed(), - } - }).map(Some).boxed() - }) - } - } - } - BlockId::Hash(h) => { - self.sync.with_context(|ctx| - self.on_demand.header_by_hash(ctx, request::HeaderByHash(h)) - .then(|res| future::done(match res { - Ok(h) => Ok(Some(h)), - Err(e) => Err(err_premature_cancel(e)), - })) - .boxed() - ) - } - _ => None, // latest, earliest, and pending will have all already returned. - }; - - match maybe_future { - Some(recv) => recv, - None => future::err(errors::network_disabled()).boxed() - } - } - - // helper for getting account info at a given block. - fn account(&self, address: Address, id: BlockId) -> BoxFuture, Error> { - let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); - - self.header(id).and_then(move |header| { - let header = match header { - None => return future::ok(None).boxed(), - Some(hdr) => hdr, - }; - - sync.with_context(|ctx| on_demand.account(ctx, request::Account { - header: header, - address: address, - })) - .map(|x| x.map_err(err_premature_cancel).boxed()) - .unwrap_or_else(|| future::err(errors::network_disabled()).boxed()) - }).boxed() - } - - // helper for getting proved execution. - fn proved_execution(&self, req: CallRequest, num: Trailing) -> BoxFuture { - const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]); - - - let (sync, on_demand, client) = (self.sync.clone(), self.on_demand.clone(), self.client.clone()); - let req: CRequest = req.into(); - let id = num.0.into(); - - let from = req.from.unwrap_or(Address::zero()); - let nonce_fut = match req.nonce { - Some(nonce) => future::ok(Some(nonce)).boxed(), - None => self.account(from, id).map(|acc| acc.map(|a| a.nonce)).boxed(), - }; - - let gas_price_fut = match req.gas_price { - Some(price) => future::ok(price).boxed(), - None => dispatch::fetch_gas_price_corpus( - self.sync.clone(), - self.client.clone(), - self.on_demand.clone(), - self.cache.clone(), - ).map(|corp| match corp.median() { - Some(median) => *median, - None => DEFAULT_GAS_PRICE, - }).boxed() - }; - - // if nonce resolves, this should too since it'll be in the LRU-cache. - let header_fut = self.header(id); - - // fetch missing transaction fields from the network. - nonce_fut.join(gas_price_fut).and_then(move |(nonce, gas_price)| { - let action = req.to.map_or(Action::Create, Action::Call); - let gas = req.gas.unwrap_or(U256::from(10_000_000)); // better gas amount? - let value = req.value.unwrap_or_else(U256::zero); - let data = req.data.map_or_else(Vec::new, |d| d.to_vec()); - - future::done(match nonce { - Some(n) => Ok(EthTransaction { - nonce: n, - action: action, - gas: gas, - gas_price: gas_price, - value: value, - data: data, - }.fake_sign(from)), - None => Err(errors::unknown_block()), - }) - }).join(header_fut).and_then(move |(tx, hdr)| { - // then request proved execution. - // TODO: get last-hashes from network. - let (env_info, hdr) = match (client.env_info(id), hdr) { - (Some(env_info), Some(hdr)) => (env_info, hdr), - _ => return future::err(errors::unknown_block()).boxed(), - }; - let request = request::TransactionProof { - tx: tx, - header: hdr, - env_info: env_info, - engine: client.engine().clone(), - }; - - let proved_future = sync.with_context(move |ctx| { - on_demand.transaction_proof(ctx, request).map_err(err_premature_cancel).boxed() - }); - - match proved_future { - Some(fut) => fut.boxed(), - None => future::err(errors::network_disabled()).boxed(), - } - }).boxed() } } @@ -281,7 +144,7 @@ impl Eth for EthClient { } fn balance(&self, address: RpcH160, num: Trailing) -> BoxFuture { - self.account(address.into(), num.0.into()) + self.fetcher().account(address.into(), num.0.into()) .map(|acc| acc.map_or(0.into(), |a| a.balance).into()).boxed() } @@ -298,14 +161,14 @@ impl Eth for EthClient { } fn transaction_count(&self, address: RpcH160, num: Trailing) -> BoxFuture { - self.account(address.into(), num.0.into()) + self.fetcher().account(address.into(), num.0.into()) .map(|acc| acc.map_or(0.into(), |a| a.nonce).into()).boxed() } fn block_transaction_count_by_hash(&self, hash: RpcH256) -> BoxFuture, Error> { let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); - self.header(BlockId::Hash(hash.into())).and_then(move |hdr| { + self.fetcher().header(BlockId::Hash(hash.into())).and_then(move |hdr| { let hdr = match hdr { None => return future::ok(None).boxed(), Some(hdr) => hdr, @@ -316,7 +179,7 @@ impl Eth for EthClient { } else { sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr))) .map(|x| x.map(|b| Some(U256::from(b.transactions_count()).into()))) - .map(|x| x.map_err(err_premature_cancel).boxed()) + .map(|x| x.map_err(errors::on_demand_cancel).boxed()) .unwrap_or_else(|| future::err(errors::network_disabled()).boxed()) } }).boxed() @@ -325,7 +188,7 @@ impl Eth for EthClient { fn block_transaction_count_by_number(&self, num: BlockNumber) -> BoxFuture, Error> { let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); - self.header(num.into()).and_then(move |hdr| { + self.fetcher().header(num.into()).and_then(move |hdr| { let hdr = match hdr { None => return future::ok(None).boxed(), Some(hdr) => hdr, @@ -336,7 +199,7 @@ impl Eth for EthClient { } else { sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr))) .map(|x| x.map(|b| Some(U256::from(b.transactions_count()).into()))) - .map(|x| x.map_err(err_premature_cancel).boxed()) + .map(|x| x.map_err(errors::on_demand_cancel).boxed()) .unwrap_or_else(|| future::err(errors::network_disabled()).boxed()) } }).boxed() @@ -345,7 +208,7 @@ impl Eth for EthClient { fn block_uncles_count_by_hash(&self, hash: RpcH256) -> BoxFuture, Error> { let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); - self.header(BlockId::Hash(hash.into())).and_then(move |hdr| { + self.fetcher().header(BlockId::Hash(hash.into())).and_then(move |hdr| { let hdr = match hdr { None => return future::ok(None).boxed(), Some(hdr) => hdr, @@ -356,7 +219,7 @@ impl Eth for EthClient { } else { sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr))) .map(|x| x.map(|b| Some(U256::from(b.uncles_count()).into()))) - .map(|x| x.map_err(err_premature_cancel).boxed()) + .map(|x| x.map_err(errors::on_demand_cancel).boxed()) .unwrap_or_else(|| future::err(errors::network_disabled()).boxed()) } }).boxed() @@ -365,7 +228,7 @@ impl Eth for EthClient { fn block_uncles_count_by_number(&self, num: BlockNumber) -> BoxFuture, Error> { let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); - self.header(num.into()).and_then(move |hdr| { + self.fetcher().header(num.into()).and_then(move |hdr| { let hdr = match hdr { None => return future::ok(None).boxed(), Some(hdr) => hdr, @@ -376,7 +239,7 @@ impl Eth for EthClient { } else { sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr))) .map(|x| x.map(|b| Some(U256::from(b.uncles_count()).into()))) - .map(|x| x.map_err(err_premature_cancel).boxed()) + .map(|x| x.map_err(errors::on_demand_cancel).boxed()) .unwrap_or_else(|| future::err(errors::network_disabled()).boxed()) } }).boxed() @@ -411,7 +274,7 @@ impl Eth for EthClient { } fn call(&self, req: CallRequest, num: Trailing) -> BoxFuture { - self.proved_execution(req, num).and_then(|res| { + self.fetcher().proved_execution(req, num).and_then(|res| { match res { Ok(exec) => Ok(exec.output.into()), Err(e) => Err(errors::execution(e)), @@ -421,7 +284,7 @@ impl Eth for EthClient { fn estimate_gas(&self, req: CallRequest, num: Trailing) -> BoxFuture { // TODO: binary chop for more accurate estimates. - self.proved_execution(req, num).and_then(|res| { + self.fetcher().proved_execution(req, num).and_then(|res| { match res { Ok(exec) => Ok((exec.refunded + exec.gas_used).into()), Err(e) => Err(errors::execution(e)), diff --git a/rpc/src/v1/impls/light/parity.rs b/rpc/src/v1/impls/light/parity.rs index 569398193..65cc90966 100644 --- a/rpc/src/v1/impls/light/parity.rs +++ b/rpc/src/v1/impls/light/parity.rs @@ -32,6 +32,7 @@ use jsonrpc_core::Error; use jsonrpc_macros::Trailing; use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings}; use v1::helpers::dispatch::{LightDispatcher, DEFAULT_MAC}; +use v1::helpers::light_fetch::LightFetch; use v1::metadata::Metadata; use v1::traits::Parity; use v1::types::{ @@ -40,7 +41,7 @@ use v1::types::{ TransactionStats, LocalTransactionStatus, BlockNumber, ConsensusCapability, VersionInfo, OperationsInfo, DappId, ChainStatus, - AccountInfo, HwAccountInfo, + AccountInfo, HwAccountInfo, Header, RichHeader, }; /// Parity implementation for light client. @@ -75,6 +76,16 @@ impl ParityClient { dapps_port: dapps_port, } } + + /// Create a light blockchain data fetcher. + fn fetcher(&self) -> LightFetch { + LightFetch { + client: self.light_dispatch.client.clone(), + on_demand: self.light_dispatch.on_demand.clone(), + sync: self.light_dispatch.sync.clone(), + cache: self.light_dispatch.cache.clone(), + } + } } impl Parity for ParityClient { @@ -342,4 +353,38 @@ impl Parity for ParityClient { capability: Capability::Light, }) } + + fn block_header(&self, number: Trailing) -> BoxFuture, Error> { + use ethcore::encoded; + + let engine = self.light_dispatch.client.engine().clone(); + let from_encoded = move |encoded: encoded::Header| { + let header = encoded.decode(); + let extra_info = engine.extra_info(&header); + RichHeader { + inner: Header { + hash: Some(header.hash().into()), + size: Some(encoded.rlp().as_raw().len().into()), + parent_hash: header.parent_hash().clone().into(), + uncles_hash: header.uncles_hash().clone().into(), + author: header.author().clone().into(), + miner: header.author().clone().into(), + state_root: header.state_root().clone().into(), + transactions_root: header.transactions_root().clone().into(), + receipts_root: header.receipts_root().clone().into(), + number: Some(header.number().into()), + gas_used: header.gas_used().clone().into(), + gas_limit: header.gas_limit().clone().into(), + logs_bloom: header.log_bloom().clone().into(), + timestamp: header.timestamp().into(), + difficulty: header.difficulty().clone().into(), + seal_fields: header.seal().iter().cloned().map(Into::into).collect(), + extra_data: Bytes::new(header.extra_data().clone()), + }, + extra_info: extra_info, + } + }; + + self.fetcher().header(number.0.into()).map(move |encoded| encoded.map(from_encoded)).boxed() + } } diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 83e0a505d..d2934313e 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -28,6 +28,7 @@ use crypto::ecies; use ethkey::{Brain, Generator}; use ethstore::random_phrase; use ethsync::{SyncProvider, ManageNetwork}; +use ethcore::ids::BlockId; use ethcore::miner::MinerService; use ethcore::client::{MiningBlockChainClient}; use ethcore::mode::Mode; @@ -47,7 +48,7 @@ use v1::types::{ TransactionStats, LocalTransactionStatus, BlockNumber, ConsensusCapability, VersionInfo, OperationsInfo, DappId, ChainStatus, - AccountInfo, HwAccountInfo, + AccountInfo, HwAccountInfo, Header, RichHeader }; /// Parity implementation. @@ -393,4 +394,38 @@ impl Parity for ParityClient where capability: Capability::Full, }) } + + fn block_header(&self, number: Trailing) -> BoxFuture, Error> { + const EXTRA_INFO_PROOF: &'static str = "Object exists in in blockchain (fetched earlier), extra_info is always available if object exists; qed"; + + let client = take_weakf!(self.client); + let id: BlockId = number.0.into(); + let encoded = match client.block_header(id.clone()) { + Some(encoded) => encoded, + None => return future::ok(None).boxed(), + }; + + future::ok(Some(RichHeader { + inner: Header { + hash: Some(encoded.hash().into()), + size: Some(encoded.rlp().as_raw().len().into()), + parent_hash: encoded.parent_hash().into(), + uncles_hash: encoded.uncles_hash().into(), + author: encoded.author().into(), + miner: encoded.author().into(), + state_root: encoded.state_root().into(), + transactions_root: encoded.transactions_root().into(), + receipts_root: encoded.receipts_root().into(), + number: Some(encoded.number().into()), + gas_used: encoded.gas_used().into(), + gas_limit: encoded.gas_limit().into(), + logs_bloom: encoded.log_bloom().into(), + timestamp: encoded.timestamp().into(), + difficulty: encoded.difficulty().into(), + seal_fields: encoded.seal().into_iter().map(Into::into).collect(), + extra_data: Bytes::new(encoded.extra_data()), + }, + extra_info: client.block_extra_info(id).expect(EXTRA_INFO_PROOF), + })).boxed() + } } diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs index 8a15addae..b374ebd53 100644 --- a/rpc/src/v1/traits/parity.rs +++ b/rpc/src/v1/traits/parity.rs @@ -28,7 +28,7 @@ use v1::types::{ TransactionStats, LocalTransactionStatus, BlockNumber, ConsensusCapability, VersionInfo, OperationsInfo, DappId, ChainStatus, - AccountInfo, HwAccountInfo, + AccountInfo, HwAccountInfo, RichHeader, }; build_rpc_trait! { @@ -198,5 +198,10 @@ build_rpc_trait! { /// Get node kind info. #[rpc(name = "parity_nodeKind")] fn node_kind(&self) -> Result<::v1::types::NodeKind, Error>; + + /// Get block header. + /// Same as `eth_getBlockByNumber` but without uncles and transactions. + #[rpc(async, name = "parity_getBlockHeaderByNumber")] + fn block_header(&self, Trailing) -> BoxFuture, Error>; } } diff --git a/rpc/src/v1/types/block.rs b/rpc/src/v1/types/block.rs index e63137520..4077d7221 100644 --- a/rpc/src/v1/types/block.rs +++ b/rpc/src/v1/types/block.rs @@ -96,34 +96,90 @@ pub struct Block { pub size: Option, } -/// Block representation with additional info +/// Block header representation. +#[derive(Debug, Serialize)] +pub struct Header { + /// Hash of the block + pub hash: Option, + /// Hash of the parent + #[serde(rename="parentHash")] + pub parent_hash: H256, + /// Hash of the uncles + #[serde(rename="sha3Uncles")] + pub uncles_hash: H256, + /// Authors address + pub author: H160, + // TODO: get rid of this one + /// ? + pub miner: H160, + /// State root hash + #[serde(rename="stateRoot")] + pub state_root: H256, + /// Transactions root hash + #[serde(rename="transactionsRoot")] + pub transactions_root: H256, + /// Transactions receipts root hash + #[serde(rename="receiptsRoot")] + pub receipts_root: H256, + /// Block number + pub number: Option, + /// Gas Used + #[serde(rename="gasUsed")] + pub gas_used: U256, + /// Gas Limit + #[serde(rename="gasLimit")] + pub gas_limit: U256, + /// Extra data + #[serde(rename="extraData")] + pub extra_data: Bytes, + /// Logs bloom + #[serde(rename="logsBloom")] + pub logs_bloom: H2048, + /// Timestamp + pub timestamp: U256, + /// Difficulty + pub difficulty: U256, + /// Seal fields + #[serde(rename="sealFields")] + pub seal_fields: Vec, + /// Size in bytes + pub size: Option, +} + +/// Block representation with additional info. +pub type RichBlock = Rich; + +/// Header representation with additional info. +pub type RichHeader = Rich
; + +/// Value representation with additional info #[derive(Debug)] -pub struct RichBlock { - /// Standard block - pub block: Block, +pub struct Rich { + /// Standard value. + pub inner: T, /// Engine-specific fields with additional description. /// Should be included directly to serialized block object. // TODO [ToDr] #[serde(skip_serializing)] pub extra_info: BTreeMap, } -impl Deref for RichBlock { - type Target = Block; +impl Deref for Rich { + type Target = T; fn deref(&self) -> &Self::Target { - &self.block + &self.inner } } -impl Serialize for RichBlock { +impl Serialize for Rich { fn serialize(&self, serializer: S) -> Result where S: Serializer { use serde_json::{to_value, Value}; - let serialized = (to_value(&self.block), to_value(&self.extra_info)); - if let (Ok(Value::Object(mut block)), Ok(Value::Object(extras))) = serialized { + let serialized = (to_value(&self.inner), to_value(&self.extra_info)); + if let (Ok(Value::Object(mut value)), Ok(Value::Object(extras))) = serialized { // join two objects - block.extend(extras); + value.extend(extras); // and serialize - block.serialize(serializer) + value.serialize(serializer) } else { Err(S::Error::custom("Unserializable structures.")) } @@ -135,7 +191,7 @@ mod tests { use std::collections::BTreeMap; use serde_json; use v1::types::{Transaction, H64, H160, H256, H2048, Bytes, U256}; - use super::{Block, RichBlock, BlockTransactions}; + use super::{Block, RichBlock, BlockTransactions, Header, RichHeader}; #[test] fn test_serialize_block_transactions() { @@ -174,7 +230,7 @@ mod tests { }; let serialized_block = serde_json::to_string(&block).unwrap(); let rich_block = RichBlock { - block: block, + inner: block, extra_info: map![ "mixHash".into() => format!("0x{:?}", H256::default()), "nonce".into() => format!("0x{:?}", H64::default()) @@ -212,7 +268,7 @@ mod tests { }; let serialized_block = serde_json::to_string(&block).unwrap(); let rich_block = RichBlock { - block: block, + inner: block, extra_info: map![ "mixHash".into() => format!("0x{:?}", H256::default()), "nonce".into() => format!("0x{:?}", H64::default()) @@ -223,4 +279,39 @@ mod tests { assert_eq!(serialized_block, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","author":"0x0000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","number":"0x0","gasUsed":"0x0","gasLimit":"0x0","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","difficulty":"0x0","totalDifficulty":"0x0","sealFields":["0x","0x"],"uncles":[],"transactions":[],"size":null}"#); assert_eq!(serialized_rich_block, r#"{"author":"0x0000000000000000000000000000000000000000","difficulty":"0x0","extraData":"0x","gasLimit":"0x0","gasUsed":"0x0","hash":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","sealFields":["0x","0x"],"sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","size":null,"stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","totalDifficulty":"0x0","transactions":[],"transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","uncles":[]}"#); } + + #[test] + fn test_serialize_header() { + let header = Header { + hash: Some(H256::default()), + parent_hash: H256::default(), + uncles_hash: H256::default(), + author: H160::default(), + miner: H160::default(), + state_root: H256::default(), + transactions_root: H256::default(), + receipts_root: H256::default(), + number: Some(U256::default()), + gas_used: U256::default(), + gas_limit: U256::default(), + extra_data: Bytes::default(), + logs_bloom: H2048::default(), + timestamp: U256::default(), + difficulty: U256::default(), + seal_fields: vec![Bytes::default(), Bytes::default()], + size: Some(69.into()), + }; + let serialized_header = serde_json::to_string(&header).unwrap(); + let rich_header = RichHeader { + inner: header, + extra_info: map![ + "mixHash".into() => format!("0x{:?}", H256::default()), + "nonce".into() => format!("0x{:?}", H64::default()) + ], + }; + let serialized_rich_header = serde_json::to_string(&rich_header).unwrap(); + + assert_eq!(serialized_header, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","author":"0x0000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","number":"0x0","gasUsed":"0x0","gasLimit":"0x0","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","difficulty":"0x0","sealFields":["0x","0x"],"size":"0x45"}"#); + assert_eq!(serialized_rich_header, r#"{"author":"0x0000000000000000000000000000000000000000","difficulty":"0x0","extraData":"0x","gasLimit":"0x0","gasUsed":"0x0","hash":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","sealFields":["0x","0x"],"sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","size":"0x45","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000"}"#); + } } diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs index 0ec60a74f..d76c92deb 100644 --- a/rpc/src/v1/types/mod.rs +++ b/rpc/src/v1/types/mod.rs @@ -45,7 +45,7 @@ mod work; pub use self::account_info::{AccountInfo, HwAccountInfo}; pub use self::bytes::Bytes; -pub use self::block::{RichBlock, Block, BlockTransactions}; +pub use self::block::{RichBlock, Block, BlockTransactions, Header, RichHeader, Rich}; pub use self::block_number::BlockNumber; pub use self::call_request::CallRequest; pub use self::confirmations::{