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