Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add getBlockReceipts #3321

Merged
merged 5 commits into from
Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading