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> {
BlockChainClient::block_receipts(self, &req.hash)
BlockChainClient::encoded_block_receipts(self, &req.hash)
.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)))
}
/// 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>> {

View File

@ -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 {

View File

@ -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(

View File

@ -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;

View File

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

View File

@ -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>;

View File

@ -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);

View File

@ -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;

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,