feat(rpc): impl block_by_hash (#1460)

This commit is contained in:
Matthias Seitz
2023-02-20 14:58:02 +01:00
committed by GitHub
parent 7ec4148efe
commit 279c0f917c
6 changed files with 114 additions and 26 deletions

View File

@ -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()
));

View File

@ -18,6 +18,28 @@ pub enum BlockTransactions {
Full(Vec<Transaction>),
}
/// 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<bool> 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<Self, BlockError> {
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<Self, BlockError> {
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<T: Serialize> Serialize for Rich<T> {
}
}
}
#[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());
}
}

View File

@ -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<Client, Pool, Network> EthApi<Client, Pool, Network>
where
@ -12,14 +15,13 @@ where
pub(crate) async fn block_by_hash(
&self,
hash: H256,
_full: bool,
full: bool,
) -> EthResult<Option<RichBlock>> {
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)
}

View File

@ -56,8 +56,8 @@ where
Ok(Some(EthApiSpec::chain_id(self)))
}
async fn block_by_hash(&self, _hash: H256, _full: bool) -> Result<Option<RichBlock>> {
Err(internal_rpc_err("unimplemented"))
async fn block_by_hash(&self, hash: H256, full: bool) -> Result<Option<RichBlock>> {
Ok(EthApi::block_by_hash(self, hash, full).await?)
}
async fn block_by_number(

View File

@ -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),

View File

@ -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<Ok, Err> {
/// Converts the error of the [Result] to an [RpcResult] via the `Err` [Display] impl.