// Copyright 2015-2019 Parity Technologies (UK) Ltd. // This file is part of Parity Ethereum. // Parity Ethereum 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 Ethereum 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 Ethereum. If not, see . use std::sync::Arc; use ethereum_types::{Address, H256, U256}; use light::{ cache::Cache as LightDataCache, client::LightChainClient, on_demand::{request, OnDemandRequester}, TransactionQueue as LightTransactionQueue, }; use parking_lot::{Mutex, RwLock}; use stats::Corpus; use sync::{LightNetworkDispatcher, LightSyncProvider, ManageNetwork}; use types::{ basic_account::BasicAccount, ids::BlockId, transaction::{Error as TransactionError, PendingTransaction, SignedTransaction}, }; use jsonrpc_core::{ futures::{future, future::Either, Future, IntoFuture}, BoxFuture, Result, }; use v1::{ helpers::{errors, nonce, FilledTransactionRequest, TransactionRequest}, types::RichRawTransaction as RpcRichRawTransaction, }; use super::{Accounts, Dispatcher, PostSign, SignWith}; /// Dispatcher for light clients -- fetches default gas price, next nonce, etc. from network. pub struct LightDispatcher where S: LightSyncProvider + LightNetworkDispatcher + ManageNetwork + 'static, OD: OnDemandRequester + 'static, { /// Sync service. pub sync: Arc, /// Header chain client. pub client: Arc, /// On-demand request service. pub on_demand: Arc, /// Data cache. pub cache: Arc>, /// Transaction queue. pub transaction_queue: Arc>, /// Nonce reservations pub nonces: Arc>, /// Gas Price percentile value used as default gas price. pub gas_price_percentile: usize, } impl LightDispatcher where S: LightSyncProvider + LightNetworkDispatcher + ManageNetwork + 'static, OD: OnDemandRequester + 'static, { /// Create a new `LightDispatcher` from its requisite parts. /// /// For correct operation, the OnDemand service is assumed to be registered as a network handler, pub fn new( sync: Arc, client: Arc, on_demand: Arc, cache: Arc>, transaction_queue: Arc>, nonces: Arc>, gas_price_percentile: usize, ) -> Self { LightDispatcher { sync, client, on_demand, cache, transaction_queue, nonces, gas_price_percentile, } } /// Get a recent gas price corpus. // TODO: this could be `impl Trait`. pub fn gas_price_corpus(&self) -> BoxFuture> { fetch_gas_price_corpus( self.sync.clone(), self.client.clone(), self.on_demand.clone(), self.cache.clone(), ) } /// Get an account's state fn account(&self, addr: Address) -> BoxFuture> { let best_header = self.client.best_block_header(); let account_future = self.sync.with_context(|ctx| { self.on_demand .request( ctx, request::Account { header: best_header.into(), address: addr, }, ) .expect("no back-references; therefore all back-references valid; qed") }); match account_future { Some(response) => Box::new(response.map_err(|_| errors::no_light_peers())), None => Box::new(future::err(errors::network_disabled())), } } /// Get an account's next nonce. pub fn next_nonce(&self, addr: Address) -> BoxFuture { let account_start_nonce = self .client .engine() .account_start_nonce(self.client.best_block_header().number()); Box::new(self.account(addr).and_then(move |maybe_account| { future::ok(maybe_account.map_or(account_start_nonce, |account| account.nonce)) })) } } impl Clone for LightDispatcher where S: LightSyncProvider + LightNetworkDispatcher + ManageNetwork + 'static, OD: OnDemandRequester + 'static, { fn clone(&self) -> Self { Self { sync: self.sync.clone(), client: self.client.clone(), on_demand: self.on_demand.clone(), cache: self.cache.clone(), transaction_queue: self.transaction_queue.clone(), nonces: self.nonces.clone(), gas_price_percentile: self.gas_price_percentile, } } } impl Dispatcher for LightDispatcher where S: LightSyncProvider + LightNetworkDispatcher + ManageNetwork + 'static, OD: OnDemandRequester + 'static, { // Ignore the `force_nonce` flag in order to always query the network when fetching the nonce and // the account state. If the nonce is specified in the transaction use that nonce instead but do the // network request anyway to the account state (balance) fn fill_optional_fields( &self, request: TransactionRequest, default_sender: Address, _force_nonce: bool, ) -> BoxFuture { const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]); let gas_limit = self.client.best_block_header().gas_limit(); let request_gas_price = request.gas_price; let from = request.from.unwrap_or(default_sender); let with_gas_price = move |gas_price| { let request = request; FilledTransactionRequest { from, used_default_from: request.from.is_none(), to: request.to, nonce: request.nonce, gas_price, gas: request.gas.unwrap_or_else(|| gas_limit / 3), value: request.value.unwrap_or_default(), data: request.data.unwrap_or_else(Vec::new), condition: request.condition, } }; // fast path for known gas price. let gas_price_percentile = self.gas_price_percentile; let gas_price = match request_gas_price { Some(gas_price) => Either::A(future::ok(with_gas_price(gas_price))), None => Either::B( fetch_gas_price_corpus( self.sync.clone(), self.client.clone(), self.on_demand.clone(), self.cache.clone(), ) .and_then(move |corp| match corp.percentile(gas_price_percentile) { Some(percentile) => Ok(*percentile), None => Ok(DEFAULT_GAS_PRICE), // fall back to default on error. }) .map(with_gas_price), ), }; let future_account = self.account(from); Box::new(gas_price.and_then(move |mut filled| { future_account.and_then(move |maybe_account| { let cost = filled .value .saturating_add(filled.gas.saturating_mul(filled.gas_price)); match maybe_account { Some(ref account) if cost > account.balance => { Err(errors::transaction(TransactionError::InsufficientBalance { balance: account.balance, cost, })) } Some(account) => { if filled.nonce.is_none() { filled.nonce = Some(account.nonce); } Ok(filled) } None => Err(errors::account("Account not found", "")), } }) })) } fn sign

