Skip to main content

reth_rpc_eth_api/helpers/
transaction.rs

1//! Database access for `eth_` transaction RPC methods. Loads transaction and receipt data w.r.t.
2//! network.
3
4use super::{EthApiSpec, EthSigner, LoadBlock, LoadFee, LoadReceipt, LoadState, SpawnBlocking};
5use crate::{
6    helpers::{estimate::EstimateCall, spec::SignersForRpc},
7    FromEthApiError, FullEthApiTypes, IntoEthApiError, RpcNodeCore, RpcNodeCoreExt, RpcReceipt,
8    RpcTransaction,
9};
10use alloy_consensus::{
11    transaction::{SignerRecoverable, TransactionMeta, TxHashRef},
12    BlockHeader, Transaction,
13};
14use alloy_dyn_abi::TypedData;
15use alloy_eip7928::bal::DecodedBal;
16use alloy_eips::{eip2718::Encodable2718, BlockId};
17use alloy_network::{TransactionBuilder, TransactionBuilder4844};
18use alloy_primitives::{Address, Bytes, TxHash, B256, U256};
19use alloy_rpc_types_eth::{state::EvmOverrides, TransactionInfo};
20use futures::{Future, StreamExt};
21use reth_chain_state::CanonStateSubscriptions;
22use reth_primitives_traits::{
23    BlockBody, Recovered, RecoveredBlock, SignedTransaction, TxTy, WithEncoded,
24};
25use reth_rpc_convert::{transaction::RpcConvert, RpcTxReq, TransactionConversionError};
26use reth_rpc_eth_types::{
27    block::convert_transaction_receipt,
28    utils::binary_search,
29    EthApiError::{self, TransactionConfirmationTimeout},
30    FillTransaction, SignError, TransactionSource,
31};
32use reth_storage_api::{
33    BlockNumReader, BlockReaderIdExt, ProviderBlock, ProviderReceipt, ProviderTx, ReceiptProvider,
34    TransactionsProvider,
35};
36use reth_transaction_pool::{
37    AddedTransactionOutcome, PoolPooledTx, PoolTransaction, PoolTx, TransactionOrigin,
38    TransactionPool,
39};
40use revm::state::bal::Bal as RevmBal;
41use std::{sync::Arc, time::Duration};
42
43/// Transaction related functions for the [`EthApiServer`](crate::EthApiServer) trait in
44/// the `eth_` namespace.
45///
46/// This includes utilities for transaction tracing, transacting and inspection.
47///
48/// Async functions that are spawned onto the
49/// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool) begin with `spawn_`
50///
51/// ## Calls
52///
53/// There are subtle differences between when transacting [`RpcTxReq`]:
54///
55/// The endpoints `eth_call` and `eth_estimateGas` and `eth_createAccessList` should always
56/// __disable__ the base fee check in the EVM environment.
57///
58/// The behaviour for tracing endpoints is not consistent across clients.
59/// Geth also disables the basefee check for tracing: <https://github.com/ethereum/go-ethereum/blob/bc0b87ca196f92e5af49bd33cc190ef0ec32b197/eth/tracers/api.go#L955-L955>
60/// Erigon does not: <https://github.com/ledgerwatch/erigon/blob/aefb97b07d1c4fd32a66097a24eddd8f6ccacae0/turbo/transactions/tracing.go#L209-L209>
61///
62/// See also <https://github.com/paradigmxyz/reth/issues/6240>
63///
64/// This implementation follows the behaviour of Geth and disables the basefee check for tracing.
65pub trait EthTransactions: LoadTransaction<Provider: BlockReaderIdExt> {
66    /// Returns a handle for signing data.
67    ///
68    /// Signer access in default (L1) trait method implementations.
69    fn signers(&self) -> &SignersForRpc<Self::Provider, Self::NetworkTypes>;
70
71    /// Returns a list of addresses owned by provider.
72    fn accounts(&self) -> Vec<Address> {
73        self.signers().read().iter().flat_map(|s| s.accounts()).collect()
74    }
75
76    /// Returns the timeout duration for `send_raw_transaction_sync` RPC method.
77    fn send_raw_transaction_sync_timeout(&self) -> Duration;
78
79    /// Decodes and recovers the transaction and submits it to the pool.
80    ///
81    /// Returns the hash of the transaction.
82    fn send_raw_transaction(
83        &self,
84        tx: Bytes,
85    ) -> impl Future<Output = Result<B256, Self::Error>> + Send {
86        async move {
87            let pool_transaction =
88                <PoolTx<Self::Pool> as PoolTransaction>::recover_raw_transaction(&tx)
89                    .map_err(Self::Error::from_eth_err)?;
90            self.send_pool_transaction(
91                TransactionOrigin::Local,
92                WithEncoded::new(tx, pool_transaction),
93            )
94            .await
95        }
96    }
97
98    /// Submits the transaction to the pool with the given [`TransactionOrigin`].
99    fn send_transaction(
100        &self,
101        origin: TransactionOrigin,
102        tx: WithEncoded<Recovered<PoolPooledTx<Self::Pool>>>,
103    ) -> impl Future<Output = Result<B256, Self::Error>> + Send {
104        async move {
105            let (encoded, recovered) = tx.split();
106            let pool_transaction =
107                <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
108
109            self.send_pool_transaction(origin, WithEncoded::new(encoded, pool_transaction)).await
110        }
111    }
112
113    /// Submits the pool transaction to the pool with the given [`TransactionOrigin`].
114    fn send_pool_transaction(
115        &self,
116        origin: TransactionOrigin,
117        tx: WithEncoded<PoolTx<Self::Pool>>,
118    ) -> impl Future<Output = Result<B256, Self::Error>> + Send;
119
120    /// Decodes and recovers the transaction and submits it to the pool.
121    ///
122    /// And awaits the receipt.
123    fn send_raw_transaction_sync(
124        &self,
125        tx: Bytes,
126        timeout_ms: Option<u64>,
127    ) -> impl Future<Output = Result<RpcReceipt<Self::NetworkTypes>, Self::Error>> + Send
128    where
129        Self: LoadReceipt + 'static,
130    {
131        let this = self.clone();
132        let configured_timeout = self.send_raw_transaction_sync_timeout();
133        let timeout_duration = timeout_ms
134            .filter(|timeout_ms| *timeout_ms > 0)
135            .map(Duration::from_millis)
136            .map(|timeout| timeout.min(configured_timeout))
137            .unwrap_or(configured_timeout);
138        async move {
139            let mut stream = this.provider().canonical_state_stream();
140            let hash = EthTransactions::send_raw_transaction(&this, tx).await?;
141            tokio::time::timeout(timeout_duration, async {
142                while let Some(notification) = stream.next().await {
143                    let chain = notification.committed();
144                    if let Some((block, tx, receipt, all_receipts)) =
145                        chain.find_transaction_and_receipt_by_hash(hash) &&
146                        let Some(receipt) = convert_transaction_receipt(
147                            block,
148                            all_receipts,
149                            tx,
150                            receipt,
151                            this.converter(),
152                        )
153                        .transpose()
154                        .map_err(Self::Error::from)?
155                    {
156                        return Ok(receipt);
157                    }
158                }
159                Err(Self::Error::from_eth_err(TransactionConfirmationTimeout {
160                    hash,
161                    duration: timeout_duration,
162                }))
163            })
164            .await
165            .unwrap_or_else(|_elapsed| {
166                Err(Self::Error::from_eth_err(TransactionConfirmationTimeout {
167                    hash,
168                    duration: timeout_duration,
169                }))
170            })
171        }
172    }
173
174    /// Returns the transaction by hash.
175    ///
176    /// Checks the pool and state.
177    ///
178    /// Returns `Ok(None)` if no matching transaction was found.
179    #[expect(clippy::complexity)]
180    fn transaction_by_hash(
181        &self,
182        hash: B256,
183    ) -> impl Future<
184        Output = Result<Option<TransactionSource<ProviderTx<Self::Provider>>>, Self::Error>,
185    > + Send {
186        LoadTransaction::transaction_by_hash(self, hash)
187    }
188
189    /// Returns all transactions from the local pending pool.
190    fn pending_transactions(&self) -> Result<Vec<RpcTransaction<Self::NetworkTypes>>, Self::Error> {
191        self.pool()
192            .pending_transactions()
193            .into_iter()
194            .map(|tx| self.converter().fill_pending(tx.transaction.clone_into_consensus()))
195            .collect::<Result<Vec<_>, _>>()
196            .map_err(Self::Error::from)
197    }
198
199    /// Get all transactions in the block with the given hash.
200    ///
201    /// Returns `None` if block does not exist.
202    #[expect(clippy::type_complexity)]
203    fn transactions_by_block(
204        &self,
205        block: B256,
206    ) -> impl Future<Output = Result<Option<Vec<ProviderTx<Self::Provider>>>, Self::Error>> + Send
207    {
208        async move {
209            self.cache()
210                .get_recovered_block(block)
211                .await
212                .map(|b| b.map(|b| b.body().transactions().to_vec()))
213                .map_err(Self::Error::from_eth_err)
214        }
215    }
216
217    /// Returns the EIP-2718 encoded transaction by hash.
218    ///
219    /// If this is a pooled EIP-4844 transaction, the blob sidecar is included.
220    ///
221    /// Checks the pool and state.
222    ///
223    /// Returns `Ok(None)` if no matching transaction was found.
224    fn raw_transaction_by_hash(
225        &self,
226        hash: B256,
227    ) -> impl Future<Output = Result<Option<Bytes>, Self::Error>> + Send {
228        async move {
229            // Note: this is mostly used to fetch pooled transactions so we check the pool first
230            if let Some(tx) =
231                self.pool().get_pooled_transaction_element(hash).map(|tx| tx.encoded_2718().into())
232            {
233                return Ok(Some(tx))
234            }
235
236            self.spawn_blocking_io(move |ref this| {
237                Ok(this
238                    .provider()
239                    .transaction_by_hash(hash)
240                    .map_err(Self::Error::from_eth_err)?
241                    .map(|tx| tx.encoded_2718().into()))
242            })
243            .await
244        }
245    }
246
247    /// Returns the _historical_ transaction and the block it was mined in
248    #[expect(clippy::type_complexity)]
249    fn historical_transaction_by_hash_at(
250        &self,
251        hash: B256,
252    ) -> impl Future<
253        Output = Result<Option<(TransactionSource<ProviderTx<Self::Provider>>, B256)>, Self::Error>,
254    > + Send {
255        async move {
256            match self.transaction_by_hash_at(hash).await? {
257                None => Ok(None),
258                Some((tx, at)) => Ok(at.as_block_hash().map(|hash| (tx, hash))),
259            }
260        }
261    }
262
263    /// Returns the transaction receipt for the given hash.
264    ///
265    /// Returns None if the transaction does not exist or is pending
266    /// Note: The tx receipt is not available for pending transactions.
267    fn transaction_receipt(
268        &self,
269        hash: B256,
270    ) -> impl Future<Output = Result<Option<RpcReceipt<Self::NetworkTypes>>, Self::Error>> + Send
271    where
272        Self: LoadReceipt + 'static,
273    {
274        async move {
275            match self.load_transaction_and_receipt(hash).await? {
276                Some((tx, meta, receipt, all_receipts, block)) => self
277                    .build_transaction_receipt(tx, meta, receipt, all_receipts, block)
278                    .await
279                    .map(Some),
280                None => Ok(None),
281            }
282        }
283    }
284
285    /// Helper method that loads a transaction and its receipt.
286    ///
287    /// The returned transaction has its sender already recovered.
288    #[expect(clippy::complexity)]
289    fn load_transaction_and_receipt(
290        &self,
291        hash: TxHash,
292    ) -> impl Future<
293        Output = Result<
294            Option<(
295                Recovered<ProviderTx<Self::Provider>>,
296                TransactionMeta,
297                ProviderReceipt<Self::Provider>,
298                Option<Arc<Vec<ProviderReceipt<Self::Provider>>>>,
299                Option<Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>>,
300            )>,
301            Self::Error,
302        >,
303    > + Send
304    where
305        Self: 'static,
306    {
307        async move {
308            if let Some(cached) = self.cache().get_transaction_by_hash(hash).await &&
309                let Some(tx) = cached.recovered_transaction().map(|tx| tx.cloned())
310            {
311                let meta = cached.transaction_meta(hash);
312
313                // Best case: receipts are also cached.
314                if let Some(all_receipts) = cached.receipts.clone() &&
315                    let Some(receipt) = all_receipts.get(cached.tx_index).cloned()
316                {
317                    return Ok(Some((tx, meta, receipt, Some(all_receipts), Some(cached.block))));
318                }
319
320                // Block still cached but receipts evicted — fetch via cache since
321                // `build_transaction_receipt` needs all receipts for gas accounting
322                // anyway.
323                if let Some(receipts) = self
324                    .cache()
325                    .get_receipts(cached.block.hash())
326                    .await
327                    .map_err(Self::Error::from_eth_err)? &&
328                    let Some(receipt) = receipts.get(cached.tx_index).cloned()
329                {
330                    return Ok(Some((tx, meta, receipt, Some(receipts), Some(cached.block))));
331                }
332            }
333
334            // Full cache miss — fetch both from provider.
335            self.spawn_blocking_io(move |this| {
336                let provider = this.provider();
337                let Some((tx, meta)) = provider
338                    .transaction_by_hash_with_meta(hash)
339                    .map_err(Self::Error::from_eth_err)?
340                else {
341                    return Ok(None);
342                };
343
344                let tx = tx.try_into_recovered_unchecked().map_err(Self::Error::from_eth_err)?;
345
346                let receipt = provider.receipt_by_hash(hash).map_err(Self::Error::from_eth_err)?;
347
348                Ok(receipt.map(|receipt| (tx, meta, receipt, None, None)))
349            })
350            .await
351        }
352    }
353
354    /// Get transaction by [`BlockId`] and index of transaction within that block.
355    ///
356    /// Returns `Ok(None)` if the block does not exist, or index is out of range.
357    fn transaction_by_block_and_tx_index(
358        &self,
359        block_id: BlockId,
360        index: usize,
361    ) -> impl Future<Output = Result<Option<RpcTransaction<Self::NetworkTypes>>, Self::Error>> + Send
362    where
363        Self: LoadBlock,
364    {
365        async move {
366            if let Some(block) = self.recovered_block(block_id).await? {
367                let block_hash = block.hash();
368                let block_number = block.number();
369                let block_timestamp = block.timestamp();
370                let base_fee_per_gas = block.base_fee_per_gas();
371                if let Some((signer, tx)) = block.transactions_with_sender().nth(index) {
372                    let tx_info = TransactionInfo {
373                        hash: Some(*tx.tx_hash()),
374                        block_hash: Some(block_hash),
375                        block_number: Some(block_number),
376                        block_timestamp: Some(block_timestamp),
377                        base_fee: base_fee_per_gas,
378                        index: Some(index as u64),
379                    };
380
381                    return Ok(Some(
382                        self.converter().fill(tx.clone().with_signer(*signer), tx_info)?,
383                    ))
384                }
385            }
386
387            Ok(None)
388        }
389    }
390
391    /// Find a transaction by sender's address and nonce.
392    fn get_transaction_by_sender_and_nonce(
393        &self,
394        sender: Address,
395        nonce: u64,
396        include_pending: bool,
397    ) -> impl Future<Output = Result<Option<RpcTransaction<Self::NetworkTypes>>, Self::Error>> + Send
398    where
399        Self: LoadBlock + LoadState,
400    {
401        async move {
402            // Check the pool first
403            if include_pending &&
404                let Some(tx) =
405                    RpcNodeCore::pool(self).get_transaction_by_sender_and_nonce(sender, nonce)
406            {
407                let transaction = tx.transaction.clone_into_consensus();
408                return Ok(Some(self.converter().fill_pending(transaction)?));
409            }
410
411            // Note: we can't optimize for contracts (account with code) and cannot shortcircuit if
412            // the address has code, because with 7702 EOAs can also have code
413
414            let highest = self.transaction_count(sender, None).await?.saturating_to::<u64>();
415
416            // If the nonce is higher or equal to the highest nonce, the transaction is pending or
417            // not exists.
418            if nonce >= highest {
419                return Ok(None);
420            }
421
422            let high = self.provider().best_block_number().map_err(Self::Error::from_eth_err)?;
423
424            // Perform a binary search over the block range to find the block in which the sender's
425            // nonce reached the requested nonce.
426            let num = binary_search::<_, _, Self::Error>(1, high, |mid| async move {
427                let mid_nonce =
428                    self.transaction_count(sender, Some(mid.into())).await?.saturating_to::<u64>();
429
430                Ok(mid_nonce > nonce)
431            })
432            .await?;
433
434            let block_id = num.into();
435            self.recovered_block(block_id)
436                .await?
437                .and_then(|block| {
438                    let block_hash = block.hash();
439                    let block_number = block.number();
440                    let block_timestamp = block.timestamp();
441                    let base_fee_per_gas = block.base_fee_per_gas();
442
443                    block
444                        .transactions_with_sender()
445                        .enumerate()
446                        .find(|(_, (signer, tx))| **signer == sender && (*tx).nonce() == nonce)
447                        .map(|(index, (signer, tx))| {
448                            let tx_info = TransactionInfo {
449                                hash: Some(*tx.tx_hash()),
450                                block_hash: Some(block_hash),
451                                block_number: Some(block_number),
452                                block_timestamp: Some(block_timestamp),
453                                base_fee: base_fee_per_gas,
454                                index: Some(index as u64),
455                            };
456                            Ok(self.converter().fill(tx.clone().with_signer(*signer), tx_info)?)
457                        })
458                })
459                .ok_or(EthApiError::HeaderNotFound(block_id))?
460                .map(Some)
461        }
462    }
463
464    /// Get transaction, as raw bytes, by [`BlockId`] and index of transaction within that block.
465    ///
466    /// Returns `Ok(None)` if the block does not exist, or index is out of range.
467    fn raw_transaction_by_block_and_tx_index(
468        &self,
469        block_id: BlockId,
470        index: usize,
471    ) -> impl Future<Output = Result<Option<Bytes>, Self::Error>> + Send
472    where
473        Self: LoadBlock,
474    {
475        async move {
476            if let Some(block) = self.recovered_block(block_id).await? &&
477                let Some(tx) = block.body().transactions().get(index)
478            {
479                return Ok(Some(tx.encoded_2718().into()))
480            }
481
482            Ok(None)
483        }
484    }
485
486    /// Signs transaction with a matching signer, if any and submits the transaction to the pool.
487    /// Returns the hash of the signed transaction.
488    fn send_transaction_request(
489        &self,
490        mut request: RpcTxReq<Self::NetworkTypes>,
491    ) -> impl Future<Output = Result<B256, Self::Error>> + Send
492    where
493        Self: EthApiSpec + LoadBlock + EstimateCall,
494    {
495        async move {
496            let from = match request.as_ref().from() {
497                Some(from) => from,
498                None => return Err(SignError::NoAccount.into_eth_err()),
499            };
500
501            if self.find_signer(&from).is_err() {
502                return Err(SignError::NoAccount.into_eth_err())
503            }
504
505            // set nonce if not already set before
506            if request.as_ref().nonce().is_none() {
507                let nonce = self.next_available_nonce_for(&request).await?;
508                request.as_mut().set_nonce(nonce);
509            }
510
511            let chain_id = self.chain_id();
512            // A caller that pins `chainId` is asserting which chain it means to sign for.
513            // Silently rewriting it would sign and submit on a different chain than asked
514            // for, so reject the mismatch instead. Matches geth's `setDefaults`.
515            if let Some(request_chain_id) = request.as_ref().chain_id() &&
516                request_chain_id != chain_id.to::<u64>()
517            {
518                return Err(EthApiError::InvalidParams(format!(
519                    "chainId does not match node's (have={request_chain_id}, want={})",
520                    chain_id.to::<u64>()
521                ))
522                .into())
523            }
524            request.as_mut().set_chain_id(chain_id.to());
525
526            if request.as_ref().gas_limit().is_none() {
527                let estimated_gas = self
528                    .estimate_gas_at(request.clone(), BlockId::pending(), EvmOverrides::default())
529                    .await?;
530                request.as_mut().set_gas_limit(estimated_gas.to());
531            }
532
533            let transaction = self.sign_request(&from, request).await?.with_signer(from);
534
535            let pool_transaction =
536                <<Self as RpcNodeCore>::Pool as TransactionPool>::Transaction::try_from_consensus(
537                    transaction,
538                )
539                .map_err(|e| {
540                    Self::Error::from_eth_err(TransactionConversionError::Other(e.to_string()))
541                })?;
542
543            // submit the transaction to the pool with a `Local` origin
544            let AddedTransactionOutcome { hash, .. } = self
545                .pool()
546                .add_transaction(TransactionOrigin::Local, pool_transaction)
547                .await
548                .map_err(Self::Error::from_eth_err)?;
549
550            Ok(hash)
551        }
552    }
553
554    /// Fills the defaults on a given unsigned transaction.
555    fn fill_transaction(
556        &self,
557        mut request: RpcTxReq<Self::NetworkTypes>,
558    ) -> impl Future<Output = Result<FillTransaction<TxTy<Self::Primitives>>, Self::Error>> + Send
559    where
560        Self: EthApiSpec + LoadBlock + EstimateCall + LoadFee,
561    {
562        async move {
563            if request.as_ref().value().is_none() {
564                request.as_mut().set_value(U256::ZERO);
565            }
566
567            if request.as_ref().nonce().is_none() {
568                let nonce = self.next_available_nonce_for(&request).await?;
569                request.as_mut().set_nonce(nonce);
570            }
571
572            let chain_id = self.chain_id();
573            // A caller that pins `chainId` is asserting which chain it means to sign for.
574            // Silently rewriting it would sign and submit on a different chain than asked
575            // for, so reject the mismatch instead. Matches geth's `setDefaults`.
576            if let Some(request_chain_id) = request.as_ref().chain_id() &&
577                request_chain_id != chain_id.to::<u64>()
578            {
579                return Err(EthApiError::InvalidParams(format!(
580                    "chainId does not match node's (have={request_chain_id}, want={})",
581                    chain_id.to::<u64>()
582                ))
583                .into())
584            }
585            request.as_mut().set_chain_id(chain_id.to());
586
587            if request.as_ref().has_eip4844_fields() &&
588                request.as_ref().max_fee_per_blob_gas().is_none()
589            {
590                let blob_fee = self.blob_base_fee().await?;
591                request.as_mut().set_max_fee_per_blob_gas(blob_fee.to());
592            }
593
594            // Use `sidecar.is_some()` instead of `blob_sidecar().is_some()` to handle
595            // both EIP-4844 (v0) and EIP-7594 (v1) sidecar formats
596            if request.as_ref().sidecar.is_some() &&
597                request.as_ref().blob_versioned_hashes.is_none()
598            {
599                request.as_mut().populate_blob_hashes();
600            }
601
602            if request.as_ref().gas_limit().is_none() {
603                let estimated_gas = self
604                    .estimate_gas_at(request.clone(), BlockId::pending(), EvmOverrides::default())
605                    .await?;
606                request.as_mut().set_gas_limit(estimated_gas.to());
607            }
608
609            if request.as_ref().gas_price().is_none() {
610                let tip = if let Some(tip) = request.as_ref().max_priority_fee_per_gas() {
611                    tip
612                } else {
613                    let tip = self.suggested_priority_fee().await?.to::<u128>();
614                    request.as_mut().set_max_priority_fee_per_gas(tip);
615                    tip
616                };
617                if request.as_ref().max_fee_per_gas().is_none() {
618                    let header =
619                        self.provider().latest_header().map_err(Self::Error::from_eth_err)?;
620                    let base_fee = header.and_then(|h| h.base_fee_per_gas()).unwrap_or_default();
621                    // Use `2 * base_fee` as headroom, matching go-ethereum's
622                    // `setLondonFeeDefaults`, so the transaction does not
623                    // become invalid if the base fee rises before it is
624                    // included. This does not increase the effective price the sender pays:
625                    // `max_fee_per_gas` is only an upper bound and the sender still pays
626                    // `base_fee + min(tip, max_fee_per_gas - base_fee)`.
627                    request.as_mut().set_max_fee_per_gas(base_fee as u128 * 2 + tip);
628                }
629            }
630
631            let tx = self.converter().build_simulate_v1_transaction(request)?;
632
633            let raw = tx.encoded_2718().into();
634
635            Ok(FillTransaction { raw, tx })
636        }
637    }
638
639    /// Signs a transaction, with configured signers.
640    fn sign_request(
641        &self,
642        from: &Address,
643        txn: RpcTxReq<Self::NetworkTypes>,
644    ) -> impl Future<Output = Result<ProviderTx<Self::Provider>, Self::Error>> + Send {
645        async move {
646            self.find_signer(from)?
647                .sign_transaction(txn, from)
648                .await
649                .map_err(Self::Error::from_eth_err)
650        }
651    }
652
653    /// Signs given message. Returns the signature.
654    fn sign(
655        &self,
656        account: Address,
657        message: Bytes,
658    ) -> impl Future<Output = Result<Bytes, Self::Error>> + Send {
659        async move {
660            Ok(self
661                .find_signer(&account)?
662                .sign(account, &message)
663                .await
664                .map_err(Self::Error::from_eth_err)?
665                .as_bytes()
666                .into())
667        }
668    }
669
670    /// Signs a transaction request using the given account in request
671    /// Returns the EIP-2718 encoded signed transaction.
672    fn sign_transaction(
673        &self,
674        request: RpcTxReq<Self::NetworkTypes>,
675    ) -> impl Future<Output = Result<Bytes, Self::Error>> + Send {
676        async move {
677            let from = match request.as_ref().from() {
678                Some(from) => from,
679                None => return Err(SignError::NoAccount.into_eth_err()),
680            };
681
682            Ok(self.sign_request(&from, request).await?.encoded_2718().into())
683        }
684    }
685
686    /// Encodes and signs the typed data according EIP-712. Payload must implement Eip712 trait.
687    fn sign_typed_data(&self, data: &TypedData, account: Address) -> Result<Bytes, Self::Error> {
688        Ok(self
689            .find_signer(&account)?
690            .sign_typed_data(account, data)
691            .map_err(Self::Error::from_eth_err)?
692            .as_bytes()
693            .into())
694    }
695
696    /// Returns the signer for the given account, if found in configured signers.
697    #[expect(clippy::type_complexity)]
698    fn find_signer(
699        &self,
700        account: &Address,
701    ) -> Result<
702        Box<dyn EthSigner<ProviderTx<Self::Provider>, RpcTxReq<Self::NetworkTypes>> + 'static>,
703        Self::Error,
704    > {
705        self.signers()
706            .read()
707            .iter()
708            .find(|signer| signer.is_signer_for(account))
709            .map(|signer| dyn_clone::clone_box(&**signer))
710            .ok_or_else(|| SignError::NoAccount.into_eth_err())
711    }
712}
713
714/// Loads a transaction from database.
715///
716/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` transactions RPC
717/// methods.
718pub trait LoadTransaction: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt {
719    /// Returns the transaction by hash.
720    ///
721    /// Checks the pool and state.
722    ///
723    /// Returns `Ok(None)` if no matching transaction was found.
724    #[expect(clippy::complexity)]
725    fn transaction_by_hash(
726        &self,
727        hash: B256,
728    ) -> impl Future<
729        Output = Result<Option<TransactionSource<ProviderTx<Self::Provider>>>, Self::Error>,
730    > + Send {
731        async move {
732            // First, try the RPC cache
733            if let Some(cached) = self.cache().get_transaction_by_hash(hash).await &&
734                let Some(source) = cached.to_transaction_source()
735            {
736                return Ok(Some(source));
737            }
738
739            // Cache miss - try to find the transaction on disk
740            if let Some((tx, meta)) = self
741                .spawn_blocking_io(move |this| {
742                    this.provider()
743                        .transaction_by_hash_with_meta(hash)
744                        .map_err(Self::Error::from_eth_err)
745                })
746                .await?
747            {
748                // Note: we assume this transaction is valid, because it's mined (or
749                // part of pending block) and already. We don't need to
750                // check for pre EIP-2 because this transaction could be pre-EIP-2.
751                let transaction = tx
752                    .try_into_recovered_unchecked()
753                    .map_err(|_| EthApiError::InvalidTransactionSignature)?;
754
755                return Ok(Some(TransactionSource::Block {
756                    transaction,
757                    index: meta.index,
758                    block_hash: meta.block_hash,
759                    block_number: meta.block_number,
760                    block_timestamp: meta.timestamp,
761                    base_fee: meta.base_fee,
762                }));
763            }
764
765            // tx not found on disk, check pool
766            if let Some(tx) = self.pool().get(&hash).map(|tx| tx.transaction.clone_into_consensus())
767            {
768                return Ok(Some(TransactionSource::Pool(tx.into())));
769            }
770
771            Ok(None)
772        }
773    }
774
775    /// Returns the transaction by including its corresponding [`BlockId`].
776    ///
777    /// Note: this supports pending transactions
778    #[expect(clippy::type_complexity)]
779    fn transaction_by_hash_at(
780        &self,
781        transaction_hash: B256,
782    ) -> impl Future<
783        Output = Result<
784            Option<(TransactionSource<ProviderTx<Self::Provider>>, BlockId)>,
785            Self::Error,
786        >,
787    > + Send {
788        async move {
789            Ok(self.transaction_by_hash(transaction_hash).await?.map(|tx| match tx {
790                tx @ TransactionSource::Pool(_) => (tx, BlockId::pending()),
791                tx @ TransactionSource::Block { block_hash, .. } => {
792                    (tx, BlockId::Hash(block_hash.into()))
793                }
794            }))
795        }
796    }
797
798    /// Fetches the transaction and the transaction's block
799    #[expect(clippy::type_complexity)]
800    fn transaction_and_block(
801        &self,
802        hash: B256,
803    ) -> impl Future<
804        Output = Result<
805            Option<(
806                TransactionSource<ProviderTx<Self::Provider>>,
807                Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>,
808            )>,
809            Self::Error,
810        >,
811    > + Send {
812        async move {
813            Ok(self
814                .transaction_and_block_and_maybe_bal(hash)
815                .await?
816                .map(|(transaction, block, _)| (transaction, block)))
817        }
818    }
819
820    /// Fetches the transaction and the transaction's block, together with the block's cached
821    /// block access list, if any.
822    ///
823    /// The BAL is only returned if it is already cached, it is never fetched from the BAL store.
824    #[expect(clippy::type_complexity)]
825    fn transaction_and_block_and_maybe_bal(
826        &self,
827        hash: B256,
828    ) -> impl Future<
829        Output = Result<
830            Option<(
831                TransactionSource<ProviderTx<Self::Provider>>,
832                Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>,
833                Option<Arc<DecodedBal<Arc<RevmBal>>>>,
834            )>,
835            Self::Error,
836        >,
837    > + Send {
838        async move {
839            let (transaction, at) = match self.transaction_by_hash_at(hash).await? {
840                None => return Ok(None),
841                Some(res) => res,
842            };
843
844            // Note: this is always either hash or pending
845            let block_hash = match at {
846                BlockId::Hash(hash) => hash.block_hash,
847                _ => return Ok(None),
848            };
849            let block_and_bal = self
850                .cache()
851                .get_recovered_block_and_maybe_bal(block_hash)
852                .await
853                .map_err(Self::Error::from_eth_err)?;
854            Ok(block_and_bal.map(|(block, bal)| (transaction, block, bal)))
855        }
856    }
857}