feat: add getBlockReceipts (#3321)

This commit is contained in:
Matthias Seitz
2023-06-26 21:55:17 +02:00
committed by GitHub
parent 90a99476cd
commit 9721ecfc6d
6 changed files with 176 additions and 101 deletions

View File

@@ -71,6 +71,13 @@ pub trait EthApi {
number: BlockNumberOrTag,
) -> RpcResult<Option<U256>>;
/// Returns all transaction receipts for a given block.
#[method(name = "getBlockReceipts")]
async fn block_receipts(
&self,
number: BlockNumberOrTag,
) -> RpcResult<Option<Vec<TransactionReceipt>>>;
/// Returns an uncle block of the given block and index.
#[method(name = "getUncleByBlockHashAndIndex")]
async fn uncle_by_block_hash_and_index(

View File

@@ -1,12 +1,15 @@
//! Contains RPC handler implementations specific to blocks.
use crate::{
eth::error::{EthApiError, EthResult},
eth::{
api::transactions::build_transaction_receipt_with_block_receipts,
error::{EthApiError, EthResult},
},
EthApi,
};
use reth_primitives::BlockId;
use reth_primitives::{BlockId, BlockNumberOrTag, TransactionMeta};
use reth_provider::{BlockReaderIdExt, EvmEnvProvider, StateProviderFactory};
use reth_rpc_types::{Block, Index, RichBlock};
use reth_rpc_types::{Block, Index, RichBlock, TransactionReceipt};
impl<Provider, Pool, Network> EthApi<Provider, Pool, Network>
where
@@ -46,6 +49,47 @@ where
Ok(uncle)
}
/// Returns all transaction receipts in the block.
///
/// Returns `None` if the block wasn't found.
pub(crate) async fn block_receipts(
&self,
number: BlockNumberOrTag,
) -> EthResult<Option<Vec<TransactionReceipt>>> {
let mut block_and_receipts = None;
if number.is_pending() {
block_and_receipts = self.provider().pending_block_and_receipts()?;
} else if let Some(block_hash) = self.provider().block_hash_for_id(number.into())? {
block_and_receipts = self.cache().get_block_and_receipts(block_hash).await?;
}
if let Some((block, receipts)) = block_and_receipts {
let block_number = block.number;
let base_fee = block.base_fee_per_gas;
let block_hash = block.hash;
let receipts = block
.body
.into_iter()
.zip(receipts.clone())
.enumerate()
.map(|(idx, (tx, receipt))| {
let meta = TransactionMeta {
tx_hash: tx.hash,
index: idx as u64,
block_hash,
block_number,
base_fee,
};
build_transaction_receipt_with_block_receipts(tx, meta, receipt, &receipts)
})
.collect::<EthResult<Vec<_>>>();
return receipts.map(Some)
}
Ok(None)
}
/// Returns the number transactions in the given block.
///
/// Returns `None` if the block does not exist

View File

@@ -122,6 +122,15 @@ where
Ok(EthApi::ommers(self, number)?.map(|ommers| U256::from(ommers.len())))
}
/// Handler for: `eth_getBlockReceipts`
async fn block_receipts(
&self,
number: BlockNumberOrTag,
) -> Result<Option<Vec<TransactionReceipt>>> {
trace!(target: "rpc::eth", ?number, "Serving eth_getBlockReceipts");
Ok(EthApi::block_receipts(self, number).await?)
}
/// Handler for: `eth_getUncleByBlockHashAndIndex`
async fn uncle_by_block_hash_and_index(
&self,

View File

@@ -649,6 +649,28 @@ where
// === impl EthApi ===
impl<Provider, Pool, Network> EthApi<Provider, Pool, Network>
where
Provider: BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + 'static,
{
/// Helper function for `eth_getTransactionReceipt`
///
/// Returns the receipt
pub(crate) async fn build_transaction_receipt(
&self,
tx: TransactionSigned,
meta: TransactionMeta,
receipt: Receipt,
) -> EthResult<TransactionReceipt> {
// get all receipts for the block
let all_receipts = match self.cache().get_receipts(meta.block_hash).await? {
Some(recpts) => recpts,
None => return Err(EthApiError::UnknownBlockNumber),
};
build_transaction_receipt_with_block_receipts(tx, meta, receipt, &all_receipts)
}
}
impl<Provider, Pool, Network> EthApi<Provider, Pool, Network>
where
Pool: TransactionPool + 'static,
@@ -699,88 +721,6 @@ where
Ok(None)
}
/// Helper function for `eth_getTransactionReceipt`
///
/// Returns the receipt
pub(crate) async fn build_transaction_receipt(
&self,
tx: TransactionSigned,
meta: TransactionMeta,
receipt: Receipt,
) -> EthResult<TransactionReceipt> {
let transaction =
tx.clone().into_ecrecovered().ok_or(EthApiError::InvalidTransactionSignature)?;
// get all receipts for the block
let all_receipts = match self.cache().get_receipts(meta.block_hash).await? {
Some(recpts) => recpts,
None => return Err(EthApiError::UnknownBlockNumber),
};
// get the previous transaction cumulative gas used
let gas_used = if meta.index == 0 {
receipt.cumulative_gas_used
} else {
let prev_tx_idx = (meta.index - 1) as usize;
all_receipts
.get(prev_tx_idx)
.map(|prev_receipt| receipt.cumulative_gas_used - prev_receipt.cumulative_gas_used)
.unwrap_or_default()
};
let mut res_receipt = TransactionReceipt {
transaction_hash: Some(meta.tx_hash),
transaction_index: Some(U256::from(meta.index)),
block_hash: Some(meta.block_hash),
block_number: Some(U256::from(meta.block_number)),
from: transaction.signer(),
to: None,
cumulative_gas_used: U256::from(receipt.cumulative_gas_used),
gas_used: Some(U256::from(gas_used)),
contract_address: None,
logs: Vec::with_capacity(receipt.logs.len()),
effective_gas_price: U128::from(transaction.effective_gas_price(meta.base_fee)),
transaction_type: tx.transaction.tx_type().into(),
// TODO pre-byzantium receipts have a post-transaction state root
state_root: None,
logs_bloom: receipt.bloom_slow(),
status_code: if receipt.success { Some(U64::from(1)) } else { Some(U64::from(0)) },
};
match tx.transaction.kind() {
Create => {
res_receipt.contract_address =
Some(create_address(transaction.signer(), tx.transaction.nonce()));
}
Call(addr) => {
res_receipt.to = Some(*addr);
}
}
// get number of logs in the block
let mut num_logs = 0;
for prev_receipt in all_receipts.iter().take(meta.index as usize) {
num_logs += prev_receipt.logs.len();
}
for (tx_log_idx, log) in receipt.logs.into_iter().enumerate() {
let rpclog = Log {
address: log.address,
topics: log.topics,
data: log.data,
block_hash: Some(meta.block_hash),
block_number: Some(U256::from(meta.block_number)),
transaction_hash: Some(meta.tx_hash),
transaction_index: Some(U256::from(meta.index)),
log_index: Some(U256::from(num_logs + tx_log_idx)),
removed: false,
};
res_receipt.logs.push(rpclog);
}
Ok(res_receipt)
}
}
/// Represents from where a transaction was fetched.
#[derive(Debug, Clone, Eq, PartialEq)]
@@ -871,6 +811,80 @@ impl From<TransactionSource> for Transaction {
}
}
/// Helper function to construct a transaction receipt
pub(crate) fn build_transaction_receipt_with_block_receipts(
tx: TransactionSigned,
meta: TransactionMeta,
receipt: Receipt,
all_receipts: &[Receipt],
) -> EthResult<TransactionReceipt> {
let transaction =
tx.clone().into_ecrecovered().ok_or(EthApiError::InvalidTransactionSignature)?;
// get the previous transaction cumulative gas used
let gas_used = if meta.index == 0 {
receipt.cumulative_gas_used
} else {
let prev_tx_idx = (meta.index - 1) as usize;
all_receipts
.get(prev_tx_idx)
.map(|prev_receipt| receipt.cumulative_gas_used - prev_receipt.cumulative_gas_used)
.unwrap_or_default()
};
let mut res_receipt = TransactionReceipt {
transaction_hash: Some(meta.tx_hash),
transaction_index: Some(U256::from(meta.index)),
block_hash: Some(meta.block_hash),
block_number: Some(U256::from(meta.block_number)),
from: transaction.signer(),
to: None,
cumulative_gas_used: U256::from(receipt.cumulative_gas_used),
gas_used: Some(U256::from(gas_used)),
contract_address: None,
logs: Vec::with_capacity(receipt.logs.len()),
effective_gas_price: U128::from(transaction.effective_gas_price(meta.base_fee)),
transaction_type: tx.transaction.tx_type().into(),
// TODO pre-byzantium receipts have a post-transaction state root
state_root: None,
logs_bloom: receipt.bloom_slow(),
status_code: if receipt.success { Some(U64::from(1)) } else { Some(U64::from(0)) },
};
match tx.transaction.kind() {
Create => {
res_receipt.contract_address =
Some(create_address(transaction.signer(), tx.transaction.nonce()));
}
Call(addr) => {
res_receipt.to = Some(*addr);
}
}
// get number of logs in the block
let mut num_logs = 0;
for prev_receipt in all_receipts.iter().take(meta.index as usize) {
num_logs += prev_receipt.logs.len();
}
for (tx_log_idx, log) in receipt.logs.into_iter().enumerate() {
let rpclog = Log {
address: log.address,
topics: log.topics,
data: log.data,
block_hash: Some(meta.block_hash),
block_number: Some(U256::from(meta.block_number)),
transaction_hash: Some(meta.tx_hash),
transaction_index: Some(U256::from(meta.index)),
log_index: Some(U256::from(num_logs + tx_log_idx)),
removed: false,
};
res_receipt.logs.push(rpclog);
}
Ok(res_receipt)
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -201,6 +201,19 @@ impl EthStateCache {
rx.await.map_err(|_| ProviderError::CacheServiceUnavailable)?
}
/// Fetches both receipts and block for the given block hash.
pub(crate) async fn get_block_and_receipts(
&self,
block_hash: H256,
) -> Result<Option<(SealedBlock, Vec<Receipt>)>> {
let block = self.get_sealed_block(block_hash);
let receipts = self.get_receipts(block_hash);
let (block, receipts) = futures::try_join!(block, receipts)?;
Ok(block.zip(receipts))
}
/// Requests the evm env config for the block hash.
///
/// Returns an error if the corresponding header (required for populating the envs) was not

View File

@@ -9,7 +9,7 @@ use crate::{
};
use async_trait::async_trait;
use jsonrpsee::{core::RpcResult, server::IdProvider};
use reth_primitives::{BlockHashOrNumber, Receipt, SealedBlock, H256};
use reth_primitives::{BlockHashOrNumber, Receipt, SealedBlock};
use reth_provider::{BlockIdReader, BlockReader, EvmEnvProvider};
use reth_rpc_api::EthFilterApiServer;
use reth_rpc_types::{Filter, FilterBlockOption, FilterChanges, FilterId, FilteredParams, Log};
@@ -283,7 +283,8 @@ where
FilterBlockOption::AtBlockHash(block_hash) => {
let mut all_logs = Vec::new();
// all matching logs in the block, if it exists
if let Some((block, receipts)) = self.block_and_receipts_by_hash(block_hash).await?
if let Some((block, receipts)) =
self.eth_cache.get_block_and_receipts(block_hash).await?
{
let filter = FilteredParams::new(Some(filter));
logs_utils::append_matching_block_logs(
@@ -343,20 +344,7 @@ where
None => return Ok(None),
};
self.block_and_receipts_by_hash(block_hash).await
}
/// Fetches both receipts and block for the given block hash.
async fn block_and_receipts_by_hash(
&self,
block_hash: H256,
) -> EthResult<Option<(SealedBlock, Vec<Receipt>)>> {
let block = self.eth_cache.get_sealed_block(block_hash);
let receipts = self.eth_cache.get_receipts(block_hash);
let (block, receipts) = futures::try_join!(block, receipts)?;
Ok(block.zip(receipts))
Ok(self.eth_cache.get_block_and_receipts(block_hash).await?)
}
/// Returns all logs in the given _inclusive_ range that match the filter