Integrate Request evm env filling in ethapi (#9358)

Co-authored-by: Emilia Hane <elsaemiliaevahane@gmail.com>
This commit is contained in:
nk_ysg
2024-07-09 23:57:37 +08:00
committed by GitHub
parent e97e379e43
commit 2fa37b1ad1
4 changed files with 182 additions and 178 deletions

View File

@ -6,7 +6,7 @@ use reth_evm::{ConfigureEvm, ConfigureEvmEnv};
use reth_primitives::{
revm_primitives::{
BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, HaltReason,
ResultAndState, TransactTo,
ResultAndState, TransactTo, TxEnv,
},
Bytes, TransactionSignedEcRecovered, TxKind, B256, U256,
};
@ -16,8 +16,8 @@ use reth_rpc_eth_types::{
cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper},
error::ensure_success,
revm_utils::{
apply_state_overrides, build_call_evm_env, caller_gas_allowance,
cap_tx_gas_limit_with_caller_allowance, get_precompiles, prepare_call_env,
apply_block_overrides, apply_state_overrides, caller_gas_allowance,
cap_tx_gas_limit_with_caller_allowance, get_precompiles, CallFees,
},
EthApiError, EthResult, RevertError, RpcInvalidTransactionError, StateCacheDb,
};
@ -29,6 +29,8 @@ use reth_rpc_types::{
};
use revm::{Database, DatabaseCommit};
use revm_inspectors::access_list::AccessListInspector;
#[cfg(feature = "optimism")]
use revm_primitives::OptimismFields;
use tracing::trace;
use super::{LoadBlock, LoadPendingBlock, LoadState, LoadTransaction, SpawnBlocking, Trace};
@ -136,7 +138,7 @@ pub trait EthCall: Call + LoadPendingBlock {
let state_overrides = state_override.take();
let overrides = EvmOverrides::new(state_overrides, block_overrides.clone());
let env = prepare_call_env(
let env = this.prepare_call_env(
cfg.clone(),
block_env.clone(),
tx,
@ -206,7 +208,7 @@ pub trait EthCall: Call + LoadPendingBlock {
{
let state = self.state_at_block_id(at)?;
let mut env = build_call_evm_env(cfg, block, request.clone())?;
let mut env = self.build_call_evm_env(cfg, block, request.clone())?;
// we want to disable this in eth_createAccessList, since this is common practice used by
// other node impls and providers <https://github.com/foundry-rs/foundry/issues/4388>
@ -359,7 +361,7 @@ pub trait Call: LoadState + SpawnBlocking {
let mut db =
CacheDB::new(StateProviderDatabase::new(StateProviderTraitObjWrapper(&state)));
let env = prepare_call_env(
let env = this.prepare_call_env(
cfg,
block_env,
request,
@ -530,7 +532,7 @@ pub trait Call: LoadState + SpawnBlocking {
.unwrap_or(block_env_gas_limit);
// Configure the evm env
let mut env = build_call_evm_env(cfg, block, request)?;
let mut env = self.build_call_evm_env(cfg, block, request)?;
let mut db = CacheDB::new(StateProviderDatabase::new(state));
// Apply any state overrides if specified.
@ -776,4 +778,161 @@ pub trait Call: LoadState + SpawnBlocking {
}
}
}
/// Configures a new [`TxEnv`] for the [`TransactionRequest`]
///
/// 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,
) -> EthResult<TxEnv> {
// Ensure that if versioned hashes are set, they're not empty
if request.blob_versioned_hashes.as_ref().map_or(false, |hashes| hashes.is_empty()) {
return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into())
}
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,
..
} = 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(|| block_env.gas_limit.min(U256::from(u64::MAX)).to());
let env = TxEnv {
gas_limit: gas_limit
.try_into()
.map_err(|_| RpcInvalidTransactionError::GasUintOverflow)?,
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()?.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,
authorization_list: None,
#[cfg(feature = "optimism")]
optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() },
};
Ok(env)
}
/// Creates a new [`EnvWithHandlerCfg`] to be used for executing the [`TransactionRequest`] in
/// `eth_call`.
///
/// Note: this does _not_ access the Database to check the sender.
fn build_call_evm_env(
&self,
cfg: CfgEnvWithHandlerCfg,
block: BlockEnv,
request: TransactionRequest,
) -> EthResult<EnvWithHandlerCfg> {
let tx = self.create_txn_env(&block, request)?;
Ok(EnvWithHandlerCfg::new_with_cfg_env(cfg, block, tx))
}
/// Prepares the [`EnvWithHandlerCfg`] for execution.
///
/// Does not commit any changes to the underlying database.
///
/// EVM settings:
/// - `disable_block_gas_limit` is set to `true`
/// - `disable_eip3607` is set to `true`
/// - `disable_base_fee` is set to `true`
/// - `nonce` is set to `None`
fn prepare_call_env<DB>(
&self,
mut cfg: CfgEnvWithHandlerCfg,
mut block: BlockEnv,
request: TransactionRequest,
gas_limit: u64,
db: &mut CacheDB<DB>,
overrides: EvmOverrides,
) -> EthResult<EnvWithHandlerCfg>
where
DB: DatabaseRef,
EthApiError: From<<DB as DatabaseRef>::Error>,
{
// we want to disable this in eth_call, since this is common practice used by other node
// impls and providers <https://github.com/foundry-rs/foundry/issues/4388>
cfg.disable_block_gas_limit = true;
// Disabled because eth_call is sometimes used with eoa senders
// See <https://github.com/paradigmxyz/reth/issues/1959>
cfg.disable_eip3607 = true;
// The basefee should be ignored for eth_call
// See:
// <https://github.com/ethereum/go-ethereum/blob/ee8e83fa5f6cb261dad2ed0a7bbcde4930c41e6c/internal/ethapi/api.go#L985>
cfg.disable_base_fee = true;
// apply block overrides, we need to apply them first so that they take effect when we we
// create the evm env via `build_call_evm_env`, e.g. basefee
if let Some(mut block_overrides) = overrides.block {
if let Some(block_hashes) = block_overrides.block_hash.take() {
// override block hashes
db.block_hashes
.extend(block_hashes.into_iter().map(|(num, hash)| (U256::from(num), hash)))
}
apply_block_overrides(*block_overrides, &mut block);
}
let request_gas = request.gas;
let mut env = self.build_call_evm_env(cfg, block, request)?;
// set nonce to None so that the next nonce is used when transacting the call
env.tx.nonce = None;
// apply state overrides
if let Some(state_overrides) = overrides.state {
apply_state_overrides(state_overrides, db)?;
}
if request_gas.is_none() {
// No gas limit was provided in the request, so we need to cap the transaction gas limit
if env.tx.gas_price > U256::ZERO {
// If gas price is specified, cap transaction gas limit with caller allowance
trace!(target: "rpc::eth::call", ?env, "Applying gas limit cap with caller allowance");
cap_tx_gas_limit_with_caller_allowance(db, &mut env.tx)?;
} else {
// If no gas price is specified, 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>
trace!(target: "rpc::eth::call", ?env, "Applying gas limit cap as the maximum gas limit");
env.tx.gas_limit = gas_limit;
}
}
Ok(env)
}
}

View File

@ -1,23 +1,18 @@
//! utilities for working with revm
use std::cmp::min;
use reth_primitives::{Address, TxKind, B256, U256};
use reth_primitives::{Address, B256, U256};
use reth_rpc_types::{
state::{AccountOverride, EvmOverrides, StateOverride},
BlockOverrides, TransactionRequest,
state::{AccountOverride, StateOverride},
BlockOverrides,
};
#[cfg(feature = "optimism")]
use revm::primitives::{Bytes, OptimismFields};
use revm::{
db::CacheDB,
precompile::{PrecompileSpecId, Precompiles},
primitives::{
db::DatabaseRef, BlockEnv, Bytecode, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv,
},
primitives::{db::DatabaseRef, Bytecode, SpecId, TxEnv},
Database,
};
use tracing::trace;
use revm_primitives::BlockEnv;
use std::cmp::min;
use super::{EthApiError, EthResult, RpcInvalidTransactionError};
@ -28,155 +23,6 @@ pub fn get_precompiles(spec_id: SpecId) -> impl IntoIterator<Item = Address> {
Precompiles::new(spec).addresses().copied().map(Address::from)
}
/// Prepares the [`EnvWithHandlerCfg`] for execution.
///
/// Does not commit any changes to the underlying database.
///
/// EVM settings:
/// - `disable_block_gas_limit` is set to `true`
/// - `disable_eip3607` is set to `true`
/// - `disable_base_fee` is set to `true`
/// - `nonce` is set to `None`
pub fn prepare_call_env<DB>(
mut cfg: CfgEnvWithHandlerCfg,
mut block: BlockEnv,
request: TransactionRequest,
gas_limit: u64,
db: &mut CacheDB<DB>,
overrides: EvmOverrides,
) -> EthResult<EnvWithHandlerCfg>
where
DB: DatabaseRef,
EthApiError: From<<DB as DatabaseRef>::Error>,
{
// we want to disable this in eth_call, since this is common practice used by other node
// impls and providers <https://github.com/foundry-rs/foundry/issues/4388>
cfg.disable_block_gas_limit = true;
// Disabled because eth_call is sometimes used with eoa senders
// See <https://github.com/paradigmxyz/reth/issues/1959>
cfg.disable_eip3607 = true;
// The basefee should be ignored for eth_call
// See:
// <https://github.com/ethereum/go-ethereum/blob/ee8e83fa5f6cb261dad2ed0a7bbcde4930c41e6c/internal/ethapi/api.go#L985>
cfg.disable_base_fee = true;
// apply block overrides, we need to apply them first so that they take effect when we we create
// the evm env via `build_call_evm_env`, e.g. basefee
if let Some(mut block_overrides) = overrides.block {
if let Some(block_hashes) = block_overrides.block_hash.take() {
// override block hashes
db.block_hashes
.extend(block_hashes.into_iter().map(|(num, hash)| (U256::from(num), hash)))
}
apply_block_overrides(*block_overrides, &mut block);
}
let request_gas = request.gas;
let mut env = build_call_evm_env(cfg, block, request)?;
// set nonce to None so that the next nonce is used when transacting the call
env.tx.nonce = None;
// apply state overrides
if let Some(state_overrides) = overrides.state {
apply_state_overrides(state_overrides, db)?;
}
if request_gas.is_none() {
// No gas limit was provided in the request, so we need to cap the transaction gas limit
if env.tx.gas_price > U256::ZERO {
// If gas price is specified, cap transaction gas limit with caller allowance
trace!(target: "rpc::eth::call", ?env, "Applying gas limit cap with caller allowance");
cap_tx_gas_limit_with_caller_allowance(db, &mut env.tx)?;
} else {
// If no gas price is specified, 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>
trace!(target: "rpc::eth::call", ?env, "Applying gas limit cap as the maximum gas limit");
env.tx.gas_limit = gas_limit;
}
}
Ok(env)
}
/// Creates a new [`EnvWithHandlerCfg`] to be used for executing the [`TransactionRequest`] in
/// `eth_call`.
///
/// Note: this does _not_ access the Database to check the sender.
pub fn build_call_evm_env(
cfg: CfgEnvWithHandlerCfg,
block: BlockEnv,
request: TransactionRequest,
) -> EthResult<EnvWithHandlerCfg> {
let tx = create_txn_env(&block, request)?;
Ok(EnvWithHandlerCfg::new_with_cfg_env(cfg, block, tx))
}
/// Configures a new [`TxEnv`] for the [`TransactionRequest`]
///
/// All [`TxEnv`] fields are derived from the given [`TransactionRequest`], if fields are `None`,
/// they fall back to the [`BlockEnv`]'s settings.
pub fn create_txn_env(block_env: &BlockEnv, request: TransactionRequest) -> EthResult<TxEnv> {
// Ensure that if versioned hashes are set, they're not empty
if request.blob_versioned_hashes.as_ref().map_or(false, |hashes| hashes.is_empty()) {
return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into())
}
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,
..
} = 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(|| block_env.gas_limit.min(U256::from(u64::MAX)).to());
let env = TxEnv {
gas_limit: gas_limit.try_into().map_err(|_| RpcInvalidTransactionError::GasUintOverflow)?,
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()?.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,
authorization_list: None,
#[cfg(feature = "optimism")]
optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() },
};
Ok(env)
}
/// Caps the configured [`TxEnv`] `gas_limit` with the allowance of the caller.
pub fn cap_tx_gas_limit_with_caller_allowance<DB>(db: &mut DB, env: &mut TxEnv) -> EthResult<()>
where
@ -217,26 +63,26 @@ where
.unwrap_or_default())
}
/// Helper type for representing the fees of a [`TransactionRequest`]
/// Helper type for representing the fees of a [`reth_rpc_types::TransactionRequest`]
#[derive(Debug)]
pub struct CallFees {
/// EIP-1559 priority fee
max_priority_fee_per_gas: Option<U256>,
pub max_priority_fee_per_gas: Option<U256>,
/// Unified gas price setting
///
/// Will be the configured `basefee` if unset in the request
///
/// `gasPrice` for legacy,
/// `maxFeePerGas` for EIP-1559
gas_price: U256,
pub gas_price: U256,
/// Max Fee per Blob gas for EIP-4844 transactions
max_fee_per_blob_gas: Option<U256>,
pub max_fee_per_blob_gas: Option<U256>,
}
// === impl CallFees ===
impl CallFees {
/// Ensures the fields of a [`TransactionRequest`] are not conflicting.
/// Ensures the fields of a [`reth_rpc_types::TransactionRequest`] are not conflicting.
///
/// # EIP-4844 transactions
///
@ -246,7 +92,7 @@ impl CallFees {
///
/// Note: Due to the `Default` impl of [`BlockEnv`] (Some(0)) this assumes the `block_blob_fee`
/// is always `Some`
fn ensure_fees(
pub fn ensure_fees(
call_gas_price: Option<U256>,
call_max_fee: Option<U256>,
call_priority_fee: Option<U256>,
@ -346,7 +192,7 @@ impl CallFees {
}
/// Applies the given block overrides to the env
fn apply_block_overrides(overrides: BlockOverrides, env: &mut BlockEnv) {
pub fn apply_block_overrides(overrides: BlockOverrides, env: &mut BlockEnv) {
let BlockOverrides {
number,
difficulty,

View File

@ -15,7 +15,7 @@ use reth_provider::{
use reth_revm::database::StateProviderDatabase;
use reth_rpc_api::DebugApiServer;
use reth_rpc_eth_api::helpers::{Call, EthApiSpec, EthTransactions, TraceExt};
use reth_rpc_eth_types::{revm_utils::prepare_call_env, EthApiError, EthResult, StateCacheDb};
use reth_rpc_eth_types::{EthApiError, EthResult, StateCacheDb};
use reth_rpc_server_types::{result::internal_rpc_err, ToRpcResult};
use reth_rpc_types::{
state::EvmOverrides,
@ -503,7 +503,7 @@ where
let state_overrides = state_overrides.take();
let overrides = EvmOverrides::new(state_overrides, block_overrides.clone());
let env = prepare_call_env(
let env = this.eth_api().prepare_call_env(
cfg.clone(),
block_env.clone(),
tx,

View File

@ -14,7 +14,6 @@ use reth_rpc_api::TraceApiServer;
use reth_rpc_eth_api::helpers::{Call, TraceExt};
use reth_rpc_eth_types::{
error::{EthApiError, EthResult},
revm_utils::prepare_call_env,
utils::recover_raw_transaction,
};
use reth_rpc_types::{
@ -158,7 +157,7 @@ where
let mut calls = calls.into_iter().peekable();
while let Some((call, trace_types)) = calls.next() {
let env = prepare_call_env(
let env = this.eth_api().prepare_call_env(
cfg.clone(),
block_env.clone(),
call,