Skip to content
Closed
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
10 changes: 9 additions & 1 deletion crates/optimism/rpc/src/eth/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use reth_rpc_convert::transaction::ConvertReceiptInput;
use reth_rpc_eth_api::{
helpers::{
receipt::calculate_gas_used_and_next_log_index, spec::SignersForRpc, EthTransactions,
LoadReceipt, LoadTransaction,
FillTransaction, LoadReceipt, LoadTransaction,
},
try_into_op_tx_info, EthApiTypes as _, FromEthApiError, FromEvmError, RpcConvert, RpcNodeCore,
RpcReceipt, TxInfoMapper,
Expand Down Expand Up @@ -220,6 +220,14 @@ where
{
}

impl<N, Rpc> FillTransaction for OpEthApi<N, Rpc>
where
N: RpcNodeCore,
OpEthApiError: FromEvmError<N::Evm>,
Rpc: RpcConvert<Primitives = N::Primitives, Error = OpEthApiError, Evm = N::Evm>,
{
}

impl<N, Rpc> OpEthApi<N, Rpc>
where
N: RpcNodeCore,
Expand Down
31 changes: 30 additions & 1 deletion crates/rpc/rpc-eth-api/src/core.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Implementation of the [`jsonrpsee`] generated [`EthApiServer`] trait. Handles RPC requests for
//! the `eth_` namespace.
use crate::{
helpers::{EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, FullEthApi},
helpers::{
EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, FillTransaction,
FullEthApi,
},
RpcBlock, RpcHeader, RpcReceipt, RpcTransaction,
};
use alloy_dyn_abi::TypedData;
Expand Down Expand Up @@ -270,6 +273,21 @@ pub trait EthApi<TxReq: RpcObject, T: RpcObject, B: RpcObject, R: RpcObject, H:
state_override: Option<StateOverride>,
) -> RpcResult<U256>;

/// Fills missing fields in a transaction request.
///
/// This method takes a transaction request and fills in any missing fields
/// (nonce, gas limit, gas price/fees, chain id) based on the current state
/// at the specified block.
///
/// Returns the filled transaction.
#[method(name = "fillTransaction")]
async fn fill_transaction(
&self,
request: TxReq,
block_number: BlockId,
state_override: Option<StateOverride>,
) -> RpcResult<TxReq>;

/// Returns the current price per gas in wei.
#[method(name = "gasPrice")]
async fn gas_price(&self) -> RpcResult<U256>;
Expand Down Expand Up @@ -721,6 +739,17 @@ where
.await?)
}

/// Handler for: `eth_fillTransaction`
async fn fill_transaction(
&self,
request: RpcTxReq<T::NetworkTypes>,
block_number: BlockId,
state_override: Option<StateOverride>,
) -> RpcResult<RpcTxReq<T::NetworkTypes>> {
trace!(target: "rpc::eth", ?request, ?block_number, "Serving eth_fillTransaction");
Ok(FillTransaction::fill_transaction(self, request, block_number, state_override).await?)
}

