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_eips::{eip2718::Encodable2718, BlockId};
16use alloy_network::{TransactionBuilder, TransactionBuilder4844};
17use alloy_primitives::{Address, Bytes, TxHash, B256, U256};
18use alloy_rpc_types_eth::{state::EvmOverrides, TransactionInfo};
19use futures::{Future, StreamExt};
20use reth_chain_state::CanonStateSubscriptions;
21use reth_primitives_traits::{
22 BlockBody, Recovered, RecoveredBlock, SignedTransaction, TxTy, WithEncoded,
23};
24use reth_rpc_convert::{transaction::RpcConvert, RpcTxReq, TransactionConversionError};
25use reth_rpc_eth_types::{
26 block::convert_transaction_receipt,
27 utils::binary_search,
28 EthApiError::{self, TransactionConfirmationTimeout},
29 FillTransaction, SignError, TransactionSource,
30};
31use reth_storage_api::{
32 BlockNumReader, BlockReaderIdExt, ProviderBlock, ProviderReceipt, ProviderTx, ReceiptProvider,
33 TransactionsProvider,
34};
35use reth_transaction_pool::{
36 AddedTransactionOutcome, PoolPooledTx, PoolTransaction, PoolTx, TransactionOrigin,
37 TransactionPool,
38};
39use std::{sync::Arc, time::Duration};
40
41pub trait EthTransactions: LoadTransaction<Provider: BlockReaderIdExt> {
64 fn signers(&self) -> &SignersForRpc<Self::Provider, Self::NetworkTypes>;
68
69 fn accounts(&self) -> Vec<Address> {
71 self.signers().read().iter().flat_map(|s| s.accounts()).collect()
72 }
73
74 fn send_raw_transaction_sync_timeout(&self) -> Duration;
76
77 fn send_raw_transaction(
81 &self,
82 tx: Bytes,
83 ) -> impl Future<Output = Result<B256, Self::Error>> + Send {
84 async move {
85 let pool_transaction =
86 <PoolTx<Self::Pool> as PoolTransaction>::recover_raw_transaction(&tx)
87 .map_err(Self::Error::from_eth_err)?;
88 self.send_pool_transaction(
89 TransactionOrigin::Local,
90 WithEncoded::new(tx, pool_transaction),
91 )
92 .await
93 }
94 }
95
96 fn send_transaction(
98 &self,
99 origin: TransactionOrigin,
100 tx: WithEncoded<Recovered<PoolPooledTx<Self::Pool>>>,
101 ) -> impl Future<Output = Result<B256, Self::Error>> + Send {
102 async move {
103 let (encoded, recovered) = tx.split();
104 let pool_transaction =
105 <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
106
107 self.send_pool_transaction(origin, WithEncoded::new(encoded, pool_transaction)).await
108 }
109 }
110
111 fn send_pool_transaction(
113 &self,
114 origin: TransactionOrigin,
115 tx: WithEncoded<PoolTx<Self::Pool>>,
116 ) -> impl Future<Output = Result<B256, Self::Error>> + Send;
117
118 fn send_raw_transaction_sync(
122 &self,
123 tx: Bytes,
124 timeout_ms: Option<u64>,
125 ) -> impl Future<Output = Result<RpcReceipt<Self::NetworkTypes>, Self::Error>> + Send
126 where
127 Self: LoadReceipt + 'static,
128 {
129 let this = self.clone();
130 let configured_timeout = self.send_raw_transaction_sync_timeout();
131 let timeout_duration = timeout_ms
132 .filter(|timeout_ms| *timeout_ms > 0)
133 .map(Duration::from_millis)
134 .map(|timeout| timeout.min(configured_timeout))
135 .unwrap_or(configured_timeout);
136 async move {
137 let mut stream = this.provider().canonical_state_stream();
138 let hash = EthTransactions::send_raw_transaction(&this, tx).await?;
139 tokio::time::timeout(timeout_duration, async {
140 while let Some(notification) = stream.next().await {
141 let chain = notification.committed();
142 if let Some((block, tx, receipt, all_receipts)) =
143 chain.find_transaction_and_receipt_by_hash(hash) &&
144 let Some(receipt) = convert_transaction_receipt(
145 block,
146 all_receipts,
147 tx,
148 receipt,
149 this.converter(),
150 )
151 .transpose()
152 .map_err(Self::Error::from)?
153 {
154 return Ok(receipt);
155 }
156 }
157 Err(Self::Error::from_eth_err(TransactionConfirmationTimeout {
158 hash,
159 duration: timeout_duration,
160 }))
161 })
162 .await
163 .unwrap_or_else(|_elapsed| {
164 Err(Self::Error::from_eth_err(TransactionConfirmationTimeout {
165 hash,
166 duration: timeout_duration,
167 }))
168 })
169 }
170 }
171
172 #[expect(clippy::complexity)]
178 fn transaction_by_hash(
179 &self,
180 hash: B256,
181 ) -> impl Future<
182 Output = Result<Option<TransactionSource<ProviderTx<Self::Provider>>>, Self::Error>,
183 > + Send {
184 LoadTransaction::transaction_by_hash(self, hash)
185 }
186
187 fn pending_transactions(&self) -> Result<Vec<RpcTransaction<Self::NetworkTypes>>, Self::Error> {
189 self.pool()
190 .pending_transactions()
191 .into_iter()
192 .map(|tx| self.converter().fill_pending(tx.transaction.clone_into_consensus()))
193 .collect::<Result<Vec<_>, _>>()
194 .map_err(Self::Error::from)
195 }
196
197 #[expect(clippy::type_complexity)]
201 fn transactions_by_block(
202 &self,
203 block: B256,
204 ) -> impl Future<Output = Result<Option<Vec<ProviderTx<Self::Provider>>>, Self::Error>> + Send
205 {
206 async move {
207 self.cache()
208 .get_recovered_block(block)
209 .await
210 .map(|b| b.map(|b| b.body().transactions().to_vec()))
211 .map_err(Self::Error::from_eth_err)
212 }
213 }
214
215 fn raw_transaction_by_hash(
223 &self,
224 hash: B256,
225 ) -> impl Future<Output = Result<Option<Bytes>, Self::Error>> + Send {
226 async move {
227 if let Some(tx) =
229 self.pool().get_pooled_transaction_element(hash).map(|tx| tx.encoded_2718().into())
230 {
231 return Ok(Some(tx))
232 }
233
234 self.spawn_blocking_io(move |ref this| {
235 Ok(this
236 .provider()
237 .transaction_by_hash(hash)
238 .map_err(Self::Error::from_eth_err)?
239 .map(|tx| tx.encoded_2718().into()))
240 })
241 .await
242 }
243 }
244
245 #[expect(clippy::type_complexity)]
247 fn historical_transaction_by_hash_at(
248 &self,
249 hash: B256,
250 ) -> impl Future<
251 Output = Result<Option<(TransactionSource<ProviderTx<Self::Provider>>, B256)>, Self::Error>,
252 > + Send {
253 async move {
254 match self.transaction_by_hash_at(hash).await? {
255 None => Ok(None),
256 Some((tx, at)) => Ok(at.as_block_hash().map(|hash| (tx, hash))),
257 }
258 }
259 }
260
261 fn transaction_receipt(
266 &self,
267 hash: B256,
268 ) -> impl Future<Output = Result<Option<RpcReceipt<Self::NetworkTypes>>, Self::Error>> + Send
269 where
270 Self: LoadReceipt + 'static,
271 {
272 async move {
273 match self.load_transaction_and_receipt(hash).await? {
274 Some((tx, meta, receipt, all_receipts)) => {
275 self.build_transaction_receipt(tx, meta, receipt, all_receipts).await.map(Some)
276 }
277 None => Ok(None),
278 }
279 }
280 }
281
282 #[expect(clippy::complexity)]
286 fn load_transaction_and_receipt(
287 &self,
288 hash: TxHash,
289 ) -> impl Future<
290 Output = Result<
291 Option<(
292 Recovered<ProviderTx<Self::Provider>>,
293 TransactionMeta,
294 ProviderReceipt<Self::Provider>,
295 Option<Arc<Vec<ProviderReceipt<Self::Provider>>>>,
296 )>,
297 Self::Error,
298 >,
299 > + Send
300 where
301 Self: 'static,
302 {
303 async move {
304 if let Some(cached) = self.cache().get_transaction_by_hash(hash).await &&
305 let Some(tx) = cached.recovered_transaction().map(|tx| tx.cloned())
306 {
307 let meta = cached.transaction_meta(hash);
308
309 if let Some(all_receipts) = cached.receipts.clone() &&
311 let Some(receipt) = all_receipts.get(cached.tx_index).cloned()
312 {
313 return Ok(Some((tx, meta, receipt, Some(all_receipts))));
314 }
315
316 if let Some(receipts) = self
320 .cache()
321 .get_receipts(cached.block.hash())
322 .await
323 .map_err(Self::Error::from_eth_err)? &&
324 let Some(receipt) = receipts.get(cached.tx_index).cloned()
325 {
326 return Ok(Some((tx, meta, receipt, Some(receipts))));
327 }
328 }
329
330 self.spawn_blocking_io(move |this| {
332 let provider = this.provider();
333 let Some((tx, meta)) = provider
334 .transaction_by_hash_with_meta(hash)
335 .map_err(Self::Error::from_eth_err)?
336 else {
337 return Ok(None);
338 };
339
340 let tx = tx.try_into_recovered_unchecked().map_err(Self::Error::from_eth_err)?;
341
342 let receipt = provider.receipt_by_hash(hash).map_err(Self::Error::from_eth_err)?;
343
344 Ok(receipt.map(|receipt| (tx, meta, receipt, None)))
345 })
346 .await
347 }
348 }
349
350 fn transaction_by_block_and_tx_index(
354 &self,
355 block_id: BlockId,
356 index: usize,
357 ) -> impl Future<Output = Result<Option<RpcTransaction<Self::NetworkTypes>>, Self::Error>> + Send
358 where
359 Self: LoadBlock,
360 {
361 async move {
362 if let Some(block) = self.recovered_block(block_id).await? {
363 let block_hash = block.hash();
364 let block_number = block.number();
365 let block_timestamp = block.timestamp();
366 let base_fee_per_gas = block.base_fee_per_gas();
367 if let Some((signer, tx)) = block.transactions_with_sender().nth(index) {
368 let tx_info = TransactionInfo {
369 hash: Some(*tx.tx_hash()),
370 block_hash: Some(block_hash),
371 block_number: Some(block_number),
372 block_timestamp: Some(block_timestamp),
373 base_fee: base_fee_per_gas,
374 index: Some(index as u64),
375 };
376
377 return Ok(Some(
378 self.converter().fill(tx.clone().with_signer(*signer), tx_info)?,
379 ))
380 }
381 }
382
383 Ok(None)
384 }
385 }
386
387 fn get_transaction_by_sender_and_nonce(
389 &self,
390 sender: Address,
391 nonce: u64,
392 include_pending: bool,
393 ) -> impl Future<Output = Result<Option<RpcTransaction<Self::NetworkTypes>>, Self::Error>> + Send
394 where
395 Self: LoadBlock + LoadState,
396 {
397 async move {
398 if include_pending &&
400 let Some(tx) =
401 RpcNodeCore::pool(self).get_transaction_by_sender_and_nonce(sender, nonce)
402 {
403 let transaction = tx.transaction.clone_into_consensus();
404 return Ok(Some(self.converter().fill_pending(transaction)?));
405 }
406
407 let highest = self.transaction_count(sender, None).await?.saturating_to::<u64>();
411
412 if nonce >= highest {
415 return Ok(None);
416 }
417
418 let high = self.provider().best_block_number().map_err(Self::Error::from_eth_err)?;
419
420 let num = binary_search::<_, _, Self::Error>(1, high, |mid| async move {
423 let mid_nonce =
424 self.transaction_count(sender, Some(mid.into())).await?.saturating_to::<u64>();
425
426 Ok(mid_nonce > nonce)
427 })
428 .await?;
429
430 let block_id = num.into();
431 self.recovered_block(block_id)
432 .await?
433 .and_then(|block| {
434 let block_hash = block.hash();
435 let block_number = block.number();
436 let block_timestamp = block.timestamp();
437 let base_fee_per_gas = block.base_fee_per_gas();
438
439 block
440 .transactions_with_sender()
441 .enumerate()
442 .find(|(_, (signer, tx))| **signer == sender && (*tx).nonce() == nonce)
443 .map(|(index, (signer, tx))| {
444 let tx_info = TransactionInfo {
445 hash: Some(*tx.tx_hash()),
446 block_hash: Some(block_hash),
447 block_number: Some(block_number),
448 block_timestamp: Some(block_timestamp),
449 base_fee: base_fee_per_gas,
450 index: Some(index as u64),
451 };
452 Ok(self.converter().fill(tx.clone().with_signer(*signer), tx_info)?)
453 })
454 })
455 .ok_or(EthApiError::HeaderNotFound(block_id))?
456 .map(Some)
457 }
458 }
459
460 fn raw_transaction_by_block_and_tx_index(
464 &self,
465 block_id: BlockId,
466 index: usize,
467 ) -> impl Future<Output = Result<Option<Bytes>, Self::Error>> + Send
468 where
469 Self: LoadBlock,
470 {
471 async move {
472 if let Some(block) = self.recovered_block(block_id).await? &&
473 let Some(tx) = block.body().transactions().get(index)
474 {
475 return Ok(Some(tx.encoded_2718().into()))
476 }
477
478 Ok(None)
479 }
480 }
481
482 fn send_transaction_request(
485 &self,
486 mut request: RpcTxReq<Self::NetworkTypes>,
487 ) -> impl Future<Output = Result<B256, Self::Error>> + Send
488 where
489 Self: EthApiSpec + LoadBlock + EstimateCall,
490 {
491 async move {
492 let from = match request.as_ref().from() {
493 Some(from) => from,
494 None => return Err(SignError::NoAccount.into_eth_err()),
495 };
496
497 if self.find_signer(&from).is_err() {
498 return Err(SignError::NoAccount.into_eth_err())
499 }
500
501 if request.as_ref().nonce().is_none() {
503 let nonce = self.next_available_nonce_for(&request).await?;
504 request.as_mut().set_nonce(nonce);
505 }
506
507 let chain_id = self.chain_id();
508 request.as_mut().set_chain_id(chain_id.to());
509
510 let estimated_gas = self
511 .estimate_gas_at(request.clone(), BlockId::pending(), EvmOverrides::default())
512 .await?;
513 let gas_limit = estimated_gas;
514 request.as_mut().set_gas_limit(gas_limit.to());
515
516 let transaction = self.sign_request(&from, request).await?.with_signer(from);
517
518 let pool_transaction =
519 <<Self as RpcNodeCore>::Pool as TransactionPool>::Transaction::try_from_consensus(
520 transaction,
521 )
522 .map_err(|e| {
523 Self::Error::from_eth_err(TransactionConversionError::Other(e.to_string()))
524 })?;
525
526 let AddedTransactionOutcome { hash, .. } = self
528 .pool()
529 .add_transaction(TransactionOrigin::Local, pool_transaction)
530 .await
531 .map_err(Self::Error::from_eth_err)?;
532
533 Ok(hash)
534 }
535 }
536
537 fn fill_transaction(
539 &self,
540 mut request: RpcTxReq<Self::NetworkTypes>,
541 ) -> impl Future<Output = Result<FillTransaction<TxTy<Self::Primitives>>, Self::Error>> + Send
542 where
543 Self: EthApiSpec + LoadBlock + EstimateCall + LoadFee,
544 {
545 async move {
546 if request.as_ref().value().is_none() {
547 request.as_mut().set_value(U256::ZERO);
548 }
549
550 if request.as_ref().nonce().is_none() {
551 let nonce = self.next_available_nonce_for(&request).await?;
552 request.as_mut().set_nonce(nonce);
553 }
554
555 let chain_id = self.chain_id();
556 request.as_mut().set_chain_id(chain_id.to());
557
558 if request.as_ref().has_eip4844_fields() &&
559 request.as_ref().max_fee_per_blob_gas().is_none()
560 {
561 let blob_fee = self.blob_base_fee().await?;
562 request.as_mut().set_max_fee_per_blob_gas(blob_fee.to());
563 }
564
565 if request.as_ref().sidecar.is_some() &&
568 request.as_ref().blob_versioned_hashes.is_none()
569 {
570 request.as_mut().populate_blob_hashes();
571 }
572
573 if request.as_ref().gas_limit().is_none() {
574 let estimated_gas = self
575 .estimate_gas_at(request.clone(), BlockId::pending(), EvmOverrides::default())
576 .await?;
577 request.as_mut().set_gas_limit(estimated_gas.to());
578 }
579
580 if request.as_ref().gas_price().is_none() {
581 let tip = if let Some(tip) = request.as_ref().max_priority_fee_per_gas() {
582 tip
583 } else {
584 let tip = self.suggested_priority_fee().await?.to::<u128>();
585 request.as_mut().set_max_priority_fee_per_gas(tip);
586 tip
587 };
588 if request.as_ref().max_fee_per_gas().is_none() {
589 let header =
590 self.provider().latest_header().map_err(Self::Error::from_eth_err)?;
591 let base_fee = header.and_then(|h| h.base_fee_per_gas()).unwrap_or_default();
592 request.as_mut().set_max_fee_per_gas(base_fee as u128 * 2 + tip);
599 }
600 }
601
602 let tx = self.converter().build_simulate_v1_transaction(request)?;
603
604 let raw = tx.encoded_2718().into();
605
606 Ok(FillTransaction { raw, tx })
607 }
608 }
609
610 fn sign_request(
612 &self,
613 from: &Address,
614 txn: RpcTxReq<Self::NetworkTypes>,
615 ) -> impl Future<Output = Result<ProviderTx<Self::Provider>, Self::Error>> + Send {
616 async move {
617 self.find_signer(from)?
618 .sign_transaction(txn, from)
619 .await
620 .map_err(Self::Error::from_eth_err)
621 }
622 }
623
624 fn sign(
626 &self,
627 account: Address,
628 message: Bytes,
629 ) -> impl Future<Output = Result<Bytes, Self::Error>> + Send {
630 async move {
631 Ok(self
632 .find_signer(&account)?
633 .sign(account, &message)
634 .await
635 .map_err(Self::Error::from_eth_err)?
636 .as_bytes()
637 .into())
638 }
639 }
640
641 fn sign_transaction(
644 &self,
645 request: RpcTxReq<Self::NetworkTypes>,
646 ) -> impl Future<Output = Result<Bytes, Self::Error>> + Send {
647 async move {
648 let from = match request.as_ref().from() {
649 Some(from) => from,
650 None => return Err(SignError::NoAccount.into_eth_err()),
651 };
652
653 Ok(self.sign_request(&from, request).await?.encoded_2718().into())
654 }
655 }
656
657 fn sign_typed_data(&self, data: &TypedData, account: Address) -> Result<Bytes, Self::Error> {
659 Ok(self
660 .find_signer(&account)?
661 .sign_typed_data(account, data)
662 .map_err(Self::Error::from_eth_err)?
663 .as_bytes()
664 .into())
665 }
666
667 #[expect(clippy::type_complexity)]
669 fn find_signer(
670 &self,
671 account: &Address,
672 ) -> Result<
673 Box<dyn EthSigner<ProviderTx<Self::Provider>, RpcTxReq<Self::NetworkTypes>> + 'static>,
674 Self::Error,
675 > {
676 self.signers()
677 .read()
678 .iter()
679 .find(|signer| signer.is_signer_for(account))
680 .map(|signer| dyn_clone::clone_box(&**signer))
681 .ok_or_else(|| SignError::NoAccount.into_eth_err())
682 }
683}
684
685pub trait LoadTransaction: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt {
690 #[expect(clippy::complexity)]
696 fn transaction_by_hash(
697 &self,
698 hash: B256,
699 ) -> impl Future<
700 Output = Result<Option<TransactionSource<ProviderTx<Self::Provider>>>, Self::Error>,
701 > + Send {
702 async move {
703 if let Some(cached) = self.cache().get_transaction_by_hash(hash).await &&
705 let Some(source) = cached.to_transaction_source()
706 {
707 return Ok(Some(source));
708 }
709
710 if let Some((tx, meta)) = self
712 .spawn_blocking_io(move |this| {
713 this.provider()
714 .transaction_by_hash_with_meta(hash)
715 .map_err(Self::Error::from_eth_err)
716 })
717 .await?
718 {
719 let transaction = tx
723 .try_into_recovered_unchecked()
724 .map_err(|_| EthApiError::InvalidTransactionSignature)?;
725
726 return Ok(Some(TransactionSource::Block {
727 transaction,
728 index: meta.index,
729 block_hash: meta.block_hash,
730 block_number: meta.block_number,
731 block_timestamp: meta.timestamp,
732 base_fee: meta.base_fee,
733 }));
734 }
735
736 if let Some(tx) = self.pool().get(&hash).map(|tx| tx.transaction.clone_into_consensus())
738 {
739 return Ok(Some(TransactionSource::Pool(tx.into())));
740 }
741
742 Ok(None)
743 }
744 }
745
746 #[expect(clippy::type_complexity)]
750 fn transaction_by_hash_at(
751 &self,
752 transaction_hash: B256,
753 ) -> impl Future<
754 Output = Result<
755 Option<(TransactionSource<ProviderTx<Self::Provider>>, BlockId)>,
756 Self::Error,
757 >,
758 > + Send {
759 async move {
760 Ok(self.transaction_by_hash(transaction_hash).await?.map(|tx| match tx {
761 tx @ TransactionSource::Pool(_) => (tx, BlockId::pending()),
762 tx @ TransactionSource::Block { block_hash, .. } => {
763 (tx, BlockId::Hash(block_hash.into()))
764 }
765 }))
766 }
767 }
768
769 #[expect(clippy::type_complexity)]
771 fn transaction_and_block(
772 &self,
773 hash: B256,
774 ) -> impl Future<
775 Output = Result<
776 Option<(
777 TransactionSource<ProviderTx<Self::Provider>>,
778 Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>,
779 )>,
780 Self::Error,
781 >,
782 > + Send {
783 async move {
784 let (transaction, at) = match self.transaction_by_hash_at(hash).await? {
785 None => return Ok(None),
786 Some(res) => res,
787 };
788
789 let block_hash = match at {
791 BlockId::Hash(hash) => hash.block_hash,
792 _ => return Ok(None),
793 };
794 let block = self
795 .cache()
796 .get_recovered_block(block_hash)
797 .await
798 .map_err(Self::Error::from_eth_err)?;
799 Ok(block.map(|block| (transaction, block)))
800 }
801 }
802}