( &self, filled: FilledTransactionRequest, signer: &Arc, password: SignWith, post_sign: P, ) -> BoxFuture where P: PostSign + 'static, ::Future: Send, { let chain_id = self.client.signing_chain_id(); let nonce = filled.nonce.expect("nonce is always provided; qed"); let future = signer .sign_transaction(filled, chain_id, nonce, password) .into_future() .and_then(move |signed| post_sign.execute(signed)); Box::new(future) } fn enrich(&self, signed_transaction: SignedTransaction) -> RpcRichRawTransaction { RpcRichRawTransaction::from_signed(signed_transaction) } fn dispatch_transaction(&self, signed_transaction: PendingTransaction) -> Result { let hash = signed_transaction.transaction.hash(); self.transaction_queue .write() .import(signed_transaction) .map_err(errors::transaction) .map(|_| hash) } } /// Get a recent gas price corpus. // TODO: this could be `impl Trait`. pub fn fetch_gas_price_corpus( sync: Arc, client: Arc, on_demand: Arc, cache: Arc>, ) -> BoxFuture> where S: LightSyncProvider + LightNetworkDispatcher + ManageNetwork + 'static, OD: OnDemandRequester + 'static, { const GAS_PRICE_SAMPLE_SIZE: usize = 100; if let Some(cached) = { cache.lock().gas_price_corpus() } { return Box::new(future::ok(cached)); } 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_requests = client .ancestry_iter(BlockId::Latest) .filter(|hdr| hdr.gas_used() != U256::default()) .take(GAS_PRICE_SAMPLE_SIZE) .map(|hdr| request::Body(hdr.into())) .collect::>(); // when the blocks come in, collect gas prices into a vector on_demand .request(ctx, block_requests) .expect("no back-references; therefore all back-references are valid; qed") .map(|bodies| { bodies.into_iter().fold(Vec::new(), |mut v, block| { for t in block.transaction_views().iter() { v.push(t.gas_price()) } v }) }) .map(move |prices| { // produce a corpus from the vector and cache it. // It's later used to get a percentile for default gas price. let corpus: ::stats::Corpus<_> = prices.into(); cache.lock().set_gas_price_corpus(corpus.clone()); corpus }) }); match eventual_corpus { Some(corp) => Box::new(corp.map_err(|_| errors::no_light_peers())), None => Box::new(future::err(errors::network_disabled())), } }