reth_rpc_convert/
transaction.rs

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