1use 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::{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, TransactionRequest};
20use evm2::evm::Bal as EvmBal;
21use futures::{Future, StreamExt};
22use reth_chain_state::CanonStateSubscriptions;
23use reth_primitives_traits::{
24 BlockBody, Recovered, RecoveredBlock, SignedTransaction, TxTy, WithEncoded,
25};
26use reth_rpc_convert::{transaction::RpcConvert, RpcTxReq, TransactionConversionError};
27use reth_rpc_eth_types::{
28 block::convert_transaction_receipt,
29 utils::{binary_search, decode_raw_transaction},
30 EthApiError::{self, TransactionConfirmationTimeout},
31 EthResult, FillTransaction, SignError, TransactionSource,
32};
33use reth_storage_api::{
34 BlockNumReader, BlockReaderIdExt, ProviderBlock, ProviderReceipt, ProviderTx, ReceiptProvider,
35 TransactionsProvider,
36};
37use reth_transaction_pool::{
38 AddedTransactionOutcome, PoolPooledTx, PoolTransaction, PoolTx, TransactionOrigin,
39 TransactionPool,
40};
41use std::{sync::Arc, time::Duration};
42
43pub trait EthTransactions: LoadTransaction<Provider: BlockReaderIdExt> {
66 fn signers(&self) -> &SignersForRpc<Self::Provider, Self::NetworkTypes>;
70
71 fn accounts(&self) -> Vec<Address> {
73 self.signers().read().iter().flat_map(|s| s.accounts()).collect()
74 }
75
76 fn send_raw_transaction_sync_timeout(&self) -> Duration {
78 self.eth_api_settings().send_raw_transaction_sync_timeout
79 }
80
81 fn send_raw_transaction(
85 &self,
86 tx: Bytes,
87 ) -> impl Future<Output = Result<B256, Self::Error>> + Send {
88 async move {
89 let pool_transaction = self.recover_raw_pool_transaction(&tx)?;
90 self.send_pool_transaction(
91 TransactionOrigin::Local,
92 WithEncoded::new(tx, pool_transaction),
93 )
94 .await
95 }
96 }
97
98 fn recover_raw_pool_transaction(&self, tx: &[u8]) -> Result<PoolTx<Self::Pool>, Self::Error> {
100 match self.eth_api_settings().sender_recovery_cache.as_ref() {
101 Some(cache) => {
102 <PoolTx<Self::Pool> as PoolTransaction>::recover_raw_transaction_with_cache(
103 tx, cache,
104 )
105 }
106 None => <PoolTx<Self::Pool> as PoolTransaction>::recover_raw_transaction(tx),
107 }
108 .map_err(Self::Error::from_eth_err)
109 }
110
111 fn send_transaction(
113 &self,
114 origin: TransactionOrigin,
115 tx: WithEncoded<Recovered<PoolPooledTx<Self::Pool>>>,
116 ) -> impl Future<Output = Result<B256, Self::Error>> + Send {
117 async move {
118 let (encoded, recovered) = tx.split();
119 let pool_transaction =
120 <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
121
122 self.send_pool_transaction(origin, WithEncoded::new(encoded, pool_transaction)).await
123 }
124 }
125
126 fn send_pool_transaction(
128 &self,
129 origin: TransactionOrigin,
130 tx: WithEncoded<PoolTx<Self::Pool>>,
131 ) -> impl Future<Output = Result<B256, Self::Error>> + Send;
132
133 fn send_raw_transaction_sync(
137 &self,
138 tx: Bytes,
139 timeout_ms: Option<u64>,
140 ) -> impl Future<Output = Result<RpcReceipt<Self::NetworkTypes>, Self::Error>> + Send
141 where
142 Self: LoadReceipt,
143 {
144 let this = self.clone();
145 let configured_timeout = self.send_raw_transaction_sync_timeout();
146 let timeout_duration = timeout_ms
147 .filter(|timeout_ms| *timeout_ms > 0)
148 .map(Duration::from_millis)
149 .map(|timeout| timeout.min(configured_timeout))
150 .unwrap_or(configured_timeout);
151 async move {
152 let mut stream = this.provider().canonical_state_stream();
153 let hash = EthTransactions::send_raw_transaction(&this, tx).await?;
154 tokio::time::timeout(timeout_duration, async {
155 while let Some(notification) = stream.next().await {
156 let chain = notification.committed();
157 if let Some((block, tx, receipt, all_receipts)) =
158 chain.find_transaction_and_receipt_by_hash(hash) &&
159 let Some(receipt) = convert_transaction_receipt(
160 block,
161 all_receipts,
162 tx,
163 receipt,
164 this.converter(),
165 )
166 .transpose()?
167 {
168 return Ok(receipt);
169 }
170 }
171 Err(Self::Error::from_eth_err(TransactionConfirmationTimeout {
172 hash,
173 duration: timeout_duration,
174 }))
175 })
176 .await
177 .unwrap_or_else(|_elapsed| {
178 Err(Self::Error::from_eth_err(TransactionConfirmationTimeout {
179 hash,
180 duration: timeout_duration,
181 }))
182 })
183 }
184 }
185
186 #[expect(clippy::complexity)]
192 fn transaction_by_hash(
193 &self,
194 hash: B256,
195 ) -> impl Future<
196 Output = Result<Option<TransactionSource<ProviderTx<Self::Provider>>>, Self::Error>,
197 > + Send {
198 LoadTransaction::transaction_by_hash(self, hash)
199 }
200
201 fn pending_transactions(&self) -> Result<Vec<RpcTransaction<Self::NetworkTypes>>, Self::Error> {
203 self.pool()
204 .pending_transactions()
205 .into_iter()
206 .map(|tx| self.converter().fill_pending(tx.transaction.clone_into_consensus()))
207 .collect::<Result<Vec<_>, _>>()
208 .map_err(Self::Error::from)
209 }
210
211 #[expect(clippy::type_complexity)]
215 fn transactions_by_block(
216 &self,
217 block: B256,
218 ) -> impl Future<Output = Result<Option<Vec<ProviderTx<Self::Provider>>>, Self::Error>> + Send
219 {
220 async move {
221 self.cache()
222 .get_recovered_block(block)
223 .await
224 .map(|b| b.map(|b| b.body().transactions().to_vec()))
225 .map_err(Self::Error::from_eth_err)
226 }
227 }
228
229 fn raw_transaction_by_hash(
237 &self,
238 hash: B256,
239 ) -> impl Future<Output = Result<Option<Bytes>, Self::Error>> + Send {
240 async move {
241 if let Some(tx) =
243 self.pool().get_pooled_transaction_element(hash).map(|tx| tx.encoded_2718().into())
244 {
245 return Ok(Some(tx))
246 }
247
248 self.spawn_blocking_io(move |ref this| {
249 Ok(this
250 .provider()
251 .transaction_by_hash(hash)
252 .map_err(Self::Error::from_eth_err)?
253 .map(|tx| tx.encoded_2718().into()))
254 })
255 .await
256 }
257 }
258
259 #[expect(clippy::type_complexity)]
261 fn historical_transaction_by_hash_at(
262 &self,
263 hash: B256,
264 ) -> impl Future<
265 Output = Result<Option<(TransactionSource<ProviderTx<Self::Provider>>, B256)>, Self::Error>,
266 > + Send {
267 async move {
268 match self.transaction_by_hash_at(hash).await? {
269 None => Ok(None),
270 Some((tx, at)) => Ok(at.as_block_hash().map(|hash| (tx, hash))),
271 }
272 }
273 }
274
275 fn transaction_receipt(
280 &self,
281 hash: B256,
282 ) -> impl Future<Output = Result<Option<RpcReceipt<Self::NetworkTypes>>, Self::Error>> + Send
283 where
284 Self: LoadReceipt,
285 {
286 async move {
287 match self.load_transaction_and_receipt(hash).await? {
288 Some((tx, meta, receipt, all_receipts, block)) => self
289 .build_transaction_receipt(tx, meta, receipt, all_receipts, block)
290 .await
291 .map(Some),
292 None => Ok(None),
293 }
294 }
295 }
296
297 #[expect(clippy::complexity)]
301 fn load_transaction_and_receipt(
302 &self,
303 hash: TxHash,
304 ) -> impl Future<
305 Output = Result<
306 Option<(
307 Recovered<ProviderTx<Self::Provider>>,
308 TransactionMeta,
309 ProviderReceipt<Self::Provider>,
310 Option<Arc<Vec<ProviderReceipt<Self::Provider>>>>,
311 Option<Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>>,
312 )>,
313 Self::Error,
314 >,
315 > + Send
316 where
317 Self: 'static,
318 {
319 async move {
320 if let Some(cached) = self.cache().get_transaction_by_hash(hash).await &&
321 let Some(tx) = cached.recovered_transaction().map(|tx| tx.cloned())
322 {
323 let meta = cached.transaction_meta(hash);
324
325 if let Some(all_receipts) = cached.receipts.clone() &&
327 let Some(receipt) = all_receipts.get(cached.tx_index).cloned()
328 {
329 return Ok(Some((tx, meta, receipt, Some(all_receipts), Some(cached.block))));
330 }
331
332 if let Some(receipts) = self
336 .cache()
337 .get_receipts(cached.block.hash())
338 .await
339 .map_err(Self::Error::from_eth_err)? &&
340 let Some(receipt) = receipts.get(cached.tx_index).cloned()
341 {
342 return Ok(Some((tx, meta, receipt, Some(receipts), Some(cached.block))));
343 }
344 }
345
346 self.spawn_blocking_io(move |this| {
348 let provider = this.provider();
349 let Some((tx, meta)) = provider
350 .transaction_by_hash_with_meta(hash)
351 .map_err(Self::Error::from_eth_err)?
352 else {
353 return Ok(None);
354 };
355
356 let tx = this.recover_mined_transaction(tx).map_err(Self::Error::from_eth_err)?;
357
358 let receipt = provider.receipt_by_hash(hash).map_err(Self::Error::from_eth_err)?;
359
360 Ok(receipt.map(|receipt| (tx, meta, receipt, None, None)))
361 })
362 .await
363 }
364 }
365
366 fn transaction_by_block_and_tx_index(
370 &self,
371 block_id: BlockId,
372 index: usize,
373 ) -> impl Future<Output = Result<Option<RpcTransaction<Self::NetworkTypes>>, Self::Error>> + Send
374 where
375 Self: LoadBlock,
376 {
377 async move {
378 if let Some(block) = self.recovered_block(block_id).await? {
379 let block_hash = block.hash();
380 let block_number = block.number();
381 let block_timestamp = block.timestamp();
382 let base_fee_per_gas = block.base_fee_per_gas();
383 if let Some((signer, tx)) = block.transactions_with_sender().nth(index) {
384 let tx_info = TransactionInfo {
385 hash: Some(*tx.tx_hash()),
386 block_hash: Some(block_hash),
387 block_number: Some(block_number),
388 block_timestamp: Some(block_timestamp),
389 base_fee: base_fee_per_gas,
390 index: Some(index as u64),
391 };
392
393 return Ok(Some(
394 self.converter().fill(tx.clone().with_signer(*signer), tx_info)?,
395 ))
396 }
397 }
398
399 Ok(None)
400 }
401 }
402
403 fn get_transaction_by_sender_and_nonce(
405 &self,
406 sender: Address,
407 nonce: u64,
408 include_pending: bool,
409 ) -> impl Future<Output = Result<Option<RpcTransaction<Self::NetworkTypes>>, Self::Error>> + Send
410 where
411 Self: LoadBlock + LoadState,
412 {
413 async move {
414 if include_pending &&
416 let Some(tx) =
417 RpcNodeCore::pool(self).get_transaction_by_sender_and_nonce(sender, nonce)
418 {
419 let transaction = tx.transaction.clone_into_consensus();
420 return Ok(Some(self.converter().fill_pending(transaction)?));
421 }
422
423 let highest = self.transaction_count(sender, None).await?.saturating_to::<u64>();
427
428 if nonce >= highest {
431 return Ok(None);
432 }
433
434 let high = self.provider().best_block_number().map_err(Self::Error::from_eth_err)?;
435
436 let num = binary_search::<_, _, Self::Error>(1, high, |mid| async move {
439 let mid_nonce =
440 self.transaction_count(sender, Some(mid.into())).await?.saturating_to::<u64>();
441
442 Ok(mid_nonce > nonce)
443 })
444 .await?;
445
446 let block_id = num.into();
447 let Some(block) = self.recovered_block(block_id).await? else {
448 return Err(EthApiError::HeaderNotFound(block_id).into())
449 };
450
451 let block_hash = block.hash();
452 let block_number = block.number();
453 let block_timestamp = block.timestamp();
454 let base_fee_per_gas = block.base_fee_per_gas();
455
456 block
459 .transactions_with_sender()
460 .enumerate()
461 .find(|(_, (signer, tx))| **signer == sender && (*tx).nonce() == nonce)
462 .map(|(index, (signer, tx))| {
463 let tx_info = TransactionInfo {
464 hash: Some(*tx.tx_hash()),
465 block_hash: Some(block_hash),
466 block_number: Some(block_number),
467 block_timestamp: Some(block_timestamp),
468 base_fee: base_fee_per_gas,
469 index: Some(index as u64),
470 };
471 Ok(self.converter().fill(tx.clone().with_signer(*signer), tx_info)?)
472 })
473 .transpose()
474 }
475 }
476
477 fn raw_transaction_by_block_and_tx_index(
481 &self,
482 block_id: BlockId,
483 index: usize,
484 ) -> impl Future<Output = Result<Option<Bytes>, Self::Error>> + Send
485 where
486 Self: LoadBlock,
487 {
488 async move {
489 if let Some(block) = self.recovered_block(block_id).await? &&
490 let Some(tx) = block.body().transactions().get(index)
491 {
492 return Ok(Some(tx.encoded_2718().into()))
493 }
494
495 Ok(None)
496 }
497 }
498
499 fn send_transaction_request(
504 &self,
505 mut request: RpcTxReq<Self::NetworkTypes>,
506 ) -> impl Future<Output = Result<B256, Self::Error>> + Send
507 where
508 Self: EthApiSpec + EstimateCall + LoadFee,
509 {
510 async move {
511 ensure_no_conflicting_fee_fields(request.as_ref())?;
512
513 let from = match request.as_ref().from() {
514 Some(from) => from,
515 None => return Err(SignError::NoAccount.into_eth_err()),
516 };
517
518 let signer = self.find_signer(&from)?;
519
520 let _nonce_guard = match signer.nonce_lock() {
523 Some(lock) if request.as_ref().nonce().is_none() => Some(lock.lock_owned().await),
524 _ => None,
525 };
526
527 if request.as_ref().nonce().is_none() {
529 let nonce = self.next_available_nonce_for(&request).await?;
530 request.as_mut().set_nonce(nonce);
531 }
532
533 let chain_id = self.chain_id();
534 if let Some(request_chain_id) = request.as_ref().chain_id() &&
538 request_chain_id != chain_id.to::<u64>()
539 {
540 return Err(EthApiError::InvalidParams(format!(
541 "chainId does not match node's (have={request_chain_id}, want={})",
542 chain_id.to::<u64>()
543 ))
544 .into())
545 }
546 request.as_mut().set_chain_id(chain_id.to());
547
548 if request.as_ref().gas_price().is_none() {
550 let tip = if let Some(tip) = request.as_ref().max_priority_fee_per_gas() {
551 tip
552 } else {
553 let tip = self.suggested_priority_fee().await?.to::<u128>();
554 request.as_mut().set_max_priority_fee_per_gas(tip);
555 tip
556 };
557 if request.as_ref().max_fee_per_gas().is_none() {
558 let header =
559 self.provider().latest_header().map_err(Self::Error::from_eth_err)?;
560 let base_fee = header.and_then(|h| h.base_fee_per_gas()).unwrap_or_default();
561 request.as_mut().set_max_fee_per_gas(base_fee as u128 * 2 + tip);
562 }
563 }
564
565 if request.as_ref().gas_limit().is_none() {
566 let estimated_gas = self
567 .estimate_gas_at(request.clone(), BlockId::pending(), EvmOverrides::default())
568 .await?;
569 request.as_mut().set_gas_limit(estimated_gas.to());
570 }
571
572 let transaction = self.sign_request(&from, request).await?.with_signer(from);
573
574 let pool_transaction =
575 <<Self as RpcNodeCore>::Pool as TransactionPool>::Transaction::try_from_consensus(
576 transaction,
577 )
578 .map_err(|e| {
579 Self::Error::from_eth_err(TransactionConversionError::Other(e.to_string()))
580 })?;
581
582 let AddedTransactionOutcome { hash, .. } = self
584 .pool()
585 .add_transaction(TransactionOrigin::Local, pool_transaction)
586 .await
587 .map_err(Self::Error::from_eth_err)?;
588
589 Ok(hash)
590 }
591 }
592
593 fn fill_transaction(
597 &self,
598 mut request: RpcTxReq<Self::NetworkTypes>,
599 ) -> impl Future<Output = Result<FillTransaction<TxTy<Self::Primitives>>, Self::Error>> + Send
600 where
601 Self: EthApiSpec + EstimateCall + LoadFee,
602 {
603 async move {
604 ensure_no_conflicting_fee_fields(request.as_ref())?;
605
606 if request.as_ref().value().is_none() {
607 request.as_mut().set_value(U256::ZERO);
608 }
609
610 if request.as_ref().nonce().is_none() {
611 let nonce = self.next_available_nonce_for(&request).await?;
612 request.as_mut().set_nonce(nonce);
613 }
614
615 let chain_id = self.chain_id();
616 if let Some(request_chain_id) = request.as_ref().chain_id() &&
620 request_chain_id != chain_id.to::<u64>()
621 {
622 return Err(EthApiError::InvalidParams(format!(
623 "chainId does not match node's (have={request_chain_id}, want={})",
624 chain_id.to::<u64>()
625 ))
626 .into())
627 }
628 request.as_mut().set_chain_id(chain_id.to());
629
630 if request.as_ref().has_eip4844_fields() &&
631 request.as_ref().max_fee_per_blob_gas().is_none()
632 {
633 let blob_fee = self.blob_base_fee().await?;
634 request.as_mut().set_max_fee_per_blob_gas(blob_fee.to());
635 }
636
637 if request.as_ref().sidecar.is_some() &&
640 request.as_ref().blob_versioned_hashes.is_none()
641 {
642 request.as_mut().populate_blob_hashes();
643 }
644
645 if request.as_ref().gas_limit().is_none() {
646 let estimated_gas = self
647 .estimate_gas_at(request.clone(), BlockId::pending(), EvmOverrides::default())
648 .await?;
649 request.as_mut().set_gas_limit(estimated_gas.to());
650 }
651
652 if request.as_ref().gas_price().is_none() {
653 let tip = if let Some(tip) = request.as_ref().max_priority_fee_per_gas() {
654 tip
655 } else {
656 let tip = self.suggested_priority_fee().await?.to::<u128>();
657 request.as_mut().set_max_priority_fee_per_gas(tip);
658 tip
659 };
660 if request.as_ref().max_fee_per_gas().is_none() {
661 let header =
662 self.provider().latest_header().map_err(Self::Error::from_eth_err)?;
663 let base_fee = header.and_then(|h| h.base_fee_per_gas()).unwrap_or_default();
664 request.as_mut().set_max_fee_per_gas(base_fee as u128 * 2 + tip);
671 }
672 }
673
674 let tx = self.converter().build_simulate_v1_transaction(request)?;
675
676 let raw = tx.encoded_2718().into();
677
678 Ok(FillTransaction { raw, tx })
679 }
680 }
681
682 fn sign_request(
684 &self,
685 from: &Address,
686 txn: RpcTxReq<Self::NetworkTypes>,
687 ) -> impl Future<Output = Result<ProviderTx<Self::Provider>, Self::Error>> + Send {
688 async move {
689 self.find_signer(from)?
690 .sign_transaction(txn, from)
691 .await
692 .map_err(Self::Error::from_eth_err)
693 }
694 }
695
696 fn sign(
698 &self,
699 account: Address,
700 message: Bytes,
701 ) -> impl Future<Output = Result<Bytes, Self::Error>> + Send {
702 async move {
703 Ok(self
704 .find_signer(&account)?
705 .sign(account, &message)
706 .await
707 .map_err(Self::Error::from_eth_err)?
708 .as_bytes()
709 .into())
710 }
711 }
712
713 fn sign_transaction(
718 &self,
719 request: RpcTxReq<Self::NetworkTypes>,
720 ) -> impl Future<Output = Result<Bytes, Self::Error>> + Send {
721 async move {
722 ensure_no_conflicting_fee_fields(request.as_ref())?;
723
724 let from = match request.as_ref().from() {
725 Some(from) => from,
726 None => return Err(SignError::NoAccount.into_eth_err()),
727 };
728
729 Ok(self.sign_request(&from, request).await?.encoded_2718().into())
730 }
731 }
732
733 fn sign_typed_data(&self, data: &TypedData, account: Address) -> Result<Bytes, Self::Error> {
735 Ok(self
736 .find_signer(&account)?
737 .sign_typed_data(account, data)
738 .map_err(Self::Error::from_eth_err)?
739 .as_bytes()
740 .into())
741 }
742
743 #[expect(clippy::type_complexity)]
745 fn find_signer(
746 &self,
747 account: &Address,
748 ) -> Result<
749 Box<dyn EthSigner<ProviderTx<Self::Provider>, RpcTxReq<Self::NetworkTypes>> + 'static>,
750 Self::Error,
751 > {
752 self.signers()
753 .read()
754 .iter()
755 .find(|signer| signer.is_signer_for(account))
756 .map(|signer| dyn_clone::clone_box(&**signer))
757 .ok_or_else(|| SignError::NoAccount.into_eth_err())
758 }
759}
760
761pub trait LoadTransaction: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt {
766 fn recover_raw_transaction<T: SignedTransaction>(&self, tx: &[u8]) -> EthResult<Recovered<T>> {
768 let transaction = decode_raw_transaction::<T>(tx)?;
769 let signer = match self.eth_api_settings().sender_recovery_cache.as_ref() {
770 Some(cache) => cache.recover(&transaction),
771 None => transaction.try_recover(),
772 }
773 .map_err(|_| EthApiError::InvalidTransactionSignature)?;
774 Ok(Recovered::new_unchecked(transaction, signer))
775 }
776
777 fn recover_raw_transactions<T: SignedTransaction>(
781 &self,
782 txs: impl IntoIterator<Item = impl AsRef<[u8]>>,
783 ) -> EthResult<Vec<Recovered<T>>> {
784 txs.into_iter().map(|tx| self.recover_raw_transaction(tx.as_ref())).collect()
785 }
786
787 fn recover_mined_transaction<T: SignedTransaction>(&self, tx: T) -> EthResult<Recovered<T>> {
793 let cached = self
794 .eth_api_settings()
795 .sender_recovery_cache
796 .as_ref()
797 .and_then(|cache| cache.get(tx.tx_hash()));
798 let signer = match cached {
799 Some(signer) => signer,
800 None => tx
801 .recover_signer_unchecked()
802 .map_err(|_| EthApiError::InvalidTransactionSignature)?,
803 };
804 Ok(Recovered::new_unchecked(tx, signer))
805 }
806
807 #[expect(clippy::complexity)]
813 fn transaction_by_hash(
814 &self,
815 hash: B256,
816 ) -> impl Future<
817 Output = Result<Option<TransactionSource<ProviderTx<Self::Provider>>>, Self::Error>,
818 > + Send {
819 async move {
820 if let Some(cached) = self.cache().get_transaction_by_hash(hash).await &&
822 let Some(source) = cached.to_transaction_source()
823 {
824 return Ok(Some(source));
825 }
826
827 if let Some((tx, meta)) = self
829 .spawn_blocking_io(move |this| {
830 this.provider()
831 .transaction_by_hash_with_meta(hash)
832 .map_err(Self::Error::from_eth_err)
833 })
834 .await?
835 {
836 let transaction =
840 self.recover_mined_transaction(tx).map_err(Self::Error::from_eth_err)?;
841
842 return Ok(Some(TransactionSource::Block {
843 transaction,
844 index: meta.index,
845 block_hash: meta.block_hash,
846 block_number: meta.block_number,
847 block_timestamp: meta.timestamp,
848 base_fee: meta.base_fee,
849 }));
850 }
851
852 if let Some(tx) = self.pool().get(&hash).map(|tx| tx.transaction.clone_into_consensus())
854 {
855 return Ok(Some(TransactionSource::Pool(tx.into())));
856 }
857
858 Ok(None)
859 }
860 }
861
862 #[expect(clippy::type_complexity)]
866 fn transaction_by_hash_at(
867 &self,
868 transaction_hash: B256,
869 ) -> impl Future<
870 Output = Result<
871 Option<(TransactionSource<ProviderTx<Self::Provider>>, BlockId)>,
872 Self::Error,
873 >,
874 > + Send {
875 async move {
876 Ok(self.transaction_by_hash(transaction_hash).await?.map(|tx| match tx {
877 tx @ TransactionSource::Pool(_) => (tx, BlockId::pending()),
878 tx @ TransactionSource::Block { block_hash, .. } => (tx, BlockId::hash(block_hash)),
879 }))
880 }
881 }
882
883 #[expect(clippy::type_complexity)]
885 fn transaction_and_block(
886 &self,
887 hash: B256,
888 ) -> impl Future<
889 Output = Result<
890 Option<(
891 TransactionSource<ProviderTx<Self::Provider>>,
892 Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>,
893 )>,
894 Self::Error,
895 >,
896 > + Send {
897 async move {
898 Ok(self
899 .transaction_and_block_and_maybe_bal(hash)
900 .await?
901 .map(|(transaction, block, _)| (transaction, block)))
902 }
903 }
904
905 #[expect(clippy::type_complexity)]
910 fn transaction_and_block_and_maybe_bal(
911 &self,
912 hash: B256,
913 ) -> impl Future<
914 Output = Result<
915 Option<(
916 TransactionSource<ProviderTx<Self::Provider>>,
917 Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>,
918 Option<Arc<DecodedBal<Arc<EvmBal>>>>,
919 )>,
920 Self::Error,
921 >,
922 > + Send {
923 async move {
924 let (transaction, at) = match self.transaction_by_hash_at(hash).await? {
925 None => return Ok(None),
926 Some(res) => res,
927 };
928
929 let block_hash = match at {
931 BlockId::Hash(hash) => hash.block_hash,
932 _ => return Ok(None),
933 };
934 let block_and_bal = self
935 .cache()
936 .get_recovered_block_and_maybe_bal(block_hash)
937 .await
938 .map_err(Self::Error::from_eth_err)?;
939 Ok(block_and_bal.map(|(block, bal)| (transaction, block, bal)))
940 }
941 }
942}
943
944const fn ensure_no_conflicting_fee_fields(request: &TransactionRequest) -> EthResult<()> {
946 if request.gas_price.is_some() && request.has_eip1559_fields() {
947 return Err(EthApiError::ConflictingFeeFieldsInRequest)
948 }
949 Ok(())
950}