// 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 .
//! Eth RPC interface for the light client.
use std::{collections::BTreeSet, sync::Arc};
use jsonrpc_core::{
futures::{future, future::Either, Future},
BoxFuture, Result,
};
use light::{
cache::Cache as LightDataCache,
cht,
client::LightChainClient,
on_demand::{request, OnDemandRequester},
TransactionQueue,
};
use ethereum_types::{Address, H160, H256, H64, U256, U64};
use hash::{KECCAK_EMPTY_LIST_RLP, KECCAK_NULL_RLP};
use parking_lot::{Mutex, RwLock};
use rlp::Rlp;
use types::{
encoded, filter::Filter as EthcoreFilter, ids::BlockId, transaction::SignedTransaction,
};
use v1::{
helpers::{
deprecated::{self, DeprecationNotice},
errors,
light_fetch::{self, LightFetch},
limit_logs, PollManager, SyncPollFilter,
},
impls::eth_filter::Filterable,
metadata::Metadata,
traits::Eth,
types::{
Block, BlockNumber, BlockTransactions, Bytes, CallRequest, EthAccount, Filter, Index,
LightBlockNumber, Log, Receipt, RichBlock, SyncInfo as RpcSyncInfo,
SyncStatus as RpcSyncStatus, Transaction, Work,
},
};
use sync::{LightNetworkDispatcher, LightSyncInfo, LightSyncProvider, ManageNetwork};
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<
C,
S: LightSyncProvider + LightNetworkDispatcher + 'static,
OD: OnDemandRequester + 'static,
> {
sync: Arc,
client: Arc,
on_demand: Arc,
transaction_queue: Arc>,
accounts: Arc Vec + Send + Sync>,
cache: Arc>,
polls: Mutex>,
poll_lifetime: u32,
gas_price_percentile: usize,
deprecation_notice: DeprecationNotice,
}
impl Clone for EthClient
where
S: LightSyncProvider + LightNetworkDispatcher + 'static,
OD: OnDemandRequester + 'static,
{
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,
deprecation_notice: Default::default(),
}
}
}
impl EthClient
where
C: LightChainClient + 'static,
S: LightSyncProvider + LightNetworkDispatcher + ManageNetwork + 'static,
OD: OnDemandRequester + 'static,
{
/// 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 Vec + Send + Sync>,
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,
deprecation_notice: Default::default(),
}
}
/// 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()),
size: Some(block.rlp().as_raw().len().into()),
parent_hash: *header.parent_hash(),
uncles_hash: *header.uncles_hash(),
author: *header.author(),
miner: *header.author(),
state_root: *header.state_root(),
transactions_root: *header.transactions_root(),
receipts_root: *header.receipts_root(),
number: Some(header.number().into()),
gas_used: *header.gas_used(),
gas_limit: *header.gas_limit(),
logs_bloom: Some(*header.log_bloom()),
timestamp: header.timestamp().into(),
difficulty: *header.difficulty(),
total_difficulty: score.map(Into::into),
seal_fields: header.seal().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,
}
};
// 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_error),
),
None => Either::A(future::err(errors::network_disabled())),
}
}
}
}))
}
}
impl Eth for EthClient
where
C: LightChainClient + 'static,
S: LightSyncInfo + LightSyncProvider + LightNetworkDispatcher + ManageNetwork + 'static,
OD: OnDemandRequester + 'static,
{
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(RpcSyncStatus::Info(RpcSyncInfo {
starting_block: U256::from(self.sync.start_block()),
current_block,
highest_block,
warp_chunks_amount: None,
warp_chunks_processed: None,
}))
} else {
Ok(RpcSyncStatus::None)
}
}
fn author(&self) -> Result {
(self.accounts)()
.first()
.cloned()
.map(From::from)
.ok_or_else(|| errors::account("No accounts were found", ""))
}
fn is_mining(&self) -> Result {
Ok(false)
}
fn chain_id(&self) -> Result