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

@ -176,7 +176,7 @@ impl<T: ProvingBlockChainClient + ?Sized> Provider for T {
} }
fn block_receipts(&self, req: request::CompleteReceiptsRequest) -> Option<request::ReceiptsResponse> { fn block_receipts(&self, req: request::CompleteReceiptsRequest) -> Option<request::ReceiptsResponse> {
BlockChainClient::block_receipts(self, &req.hash) BlockChainClient::encoded_block_receipts(self, &req.hash)
.map(|x| ::request::ReceiptsResponse { receipts: ::rlp::decode_list(&x) }) .map(|x| ::request::ReceiptsResponse { receipts: ::rlp::decode_list(&x) })
} }

View File

@ -160,11 +160,6 @@ pub trait BlockProvider {
.and_then(|n| body.view().localized_transaction_at(&address.block_hash, n, address.index))) .and_then(|n| body.view().localized_transaction_at(&address.block_hash, n, address.index)))
} }
/// Get transaction receipt.
fn transaction_receipt(&self, address: &TransactionAddress) -> Option<Receipt> {
self.block_receipts(&address.block_hash).and_then(|br| br.receipts.into_iter().nth(address.index))
}
/// Get a list of transactions for a given block. /// Get a list of transactions for a given block.
/// Returns None if block does not exist. /// Returns None if block does not exist.
fn transactions(&self, hash: &H256) -> Option<Vec<LocalizedTransaction>> { fn transactions(&self, hash: &H256) -> Option<Vec<LocalizedTransaction>> {

View File

@ -1792,26 +1792,49 @@ impl BlockChainClient for Client {
} }
fn transaction_receipt(&self, id: TransactionId) -> Option<LocalizedReceipt> { fn transaction_receipt(&self, id: TransactionId) -> Option<LocalizedReceipt> {
// NOTE Don't use block_receipts here for performance reasons
let address = self.transaction_address(id)?;
let hash = address.block_hash;
let chain = self.chain.read(); let chain = self.chain.read();
self.transaction_address(id) let number = chain.block_number(&hash)?;
.and_then(|address| chain.block_number(&address.block_hash).and_then(|block_number| { let body = chain.block_body(&hash)?;
let transaction = chain.block_body(&address.block_hash) let mut receipts = chain.block_receipts(&hash)?.receipts;
.and_then(|body| body.view().localized_transaction_at(&address.block_hash, block_number, address.index)); receipts.truncate(address.index + 1);
let previous_receipts = (0..address.index + 1) let transaction = body.view().localized_transaction_at(&hash, number, address.index)?;
.map(|index| { let receipt = receipts.pop()?;
let mut address = address.clone(); let gas_used = receipts.last().map_or_else(|| 0.into(), |r| r.gas_used);
address.index = index; let no_of_logs = receipts.into_iter().map(|receipt| receipt.logs.len()).sum::<usize>();
chain.transaction_receipt(&address)
}) let receipt = transaction_receipt(self.engine().machine(), transaction, receipt, gas_used, no_of_logs);
.collect(); Some(receipt)
match (transaction, previous_receipts) { }
(Some(transaction), Some(previous_receipts)) => {
Some(transaction_receipt(self.engine().machine(), transaction, previous_receipts)) fn block_receipts(&self, id: BlockId) -> Option<Vec<LocalizedReceipt>> {
}, let hash = self.block_hash(id)?;
_ => None,
} let chain = self.chain.read();
})) let receipts = chain.block_receipts(&hash)?;
let number = chain.block_number(&hash)?;
let body = chain.block_body(&hash)?;
let engine = self.engine.clone();
let mut gas_used = 0.into();
let mut no_of_logs = 0;
Some(body
.view()
.localized_transactions(&hash, number)
.into_iter()
.zip(receipts.receipts)
.map(move |(transaction, receipt)| {
let result = transaction_receipt(engine.machine(), transaction, receipt, gas_used, no_of_logs);
gas_used = result.cumulative_gas_used;
no_of_logs += result.logs.len();
result
})
.collect()
)
} }
fn tree_route(&self, from: &H256, to: &H256) -> Option<TreeRoute> { fn tree_route(&self, from: &H256, to: &H256) -> Option<TreeRoute> {
@ -1830,7 +1853,7 @@ impl BlockChainClient for Client {
self.state_db.read().journal_db().state(hash) self.state_db.read().journal_db().state(hash)
} }
fn block_receipts(&self, hash: &H256) -> Option<Bytes> { fn encoded_block_receipts(&self, hash: &H256) -> Option<Bytes> {
self.chain.read().block_receipts(hash).map(|receipts| ::rlp::encode(&receipts).into_vec()) self.chain.read().block_receipts(hash).map(|receipts| ::rlp::encode(&receipts).into_vec())
} }
@ -2385,16 +2408,14 @@ impl Drop for Client {
/// Returns `LocalizedReceipt` given `LocalizedTransaction` /// Returns `LocalizedReceipt` given `LocalizedTransaction`
/// and a vector of receipts from given block up to transaction index. /// and a vector of receipts from given block up to transaction index.
fn transaction_receipt(machine: &::machine::EthereumMachine, mut tx: LocalizedTransaction, mut receipts: Vec<Receipt>) -> LocalizedReceipt { fn transaction_receipt(
assert_eq!(receipts.len(), tx.transaction_index + 1, "All previous receipts are provided."); machine: &::machine::EthereumMachine,
mut tx: LocalizedTransaction,
receipt: Receipt,
prior_gas_used: U256,
prior_no_of_logs: usize,
) -> LocalizedReceipt {
let sender = tx.sender(); let sender = tx.sender();
let receipt = receipts.pop().expect("Current receipt is provided; qed");
let prior_gas_used = match tx.transaction_index {
0 => 0.into(),
i => receipts.get(i - 1).expect("All previous receipts are provided; qed").gas_used,
};
let no_of_logs = receipts.into_iter().map(|receipt| receipt.logs.len()).sum::<usize>();
let transaction_hash = tx.hash(); let transaction_hash = tx.hash();
let block_hash = tx.block_hash; let block_hash = tx.block_hash;
let block_number = tx.block_number; let block_number = tx.block_number;
@ -2423,7 +2444,7 @@ fn transaction_receipt(machine: &::machine::EthereumMachine, mut tx: LocalizedTr
transaction_hash: transaction_hash, transaction_hash: transaction_hash,
transaction_index: transaction_index, transaction_index: transaction_index,
transaction_log_index: i, transaction_log_index: i,
log_index: no_of_logs + i, log_index: prior_no_of_logs + i,
}).collect(), }).collect(),
log_bloom: receipt.log_bloom, log_bloom: receipt.log_bloom,
outcome: receipt.outcome, outcome: receipt.outcome,
@ -2471,6 +2492,33 @@ mod tests {
assert!(client.tree_route(&genesis, &new_hash).is_none()); assert!(client.tree_route(&genesis, &new_hash).is_none());
} }
#[test]
fn should_return_block_receipts() {
use client::{BlockChainClient, BlockId, TransactionId};
use test_helpers::{generate_dummy_client_with_data};
let client = generate_dummy_client_with_data(2, 2, &[1.into(), 1.into()]);
let receipts = client.block_receipts(BlockId::Latest).unwrap();
assert_eq!(receipts.len(), 2);
assert_eq!(receipts[0].transaction_index, 0);
assert_eq!(receipts[0].block_number, 2);
assert_eq!(receipts[0].cumulative_gas_used, 53_000.into());
assert_eq!(receipts[0].gas_used, 53_000.into());
assert_eq!(receipts[1].transaction_index, 1);
assert_eq!(receipts[1].block_number, 2);
assert_eq!(receipts[1].cumulative_gas_used, 106_000.into());
assert_eq!(receipts[1].gas_used, 53_000.into());
let receipt = client.transaction_receipt(TransactionId::Hash(receipts[0].transaction_hash));
assert_eq!(receipt, Some(receipts[0].clone()));
let receipt = client.transaction_receipt(TransactionId::Hash(receipts[1].transaction_hash));
assert_eq!(receipt, Some(receipts[1].clone()));
}
#[test] #[test]
fn should_return_correct_log_index() { fn should_return_correct_log_index() {
use hash::keccak; use hash::keccak;
@ -2514,20 +2562,15 @@ mod tests {
topics: vec![], topics: vec![],
data: vec![], data: vec![],
}]; }];
let receipts = vec![Receipt { let receipt = Receipt {
outcome: TransactionOutcome::StateRoot(state_root),
gas_used: 5.into(),
log_bloom: Default::default(),
logs: vec![logs[0].clone()],
}, Receipt {
outcome: TransactionOutcome::StateRoot(state_root), outcome: TransactionOutcome::StateRoot(state_root),
gas_used: gas_used, gas_used: gas_used,
log_bloom: Default::default(), log_bloom: Default::default(),
logs: logs.clone(), logs: logs.clone(),
}]; };
// when // when
let receipt = transaction_receipt(&machine, transaction, receipts); let receipt = transaction_receipt(&machine, transaction, receipt, 5.into(), 1);
// then // then
assert_eq!(receipt, LocalizedReceipt { assert_eq!(receipt, LocalizedReceipt {

View File

@ -687,6 +687,10 @@ impl BlockChainClient for TestBlockChainClient {
self.receipts.read().get(&id).cloned() self.receipts.read().get(&id).cloned()
} }
fn block_receipts(&self, _id: BlockId) -> Option<Vec<LocalizedReceipt>> {
Some(self.receipts.read().values().cloned().collect())
}
fn logs(&self, filter: Filter) -> Result<Vec<LocalizedLogEntry>, BlockId> { fn logs(&self, filter: Filter) -> Result<Vec<LocalizedLogEntry>, BlockId> {
match self.error_on_logs.read().as_ref() { match self.error_on_logs.read().as_ref() {
Some(id) => return Err(id.clone()), Some(id) => return Err(id.clone()),
@ -786,7 +790,7 @@ impl BlockChainClient for TestBlockChainClient {
None None
} }
fn block_receipts(&self, hash: &H256) -> Option<Bytes> { fn encoded_block_receipts(&self, hash: &H256) -> Option<Bytes> {
// starts with 'f' ? // starts with 'f' ?
if *hash > H256::from("f000000000000000000000000000000000000000000000000000000000000000") { if *hash > H256::from("f000000000000000000000000000000000000000000000000000000000000000") {
let receipt = BlockReceipts::new(vec![Receipt::new( let receipt = BlockReceipts::new(vec![Receipt::new(

View File

@ -281,6 +281,9 @@ pub trait BlockChainClient : Sync + Send + AccountData + BlockChain + CallContra
/// Get transaction receipt with given hash. /// Get transaction receipt with given hash.
fn transaction_receipt(&self, id: TransactionId) -> Option<LocalizedReceipt>; fn transaction_receipt(&self, id: TransactionId) -> Option<LocalizedReceipt>;
/// Get localized receipts for all transaction in given block.
fn block_receipts(&self, id: BlockId) -> Option<Vec<LocalizedReceipt>>;
/// Get a tree route between `from` and `to`. /// Get a tree route between `from` and `to`.
/// See `BlockChain::tree_route`. /// See `BlockChain::tree_route`.
fn tree_route(&self, from: &H256, to: &H256) -> Option<TreeRoute>; fn tree_route(&self, from: &H256, to: &H256) -> Option<TreeRoute>;
@ -292,7 +295,7 @@ pub trait BlockChainClient : Sync + Send + AccountData + BlockChain + CallContra
fn state_data(&self, hash: &H256) -> Option<Bytes>; fn state_data(&self, hash: &H256) -> Option<Bytes>;
/// Get raw block receipts data by block header hash. /// Get raw block receipts data by block header hash.
fn block_receipts(&self, hash: &H256) -> Option<Bytes>; fn encoded_block_receipts(&self, hash: &H256) -> Option<Bytes>;
/// Get block queue information. /// Get block queue information.
fn queue_info(&self) -> BlockQueueInfo; fn queue_info(&self) -> BlockQueueInfo;

View File

@ -50,7 +50,7 @@ use executive::contract_address;
use header::{Header, BlockNumber}; use header::{Header, BlockNumber};
use miner; use miner;
use miner::pool_client::{PoolClient, CachedNonceClient, NonceCache}; use miner::pool_client::{PoolClient, CachedNonceClient, NonceCache};
use receipt::{Receipt, RichReceipt}; use receipt::RichReceipt;
use spec::Spec; use spec::Spec;
use state::State; use state::State;
use ethkey::Password; use ethkey::Password;
@ -1039,19 +1039,17 @@ impl miner::MinerService for Miner {
self.transaction_queue.status() self.transaction_queue.status()
} }
fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option<RichReceipt> { fn pending_receipts(&self, best_block: BlockNumber) -> Option<Vec<RichReceipt>> {
self.map_existing_pending_block(|pending| { self.map_existing_pending_block(|pending| {
let txs = pending.transactions(); let receipts = pending.receipts();
txs.iter() pending.transactions()
.map(|t| t.hash()) .into_iter()
.position(|t| t == *hash) .enumerate()
.map(|index| { .map(|(index, tx)| {
let receipts = pending.receipts();
let prev_gas = if index == 0 { Default::default() } else { receipts[index - 1].gas_used }; let prev_gas = if index == 0 { Default::default() } else { receipts[index - 1].gas_used };
let tx = &txs[index];
let receipt = &receipts[index]; let receipt = &receipts[index];
RichReceipt { RichReceipt {
transaction_hash: hash.clone(), transaction_hash: tx.hash(),
transaction_index: index, transaction_index: index,
cumulative_gas_used: receipt.gas_used, cumulative_gas_used: receipt.gas_used,
gas_used: receipt.gas_used - prev_gas, gas_used: receipt.gas_used - prev_gas,
@ -1067,15 +1065,7 @@ impl miner::MinerService for Miner {
outcome: receipt.outcome.clone(), outcome: receipt.outcome.clone(),
} }
}) })
}, best_block).and_then(|x| x) .collect()
}
fn pending_receipts(&self, best_block: BlockNumber) -> Option<BTreeMap<H256, Receipt>> {
self.map_existing_pending_block(|pending| {
let hashes = pending.transactions().iter().map(|t| t.hash());
let receipts = pending.receipts().iter().cloned();
hashes.zip(receipts).collect()
}, best_block) }, best_block)
} }

View File

@ -44,7 +44,7 @@ use client::{
}; };
use error::Error; use error::Error;
use header::{BlockNumber, Header}; use header::{BlockNumber, Header};
use receipt::{RichReceipt, Receipt}; use receipt::RichReceipt;
use transaction::{self, UnverifiedTransaction, SignedTransaction, PendingTransaction}; use transaction::{self, UnverifiedTransaction, SignedTransaction, PendingTransaction};
use state::StateInfo; use state::StateInfo;
use ethkey::Password; use ethkey::Password;
@ -95,10 +95,13 @@ pub trait MinerService : Send + Sync {
// Pending block // Pending block
/// Get a list of all pending receipts from pending block. /// Get a list of all pending receipts from pending block.
fn pending_receipts(&self, best_block: BlockNumber) -> Option<BTreeMap<H256, Receipt>>; fn pending_receipts(&self, best_block: BlockNumber) -> Option<Vec<RichReceipt>>;
/// Get a particular receipt from pending block. /// Get a particular receipt from pending block.
fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option<RichReceipt>; fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option<RichReceipt> {
let receipts = self.pending_receipts(best_block)?;
receipts.into_iter().find(|r| &r.transaction_hash == hash)
}
/// Get `Some` `clone()` of the current pending block's state or `None` if we're not sealing. /// Get `Some` `clone()` of the current pending block's state or `None` if we're not sealing.
fn pending_state(&self, latest_block_number: BlockNumber) -> Option<Self::State>; fn pending_state(&self, latest_block_number: BlockNumber) -> Option<Self::State>;

View File

@ -226,7 +226,7 @@ impl SyncSupplier {
let mut added_receipts = 0usize; let mut added_receipts = 0usize;
let mut data = Bytes::new(); let mut data = Bytes::new();
for i in 0..count { for i in 0..count {
if let Some(mut receipts_bytes) = io.chain().block_receipts(&rlp.val_at::<H256>(i)?) { if let Some(mut receipts_bytes) = io.chain().encoded_block_receipts(&rlp.val_at::<H256>(i)?) {
data.append(&mut receipts_bytes); data.append(&mut receipts_bytes);
added_receipts += receipts_bytes.len(); added_receipts += receipts_bytes.len();
added_headers += 1; added_headers += 1;

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::client::{BlockChainClient, BlockId, TransactionId, UncleId, StateOrBlock, StateClient, StateInfo, Call, EngineInfo};
use ethcore::filter::Filter as EthcoreFilter; use ethcore::filter::Filter as EthcoreFilter;
use ethcore::header::{BlockNumber as EthBlockNumber}; use ethcore::header::{BlockNumber as EthBlockNumber};
use ethcore::log_entry::LogEntry;
use ethcore::miner::{self, MinerService}; use ethcore::miner::{self, MinerService};
use ethcore::snapshot::SnapshotService; use ethcore::snapshot::SnapshotService;
use ethcore::encoded; 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 { 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 receipts = miner.pending_receipts(best_block).unwrap_or_default();
let pending_logs = receipts.into_iter() receipts.into_iter()
.flat_map(|(hash, r)| r.logs.into_iter().map(|l| (hash.clone(), l)).collect::<Vec<(H256, LogEntry)>>()) .flat_map(|r| {
.collect::<Vec<(H256, LogEntry)>>(); let hash = r.transaction_hash;
r.logs.into_iter().map(move |l| (hash, l))
pending_logs.into_iter() })
.filter(|pair| filter.matches(&pair.1)) .filter(|pair| filter.matches(&pair.1))
.map(|pair| { .map(|pair| {
let mut log = Log::from(pair.1); 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>> { fn transaction_receipt(&self, hash: RpcH256) -> BoxFuture<Option<Receipt>> {
let best_block = self.client.chain_info().best_block_number;
let hash: H256 = hash.into(); let hash: H256 = hash.into();
match (self.miner.pending_receipt(best_block, &hash), self.options.allow_pending_receipt_query) { if self.options.allow_pending_receipt_query {
(Some(receipt), true) => Box::new(future::ok(Some(receipt.into()))), let best_block = self.client.chain_info().best_block_number;
_ => { if let Some(receipt) = self.miner.pending_receipt(best_block, &hash) {
let receipt = self.client.transaction_receipt(TransactionId::Hash(hash)); return Box::new(future::ok(Some(receipt.into())));
Box::new(future::ok(receipt.map(Into::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>> { 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::helpers::light_fetch::{self, LightFetch};
use v1::traits::Eth; use v1::traits::Eth;
use v1::types::{ 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, Transaction, CallRequest, Index, Filter, Log, Receipt, Work,
H64 as RpcH64, H256 as RpcH256, H160 as RpcH160, U256 as RpcU256, H64 as RpcH64, H256 as RpcH256, H160 as RpcH160, U256 as RpcU256,
}; };
@ -67,23 +67,6 @@ pub struct EthClient<T> {
gas_price_percentile: usize, 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> { impl<T> Clone for EthClient<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
// each instance should have its own poll manager. // 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> { 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())) .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>> { 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> { 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())) .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>> { fn block_transaction_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>> {
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); 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 { if hdr.transactions_root() == KECCAK_NULL_RLP {
Either::A(future::ok(Some(U256::from(0).into()))) Either::A(future::ok(Some(U256::from(0).into())))
} else { } 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>> { fn block_uncles_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>> {
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); 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 { if hdr.uncles_hash() == KECCAK_EMPTY_LIST_RLP {
Either::B(future::ok(Some(U256::from(0).into()))) Either::B(future::ok(Some(U256::from(0).into())))
} else { } else {
@ -371,7 +354,7 @@ impl<T: LightChainClient + 'static> Eth for EthClient<T> {
} }
fn code_at(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<Bytes> { 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> { 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>> { 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()) 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>> { fn uncle_by_block_number_and_index(&self, num: BlockNumber, idx: Index) -> BoxFuture<Option<RichBlock>> {
let client = self.client.clone(); 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) extract_uncle_at_index(block, idx, client)
})) }))
} }

View File

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

View File

@ -45,7 +45,7 @@ use v1::types::{
TransactionStats, LocalTransactionStatus, TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo, BlockNumber, ConsensusCapability, VersionInfo,
OperationsInfo, ChainStatus, OperationsInfo, ChainStatus,
AccountInfo, HwAccountInfo, RichHeader, AccountInfo, HwAccountInfo, RichHeader, Receipt,
block_number_to_id block_number_to_id
}; };
use Host; use Host;
@ -332,7 +332,7 @@ impl<C, M, U, S> Parity for ParityClient<C, M, U> where
fn ws_url(&self) -> Result<String> { fn ws_url(&self) -> Result<String> {
helpers::to_url(&self.ws_address) 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> { 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 (header, extra) = if number == BlockNumber::Pending {
let info = self.client.chain_info(); 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) (header.encoded(), None)
} else { } else {
@ -398,7 +399,7 @@ impl<C, M, U, S> Parity for ParityClient<C, M, U> where
BlockNumber::Pending => unreachable!(), // Already covered 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); let info = self.client.block_extra_info(id).expect(EXTRA_INFO_PROOF);
(header, Some(info)) (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> { fn ipfs_cid(&self, content: Bytes) -> Result<String> {
ipfs::cid(content) 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 (mut state, header) = if num == BlockNumber::Pending {
let info = self.client.chain_info(); let info = self.client.chain_info();
let state = self.miner.pending_state(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(errors::state_pruned())?; let header = self.miner.pending_block_header(info.best_block_number).ok_or_else(errors::state_pruned)?;
(state, header) (state, header)
} else { } else {
@ -439,8 +461,8 @@ impl<C, M, U, S> Parity for ParityClient<C, M, U> where
BlockNumber::Pending => unreachable!(), // Already covered BlockNumber::Pending => unreachable!(), // Already covered
}; };
let state = self.client.state_at(id).ok_or(errors::state_pruned())?; let state = self.client.state_at(id).ok_or_else(errors::state_pruned)?;
let header = self.client.block_header(id).ok_or(errors::state_pruned())?.decode().map_err(errors::decode)?; let header = self.client.block_header(id).ok_or_else(errors::state_pruned)?.decode().map_err(errors::decode)?;
(state, header) (state, header)
}; };

View File

@ -28,7 +28,7 @@ use ethcore::error::Error;
use ethcore::header::{BlockNumber, Header}; use ethcore::header::{BlockNumber, Header};
use ethcore::ids::BlockId; use ethcore::ids::BlockId;
use ethcore::miner::{self, MinerService, AuthoringParams}; use ethcore::miner::{self, MinerService, AuthoringParams};
use ethcore::receipt::{Receipt, RichReceipt}; use ethcore::receipt::RichReceipt;
use ethereum_types::{H256, U256, Address}; use ethereum_types::{H256, U256, Address};
use miner::pool::local_transactions::Status as LocalTransactionStatus; use miner::pool::local_transactions::Status as LocalTransactionStatus;
use miner::pool::{verifier, VerifiedTransaction, QueueStatus}; use miner::pool::{verifier, VerifiedTransaction, QueueStatus};
@ -46,7 +46,7 @@ pub struct TestMinerService {
/// Pre-existed local transactions /// Pre-existed local transactions
pub local_transactions: Mutex<BTreeMap<H256, LocalTransactionStatus>>, pub local_transactions: Mutex<BTreeMap<H256, LocalTransactionStatus>>,
/// Pre-existed pending receipts /// Pre-existed pending receipts
pub pending_receipts: Mutex<BTreeMap<H256, Receipt>>, pub pending_receipts: Mutex<Vec<RichReceipt>>,
/// Next nonces. /// Next nonces.
pub next_nonces: RwLock<HashMap<Address, U256>>, pub next_nonces: RwLock<HashMap<Address, U256>>,
/// Password held by Engine. /// Password held by Engine.
@ -58,11 +58,11 @@ pub struct TestMinerService {
impl Default for TestMinerService { impl Default for TestMinerService {
fn default() -> TestMinerService { fn default() -> TestMinerService {
TestMinerService { TestMinerService {
imported_transactions: Mutex::new(Vec::new()), imported_transactions: Default::default(),
pending_transactions: Mutex::new(HashMap::new()), pending_transactions: Default::default(),
local_transactions: Mutex::new(BTreeMap::new()), local_transactions: Default::default(),
pending_receipts: Mutex::new(BTreeMap::new()), pending_receipts: Default::default(),
next_nonces: RwLock::new(HashMap::new()), next_nonces: Default::default(),
password: RwLock::new("".into()), password: RwLock::new("".into()),
authoring_params: RwLock::new(AuthoringParams { authoring_params: RwLock::new(AuthoringParams {
author: Address::zero(), author: Address::zero(),
@ -230,23 +230,7 @@ impl MinerService for TestMinerService {
}).collect() }).collect()
} }
fn pending_receipt(&self, _best_block: BlockNumber, hash: &H256) -> Option<RichReceipt> { fn pending_receipts(&self, _best_block: BlockNumber) -> Option<Vec<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>> {
Some(self.pending_receipts.lock().clone()) Some(self.pending_receipts.lock().clone())
} }

View File

@ -16,7 +16,8 @@
use std::sync::Arc; use std::sync::Arc;
use ethcore::account_provider::AccountProvider; 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 ethcore_logger::RotatingLogger;
use ethereum_types::{Address, U256, H256}; use ethereum_types::{Address, U256, H256};
use ethstore::ethkey::{Generator, Random}; use ethstore::ethkey::{Generator, Random};
@ -531,3 +532,34 @@ fn rpc_parity_call() {
assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); 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, TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo, BlockNumber, ConsensusCapability, VersionInfo,
OperationsInfo, ChainStatus, OperationsInfo, ChainStatus,
AccountInfo, HwAccountInfo, RichHeader, AccountInfo, HwAccountInfo, RichHeader, Receipt,
}; };
build_rpc_trait! { build_rpc_trait! {
@ -211,6 +211,12 @@ build_rpc_trait! {
#[rpc(name = "parity_getBlockHeaderByNumber")] #[rpc(name = "parity_getBlockHeaderByNumber")]
fn block_header(&self, Trailing<BlockNumber>) -> BoxFuture<RichHeader>; 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. /// Get IPFS CIDv0 given protobuf encoded bytes.
#[rpc(name = "parity_cidV0")] #[rpc(name = "parity_cidV0")]
fn ipfs_cid(&self, Bytes) -> Result<String>; 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 { impl Serialize for BlockNumber {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
match *self { match *self {

View File

@ -49,7 +49,7 @@ pub mod pubsub;
pub use self::account_info::{AccountInfo, ExtAccountInfo, HwAccountInfo}; pub use self::account_info::{AccountInfo, ExtAccountInfo, HwAccountInfo};
pub use self::bytes::Bytes; pub use self::bytes::Bytes;
pub use self::block::{RichBlock, Block, BlockTransactions, Header, RichHeader, Rich}; 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::call_request::CallRequest;
pub use self::confirmations::{ pub use self::confirmations::{
ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken, ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken,