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