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:
committed by
Marek Kotewicz
parent
3f95a62e4f
commit
cc963d42a0
@@ -160,11 +160,6 @@ pub trait BlockProvider {
|
||||
.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.
|
||||
/// Returns None if block does not exist.
|
||||
fn transactions(&self, hash: &H256) -> Option<Vec<LocalizedTransaction>> {
|
||||
|
||||
@@ -1792,26 +1792,49 @@ impl BlockChainClient for Client {
|
||||
}
|
||||
|
||||
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();
|
||||
self.transaction_address(id)
|
||||
.and_then(|address| chain.block_number(&address.block_hash).and_then(|block_number| {
|
||||
let transaction = chain.block_body(&address.block_hash)
|
||||
.and_then(|body| body.view().localized_transaction_at(&address.block_hash, block_number, address.index));
|
||||
let number = chain.block_number(&hash)?;
|
||||
let body = chain.block_body(&hash)?;
|
||||
let mut receipts = chain.block_receipts(&hash)?.receipts;
|
||||
receipts.truncate(address.index + 1);
|
||||
|
||||
let previous_receipts = (0..address.index + 1)
|
||||
.map(|index| {
|
||||
let mut address = address.clone();
|
||||
address.index = index;
|
||||
chain.transaction_receipt(&address)
|
||||
})
|
||||
.collect();
|
||||
match (transaction, previous_receipts) {
|
||||
(Some(transaction), Some(previous_receipts)) => {
|
||||
Some(transaction_receipt(self.engine().machine(), transaction, previous_receipts))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}))
|
||||
let transaction = body.view().localized_transaction_at(&hash, number, address.index)?;
|
||||
let receipt = receipts.pop()?;
|
||||
let gas_used = receipts.last().map_or_else(|| 0.into(), |r| r.gas_used);
|
||||
let no_of_logs = receipts.into_iter().map(|receipt| receipt.logs.len()).sum::<usize>();
|
||||
|
||||
let receipt = transaction_receipt(self.engine().machine(), transaction, receipt, gas_used, no_of_logs);
|
||||
Some(receipt)
|
||||
}
|
||||
|
||||
fn block_receipts(&self, id: BlockId) -> Option<Vec<LocalizedReceipt>> {
|
||||
let hash = self.block_hash(id)?;
|
||||
|
||||
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> {
|
||||
@@ -1830,7 +1853,7 @@ impl BlockChainClient for Client {
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -2385,16 +2408,14 @@ impl Drop for Client {
|
||||
|
||||
/// Returns `LocalizedReceipt` given `LocalizedTransaction`
|
||||
/// 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 {
|
||||
assert_eq!(receipts.len(), tx.transaction_index + 1, "All previous receipts are provided.");
|
||||
|
||||
fn transaction_receipt(
|
||||
machine: &::machine::EthereumMachine,
|
||||
mut tx: LocalizedTransaction,
|
||||
receipt: Receipt,
|
||||
prior_gas_used: U256,
|
||||
prior_no_of_logs: usize,
|
||||
) -> LocalizedReceipt {
|
||||
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 block_hash = tx.block_hash;
|
||||
let block_number = tx.block_number;
|
||||
@@ -2423,7 +2444,7 @@ fn transaction_receipt(machine: &::machine::EthereumMachine, mut tx: LocalizedTr
|
||||
transaction_hash: transaction_hash,
|
||||
transaction_index: transaction_index,
|
||||
transaction_log_index: i,
|
||||
log_index: no_of_logs + i,
|
||||
log_index: prior_no_of_logs + i,
|
||||
}).collect(),
|
||||
log_bloom: receipt.log_bloom,
|
||||
outcome: receipt.outcome,
|
||||
@@ -2471,6 +2492,33 @@ mod tests {
|
||||
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]
|
||||
fn should_return_correct_log_index() {
|
||||
use hash::keccak;
|
||||
@@ -2514,20 +2562,15 @@ mod tests {
|
||||
topics: vec![],
|
||||
data: vec![],
|
||||
}];
|
||||
let receipts = vec![Receipt {
|
||||
outcome: TransactionOutcome::StateRoot(state_root),
|
||||
gas_used: 5.into(),
|
||||
log_bloom: Default::default(),
|
||||
logs: vec![logs[0].clone()],
|
||||
}, Receipt {
|
||||
let receipt = Receipt {
|
||||
outcome: TransactionOutcome::StateRoot(state_root),
|
||||
gas_used: gas_used,
|
||||
log_bloom: Default::default(),
|
||||
logs: logs.clone(),
|
||||
}];
|
||||
};
|
||||
|
||||
// when
|
||||
let receipt = transaction_receipt(&machine, transaction, receipts);
|
||||
let receipt = transaction_receipt(&machine, transaction, receipt, 5.into(), 1);
|
||||
|
||||
// then
|
||||
assert_eq!(receipt, LocalizedReceipt {
|
||||
|
||||
@@ -687,6 +687,10 @@ impl BlockChainClient for TestBlockChainClient {
|
||||
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> {
|
||||
match self.error_on_logs.read().as_ref() {
|
||||
Some(id) => return Err(id.clone()),
|
||||
@@ -786,7 +790,7 @@ impl BlockChainClient for TestBlockChainClient {
|
||||
None
|
||||
}
|
||||
|
||||
fn block_receipts(&self, hash: &H256) -> Option<Bytes> {
|
||||
fn encoded_block_receipts(&self, hash: &H256) -> Option<Bytes> {
|
||||
// starts with 'f' ?
|
||||
if *hash > H256::from("f000000000000000000000000000000000000000000000000000000000000000") {
|
||||
let receipt = BlockReceipts::new(vec![Receipt::new(
|
||||
|
||||
@@ -281,6 +281,9 @@ pub trait BlockChainClient : Sync + Send + AccountData + BlockChain + CallContra
|
||||
/// Get transaction receipt with given hash.
|
||||
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`.
|
||||
/// See `BlockChain::tree_route`.
|
||||
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>;
|
||||
|
||||
/// 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.
|
||||
fn queue_info(&self) -> BlockQueueInfo;
|
||||
|
||||
@@ -50,7 +50,7 @@ use executive::contract_address;
|
||||
use header::{Header, BlockNumber};
|
||||
use miner;
|
||||
use miner::pool_client::{PoolClient, CachedNonceClient, NonceCache};
|
||||
use receipt::{Receipt, RichReceipt};
|
||||
use receipt::RichReceipt;
|
||||
use spec::Spec;
|
||||
use state::State;
|
||||
use ethkey::Password;
|
||||
@@ -1039,19 +1039,17 @@ impl miner::MinerService for Miner {
|
||||
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| {
|
||||
let txs = pending.transactions();
|
||||
txs.iter()
|
||||
.map(|t| t.hash())
|
||||
.position(|t| t == *hash)
|
||||
.map(|index| {
|
||||
let receipts = pending.receipts();
|
||||
let receipts = pending.receipts();
|
||||
pending.transactions()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, tx)| {
|
||||
let prev_gas = if index == 0 { Default::default() } else { receipts[index - 1].gas_used };
|
||||
let tx = &txs[index];
|
||||
let receipt = &receipts[index];
|
||||
RichReceipt {
|
||||
transaction_hash: hash.clone(),
|
||||
transaction_hash: tx.hash(),
|
||||
transaction_index: index,
|
||||
cumulative_gas_used: receipt.gas_used,
|
||||
gas_used: receipt.gas_used - prev_gas,
|
||||
@@ -1067,15 +1065,7 @@ impl miner::MinerService for Miner {
|
||||
outcome: receipt.outcome.clone(),
|
||||
}
|
||||
})
|
||||
}, best_block).and_then(|x| x)
|
||||
}
|
||||
|
||||
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()
|
||||
.collect()
|
||||
}, best_block)
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ use client::{
|
||||
};
|
||||
use error::Error;
|
||||
use header::{BlockNumber, Header};
|
||||
use receipt::{RichReceipt, Receipt};
|
||||
use receipt::RichReceipt;
|
||||
use transaction::{self, UnverifiedTransaction, SignedTransaction, PendingTransaction};
|
||||
use state::StateInfo;
|
||||
use ethkey::Password;
|
||||
@@ -95,10 +95,13 @@ pub trait MinerService : Send + Sync {
|
||||
// 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.
|
||||
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.
|
||||
fn pending_state(&self, latest_block_number: BlockNumber) -> Option<Self::State>;
|
||||
|
||||
@@ -38,9 +38,9 @@ impl<'a> BodyView<'a> {
|
||||
/// ```
|
||||
/// #[macro_use]
|
||||
/// extern crate ethcore;
|
||||
///
|
||||
///
|
||||
/// use ethcore::views::{BodyView};
|
||||
///
|
||||
///
|
||||
/// fn main() {
|
||||
/// let bytes : &[u8] = &[];
|
||||
/// let body_view = view!(BodyView, bytes);
|
||||
|
||||
Reference in New Issue
Block a user