diff --git a/crates/rpc/rpc/src/eth/api/call.rs b/crates/rpc/rpc/src/eth/api/call.rs index df54c1461a..ea6abc8bef 100644 --- a/crates/rpc/rpc/src/eth/api/call.rs +++ b/crates/rpc/rpc/src/eth/api/call.rs @@ -12,6 +12,7 @@ use crate::{ EthApi, }; use ethers_core::utils::get_contract_address; +use reth_network_api::NetworkInfo; use reth_primitives::{AccessList, BlockId, BlockNumberOrTag, U256}; use reth_provider::{BlockProvider, EvmEnvProvider, StateProvider, StateProviderFactory}; use reth_revm::{ @@ -34,7 +35,7 @@ impl EthApi where Pool: TransactionPool + Clone + 'static, Client: BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, - Network: Send + Sync + 'static, + Network: NetworkInfo + Send + Sync + 'static, { /// Estimate gas needed for execution of the `request` at the [BlockId]. pub(crate) async fn estimate_gas_at( diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index 8a85776529..120c7ed6fc 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -23,6 +23,7 @@ use reth_rpc_types::{ }; use reth_transaction_pool::TransactionPool; +use reth_network_api::NetworkInfo; use serde_json::Value; use std::collections::BTreeMap; use tracing::trace; @@ -33,7 +34,7 @@ where Self: EthApiSpec + EthTransactions, Pool: TransactionPool + 'static, Client: BlockProvider + HeaderProvider + StateProviderFactory + EvmEnvProvider + 'static, - Network: Send + Sync + 'static, + Network: NetworkInfo + Send + Sync + 'static, { /// Handler for: `eth_protocolVersion` async fn protocol_version(&self) -> Result { diff --git a/crates/rpc/rpc/src/eth/api/state.rs b/crates/rpc/rpc/src/eth/api/state.rs index 9aadb79c16..a06b700004 100644 --- a/crates/rpc/rpc/src/eth/api/state.rs +++ b/crates/rpc/rpc/src/eth/api/state.rs @@ -1,7 +1,7 @@ //! Contains RPC handler implementations specific to state. use crate::{ - eth::error::{EthApiError, EthResult}, + eth::error::{EthApiError, EthResult, InvalidTransactionError}, EthApi, }; use reth_primitives::{ @@ -12,10 +12,12 @@ use reth_provider::{ AccountProvider, BlockProvider, EvmEnvProvider, StateProvider, StateProviderFactory, }; use reth_rpc_types::{EIP1186AccountProofResponse, StorageProof}; +use reth_transaction_pool::{PoolTransaction, TransactionPool}; impl EthApi where Client: BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, + Pool: TransactionPool + Clone + 'static, { pub(crate) fn get_code(&self, address: Address, block_id: Option) -> EthResult { let state = self.state_at_block_id_or_latest(block_id)?; @@ -29,14 +31,43 @@ where Ok(balance) } + /// Returns the number of transactions sent from an address at the given block identifier. + /// + /// If this is [BlockNumberOrTag::Pending] then this will look up the highest transaction in + /// pool and return the next nonce (highest + 1). pub(crate) fn get_transaction_count( &self, address: Address, block_id: Option, ) -> EthResult { + if let Some(BlockId::Number(BlockNumberOrTag::Pending)) = block_id { + // lookup transactions in pool + let address_txs = self.pool().get_transactions_by_sender(address); + + if address_txs.is_empty() { + // get max transaction with the highest nonce + let highest_nonce_tx = address_txs + .into_iter() + .reduce(|accum, item| { + if item.transaction.nonce() > accum.transaction.nonce() { + item + } else { + accum + } + }) + .expect("Not empty; qed"); + + let tx_count = highest_nonce_tx + .transaction + .nonce() + .checked_add(1) + .ok_or(InvalidTransactionError::NonceMaxValue)?; + return Ok(U256::from(tx_count)) + } + } + let state = self.state_at_block_id_or_latest(block_id)?; - let nonce = U256::from(state.account_nonce(address)?.unwrap_or_default()); - Ok(nonce) + Ok(U256::from(state.account_nonce(address)?.unwrap_or_default())) } pub(crate) fn storage_at( diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index 6765d4fd17..adac545fb1 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -5,9 +5,10 @@ use crate::{ revm_utils::{inspect, prepare_call_env, transact}, utils::recover_raw_transaction, }, - EthApi, + EthApi, EthApiSpec, }; use async_trait::async_trait; +use reth_network_api::NetworkInfo; use reth_primitives::{ Address, BlockId, BlockNumberOrTag, Bytes, FromRecoveredTransaction, IntoRecoveredTransaction, Receipt, Transaction as PrimitiveTransaction, @@ -111,7 +112,7 @@ impl EthTransactions for EthApi where Pool: TransactionPool + Clone + 'static, Client: BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, - Network: Send + Sync + 'static, + Network: NetworkInfo + Send + Sync + 'static, { fn state_at(&self, at: BlockId) -> EthResult> { self.state_at_block_id(at) @@ -224,19 +225,69 @@ where Ok(hash) } - async fn send_transaction(&self, request: TransactionRequest) -> EthResult { + async fn send_transaction(&self, mut request: TransactionRequest) -> EthResult { let from = match request.from { Some(from) => from, None => return Err(SignError::NoAccount.into()), }; + + // set nonce if not already set before + if request.nonce.is_none() { + let nonce = + self.get_transaction_count(from, Some(BlockId::Number(BlockNumberOrTag::Pending)))?; + request.nonce = Some(nonce); + } + + let chain_id = self.chain_id(); + // TODO: we need an oracle to fetch the gas price of the current chain + let gas_price = request.gas_price.unwrap_or_default(); + let max_fee_per_gas = request.max_fee_per_gas.unwrap_or_default(); + + let estimated_gas = self + .estimate_gas_at( + CallRequest { + from: Some(from), + to: request.to, + gas: request.gas, + gas_price: Some(U256::from(gas_price)), + max_fee_per_gas: Some(U256::from(max_fee_per_gas)), + value: request.value, + data: request.data.clone(), + nonce: request.nonce, + chain_id: Some(chain_id), + access_list: request.access_list.clone(), + max_priority_fee_per_gas: Some(U256::from(max_fee_per_gas)), + }, + BlockId::Number(BlockNumberOrTag::Pending), + ) + .await?; + let gas_limit = estimated_gas; + let transaction = match request.into_typed_request() { - Some(tx) => tx, + Some(TypedTransactionRequest::Legacy(mut m)) => { + m.chain_id = Some(chain_id.as_u64()); + m.gas_limit = gas_limit; + m.gas_price = gas_price; + + TypedTransactionRequest::Legacy(m) + } + Some(TypedTransactionRequest::EIP2930(mut m)) => { + m.chain_id = chain_id.as_u64(); + m.gas_limit = gas_limit; + m.gas_price = gas_price; + + TypedTransactionRequest::EIP2930(m) + } + Some(TypedTransactionRequest::EIP1559(mut m)) => { + m.chain_id = chain_id.as_u64(); + m.gas_limit = gas_limit; + m.max_fee_per_gas = max_fee_per_gas; + + TypedTransactionRequest::EIP1559(m) + } None => return Err(EthApiError::ConflictingFeeFieldsInRequest), }; - // TODO we need to update additional settings in the transaction: nonce, gaslimit, chainid, - // gasprice - let signed_tx = self.sign_request(&from, transaction)?; let recovered = @@ -534,6 +585,7 @@ impl From for Transaction { mod tests { use super::*; use crate::{eth::cache::EthStateCache, EthApi}; + use reth_network_api::test_utils::NoopNetwork; use reth_primitives::{hex_literal::hex, Bytes}; use reth_provider::test_utils::NoopProvider; use reth_transaction_pool::{test_utils::testing_pool, TransactionPool}; @@ -541,13 +593,14 @@ mod tests { #[tokio::test] async fn send_raw_transaction() { let noop_provider = NoopProvider::default(); + let noop_network_provider = NoopNetwork::default(); let pool = testing_pool(); let eth_api = EthApi::new( noop_provider, pool.clone(), - (), + noop_network_provider, EthStateCache::spawn(NoopProvider::default(), Default::default()), ); diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index 8807d5e29b..cceba4fd23 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -96,7 +96,7 @@ use crate::{ pool::PoolInner, traits::{NewTransactionEvent, PoolSize}, }; -use reth_primitives::{TxHash, U256}; +use reth_primitives::{Address, TxHash, U256}; use reth_provider::StateProviderFactory; use std::{collections::HashMap, sync::Arc}; use tokio::sync::mpsc::Receiver; @@ -325,6 +325,18 @@ where fn on_propagated(&self, txs: PropagatedTransactions) { self.inner().on_propagated(txs) } + + fn get_transactions_by_sender( + &self, + sender: Address, + ) -> Vec>> { + self.pool + .pooled_transactions() + .iter() + .filter(|tx| tx.transaction.sender().eq(&sender)) + .map(Arc::clone) + .collect() + } } impl Clone for Pool { diff --git a/crates/transaction-pool/src/pool/mod.rs b/crates/transaction-pool/src/pool/mod.rs index 82d76bd5ee..5f1652a8aa 100644 --- a/crates/transaction-pool/src/pool/mod.rs +++ b/crates/transaction-pool/src/pool/mod.rs @@ -374,6 +374,18 @@ where self.pool.read().get(tx_hash) } + /// Returns all transactions of the address + pub(crate) fn get_transactions_by_sender( + &self, + sender: Address, + ) -> Vec>> { + self.pooled_transactions() + .iter() + .filter(|tx| tx.transaction.sender().eq(&sender)) + .map(Arc::clone) + .collect() + } + /// Returns all the transactions belonging to the hashes. /// /// If no transaction exists, it is skipped. diff --git a/crates/transaction-pool/src/traits.rs b/crates/transaction-pool/src/traits.rs index a7bc772ce6..0aad8d2fd7 100644 --- a/crates/transaction-pool/src/traits.rs +++ b/crates/transaction-pool/src/traits.rs @@ -145,6 +145,12 @@ pub trait TransactionPool: Send + Sync + Clone { /// /// Consumer: P2P fn on_propagated(&self, txs: PropagatedTransactions); + + /// Returns all transactions sent by a given user + fn get_transactions_by_sender( + &self, + sender: Address, + ) -> Vec>>; } /// Represents a transaction that was propagated over the network.