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:
Delweng
2024-08-27 00:25:48 +08:00
committed by GitHub
parent 4138b52a31
commit ec31b247e9
4 changed files with 166 additions and 86 deletions

View File

@@ -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");

View File

@@ -59,4 +59,4 @@ serde_json.workspace = true
[features]
js-tracer = ["revm-inspectors/js-tracer"]
js-tracer = ["revm-inspectors/js-tracer"]

View File

@@ -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));
}
}

View File

@@ -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));
}
}