From 3adeb98b9e71d9bce7e47c1cb1081ae36c576c5b Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 30 Mar 2026 10:38:54 -0600 Subject: [PATCH] feat: add reth-rpc-traits crate Amp-Thread-ID: https://ampcode.com/threads/T-019d3f63-c296-706d-a4e2-fedcdc10dc5a Co-authored-by: Amp --- Cargo.toml | 5 +- crates/rpc-traits/Cargo.toml | 36 +++++++++++++ crates/rpc-traits/src/header.rs | 15 ++++++ crates/rpc-traits/src/lib.rs | 21 ++++++++ crates/rpc-traits/src/signable.rs | 39 ++++++++++++++ crates/rpc-traits/src/transaction.rs | 78 ++++++++++++++++++++++++++++ 6 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 crates/rpc-traits/Cargo.toml create mode 100644 crates/rpc-traits/src/header.rs create mode 100644 crates/rpc-traits/src/lib.rs create mode 100644 crates/rpc-traits/src/signable.rs create mode 100644 crates/rpc-traits/src/transaction.rs diff --git a/Cargo.toml b/Cargo.toml index 7d3ac5a..8288ea3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["crates/codecs", "crates/codecs-derive", "crates/primitives-traits", "crates/zstd-compressors"] +members = ["crates/codecs", "crates/codecs-derive", "crates/primitives-traits", "crates/rpc-traits", "crates/zstd-compressors"] resolver = "3" [workspace.package] @@ -15,6 +15,7 @@ repository = "https://github.com/paradigmxyz/reth-core" reth-codecs = { version = "0.1.0", path = "crates/codecs", default-features = false } reth-codecs-derive = { version = "0.1.0", path = "crates/codecs-derive" } reth-primitives-traits = { version = "0.1.0", path = "crates/primitives-traits", default-features = false } +reth-rpc-traits = { version = "0.1.0", path = "crates/rpc-traits", default-features = false } reth-zstd-compressors = { version = "0.1.0", path = "crates/zstd-compressors", default-features = false } # eth/alloy @@ -24,6 +25,8 @@ alloy-rpc-types-eth = { version = "1.7.3", default-features = false } alloy-trie = { version = "0.9.4", default-features = false } alloy-consensus = { version = "1.7.3", default-features = false } alloy-eips = { version = "1.7.3", default-features = false } +alloy-network = { version = "1.8.2", default-features = false } +alloy-signer = { version = "1.8.2", default-features = false } alloy-genesis = { version = "1.7.3", default-features = false } revm-primitives = { version = "22.1.0", default-features = false } revm-bytecode = { version = "9.0.0", default-features = false } diff --git a/crates/rpc-traits/Cargo.toml b/crates/rpc-traits/Cargo.toml new file mode 100644 index 0000000..bd8f156 --- /dev/null +++ b/crates/rpc-traits/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "reth-rpc-traits" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "RPC conversion traits for Ethereum types." + +[lints] +workspace = true + +[dependencies] +# reth +reth-primitives-traits = { workspace = true, default-features = false } + +# ethereum +alloy-primitives.workspace = true +alloy-consensus.workspace = true +alloy-rpc-types-eth.workspace = true +alloy-network.workspace = true +alloy-signer.workspace = true + +# error +thiserror.workspace = true + +[features] +default = ["std"] +std = [ + "alloy-primitives/std", + "alloy-consensus/std", + "alloy-rpc-types-eth/std", + "reth-primitives-traits/std", + "thiserror/std", +] diff --git a/crates/rpc-traits/src/header.rs b/crates/rpc-traits/src/header.rs new file mode 100644 index 0000000..d54a9e4 --- /dev/null +++ b/crates/rpc-traits/src/header.rs @@ -0,0 +1,15 @@ +use alloy_consensus::Sealable; +use alloy_primitives::U256; +use reth_primitives_traits::SealedHeader; + +/// Conversion trait for obtaining RPC header from a consensus header. +pub trait FromConsensusHeader { + /// Takes a consensus header and converts it into `self`. + fn from_consensus_header(header: SealedHeader, block_size: usize) -> Self; +} + +impl FromConsensusHeader for alloy_rpc_types_eth::Header { + fn from_consensus_header(header: SealedHeader, block_size: usize) -> Self { + Self::from_consensus(header.into(), None, Some(U256::from(block_size))) + } +} diff --git a/crates/rpc-traits/src/lib.rs b/crates/rpc-traits/src/lib.rs new file mode 100644 index 0000000..7dd9101 --- /dev/null +++ b/crates/rpc-traits/src/lib.rs @@ -0,0 +1,21 @@ +//! RPC conversion traits for Ethereum types. +//! +//! This crate provides traits for converting between consensus-layer types and RPC response types. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +mod signable; +pub use signable::{SignTxRequestError, SignableTxRequest}; + +mod header; +pub use header::FromConsensusHeader; + +mod transaction; +pub use transaction::{FromConsensusTx, TryIntoSimTx, TxInfoMapper}; diff --git a/crates/rpc-traits/src/signable.rs b/crates/rpc-traits/src/signable.rs new file mode 100644 index 0000000..ca07bf9 --- /dev/null +++ b/crates/rpc-traits/src/signable.rs @@ -0,0 +1,39 @@ +use core::{fmt::Debug, future::Future}; + +use alloy_consensus::{EthereumTxEnvelope, SignableTransaction, TxEip4844}; +use alloy_network::TxSigner; +use alloy_primitives::Signature; +use alloy_rpc_types_eth::TransactionRequest; + +/// Error for [`SignableTxRequest`] trait. +#[derive(Debug, thiserror::Error)] +pub enum SignTxRequestError { + /// The transaction request is invalid. + #[error("invalid transaction request")] + InvalidTransactionRequest, + + /// The signer is not supported. + #[error(transparent)] + SignerNotSupported(#[from] alloy_signer::Error), +} + +/// An abstraction over transaction requests that can be signed. +pub trait SignableTxRequest: Send + Sync + 'static { + /// Attempts to build a transaction request and sign it with the given signer. + fn try_build_and_sign( + self, + signer: impl TxSigner + Send, + ) -> impl Future> + Send; +} + +impl SignableTxRequest> for TransactionRequest { + async fn try_build_and_sign( + self, + signer: impl TxSigner + Send, + ) -> Result, SignTxRequestError> { + let mut tx = + self.build_typed_tx().map_err(|_| SignTxRequestError::InvalidTransactionRequest)?; + let signature = signer.sign_transaction(&mut tx).await?; + Ok(tx.into_signed(signature).into()) + } +} diff --git a/crates/rpc-traits/src/transaction.rs b/crates/rpc-traits/src/transaction.rs new file mode 100644 index 0000000..4d6bd8c --- /dev/null +++ b/crates/rpc-traits/src/transaction.rs @@ -0,0 +1,78 @@ +use alloy_consensus::{error::ValueError, transaction::Recovered, EthereumTxEnvelope, TxEip4844}; +use alloy_primitives::Address; +use alloy_rpc_types_eth::{Transaction, TransactionInfo, TransactionRequest}; +use core::{convert::Infallible, error}; + +/// Converts `T` into `self`. +/// +/// Should create an RPC transaction response object based on a consensus transaction, its signer +/// [`Address`] and an additional context [`FromConsensusTx::TxInfo`]. +pub trait FromConsensusTx: Sized { + /// An additional context, usually [`TransactionInfo`] in a wrapper that carries some + /// implementation specific extra information. + type TxInfo; + /// An associated RPC conversion error. + type Err: error::Error; + + /// Performs the conversion consuming `tx` with `signer` and `tx_info`. See [`FromConsensusTx`] + /// for details. + fn from_consensus_tx(tx: T, signer: Address, tx_info: Self::TxInfo) -> Result; +} + +impl> + FromConsensusTx for Transaction +{ + type TxInfo = TransactionInfo; + type Err = Infallible; + + fn from_consensus_tx( + tx: TxIn, + signer: Address, + tx_info: Self::TxInfo, + ) -> Result { + Ok(Self::from_transaction(Recovered::new_unchecked(tx.into(), signer), tx_info)) + } +} + +/// Converts `self` into `T`. +/// +/// Should create a fake transaction for simulation using [`TransactionRequest`]. +pub trait TryIntoSimTx +where + Self: Sized, +{ + /// Performs the conversion. + /// + /// Should return a signed typed transaction envelope for the [`eth_simulateV1`] endpoint with a + /// dummy signature or an error if [required fields] are missing. + /// + /// [`eth_simulateV1`]: + /// [required fields]: TransactionRequest::buildable_type + fn try_into_sim_tx(self) -> Result>; +} + +impl TryIntoSimTx> for TransactionRequest { + fn try_into_sim_tx(self) -> Result, ValueError> { + Self::build_typed_simulate_transaction(self) + } +} + +/// Adds extra context to [`TransactionInfo`]. +pub trait TxInfoMapper { + /// An associated output type that carries [`TransactionInfo`] with some extra context. + type Out; + /// An associated error that can occur during the mapping. + type Err; + + /// Performs the conversion. + fn try_map(&self, tx: &T, tx_info: TransactionInfo) -> Result; +} + +impl TxInfoMapper for () { + type Out = TransactionInfo; + type Err = Infallible; + + fn try_map(&self, _tx: &T, tx_info: TransactionInfo) -> Result { + Ok(tx_info) + } +}