RPC: parity_getBlockReceipts (#9527)

* Block receipts RPC.

* Use lazy evaluation of block receipts (ecrecover).

* Optimize transaction_receipt to prevent performance regression.

* Fix RPC grumbles.

* Add block & transaction receipt tests.

* Fix conversion to block id.
This commit is contained in:
Tomasz Drwięga
2018-09-25 19:06:14 +02:00
committed by Marek Kotewicz
parent 3f95a62e4f
commit cc963d42a0
18 changed files with 241 additions and 155 deletions

View File

@@ -29,7 +29,6 @@ use ethcore::account_provider::AccountProvider;
use ethcore::client::{BlockChainClient, BlockId, TransactionId, UncleId, StateOrBlock, StateClient, StateInfo, Call, EngineInfo};
use ethcore::filter::Filter as EthcoreFilter;
use ethcore::header::{BlockNumber as EthBlockNumber};
use ethcore::log_entry::LogEntry;
use ethcore::miner::{self, MinerService};
use ethcore::snapshot::SnapshotService;
use ethcore::encoded;
@@ -419,11 +418,11 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM, T: StateInfo + 'static> EthClient<C, SN, S
pub fn pending_logs<M>(miner: &M, best_block: EthBlockNumber, filter: &EthcoreFilter) -> Vec<Log> where M: MinerService {
let receipts = miner.pending_receipts(best_block).unwrap_or_default();
let pending_logs = receipts.into_iter()
.flat_map(|(hash, r)| r.logs.into_iter().map(|l| (hash.clone(), l)).collect::<Vec<(H256, LogEntry)>>())
.collect::<Vec<(H256, LogEntry)>>();
pending_logs.into_iter()
receipts.into_iter()
.flat_map(|r| {
let hash = r.transaction_hash;
r.logs.into_iter().map(move |l| (hash, l))
})
.filter(|pair| filter.matches(&pair.1))
.map(|pair| {
let mut log = Log::from(pair.1);
@@ -673,16 +672,17 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM, T: StateInfo + 'static> Eth for EthClient<
}
fn transaction_receipt(&self, hash: RpcH256) -> BoxFuture<Option<Receipt>> {
let best_block = self.client.chain_info().best_block_number;
let hash: H256 = hash.into();
match (self.miner.pending_receipt(best_block, &hash), self.options.allow_pending_receipt_query) {
(Some(receipt), true) => Box::new(future::ok(Some(receipt.into()))),
_ => {
let receipt = self.client.transaction_receipt(TransactionId::Hash(hash));
Box::new(future::ok(receipt.map(Into::into)))
if self.options.allow_pending_receipt_query {
let best_block = self.client.chain_info().best_block_number;
if let Some(receipt) = self.miner.pending_receipt(best_block, &hash) {
return Box::new(future::ok(Some(receipt.into())));
}
}
let receipt = self.client.transaction_receipt(TransactionId::Hash(hash));
Box::new(future::ok(receipt.map(Into::into)))
}
fn uncle_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> BoxFuture<Option<RichBlock>> {

View File

@@ -46,7 +46,7 @@ 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,
RichBlock, Block, BlockTransactions, BlockNumber, LightBlockNumber, Bytes, SyncStatus, SyncInfo,
Transaction, CallRequest, Index, Filter, Log, Receipt, Work,
H64 as RpcH64, H256 as RpcH256, H160 as RpcH160, U256 as RpcU256,
};
@@ -67,23 +67,6 @@ pub struct EthClient<T> {
gas_price_percentile: usize,
}
impl<T> EthClient<T> {
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<T> Clone for EthClient<T> {
fn clone(&self) -> Self {
// each instance should have its own poll manager.
@@ -285,7 +268,7 @@ impl<T: LightChainClient + 'static> Eth for EthClient<T> {
}
fn balance(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256> {
Box::new(self.fetcher().account(address.into(), Self::num_to_id(num.unwrap_or_default()))
Box::new(self.fetcher().account(address.into(), num.unwrap_or_default().to_block_id())
.map(|acc| acc.map_or(0.into(), |a| a.balance).into()))
}
@@ -298,11 +281,11 @@ impl<T: LightChainClient + 'static> Eth for EthClient<T> {
}
fn block_by_number(&self, num: BlockNumber, include_txs: bool) -> BoxFuture<Option<RichBlock>> {
Box::new(self.rich_block(Self::num_to_id(num), include_txs).map(Some))
Box::new(self.rich_block(num.to_block_id(), include_txs).map(Some))
}
fn transaction_count(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256> {
Box::new(self.fetcher().account(address.into(), Self::num_to_id(num.unwrap_or_default()))
Box::new(self.fetcher().account(address.into(), num.unwrap_or_default().to_block_id())
.map(|acc| acc.map_or(0.into(), |a| a.nonce).into()))
}
@@ -325,7 +308,7 @@ impl<T: LightChainClient + 'static> Eth for EthClient<T> {
fn block_transaction_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>> {
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone());
Box::new(self.fetcher().header(Self::num_to_id(num)).and_then(move |hdr| {
Box::new(self.fetcher().header(num.to_block_id()).and_then(move |hdr| {
if hdr.transactions_root() == KECCAK_NULL_RLP {
Either::A(future::ok(Some(U256::from(0).into())))
} else {
@@ -357,7 +340,7 @@ impl<T: LightChainClient + 'static> Eth for EthClient<T> {
fn block_uncles_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>> {
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone());
Box::new(self.fetcher().header(Self::num_to_id(num)).and_then(move |hdr| {
Box::new(self.fetcher().header(num.to_block_id()).and_then(move |hdr| {
if hdr.uncles_hash() == KECCAK_EMPTY_LIST_RLP {
Either::B(future::ok(Some(U256::from(0).into())))
} else {
@@ -371,7 +354,7 @@ impl<T: LightChainClient + 'static> Eth for EthClient<T> {
}
fn code_at(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<Bytes> {
Box::new(self.fetcher().code(address.into(), Self::num_to_id(num.unwrap_or_default())).map(Into::into))
Box::new(self.fetcher().code(address.into(), num.unwrap_or_default().to_block_id()).map(Into::into))
}
fn send_raw_transaction(&self, raw: Bytes) -> Result<RpcH256> {
@@ -438,7 +421,7 @@ impl<T: LightChainClient + 'static> Eth for EthClient<T> {
}
fn transaction_by_block_number_and_index(&self, num: BlockNumber, idx: Index) -> BoxFuture<Option<Transaction>> {
Box::new(self.fetcher().block(Self::num_to_id(num)).map(move |block| {
Box::new(self.fetcher().block(num.to_block_id()).map(move |block| {
light_fetch::extract_transaction_at_index(block, idx.value())
}))
}
@@ -482,7 +465,7 @@ impl<T: LightChainClient + 'static> Eth for EthClient<T> {
fn uncle_by_block_number_and_index(&self, num: BlockNumber, idx: Index) -> BoxFuture<Option<RichBlock>> {
let client = self.client.clone();
Box::new(self.fetcher().block(Self::num_to_id(num)).map(move |block| {
Box::new(self.fetcher().block(num.to_block_id()).map(move |block| {
extract_uncle_at_index(block, idx, client)
}))
}

View File

@@ -26,7 +26,6 @@ use ethstore::random_phrase;
use sync::LightSyncProvider;
use ethcore::account_provider::AccountProvider;
use ethcore_logger::RotatingLogger;
use ethcore::ids::BlockId;
use light::client::LightChainClient;
@@ -42,9 +41,9 @@ use v1::types::{
Bytes, U256, U64, H160, H256, H512, CallRequest,
Peers, Transaction, RpcSettings, Histogram,
TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo,
BlockNumber, LightBlockNumber, ConsensusCapability, VersionInfo,
OperationsInfo, ChainStatus,
AccountInfo, HwAccountInfo, Header, RichHeader,
AccountInfo, HwAccountInfo, Header, RichHeader, Receipt,
};
use Host;
@@ -403,18 +402,15 @@ impl Parity for ParityClient {
extra_info: extra_info,
})
};
// 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`.
let id = match number.unwrap_or_default() {
BlockNumber::Num(n) => BlockId::Number(n),
BlockNumber::Earliest => BlockId::Earliest,
BlockNumber::Latest | BlockNumber::Pending => BlockId::Latest,
};
let id = number.unwrap_or_default().to_block_id();
Box::new(self.fetcher().header(id).and_then(from_encoded))
}
fn block_receipts(&self, number: Trailing<BlockNumber>) -> BoxFuture<Vec<Receipt>> {
let id = number.unwrap_or_default().to_block_id();
Box::new(self.fetcher().receipts(id).and_then(|receipts| Ok(receipts.into_iter().map(Into::into).collect())))
}
fn ipfs_cid(&self, content: Bytes) -> Result<String> {
ipfs::cid(content)
}

View File

@@ -45,7 +45,7 @@ use v1::types::{
TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo,
OperationsInfo, ChainStatus,
AccountInfo, HwAccountInfo, RichHeader,
AccountInfo, HwAccountInfo, RichHeader, Receipt,
block_number_to_id
};
use Host;
@@ -332,7 +332,7 @@ impl<C, M, U, S> Parity for ParityClient<C, M, U> where
fn ws_url(&self) -> Result<String> {
helpers::to_url(&self.ws_address)
.ok_or_else(|| errors::ws_disabled())
.ok_or_else(errors::ws_disabled)
}
fn next_nonce(&self, address: H160) -> BoxFuture<U256> {
@@ -387,7 +387,8 @@ impl<C, M, U, S> Parity for ParityClient<C, M, U> where
let (header, extra) = if number == BlockNumber::Pending {
let info = self.client.chain_info();
let header = try_bf!(self.miner.pending_block_header(info.best_block_number).ok_or(errors::unknown_block()));
let header =
try_bf!(self.miner.pending_block_header(info.best_block_number).ok_or_else(errors::unknown_block));
(header.encoded(), None)
} else {
@@ -398,7 +399,7 @@ impl<C, M, U, S> Parity for ParityClient<C, M, U> where
BlockNumber::Pending => unreachable!(), // Already covered
};
let header = try_bf!(self.client.block_header(id.clone()).ok_or(errors::unknown_block()));
let header = try_bf!(self.client.block_header(id.clone()).ok_or_else(errors::unknown_block));
let info = self.client.block_extra_info(id).expect(EXTRA_INFO_PROOF);
(header, Some(info))
@@ -410,6 +411,27 @@ impl<C, M, U, S> Parity for ParityClient<C, M, U> where
}))
}
fn block_receipts(&self, number: Trailing<BlockNumber>) -> BoxFuture<Vec<Receipt>> {
let number = number.unwrap_or_default();
let id = match number {
BlockNumber::Pending => {
let info = self.client.chain_info();
let receipts = try_bf!(self.miner.pending_receipts(info.best_block_number).ok_or_else(errors::unknown_block));
return Box::new(future::ok(receipts
.into_iter()
.map(Into::into)
.collect()
))
},
BlockNumber::Num(num) => BlockId::Number(num),
BlockNumber::Earliest => BlockId::Earliest,
BlockNumber::Latest => BlockId::Latest,
};
let receipts = try_bf!(self.client.block_receipts(id).ok_or_else(errors::unknown_block));
Box::new(future::ok(receipts.into_iter().map(Into::into).collect()))
}
fn ipfs_cid(&self, content: Bytes) -> Result<String> {
ipfs::cid(content)
}
@@ -427,8 +449,8 @@ impl<C, M, U, S> Parity for ParityClient<C, M, U> where
let (mut state, header) = if num == BlockNumber::Pending {
let info = self.client.chain_info();
let state = self.miner.pending_state(info.best_block_number).ok_or(errors::state_pruned())?;
let header = self.miner.pending_block_header(info.best_block_number).ok_or(errors::state_pruned())?;
let state = self.miner.pending_state(info.best_block_number).ok_or_else(errors::state_pruned)?;
let header = self.miner.pending_block_header(info.best_block_number).ok_or_else(errors::state_pruned)?;
(state, header)
} else {
@@ -439,8 +461,8 @@ impl<C, M, U, S> Parity for ParityClient<C, M, U> where
BlockNumber::Pending => unreachable!(), // Already covered
};
let state = self.client.state_at(id).ok_or(errors::state_pruned())?;
let header = self.client.block_header(id).ok_or(errors::state_pruned())?.decode().map_err(errors::decode)?;
let state = self.client.state_at(id).ok_or_else(errors::state_pruned)?;
let header = self.client.block_header(id).ok_or_else(errors::state_pruned)?.decode().map_err(errors::decode)?;
(state, header)
};

View File

@@ -28,7 +28,7 @@ use ethcore::error::Error;
use ethcore::header::{BlockNumber, Header};
use ethcore::ids::BlockId;
use ethcore::miner::{self, MinerService, AuthoringParams};
use ethcore::receipt::{Receipt, RichReceipt};
use ethcore::receipt::RichReceipt;
use ethereum_types::{H256, U256, Address};
use miner::pool::local_transactions::Status as LocalTransactionStatus;
use miner::pool::{verifier, VerifiedTransaction, QueueStatus};
@@ -46,7 +46,7 @@ pub struct TestMinerService {
/// Pre-existed local transactions
pub local_transactions: Mutex<BTreeMap<H256, LocalTransactionStatus>>,
/// Pre-existed pending receipts
pub pending_receipts: Mutex<BTreeMap<H256, Receipt>>,
pub pending_receipts: Mutex<Vec<RichReceipt>>,
/// Next nonces.
pub next_nonces: RwLock<HashMap<Address, U256>>,
/// Password held by Engine.
@@ -58,11 +58,11 @@ pub struct TestMinerService {
impl Default for TestMinerService {
fn default() -> TestMinerService {
TestMinerService {
imported_transactions: Mutex::new(Vec::new()),
pending_transactions: Mutex::new(HashMap::new()),
local_transactions: Mutex::new(BTreeMap::new()),
pending_receipts: Mutex::new(BTreeMap::new()),
next_nonces: RwLock::new(HashMap::new()),
imported_transactions: Default::default(),
pending_transactions: Default::default(),
local_transactions: Default::default(),
pending_receipts: Default::default(),
next_nonces: Default::default(),
password: RwLock::new("".into()),
authoring_params: RwLock::new(AuthoringParams {
author: Address::zero(),
@@ -230,23 +230,7 @@ impl MinerService for TestMinerService {
}).collect()
}
fn pending_receipt(&self, _best_block: BlockNumber, hash: &H256) -> Option<RichReceipt> {
// Not much point implementing this since the logic is complex and the only thing it relies on is pending_receipts, which is already tested.
self.pending_receipts(0).unwrap().get(hash).map(|r|
RichReceipt {
transaction_hash: Default::default(),
transaction_index: Default::default(),
cumulative_gas_used: r.gas_used.clone(),
gas_used: r.gas_used.clone(),
contract_address: None,
logs: r.logs.clone(),
log_bloom: r.log_bloom,
outcome: r.outcome.clone(),
}
)
}
fn pending_receipts(&self, _best_block: BlockNumber) -> Option<BTreeMap<H256, Receipt>> {
fn pending_receipts(&self, _best_block: BlockNumber) -> Option<Vec<RichReceipt>> {
Some(self.pending_receipts.lock().clone())
}

View File

@@ -16,7 +16,8 @@
use std::sync::Arc;
use ethcore::account_provider::AccountProvider;
use ethcore::client::{TestBlockChainClient, Executed};
use ethcore::client::{TestBlockChainClient, Executed, TransactionId};
use ethcore::receipt::{LocalizedReceipt, TransactionOutcome};
use ethcore_logger::RotatingLogger;
use ethereum_types::{Address, U256, H256};
use ethstore::ethkey::{Generator, Random};
@@ -531,3 +532,34 @@ fn rpc_parity_call() {
assert_eq!(io.handle_request_sync(request), Some(response.to_owned()));
}
#[test]
fn rpc_parity_block_receipts() {
let deps = Dependencies::new();
deps.client.receipts.write()
.insert(TransactionId::Hash(1.into()), LocalizedReceipt {
transaction_hash: 1.into(),
transaction_index: 0,
block_hash: 3.into(),
block_number: 0,
cumulative_gas_used: 21_000.into(),
gas_used: 21_000.into(),
contract_address: None,
logs: vec![],
log_bloom: 1.into(),
outcome: TransactionOutcome::Unknown,
to: None,
from: 9.into(),
});
let io = deps.default_client();
let request = r#"{
"jsonrpc": "2.0",
"method": "parity_getBlockReceipts",
"params": [],
"id": 1
}"#;
let response = r#"{"jsonrpc":"2.0","result":[{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000003","blockNumber":"0x0","contractAddress":null,"cumulativeGasUsed":"0x5208","from":"0x0000000000000000000000000000000000000009","gasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001","root":null,"status":null,"to":null,"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000001","transactionIndex":"0x0"}],"id":1}"#;
assert_eq!(io.handle_request_sync(request), Some(response.to_owned()));
}

View File

@@ -27,7 +27,7 @@ use v1::types::{
TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo,
OperationsInfo, ChainStatus,
AccountInfo, HwAccountInfo, RichHeader,
AccountInfo, HwAccountInfo, RichHeader, Receipt,
};
build_rpc_trait! {
@@ -211,6 +211,12 @@ build_rpc_trait! {
#[rpc(name = "parity_getBlockHeaderByNumber")]
fn block_header(&self, Trailing<BlockNumber>) -> BoxFuture<RichHeader>;
/// Get block receipts.
/// Allows you to fetch receipts from the entire block at once.
/// If no parameter is provided defaults to `latest`.
#[rpc(name = "parity_getBlockReceipts")]
fn block_receipts(&self, Trailing<BlockNumber>) -> BoxFuture<Vec<Receipt>>;
/// Get IPFS CIDv0 given protobuf encoded bytes.
#[rpc(name = "parity_cidV0")]
fn ipfs_cid(&self, Bytes) -> Result<String>;

View File

@@ -54,6 +54,31 @@ impl BlockNumber {
}
}
/// BlockNumber to BlockId conversion
///
/// NOTE use only for light clients.
pub trait LightBlockNumber {
/// Convert block number to block id.
fn to_block_id(self) -> BlockId;
}
impl LightBlockNumber for BlockNumber {
fn to_block_id(self) -> 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 self {
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 Serialize for BlockNumber {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
match *self {

View File

@@ -49,7 +49,7 @@ pub mod pubsub;
pub use self::account_info::{AccountInfo, ExtAccountInfo, HwAccountInfo};
pub use self::bytes::Bytes;
pub use self::block::{RichBlock, Block, BlockTransactions, Header, RichHeader, Rich};
pub use self::block_number::{BlockNumber, block_number_to_id};
pub use self::block_number::{BlockNumber, LightBlockNumber, block_number_to_id};
pub use self::call_request::CallRequest;
pub use self::confirmations::{
ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken,