diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index 6d04dfe6c..fb4eb8008 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -69,13 +69,11 @@ where EthApiClient::balance(client, address, None).await.unwrap(); EthApiClient::transaction_count(client, address, None).await.unwrap(); EthApiClient::storage_at(client, address, U256::default(), None).await.unwrap(); + EthApiClient::block_by_hash(client, hash, false).await.unwrap(); // Unimplemented assert!(is_unimplemented(EthApiClient::syncing(client).await.err().unwrap())); assert!(is_unimplemented(EthApiClient::author(client).await.err().unwrap())); - assert!(is_unimplemented( - EthApiClient::block_by_hash(client, hash, false).await.err().unwrap() - )); assert!(is_unimplemented( EthApiClient::block_by_number(client, block_number, false).await.err().unwrap() )); diff --git a/crates/rpc/rpc-types/src/eth/block.rs b/crates/rpc/rpc-types/src/eth/block.rs index 1aefeee95..3e4080722 100644 --- a/crates/rpc/rpc-types/src/eth/block.rs +++ b/crates/rpc/rpc-types/src/eth/block.rs @@ -18,6 +18,28 @@ pub enum BlockTransactions { Full(Vec), } +/// Determines how the `transactions` field of [Block] should be filled. +/// +/// This essentially represents the `full:bool` argument in RPC calls that determine whether the +/// response should include full transaction objects or just the hashes. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum BlockTransactionsKind { + /// Only include hashes: [BlockTransactions::Hashes] + Hashes, + /// Include full transaction objects: [BlockTransactions::Full] + Full, +} + +impl From for BlockTransactionsKind { + fn from(is_full: bool) -> Self { + if is_full { + BlockTransactionsKind::Full + } else { + BlockTransactionsKind::Hashes + } + } +} + /// Error that can occur when converting other types to blocks #[derive(Debug, thiserror::Error)] pub enum BlockError { @@ -49,20 +71,49 @@ pub struct Block { } impl Block { - /// Create a new block response from a [primitive block](reth_primitives::Block), using the + /// Converts the given primitive block into a [Block] response with the given + /// [BlockTransactionsKind] + pub fn from_block( + block: PrimitiveBlock, + total_difficulty: U256, + kind: BlockTransactionsKind, + ) -> Result { + match kind { + BlockTransactionsKind::Hashes => { + Ok(Self::from_block_hashes_only(block, total_difficulty)) + } + BlockTransactionsKind::Full => Self::from_block_full(block, total_difficulty), + } + } + + /// Create a new [Block] response from a [primitive block](reth_primitives::Block), using the /// total difficulty to populate its field in the rpc response. + /// + /// This will populate the `transactions` field with only the hashes of the transactions in the + /// block: [BlockTransactions::Hashes] + pub fn from_block_hashes_only(block: PrimitiveBlock, total_difficulty: U256) -> Self { + let block_hash = block.header.hash_slow(); + let transactions = block.body.iter().map(|tx| tx.hash).collect(); + + Self::from_block_with_transactions( + block_hash, + block, + total_difficulty, + BlockTransactions::Hashes(transactions), + ) + } + + /// Create a new [Block] response from a [primitive block](reth_primitives::Block), using the + /// total difficulty to populate its field in the rpc response. + /// + /// This will populate the `transactions` field with the _full_ [Transaction] objects: + /// [BlockTransactions::Full] pub fn from_block_full( block: PrimitiveBlock, total_difficulty: U256, ) -> Result { let block_hash = block.header.hash_slow(); - let block_length = block.length(); let block_number = block.number; - let uncles = block.ommers.into_iter().map(|h| h.hash_slow()).collect(); - let base_fee_per_gas = block.header.base_fee_per_gas; - - let header = Header::from_primitive_with_hash(block.header, block_hash); - let mut transactions = Vec::with_capacity(block.body.len()); for (idx, tx) in block.body.iter().enumerate() { let signed_tx = tx.clone().into_ecrecovered().ok_or(BlockError::InvalidSignature)?; @@ -74,15 +125,35 @@ impl Block { )) } - Ok(Self { + Ok(Self::from_block_with_transactions( + block_hash, + block, + total_difficulty, + BlockTransactions::Full(transactions), + )) + } + + fn from_block_with_transactions( + block_hash: H256, + block: PrimitiveBlock, + total_difficulty: U256, + transactions: BlockTransactions, + ) -> Self { + let block_length = block.length(); + let uncles = block.ommers.into_iter().map(|h| h.hash_slow()).collect(); + let base_fee_per_gas = block.header.base_fee_per_gas; + + let header = Header::from_primitive_with_hash(block.header, block_hash); + + Self { header, uncles, - transactions: BlockTransactions::Full(transactions), + transactions, base_fee_per_gas: base_fee_per_gas.map(U256::from), total_difficulty, size: Some(U256::from(block_length)), withdrawals: block.withdrawals, - }) + } } } @@ -241,3 +312,17 @@ impl Serialize for Rich { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_full_conversion() { + let full = true; + assert_eq!(BlockTransactionsKind::Full, full.into()); + + let full = false; + assert_eq!(BlockTransactionsKind::Hashes, full.into()); + } +} diff --git a/crates/rpc/rpc/src/eth/api/block.rs b/crates/rpc/rpc/src/eth/api/block.rs index 545303383..ad1dd245e 100644 --- a/crates/rpc/rpc/src/eth/api/block.rs +++ b/crates/rpc/rpc/src/eth/api/block.rs @@ -1,9 +1,12 @@ //! Contains RPC handler implementations specific to blocks. -use crate::{eth::error::EthResult, EthApi}; +use crate::{ + eth::error::{EthApiError, EthResult}, + EthApi, +}; use reth_primitives::{rpc::BlockId, H256}; use reth_provider::{BlockProvider, StateProviderFactory}; -use reth_rpc_types::RichBlock; +use reth_rpc_types::{Block, RichBlock}; impl EthApi where @@ -12,14 +15,13 @@ where pub(crate) async fn block_by_hash( &self, hash: H256, - _full: bool, + full: bool, ) -> EthResult> { - let block = self.client().block(BlockId::Hash(hash.0.into()))?; - if let Some(_block) = block { - // TODO: GET TD FOR BLOCK - needs block provider? or header provider? - // let total_difficulty = todo!(); - // let rich_block = Block::from_block_full(block, total_difficulty); - todo!() + if let Some(block) = self.client().block_by_hash(hash)? { + let total_difficulty = + self.client().header_td(&hash)?.ok_or_else(|| EthApiError::UnknownBlockNumber)?; + let block = Block::from_block(block, total_difficulty, full.into())?; + Ok(Some(block.into())) } else { Ok(None) } diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index 2ad83a976..0422a957e 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -56,8 +56,8 @@ where Ok(Some(EthApiSpec::chain_id(self))) } - async fn block_by_hash(&self, _hash: H256, _full: bool) -> Result> { - Err(internal_rpc_err("unimplemented")) + async fn block_by_hash(&self, hash: H256, full: bool) -> Result> { + Ok(EthApi::block_by_hash(self, hash, full).await?) } async fn block_by_number( diff --git a/crates/rpc/rpc/src/eth/error.rs b/crates/rpc/rpc/src/eth/error.rs index 24a7ef1c1..1e7041274 100644 --- a/crates/rpc/rpc/src/eth/error.rs +++ b/crates/rpc/rpc/src/eth/error.rs @@ -1,6 +1,7 @@ //! Error variants for the `eth_` namespace. use jsonrpsee::{core::Error as RpcError, types::error::INVALID_PARAMS_CODE}; +use reth_rpc_types::BlockError; use reth_transaction_pool::error::PoolError; use crate::result::{internal_rpc_err, rpc_err}; @@ -25,6 +26,9 @@ pub(crate) enum EthApiError { UnknownBlockNumber, #[error("Invalid block range")] InvalidBlockRange, + /// Thrown when constructing an RPC block from a primitive block data failed. + #[error(transparent)] + InvalidBlockData(#[from] BlockError), /// Other internal error #[error(transparent)] Internal(#[from] reth_interfaces::Error), diff --git a/crates/rpc/rpc/src/result.rs b/crates/rpc/rpc/src/result.rs index 5a28e3bea..291e7762b 100644 --- a/crates/rpc/rpc/src/result.rs +++ b/crates/rpc/rpc/src/result.rs @@ -1,12 +1,11 @@ //! Additional helpers for converting errors. +use crate::eth::error::EthApiError; use jsonrpsee::core::{Error as RpcError, RpcResult}; use reth_interfaces::Result as RethResult; use reth_primitives::Block; use std::fmt::Display; -use crate::eth::error::EthApiError; - /// Helper trait to easily convert various `Result` types into [`RpcResult`] pub(crate) trait ToRpcResult { /// Converts the error of the [Result] to an [RpcResult] via the `Err` [Display] impl.