// Copyright 2015-2017 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity. If not, see . //! Helpers for fetching blockchain data either from the light client or the network. use std::sync::Arc; use ethcore::basic_account::BasicAccount; use ethcore::encoded; use ethcore::executed::{Executed, ExecutionError}; use ethcore::ids::BlockId; use ethcore::transaction::{Action, Transaction as EthTransaction}; use futures::{future, Future, BoxFuture}; use jsonrpc_core::Error; use jsonrpc_macros::Trailing; use light::cache::Cache; use light::client::LightChainClient; use light::cht; use light::on_demand::{request, OnDemand, HeaderRef, Request as OnDemandRequest, Response as OnDemandResponse}; use light::request::Field; use ethsync::LightSync; use util::{Address, Mutex, 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, /// The on-demand request service. pub on_demand: Arc, /// Handle to the network. pub sync: Arc, /// The light data cache. pub cache: Arc>, } /// Type alias for convenience. pub type ExecutionResult = Result; // extract the header indicated by the given `HeaderRef` from the given responses. // fails only if they do not correspond. fn extract_header(res: &[OnDemandResponse], header: HeaderRef) -> Option { match header { HeaderRef::Stored(hdr) => Some(hdr), HeaderRef::Unresolved(idx, _) => match res.get(idx) { Some(&OnDemandResponse::HeaderByHash(ref hdr)) => Some(hdr.clone()), _ => None, }, } } impl LightFetch { // push the necessary requests onto the request chain to get the header by the given ID. // yield a header reference which other requests can use. fn make_header_requests(&self, id: BlockId, reqs: &mut Vec) -> Result { if let Some(h) = self.client.block_header(id) { return Ok(h.into()); } 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 => Err(errors::unknown_block()), Some(root) => { let req = request::HeaderProof::new(n, root) .expect("only fails for 0; client always stores genesis; client already queried; qed"); let idx = reqs.len(); let hash_ref = Field::back_ref(idx, 0); reqs.push(req.into()); reqs.push(request::HeaderByHash(hash_ref.clone()).into()); Ok(HeaderRef::Unresolved(idx + 1, hash_ref)) } } } BlockId::Hash(h) => { reqs.push(request::HeaderByHash(h.into()).into()); let idx = reqs.len(); Ok(HeaderRef::Unresolved(idx, h.into())) } _ => Err(errors::unknown_block()) // latest, earliest, and pending will have all already returned. } } /// Get a block header from the on demand service or client, or error. pub fn header(&self, id: BlockId) -> BoxFuture { let mut reqs = Vec::new(); let header_ref = match self.make_header_requests(id, &mut reqs) { Ok(r) => r, Err(e) => return future::err(e).boxed(), }; let maybe_future = self.sync.with_context(move |ctx| { self.on_demand.request_raw(ctx, reqs) .expect("all back-references known to be valid; qed") .map(|res| extract_header(&res, header_ref) .expect("these responses correspond to requests that header_ref belongs to. \ therefore it will not fail; qed")) .map_err(errors::on_demand_cancel) .boxed() }); match maybe_future { Some(recv) => recv, None => future::err(errors::network_disabled()).boxed() } } /// helper for getting account info at a given block. /// `None` indicates the account doesn't exist at the given block. pub fn account(&self, address: Address, id: BlockId) -> BoxFuture, Error> { let mut reqs = Vec::new(); let header_ref = match self.make_header_requests(id, &mut reqs) { Ok(r) => r, Err(e) => return future::err(e).boxed(), }; reqs.push(request::Account { header: header_ref, address: address }.into()); let maybe_future = self.sync.with_context(move |ctx| { self.on_demand.request_raw(ctx, reqs) .expect("all back-references known to be valid; qed") .map(|mut res| match res.pop() { Some(OnDemandResponse::Account(acc)) => acc, _ => panic!("responses correspond directly with requests in amount and type; qed"), }) .map_err(errors::on_demand_cancel) .boxed() }); match maybe_future { Some(recv) => recv, None => future::err(errors::network_disabled()).boxed() } } /// helper for getting proved execution. pub fn proved_execution(&self, req: CallRequest, num: Trailing) -> BoxFuture { const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]); let (sync, on_demand, client) = (self.sync.clone(), self.on_demand.clone(), self.client.clone()); let req: CallRequestHelper = req.into(); let id = num.unwrap_or_default().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 = match client.env_info(id) { Some(env_info) => env_info, _ => return future::err(errors::unknown_block()).boxed(), }; let request = request::TransactionProof { tx: tx, header: hdr.into(), env_info: env_info, engine: client.engine().clone(), }; let proved_future = sync.with_context(move |ctx| { on_demand .request(ctx, request) .expect("no back-references; therefore all back-refs valid; qed") .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 itself. fails on unknown block ID. pub fn block(&self, id: BlockId) -> BoxFuture { let mut reqs = Vec::new(); let header_ref = match self.make_header_requests(id, &mut reqs) { Ok(r) => r, Err(e) => return future::err(e).boxed(), }; reqs.push(request::Body(header_ref).into()); let maybe_future = self.sync.with_context(move |ctx| { self.on_demand.request_raw(ctx, reqs) .expect("all back-references known to be valid; qed") .map(|mut res| match res.pop() { Some(OnDemandResponse::Body(b)) => b, _ => panic!("responses correspond directly with requests in amount and type; qed"), }) .map_err(errors::on_demand_cancel) .boxed() }); match maybe_future { Some(recv) => recv, None => future::err(errors::network_disabled()).boxed() } } }