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::{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, decode_raw_transaction},
29 EthApiError::{self, TransactionConfirmationTimeout},
30 EthResult, 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
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 + 'static,
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 .map_err(Self::Error::from)?
168 {
169 return Ok(receipt);
170 }
171 }
172 Err(Self::Error::from_eth_err(TransactionConfirmationTimeout {
173 hash,
174 duration: timeout_duration,
175 }))
176 })
177 .await
178 .unwrap_or_else(|_elapsed| {
179 Err(Self::Error::from_eth_err(TransactionConfirmationTimeout {
180 hash,
181 duration: timeout_duration,
182 }))
183 })
184 }
185 }
186
187 #[expect(clippy::complexity)]
193 fn transaction_by_hash(
194 &self,
195 hash: B256,
196 ) -> impl Future<
197 Output = Result<Option<TransactionSource<ProviderTx<Self::Provider>>>, Self::Error>,
198 > + Send {
199 LoadTransaction::transaction_by_hash(self, hash)
200 }
201
202 fn pending_transactions(&self) -> Result<Vec<RpcTransaction<Self::NetworkTypes>>, Self::Error> {
204 self.pool()
205 .pending_transactions()
206 .into_iter()
207 .map(|tx| self.converter().fill_pending(tx.transaction.clone_into_consensus()))
208 .collect::<Result<Vec<_>, _>>()
209 .map_err(Self::Error::from)
210 }
211
212 #[expect(clippy::type_complexity)]
216 fn transactions_by_block(
217 &self,
218 block: B256,
219 ) -> impl Future<Output = Result<Option<Vec<ProviderTx<Self::Provider>>>, Self::Error>> + Send
220 {
221 async move {
222 self.cache()
223 .get_recovered_block(block)
224 .await
225 .map(|b| b.map(|b| b.body().transactions().to_vec()))
226 .map_err(Self::Error::from_eth_err)
227 }
228 }
229
230 fn raw_transaction_by_hash(
238 &self,
239 hash: B256,
240 ) -> impl Future<Output = Result<Option<Bytes>, Self::Error>> + Send {
241 async move {
242 if let Some(tx) =
244 self.pool().get_pooled_transaction_element(hash).map(|tx| tx.encoded_2718().into())
245 {
246 return Ok(Some(tx))
247 }
248
249 self.spawn_blocking_io(move |ref this| {
250 Ok(this
251 .provider()
252 .transaction_by_hash(hash)
253 .map_err(Self::Error::from_eth_err)?
254 .map(|tx| tx.encoded_2718().into()))
255 })
256 .await
257 }
258 }
259
260 #[expect(clippy::type_complexity)]
262 fn historical_transaction_by_hash_at(
263 &self,
264 hash: B256,
265 ) -> impl Future<
266 Output = Result<Option<(TransactionSource<ProviderTx<Self::Provider>>, B256)>, Self::Error>,
267 > + Send {
268 async move {
269 match self.transaction_by_hash_at(hash).await? {
270 None => Ok(None),
271 Some((tx, at)) => Ok(at.as_block_hash().map(|hash| (tx, hash))),
272 }
273 }
274 }
275
276 fn transaction_receipt(
281 &self,
282 hash: B256,
283 ) -> impl Future<Output = Result<Option<RpcReceipt<Self::NetworkTypes>>, Self::Error>> + Send
284 where
285 Self: LoadReceipt + 'static,
286 {
287 async move {
288 match self.load_transaction_and_receipt(hash).await? {
289 Some((tx, meta, receipt, all_receipts, block)) => self
290 .build_transaction_receipt(tx, meta, receipt, all_receipts, block)
291 .await
292 .map(Some),
293 None => Ok(None),
294 }
295 }
296 }
297
298 #[expect(clippy::complexity)]
302 fn load_transaction_and_receipt(
303 &self,
304 hash: TxHash,
305 ) -> impl Future<
306 Output = Result<
307 Option<(
308 Recovered<ProviderTx<Self::Provider>>,
309 TransactionMeta,
310 ProviderReceipt<Self::Provider>,
311 Option<Arc<Vec<ProviderReceipt<Self::Provider>>>>,
312 Option<Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>>,
313 )>,
314 Self::Error,
315 >,
316 > + Send
317 where
318 Self: 'static,
319 {
320 async move {
321 if let Some(cached) = self.cache().get_transaction_by_hash(hash).await &&
322 let Some(tx) = cached.recovered_transaction().map(|tx| tx.cloned())
323 {
324 let meta = cached.transaction_meta(hash);
325
326 if let Some(all_receipts) = cached.receipts.clone() &&
328 let Some(receipt) = all_receipts.get(cached.tx_index).cloned()
329 {
330 return Ok(Some((tx, meta, receipt, Some(all_receipts), Some(cached.block))));
331 }
332
333 if let Some(receipts) = self
337 .cache()
338 .get_receipts(cached.block.hash())
339 .await
340 .map_err(Self::Error::from_eth_err)? &&
341 let Some(receipt) = receipts.get(cached.tx_index).cloned()
342 {
343 return Ok(Some((tx, meta, receipt, Some(receipts), Some(cached.block))));
344 }
345 }
346
347 self.spawn_blocking_io(move |this| {
349 let provider = this.provider();
350 let Some((tx, meta)) = provider
351 .transaction_by_hash_with_meta(hash)
352 .map_err(Self::Error::from_eth_err)?
353 else {
354 return Ok(None);
355 };
356
357 let tx = tx.try_into_recovered_unchecked().map_err(Self::Error::from_eth_err)?;
358
359 let receipt = provider.receipt_by_hash(hash).map_err(Self::Error::from_eth_err)?;
360
361 Ok(receipt.map(|receipt| (tx, meta, receipt, None, None)))
362 })
363 .await
364 }
365 }
366
367 fn transaction_by_block_and_tx_index(
371 &self,
372 block_id: BlockId,
373 index: usize,
374 ) -> impl Future<Output = Result<Option<RpcTransaction<Self::NetworkTypes>>, Self::Error>> + Send
375 where
376 Self: LoadBlock,
377 {
378 async move {
379 if let Some(block) = self.recovered_block(block_id).await? {
380 let block_hash = block.hash();
381 let block_number = block.number();
382 let block_timestamp = block.timestamp();
383 let base_fee_per_gas = block.base_fee_per_gas();
384 if let Some((signer, tx)) = block.transactions_with_sender().nth(index) {
385 let tx_info = TransactionInfo {
386 hash: Some(*tx.tx_hash()),
387 block_hash: Some(block_hash),
388 block_number: Some(block_number),
389 block_timestamp: Some(block_timestamp),
390 base_fee: base_fee_per_gas,
391 index: Some(index as u64),
392 };
393
394 return Ok(Some(
395 self.converter().fill(tx.clone().with_signer(*signer), tx_info)?,
396 ))
397 }
398 }
399
400 Ok(None)
401 }
402 }
403
404 fn get_transaction_by_sender_and_nonce(
406 &self,
407 sender: Address,
408 nonce: u64,
409 include_pending: bool,
410 ) -> impl Future<Output = Result<Option<RpcTransaction<Self::NetworkTypes>>, Self::Error>> + Send
411 where
412 Self: LoadBlock + LoadState,
413 {
414 async move {
415 if include_pending &&
417 let Some(tx) =
418 RpcNodeCore::pool(self).get_transaction_by_sender_and_nonce(sender, nonce)
419 {
420 let transaction = tx.transaction.clone_into_consensus();
421 return Ok(Some(self.converter().fill_pending(transaction)?));
422 }
423
424 let highest = self.transaction_count(sender, None).await?.saturating_to::<u64>();
428
429 if nonce >= highest {
432 return Ok(None);
433 }
434
435 let high = self.provider().best_block_number().map_err(Self::Error::from_eth_err)?;
436
437 let num = binary_search::<_, _, Self::Error>(1, high, |mid| async move {
440 let mid_nonce =
441 self.transaction_count(sender, Some(mid.into())).await?.saturating_to::<u64>();
442
443 Ok(mid_nonce > nonce)
444 })
445 .await?;
446
447 let block_id = num.into();
448 let Some(block) = self.recovered_block(block_id).await? else {
449 return Err(EthApiError::HeaderNotFound(block_id).into())
450 };
451
452 let block_hash = block.hash();
453 let block_number = block.number();
454 let block_timestamp = block.timestamp();
455 let base_fee_per_gas = block.base_fee_per_gas();
456
457 block
460 .transactions_with_sender()
461 .enumerate()
462 .find(|(_, (signer, tx))| **signer == sender && (*tx).nonce() == nonce)
463 .map(|(index, (signer, tx))| {
464 let tx_info = TransactionInfo {
465 hash: Some(*tx.tx_hash()),
466 block_hash: Some(block_hash),
467 block_number: Some(block_number),
468 block_timestamp: Some(block_timestamp),
469 base_fee: base_fee_per_gas,
470 index: Some(index as u64),
471 };
472 Ok(self.converter().fill(tx.clone().with_signer(*signer), tx_info)?)
473 })
474 .transpose()
475 }
476 }
477
478 fn raw_transaction_by_block_and_tx_index(
482 &self,
483 block_id: BlockId,
484 index: usize,
485 ) -> impl Future<Output = Result<Option<Bytes>, Self::Error>> + Send
486 where
487 Self: LoadBlock,
488 {
489 async move {
490 if let Some(block) = self.recovered_block(block_id).await? &&
491 let Some(tx) = block.body().transactions().get(index)
492 {
493 return Ok(Some(tx.encoded_2718().into()))
494 }
495
496 Ok(None)
497 }
498 }
499
500 fn send_transaction_request(
503 &self,
504 mut request: RpcTxReq<Self::NetworkTypes>,
505 ) -> impl Future<Output = Result<B256, Self::Error>> + Send
506 where
507 Self: EthApiSpec + LoadBlock + EstimateCall + LoadFee,
508 {
509 async move {
510 let from = match request.as_ref().from() {
511 Some(from) => from,
512 None => return Err(SignError::NoAccount.into_eth_err()),
513 };
514
515 if self.find_signer(&from).is_err() {
516 return Err(SignError::NoAccount.into_eth_err())
517 }
518
519 if request.as_ref().nonce().is_none() {
521 let nonce = self.next_available_nonce_for(&request).await?;
522 request.as_mut().set_nonce(nonce);
523 }
524
525 let chain_id = self.chain_id();
526 if let Some(request_chain_id) = request.as_ref().chain_id() &&
530 request_chain_id != chain_id.to::<u64>()
531 {
532 return Err(EthApiError::InvalidParams(format!(
533 "chainId does not match node's (have={request_chain_id}, want={})",
534 chain_id.to::<u64>()
535 ))
536 .into())
537 }
538 request.as_mut().set_chain_id(chain_id.to());
539
540 if request.as_ref().gas_price().is_none() {
542 let tip = if let Some(tip) = request.as_ref().max_priority_fee_per_gas() {
543 tip
544 } else {
545 let tip = self.suggested_priority_fee().await?.to::<u128>();
546 request.as_mut().set_max_priority_fee_per_gas(tip);
547 tip
548 };
549 if request.as_ref().max_fee_per_gas().is_none() {
550 let header =
551 self.provider().latest_header().map_err(Self::Error::from_eth_err)?;
552 let base_fee = header.and_then(|h| h.base_fee_per_gas()).unwrap_or_default();
553 request.as_mut().set_max_fee_per_gas(base_fee as u128 * 2 + tip);
554 }
555 }
556
557 if request.as_ref().gas_limit().is_none() {
558 let estimated_gas = self
559 .estimate_gas_at(request.clone(), BlockId::pending(), EvmOverrides::default())
560 .await?;
561 request.as_mut().set_gas_limit(estimated_gas.to());
562 }
563
564 let transaction = self.sign_request(&from, request).await?.with_signer(from);
565
566 let pool_transaction =
567 <<Self as RpcNodeCore>::Pool as TransactionPool>::Transaction::try_from_consensus(
568 transaction,
569 )
570 .map_err(|e| {
571 Self::Error::from_eth_err(TransactionConversionError::Other(e.to_string()))
572 })?;
573
574 let AddedTransactionOutcome { hash, .. } = self
576 .pool()
577 .add_transaction(TransactionOrigin::Local, pool_transaction)
578 .await
579 .map_err(Self::Error::from_eth_err)?;
580
581 Ok(hash)
582 }
583 }
584
585 fn fill_transaction(
587 &self,
588 mut request: RpcTxReq<Self::NetworkTypes>,
589 ) -> impl Future<Output = Result<FillTransaction<TxTy<Self::Primitives>>, Self::Error>> + Send
590 where
591 Self: EthApiSpec + LoadBlock + EstimateCall + LoadFee,
592 {
593 async move {
594 if request.as_ref().value().is_none() {
595 request.as_mut().set_value(U256::ZERO);
596 }
597
598 if request.as_ref().nonce().is_none() {
599 let nonce = self.next_available_nonce_for(&request).await?;
600 request.as_mut().set_nonce(nonce);
601 }
602
603 let chain_id = self.chain_id();
604 if let Some(request_chain_id) = request.as_ref().chain_id() &&
608 request_chain_id != chain_id.to::<u64>()
609 {
610 return Err(EthApiError::InvalidParams(format!(
611 "chainId does not match node's (have={request_chain_id}, want={})",
612 chain_id.to::<u64>()
613 ))
614 .into())
615 }
616 request.as_mut().set_chain_id(chain_id.to());
617
618 if request.as_ref().has_eip4844_fields() &&
619 request.as_ref().max_fee_per_blob_gas().is_none()
620 {
621 let blob_fee = self.blob_base_fee().await?;
622 request.as_mut().set_max_fee_per_blob_gas(blob_fee.to());
623 }
624
625 if request.as_ref().sidecar.is_some() &&
628 request.as_ref().blob_versioned_hashes.is_none()
629 {
630 request.as_mut().populate_blob_hashes();
631 }
632
633 if request.as_ref().gas_limit().is_none() {
634 let estimated_gas = self
635 .estimate_gas_at(request.clone(), BlockId::pending(), EvmOverrides::default())
636 .await?;
637 request.as_mut().set_gas_limit(estimated_gas.to());
638 }
639
640 if request.as_ref().gas_price().is_none() {
641 let tip = if let Some(tip) = request.as_ref().max_priority_fee_per_gas() {
642 tip
643 } else {
644 let tip = self.suggested_priority_fee().await?.to::<u128>();
645 request.as_mut().set_max_priority_fee_per_gas(tip);
646 tip
647 };
648 if request.as_ref().max_fee_per_gas().is_none() {
649 let header =
650 self.provider().latest_header().map_err(Self::Error::from_eth_err)?;
651 let base_fee = header.and_then(|h| h.base_fee_per_gas()).unwrap_or_default();
652 request.as_mut().set_max_fee_per_gas(base_fee as u128 * 2 + tip);
659 }
660 }
661
662 let tx = self.converter().build_simulate_v1_transaction(request)?;
663
664 let raw = tx.encoded_2718().into();
665
666 Ok(FillTransaction { raw, tx })
667 }
668 }
669
670 fn sign_request(
672 &self,
673 from: &Address,
674 txn: RpcTxReq<Self::NetworkTypes>,
675 ) -> impl Future<Output = Result<ProviderTx<Self::Provider>, Self::Error>> + Send {
676 async move {
677 self.find_signer(from)?
678 .sign_transaction(txn, from)
679 .await
680 .map_err(Self::Error::from_eth_err)
681 }
682 }
683
684 fn sign(
686 &self,
687 account: Address,
688 message: Bytes,
689 ) -> impl Future<Output = Result<Bytes, Self::Error>> + Send {
690 async move {
691 Ok(self
692 .find_signer(&account)?
693 .sign(account, &message)
694 .await
695 .map_err(Self::Error::from_eth_err)?
696 .as_bytes()
697 .into())
698 }
699 }
700
701 fn sign_transaction(
704 &self,
705 request: RpcTxReq<Self::NetworkTypes>,
706 ) -> impl Future<Output = Result<Bytes, Self::Error>> + Send {
707 async move {
708 let from = match request.as_ref().from() {
709 Some(from) => from,
710 None => return Err(SignError::NoAccount.into_eth_err()),
711 };
712
713 Ok(self.sign_request(&from, request).await?.encoded_2718().into())
714 }
715 }
716
717 fn sign_typed_data(&self, data: &TypedData, account: Address) -> Result<Bytes, Self::Error> {
719 Ok(self
720 .find_signer(&account)?
721 .sign_typed_data(account, data)
722 .map_err(Self::Error::from_eth_err)?
723 .as_bytes()
724 .into())
725 }
726
727 #[expect(clippy::type_complexity)]
729 fn find_signer(
730 &self,
731 account: &Address,
732 ) -> Result<
733 Box<dyn EthSigner<ProviderTx<Self::Provider>, RpcTxReq<Self::NetworkTypes>> + 'static>,
734 Self::Error,
735 > {
736 self.signers()
737 .read()
738 .iter()
739 .find(|signer| signer.is_signer_for(account))
740 .map(|signer| dyn_clone::clone_box(&**signer))
741 .ok_or_else(|| SignError::NoAccount.into_eth_err())
742 }
743}
744
745pub trait LoadTransaction: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt {
750 fn recover_raw_transaction<T: SignedTransaction>(&self, tx: &[u8]) -> EthResult<Recovered<T>> {
752 let transaction = decode_raw_transaction::<T>(tx)?;
753 let signer = match self.eth_api_settings().sender_recovery_cache.as_ref() {
754 Some(cache) => cache.recover(&transaction),
755 None => transaction.try_recover(),
756 }
757 .map_err(|_| EthApiError::InvalidTransactionSignature)?;
758 Ok(Recovered::new_unchecked(transaction, signer))
759 }
760
761 fn recover_raw_transactions<T: SignedTransaction>(
765 &self,
766 txs: impl IntoIterator<Item = impl AsRef<[u8]>>,
767 ) -> EthResult<Vec<Recovered<T>>> {
768 txs.into_iter().map(|tx| self.recover_raw_transaction(tx.as_ref())).collect()
769 }
770
771 #[expect(clippy::complexity)]
777 fn transaction_by_hash(
778 &self,
779 hash: B256,
780 ) -> impl Future<
781 Output = Result<Option<TransactionSource<ProviderTx<Self::Provider>>>, Self::Error>,
782 > + Send {
783 async move {
784 if let Some(cached) = self.cache().get_transaction_by_hash(hash).await &&
786 let Some(source) = cached.to_transaction_source()
787 {
788 return Ok(Some(source));
789 }
790
791 if let Some((tx, meta)) = self
793 .spawn_blocking_io(move |this| {
794 this.provider()
795 .transaction_by_hash_with_meta(hash)
796 .map_err(Self::Error::from_eth_err)
797 })
798 .await?
799 {
800 let transaction = tx
804 .try_into_recovered_unchecked()
805 .map_err(|_| EthApiError::InvalidTransactionSignature)?;
806
807 return Ok(Some(TransactionSource::Block {
808 transaction,
809 index: meta.index,
810 block_hash: meta.block_hash,
811 block_number: meta.block_number,
812 block_timestamp: meta.timestamp,
813 base_fee: meta.base_fee,
814 }));
815 }
816
817 if let Some(tx) = self.pool().get(&hash).map(|tx| tx.transaction.clone_into_consensus())
819 {
820 return Ok(Some(TransactionSource::Pool(tx.into())));
821 }
822
823 Ok(None)
824 }
825 }
826
827 #[expect(clippy::type_complexity)]
831 fn transaction_by_hash_at(
832 &self,
833 transaction_hash: B256,
834 ) -> impl Future<
835 Output = Result<
836 Option<(TransactionSource<ProviderTx<Self::Provider>>, BlockId)>,
837 Self::Error,
838 >,
839 > + Send {
840 async move {
841 Ok(self.transaction_by_hash(transaction_hash).await?.map(|tx| match tx {
842 tx @ TransactionSource::Pool(_) => (tx, BlockId::pending()),
843 tx @ TransactionSource::Block { block_hash, .. } => {
844 (tx, BlockId::Hash(block_hash.into()))
845 }
846 }))
847 }
848 }
849
850 #[expect(clippy::type_complexity)]
852 fn transaction_and_block(
853 &self,
854 hash: B256,
855 ) -> impl Future<
856 Output = Result<
857 Option<(
858 TransactionSource<ProviderTx<Self::Provider>>,
859 Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>,
860 )>,
861 Self::Error,
862 >,
863 > + Send {
864 async move {
865 Ok(self
866 .transaction_and_block_and_maybe_bal(hash)
867 .await?
868 .map(|(transaction, block, _)| (transaction, block)))
869 }
870 }
871
872 #[expect(clippy::type_complexity)]
877 fn transaction_and_block_and_maybe_bal(
878 &self,
879 hash: B256,
880 ) -> impl Future<
881 Output = Result<
882 Option<(
883 TransactionSource<ProviderTx<Self::Provider>>,
884 Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>,
885 Option<Arc<DecodedBal<Arc<RevmBal>>>>,
886 )>,
887 Self::Error,
888 >,
889 > + Send {
890 async move {
891 let (transaction, at) = match self.transaction_by_hash_at(hash).await? {
892 None => return Ok(None),
893 Some(res) => res,
894 };
895
896 let block_hash = match at {
898 BlockId::Hash(hash) => hash.block_hash,
899 _ => return Ok(None),
900 };
901 let block_and_bal = self
902 .cache()
903 .get_recovered_block_and_maybe_bal(block_hash)
904 .await
905 .map_err(Self::Error::from_eth_err)?;
906 Ok(block_and_bal.map(|(block, bal)| (transaction, block, bal)))
907 }
908 }
909}