diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index 58f2d9b8fe13..f82532432291 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -813,7 +813,8 @@ where WithOtherFields, reth_rpc_types::Block>, reth_rpc_types::AnyTransactionReceipt, - > + TraceExt, + > + TraceExt + + EthTransactions, { let otterscan_api = self.otterscan_api(); self.modules.insert(RethRpcModule::Ots, otterscan_api.into_rpc().into()); @@ -917,7 +918,8 @@ where WithOtherFields, reth_rpc_types::Block>, reth_rpc_types::AnyTransactionReceipt, - > + TraceExt, + > + TraceExt + + EthTransactions, { let eth_api = self.eth_api().clone(); OtterscanApi::new(eth_api) diff --git a/crates/rpc/rpc-eth-api/src/core.rs b/crates/rpc/rpc-eth-api/src/core.rs index 9a19307ce990..b9d22a7221e1 100644 --- a/crates/rpc/rpc-eth-api/src/core.rs +++ b/crates/rpc/rpc-eth-api/src/core.rs @@ -3,26 +3,22 @@ use alloy_dyn_abi::TypedData; use alloy_json_rpc::RpcObject; -use jsonrpsee::{core::RpcResult, proc_macros::rpc, types::ErrorObjectOwned}; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use reth_primitives::{ transaction::AccessListResult, Address, BlockId, BlockNumberOrTag, Bytes, B256, B64, U256, U64, }; -use reth_rpc_eth_types::{utils::binary_search, EthApiError}; use reth_rpc_server_types::{result::internal_rpc_err, ToRpcResult}; use reth_rpc_types::{ serde_helpers::JsonStorageKey, simulate::{SimBlock, SimulatedBlock}, state::{EvmOverrides, StateOverride}, - BlockOverrides, BlockTransactions, Bundle, EIP1186AccountProofResponse, EthCallResponse, - FeeHistory, Header, Index, StateContext, SyncStatus, TransactionRequest, Work, + BlockOverrides, Bundle, EIP1186AccountProofResponse, EthCallResponse, FeeHistory, Header, + Index, StateContext, SyncStatus, TransactionRequest, Work, }; -use reth_transaction_pool::{PoolTransaction, TransactionPool}; use tracing::trace; use crate::{ - helpers::{ - EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, FullEthApi, LoadState, - }, + helpers::{EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, FullEthApi}, RpcBlock, RpcReceipt, RpcTransaction, }; @@ -562,55 +558,8 @@ where nonce: U64, ) -> RpcResult>> { trace!(target: "rpc::eth", ?sender, ?nonce, "Serving eth_getTransactionBySenderAndNonce"); - let nonce = nonce.to::(); - - // Check the pool first - if let Some(tx) = LoadState::pool(self).get_transaction_by_sender_and_nonce(sender, nonce) { - let transaction = tx.transaction.clone().into_consensus(); - return Ok(Some(reth_rpc_types_compat::transaction::from_recovered(transaction))) - } - - // Check if the sender is a contract - if self.get_code(sender, None).await?.len() > 0 { - return Ok(None) - } - - let highest = EthState::transaction_count(self, sender, None).await?.saturating_to::(); - - // If the nonce is higher or equal to the highest nonce, the transaction is pending or not - // exists. - if nonce >= highest { - return Ok(None) - } - - // perform a binary search over the block range to find the block in which the sender's - // nonce reached the requested nonce. - let num = binary_search::<_, _, ErrorObjectOwned>( - 1, - self.block_number()?.saturating_to(), - |mid| { - async move { - let mid_nonce = EthState::transaction_count(self, sender, Some(mid.into())) - .await? - .saturating_to::(); - - // The `transaction_count` returns the `nonce` after the transaction was - // executed, which is the state of the account after the block, and we need to - // find the transaction whose nonce is the pre-state, so - // need to compare with `nonce`(no equal). - Ok(mid_nonce > nonce) - } - }, - ) - .await?; - - let Some(BlockTransactions::Full(transactions)) = - self.block_by_number(num.into(), true).await?.map(|block| block.transactions) - else { - return Err(EthApiError::UnknownBlockNumber.into()); - }; - - Ok(transactions.into_iter().find(|tx| *tx.from == *sender && tx.nonce == nonce)) + Ok(EthTransactions::get_transaction_by_sender_and_nonce(self, sender, nonce.to(), true) + .await?) } /// Handler for: `eth_getTransactionReceipt` diff --git a/crates/rpc/rpc-eth-api/src/helpers/state.rs b/crates/rpc/rpc-eth-api/src/helpers/state.rs index 5145bbade648..06c3f9b5b8b1 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/state.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/state.rs @@ -42,14 +42,7 @@ pub trait EthState: LoadState + SpawnBlocking { address: Address, block_id: Option, ) -> impl Future> + Send { - self.spawn_blocking_io(move |this| { - Ok(this - .state_at_block_id_or_latest(block_id)? - .account_code(address) - .map_err(Self::Error::from_eth_err)? - .unwrap_or_default() - .original_bytes()) - }) + LoadState::get_code(self, address, block_id) } /// Returns balance of given account, at given blocknumber. @@ -295,4 +288,23 @@ pub trait LoadState: EthApiTypes { )) }) } + + /// Returns code of given account, at the given identifier. + fn get_code( + &self, + address: Address, + block_id: Option, + ) -> impl Future> + Send + where + Self: SpawnBlocking, + { + self.spawn_blocking_io(move |this| { + Ok(this + .state_at_block_id_or_latest(block_id)? + .account_code(address) + .map_err(Self::Error::from_eth_err)? + .unwrap_or_default() + .original_bytes()) + }) + } } diff --git a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs index cf7bbf025b05..5e9fd24d785d 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs @@ -7,9 +7,10 @@ use reth_primitives::{ Address, BlockId, Bytes, Receipt, SealedBlockWithSenders, TransactionMeta, TransactionSigned, TxHash, TxKind, B256, U256, }; -use reth_provider::{BlockReaderIdExt, ReceiptProvider, TransactionsProvider}; +use reth_provider::{BlockNumReader, BlockReaderIdExt, ReceiptProvider, TransactionsProvider}; use reth_rpc_eth_types::{ - utils::recover_raw_transaction, EthApiError, EthStateCache, SignError, TransactionSource, + utils::{binary_search, recover_raw_transaction}, + EthApiError, EthStateCache, SignError, TransactionSource, }; use reth_rpc_types::{ transaction::{ @@ -18,13 +19,14 @@ use reth_rpc_types::{ }, AnyTransactionReceipt, TransactionInfo, TransactionRequest, TypedTransactionRequest, }; -use reth_rpc_types_compat::transaction::from_recovered_with_block_context; +use reth_rpc_types_compat::transaction::{from_recovered, from_recovered_with_block_context}; use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool}; use crate::{FromEthApiError, IntoEthApiError, RpcTransaction}; use super::{ - Call, EthApiSpec, EthSigner, LoadBlock, LoadFee, LoadPendingBlock, LoadReceipt, SpawnBlocking, + Call, EthApiSpec, EthSigner, LoadBlock, LoadFee, LoadPendingBlock, LoadReceipt, LoadState, + SpawnBlocking, }; /// Transaction related functions for the [`EthApiServer`](crate::EthApiServer) trait in @@ -212,6 +214,81 @@ pub trait EthTransactions: LoadTransaction { } } + /// Find a transaction by sender's address and nonce. + fn get_transaction_by_sender_and_nonce( + &self, + sender: Address, + nonce: u64, + include_pending: bool, + ) -> impl Future>, Self::Error>> + Send + where + Self: LoadBlock + LoadState, + { + async move { + // Check the pool first + if include_pending { + if let Some(tx) = + LoadState::pool(self).get_transaction_by_sender_and_nonce(sender, nonce) + { + let transaction = tx.transaction.clone().into_consensus(); + return Ok(Some(from_recovered(transaction))); + } + } + + // Check if the sender is a contract + if self.get_code(sender, None).await?.len() > 0 { + return Ok(None); + } + + let highest = self.transaction_count(sender, None).await?.saturating_to::(); + + // If the nonce is higher or equal to the highest nonce, the transaction is pending or + // not exists. + if nonce >= highest { + return Ok(None); + } + + let Ok(high) = LoadBlock::provider(self).best_block_number() else { + return Err(EthApiError::UnknownBlockNumber.into()); + }; + + // Perform a binary search over the block range to find the block in which the sender's + // nonce reached the requested nonce. + let num = binary_search::<_, _, Self::Error>(1, high, |mid| async move { + let mid_nonce = + self.transaction_count(sender, Some(mid.into())).await?.saturating_to::(); + + Ok(mid_nonce > nonce) + }) + .await?; + + self.block_with_senders(num.into()) + .await? + .and_then(|block| { + let block_hash = block.hash(); + let block_number = block.number; + let base_fee_per_gas = block.base_fee_per_gas; + + block + .into_transactions_ecrecovered() + .enumerate() + .find(|(_, tx)| tx.signer() == sender && tx.nonce() == nonce) + .map(|(index, tx)| { + let tx_info = TransactionInfo { + hash: Some(tx.hash()), + block_hash: Some(block_hash), + block_number: Some(block_number), + base_fee: base_fee_per_gas.map(u128::from), + index: Some(index as u64), + }; + from_recovered_with_block_context(tx, tx_info) + }) + }) + .ok_or(EthApiError::UnknownBlockNumber.into()) + .map(Some) + } + } + /// Get transaction, as raw bytes, by [`BlockId`] and index of transaction within that block. /// /// Returns `Ok(None)` if the block does not exist, or index is out of range. diff --git a/crates/rpc/rpc/src/otterscan.rs b/crates/rpc/rpc/src/otterscan.rs index dd9a539563bf..d151f783f8c5 100644 --- a/crates/rpc/rpc/src/otterscan.rs +++ b/crates/rpc/rpc/src/otterscan.rs @@ -4,7 +4,10 @@ use async_trait::async_trait; use jsonrpsee::{core::RpcResult, types::ErrorObjectOwned}; use reth_primitives::{Address, BlockNumberOrTag, TxHash, B256, U256}; use reth_rpc_api::{EthApiServer, OtterscanServer}; -use reth_rpc_eth_api::{helpers::TraceExt, EthApiTypes, RpcBlock, RpcReceipt, RpcTransaction}; +use reth_rpc_eth_api::{ + helpers::{EthTransactions, TraceExt}, + EthApiTypes, RpcBlock, RpcReceipt, RpcTransaction, +}; use reth_rpc_eth_types::{utils::binary_search, EthApiError}; use reth_rpc_server_types::result::internal_rpc_err; use reth_rpc_types::{ @@ -75,6 +78,7 @@ where TransactionResponse = WithOtherFields, >, > + TraceExt + + EthTransactions + 'static, { /// Handler for `{ots,erigon}_getHeaderByNumber` @@ -84,7 +88,9 @@ where /// Handler for `ots_hasCode` async fn has_code(&self, address: Address, block_number: Option) -> RpcResult { - self.eth.get_code(address, block_number.map(Into::into)).await.map(|code| !code.is_empty()) + EthApiServer::get_code(&self.eth, address, block_number.map(Into::into)) + .await + .map(|code| !code.is_empty()) } /// Handler for `ots_getApiLevel` @@ -282,51 +288,11 @@ where sender: Address, nonce: u64, ) -> RpcResult> { - // Check if the sender is a contract - if self.has_code(sender, None).await? { - return Ok(None) - } - - let highest = - EthApiServer::transaction_count(&self.eth, sender, None).await?.saturating_to::(); - - // If the nonce is higher or equal to the highest nonce, the transaction is pending or not - // exists. - if nonce >= highest { - return Ok(None) - } - - // perform a binary search over the block range to find the block in which the sender's - // nonce reached the requested nonce. - let num = binary_search::<_, _, ErrorObjectOwned>( - 1, - self.eth.block_number()?.saturating_to(), - |mid| { - async move { - let mid_nonce = - EthApiServer::transaction_count(&self.eth, sender, Some(mid.into())) - .await? - .saturating_to::(); - - // The `transaction_count` returns the `nonce` after the transaction was - // executed, which is the state of the account after the block, and we need to - // find the transaction whose nonce is the pre-state, so - // need to compare with `nonce`(no equal). - Ok(mid_nonce > nonce) - } - }, - ) - .await?; - - let Some(BlockTransactions::Full(transactions)) = - self.eth.block_by_number(num.into(), true).await?.map(|block| block.transactions) - else { - return Err(EthApiError::UnknownBlockNumber.into()); - }; - - Ok(transactions - .into_iter() - .find(|tx| *tx.from == *sender && tx.nonce == nonce) + Ok(self + .eth + .get_transaction_by_sender_and_nonce(sender, nonce, false) + .await + .map_err(|e| e.into())? .map(|tx| tx.hash)) } @@ -341,7 +307,9 @@ where self.eth.block_number()?.saturating_to(), |mid| { Box::pin(async move { - Ok(!self.eth.get_code(address, Some(mid.into())).await?.is_empty()) + Ok(!EthApiServer::get_code(&self.eth, address, Some(mid.into())) + .await? + .is_empty()) }) }, )