/// Handler for: `eth_gasPrice`
async fn gas_price(&self) -> RpcResult<U256> {
trace!(target: "rpc::eth", "Serving eth_gasPrice");
Expand Down
189 changes: 189 additions & 0 deletions crates/rpc/rpc-eth-api/src/helpers/fill.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
//! Fills transaction fields and simulates execution.

use super::{estimate::EstimateCall, Call, EthFees, LoadPendingBlock, LoadState, SpawnBlocking};
use crate::{FromEthApiError, RpcNodeCore};
use alloy_consensus::{BlockHeader, TxType};
use alloy_network::TransactionBuilder;
use alloy_primitives::U256;
use alloy_rpc_types_eth::{state::StateOverride, BlockId};
use futures::Future;
use reth_chainspec::{ChainSpecProvider, EthereumHardforks};
use reth_rpc_convert::{RpcConvert, RpcTxReq};
use reth_rpc_eth_types::{EthApiError, RpcInvalidTransactionError};
use reth_storage_api::BlockIdReader;
use tracing::trace;

/// Fills transaction fields for the [`EthApiServer`](crate::EthApiServer) trait in
/// the `eth_` namespace.
///
/// This trait provides functionality to fill missing transaction fields (nonce, gas, fees, chain
/// id).
pub trait FillTransaction: Call + EstimateCall + EthFees + LoadPendingBlock + LoadState {
/// Fills missing fields in a transaction request.
fn fill_transaction(
&self,
mut request: RpcTxReq<<Self::RpcConvert as RpcConvert>::Network>,
block_id: BlockId,
state_override: Option<StateOverride>,
) -> impl Future<Output = Result<RpcTxReq<<Self::RpcConvert as RpcConvert>::Network>, Self::Error>>
+ Send
where
Self: SpawnBlocking,
{
async move {
let tx_type = request.as_ref().transaction_type;
let supports_eip1559_fees = tx_type
.is_some_and(|tx_type| tx_type != TxType::Legacy && tx_type != TxType::Eip2930);

// Fetch header to determine base fee for EIP-1559 fees
let header_fut = async {
let provider = RpcNodeCore::provider(self);
let chain_spec = provider.chain_spec();

// If the transaction type does not support EIP-1559 fees, no need
// to fetch the header
if !supports_eip1559_fees {
return Ok(None);
}

let is_post_london = match block_id {
BlockId::Number(num) => self
.provider()
.convert_block_number(num)
.map_err(Self::Error::from_eth_err)?
.map(|block_number| chain_spec.is_london_active_at_block(block_number))
.unwrap_or(false),
_ => false, // Need to fetch header to determine
};

// If the block is not post-London, no need to fetch the header
if !is_post_london {
return Ok(None);
}

let block_hash = provider
.block_hash_for_id(block_id)
.map_err(Self::Error::from_eth_err)?
.ok_or_else(|| EthApiError::HeaderNotFound(block_id))
.map_err(Self::Error::from_eth_err)?;

self.cache()
.get_header(block_hash)
.await
.map_err(Self::Error::from_eth_err)
.map(Some)
};

let chain_id_fut = async {
if request.as_ref().chain_id().is_none() {
let (evm_env, _) = self.evm_env_at(block_id).await?;
Ok(Some(evm_env.cfg_env.chain_id))
} else {
Ok(None)
}
};

let nonce_fut = async {
if request.as_ref().nonce().is_none() &&
let Some(from) = request.as_ref().from()
{
let nonce = self
.state_at_block_id(block_id)
.await?
.account_nonce(&from)
.map_err(Self::Error::from_eth_err)?
.unwrap_or_default();
return Ok(Some(nonce));
}
Ok(None)
};

let blob_fee_fut = async {
let tx_req = request.as_ref();

if tx_req.max_fee_per_blob_gas.is_some() {
return Ok(None);
}

if tx_type.is_some_and(|tx_type| tx_type != TxType::Eip4844) {
return Ok(None);
}

if !tx_req.blob_versioned_hashes.is_some() && !tx_req.sidecar.is_some() {
return Ok(None);
}

let blob_fee = EthFees::blob_base_fee(self).await?;
Ok(Some(blob_fee))
};

let (header, chain_id, nonce, blob_fee) =
futures::try_join!(header_fut, chain_id_fut, nonce_fut, blob_fee_fut)?;

if let Some(chain_id) = chain_id {
request.as_mut().set_chain_id(chain_id);
}

if let Some(nonce) = nonce {
request.as_mut().set_nonce(nonce);
}

if let Some(blob_fee) = blob_fee {
request.as_mut().max_fee_per_blob_gas = Some(blob_fee.to());
}

let base_fee = header.and_then(|h| h.base_fee_per_gas());
let use_eip1559_fees = supports_eip1559_fees && base_fee.is_some();

if use_eip1559_fees {
// Derive EIP-1559 fee fields
let suggested_priority_fee = EthFees::suggested_priority_fee(self).await?;

if request.as_ref().max_priority_fee_per_gas().is_none() {
request.as_mut().set_max_priority_fee_per_gas(suggested_priority_fee.to());
}

if request.as_ref().max_fee_per_gas().is_none() {
let max_fee =
suggested_priority_fee.saturating_add(U256::from(base_fee.unwrap()));
request.as_mut().set_max_fee_per_gas(max_fee.to());
}
} else {
// Derive legacy gas price field
if request.as_ref().max_fee_per_gas().is_some() ||
request.as_ref().max_priority_fee_per_gas().is_some()
{
return Err(Self::Error::from_eth_err(EthApiError::InvalidTransaction(
RpcInvalidTransactionError::TxTypeNotSupported,
)));
}

if request.as_ref().gas_price().is_none() {
let gas_price = EthFees::gas_price(self).await?;
request.as_mut().set_gas_price(gas_price.to());
}
}

let gas_limit = EstimateCall::estimate_gas_at(
self,
request.clone(),
block_id,
state_override.clone(),
)
.await?;

// Set the estimated gas if not already set by the user
if request.as_ref().gas_limit().is_none() {
request.as_mut().set_gas_limit(gas_limit.to());
}

trace!(
target: "rpc::eth",
?request,
"Filled transaction"
);

Ok(request)
}
}
}
4 changes: 4 additions & 0 deletions crates/rpc/rpc-eth-api/src/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub mod call;
pub mod config;
pub mod estimate;
pub mod fee;
pub mod fill;
pub mod pending_block;
pub mod receipt;
pub mod signer;
Expand All @@ -32,6 +33,7 @@ pub use block::{EthBlocks, LoadBlock};
pub use blocking_task::SpawnBlocking;
pub use call::{Call, EthCall};
pub use fee::{EthFees, LoadFee};
pub use fill::FillTransaction;
pub use pending_block::LoadPendingBlock;
pub use receipt::LoadReceipt;
pub use signer::EthSigner;
Expand All @@ -58,6 +60,7 @@ pub trait FullEthApi:
+ EthState
+ EthCall
+ EthFees
+ FillTransaction
+ Trace
+ LoadReceipt
{
Expand All @@ -71,6 +74,7 @@ impl<T> FullEthApi for T where
+ EthState
+ EthCall
+ EthFees
+ FillTransaction
+ Trace
+ LoadReceipt
{
Expand Down
Loading
Loading