reth_rpc_convert/
transaction.rs

1//! Compatibility functions for rpc `Transaction` type.
2use crate::{
3    fees::{CallFees, CallFeesError},
4    RpcHeader, RpcReceipt, RpcTransaction, RpcTxReq, RpcTypes, SignableTxRequest,
5};
6use alloy_consensus::{
7    error::ValueError, transaction::Recovered, EthereumTxEnvelope, Sealable, TxEip4844,
8};
9use alloy_network::Network;
10use alloy_primitives::{Address, TxKind, U256};
11use alloy_rpc_types_eth::{
12    request::{TransactionInputError, TransactionRequest},
13    Transaction, TransactionInfo,
14};
15use core::error;
16use dyn_clone::DynClone;
17use reth_evm::{
18    revm::context_interface::{either::Either, Block},
19    BlockEnvFor, ConfigureEvm, EvmEnvFor, TxEnvFor,
20};
21use reth_primitives_traits::{
22    BlockTy, HeaderTy, NodePrimitives, SealedBlock, SealedHeader, SealedHeaderFor, TransactionMeta,
23    TxTy,
24};
25use revm_context::{BlockEnv, CfgEnv, TxEnv};
26use std::{convert::Infallible, error::Error, fmt::Debug, marker::PhantomData};
27use thiserror::Error;
28
29/// Input for [`RpcConvert::convert_receipts`].
30#[derive(Debug, Clone)]
31pub struct ConvertReceiptInput<'a, N: NodePrimitives> {
32    /// Primitive receipt.
33    pub receipt: N::Receipt,
34    /// Transaction the receipt corresponds to.
35    pub tx: Recovered<&'a N::SignedTx>,
36    /// Gas used by the transaction.
37    pub gas_used: u64,
38    /// Number of logs emitted before this transaction.
39    pub next_log_index: usize,
40    /// Metadata for the transaction.
41    pub meta: TransactionMeta,
42}
43
44/// A type that knows how to convert primitive receipts to RPC representations.
45pub trait ReceiptConverter<N: NodePrimitives>: Debug + 'static {
46    /// RPC representation.
47    type RpcReceipt;
48
49    /// Error that may occur during conversion.
50    type Error;
51
52    /// Converts a set of primitive receipts to RPC representations. It is guaranteed that all
53    /// receipts are from the same block.
54    fn convert_receipts(
55        &self,
56        receipts: Vec<ConvertReceiptInput<'_, N>>,
57    ) -> Result<Vec<Self::RpcReceipt>, Self::Error>;
58
59    /// Converts a set of primitive receipts to RPC representations. It is guaranteed that all
60    /// receipts are from `block`.
61    fn convert_receipts_with_block(
62        &self,
63        receipts: Vec<ConvertReceiptInput<'_, N>>,
64        _block: &SealedBlock<N::Block>,
65    ) -> Result<Vec<Self::RpcReceipt>, Self::Error> {
66        self.convert_receipts(receipts)
67    }
68}
69
70/// A type that knows how to convert a consensus header into an RPC header.
71pub trait HeaderConverter<Consensus, Rpc>: Debug + Send + Sync + Unpin + Clone + 'static {
72    /// An associated RPC conversion error.
73    type Err: error::Error;
74
75    /// Converts a consensus header into an RPC header.
76    fn convert_header(
77        &self,
78        header: SealedHeader<Consensus>,
79        block_size: usize,
80    ) -> Result<Rpc, Self::Err>;
81}
82
83/// Default implementation of [`HeaderConverter`] that uses [`FromConsensusHeader`] to convert
84/// headers.
85impl<Consensus, Rpc> HeaderConverter<Consensus, Rpc> for ()
86where
87    Rpc: FromConsensusHeader<Consensus>,
88{
89    type Err = Infallible;
90
91    fn convert_header(
92        &self,
93        header: SealedHeader<Consensus>,
94        block_size: usize,
95    ) -> Result<Rpc, Self::Err> {
96        Ok(Rpc::from_consensus_header(header, block_size))
97    }
98}
99
100/// Conversion trait for obtaining RPC header from a consensus header.
101pub trait FromConsensusHeader<T> {
102    /// Takes a consensus header and converts it into `self`.
103    fn from_consensus_header(header: SealedHeader<T>, block_size: usize) -> Self;
104}
105
106impl<T: Sealable> FromConsensusHeader<T> for alloy_rpc_types_eth::Header<T> {
107    fn from_consensus_header(header: SealedHeader<T>, block_size: usize) -> Self {
108        Self::from_consensus(header.into(), None, Some(U256::from(block_size)))
109    }
110}
111
112/// Responsible for the conversions from and into RPC requests and responses.
113///
114/// The JSON-RPC schema and the Node primitives are configurable using the [`RpcConvert::Network`]
115/// and [`RpcConvert::Primitives`] associated types respectively.
116///
117/// A generic implementation [`RpcConverter`] should be preferred over a manual implementation. As
118/// long as its trait bound requirements are met, the implementation is created automatically and
119/// can be used in RPC method handlers for all the conversions.
120#[auto_impl::auto_impl(&, Box, Arc)]
121pub trait RpcConvert: Send + Sync + Unpin + Debug + DynClone + 'static {
122    /// Associated lower layer consensus types to convert from and into types of [`Self::Network`].
123    type Primitives: NodePrimitives;
124
125    /// The EVM configuration.
126    type Evm: ConfigureEvm<Primitives = Self::Primitives>;
127
128    /// Associated upper layer JSON-RPC API network requests and responses to convert from and into
129    /// types of [`Self::Primitives`].
130    type Network: RpcTypes<TransactionRequest: SignableTxRequest<TxTy<Self::Primitives>>>;
131
132    /// An associated RPC conversion error.
133    type Error: error::Error + Into<jsonrpsee_types::ErrorObject<'static>>;
134
135    /// Wrapper for `fill()` with default `TransactionInfo`
136    /// Create a new rpc transaction result for a _pending_ signed transaction, setting block
137    /// environment related fields to `None`.
138    fn fill_pending(
139        &self,
140        tx: Recovered<TxTy<Self::Primitives>>,
141    ) -> Result<RpcTransaction<Self::Network>, Self::Error> {
142        self.fill(tx, TransactionInfo::default())
143    }
144
145    /// Create a new rpc transaction result for a mined transaction, using the given block hash,
146    /// number, and tx index fields to populate the corresponding fields in the rpc result.
147    ///
148    /// The block hash, number, and tx index fields should be from the original block where the
149    /// transaction was mined.
150    fn fill(
151        &self,
152        tx: Recovered<TxTy<Self::Primitives>>,
153        tx_info: TransactionInfo,
154    ) -> Result<RpcTransaction<Self::Network>, Self::Error>;
155
156    /// Builds a fake transaction from a transaction request for inclusion into block built in
157    /// `eth_simulateV1`.
158    fn build_simulate_v1_transaction(
159        &self,
160        request: RpcTxReq<Self::Network>,
161    ) -> Result<TxTy<Self::Primitives>, Self::Error>;
162
163    /// Creates a transaction environment for execution based on `request` with corresponding
164    /// `cfg_env` and `block_env`.
165    fn tx_env(
166        &self,
167        request: RpcTxReq<Self::Network>,
168        evm_env: &EvmEnvFor<Self::Evm>,
169    ) -> Result<TxEnvFor<Self::Evm>, Self::Error>;
170
171    /// Converts a set of primitive receipts to RPC representations. It is guaranteed that all
172    /// receipts are from the same block.
173    fn convert_receipts(
174        &self,
175        receipts: Vec<ConvertReceiptInput<'_, Self::Primitives>>,
176    ) -> Result<Vec<RpcReceipt<Self::Network>>, Self::Error>;
177
178    /// Converts a set of primitive receipts to RPC representations. It is guaranteed that all
179    /// receipts are from the same block.
180    ///
181    /// Also accepts the corresponding block in case the receipt requires additional metadata.
182    fn convert_receipts_with_block(
183        &self,
184        receipts: Vec<ConvertReceiptInput<'_, Self::Primitives>>,
185        block: &SealedBlock<BlockTy<Self::Primitives>>,
186    ) -> Result<Vec<RpcReceipt<Self::Network>>, Self::Error>;
187
188    /// Converts a primitive header to an RPC header.
189    fn convert_header(
190        &self,
191        header: SealedHeaderFor<Self::Primitives>,
192        block_size: usize,
193    ) -> Result<RpcHeader<Self::Network>, Self::Error>;
194}
195
196dyn_clone::clone_trait_object!(
197    <Primitives, Network, Error, Evm>
198    RpcConvert<Primitives = Primitives, Network = Network, Error = Error, Evm = Evm>
199);
200
201/// Converts `self` into `T`. The opposite of [`FromConsensusTx`].
202///
203/// Should create an RPC transaction response object based on a consensus transaction, its signer
204/// [`Address`] and an additional context [`IntoRpcTx::TxInfo`].
205///
206/// Avoid implementing [`IntoRpcTx`] and use [`FromConsensusTx`] instead. Implementing it
207/// automatically provides an implementation of [`IntoRpcTx`] thanks to the blanket implementation
208/// in this crate.
209///
210/// Prefer using [`IntoRpcTx`] over [`FromConsensusTx`] when specifying trait bounds on a generic
211/// function to ensure that types that only implement [`IntoRpcTx`] can be used as well.
212pub trait IntoRpcTx<T> {
213    /// An additional context, usually [`TransactionInfo`] in a wrapper that carries some
214    /// implementation specific extra information.
215    type TxInfo;
216    /// An associated RPC conversion error.
217    type Err: error::Error;
218
219    /// Performs the conversion consuming `self` with `signer` and `tx_info`. See [`IntoRpcTx`]
220    /// for details.
221    fn into_rpc_tx(self, signer: Address, tx_info: Self::TxInfo) -> Result<T, Self::Err>;
222}
223
224/// Converts `T` into `self`. It is reciprocal of [`IntoRpcTx`].
225///
226/// Should create an RPC transaction response object based on a consensus transaction, its signer
227/// [`Address`] and an additional context [`FromConsensusTx::TxInfo`].
228///
229/// Prefer implementing [`FromConsensusTx`] over [`IntoRpcTx`] because it automatically provides an
230/// implementation of [`IntoRpcTx`] thanks to the blanket implementation in this crate.
231///
232/// Prefer using [`IntoRpcTx`] over using [`FromConsensusTx`] when specifying trait bounds on a
233/// generic function. This way, types that directly implement [`IntoRpcTx`] can be used as arguments
234/// as well.
235pub trait FromConsensusTx<T>: Sized {
236    /// An additional context, usually [`TransactionInfo`] in a wrapper that carries some
237    /// implementation specific extra information.
238    type TxInfo;
239    /// An associated RPC conversion error.
240    type Err: error::Error;
241
242    /// Performs the conversion consuming `tx` with `signer` and `tx_info`. See [`FromConsensusTx`]
243    /// for details.
244    fn from_consensus_tx(tx: T, signer: Address, tx_info: Self::TxInfo) -> Result<Self, Self::Err>;
245}
246
247impl<TxIn: alloy_consensus::Transaction, T: alloy_consensus::Transaction + From<TxIn>>
248    FromConsensusTx<TxIn> for Transaction<T>
249{
250    type TxInfo = TransactionInfo;
251    type Err = Infallible;
252
253    fn from_consensus_tx(
254        tx: TxIn,
255        signer: Address,
256        tx_info: Self::TxInfo,
257    ) -> Result<Self, Self::Err> {
258        Ok(Self::from_transaction(Recovered::new_unchecked(tx.into(), signer), tx_info))
259    }
260}
261
262impl<ConsensusTx, RpcTx> IntoRpcTx<RpcTx> for ConsensusTx
263where
264    ConsensusTx: alloy_consensus::Transaction,
265    RpcTx: FromConsensusTx<Self>,
266    <RpcTx as FromConsensusTx<ConsensusTx>>::Err: Debug,
267{
268    type TxInfo = RpcTx::TxInfo;
269    type Err = <RpcTx as FromConsensusTx<ConsensusTx>>::Err;
270
271    fn into_rpc_tx(self, signer: Address, tx_info: Self::TxInfo) -> Result<RpcTx, Self::Err> {
272        RpcTx::from_consensus_tx(self, signer, tx_info)
273    }
274}
275
276/// Converts `Tx` into `RpcTx`
277///
278/// Where:
279/// * `Tx` is a transaction from the consensus layer.
280/// * `RpcTx` is a transaction response object of the RPC API
281///
282/// The conversion function is accompanied by `signer`'s address and `tx_info` providing extra
283/// context about a transaction in a block.
284///
285/// The `RpcTxConverter` has two blanket implementations:
286/// * `()` assuming `Tx` implements [`IntoRpcTx`] and is used as default for [`RpcConverter`].
287/// * `Fn(Tx, Address, TxInfo) -> RpcTx` and can be applied using
288///   [`RpcConverter::with_rpc_tx_converter`].
289///
290/// One should prefer to implement [`IntoRpcTx`] for `Tx` to get the `RpcTxConverter` implementation
291/// for free, thanks to the blanket implementation, unless the conversion requires more context. For
292/// example, some configuration parameters or access handles to database, network, etc.
293pub trait RpcTxConverter<Tx, RpcTx, TxInfo>: Clone + Debug + Unpin + Send + Sync + 'static {
294    /// An associated error that can happen during the conversion.
295    type Err;
296
297    /// Performs the conversion of `tx` from `Tx` into `RpcTx`.
298    ///
299    /// See [`RpcTxConverter`] for more information.
300    fn convert_rpc_tx(&self, tx: Tx, signer: Address, tx_info: TxInfo) -> Result<RpcTx, Self::Err>;
301}
302
303impl<Tx, RpcTx> RpcTxConverter<Tx, RpcTx, Tx::TxInfo> for ()
304where
305    Tx: IntoRpcTx<RpcTx>,
306{
307    type Err = Tx::Err;
308
309    fn convert_rpc_tx(
310        &self,
311        tx: Tx,
312        signer: Address,
313        tx_info: Tx::TxInfo,
314    ) -> Result<RpcTx, Self::Err> {
315        tx.into_rpc_tx(signer, tx_info)
316    }
317}
318
319impl<Tx, RpcTx, F, TxInfo, E> RpcTxConverter<Tx, RpcTx, TxInfo> for F
320where
321    F: Fn(Tx, Address, TxInfo) -> Result<RpcTx, E> + Clone + Debug + Unpin + Send + Sync + 'static,
322{
323    type Err = E;
324
325    fn convert_rpc_tx(&self, tx: Tx, signer: Address, tx_info: TxInfo) -> Result<RpcTx, Self::Err> {
326        self(tx, signer, tx_info)
327    }
328}
329
330/// Converts `TxReq` into `SimTx`.
331///
332/// Where:
333/// * `TxReq` is a transaction request received from an RPC API
334/// * `SimTx` is the corresponding consensus layer transaction for execution simulation
335///
336/// The `SimTxConverter` has two blanket implementations:
337/// * `()` assuming `TxReq` implements [`TryIntoSimTx`] and is used as default for [`RpcConverter`].
338/// * `Fn(TxReq) -> Result<SimTx, ValueError<TxReq>>` and can be applied using
339///   [`RpcConverter::with_sim_tx_converter`].
340///
341/// One should prefer to implement [`TryIntoSimTx`] for `TxReq` to get the `SimTxConverter`
342/// implementation for free, thanks to the blanket implementation, unless the conversion requires
343/// more context. For example, some configuration parameters or access handles to database, network,
344/// etc.
345pub trait SimTxConverter<TxReq, SimTx>: Clone + Debug + Unpin + Send + Sync + 'static {
346    /// An associated error that can occur during the conversion.
347    type Err: Error;
348
349    /// Performs the conversion from `tx_req` into `SimTx`.
350    ///
351    /// See [`SimTxConverter`] for more information.
352    fn convert_sim_tx(&self, tx_req: TxReq) -> Result<SimTx, Self::Err>;
353}
354
355impl<TxReq, SimTx> SimTxConverter<TxReq, SimTx> for ()
356where
357    TxReq: TryIntoSimTx<SimTx> + Debug,
358{
359    type Err = ValueError<TxReq>;
360
361    fn convert_sim_tx(&self, tx_req: TxReq) -> Result<SimTx, Self::Err> {
362        tx_req.try_into_sim_tx()
363    }
364}
365
366impl<TxReq, SimTx, F, E> SimTxConverter<TxReq, SimTx> for F
367where
368    TxReq: Debug,
369    E: Error,
370    F: Fn(TxReq) -> Result<SimTx, E> + Clone + Debug + Unpin + Send + Sync + 'static,
371{
372    type Err = E;
373
374    fn convert_sim_tx(&self, tx_req: TxReq) -> Result<SimTx, Self::Err> {
375        self(tx_req)
376    }
377}
378
379/// Converts `self` into `T`.
380///
381/// Should create a fake transaction for simulation using [`TransactionRequest`].
382pub trait TryIntoSimTx<T>
383where
384    Self: Sized,
385{
386    /// Performs the conversion.
387    ///
388    /// Should return a signed typed transaction envelope for the [`eth_simulateV1`] endpoint with a
389    /// dummy signature or an error if [required fields] are missing.
390    ///
391    /// [`eth_simulateV1`]: <https://github.com/ethereum/execution-apis/pull/484>
392    /// [required fields]: TransactionRequest::buildable_type
393    fn try_into_sim_tx(self) -> Result<T, ValueError<Self>>;
394}
395
396/// Adds extra context to [`TransactionInfo`].
397pub trait TxInfoMapper<T> {
398    /// An associated output type that carries [`TransactionInfo`] with some extra context.
399    type Out;
400    /// An associated error that can occur during the mapping.
401    type Err;
402
403    /// Performs the conversion.
404    fn try_map(&self, tx: &T, tx_info: TransactionInfo) -> Result<Self::Out, Self::Err>;
405}
406
407impl<T> TxInfoMapper<T> for () {
408    type Out = TransactionInfo;
409    type Err = Infallible;
410
411    fn try_map(&self, _tx: &T, tx_info: TransactionInfo) -> Result<Self::Out, Self::Err> {
412        Ok(tx_info)
413    }
414}
415
416impl TryIntoSimTx<EthereumTxEnvelope<TxEip4844>> for TransactionRequest {
417    fn try_into_sim_tx(self) -> Result<EthereumTxEnvelope<TxEip4844>, ValueError<Self>> {
418        Self::build_typed_simulate_transaction(self)
419    }
420}
421
422/// Converts `TxReq` into `TxEnv`.
423///
424/// Where:
425/// * `TxReq` is a transaction request received from an RPC API
426/// * `TxEnv` is the corresponding transaction environment for execution
427///
428/// The `TxEnvConverter` has two blanket implementations:
429/// * `()` assuming `TxReq` implements [`TryIntoTxEnv`] and is used as default for [`RpcConverter`].
430/// * `Fn(TxReq, &CfgEnv<Spec>, &BlockEnv) -> Result<TxEnv, E>` and can be applied using
431///   [`RpcConverter::with_tx_env_converter`].
432///
433/// One should prefer to implement [`TryIntoTxEnv`] for `TxReq` to get the `TxEnvConverter`
434/// implementation for free, thanks to the blanket implementation, unless the conversion requires
435/// more context. For example, some configuration parameters or access handles to database, network,
436/// etc.
437pub trait TxEnvConverter<TxReq, Evm: ConfigureEvm>:
438    Debug + Send + Sync + Unpin + Clone + 'static
439{
440    /// An associated error that can occur during conversion.
441    type Error;
442
443    /// Converts a rpc transaction request into a transaction environment.
444    ///
445    /// See [`TxEnvConverter`] for more information.
446    fn convert_tx_env(
447        &self,
448        tx_req: TxReq,
449        evm_env: &EvmEnvFor<Evm>,
450    ) -> Result<TxEnvFor<Evm>, Self::Error>;
451}
452
453impl<TxReq, Evm> TxEnvConverter<TxReq, Evm> for ()
454where
455    TxReq: TryIntoTxEnv<TxEnvFor<Evm>, BlockEnvFor<Evm>>,
456    Evm: ConfigureEvm,
457{
458    type Error = TxReq::Err;
459
460    fn convert_tx_env(
461        &self,
462        tx_req: TxReq,
463        evm_env: &EvmEnvFor<Evm>,
464    ) -> Result<TxEnvFor<Evm>, Self::Error> {
465        tx_req.try_into_tx_env(&evm_env.cfg_env, &evm_env.block_env)
466    }
467}
468
469/// Converts rpc transaction requests into transaction environment using a closure.
470impl<F, TxReq, E, Evm> TxEnvConverter<TxReq, Evm> for F
471where
472    F: Fn(TxReq, &EvmEnvFor<Evm>) -> Result<TxEnvFor<Evm>, E>
473        + Debug
474        + Send
475        + Sync
476        + Unpin
477        + Clone
478        + 'static,
479    TxReq: Clone,
480    Evm: ConfigureEvm,
481    E: error::Error + Send + Sync + 'static,
482{
483    type Error = E;
484
485    fn convert_tx_env(
486        &self,
487        tx_req: TxReq,
488        evm_env: &EvmEnvFor<Evm>,
489    ) -> Result<TxEnvFor<Evm>, Self::Error> {
490        self(tx_req, evm_env)
491    }
492}
493
494/// Converts `self` into `T`.
495///
496/// Should create an executable transaction environment using [`TransactionRequest`].
497pub trait TryIntoTxEnv<T, BlockEnv = reth_evm::revm::context::BlockEnv> {
498    /// An associated error that can occur during the conversion.
499    type Err;
500
501    /// Performs the conversion.
502    fn try_into_tx_env<Spec>(
503        self,
504        cfg_env: &CfgEnv<Spec>,
505        block_env: &BlockEnv,
506    ) -> Result<T, Self::Err>;
507}
508
509/// An Ethereum specific transaction environment error than can occur during conversion from
510/// [`TransactionRequest`].
511#[derive(Debug, Error)]
512pub enum EthTxEnvError {
513    /// Error while decoding or validating transaction request fees.
514    #[error(transparent)]
515    CallFees(#[from] CallFeesError),
516    /// Both data and input fields are set and not equal.
517    #[error(transparent)]
518    Input(#[from] TransactionInputError),
519}
520
521impl TryIntoTxEnv<TxEnv> for TransactionRequest {
522    type Err = EthTxEnvError;
523
524    fn try_into_tx_env<Spec>(
525        self,
526        cfg_env: &CfgEnv<Spec>,
527        block_env: &BlockEnv,
528    ) -> Result<TxEnv, Self::Err> {
529        // Ensure that if versioned hashes are set, they're not empty
530        if self.blob_versioned_hashes.as_ref().is_some_and(|hashes| hashes.is_empty()) {
531            return Err(CallFeesError::BlobTransactionMissingBlobHashes.into())
532        }
533
534        let tx_type = self.minimal_tx_type() as u8;
535
536        let Self {
537            from,
538            to,
539            gas_price,
540            max_fee_per_gas,
541            max_priority_fee_per_gas,
542            gas,
543            value,
544            input,
545            nonce,
546            access_list,
547            chain_id,
548            blob_versioned_hashes,
549            max_fee_per_blob_gas,
550            authorization_list,
551            transaction_type: _,
552            sidecar: _,
553        } = self;
554
555        let CallFees { max_priority_fee_per_gas, gas_price, max_fee_per_blob_gas } =
556            CallFees::ensure_fees(
557                gas_price.map(U256::from),
558                max_fee_per_gas.map(U256::from),
559                max_priority_fee_per_gas.map(U256::from),
560                U256::from(block_env.basefee),
561                blob_versioned_hashes.as_deref(),
562                max_fee_per_blob_gas.map(U256::from),
563                block_env.blob_gasprice().map(U256::from),
564            )?;
565
566        let gas_limit = gas.unwrap_or(
567            // Use maximum allowed gas limit. The reason for this
568            // is that both Erigon and Geth use pre-configured gas cap even if
569            // it's possible to derive the gas limit from the block:
570            // <https://github.com/ledgerwatch/erigon/blob/eae2d9a79cb70dbe30b3a6b79c436872e4605458/cmd/rpcdaemon/commands/trace_adhoc.go#L956
571            // https://github.com/ledgerwatch/erigon/blob/eae2d9a79cb70dbe30b3a6b79c436872e4605458/eth/ethconfig/config.go#L94>
572            block_env.gas_limit,
573        );
574
575        let chain_id = chain_id.unwrap_or(cfg_env.chain_id);
576
577        let caller = from.unwrap_or_default();
578
579        let nonce = nonce.unwrap_or_default();
580
581        let env = TxEnv {
582            tx_type,
583            gas_limit,
584            nonce,
585            caller,
586            gas_price: gas_price.saturating_to(),
587            gas_priority_fee: max_priority_fee_per_gas.map(|v| v.saturating_to()),
588            kind: to.unwrap_or(TxKind::Create),
589            value: value.unwrap_or_default(),
590            data: input.try_into_unique_input().map_err(EthTxEnvError::from)?.unwrap_or_default(),
591            chain_id: Some(chain_id),
592            access_list: access_list.unwrap_or_default(),
593            // EIP-4844 fields
594            blob_hashes: blob_versioned_hashes.unwrap_or_default(),
595            max_fee_per_blob_gas: max_fee_per_blob_gas
596                .map(|v| v.saturating_to())
597                .unwrap_or_default(),
598            // EIP-7702 fields
599            authorization_list: authorization_list
600                .unwrap_or_default()
601                .into_iter()
602                .map(Either::Left)
603                .collect(),
604        };
605
606        Ok(env)
607    }
608}
609
610/// Conversion into transaction RPC response failed.
611#[derive(Debug, Clone, Error)]
612#[error("Failed to convert transaction into RPC response: {0}")]
613pub struct TransactionConversionError(String);
614
615/// Generic RPC response object converter for `Evm` and network `Network`.
616///
617/// The main purpose of this struct is to provide an implementation of [`RpcConvert`] for generic
618/// associated types. This struct can then be used for conversions in RPC method handlers.
619///
620/// An [`RpcConvert`] implementation is generated if the following traits are implemented for the
621/// network and EVM associated primitives:
622/// * [`FromConsensusTx`]: from signed transaction into RPC response object.
623/// * [`TryIntoSimTx`]: from RPC transaction request into a simulated transaction.
624/// * [`TryIntoTxEnv`] or [`TxEnvConverter`]: from RPC transaction request into an executable
625///   transaction.
626/// * [`TxInfoMapper`]: from [`TransactionInfo`] into [`FromConsensusTx::TxInfo`]. Should be
627///   implemented for a dedicated struct that is assigned to `Map`. If [`FromConsensusTx::TxInfo`]
628///   is [`TransactionInfo`] then `()` can be used as `Map` which trivially passes over the input
629///   object.
630#[derive(Debug)]
631pub struct RpcConverter<
632    Network,
633    Evm,
634    Receipt,
635    Header = (),
636    Map = (),
637    SimTx = (),
638    RpcTx = (),
639    TxEnv = (),
640> {
641    network: PhantomData<Network>,
642    evm: PhantomData<Evm>,
643    receipt_converter: Receipt,
644    header_converter: Header,
645    mapper: Map,
646    tx_env_converter: TxEnv,
647    sim_tx_converter: SimTx,
648    rpc_tx_converter: RpcTx,
649}
650
651impl<Network, Evm, Receipt> RpcConverter<Network, Evm, Receipt> {
652    /// Creates a new [`RpcConverter`] with `receipt_converter` and `mapper`.
653    pub const fn new(receipt_converter: Receipt) -> Self {
654        Self {
655            network: PhantomData,
656            evm: PhantomData,
657            receipt_converter,
658            header_converter: (),
659            mapper: (),
660            tx_env_converter: (),
661            sim_tx_converter: (),
662            rpc_tx_converter: (),
663        }
664    }
665}
666
667impl<Network, Evm, Receipt, Header, Map, SimTx, RpcTx, TxEnv>
668    RpcConverter<Network, Evm, Receipt, Header, Map, SimTx, RpcTx, TxEnv>
669{
670    /// Converts the network type
671    pub fn with_network<N>(
672        self,
673    ) -> RpcConverter<N, Evm, Receipt, Header, Map, SimTx, RpcTx, TxEnv> {
674        let Self {
675            receipt_converter,
676            header_converter,
677            mapper,
678            evm,
679            sim_tx_converter,
680            rpc_tx_converter,
681            tx_env_converter,
682            ..
683        } = self;
684        RpcConverter {
685            receipt_converter,
686            header_converter,
687            mapper,
688            network: Default::default(),
689            evm,
690            sim_tx_converter,
691            rpc_tx_converter,
692            tx_env_converter,
693        }
694    }
695
696    /// Converts the transaction environment type.
697    pub fn with_tx_env_converter<TxEnvNew>(
698        self,
699        tx_env_converter: TxEnvNew,
700    ) -> RpcConverter<Network, Evm, Receipt, Header, Map, SimTx, RpcTx, TxEnvNew> {
701        let Self {
702            receipt_converter,
703            header_converter,
704            mapper,
705            network,
706            evm,
707            sim_tx_converter,
708            rpc_tx_converter,
709            tx_env_converter: _,
710            ..
711        } = self;
712        RpcConverter {
713            receipt_converter,
714            header_converter,
715            mapper,
716            network,
717            evm,
718            sim_tx_converter,
719            rpc_tx_converter,
720            tx_env_converter,
721        }
722    }
723
724    /// Configures the header converter.
725    pub fn with_header_converter<HeaderNew>(
726        self,
727        header_converter: HeaderNew,
728    ) -> RpcConverter<Network, Evm, Receipt, HeaderNew, Map, SimTx, RpcTx, TxEnv> {
729        let Self {
730            receipt_converter,
731            header_converter: _,
732            mapper,
733            network,
734            evm,
735            sim_tx_converter,
736            rpc_tx_converter,
737            tx_env_converter,
738        } = self;
739        RpcConverter {
740            receipt_converter,
741            header_converter,
742            mapper,
743            network,
744            evm,
745            sim_tx_converter,
746            rpc_tx_converter,
747            tx_env_converter,
748        }
749    }
750
751    /// Configures the mapper.
752    pub fn with_mapper<MapNew>(
753        self,
754        mapper: MapNew,
755    ) -> RpcConverter<Network, Evm, Receipt, Header, MapNew, SimTx, RpcTx, TxEnv> {
756        let Self {
757            receipt_converter,
758            header_converter,
759            mapper: _,
760            network,
761            evm,
762            sim_tx_converter,
763            rpc_tx_converter,
764            tx_env_converter,
765        } = self;
766        RpcConverter {
767            receipt_converter,
768            header_converter,
769            mapper,
770            network,
771            evm,
772            sim_tx_converter,
773            rpc_tx_converter,
774            tx_env_converter,
775        }
776    }
777
778    /// Swaps the simulate transaction converter with `sim_tx_converter`.
779    pub fn with_sim_tx_converter<SimTxNew>(
780        self,
781        sim_tx_converter: SimTxNew,
782    ) -> RpcConverter<Network, Evm, Receipt, Header, Map, SimTxNew, RpcTx, TxEnv> {
783        let Self {
784            receipt_converter,
785            header_converter,
786            mapper,
787            network,
788            evm,
789            rpc_tx_converter,
790            tx_env_converter,
791            ..
792        } = self;
793        RpcConverter {
794            receipt_converter,
795            header_converter,
796            mapper,
797            network,
798            evm,
799            sim_tx_converter,
800            rpc_tx_converter,
801            tx_env_converter,
802        }
803    }
804
805    /// Swaps the RPC transaction converter with `rpc_tx_converter`.
806    pub fn with_rpc_tx_converter<RpcTxNew>(
807        self,
808        rpc_tx_converter: RpcTxNew,
809    ) -> RpcConverter<Network, Evm, Receipt, Header, Map, SimTx, RpcTxNew, TxEnv> {
810        let Self {
811            receipt_converter,
812            header_converter,
813            mapper,
814            network,
815            evm,
816            sim_tx_converter,
817            tx_env_converter,
818            ..
819        } = self;
820        RpcConverter {
821            receipt_converter,
822            header_converter,
823            mapper,
824            network,
825            evm,
826            sim_tx_converter,
827            rpc_tx_converter,
828            tx_env_converter,
829        }
830    }
831
832    /// Converts `self` into a boxed converter.
833    pub fn erased(
834        self,
835    ) -> Box<
836        dyn RpcConvert<
837            Primitives = <Self as RpcConvert>::Primitives,
838            Network = <Self as RpcConvert>::Network,
839            Error = <Self as RpcConvert>::Error,
840            Evm = <Self as RpcConvert>::Evm,
841        >,
842    >
843    where
844        Self: RpcConvert,
845    {
846        Box::new(self)
847    }
848}
849
850impl<Network, Evm, Receipt, Header, Map, SimTx, RpcTx, TxEnv> Default
851    for RpcConverter<Network, Evm, Receipt, Header, Map, SimTx, RpcTx, TxEnv>
852where
853    Receipt: Default,
854    Header: Default,
855    Map: Default,
856    SimTx: Default,
857    RpcTx: Default,
858    TxEnv: Default,
859{
860    fn default() -> Self {
861        Self {
862            network: Default::default(),
863            evm: Default::default(),
864            receipt_converter: Default::default(),
865            header_converter: Default::default(),
866            mapper: Default::default(),
867            sim_tx_converter: Default::default(),
868            rpc_tx_converter: Default::default(),
869            tx_env_converter: Default::default(),
870        }
871    }
872}
873
874impl<
875        Network,
876        Evm,
877        Receipt: Clone,
878        Header: Clone,
879        Map: Clone,
880        SimTx: Clone,
881        RpcTx: Clone,
882        TxEnv: Clone,
883    > Clone for RpcConverter<Network, Evm, Receipt, Header, Map, SimTx, RpcTx, TxEnv>
884{
885    fn clone(&self) -> Self {
886        Self {
887            network: Default::default(),
888            evm: Default::default(),
889            receipt_converter: self.receipt_converter.clone(),
890            header_converter: self.header_converter.clone(),
891            mapper: self.mapper.clone(),
892            sim_tx_converter: self.sim_tx_converter.clone(),
893            rpc_tx_converter: self.rpc_tx_converter.clone(),
894            tx_env_converter: self.tx_env_converter.clone(),
895        }
896    }
897}
898
899impl<N, Network, Evm, Receipt, Header, Map, SimTx, RpcTx, TxEnv> RpcConvert
900    for RpcConverter<Network, Evm, Receipt, Header, Map, SimTx, RpcTx, TxEnv>
901where
902    N: NodePrimitives,
903    Network: RpcTypes<TransactionRequest: SignableTxRequest<N::SignedTx>>,
904    Evm: ConfigureEvm<Primitives = N> + 'static,
905    Receipt: ReceiptConverter<
906            N,
907            RpcReceipt = RpcReceipt<Network>,
908            Error: From<TransactionConversionError>
909                       + From<TxEnv::Error>
910                       + From<<Map as TxInfoMapper<TxTy<N>>>::Err>
911                       + From<RpcTx::Err>
912                       + From<Header::Err>
913                       + Error
914                       + Unpin
915                       + Sync
916                       + Send
917                       + Into<jsonrpsee_types::ErrorObject<'static>>,
918        > + Send
919        + Sync
920        + Unpin
921        + Clone
922        + Debug,
923    Header: HeaderConverter<HeaderTy<N>, RpcHeader<Network>>,
924    Map: TxInfoMapper<TxTy<N>> + Clone + Debug + Unpin + Send + Sync + 'static,
925    SimTx: SimTxConverter<RpcTxReq<Network>, TxTy<N>>,
926    RpcTx:
927        RpcTxConverter<TxTy<N>, Network::TransactionResponse, <Map as TxInfoMapper<TxTy<N>>>::Out>,
928    TxEnv: TxEnvConverter<RpcTxReq<Network>, Evm>,
929{
930    type Primitives = N;
931    type Evm = Evm;
932    type Network = Network;
933    type Error = Receipt::Error;
934
935    fn fill(
936        &self,
937        tx: Recovered<TxTy<N>>,
938        tx_info: TransactionInfo,
939    ) -> Result<Network::TransactionResponse, Self::Error> {
940        let (tx, signer) = tx.into_parts();
941        let tx_info = self.mapper.try_map(&tx, tx_info)?;
942
943        self.rpc_tx_converter.convert_rpc_tx(tx, signer, tx_info).map_err(Into::into)
944    }
945
946    fn build_simulate_v1_transaction(
947        &self,
948        request: RpcTxReq<Network>,
949    ) -> Result<TxTy<N>, Self::Error> {
950        Ok(self
951            .sim_tx_converter
952            .convert_sim_tx(request)
953            .map_err(|e| TransactionConversionError(e.to_string()))?)
954    }
955
956    fn tx_env(
957        &self,
958        request: RpcTxReq<Network>,
959        evm_env: &EvmEnvFor<Evm>,
960    ) -> Result<TxEnvFor<Evm>, Self::Error> {
961        self.tx_env_converter.convert_tx_env(request, evm_env).map_err(Into::into)
962    }
963
964    fn convert_receipts(
965        &self,
966        receipts: Vec<ConvertReceiptInput<'_, Self::Primitives>>,
967    ) -> Result<Vec<RpcReceipt<Self::Network>>, Self::Error> {
968        self.receipt_converter.convert_receipts(receipts)
969    }
970
971    fn convert_receipts_with_block(
972        &self,
973        receipts: Vec<ConvertReceiptInput<'_, Self::Primitives>>,
974        block: &SealedBlock<BlockTy<Self::Primitives>>,
975    ) -> Result<Vec<RpcReceipt<Self::Network>>, Self::Error> {
976        self.receipt_converter.convert_receipts_with_block(receipts, block)
977    }
978
979    fn convert_header(
980        &self,
981        header: SealedHeaderFor<Self::Primitives>,
982        block_size: usize,
983    ) -> Result<RpcHeader<Self::Network>, Self::Error> {
984        Ok(self.header_converter.convert_header(header, block_size)?)
985    }
986}
987
988/// Optimism specific RPC transaction compatibility implementations.
989#[cfg(feature = "op")]
990pub mod op {
991    use super::*;
992    use alloy_consensus::SignableTransaction;
993    use alloy_primitives::{Address, Bytes, Signature};
994    use op_alloy_consensus::{
995        transaction::{OpDepositInfo, OpTransactionInfo},
996        OpTxEnvelope,
997    };
998    use op_alloy_rpc_types::OpTransactionRequest;
999    use op_revm::OpTransaction;
1000    use reth_optimism_primitives::DepositReceipt;
1001    use reth_primitives_traits::SignedTransaction;
1002    use reth_storage_api::{errors::ProviderError, ReceiptProvider};
1003
1004    /// Creates [`OpTransactionInfo`] by adding [`OpDepositInfo`] to [`TransactionInfo`] if `tx` is
1005    /// a deposit.
1006    pub fn try_into_op_tx_info<Tx, T>(
1007        provider: &T,
1008        tx: &Tx,
1009        tx_info: TransactionInfo,
1010    ) -> Result<OpTransactionInfo, ProviderError>
1011    where
1012        Tx: op_alloy_consensus::OpTransaction + SignedTransaction,
1013        T: ReceiptProvider<Receipt: DepositReceipt>,
1014    {
1015        let deposit_meta = if tx.is_deposit() {
1016            provider.receipt_by_hash(*tx.tx_hash())?.and_then(|receipt| {
1017                receipt.as_deposit_receipt().map(|receipt| OpDepositInfo {
1018                    deposit_receipt_version: receipt.deposit_receipt_version,
1019                    deposit_nonce: receipt.deposit_nonce,
1020                })
1021            })
1022        } else {
1023            None
1024        }
1025        .unwrap_or_default();
1026
1027        Ok(OpTransactionInfo::new(tx_info, deposit_meta))
1028    }
1029
1030    impl<T: op_alloy_consensus::OpTransaction + alloy_consensus::Transaction> FromConsensusTx<T>
1031        for op_alloy_rpc_types::Transaction<T>
1032    {
1033        type TxInfo = OpTransactionInfo;
1034        type Err = Infallible;
1035
1036        fn from_consensus_tx(
1037            tx: T,
1038            signer: Address,
1039            tx_info: Self::TxInfo,
1040        ) -> Result<Self, Self::Err> {
1041            Ok(Self::from_transaction(Recovered::new_unchecked(tx, signer), tx_info))
1042        }
1043    }
1044
1045    impl TryIntoSimTx<OpTxEnvelope> for OpTransactionRequest {
1046        fn try_into_sim_tx(self) -> Result<OpTxEnvelope, ValueError<Self>> {
1047            let tx = self
1048                .build_typed_tx()
1049                .map_err(|request| ValueError::new(request, "Required fields missing"))?;
1050
1051            // Create an empty signature for the transaction.
1052            let signature = Signature::new(Default::default(), Default::default(), false);
1053
1054            Ok(tx.into_signed(signature).into())
1055        }
1056    }
1057
1058    impl TryIntoTxEnv<OpTransaction<TxEnv>> for OpTransactionRequest {
1059        type Err = EthTxEnvError;
1060
1061        fn try_into_tx_env<Spec>(
1062            self,
1063            cfg_env: &CfgEnv<Spec>,
1064            block_env: &BlockEnv,
1065        ) -> Result<OpTransaction<TxEnv>, Self::Err> {
1066            Ok(OpTransaction {
1067                base: self.as_ref().clone().try_into_tx_env(cfg_env, block_env)?,
1068                enveloped_tx: Some(Bytes::new()),
1069                deposit: Default::default(),
1070            })
1071        }
1072    }
1073}
1074
1075/// Trait for converting network transaction responses to primitive transaction types.
1076pub trait TryFromTransactionResponse<N: Network> {
1077    /// The error type returned if the conversion fails.
1078    type Error: core::error::Error + Send + Sync + Unpin;
1079
1080    /// Converts a network transaction response to a primitive transaction type.
1081    ///
1082    /// # Returns
1083    ///
1084    /// Returns `Ok(Self)` on successful conversion, or `Err(Self::Error)` if the conversion fails.
1085    fn from_transaction_response(
1086        transaction_response: N::TransactionResponse,
1087    ) -> Result<Self, Self::Error>
1088    where
1089        Self: Sized;
1090}
1091
1092impl TryFromTransactionResponse<alloy_network::Ethereum>
1093    for reth_ethereum_primitives::TransactionSigned
1094{
1095    type Error = Infallible;
1096
1097    fn from_transaction_response(transaction_response: Transaction) -> Result<Self, Self::Error> {
1098        Ok(transaction_response.into_inner().into())
1099    }
1100}
1101
1102#[cfg(feature = "op")]
1103impl TryFromTransactionResponse<op_alloy_network::Optimism>
1104    for reth_optimism_primitives::OpTransactionSigned
1105{
1106    type Error = Infallible;
1107
1108    fn from_transaction_response(
1109        transaction_response: op_alloy_rpc_types::Transaction,
1110    ) -> Result<Self, Self::Error> {
1111        Ok(transaction_response.inner.into_inner())
1112    }
1113}
1114
1115#[cfg(test)]
1116mod transaction_response_tests {
1117    use super::*;
1118    use alloy_consensus::{transaction::Recovered, EthereumTxEnvelope, Signed, TxLegacy};
1119    use alloy_network::Ethereum;
1120    use alloy_primitives::{Address, Signature, B256, U256};
1121    use alloy_rpc_types_eth::Transaction;
1122
1123    #[test]
1124    fn test_ethereum_transaction_conversion() {
1125        let signed_tx = Signed::new_unchecked(
1126            TxLegacy::default(),
1127            Signature::new(U256::ONE, U256::ONE, false),
1128            B256::ZERO,
1129        );
1130        let envelope = EthereumTxEnvelope::Legacy(signed_tx);
1131
1132        let tx_response = Transaction {
1133            inner: Recovered::new_unchecked(envelope, Address::ZERO),
1134            block_hash: None,
1135            block_number: None,
1136            transaction_index: None,
1137            effective_gas_price: None,
1138        };
1139
1140        let result = <reth_ethereum_primitives::TransactionSigned as TryFromTransactionResponse<
1141            Ethereum,
1142        >>::from_transaction_response(tx_response);
1143        assert!(result.is_ok());
1144    }
1145
1146    #[cfg(feature = "op")]
1147    mod op {
1148        use super::*;
1149        use crate::transaction::TryIntoTxEnv;
1150        use revm_context::{BlockEnv, CfgEnv};
1151
1152        #[test]
1153        fn test_optimism_transaction_conversion() {
1154            use op_alloy_consensus::OpTxEnvelope;
1155            use op_alloy_network::Optimism;
1156            use reth_optimism_primitives::OpTransactionSigned;
1157
1158            let signed_tx = Signed::new_unchecked(
1159                TxLegacy::default(),
1160                Signature::new(U256::ONE, U256::ONE, false),
1161                B256::ZERO,
1162            );
1163            let envelope = OpTxEnvelope::Legacy(signed_tx);
1164
1165            let inner_tx = Transaction {
1166                inner: Recovered::new_unchecked(envelope, Address::ZERO),
1167                block_hash: None,
1168                block_number: None,
1169                transaction_index: None,
1170                effective_gas_price: None,
1171            };
1172
1173            let tx_response = op_alloy_rpc_types::Transaction {
1174                inner: inner_tx,
1175                deposit_nonce: None,
1176                deposit_receipt_version: None,
1177            };
1178
1179            let result = <OpTransactionSigned as TryFromTransactionResponse<Optimism>>::from_transaction_response(tx_response);
1180
1181            assert!(result.is_ok());
1182        }
1183
1184        #[test]
1185        fn test_op_into_tx_env() {
1186            use op_alloy_rpc_types::OpTransactionRequest;
1187            use op_revm::{transaction::OpTxTr, OpSpecId};
1188            use revm_context::Transaction;
1189
1190            let s = r#"{"from":"0x0000000000000000000000000000000000000000","to":"0x6d362b9c3ab68c0b7c79e8a714f1d7f3af63655f","input":"0x1626ba7ec8ee0d506e864589b799a645ddb88b08f5d39e8049f9f702b3b61fa15e55fc73000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000550000002d6db27c52e3c11c1cf24072004ac75cba49b25bf45f513902e469755e1f3bf2ca8324ad16930b0a965c012a24bb1101f876ebebac047bd3b6bf610205a27171eaaeffe4b5e5589936f4e542d637b627311b0000000000000000000000","data":"0x1626ba7ec8ee0d506e864589b799a645ddb88b08f5d39e8049f9f702b3b61fa15e55fc73000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000550000002d6db27c52e3c11c1cf24072004ac75cba49b25bf45f513902e469755e1f3bf2ca8324ad16930b0a965c012a24bb1101f876ebebac047bd3b6bf610205a27171eaaeffe4b5e5589936f4e542d637b627311b0000000000000000000000","chainId":"0x7a69"}"#;
1191
1192            let req: OpTransactionRequest = serde_json::from_str(s).unwrap();
1193
1194            let cfg = CfgEnv::<OpSpecId>::default();
1195            let block_env = BlockEnv::default();
1196            let tx_env = req.try_into_tx_env(&cfg, &block_env).unwrap();
1197            assert_eq!(tx_env.gas_limit(), block_env.gas_limit);
1198            assert_eq!(tx_env.gas_price(), 0);
1199            assert!(tx_env.enveloped_tx().unwrap().is_empty());
1200        }
1201    }
1202}