mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-02-03 11:34:57 -05:00
feat(rpc/ots): implement ots_getTransactionBySenderAndNonce (#9263)
Signed-off-by: jsvisa <delweng@gmail.com> Co-authored-by: Emilia Hane <emiliaha95@gmail.com> Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
@@ -5,7 +5,7 @@ use reth_rpc_types::{
|
||||
BlockDetails, ContractCreator, InternalOperation, OtsBlockTransactions, TraceEntry,
|
||||
TransactionsWithReceipts,
|
||||
},
|
||||
Header, Transaction,
|
||||
Header,
|
||||
};
|
||||
|
||||
/// Otterscan rpc interface.
|
||||
@@ -87,7 +87,7 @@ pub trait Otterscan {
|
||||
&self,
|
||||
sender: Address,
|
||||
nonce: u64,
|
||||
) -> RpcResult<Option<Transaction>>;
|
||||
) -> RpcResult<Option<TxHash>>;
|
||||
|
||||
/// Gets the transaction hash and the address who created a contract.
|
||||
#[method(name = "getContractCreator")]
|
||||
|
||||
@@ -334,12 +334,10 @@ where
|
||||
.err()
|
||||
.unwrap()
|
||||
));
|
||||
assert!(is_unimplemented(
|
||||
OtterscanClient::get_transaction_by_sender_and_nonce(client, sender, nonce,)
|
||||
.await
|
||||
.err()
|
||||
.unwrap()
|
||||
));
|
||||
assert!(OtterscanClient::get_transaction_by_sender_and_nonce(client, sender, nonce)
|
||||
.await
|
||||
.err()
|
||||
.is_none());
|
||||
assert!(OtterscanClient::get_contract_creator(client, address).await.unwrap().is_none());
|
||||
}
|
||||
|
||||
@@ -550,7 +548,7 @@ async fn test_eth_logs_args() {
|
||||
let client = handle.http_client().unwrap();
|
||||
|
||||
let mut params = ArrayParams::default();
|
||||
params.insert( serde_json::json!({"blockHash":"0x58dc57ab582b282c143424bd01e8d923cddfdcda9455bad02a29522f6274a948"})).unwrap();
|
||||
params.insert(serde_json::json!({"blockHash":"0x58dc57ab582b282c143424bd01e8d923cddfdcda9455bad02a29522f6274a948"})).unwrap();
|
||||
|
||||
let resp = client.request::<Vec<Log>, _>("eth_getLogs", params).await;
|
||||
// block does not exist
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use alloy_primitives::Bytes;
|
||||
use async_trait::async_trait;
|
||||
use futures::future::BoxFuture;
|
||||
use jsonrpsee::core::RpcResult;
|
||||
use reth_primitives::{Address, BlockId, BlockNumberOrTag, TxHash, B256};
|
||||
use reth_rpc_api::{EthApiServer, OtterscanServer};
|
||||
@@ -14,7 +15,7 @@ use reth_rpc_types::{
|
||||
},
|
||||
parity::{Action, CreateAction, CreateOutput, TraceOutput},
|
||||
},
|
||||
BlockTransactions, Header, Transaction,
|
||||
BlockTransactions, Header,
|
||||
};
|
||||
use revm_inspectors::{
|
||||
tracing::{types::CallTraceNode, TracingInspectorConfig},
|
||||
@@ -30,6 +31,41 @@ pub struct OtterscanApi<Eth> {
|
||||
eth: Eth,
|
||||
}
|
||||
|
||||
/// 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<'a, F>(low: u64, high: u64, check: F) -> RpcResult<u64>
|
||||
where
|
||||
F: Fn(u64) -> BoxFuture<'a, 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)
|
||||
}
|
||||
|
||||
impl<Eth> OtterscanApi<Eth> {
|
||||
/// Creates a new instance of `Otterscan`.
|
||||
pub const fn new(eth: Eth) -> Self {
|
||||
@@ -234,10 +270,51 @@ where
|
||||
/// Handler for `getTransactionBySenderAndNonce`
|
||||
async fn get_transaction_by_sender_and_nonce(
|
||||
&self,
|
||||
_sender: Address,
|
||||
_nonce: u64,
|
||||
) -> RpcResult<Option<Transaction>> {
|
||||
Err(internal_rpc_err("unimplemented"))
|
||||
sender: Address,
|
||||
nonce: u64,
|
||||
) -> RpcResult<Option<TxHash>> {
|
||||
// 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::<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(1, self.eth.block_number()?.saturating_to(), |mid| {
|
||||
Box::pin(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)
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
|
||||
let Some(BlockTransactions::Full(transactions)) =
|
||||
self.eth.block_by_number(num.into(), true).await?.map(|block| block.inner.transactions)
|
||||
else {
|
||||
return Err(EthApiError::UnknownBlockNumber.into());
|
||||
};
|
||||
|
||||
Ok(transactions
|
||||
.into_iter()
|
||||
.find(|tx| tx.from == sender && tx.nonce == nonce)
|
||||
.map(|tx| tx.hash))
|
||||
}
|
||||
|
||||
/// Handler for `getContractCreator`
|
||||
@@ -246,23 +323,12 @@ where
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// use binary search from block [1, latest block number] to find the first block where the
|
||||
// contract was deployed
|
||||
let mut low = 1;
|
||||
let mut high = self.eth.block_number()?.saturating_to::<u64>();
|
||||
let mut num = high;
|
||||
|
||||
while low <= high {
|
||||
let mid = (low + high) / 2;
|
||||
if self.eth.get_code(address, Some(mid.into())).await?.is_empty() {
|
||||
// not found in current block, need to search in the later blocks
|
||||
low = mid + 1;
|
||||
} else {
|
||||
// found in current block, try to find a lower block
|
||||
high = mid - 1;
|
||||
num = mid;
|
||||
}
|
||||
}
|
||||
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()) },
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
|
||||
let traces = self
|
||||
.eth
|
||||
@@ -307,3 +373,27 @@ where
|
||||
Ok(found)
|
||||
}
|
||||
}
|
||||
|
||||
#[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