From 2fa37b1ad1b273f1755e5c5c2f8e705b6f9c1f94 Mon Sep 17 00:00:00 2001 From: nk_ysg Date: Tue, 9 Jul 2024 23:57:37 +0800 Subject: [PATCH] Integrate Request evm env filling in ethapi (#9358) Co-authored-by: Emilia Hane --- crates/rpc/rpc-eth-api/src/helpers/call.rs | 173 +++++++++++++++++++- crates/rpc/rpc-eth-types/src/revm_utils.rs | 180 ++------------------- crates/rpc/rpc/src/debug.rs | 4 +- crates/rpc/rpc/src/trace.rs | 3 +- 4 files changed, 182 insertions(+), 178 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index 0379292894cf..932138f3929d 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -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 @@ -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 { + // 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 { + 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( + &self, + mut cfg: CfgEnvWithHandlerCfg, + mut block: BlockEnv, + request: TransactionRequest, + gas_limit: u64, + db: &mut CacheDB, + overrides: EvmOverrides, + ) -> EthResult + where + DB: DatabaseRef, + EthApiError: From<::Error>, + { + // we want to disable this in eth_call, since this is common practice used by other node + // impls and providers + cfg.disable_block_gas_limit = true; + + // Disabled because eth_call is sometimes used with eoa senders + // See + cfg.disable_eip3607 = true; + + // The basefee should be ignored for eth_call + // See: + // + 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: + // + trace!(target: "rpc::eth::call", ?env, "Applying gas limit cap as the maximum gas limit"); + env.tx.gas_limit = gas_limit; + } + } + + Ok(env) + } } diff --git a/crates/rpc/rpc-eth-types/src/revm_utils.rs b/crates/rpc/rpc-eth-types/src/revm_utils.rs index 0903d8056b76..27ab25021acb 100644 --- a/crates/rpc/rpc-eth-types/src/revm_utils.rs +++ b/crates/rpc/rpc-eth-types/src/revm_utils.rs @@ -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 { 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( - mut cfg: CfgEnvWithHandlerCfg, - mut block: BlockEnv, - request: TransactionRequest, - gas_limit: u64, - db: &mut CacheDB, - overrides: EvmOverrides, -) -> EthResult -where - DB: DatabaseRef, - EthApiError: From<::Error>, -{ - // we want to disable this in eth_call, since this is common practice used by other node - // impls and providers - cfg.disable_block_gas_limit = true; - - // Disabled because eth_call is sometimes used with eoa senders - // See - cfg.disable_eip3607 = true; - - // The basefee should be ignored for eth_call - // See: - // - 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: - // - 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 { - 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 { - // 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: &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, + pub max_priority_fee_per_gas: Option, /// 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, + pub max_fee_per_blob_gas: Option, } // === 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, call_max_fee: Option, call_priority_fee: Option, @@ -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, diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 8e04c6256661..847ab6ae5a33 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -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, diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index ff98194b91fb..fd0174a4e174 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -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,