Merge pull request #5383 from paritytech/block_header_rpc

parity_getBlockHeaderByNumber and LightFetch utility
This commit is contained in:
Robert Habermeier 2017-04-12 14:39:50 +02:00 committed by GitHub
commit 13633414a3
11 changed files with 460 additions and 201 deletions

View File

@ -85,6 +85,13 @@ pub trait LightChainClient: Send + Sync {
/// Get the signing network ID.
fn signing_network_id(&self) -> Option<u64>;
/// 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<EnvInfo>;
/// Get a handle to the consensus engine.
fn engine(&self) -> &Arc<Engine>;
/// Query whether a block is known.
fn is_known(&self, hash: &H256) -> bool;
@ -350,6 +357,14 @@ impl LightChainClient for Client {
Client::signing_network_id(self)
}
fn env_info(&self, id: BlockId) -> Option<EnvInfo> {
Client::env_info(self, id)
}
fn engine(&self) -> &Arc<Engine> {
Client::engine(self)
}
fn is_known(&self, hash: &H256) -> bool {
self.status(hash) == BlockStatus::InChain
}

View File

@ -355,3 +355,8 @@ pub fn deprecated<T: Into<Option<String>>>(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", "")
}

View File

@ -0,0 +1,217 @@
// 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 <http://www.gnu.org/licenses/>.
//! 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 CallRequestHelper, 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<LightChainClient>,
/// The on-demand request service.
pub on_demand: Arc<OnDemand>,
/// Handle to the network.
pub sync: Arc<LightSync>,
/// The light data cache.
pub cache: Arc<Mutex<Cache>>,
}
/// Type alias for convenience.
pub type ExecutionResult = Result<Executed, ExecutionError>;
impl LightFetch {
/// Get a block header from the on demand service or client, or error.
pub fn header(&self, id: BlockId) -> BoxFuture<Option<encoded::Header>, 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<Option<BasicAccount>, 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(Some))
.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<BlockNumber>) -> BoxFuture<ExecutionResult, Error> {
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: CallRequestHelper = 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()
}
/// Get a block.
pub fn block(&self, id: BlockId) -> BoxFuture<Option<encoded::Block>, Error> {
let (on_demand, sync) = (self.on_demand.clone(), self.sync.clone());
self.header(id).and_then(move |hdr| {
let req = match hdr {
Some(hdr) => request::Body::new(hdr),
None => return future::ok(None).boxed(),
};
match sync.with_context(move |ctx| on_demand.block(ctx, req)) {
Some(fut) => fut.map_err(errors::on_demand_cancel).map(Some).boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
}
}

View File

@ -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;
pub mod ipfs;

View File

@ -146,7 +146,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> 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<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> 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(),

View File

@ -48,6 +48,7 @@ use v1::impls::eth_filter::Filterable;
use v1::helpers::{CallRequest as CRequest, errors, limit_logs, dispatch};
use v1::helpers::{PollFilter, PollManager};
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,
@ -84,12 +85,6 @@ impl Clone for EthClient {
}
}
// 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<Executed, ExecutionError>;
impl EthClient {
/// Create a new `EthClient` with a handle to the light sync instance, client,
@ -113,165 +108,15 @@ impl EthClient {
}
}
/// Get a block header from the on demand service or client, or error.
fn header(&self, id: BlockId) -> BoxFuture<Option<encoded::Header>, 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<Option<BasicAccount>, 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,
};
let maybe_fut = sync.with_context(|ctx| on_demand.account(ctx, request::Account {
header: header,
address: address,
}));
match maybe_fut {
Some(fut) => fut.map(Some).map_err(err_premature_cancel).boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
}
// helper for getting proved execution.
fn proved_execution(&self, req: CallRequest, num: Trailing<BlockNumber>) -> BoxFuture<ExecutionResult, Error> {
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()
}
fn block(&self, id: BlockId) -> BoxFuture<Option<encoded::Block>, Error> {
let (on_demand, sync) = (self.on_demand.clone(), self.sync.clone());
self.header(id).and_then(move |hdr| {
let req = match hdr {
Some(hdr) => request::Body::new(hdr),
None => return future::ok(None).boxed(),
};
match sync.with_context(move |ctx| on_demand.block(ctx, req)) {
Some(fut) => fut.map_err(err_premature_cancel).map(Some).boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
}
// get a "rich" block structure
@ -284,7 +129,7 @@ impl EthClient {
let header = block.decode_header();
let extra_info = engine.extra_info(&header);
RichBlock {
block: Block {
inner: Block {
hash: Some(header.hash().into()),
size: Some(block.rlp().as_raw().len().into()),
parent_hash: header.parent_hash().clone().into(),
@ -314,7 +159,7 @@ impl EthClient {
};
// get the block itself.
self.block(id).and_then(move |block| match block {
self.fetcher().block(id).and_then(move |block| match block {
None => return future::ok(None).boxed(),
Some(block) => {
// then fetch the total difficulty (this is much easier after getting the block).
@ -354,7 +199,7 @@ impl EthClient {
};
Some(fill_rich(block, score))
}).map_err(err_premature_cancel).boxed(),
}).map_err(errors::on_demand_cancel).boxed(),
None => return future::err(errors::network_disabled()).boxed(),
}
}
@ -426,7 +271,7 @@ impl Eth for EthClient {
}
fn balance(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> {
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()
}
@ -443,14 +288,14 @@ impl Eth for EthClient {
}
fn transaction_count(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> {
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<Option<RpcU256>, 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,
@ -461,7 +306,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()
@ -470,7 +315,7 @@ impl Eth for EthClient {
fn block_transaction_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>, 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,
@ -481,7 +326,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()
@ -490,7 +335,7 @@ impl Eth for EthClient {
fn block_uncles_count_by_hash(&self, hash: RpcH256) -> BoxFuture<Option<RpcU256>, 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,
@ -501,7 +346,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()
@ -510,7 +355,7 @@ impl Eth for EthClient {
fn block_uncles_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>, 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,
@ -521,7 +366,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()
@ -556,7 +401,7 @@ impl Eth for EthClient {
}
fn call(&self, req: CallRequest, num: Trailing<BlockNumber>) -> BoxFuture<Bytes, Error> {
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)),
@ -566,7 +411,7 @@ impl Eth for EthClient {
fn estimate_gas(&self, req: CallRequest, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> {
// 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)),
@ -696,7 +541,7 @@ impl Filterable for EthClient {
future::ok(matches)
}) // and then collect them into a vector.
.map(|matches| matches.into_iter().map(|(_, v)| v).collect())
.map_err(err_premature_cancel)
.map_err(errors::on_demand_cancel)
});
match maybe_future {

View File

@ -32,6 +32,7 @@ use jsonrpc_core::Error;
use jsonrpc_macros::Trailing;
use v1::helpers::{errors, ipfs, 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 {
@ -343,6 +354,40 @@ impl Parity for ParityClient {
})
}
fn block_header(&self, number: Trailing<BlockNumber>) -> BoxFuture<Option<RichHeader>, 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()
}
fn ipfs_cid(&self, content: Bytes) -> Result<String, Error> {
ipfs::cid(content)
}

View File

@ -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.
@ -394,6 +395,40 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
})
}
fn block_header(&self, number: Trailing<BlockNumber>) -> BoxFuture<Option<RichHeader>, 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()
}
fn ipfs_cid(&self, content: Bytes) -> Result<String, Error> {
ipfs::cid(content)
}

View File

@ -28,7 +28,7 @@ use v1::types::{
TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo,
OperationsInfo, DappId, ChainStatus,
AccountInfo, HwAccountInfo,
AccountInfo, HwAccountInfo, RichHeader,
};
build_rpc_trait! {
@ -199,6 +199,11 @@ build_rpc_trait! {
#[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<BlockNumber>) -> BoxFuture<Option<RichHeader>, Error>;
/// Get IPFS CIDv0 given protobuf encoded bytes.
#[rpc(name = "parity_cidV0")]
fn ipfs_cid(&self, Bytes) -> Result<String, Error>;

View File

@ -96,34 +96,90 @@ pub struct Block {
pub size: Option<U256>,
}
/// Block representation with additional info
/// Block header representation.
#[derive(Debug, Serialize)]
pub struct Header {
/// Hash of the block
pub hash: Option<H256>,
/// 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<U256>,
/// 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<Bytes>,
/// Size in bytes
pub size: Option<U256>,
}
/// Block representation with additional info.
pub type RichBlock = Rich<Block>;
/// Header representation with additional info.
pub type RichHeader = Rich<Header>;
/// Value representation with additional info
#[derive(Debug)]
pub struct RichBlock {
/// Standard block
pub block: Block,
pub struct Rich<T> {
/// 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<String, String>,
}
impl Deref for RichBlock {
type Target = Block;
impl<T> Deref for Rich<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.block
&self.inner
}
}
impl Serialize for RichBlock {
impl<T: Serialize> Serialize for Rich<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 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"}"#);
}
}

View File

@ -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::{