Skip to content

Commit

Permalink
feat: add getBlockReceipts (#3321)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsse committed Jun 26, 2023
1 parent 90a9947 commit 9721ecf
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 101 deletions.
7 changes: 7 additions & 0 deletions crates/rpc/rpc-api/src/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
50 changes: 47 additions & 3 deletions crates/rpc/rpc/src/eth/api/block.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions crates/rpc/rpc/src/eth/api/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
178 changes: 96 additions & 82 deletions crates/rpc/rpc/src/eth/api/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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::*;
Expand Down
13 changes: 13 additions & 0 deletions crates/rpc/rpc/src/eth/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 4 additions & 16 deletions crates/rpc/rpc/src/eth/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 9721ecf

Please sign in to comment.