mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-29 09:08:05 -05:00
feat: add getBlockReceipts (#3321)
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user