Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rpc): dedup rpc getTxBySenderAndNonce #10600

Merged
merged 9 commits into from
Aug 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
jsvisa marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// 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
Loading