diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index 4a4da2917..2872e0eec 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -230,22 +230,32 @@ impl Client { } /// Get a handle to the verification engine. - pub fn engine(&self) -> &Engine { - &*self.engine + pub fn engine(&self) -> &Arc { + &self.engine } - fn latest_env_info(&self) -> EnvInfo { - let header = self.best_block_header(); + /// Get the latest environment info. + pub fn latest_env_info(&self) -> EnvInfo { + self.env_info(BlockId::Latest) + .expect("Best block header and recent hashes always stored; qed") + } - EnvInfo { + /// Get environment info for a given block. + pub fn env_info(&self, id: BlockId) -> Option { + let header = match self.block_header(id) { + Some(hdr) => hdr, + None => return None, + }; + + Some(EnvInfo { number: header.number(), author: header.author(), timestamp: header.timestamp(), difficulty: header.difficulty(), - last_hashes: self.build_last_hashes(header.hash()), + last_hashes: self.build_last_hashes(header.parent_hash()), gas_used: Default::default(), gas_limit: header.gas_limit(), - } + }) } fn build_last_hashes(&self, mut parent_hash: H256) -> Arc> { diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index 0bea7f9a1..b11ada048 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -158,6 +158,54 @@ impl Dispatcher for FullDispatcher, + client: Arc, + on_demand: Arc, + cache: Arc>, +) -> BoxFuture, Error> { + const GAS_PRICE_SAMPLE_SIZE: usize = 100; + + if let Some(cached) = cache.lock().gas_price_corpus() { + return future::ok(cached).boxed() + } + + let cache = cache.clone(); + let eventual_corpus = sync.with_context(|ctx| { + // get some recent headers with gas used, + // and request each of the blocks from the network. + let block_futures = client.ancestry_iter(BlockId::Latest) + .filter(|hdr| hdr.gas_used() != U256::default()) + .take(GAS_PRICE_SAMPLE_SIZE) + .map(request::Body::new) + .map(|req| on_demand.block(ctx, req)); + + // as the blocks come in, collect gas prices into a vector + stream::futures_unordered(block_futures) + .fold(Vec::new(), |mut v, block| { + for t in block.transaction_views().iter() { + v.push(t.gas_price()) + } + + future::ok(v) + }) + .map(move |v| { + // produce a corpus from the vector, cache it, and return + // the median as the intended gas price. + let corpus: ::stats::Corpus<_> = v.into(); + cache.lock().set_gas_price_corpus(corpus.clone()); + corpus + }) + }); + + match eventual_corpus { + Some(corp) => corp.map_err(|_| errors::no_light_peers()).boxed(), + None => future::err(errors::network_disabled()).boxed(), + } +} + /// Dispatcher for light clients -- fetches default gas price, next nonce, etc. from network. /// Light client `ETH` RPC. #[derive(Clone)] @@ -197,44 +245,12 @@ impl LightDispatcher { /// Get a recent gas price corpus. // TODO: this could be `impl Trait`. pub fn gas_price_corpus(&self) -> BoxFuture, Error> { - const GAS_PRICE_SAMPLE_SIZE: usize = 100; - - if let Some(cached) = self.cache.lock().gas_price_corpus() { - return future::ok(cached).boxed() - } - - let cache = self.cache.clone(); - let eventual_corpus = self.sync.with_context(|ctx| { - // get some recent headers with gas used, - // and request each of the blocks from the network. - let block_futures = self.client.ancestry_iter(BlockId::Latest) - .filter(|hdr| hdr.gas_used() != U256::default()) - .take(GAS_PRICE_SAMPLE_SIZE) - .map(request::Body::new) - .map(|req| self.on_demand.block(ctx, req)); - - // as the blocks come in, collect gas prices into a vector - stream::futures_unordered(block_futures) - .fold(Vec::new(), |mut v, block| { - for t in block.transaction_views().iter() { - v.push(t.gas_price()) - } - - future::ok(v) - }) - .map(move |v| { - // produce a corpus from the vector, cache it, and return - // the median as the intended gas price. - let corpus: ::stats::Corpus<_> = v.into(); - cache.lock().set_gas_price_corpus(corpus.clone()); - corpus - }) - }); - - match eventual_corpus { - Some(corp) => corp.map_err(|_| errors::no_light_peers()).boxed(), - None => future::err(errors::network_disabled()).boxed(), - } + fetch_gas_price_corpus( + self.sync.clone(), + self.client.clone(), + self.on_demand.clone(), + self.cache.clone(), + ) } /// Get an account's next nonce. @@ -285,7 +301,12 @@ impl Dispatcher for LightDispatcher { // fast path for known gas price. match request_gas_price { Some(gas_price) => future::ok(with_gas_price(gas_price)).boxed(), - None => self.gas_price_corpus().and_then(|corp| match corp.median() { + None => fetch_gas_price_corpus( + self.sync.clone(), + self.client.clone(), + self.on_demand.clone(), + self.cache.clone() + ).and_then(|corp| match corp.median() { Some(median) => future::ok(*median), None => future::ok(DEFAULT_GAS_PRICE), // fall back to default on error. }).map(with_gas_price).boxed() diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index cf8bdbbe1..47143ac75 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -629,10 +629,13 @@ impl Eth for EthClient where fn call(&self, request: CallRequest, num: Trailing) -> BoxFuture { let request = CallRequest::into(request); - let signed = self.sign_call(request)?; + let signed = match self.sign_call(request) { + Ok(signed) => signed, + Err(e) => return future::err(e).boxed(), + }; let result = match num.0 { - BlockNumber::Pending => take_weakf!(self.miner).call(&*take_weak!(self.client), &signed, Default::default()), + BlockNumber::Pending => take_weakf!(self.miner).call(&*take_weakf!(self.client), &signed, Default::default()), num => take_weakf!(self.client).call(&signed, num.into(), Default::default()), }; @@ -644,7 +647,10 @@ impl Eth for EthClient where fn estimate_gas(&self, request: CallRequest, num: Trailing) -> BoxFuture { let request = CallRequest::into(request); - let signed = self.sign_call(request)?; + let signed = match self.sign_call(request) { + Ok(signed) => signed, + Err(e) => return future::err(e).boxed(), + }; future::done(take_weakf!(self.client).estimate_gas(&signed, num.0.into()) .map(Into::into) .map_err(errors::from_call_error) diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index f6be478fa..f889faf00 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -24,6 +24,7 @@ use std::sync::Arc; use jsonrpc_core::Error; use jsonrpc_macros::Trailing; +use light::cache::Cache as LightDataCache; use light::client::Client as LightClient; use light::{cht, TransactionQueue}; use light::on_demand::{request, OnDemand}; @@ -31,17 +32,18 @@ use light::on_demand::{request, OnDemand}; use ethcore::account_provider::{AccountProvider, DappId}; use ethcore::basic_account::BasicAccount; use ethcore::encoded; +use ethcore::executed::{Executed, ExecutionError}; use ethcore::ids::BlockId; -use ethcore::transaction::SignedTransaction; +use ethcore::transaction::{Action, SignedTransaction, Transaction as EthTransaction}; use ethsync::LightSync; use rlp::{UntrustedRlp, View}; use util::sha3::{SHA3_NULL_RLP, SHA3_EMPTY_LIST_RLP}; -use util::{RwLock, U256}; +use util::{RwLock, Mutex, FixedHash, Uint, U256}; use futures::{future, Future, BoxFuture, IntoFuture}; use futures::sync::oneshot; -use v1::helpers::{CallRequest as CRequest, errors, limit_logs}; +use v1::helpers::{CallRequest as CRequest, errors, limit_logs, dispatch}; use v1::helpers::block_import::is_major_importing; use v1::traits::Eth; use v1::types::{ @@ -60,6 +62,7 @@ pub struct EthClient { on_demand: Arc, transaction_queue: Arc>, accounts: Arc, + cache: Arc>, } // helper for internal error: on demand sender cancelled. @@ -67,6 +70,8 @@ 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, /// and on-demand request service, which is assumed to be attached as a handler. @@ -76,6 +81,7 @@ impl EthClient { on_demand: Arc, transaction_queue: Arc>, accounts: Arc, + cache: Arc>, ) -> Self { EthClient { sync: sync, @@ -83,6 +89,7 @@ impl EthClient { on_demand: on_demand, transaction_queue: transaction_queue, accounts: accounts, + cache: cache, } } @@ -149,24 +156,77 @@ impl EthClient { } // helper for getting proved execution. - fn proved_execution(&self, req: CallRequest, num: Trailing) -> Result, Error> { - let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); + 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 = request.from.unwrap_or(Address::zero()); - let action = request.to.map_or(Action::Create, Action::Call); - let gas: request.gas.unwrap_or(U256::from(10_000_000)); - let value = request.value.unwrap_or_else(U256::zero); - let data = request.data.map_or_else(Vec::new, |d| d.to_vec()); + 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(), + }; - sync.with_context(|ctx| { - let nonce_fut = req.nonce.map(Some).ok_or(err_no_context()) - .or_else(|_| self.account(from, id).map(|acc| acc.map(|a| a.nonce))); + 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() + }; - let gas_price_fut = req.gas_price.map(Some).ok_or(err_no_context()) - .or_else(|_| unimplemented!()) - }) + // 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() } } @@ -344,10 +404,9 @@ impl Eth for EthClient { } fn call(&self, req: CallRequest, num: Trailing) -> BoxFuture { - self.proved_execution().and_then(|res| { + self.proved_execution(req, num).and_then(|res| { match res { - Ok(Some(exec)) => Ok(exec.output.into()), - Ok(None) => Err(errors::unknown_block()), + Ok(exec) => Ok(exec.output.into()), Err(e) => Err(errors::execution(e)), } }).boxed() @@ -355,10 +414,9 @@ impl Eth for EthClient { fn estimate_gas(&self, req: CallRequest, num: Trailing) -> BoxFuture { // TODO: binary chop for more accurate estimates. - self.proved_execution().and_then(|res| { + self.proved_execution(req, num).and_then(|res| { match res { - Ok(Some(exec)) => Ok((exec.refunded + exec.gas_used).into()), - Ok(None) => Err(errors::unknown_block()), + Ok(exec) => Ok((exec.refunded + exec.gas_used).into()), Err(e) => Err(errors::execution(e)), } }).boxed()