reth_optimism_rpc/eth/
transaction.rs
1use alloy_consensus::{transaction::Recovered, Transaction as _};
4use alloy_primitives::{Bytes, PrimitiveSignature as Signature, Sealable, Sealed, B256};
5use alloy_rpc_types_eth::TransactionInfo;
6use op_alloy_consensus::OpTxEnvelope;
7use op_alloy_rpc_types::{OpTransactionRequest, Transaction};
8use reth_node_api::FullNodeComponents;
9use reth_optimism_primitives::{OpReceipt, OpTransactionSigned};
10use reth_provider::{
11 BlockReader, BlockReaderIdExt, ProviderTx, ReceiptProvider, TransactionsProvider,
12};
13use reth_rpc_eth_api::{
14 helpers::{EthSigner, EthTransactions, LoadTransaction, SpawnBlocking},
15 FromEthApiError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt, TransactionCompat,
16};
17use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError};
18use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool};
19
20use crate::{eth::OpNodeCore, OpEthApi, OpEthApiError, SequencerClient};
21
22impl<N> EthTransactions for OpEthApi<N>
23where
24 Self: LoadTransaction<Provider: BlockReaderIdExt>,
25 N: OpNodeCore<Provider: BlockReader<Transaction = ProviderTx<Self::Provider>>>,
26{
27 fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner<ProviderTx<Self::Provider>>>>> {
28 self.inner.eth_api.signers()
29 }
30
31 async fn send_raw_transaction(&self, tx: Bytes) -> Result<B256, Self::Error> {
35 let recovered = recover_raw_transaction(&tx)?;
36 let pool_transaction = <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
37
38 if let Some(client) = self.raw_tx_forwarder().as_ref() {
41 tracing::debug!(target: "rpc::eth", hash = %pool_transaction.hash(), "forwarding raw transaction to sequencer");
42 let _ = client.forward_raw_transaction(&tx).await.inspect_err(|err| {
43 tracing::debug!(target: "rpc::eth", %err, hash=% *pool_transaction.hash(), "failed to forward raw transaction");
44 });
45 }
46
47 let hash = self
49 .pool()
50 .add_transaction(TransactionOrigin::Local, pool_transaction)
51 .await
52 .map_err(Self::Error::from_eth_err)?;
53
54 Ok(hash)
55 }
56}
57
58impl<N> LoadTransaction for OpEthApi<N>
59where
60 Self: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt,
61 N: OpNodeCore<Provider: TransactionsProvider, Pool: TransactionPool>,
62 Self::Pool: TransactionPool,
63{
64}
65
66impl<N> OpEthApi<N>
67where
68 N: OpNodeCore,
69{
70 pub fn raw_tx_forwarder(&self) -> Option<SequencerClient> {
72 self.inner.sequencer_client.clone()
73 }
74}
75
76impl<N> TransactionCompat<OpTransactionSigned> for OpEthApi<N>
77where
78 N: FullNodeComponents<Provider: ReceiptProvider<Receipt = OpReceipt>>,
79{
80 type Transaction = Transaction;
81 type Error = OpEthApiError;
82
83 fn fill(
84 &self,
85 tx: Recovered<OpTransactionSigned>,
86 tx_info: TransactionInfo,
87 ) -> Result<Self::Transaction, Self::Error> {
88 let tx = tx.convert::<OpTxEnvelope>();
89 let mut deposit_receipt_version = None;
90 let mut deposit_nonce = None;
91
92 if tx.is_deposit() {
93 self.inner
95 .eth_api
96 .provider()
97 .receipt_by_hash(tx.tx_hash())
98 .map_err(Self::Error::from_eth_err)?
99 .inspect(|receipt| {
100 if let OpReceipt::Deposit(receipt) = receipt {
101 deposit_receipt_version = receipt.deposit_receipt_version;
102 deposit_nonce = receipt.deposit_nonce;
103 }
104 });
105 }
106
107 let TransactionInfo {
108 block_hash, block_number, index: transaction_index, base_fee, ..
109 } = tx_info;
110
111 let effective_gas_price = if tx.is_deposit() {
112 0
116 } else {
117 base_fee
118 .map(|base_fee| {
119 tx.effective_tip_per_gas(base_fee).unwrap_or_default() + base_fee as u128
120 })
121 .unwrap_or_else(|| tx.max_fee_per_gas())
122 };
123
124 Ok(Transaction {
125 inner: alloy_rpc_types_eth::Transaction {
126 inner: tx,
127 block_hash,
128 block_number,
129 transaction_index,
130 effective_gas_price: Some(effective_gas_price),
131 },
132 deposit_nonce,
133 deposit_receipt_version,
134 })
135 }
136
137 fn build_simulate_v1_transaction(
138 &self,
139 request: alloy_rpc_types_eth::TransactionRequest,
140 ) -> Result<OpTransactionSigned, Self::Error> {
141 let request: OpTransactionRequest = request.into();
142 let Ok(tx) = request.build_typed_tx() else {
143 return Err(OpEthApiError::Eth(EthApiError::TransactionConversionError))
144 };
145
146 let signature = Signature::new(Default::default(), Default::default(), false);
148 Ok(OpTransactionSigned::new_unhashed(tx, signature))
149 }
150
151 fn otterscan_api_truncate_input(tx: &mut Self::Transaction) {
152 let input = match tx.inner.inner.inner_mut() {
153 OpTxEnvelope::Eip1559(tx) => &mut tx.tx_mut().input,
154 OpTxEnvelope::Eip2930(tx) => &mut tx.tx_mut().input,
155 OpTxEnvelope::Legacy(tx) => &mut tx.tx_mut().input,
156 OpTxEnvelope::Eip7702(tx) => &mut tx.tx_mut().input,
157 OpTxEnvelope::Deposit(tx) => {
158 let (mut deposit, hash) = std::mem::replace(
159 tx,
160 Sealed::new_unchecked(Default::default(), Default::default()),
161 )
162 .split();
163 deposit.input = deposit.input.slice(..4);
164 let mut deposit = deposit.seal_unchecked(hash);
165 std::mem::swap(tx, &mut deposit);
166 return
167 }
168 };
169 *input = input.slice(..4);
170 }
171}