mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-02-11 23:45:05 -05:00
feat(rpc): implement eth_getTransactionBySenderAndNonce (#10540)
Signed-off-by: jsvisa <delweng@gmail.com> Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
@@ -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<T: RpcObject, B: RpcObject> {
|
||||
index: Index,
|
||||
) -> RpcResult<Option<T>>;
|
||||
|
||||
/// 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<Option<T>>;
|
||||
|
||||
/// Returns the receipt of a transaction by transaction hash.
|
||||
#[method(name = "getTransactionReceipt")]
|
||||
async fn transaction_receipt(&self, hash: B256) -> RpcResult<Option<AnyTransactionReceipt>>;
|
||||
@@ -540,6 +550,64 @@ where
|
||||
.await?)
|
||||
}
|
||||
|
||||
/// Handler for: `eth_getTransactionBySenderAndNonce`
|
||||
async fn transaction_by_sender_and_nonce(
|
||||
&self,
|
||||
sender: Address,
|
||||
nonce: U64,
|
||||
) -> RpcResult<Option<RpcTransaction<T::NetworkTypes>>> {
|
||||
trace!(target: "rpc::eth", ?sender, ?nonce, "Serving eth_getTransactionBySenderAndNonce");
|
||||
let nonce = nonce.to::<u64>();
|
||||
|
||||
// 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::<u64>();
|
||||
|
||||
// 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::<u64>();
|
||||
|
||||
// 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<Option<AnyTransactionReceipt>> {
|
||||
trace!(target: "rpc::eth", ?hash, "Serving eth_getTransactionReceipt");
|
||||
|
||||
@@ -59,4 +59,4 @@ serde_json.workspace = true
|
||||
|
||||
|
||||
[features]
|
||||
js-tracer = ["revm-inspectors/js-tracer"]
|
||||
js-tracer = ["revm-inspectors/js-tracer"]
|
||||
|
||||
@@ -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<PooledTransactionsEleme
|
||||
|
||||
transaction.try_into_ecrecovered().or(Err(EthApiError::InvalidTransactionSignature))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub async fn binary_search<F, Fut, E>(low: u64, high: u64, check: F) -> Result<u64, E>
|
||||
where
|
||||
F: Fn(u64) -> Fut,
|
||||
Fut: Future<Output = Result<bool, E>>,
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::<u64>();
|
||||
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::<u64>();
|
||||
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
// 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<F, Fut>(low: u64, high: u64, check: F) -> RpcResult<u64>
|
||||
where
|
||||
F: Fn(u64) -> Fut,
|
||||
Fut: Future<Output = RpcResult<bool>>,
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user