reth_rpc_eth_api/helpers/
transaction.rs1use super::{EthApiSpec, EthSigner, LoadBlock, LoadReceipt, LoadState, SpawnBlocking};
5use crate::{
6 helpers::estimate::EstimateCall, FromEthApiError, FullEthApiTypes, IntoEthApiError,
7 RpcNodeCore, RpcNodeCoreExt, RpcReceipt, RpcTransaction,
8};
9use alloy_consensus::{
10 transaction::{SignerRecoverable, TransactionMeta},
11 BlockHeader, Transaction,
12};
13use alloy_dyn_abi::TypedData;
14use alloy_eips::{eip2718::Encodable2718, BlockId};
15use alloy_network::TransactionBuilder;
16use alloy_primitives::{Address, Bytes, TxHash, B256};
17use alloy_rpc_types_eth::{transaction::TransactionRequest, BlockNumberOrTag, TransactionInfo};
18use futures::Future;
19use reth_node_api::BlockBody;
20use reth_primitives_traits::{RecoveredBlock, SignedTransaction};
21use reth_rpc_eth_types::{utils::binary_search, EthApiError, SignError, TransactionSource};
22use reth_rpc_types_compat::transaction::TransactionCompat;
23use reth_storage_api::{
24 BlockNumReader, BlockReaderIdExt, ProviderBlock, ProviderReceipt, ProviderTx, ReceiptProvider,
25 TransactionsProvider,
26};
27use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool};
28use std::sync::Arc;
29
30pub trait EthTransactions: LoadTransaction<Provider: BlockReaderIdExt> {
53 #[expect(clippy::type_complexity)]
57 fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner<ProviderTx<Self::Provider>>>>>;
58
59 fn send_raw_transaction(
63 &self,
64 tx: Bytes,
65 ) -> impl Future<Output = Result<B256, Self::Error>> + Send;
66
67 #[expect(clippy::complexity)]
73 fn transaction_by_hash(
74 &self,
75 hash: B256,
76 ) -> impl Future<
77 Output = Result<Option<TransactionSource<ProviderTx<Self::Provider>>>, Self::Error>,
78 > + Send {
79 LoadTransaction::transaction_by_hash(self, hash)
80 }
81
82 #[expect(clippy::type_complexity)]
86 fn transactions_by_block(
87 &self,
88 block: B256,
89 ) -> impl Future<Output = Result<Option<Vec<ProviderTx<Self::Provider>>>, Self::Error>> + Send
90 {
91 async move {
92 self.cache()
93 .get_recovered_block(block)
94 .await
95 .map(|b| b.map(|b| b.body().transactions().to_vec()))
96 .map_err(Self::Error::from_eth_err)
97 }
98 }
99
100 fn raw_transaction_by_hash(
108 &self,
109 hash: B256,
110 ) -> impl Future<Output = Result<Option<Bytes>, Self::Error>> + Send {
111 async move {
112 if let Some(tx) =
114 self.pool().get_pooled_transaction_element(hash).map(|tx| tx.encoded_2718().into())
115 {
116 return Ok(Some(tx))
117 }
118
119 self.spawn_blocking_io(move |ref this| {
120 Ok(this
121 .provider()
122 .transaction_by_hash(hash)
123 .map_err(Self::Error::from_eth_err)?
124 .map(|tx| tx.encoded_2718().into()))
125 })
126 .await
127 }
128 }
129
130 #[expect(clippy::type_complexity)]
132 fn historical_transaction_by_hash_at(
133 &self,
134 hash: B256,
135 ) -> impl Future<
136 Output = Result<Option<(TransactionSource<ProviderTx<Self::Provider>>, B256)>, Self::Error>,
137 > + Send {
138 async move {
139 match self.transaction_by_hash_at(hash).await? {
140 None => Ok(None),
141 Some((tx, at)) => Ok(at.as_block_hash().map(|hash| (tx, hash))),
142 }
143 }
144 }
145
146 fn transaction_receipt(
151 &self,
152 hash: B256,
153 ) -> impl Future<Output = Result<Option<RpcReceipt<Self::NetworkTypes>>, Self::Error>> + Send
154 where
155 Self: LoadReceipt + 'static,
156 {
157 async move {
158 match self.load_transaction_and_receipt(hash).await? {
159 Some((tx, meta, receipt)) => {
160 self.build_transaction_receipt(tx, meta, receipt).await.map(Some)
161 }
162 None => Ok(None),
163 }
164 }
165 }
166
167 #[expect(clippy::complexity)]
169 fn load_transaction_and_receipt(
170 &self,
171 hash: TxHash,
172 ) -> impl Future<
173 Output = Result<
174 Option<(ProviderTx<Self::Provider>, TransactionMeta, ProviderReceipt<Self::Provider>)>,
175 Self::Error,
176 >,
177 > + Send
178 where
179 Self: 'static,
180 {
181 let provider = self.provider().clone();
182 self.spawn_blocking_io(move |_| {
183 let (tx, meta) = match provider
184 .transaction_by_hash_with_meta(hash)
185 .map_err(Self::Error::from_eth_err)?
186 {
187 Some((tx, meta)) => (tx, meta),
188 None => return Ok(None),
189 };
190
191 let receipt = match provider.receipt_by_hash(hash).map_err(Self::Error::from_eth_err)? {
192 Some(recpt) => recpt,
193 None => return Ok(None),
194 };
195
196 Ok(Some((tx, meta, receipt)))
197 })
198 }
199
200 fn transaction_by_block_and_tx_index(
204 &self,
205 block_id: BlockId,
206 index: usize,
207 ) -> impl Future<Output = Result<Option<RpcTransaction<Self::NetworkTypes>>, Self::Error>> + Send
208 where
209 Self: LoadBlock,
210 {
211 async move {
212 if let Some(block) = self.recovered_block(block_id).await? {
213 let block_hash = block.hash();
214 let block_number = block.number();
215 let base_fee_per_gas = block.base_fee_per_gas();
216 if let Some((signer, tx)) = block.transactions_with_sender().nth(index) {
217 let tx_info = TransactionInfo {
218 hash: Some(*tx.tx_hash()),
219 block_hash: Some(block_hash),
220 block_number: Some(block_number),
221 base_fee: base_fee_per_gas,
222 index: Some(index as u64),
223 };
224
225 return Ok(Some(
226 self.tx_resp_builder().fill(tx.clone().with_signer(*signer), tx_info)?,
227 ))
228 }
229 }
230
231 Ok(None)
232 }
233 }
234
235 fn get_transaction_by_sender_and_nonce(
237 &self,
238 sender: Address,
239 nonce: u64,
240 include_pending: bool,
241 ) -> impl Future<Output = Result<Option<RpcTransaction<Self::NetworkTypes>>, Self::Error>> + Send
242 where
243 Self: LoadBlock + LoadState,
244 {
245 async move {
246 if include_pending {
248 if let Some(tx) =
249 RpcNodeCore::pool(self).get_transaction_by_sender_and_nonce(sender, nonce)
250 {
251 let transaction = tx.transaction.clone_into_consensus();
252 return Ok(Some(self.tx_resp_builder().fill_pending(transaction)?));
253 }
254 }
255
256 if !self.get_code(sender, None).await?.is_empty() {
258 return Ok(None);
259 }
260
261 let highest = self.transaction_count(sender, None).await?.saturating_to::<u64>();
262
263 if nonce >= highest {
266 return Ok(None);
267 }
268
269 let Ok(high) = self.provider().best_block_number() else {
270 return Err(EthApiError::HeaderNotFound(BlockNumberOrTag::Latest.into()).into());
271 };
272
273 let num = binary_search::<_, _, Self::Error>(1, high, |mid| async move {
276 let mid_nonce =
277 self.transaction_count(sender, Some(mid.into())).await?.saturating_to::<u64>();
278
279 Ok(mid_nonce > nonce)
280 })
281 .await?;
282
283 let block_id = num.into();
284 self.recovered_block(block_id)
285 .await?
286 .and_then(|block| {
287 let block_hash = block.hash();
288 let block_number = block.number();
289 let base_fee_per_gas = block.base_fee_per_gas();
290
291 block
292 .transactions_with_sender()
293 .enumerate()
294 .find(|(_, (signer, tx))| **signer == sender && (*tx).nonce() == nonce)
295 .map(|(index, (signer, tx))| {
296 let tx_info = TransactionInfo {
297 hash: Some(*tx.tx_hash()),
298 block_hash: Some(block_hash),
299 block_number: Some(block_number),
300 base_fee: base_fee_per_gas,
301 index: Some(index as u64),
302 };
303 self.tx_resp_builder().fill(tx.clone().with_signer(*signer), tx_info)
304 })
305 })
306 .ok_or(EthApiError::HeaderNotFound(block_id))?
307 .map(Some)
308 }
309 }
310
311 fn raw_transaction_by_block_and_tx_index(
315 &self,
316 block_id: BlockId,
317 index: usize,
318 ) -> impl Future<Output = Result<Option<Bytes>, Self::Error>> + Send
319 where
320 Self: LoadBlock,
321 {
322 async move {
323 if let Some(block) = self.recovered_block(block_id).await? {
324 if let Some(tx) = block.body().transactions().get(index) {
325 return Ok(Some(tx.encoded_2718().into()))
326 }
327 }
328
329 Ok(None)
330 }
331 }
332
333 fn send_transaction(
336 &self,
337 mut request: TransactionRequest,
338 ) -> impl Future<Output = Result<B256, Self::Error>> + Send
339 where
340 Self: EthApiSpec + LoadBlock + EstimateCall,
341 {
342 async move {
343 let from = match request.from {
344 Some(from) => from,
345 None => return Err(SignError::NoAccount.into_eth_err()),
346 };
347
348 if self.find_signer(&from).is_err() {
349 return Err(SignError::NoAccount.into_eth_err())
350 }
351
352 if request.nonce.is_none() {
354 let nonce = self.next_available_nonce(from).await?;
355 request.nonce = Some(nonce);
356 }
357
358 let chain_id = self.chain_id();
359 request.chain_id = Some(chain_id.to());
360
361 let estimated_gas =
362 self.estimate_gas_at(request.clone(), BlockId::pending(), None).await?;
363 let gas_limit = estimated_gas;
364 request.set_gas_limit(gas_limit.to());
365
366 let transaction = self.sign_request(&from, request).await?.with_signer(from);
367
368 let pool_transaction =
369 <<Self as RpcNodeCore>::Pool as TransactionPool>::Transaction::try_from_consensus(
370 transaction,
371 )
372 .map_err(|_| EthApiError::TransactionConversionError)?;
373
374 let hash = self
376 .pool()
377 .add_transaction(TransactionOrigin::Local, pool_transaction)
378 .await
379 .map_err(Self::Error::from_eth_err)?;
380
381 Ok(hash)
382 }
383 }
384
385 fn sign_request(
387 &self,
388 from: &Address,
389 txn: TransactionRequest,
390 ) -> impl Future<Output = Result<ProviderTx<Self::Provider>, Self::Error>> + Send {
391 async move {
392 self.find_signer(from)?
393 .sign_transaction(txn, from)
394 .await
395 .map_err(Self::Error::from_eth_err)
396 }
397 }
398
399 fn sign(
401 &self,
402 account: Address,
403 message: Bytes,
404 ) -> impl Future<Output = Result<Bytes, Self::Error>> + Send {
405 async move {
406 Ok(self
407 .find_signer(&account)?
408 .sign(account, &message)
409 .await
410 .map_err(Self::Error::from_eth_err)?
411 .as_bytes()
412 .into())
413 }
414 }
415
416 fn sign_transaction(
419 &self,
420 request: TransactionRequest,
421 ) -> impl Future<Output = Result<Bytes, Self::Error>> + Send {
422 async move {
423 let from = match request.from {
424 Some(from) => from,
425 None => return Err(SignError::NoAccount.into_eth_err()),
426 };
427
428 Ok(self.sign_request(&from, request).await?.encoded_2718().into())
429 }
430 }
431
432 fn sign_typed_data(&self, data: &TypedData, account: Address) -> Result<Bytes, Self::Error> {
434 Ok(self
435 .find_signer(&account)?
436 .sign_typed_data(account, data)
437 .map_err(Self::Error::from_eth_err)?
438 .as_bytes()
439 .into())
440 }
441
442 #[expect(clippy::type_complexity)]
444 fn find_signer(
445 &self,
446 account: &Address,
447 ) -> Result<Box<(dyn EthSigner<ProviderTx<Self::Provider>> + 'static)>, Self::Error> {
448 self.signers()
449 .read()
450 .iter()
451 .find(|signer| signer.is_signer_for(account))
452 .map(|signer| dyn_clone::clone_box(&**signer))
453 .ok_or_else(|| SignError::NoAccount.into_eth_err())
454 }
455}
456
457pub trait LoadTransaction: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt {
462 #[expect(clippy::complexity)]
468 fn transaction_by_hash(
469 &self,
470 hash: B256,
471 ) -> impl Future<
472 Output = Result<Option<TransactionSource<ProviderTx<Self::Provider>>>, Self::Error>,
473 > + Send {
474 async move {
475 let mut resp = self
477 .spawn_blocking_io(move |this| {
478 match this
479 .provider()
480 .transaction_by_hash_with_meta(hash)
481 .map_err(Self::Error::from_eth_err)?
482 {
483 None => Ok(None),
484 Some((tx, meta)) => {
485 let transaction = tx
489 .try_into_recovered_unchecked()
490 .map_err(|_| EthApiError::InvalidTransactionSignature)?;
491
492 let tx = TransactionSource::Block {
493 transaction,
494 index: meta.index,
495 block_hash: meta.block_hash,
496 block_number: meta.block_number,
497 base_fee: meta.base_fee,
498 };
499 Ok(Some(tx))
500 }
501 }
502 })
503 .await?;
504
505 if resp.is_none() {
506 if let Some(tx) =
508 self.pool().get(&hash).map(|tx| tx.transaction.clone().into_consensus())
509 {
510 resp = Some(TransactionSource::Pool(tx.into()));
511 }
512 }
513
514 Ok(resp)
515 }
516 }
517
518 #[expect(clippy::type_complexity)]
522 fn transaction_by_hash_at(
523 &self,
524 transaction_hash: B256,
525 ) -> impl Future<
526 Output = Result<
527 Option<(TransactionSource<ProviderTx<Self::Provider>>, BlockId)>,
528 Self::Error,
529 >,
530 > + Send {
531 async move {
532 Ok(self.transaction_by_hash(transaction_hash).await?.map(|tx| match tx {
533 tx @ TransactionSource::Pool(_) => (tx, BlockId::pending()),
534 tx @ TransactionSource::Block { block_hash, .. } => {
535 (tx, BlockId::Hash(block_hash.into()))
536 }
537 }))
538 }
539 }
540
541 #[expect(clippy::type_complexity)]
543 fn transaction_and_block(
544 &self,
545 hash: B256,
546 ) -> impl Future<
547 Output = Result<
548 Option<(
549 TransactionSource<ProviderTx<Self::Provider>>,
550 Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>,
551 )>,
552 Self::Error,
553 >,
554 > + Send {
555 async move {
556 let (transaction, at) = match self.transaction_by_hash_at(hash).await? {
557 None => return Ok(None),
558 Some(res) => res,
559 };
560
561 let block_hash = match at {
563 BlockId::Hash(hash) => hash.block_hash,
564 _ => return Ok(None),
565 };
566 let block = self
567 .cache()
568 .get_recovered_block(block_hash)
569 .await
570 .map_err(Self::Error::from_eth_err)?;
571 Ok(block.map(|block| (transaction, block)))
572 }
573 }
574}