From 9721ecfc6d403efd6004f7745f13bbd4c5ef49e5 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 26 Jun 2023 21:55:17 +0200 Subject: [PATCH] feat: add getBlockReceipts (#3321) --- crates/rpc/rpc-api/src/eth.rs | 7 + crates/rpc/rpc/src/eth/api/block.rs | 50 +++++- crates/rpc/rpc/src/eth/api/server.rs | 9 ++ crates/rpc/rpc/src/eth/api/transactions.rs | 178 +++++++++++---------- crates/rpc/rpc/src/eth/cache.rs | 13 ++ crates/rpc/rpc/src/eth/filter.rs | 20 +-- 6 files changed, 176 insertions(+), 101 deletions(-) diff --git a/crates/rpc/rpc-api/src/eth.rs b/crates/rpc/rpc-api/src/eth.rs index 9a66557c83ce..6ca403dd7d11 100644 --- a/crates/rpc/rpc-api/src/eth.rs +++ b/crates/rpc/rpc-api/src/eth.rs @@ -71,6 +71,13 @@ pub trait EthApi { number: BlockNumberOrTag, ) -> RpcResult>; + /// Returns all transaction receipts for a given block. + #[method(name = "getBlockReceipts")] + async fn block_receipts( + &self, + number: BlockNumberOrTag, + ) -> RpcResult>>; + /// Returns an uncle block of the given block and index. #[method(name = "getUncleByBlockHashAndIndex")] async fn uncle_by_block_hash_and_index( diff --git a/crates/rpc/rpc/src/eth/api/block.rs b/crates/rpc/rpc/src/eth/api/block.rs index 472ac9bd2a89..21b557b032dd 100644 --- a/crates/rpc/rpc/src/eth/api/block.rs +++ b/crates/rpc/rpc/src/eth/api/block.rs @@ -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 EthApi 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>> { + 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::>>(); + return receipts.map(Some) + } + + Ok(None) + } + /// Returns the number transactions in the given block. /// /// Returns `None` if the block does not exist diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index 90965b8a865a..acca88323cdc 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -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>> { + 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, diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index ece5457e565d..fe4eca4be8f8 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -649,6 +649,28 @@ where // === impl EthApi === +impl EthApi +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 { + // 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 EthApi 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 { - 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 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 { + 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::*; diff --git a/crates/rpc/rpc/src/eth/cache.rs b/crates/rpc/rpc/src/eth/cache.rs index 3adf1b78924a..5e84fcd6934e 100644 --- a/crates/rpc/rpc/src/eth/cache.rs +++ b/crates/rpc/rpc/src/eth/cache.rs @@ -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)>> { + 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 diff --git a/crates/rpc/rpc/src/eth/filter.rs b/crates/rpc/rpc/src/eth/filter.rs index 891f24f4758e..47bddaa98eeb 100644 --- a/crates/rpc/rpc/src/eth/filter.rs +++ b/crates/rpc/rpc/src/eth/filter.rs @@ -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)>> { - 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