Merge pull request #5383 from paritytech/block_header_rpc
parity_getBlockHeaderByNumber and LightFetch utility
This commit is contained in:
commit
13633414a3
@ -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
|
||||
}
|
||||
|
@ -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", "")
|
||||
}
|
||||
|
217
rpc/src/v1/helpers/light_fetch.rs
Normal file
217
rpc/src/v1/helpers/light_fetch.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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(),
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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>;
|
||||
|
@ -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"}"#);
|
||||
}
|
||||
}
|
||||
|
@ -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::{
|
||||
|
Loading…
Reference in New Issue
Block a user