Skip to content
This repository was archived by the owner on Nov 6, 2020. It is now read-only.

parity_getBlockHeaderByNumber and LightFetch utility #5383

Merged
merged 5 commits into from
Apr 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions ethcore/light/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
}
Expand Down
5 changes: 5 additions & 0 deletions rpc/src/v1/helpers/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 217 additions & 0 deletions rpc/src/v1/helpers/light_fetch.rs
Original file line number Diff line number Diff line change
@@ -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()
}
}
1 change: 1 addition & 0 deletions rpc/src/v1/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions rpc/src/v1/impls/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down
Loading