feat: generic TxEnv (#13957)

This commit is contained in:
Arsenii Kulikov
2025-01-24 14:48:44 +04:00
committed by GitHub
parent 1296bacb87
commit 006eea0c34
18 changed files with 271 additions and 244 deletions

View File

@ -3,8 +3,8 @@
use super::{LoadBlock, LoadPendingBlock, LoadState, LoadTransaction, SpawnBlocking, Trace};
use crate::{
helpers::estimate::EstimateCall, FromEthApiError, FromEvmError, FullEthApiTypes,
IntoEthApiError, RpcBlock, RpcNodeCore,
helpers::estimate::EstimateCall, FromEthApiError, FromEvmError, FullEthApiTypes, RpcBlock,
RpcNodeCore,
};
use alloy_consensus::BlockHeader;
use alloy_eips::{eip1559::calc_next_block_base_fee, eip2930::AccessListResult};
@ -17,14 +17,14 @@ use alloy_rpc_types_eth::{
};
use futures::Future;
use reth_chainspec::EthChainSpec;
use reth_evm::{env::EvmEnv, ConfigureEvm, ConfigureEvmEnv, Evm};
use reth_evm::{env::EvmEnv, ConfigureEvm, ConfigureEvmEnv, Evm, TransactionEnv};
use reth_node_api::BlockBody;
use reth_primitives_traits::SignedTransaction;
use reth_provider::{BlockIdReader, ChainSpecProvider, ProviderHeader};
use reth_revm::{
database::StateProviderDatabase,
db::CacheDB,
primitives::{BlockEnv, ExecutionResult, ResultAndState, TxEnv},
primitives::{BlockEnv, ExecutionResult, ResultAndState},
DatabaseRef,
};
use reth_rpc_eth_types::{
@ -32,7 +32,6 @@ use reth_rpc_eth_types::{
error::ensure_success,
revm_utils::{
apply_block_overrides, apply_state_overrides, caller_gas_allowance, get_precompiles,
CallFees,
},
simulate::{self, EthSimulateError},
EthApiError, RevertError, RpcInvalidTransactionError, StateCacheDb,
@ -206,7 +205,7 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA
}
transactions.push(tx);
senders.push(tx_env.caller);
senders.push(tx_env.caller());
results.push(res.result);
}
@ -410,10 +409,10 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA
let mut db = CacheDB::new(StateProviderDatabase::new(state));
if request.gas.is_none() && tx_env.gas_price > U256::ZERO {
if request.gas.is_none() && tx_env.gas_price() > U256::ZERO {
let cap = caller_gas_allowance(&mut db, &tx_env)?;
// no gas limit was provided in the request, so we need to cap the request's gas limit
tx_env.gas_limit = cap.min(evm_env.block_env.gas_limit).saturating_to();
tx_env.set_gas_limit(cap.min(evm_env.block_env.gas_limit).saturating_to());
}
let from = request.from.unwrap_or_default();
@ -434,11 +433,11 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA
let (result, (evm_env, mut tx_env)) =
self.inspect(&mut db, evm_env, tx_env, &mut inspector)?;
let access_list = inspector.into_access_list();
tx_env.access_list = access_list.to_vec();
tx_env.set_access_list(access_list.clone());
match result.result {
ExecutionResult::Halt { reason, gas_used } => {
let error =
Some(RpcInvalidTransactionError::halt(reason, tx_env.gas_limit).to_string());
Some(RpcInvalidTransactionError::halt(reason, tx_env.gas_limit()).to_string());
return Ok(AccessListResult { access_list, gas_used: U256::from(gas_used), error })
}
ExecutionResult::Revert { output, gas_used } => {
@ -453,7 +452,7 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA
let res = match result.result {
ExecutionResult::Halt { reason, gas_used } => {
let error =
Some(RpcInvalidTransactionError::halt(reason, tx_env.gas_limit).to_string());
Some(RpcInvalidTransactionError::halt(reason, tx_env.gas_limit()).to_string());
AccessListResult { access_list, gas_used: U256::from(gas_used), error }
}
ExecutionResult::Revert { output, gas_used } => {
@ -490,14 +489,15 @@ pub trait Call:
f(StateProviderTraitObjWrapper(&state))
}
/// Executes the [`TxEnv`] against the given [Database] without committing state
/// Executes the `TxEnv` against the given [Database] without committing state
/// changes.
#[expect(clippy::type_complexity)]
fn transact<DB>(
&self,
db: DB,
evm_env: EvmEnv,
tx_env: TxEnv,
) -> Result<(ResultAndState, (EvmEnv, TxEnv)), Self::Error>
tx_env: <Self::Evm as ConfigureEvmEnv>::TxEnv,
) -> Result<(ResultAndState, (EvmEnv, <Self::Evm as ConfigureEvmEnv>::TxEnv)), Self::Error>
where
DB: Database,
EthApiError: From<DB::Error>,
@ -511,13 +511,14 @@ pub trait Call:
/// Executes the [`EvmEnv`] against the given [Database] without committing state
/// changes.
#[expect(clippy::type_complexity)]
fn transact_with_inspector<DB>(
&self,
db: DB,
evm_env: EvmEnv,
tx_env: TxEnv,
tx_env: <Self::Evm as ConfigureEvmEnv>::TxEnv,
inspector: impl GetInspector<DB>,
) -> Result<(ResultAndState, (EvmEnv, TxEnv)), Self::Error>
) -> Result<(ResultAndState, (EvmEnv, <Self::Evm as ConfigureEvmEnv>::TxEnv)), Self::Error>
where
DB: Database,
EthApiError: From<DB::Error>,
@ -530,12 +531,18 @@ pub trait Call:
}
/// Executes the call request at the given [`BlockId`].
#[expect(clippy::type_complexity)]
fn transact_call_at(
&self,
request: TransactionRequest,
at: BlockId,
overrides: EvmOverrides,
) -> impl Future<Output = Result<(ResultAndState, (EvmEnv, TxEnv)), Self::Error>> + Send
) -> impl Future<
Output = Result<
(ResultAndState, (EvmEnv, <Self::Evm as ConfigureEvmEnv>::TxEnv)),
Self::Error,
>,
> + Send
where
Self: LoadPendingBlock,
{
@ -585,7 +592,11 @@ pub trait Call:
) -> impl Future<Output = Result<R, Self::Error>> + Send
where
Self: LoadPendingBlock,
F: FnOnce(StateCacheDbRefMutWrapper<'_, '_>, EvmEnv, TxEnv) -> Result<R, Self::Error>
F: FnOnce(
StateCacheDbRefMutWrapper<'_, '_>,
EvmEnv,
<Self::Evm as ConfigureEvmEnv>::TxEnv,
) -> Result<R, Self::Error>
+ Send
+ 'static,
R: Send + 'static,
@ -694,84 +705,15 @@ pub trait Call:
Ok(index)
}
/// Configures a new [`TxEnv`] for the [`TransactionRequest`]
/// Configures a new `TxEnv` for the [`TransactionRequest`]
///
/// All [`TxEnv`] fields are derived from the given [`TransactionRequest`], if fields are
/// All `TxEnv` fields are derived from the given [`TransactionRequest`], if fields are
/// `None`, they fall back to the [`BlockEnv`]'s settings.
fn create_txn_env(
&self,
block_env: &BlockEnv,
request: TransactionRequest,
) -> Result<TxEnv, Self::Error> {
// Ensure that if versioned hashes are set, they're not empty
if request.blob_versioned_hashes.as_ref().is_some_and(|hashes| hashes.is_empty()) {
return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into_eth_err())
}
let TransactionRequest {
from,
to,
gas_price,
max_fee_per_gas,
max_priority_fee_per_gas,
gas,
value,
input,
nonce,
access_list,
chain_id,
blob_versioned_hashes,
max_fee_per_blob_gas,
authorization_list,
transaction_type: _,
sidecar: _,
} = request;
let CallFees { max_priority_fee_per_gas, gas_price, max_fee_per_blob_gas } =
CallFees::ensure_fees(
gas_price.map(U256::from),
max_fee_per_gas.map(U256::from),
max_priority_fee_per_gas.map(U256::from),
block_env.basefee,
blob_versioned_hashes.as_deref(),
max_fee_per_blob_gas.map(U256::from),
block_env.get_blob_gasprice().map(U256::from),
)?;
let gas_limit = gas.unwrap_or_else(|| {
// Use maximum allowed gas limit. The reason for this
// is that both Erigon and Geth use pre-configured gas cap even if
// it's possible to derive the gas limit from the block:
// <https://github.com/ledgerwatch/erigon/blob/eae2d9a79cb70dbe30b3a6b79c436872e4605458/cmd/rpcdaemon/commands/trace_adhoc.go#L956
// https://github.com/ledgerwatch/erigon/blob/eae2d9a79cb70dbe30b3a6b79c436872e4605458/eth/ethconfig/config.go#L94>
block_env.gas_limit.saturating_to()
});
#[allow(clippy::needless_update)]
let env = TxEnv {
gas_limit,
nonce,
caller: from.unwrap_or_default(),
gas_price,
gas_priority_fee: max_priority_fee_per_gas,
transact_to: to.unwrap_or(TxKind::Create),
value: value.unwrap_or_default(),
data: input
.try_into_unique_input()
.map_err(Self::Error::from_eth_err)?
.unwrap_or_default(),
chain_id,
access_list: access_list.unwrap_or_default().into(),
// EIP-4844 fields
blob_hashes: blob_versioned_hashes.unwrap_or_default(),
max_fee_per_blob_gas,
// EIP-7702 fields
authorization_list: authorization_list.map(Into::into),
..Default::default()
};
Ok(env)
}
) -> Result<<Self::Evm as ConfigureEvmEnv>::TxEnv, Self::Error>;
/// Prepares the [`EvmEnv`] for execution of calls.
///
@ -792,7 +734,7 @@ pub trait Call:
mut request: TransactionRequest,
db: &mut CacheDB<DB>,
overrides: EvmOverrides,
) -> Result<(EvmEnv, TxEnv), Self::Error>
) -> Result<(EvmEnv, <Self::Evm as ConfigureEvmEnv>::TxEnv), Self::Error>
where
DB: DatabaseRef,
EthApiError: From<<DB as DatabaseRef>::Error>,
@ -831,12 +773,12 @@ pub trait Call:
if request_gas.is_none() {
// No gas limit was provided in the request, so we need to cap the transaction gas limit
if tx_env.gas_price > U256::ZERO {
if tx_env.gas_price() > U256::ZERO {
// If gas price is specified, cap transaction gas limit with caller allowance
trace!(target: "rpc::eth::call", ?tx_env, "Applying gas limit cap with caller allowance");
let cap = caller_gas_allowance(db, &tx_env)?;
// ensure we cap gas_limit to the block's
tx_env.gas_limit = cap.min(evm_env.block_env.gas_limit).saturating_to();
tx_env.set_gas_limit(cap.min(evm_env.block_env.gas_limit).saturating_to());
}
}

View File

@ -6,19 +6,19 @@ use alloy_primitives::U256;
use alloy_rpc_types_eth::{state::StateOverride, transaction::TransactionRequest, BlockId};
use futures::Future;
use reth_chainspec::MIN_TRANSACTION_GAS;
use reth_evm::env::EvmEnv;
use reth_evm::{env::EvmEnv, ConfigureEvmEnv, TransactionEnv};
use reth_provider::StateProvider;
use reth_revm::{
database::StateProviderDatabase,
db::CacheDB,
primitives::{ExecutionResult, HaltReason, TransactTo},
primitives::{ExecutionResult, HaltReason},
};
use reth_rpc_eth_types::{
revm_utils::{apply_state_overrides, caller_gas_allowance},
EthApiError, RevertError, RpcInvalidTransactionError,
};
use reth_rpc_server_types::constants::gas_oracle::{CALL_STIPEND_GAS, ESTIMATE_GAS_ERROR_RATIO};
use revm_primitives::{db::Database, TxEnv};
use revm_primitives::{db::Database, TxKind};
use tracing::trace;
/// Gas execution estimates
@ -84,8 +84,8 @@ pub trait EstimateCall: Call {
}
// Optimize for simple transfer transactions, potentially reducing the gas estimate.
if tx_env.data.is_empty() {
if let TransactTo::Call(to) = tx_env.transact_to {
if tx_env.input().is_empty() {
if let TxKind::Call(to) = tx_env.kind() {
if let Ok(code) = db.db.account_code(&to) {
let no_code_callee = code.map(|code| code.is_empty()).unwrap_or(true);
if no_code_callee {
@ -95,7 +95,7 @@ pub trait EstimateCall: Call {
// field combos that bump the price up, so we try executing the function
// with the minimum gas limit to make sure.
let mut tx_env = tx_env.clone();
tx_env.gas_limit = MIN_TRANSACTION_GAS;
tx_env.set_gas_limit(MIN_TRANSACTION_GAS);
if let Ok((res, _)) = self.transact(&mut db, evm_env.clone(), tx_env) {
if res.result.is_success() {
return Ok(U256::from(MIN_TRANSACTION_GAS))
@ -109,7 +109,7 @@ pub trait EstimateCall: Call {
// Check funds of the sender (only useful to check if transaction gas price is more than 0).
//
// The caller allowance is check by doing `(account.balance - tx.value) / tx.gas_price`
if tx_env.gas_price > U256::ZERO {
if tx_env.gas_price() > U256::ZERO {
// cap the highest gas limit by max gas caller can afford with given gas price
highest_gas_limit = highest_gas_limit
.min(caller_gas_allowance(&mut db, &tx_env).map_err(Self::Error::from_eth_err)?);
@ -119,7 +119,7 @@ pub trait EstimateCall: Call {
let mut highest_gas_limit = highest_gas_limit.saturating_to::<u64>();
// If the provided gas limit is less than computed cap, use that
tx_env.gas_limit = tx_env.gas_limit.min(highest_gas_limit);
tx_env.set_gas_limit(tx_env.gas_limit().min(highest_gas_limit));
trace!(target: "rpc::eth::estimate", ?evm_env, ?tx_env, "Starting gas estimation");
@ -169,7 +169,7 @@ pub trait EstimateCall: Call {
// we know the tx succeeded with the configured gas limit, so we can use that as the
// highest, in case we applied a gas cap due to caller allowance above
highest_gas_limit = tx_env.gas_limit;
highest_gas_limit = tx_env.gas_limit();
// NOTE: this is the gas the transaction used, which is less than the
// transaction requires to succeed.
@ -186,7 +186,7 @@ pub trait EstimateCall: Call {
let optimistic_gas_limit = (gas_used + gas_refund + CALL_STIPEND_GAS) * 64 / 63;
if optimistic_gas_limit < highest_gas_limit {
// Set the transaction's gas limit to the calculated optimistic gas limit.
tx_env.gas_limit = optimistic_gas_limit;
tx_env.set_gas_limit(optimistic_gas_limit);
// Re-execute the transaction with the new gas limit and update the result and
// environment.
(res, (evm_env, tx_env)) = self.transact(&mut db, evm_env, tx_env)?;
@ -221,7 +221,7 @@ pub trait EstimateCall: Call {
break
};
tx_env.gas_limit = mid_gas_limit;
tx_env.set_gas_limit(mid_gas_limit);
// Execute transaction and handle potential gas errors, adjusting limits accordingly.
match self.transact(&mut db, evm_env.clone(), tx_env.clone()) {
@ -282,15 +282,15 @@ pub trait EstimateCall: Call {
&self,
env_gas_limit: U256,
evm_env: EvmEnv,
mut tx_env: TxEnv,
mut tx_env: <Self::Evm as ConfigureEvmEnv>::TxEnv,
db: &mut DB,
) -> Self::Error
where
DB: Database,
EthApiError: From<DB::Error>,
{
let req_gas_limit = tx_env.gas_limit;
tx_env.gas_limit = env_gas_limit.try_into().unwrap_or(u64::MAX);
let req_gas_limit = tx_env.gas_limit();
tx_env.set_gas_limit(env_gas_limit.try_into().unwrap_or(u64::MAX));
let (res, _) = match self.transact(db, evm_env, tx_env) {
Ok(res) => res,
Err(err) => return err,

View File

@ -18,7 +18,7 @@ use reth_rpc_eth_types::{
};
use revm::{db::CacheDB, Database, DatabaseCommit, GetInspector, Inspector};
use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig};
use revm_primitives::{EvmState, ExecutionResult, ResultAndState, TxEnv};
use revm_primitives::{EvmState, ExecutionResult, ResultAndState};
use std::{fmt::Display, sync::Arc};
/// Executes CPU heavy tasks.
@ -33,13 +33,14 @@ pub trait Trace:
{
/// Executes the [`EvmEnv`] against the given [Database] without committing state
/// changes.
#[expect(clippy::type_complexity)]
fn inspect<DB, I>(
&self,
db: DB,
evm_env: EvmEnv,
tx_env: TxEnv,
tx_env: <Self::Evm as ConfigureEvmEnv>::TxEnv,
inspector: I,
) -> Result<(ResultAndState, (EvmEnv, TxEnv)), Self::Error>
) -> Result<(ResultAndState, (EvmEnv, <Self::Evm as ConfigureEvmEnv>::TxEnv)), Self::Error>
where
DB: Database,
EthApiError: From<DB::Error>,
@ -61,7 +62,7 @@ pub trait Trace:
fn trace_at<F, R>(
&self,
evm_env: EvmEnv,
tx_env: TxEnv,
tx_env: <Self::Evm as ConfigureEvmEnv>::TxEnv,
config: TracingInspectorConfig,
at: BlockId,
f: F,
@ -88,7 +89,7 @@ pub trait Trace:
fn spawn_trace_at_with_state<F, R>(
&self,
evm_env: EvmEnv,
tx_env: TxEnv,
tx_env: <Self::Evm as ConfigureEvmEnv>::TxEnv,
config: TracingInspectorConfig,
at: BlockId,
f: F,