chore: add rpc-compat feature in reth primitives-traits (#16608)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Josh_dfG
2025-06-17 17:14:11 +01:00
committed by GitHub
parent 58cfd2e02b
commit 051cef53bc
10 changed files with 215 additions and 110 deletions

1
Cargo.lock generated
View File

@@ -9548,6 +9548,7 @@ dependencies = [
"alloy-genesis",
"alloy-primitives",
"alloy-rlp",
"alloy-rpc-types-eth",
"alloy-trie",
"arbitrary",
"auto_impl",

View File

@@ -50,6 +50,7 @@ arbitrary = { workspace = true, features = ["derive"], optional = true }
proptest = { workspace = true, optional = true }
proptest-arbitrary-interop = { workspace = true, optional = true }
rayon = { workspace = true, optional = true }
alloy-rpc-types-eth = { workspace = true, optional = true }
[dev-dependencies]
reth-codecs.workspace = true
@@ -93,6 +94,7 @@ std = [
"reth-chainspec/std",
"revm-bytecode/std",
"revm-state/std",
"alloy-rpc-types-eth?/std",
]
secp256k1 = ["alloy-consensus/secp256k1"]
test-utils = [
@@ -115,6 +117,7 @@ arbitrary = [
"op-alloy-consensus?/arbitrary",
"alloy-trie/arbitrary",
"reth-chainspec/arbitrary",
"alloy-rpc-types-eth?/arbitrary",
]
serde-bincode-compat = [
"serde",
@@ -140,6 +143,7 @@ serde = [
"revm-bytecode/serde",
"revm-state/serde",
"rand_08/serde",
"alloy-rpc-types-eth?/serde",
]
reth-codec = [
"dep:reth-codecs",
@@ -153,3 +157,4 @@ op = [
rayon = [
"dep:rayon",
]
rpc-compat = ["alloy-rpc-types-eth"]

View File

@@ -532,6 +532,205 @@ impl<B: crate::test_utils::TestBlock> RecoveredBlock<B> {
}
}
#[cfg(feature = "rpc-compat")]
mod rpc_compat {
use super::{
Block as BlockTrait, BlockBody as BlockBodyTrait, RecoveredBlock, SignedTransaction,
};
use crate::block::error::BlockRecoveryError;
use alloc::vec::Vec;
use alloy_consensus::{
transaction::Recovered, Block as CBlock, BlockBody, BlockHeader, Sealable,
};
use alloy_primitives::U256;
use alloy_rpc_types_eth::{
Block, BlockTransactions, BlockTransactionsKind, Header, TransactionInfo,
};
impl<B> RecoveredBlock<B>
where
B: BlockTrait,
{
/// Converts the block block into an RPC [`Block`] instance with the given
/// [`BlockTransactionsKind`].
///
/// The `tx_resp_builder` closure is used to build the transaction response for each
/// transaction.
pub fn into_rpc_block<T, F, E>(
self,
kind: BlockTransactionsKind,
tx_resp_builder: F,
) -> Result<Block<T, Header<B::Header>>, E>
where
F: Fn(
Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
TransactionInfo,
) -> Result<T, E>,
{
match kind {
BlockTransactionsKind::Hashes => Ok(self.into_rpc_block_with_tx_hashes()),
BlockTransactionsKind::Full => self.into_rpc_block_full(tx_resp_builder),
}
}
/// Clones the block and converts it into a [`Block`] response with the given
/// [`BlockTransactionsKind`]
///
/// This is a convenience method that avoids the need to explicitly clone the block
/// before calling [`Self::into_rpc_block`]. For transaction hashes, it only clones
/// the necessary parts for better efficiency.
///
/// The `tx_resp_builder` closure is used to build the transaction response for each
/// transaction.
pub fn clone_into_rpc_block<T, F, E>(
&self,
kind: BlockTransactionsKind,
tx_resp_builder: F,
) -> Result<Block<T, Header<B::Header>>, E>
where
F: Fn(
Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
TransactionInfo,
) -> Result<T, E>,
{
match kind {
BlockTransactionsKind::Hashes => Ok(self.to_rpc_block_with_tx_hashes()),
BlockTransactionsKind::Full => self.clone().into_rpc_block_full(tx_resp_builder),
}
}
/// Create a new [`Block`] instance from a [`RecoveredBlock`] reference.
///
/// This will populate the `transactions` field with only the hashes of the transactions in
/// the block: [`BlockTransactions::Hashes`]
///
/// This method only clones the necessary parts and avoids cloning the entire block.
pub fn to_rpc_block_with_tx_hashes<T>(&self) -> Block<T, Header<B::Header>> {
let transactions = self.body().transaction_hashes_iter().copied().collect();
let rlp_length = self.rlp_length();
let header = self.clone_sealed_header();
let withdrawals = self.body().withdrawals().cloned();
let transactions = BlockTransactions::Hashes(transactions);
let uncles =
self.body().ommers().unwrap_or(&[]).iter().map(|h| h.hash_slow()).collect();
let header = Header::from_consensus(header.into(), None, Some(U256::from(rlp_length)));
Block { header, uncles, transactions, withdrawals }
}
/// Create a new [`Block`] response from a [`RecoveredBlock`], 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 into_rpc_block_with_tx_hashes<T>(self) -> Block<T, Header<B::Header>> {
let transactions = self.body().transaction_hashes_iter().copied().collect();
let rlp_length = self.rlp_length();
let (header, body) = self.into_sealed_block().split_sealed_header_body();
let BlockBody { ommers, withdrawals, .. } = body.into_ethereum_body();
let transactions = BlockTransactions::Hashes(transactions);
let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect();
let header = Header::from_consensus(header.into(), None, Some(U256::from(rlp_length)));
Block { header, uncles, transactions, withdrawals }
}
/// Create a new [`Block`] response from a [`RecoveredBlock`], using the given closure to
/// create the rpc transactions.
///
/// This will populate the `transactions` field with the _full_
/// transaction objects: [`BlockTransactions::Full`]
pub fn into_rpc_block_full<T, F, E>(
self,
tx_resp_builder: F,
) -> Result<Block<T, Header<B::Header>>, E>
where
F: Fn(
Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
TransactionInfo,
) -> Result<T, E>,
{
let block_number = self.header().number();
let base_fee = self.header().base_fee_per_gas();
let block_length = self.rlp_length();
let block_hash = Some(self.hash());
let (block, senders) = self.split_sealed();
let (header, body) = block.split_sealed_header_body();
let BlockBody { transactions, ommers, withdrawals } = body.into_ethereum_body();
let transactions = transactions
.into_iter()
.zip(senders)
.enumerate()
.map(|(idx, (tx, sender))| {
let tx_info = TransactionInfo {
hash: Some(*tx.tx_hash()),
block_hash,
block_number: Some(block_number),
base_fee,
index: Some(idx as u64),
};
tx_resp_builder(Recovered::new_unchecked(tx, sender), tx_info)
})
.collect::<Result<Vec<_>, E>>()?;
let transactions = BlockTransactions::Full(transactions);
let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect();
let header =
Header::from_consensus(header.into(), None, Some(U256::from(block_length)));
let block = Block { header, uncles, transactions, withdrawals };
Ok(block)
}
}
impl<T> RecoveredBlock<CBlock<T>>
where
T: SignedTransaction,
{
/// Create a `RecoveredBlock` from an alloy RPC block.
///
/// # Examples
/// ```ignore
/// // Works with default Transaction type
/// let rpc_block: alloy_rpc_types_eth::Block = get_rpc_block();
/// let recovered = RecoveredBlock::from_rpc_block(rpc_block)?;
///
/// // Also works with custom transaction types that implement From<U>
/// let custom_rpc_block: alloy_rpc_types_eth::Block<CustomTx> = get_custom_rpc_block();
/// let recovered = RecoveredBlock::from_rpc_block(custom_rpc_block)?;
/// ```
pub fn from_rpc_block<U>(
block: alloy_rpc_types_eth::Block<U>,
) -> Result<Self, BlockRecoveryError<alloy_consensus::Block<T>>>
where
T: From<U>,
{
// Convert to consensus block and then convert transactions
let consensus_block = block.into_consensus().convert_transactions();
// Try to recover the block
consensus_block.try_into_recovered()
}
}
impl<T, U> TryFrom<alloy_rpc_types_eth::Block<U>> for RecoveredBlock<CBlock<T>>
where
T: SignedTransaction + From<U>,
{
type Error = BlockRecoveryError<alloy_consensus::Block<T>>;
fn try_from(block: alloy_rpc_types_eth::Block<U>) -> Result<Self, Self::Error> {
Self::from_rpc_block(block)
}
}
}
/// Bincode-compatible [`RecoveredBlock`] serde implementation.
#[cfg(feature = "serde-bincode-compat")]
pub(super) mod serde_bincode_compat {

View File

@@ -13,6 +13,8 @@
//! types.
//! - `reth-codec`: Enables db codec support for reth types including zstd compression for certain
//! types.
//! - `rpc-compat`: Adds RPC compatibility functions for the types in this crate, e.g. rpc type
//! conversions.
//! - `serde`: Adds serde support for all types.
//! - `secp256k1`: Adds secp256k1 support for transaction signing/recovery. (By default the no-std
//! friendly `k256` is used)

View File

@@ -15,7 +15,7 @@ workspace = true
# reth
revm = { workspace = true, features = ["optional_block_gas_limit", "optional_eip3607", "optional_no_base_fee"] }
revm-inspectors.workspace = true
reth-primitives-traits.workspace = true
reth-primitives-traits = { workspace = true, features = ["rpc-compat"] }
reth-errors.workspace = true
reth-evm.workspace = true
reth-storage-api.workspace = true

View File

@@ -13,7 +13,7 @@ use futures::Future;
use reth_evm::ConfigureEvm;
use reth_node_api::BlockBody;
use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedBlock};
use reth_rpc_types_compat::block::from_block;
use reth_rpc_types_compat::TransactionCompat;
use reth_storage_api::{BlockIdReader, BlockReader, ProviderHeader, ProviderReceipt, ProviderTx};
use reth_transaction_pool::{PoolTransaction, TransactionPool};
use std::sync::Arc;
@@ -59,7 +59,9 @@ pub trait EthBlocks: LoadBlock {
async move {
let Some(block) = self.recovered_block(block_id).await? else { return Ok(None) };
let block = from_block((*block).clone(), full.into(), self.tx_resp_builder())?;
let block = block.clone_into_rpc_block(full.into(), |tx, tx_info| {
self.tx_resp_builder().fill(tx, tx_info)
})?;
Ok(Some(block))
}
}

View File

@@ -19,7 +19,7 @@ reth-evm.workspace = true
reth-execution-types.workspace = true
reth-metrics.workspace = true
reth-ethereum-primitives.workspace = true
reth-primitives-traits.workspace = true
reth-primitives-traits = { workspace = true, features = ["rpc-compat"] }
reth-storage-api.workspace = true
reth-revm.workspace = true
reth-rpc-server-types.workspace = true

View File

@@ -23,7 +23,7 @@ use reth_primitives_traits::{
block::BlockTx, BlockBody as _, NodePrimitives, Recovered, RecoveredBlock, SignedTransaction,
};
use reth_rpc_server_types::result::rpc_err;
use reth_rpc_types_compat::{block::from_block, TransactionCompat};
use reth_rpc_types_compat::TransactionCompat;
use reth_storage_api::noop::NoopProvider;
use revm::{
context_interface::result::ExecutionResult,
@@ -259,6 +259,6 @@ where
let txs_kind =
if full_transactions { BlockTransactionsKind::Full } else { BlockTransactionsKind::Hashes };
let block = from_block(block, txs_kind, tx_resp_builder)?;
let block = block.into_rpc_block(txs_kind, |tx, tx_info| tx_resp_builder.fill(tx, tx_info))?;
Ok(SimulatedBlock { inner: block, calls })
}

View File

@@ -1,102 +0,0 @@
//! Compatibility functions for rpc `Block` type.
use crate::transaction::TransactionCompat;
use alloy_consensus::{transaction::Recovered, BlockBody, BlockHeader, Sealable};
use alloy_primitives::U256;
use alloy_rpc_types_eth::{
Block, BlockTransactions, BlockTransactionsKind, Header, TransactionInfo,
};
use reth_primitives_traits::{
Block as BlockTrait, BlockBody as BlockBodyTrait, NodePrimitives, RecoveredBlock,
SignedTransaction,
};
/// Converts the given primitive block into a [`Block`] response with the given
/// [`BlockTransactionsKind`]
///
/// If a `block_hash` is provided, then this is used, otherwise the block hash is computed.
#[expect(clippy::type_complexity)]
pub fn from_block<T, B>(
block: RecoveredBlock<B>,
kind: BlockTransactionsKind,
tx_resp_builder: &T,
) -> Result<Block<T::Transaction, Header<B::Header>>, T::Error>
where
T: TransactionCompat,
B: BlockTrait<Body: BlockBodyTrait<Transaction = <T::Primitives as NodePrimitives>::SignedTx>>,
{
match kind {
BlockTransactionsKind::Hashes => Ok(from_block_with_tx_hashes::<T::Transaction, B>(block)),
BlockTransactionsKind::Full => from_block_full::<T, B>(block, tx_resp_builder),
}
}
/// Create a new [`Block`] response from a [`RecoveredBlock`], 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_with_tx_hashes<T, B>(block: RecoveredBlock<B>) -> Block<T, Header<B::Header>>
where
B: BlockTrait,
{
let transactions = block.body().transaction_hashes_iter().copied().collect();
let rlp_length = block.rlp_length();
let (header, body) = block.into_sealed_block().split_sealed_header_body();
let BlockBody { ommers, withdrawals, .. } = body.into_ethereum_body();
let transactions = BlockTransactions::Hashes(transactions);
let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect();
let header = Header::from_consensus(header.into(), None, Some(U256::from(rlp_length)));
Block { header, uncles, transactions, withdrawals }
}
/// Create a new [`Block`] response from a [`RecoveredBlock`], using the
/// total difficulty to populate its field in the rpc response.
///
/// This will populate the `transactions` field with the _full_
/// [`TransactionCompat::Transaction`] objects: [`BlockTransactions::Full`]
#[expect(clippy::type_complexity)]
pub fn from_block_full<T, B>(
block: RecoveredBlock<B>,
tx_resp_builder: &T,
) -> Result<Block<T::Transaction, Header<B::Header>>, T::Error>
where
T: TransactionCompat,
B: BlockTrait<Body: BlockBodyTrait<Transaction = <T::Primitives as NodePrimitives>::SignedTx>>,
{
let block_number = block.header().number();
let base_fee = block.header().base_fee_per_gas();
let block_length = block.rlp_length();
let block_hash = Some(block.hash());
let (block, senders) = block.split_sealed();
let (header, body) = block.split_sealed_header_body();
let BlockBody { transactions, ommers, withdrawals } = body.into_ethereum_body();
let transactions = transactions
.into_iter()
.zip(senders)
.enumerate()
.map(|(idx, (tx, sender))| {
let tx_info = TransactionInfo {
hash: Some(*tx.tx_hash()),
block_hash,
block_number: Some(block_number),
base_fee,
index: Some(idx as u64),
};
tx_resp_builder.fill(Recovered::new_unchecked(tx, sender), tx_info)
})
.collect::<Result<Vec<_>, T::Error>>()?;
let transactions = BlockTransactions::Full(transactions);
let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect();
let header = Header::from_consensus(header.into(), None, Some(U256::from(block_length)));
let block = Block { header, uncles, transactions, withdrawals };
Ok(block)
}

View File

@@ -10,10 +10,8 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
pub mod block;
mod fees;
pub mod transaction;
pub use fees::{CallFees, CallFeesError};
pub use transaction::{
try_into_op_tx_info, EthTxEnvError, IntoRpcTx, RpcConverter, TransactionCompat,