Skip to content

Commit

Permalink
feat(rpc): dedup rpc getTxBySenderAndNonce (paradigmxyz#10600)
Browse files Browse the repository at this point in the history
Signed-off-by: jsvisa <[email protected]>
  • Loading branch information
jsvisa authored Aug 31, 2024
1 parent 24f0471 commit 3efe22e
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 119 deletions.
6 changes: 4 additions & 2 deletions crates/rpc/rpc-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,8 @@ where
WithOtherFields<reth_rpc_types::Transaction>,
reth_rpc_types::Block<WithOtherFields<reth_rpc_types::Transaction>>,
reth_rpc_types::AnyTransactionReceipt,
> + TraceExt,
> + TraceExt
+ EthTransactions,
{
let otterscan_api = self.otterscan_api();
self.modules.insert(RethRpcModule::Ots, otterscan_api.into_rpc().into());
Expand Down Expand Up @@ -917,7 +918,8 @@ where
WithOtherFields<reth_rpc_types::Transaction>,
reth_rpc_types::Block<WithOtherFields<reth_rpc_types::Transaction>>,
reth_rpc_types::AnyTransactionReceipt,
> + TraceExt,
> + TraceExt
+ EthTransactions,
{
let eth_api = self.eth_api().clone();
OtterscanApi::new(eth_api)
Expand Down
63 changes: 6 additions & 57 deletions crates/rpc/rpc-eth-api/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -562,55 +558,8 @@ where
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))
Ok(EthTransactions::get_transaction_by_sender_and_nonce(self, sender, nonce.to(), true)
.await?)
}

/// Handler for: `eth_getTransactionReceipt`
Expand Down
28 changes: 20 additions & 8 deletions crates/rpc/rpc-eth-api/src/helpers/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,7 @@ pub trait EthState: LoadState + SpawnBlocking {
address: Address,
block_id: Option<BlockId>,
) -> impl Future<Output = Result<Bytes, Self::Error>> + 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.
Expand Down Expand Up @@ -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<BlockId>,
) -> impl Future<Output = Result<Bytes, Self::Error>> + 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())
})
}
}
85 changes: 81 additions & 4 deletions crates/rpc/rpc-eth-api/src/helpers/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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
Expand Down Expand Up @@ -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<Output = Result<Option<RpcTransaction<Self::NetworkTypes>>, 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::<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);
}

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::<u64>();

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.
Expand Down
64 changes: 16 additions & 48 deletions crates/rpc/rpc/src/otterscan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -75,6 +78,7 @@ where
TransactionResponse = WithOtherFields<reth_rpc_types::Transaction>,
>,
> + TraceExt
+ EthTransactions
+ 'static,
{
/// Handler for `{ots,erigon}_getHeaderByNumber`
Expand All @@ -84,7 +88,9 @@ where

/// Handler for `ots_hasCode`
async fn has_code(&self, address: Address, block_number: Option<u64>) -> RpcResult<bool> {
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`
Expand Down Expand Up @@ -282,51 +288,11 @@ where
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::<_, _, 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)
}
},
)
.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))
}

Expand All @@ -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())
})
},
)
Expand Down

0 comments on commit 3efe22e

Please sign in to comment.