// Copyright 2015-2018 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 .
//! Eth RPC interface for the light client.
use std::collections::BTreeSet;
use std::sync::Arc;
use jsonrpc_core::{Result, BoxFuture};
use jsonrpc_core::futures::{future, Future};
use jsonrpc_core::futures::future::Either;
use jsonrpc_macros::Trailing;
use light::cache::Cache as LightDataCache;
use light::client::LightChainClient;
use light::{cht, TransactionQueue};
use light::on_demand::{request, OnDemand};
use ethcore::account_provider::AccountProvider;
use ethcore::encoded;
use ethcore::filter::Filter as EthcoreFilter;
use ethcore::ids::BlockId;
use sync::LightSync;
use hash::{KECCAK_NULL_RLP, KECCAK_EMPTY_LIST_RLP};
use ethereum_types::U256;
use parking_lot::{RwLock, Mutex};
use rlp::Rlp;
use transaction::SignedTransaction;
use v1::impls::eth_filter::Filterable;
use v1::helpers::{errors, limit_logs};
use v1::helpers::{SyncPollFilter, PollManager};
use v1::helpers::light_fetch::{self, LightFetch};
use v1::traits::Eth;
use v1::types::{
RichBlock, Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo,
Transaction, CallRequest, Index, Filter, Log, Receipt, Work,
H64 as RpcH64, H256 as RpcH256, H160 as RpcH160, U256 as RpcU256,
};
use v1::metadata::Metadata;
const NO_INVALID_BACK_REFS: &str = "Fails only on invalid back-references; back-references here known to be valid; qed";
/// Light client `ETH` (and filter) RPC.
pub struct EthClient {
sync: Arc,
client: Arc,
on_demand: Arc,
transaction_queue: Arc>,
accounts: Arc,
cache: Arc>,
polls: Mutex>,
poll_lifetime: u32,
gas_price_percentile: usize,
}
impl EthClient {
fn num_to_id(num: BlockNumber) -> BlockId {
// Note: Here we treat `Pending` as `Latest`.
// Since light clients don't produce pending blocks
// (they don't have state) we can safely fallback to `Latest`.
match num {
BlockNumber::Num(n) => BlockId::Number(n),
BlockNumber::Earliest => BlockId::Earliest,
BlockNumber::Latest => BlockId::Latest,
BlockNumber::Pending => {
warn!("`Pending` is deprecated and may be removed in future versions. Falling back to `Latest`");
BlockId::Latest
}
}
}
}
impl Clone for EthClient {
fn clone(&self) -> Self {
// each instance should have its own poll manager.
EthClient {
sync: self.sync.clone(),
client: self.client.clone(),
on_demand: self.on_demand.clone(),
transaction_queue: self.transaction_queue.clone(),
accounts: self.accounts.clone(),
cache: self.cache.clone(),
polls: Mutex::new(PollManager::new(self.poll_lifetime)),
poll_lifetime: self.poll_lifetime,
gas_price_percentile: self.gas_price_percentile,
}
}
}
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.
pub fn new(
sync: Arc,
client: Arc,
on_demand: Arc,
transaction_queue: Arc>,
accounts: Arc,
cache: Arc>,
gas_price_percentile: usize,
poll_lifetime: u32
) -> Self {
EthClient {
sync,
client,
on_demand,
transaction_queue,
accounts,
cache,
polls: Mutex::new(PollManager::new(poll_lifetime)),
poll_lifetime,
gas_price_percentile,
}
}
/// 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(),
gas_price_percentile: self.gas_price_percentile,
}
}
// get a "rich" block structure. Fails on unknown block.
fn rich_block(&self, id: BlockId, include_txs: bool) -> BoxFuture {
let (on_demand, sync) = (self.on_demand.clone(), self.sync.clone());
let (client, engine) = (self.client.clone(), self.client.engine().clone());
// helper for filling out a rich block once we've got a block and a score.
let fill_rich = move |block: encoded::Block, score: Option| {
let header = block.decode_header();
let extra_info = engine.extra_info(&header);
RichBlock {
inner: Block {
hash: Some(header.hash().into()),
size: Some(block.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: Some(header.log_bloom().clone().into()),
timestamp: header.timestamp().into(),
difficulty: header.difficulty().clone().into(),
total_difficulty: score.map(Into::into),
seal_fields: header.seal().into_iter().cloned().map(Into::into).collect(),
uncles: block.uncle_hashes().into_iter().map(Into::into).collect(),
transactions: match include_txs {
true => BlockTransactions::Full(block.view().localized_transactions().into_iter().map(Transaction::from_localized).collect()),
_ => BlockTransactions::Hashes(block.transaction_hashes().into_iter().map(Into::into).collect()),
},
extra_data: Bytes::new(header.extra_data().clone()),
},
extra_info: extra_info
}
};
// get the block itself.
Box::new(self.fetcher().block(id).and_then(move |block| {
// then fetch the total difficulty (this is much easier after getting the block).
match client.score(id) {
Some(score) => Either::A(future::ok(fill_rich(block, Some(score)))),
None => {
// make a CHT request to fetch the chain score.
let req = cht::block_to_cht_number(block.number())
.and_then(|num| client.cht_root(num as usize))
.and_then(|root| request::HeaderProof::new(block.number(), root));
let req = match req {
Some(req) => req,
None => {
// somehow the genesis block slipped past other checks.
// return it now.
let score = client.block_header(BlockId::Number(0))
.expect("genesis always stored; qed")
.difficulty();
return Either::A(future::ok(fill_rich(block, Some(score))))
}
};
// three possible outcomes:
// - network is down.
// - we get a score, but our hash is non-canonical.
// - we get a score, and our hash is canonical.
let maybe_fut = sync.with_context(move |ctx| on_demand.request(ctx, req).expect(NO_INVALID_BACK_REFS));
match maybe_fut {
Some(fut) => Either::B(fut
.map(move |(hash, score)| {
let score = if hash == block.hash() {
Some(score)
} else {
None
};
fill_rich(block, score)
}).map_err(errors::on_demand_cancel)),
None => Either::A(future::err(errors::network_disabled())),
}
}
}
}))
}
}
impl Eth for EthClient {
type Metadata = Metadata;
fn protocol_version(&self) -> Result {
Ok(format!("{}", ::light::net::MAX_PROTOCOL_VERSION))
}
fn syncing(&self) -> Result {
if self.sync.is_major_importing() {
let chain_info = self.client.chain_info();
let current_block = U256::from(chain_info.best_block_number);
let highest_block = self.sync.highest_block().map(U256::from)
.unwrap_or_else(|| current_block);
Ok(SyncStatus::Info(SyncInfo {
starting_block: U256::from(self.sync.start_block()).into(),
current_block: current_block.into(),
highest_block: highest_block.into(),
warp_chunks_amount: None,
warp_chunks_processed: None,
}))
} else {
Ok(SyncStatus::None)
}
}
fn author(&self) -> Result {
self.accounts.accounts()
.ok()
.and_then(|a| a.first().cloned())
.map(From::from)
.ok_or_else(|| errors::account("No accounts were found", ""))
}
fn is_mining(&self) -> Result {
Ok(false)
}
fn hashrate(&self) -> Result {
Ok(Default::default())
}
fn gas_price(&self) -> Result {
Ok(self.cache.lock().gas_price_corpus()
.and_then(|c| c.percentile(self.gas_price_percentile).cloned())
.map(RpcU256::from)
.unwrap_or_else(Default::default))
}
fn accounts(&self) -> Result> {
self.accounts.accounts()
.map_err(|e| errors::account("Could not fetch accounts.", e))
.map(|accs| accs.into_iter().map(Into::::into).collect())
}
fn block_number(&self) -> Result {
Ok(self.client.chain_info().best_block_number.into())
}
fn balance(&self, address: RpcH160, num: Trailing) -> BoxFuture {
Box::new(self.fetcher().account(address.into(), Self::num_to_id(num.unwrap_or_default()))
.map(|acc| acc.map_or(0.into(), |a| a.balance).into()))
}
fn storage_at(&self, _address: RpcH160, _key: RpcU256, _num: Trailing) -> BoxFuture {
Box::new(future::err(errors::unimplemented(None)))
}
fn block_by_hash(&self, hash: RpcH256, include_txs: bool) -> BoxFuture