diff --git a/crates/rpc/rpc-eth-api/src/core.rs b/crates/rpc/rpc-eth-api/src/core.rs index d66bc91597de..a18bd579598d 100644 --- a/crates/rpc/rpc-eth-api/src/core.rs +++ b/crates/rpc/rpc-eth-api/src/core.rs @@ -3,24 +3,26 @@ use alloy_dyn_abi::TypedData; use alloy_json_rpc::RpcObject; -use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use jsonrpsee::{core::RpcResult, proc_macros::rpc, types::ErrorObjectOwned}; 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}, - AnyTransactionReceipt, BlockOverrides, Bundle, EIP1186AccountProofResponse, EthCallResponse, - FeeHistory, Header, Index, StateContext, SyncStatus, TransactionRequest, Work, + AnyTransactionReceipt, BlockOverrides, BlockTransactions, Bundle, EIP1186AccountProofResponse, + EthCallResponse, FeeHistory, Header, Index, StateContext, SyncStatus, TransactionRequest, Work, }; +use reth_transaction_pool::{PoolTransaction, TransactionPool}; use tracing::trace; use crate::{ helpers::{ transaction::UpdateRawTxForwarder, EthApiSpec, EthBlocks, EthCall, EthFees, EthState, - EthTransactions, FullEthApi, + EthTransactions, FullEthApi, LoadState, }, RpcBlock, RpcTransaction, }; @@ -164,6 +166,14 @@ pub trait EthApi { index: Index, ) -> RpcResult>; + /// Returns information about a transaction by sender and nonce. + #[method(name = "getTransactionBySenderAndNonce")] + async fn transaction_by_sender_and_nonce( + &self, + address: Address, + nonce: U64, + ) -> RpcResult>; + /// Returns the receipt of a transaction by transaction hash. #[method(name = "getTransactionReceipt")] async fn transaction_receipt(&self, hash: B256) -> RpcResult>; @@ -540,6 +550,64 @@ where .await?) } + /// Handler for: `eth_getTransactionBySenderAndNonce` + async fn transaction_by_sender_and_nonce( + &self, + sender: Address, + 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)) + } + /// Handler for: `eth_getTransactionReceipt` async fn transaction_receipt(&self, hash: B256) -> RpcResult> { trace!(target: "rpc::eth", ?hash, "Serving eth_getTransactionReceipt"); diff --git a/crates/rpc/rpc-eth-types/Cargo.toml b/crates/rpc/rpc-eth-types/Cargo.toml index 25be4a246735..b07c98b3bca3 100644 --- a/crates/rpc/rpc-eth-types/Cargo.toml +++ b/crates/rpc/rpc-eth-types/Cargo.toml @@ -59,4 +59,4 @@ serde_json.workspace = true [features] -js-tracer = ["revm-inspectors/js-tracer"] \ No newline at end of file +js-tracer = ["revm-inspectors/js-tracer"] diff --git a/crates/rpc/rpc-eth-types/src/utils.rs b/crates/rpc/rpc-eth-types/src/utils.rs index a35708396813..ca2901ba8679 100644 --- a/crates/rpc/rpc-eth-types/src/utils.rs +++ b/crates/rpc/rpc-eth-types/src/utils.rs @@ -1,6 +1,7 @@ //! Commonly used code snippets use reth_primitives::{Bytes, PooledTransactionsElement, PooledTransactionsElementEcRecovered}; +use std::future::Future; use super::{EthApiError, EthResult}; @@ -17,3 +18,67 @@ pub fn recover_raw_transaction(data: Bytes) -> EthResult(low: u64, high: u64, check: F) -> Result +where + F: Fn(u64) -> Fut, + Fut: Future>, +{ + let mut low = low; + let mut high = high; + let mut num = high; + + while low <= high { + let mid = (low + high) / 2; + if check(mid).await? { + high = mid - 1; + num = mid; + } else { + low = mid + 1 + } + } + + Ok(num) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_binary_search() { + // in the middle + let num: Result<_, ()> = + binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 5) })).await; + assert_eq!(num, Ok(5)); + + // in the upper + let num: Result<_, ()> = + binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 7) })).await; + assert_eq!(num, Ok(7)); + + // in the lower + let num: Result<_, ()> = + binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 1) })).await; + assert_eq!(num, Ok(1)); + + // higher than the upper + let num: Result<_, ()> = + binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 11) })).await; + assert_eq!(num, Ok(10)); + } +} diff --git a/crates/rpc/rpc/src/otterscan.rs b/crates/rpc/rpc/src/otterscan.rs index d385c37a9e25..f65afe969082 100644 --- a/crates/rpc/rpc/src/otterscan.rs +++ b/crates/rpc/rpc/src/otterscan.rs @@ -1,11 +1,11 @@ use alloy_network::Network; use alloy_primitives::Bytes; use async_trait::async_trait; -use jsonrpsee::core::RpcResult; +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, RpcTransaction}; -use reth_rpc_eth_types::EthApiError; +use reth_rpc_eth_types::{utils::binary_search, EthApiError}; use reth_rpc_server_types::result::internal_rpc_err; use reth_rpc_types::{ trace::{ @@ -22,7 +22,6 @@ use revm_inspectors::{ transfer::{TransferInspector, TransferKind}, }; use revm_primitives::ExecutionResult; -use std::future::Future; const API_LEVEL: u64 = 8; @@ -295,20 +294,24 @@ where // 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(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) - } - }) + 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)) = @@ -329,11 +332,15 @@ where return Ok(None); } - let num = binary_search(1, self.eth.block_number()?.saturating_to(), |mid| { - Box::pin( - async move { Ok(!self.eth.get_code(address, Some(mid.into())).await?.is_empty()) }, - ) - }) + let num = binary_search::<_, _, ErrorObjectOwned>( + 1, + self.eth.block_number()?.saturating_to(), + |mid| { + Box::pin(async move { + Ok(!self.eth.get_code(address, Some(mid.into())).await?.is_empty()) + }) + }, + ) .await?; let traces = self @@ -380,63 +387,3 @@ where Ok(found) } } - -/// Performs a binary search within a given block range to find the desired block number. -/// -/// The binary search is performed by calling the provided asynchronous `check` closure on the -/// blocks of the range. The closure should return a future representing the result of performing -/// the desired logic at a given block. The future resolves to an `bool` where: -/// - `true` indicates that the condition has been matched, but we can try to find a lower block to -/// make the condition more matchable. -/// - `false` indicates that the condition not matched, so the target is not present in the current -/// block and should continue searching in a higher range. -/// -/// Args: -/// - `low`: The lower bound of the block range (inclusive). -/// - `high`: The upper bound of the block range (inclusive). -/// - `check`: A closure that performs the desired logic at a given block. -async fn binary_search(low: u64, high: u64, check: F) -> RpcResult -where - F: Fn(u64) -> Fut, - Fut: Future>, -{ - let mut low = low; - let mut high = high; - let mut num = high; - - while low <= high { - let mid = (low + high) / 2; - if check(mid).await? { - high = mid - 1; - num = mid; - } else { - low = mid + 1 - } - } - - Ok(num) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_binary_search() { - // in the middle - let num = binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 5) })).await; - assert_eq!(num, Ok(5)); - - // in the upper - let num = binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 7) })).await; - assert_eq!(num, Ok(7)); - - // in the lower - let num = binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 1) })).await; - assert_eq!(num, Ok(1)); - - // high than the upper - let num = binary_search(1, 10, |mid| Box::pin(async move { Ok(mid >= 11) })).await; - assert_eq!(num, Ok(10)); - } -}