Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

RPC: parity_getBlockReceipts #9527

Merged
merged 11 commits into from
Sep 25, 2018
2 changes: 1 addition & 1 deletion ethcore/light/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ impl<T: ProvingBlockChainClient + ?Sized> Provider for T {
}

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) })
}

Expand Down
5 changes: 0 additions & 5 deletions ethcore/src/blockchain/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>> {
Expand Down
119 changes: 81 additions & 38 deletions ethcore/src/client/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1785,26 +1785,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 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 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 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>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand these 3 lines. why do we pop a receipt and then take a gas_used from the one before it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So to actually generate a LocalizedReceipt we need 3 things:

  1. Actual receipt
  2. Gas used by that transaction
  3. Number of logs generate prior to this transaction (to calculate log_index_in_block).

So the logic is as follows:

  1. Fetch all receipts from 0 to index (inclusive)
  2. Take the last one (receipts.pop(); point 1.)
  3. Take the previous to last one to calculate (point 2.) (receipts only store cumulative gas, so we calculate current.gas_used - previous.gas_used to figure out what current gas usage of transaction was)
  4. Use all previous receipts to calculate total number of logs. (point 3.)


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>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method could probably use a test.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added for both block_receipt and transaction_receipt

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> {
Expand All @@ -1823,7 +1846,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())
}

Expand Down Expand Up @@ -2378,16 +2401,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;
Expand Down Expand Up @@ -2416,7 +2437,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,
Expand Down Expand Up @@ -2464,6 +2485,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;
Expand Down Expand Up @@ -2507,20 +2555,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 {
Expand Down
6 changes: 5 additions & 1 deletion ethcore/src/client/test_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down Expand Up @@ -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(
Expand Down
5 changes: 4 additions & 1 deletion ethcore/src/client/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand All @@ -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;
Expand Down
28 changes: 9 additions & 19 deletions ethcore/src/miner/miner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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)
}

Expand Down
9 changes: 6 additions & 3 deletions ethcore/src/miner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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>;
Expand Down
4 changes: 2 additions & 2 deletions ethcore/src/views/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion ethcore/sync/src/chain/supplier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ impl SyncSupplier {
let mut added_receipts = 0usize;
let mut data = Bytes::new();
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);
added_receipts += receipts_bytes.len();
added_headers += 1;
Expand Down
24 changes: 12 additions & 12 deletions rpc/src/v1/impls/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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>> {
Expand Down
Loading