From 0f1b5242acbd1f52263a4248671159e317259b9d Mon Sep 17 00:00:00 2001 From: nk_ysg Date: Sun, 7 Jul 2024 17:29:07 +0800 Subject: [PATCH 1/7] Integrate Request evm env filling in ethapi --- crates/rpc/rpc-eth-api/src/helpers/call.rs | 445 ++++++++++++++++++++- crates/rpc/rpc-eth-types/src/revm_utils.rs | 438 +------------------- crates/rpc/rpc/src/debug.rs | 4 +- crates/rpc/rpc/src/trace.rs | 3 +- 4 files changed, 442 insertions(+), 448 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index 5f6aebaa85b3..762c354adefa 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,19 +16,22 @@ 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_state_overrides, caller_gas_allowance, cap_tx_gas_limit_with_caller_allowance, + get_precompiles, }, EthApiError, EthResult, RevertError, RpcInvalidTransactionError, StateCacheDb, }; use reth_rpc_server_types::constants::gas_oracle::{ESTIMATE_GAS_ERROR_RATIO, MIN_TRANSACTION_GAS}; use reth_rpc_types::{ state::{EvmOverrides, StateOverride}, - AccessListWithGasUsed, BlockId, Bundle, EthCallResponse, StateContext, TransactionInfo, - TransactionRequest, + AccessListWithGasUsed, BlockId, BlockOverrides, Bundle, EthCallResponse, StateContext, + TransactionInfo, TransactionRequest, }; use revm::{Database, DatabaseCommit}; use revm_inspectors::access_list::AccessListInspector; +#[cfg(feature = "optimism")] +use revm_primitives::OptimismFields; +use std::cmp::min; use tracing::trace; use super::{LoadBlock, LoadPendingBlock, LoadState, LoadTransaction, SpawnBlocking, Trace}; @@ -136,7 +139,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 +209,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 +362,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 +533,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 +779,428 @@ pub trait Call: LoadState + SpawnBlocking { } } } + + /// 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( + cfg: CfgEnvWithHandlerCfg, + block: BlockEnv, + request: TransactionRequest, + ) -> EthResult { + /// 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(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 + .map(reth_rpc_types::AccessList::into_flattened) + .unwrap_or_default(), + // EIP-4844 fields + blob_hashes: blob_versioned_hashes.unwrap_or_default(), + max_fee_per_blob_gas, + #[cfg(feature = "optimism")] + optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() }, + }; + + Ok(env) + } + let tx = 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>, + { + /// Applies the given block overrides to the env + fn apply_block_overrides(overrides: BlockOverrides, env: &mut BlockEnv) { + let BlockOverrides { + number, + difficulty, + time, + gas_limit, + coinbase, + random, + base_fee, + block_hash: _, + } = overrides; + + if let Some(number) = number { + env.number = number; + } + if let Some(difficulty) = difficulty { + env.difficulty = difficulty; + } + if let Some(time) = time { + env.timestamp = U256::from(time); + } + if let Some(gas_limit) = gas_limit { + env.gas_limit = U256::from(gas_limit); + } + if let Some(coinbase) = coinbase { + env.coinbase = coinbase; + } + if let Some(random) = random { + env.prevrandao = Some(random); + } + if let Some(base_fee) = base_fee { + env.basefee = base_fee; + } + } + // 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) + } +} + +/// Helper type for representing the fees of a [`TransactionRequest`] +#[derive(Debug)] +pub struct CallFees { + /// EIP-1559 priority fee + 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, + /// Max Fee per Blob gas for EIP-4844 transactions + max_fee_per_blob_gas: Option, +} + +// === impl CallFees === + +impl CallFees { + /// Ensures the fields of a [`TransactionRequest`] are not conflicting. + /// + /// # EIP-4844 transactions + /// + /// Blob transactions have an additional fee parameter `maxFeePerBlobGas`. + /// If the `maxFeePerBlobGas` or `blobVersionedHashes` are set we treat it as an EIP-4844 + /// transaction. + /// + /// Note: Due to the `Default` impl of [`BlockEnv`] (Some(0)) this assumes the `block_blob_fee` + /// is always `Some` + fn ensure_fees( + call_gas_price: Option, + call_max_fee: Option, + call_priority_fee: Option, + block_base_fee: U256, + blob_versioned_hashes: Option<&[B256]>, + max_fee_per_blob_gas: Option, + block_blob_fee: Option, + ) -> EthResult { + /// Get the effective gas price of a transaction as specfified in EIP-1559 with relevant + /// checks. + fn get_effective_gas_price( + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + block_base_fee: U256, + ) -> EthResult { + match max_fee_per_gas { + Some(max_fee) => { + if max_fee < block_base_fee { + // `base_fee_per_gas` is greater than the `max_fee_per_gas` + return Err(RpcInvalidTransactionError::FeeCapTooLow.into()) + } + if max_fee < max_priority_fee_per_gas.unwrap_or(U256::ZERO) { + return Err( + // `max_priority_fee_per_gas` is greater than the `max_fee_per_gas` + RpcInvalidTransactionError::TipAboveFeeCap.into(), + ) + } + Ok(min( + max_fee, + block_base_fee + .checked_add(max_priority_fee_per_gas.unwrap_or(U256::ZERO)) + .ok_or_else(|| { + EthApiError::from(RpcInvalidTransactionError::TipVeryHigh) + })?, + )) + } + None => Ok(block_base_fee + .checked_add(max_priority_fee_per_gas.unwrap_or(U256::ZERO)) + .ok_or_else(|| EthApiError::from(RpcInvalidTransactionError::TipVeryHigh))?), + } + } + + let has_blob_hashes = + blob_versioned_hashes.as_ref().map(|blobs| !blobs.is_empty()).unwrap_or(false); + + match (call_gas_price, call_max_fee, call_priority_fee, max_fee_per_blob_gas) { + (gas_price, None, None, None) => { + // either legacy transaction or no fee fields are specified + // when no fields are specified, set gas price to zero + let gas_price = gas_price.unwrap_or(U256::ZERO); + Ok(Self { + gas_price, + max_priority_fee_per_gas: None, + max_fee_per_blob_gas: has_blob_hashes.then_some(block_blob_fee).flatten(), + }) + } + (None, max_fee_per_gas, max_priority_fee_per_gas, None) => { + // request for eip-1559 transaction + let effective_gas_price = get_effective_gas_price( + max_fee_per_gas, + max_priority_fee_per_gas, + block_base_fee, + )?; + let max_fee_per_blob_gas = has_blob_hashes.then_some(block_blob_fee).flatten(); + + Ok(Self { + gas_price: effective_gas_price, + max_priority_fee_per_gas, + max_fee_per_blob_gas, + }) + } + (None, max_fee_per_gas, max_priority_fee_per_gas, Some(max_fee_per_blob_gas)) => { + // request for eip-4844 transaction + let effective_gas_price = get_effective_gas_price( + max_fee_per_gas, + max_priority_fee_per_gas, + block_base_fee, + )?; + // Ensure blob_hashes are present + if !has_blob_hashes { + // Blob transaction but no blob hashes + return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into()) + } + + Ok(Self { + gas_price: effective_gas_price, + max_priority_fee_per_gas, + max_fee_per_blob_gas: Some(max_fee_per_blob_gas), + }) + } + _ => { + // this fallback covers incompatible combinations of fields + Err(EthApiError::ConflictingFeeFieldsInRequest) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use reth_primitives::constants::GWEI_TO_WEI; + + #[test] + fn test_ensure_0_fallback() { + let CallFees { gas_price, .. } = + CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO)) + .unwrap(); + assert!(gas_price.is_zero()); + } + + #[test] + fn test_blob_fees() { + let CallFees { gas_price, max_fee_per_blob_gas, .. } = + CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO)) + .unwrap(); + assert!(gas_price.is_zero()); + assert_eq!(max_fee_per_blob_gas, None); + + let CallFees { gas_price, max_fee_per_blob_gas, .. } = CallFees::ensure_fees( + None, + None, + None, + U256::from(99), + Some(&[B256::from(U256::ZERO)]), + None, + Some(U256::from(99)), + ) + .unwrap(); + assert!(gas_price.is_zero()); + assert_eq!(max_fee_per_blob_gas, Some(U256::from(99))); + } + + #[test] + fn test_eip_1559_fees() { + let CallFees { gas_price, .. } = CallFees::ensure_fees( + None, + Some(U256::from(25 * GWEI_TO_WEI)), + Some(U256::from(15 * GWEI_TO_WEI)), + U256::from(15 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ) + .unwrap(); + assert_eq!(gas_price, U256::from(25 * GWEI_TO_WEI)); + + let CallFees { gas_price, .. } = CallFees::ensure_fees( + None, + Some(U256::from(25 * GWEI_TO_WEI)), + Some(U256::from(5 * GWEI_TO_WEI)), + U256::from(15 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ) + .unwrap(); + assert_eq!(gas_price, U256::from(20 * GWEI_TO_WEI)); + + let CallFees { gas_price, .. } = CallFees::ensure_fees( + None, + Some(U256::from(30 * GWEI_TO_WEI)), + Some(U256::from(30 * GWEI_TO_WEI)), + U256::from(15 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ) + .unwrap(); + assert_eq!(gas_price, U256::from(30 * GWEI_TO_WEI)); + + let call_fees = CallFees::ensure_fees( + None, + Some(U256::from(30 * GWEI_TO_WEI)), + Some(U256::from(31 * GWEI_TO_WEI)), + U256::from(15 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ); + assert!(call_fees.is_err()); + + let call_fees = CallFees::ensure_fees( + None, + Some(U256::from(5 * GWEI_TO_WEI)), + Some(U256::from(GWEI_TO_WEI)), + U256::from(15 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ); + assert!(call_fees.is_err()); + + let call_fees = CallFees::ensure_fees( + None, + Some(U256::MAX), + Some(U256::MAX), + U256::from(5 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ); + assert!(call_fees.is_err()); + } } diff --git a/crates/rpc/rpc-eth-types/src/revm_utils.rs b/crates/rpc/rpc-eth-types/src/revm_utils.rs index 6b30de26c4da..0d8b7b04b2b2 100644 --- a/crates/rpc/rpc-eth-types/src/revm_utils.rs +++ b/crates/rpc/rpc-eth-types/src/revm_utils.rs @@ -1,23 +1,13 @@ //! utilities for working with revm -use std::cmp::min; - -use reth_primitives::{Address, TxKind, B256, U256}; -use reth_rpc_types::{ - state::{AccountOverride, EvmOverrides, StateOverride}, - BlockOverrides, TransactionRequest, -}; -#[cfg(feature = "optimism")] -use revm::primitives::{Bytes, OptimismFields}; +use reth_primitives::{Address, U256}; +use reth_rpc_types::state::{AccountOverride, StateOverride}; 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 super::{EthApiError, EthResult, RpcInvalidTransactionError}; @@ -28,156 +18,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 - .map(reth_rpc_types::AccessList::into_flattened) - .unwrap_or_default(), - // EIP-4844 fields - blob_hashes: blob_versioned_hashes.unwrap_or_default(), - max_fee_per_blob_gas, - #[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 @@ -218,170 +58,6 @@ where .unwrap_or_default()) } -/// Helper type for representing the fees of a [`TransactionRequest`] -#[derive(Debug)] -pub struct CallFees { - /// EIP-1559 priority fee - 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, - /// Max Fee per Blob gas for EIP-4844 transactions - max_fee_per_blob_gas: Option, -} - -// === impl CallFees === - -impl CallFees { - /// Ensures the fields of a [`TransactionRequest`] are not conflicting. - /// - /// # EIP-4844 transactions - /// - /// Blob transactions have an additional fee parameter `maxFeePerBlobGas`. - /// If the `maxFeePerBlobGas` or `blobVersionedHashes` are set we treat it as an EIP-4844 - /// transaction. - /// - /// Note: Due to the `Default` impl of [`BlockEnv`] (Some(0)) this assumes the `block_blob_fee` - /// is always `Some` - fn ensure_fees( - call_gas_price: Option, - call_max_fee: Option, - call_priority_fee: Option, - block_base_fee: U256, - blob_versioned_hashes: Option<&[B256]>, - max_fee_per_blob_gas: Option, - block_blob_fee: Option, - ) -> EthResult { - /// Get the effective gas price of a transaction as specfified in EIP-1559 with relevant - /// checks. - fn get_effective_gas_price( - max_fee_per_gas: Option, - max_priority_fee_per_gas: Option, - block_base_fee: U256, - ) -> EthResult { - match max_fee_per_gas { - Some(max_fee) => { - if max_fee < block_base_fee { - // `base_fee_per_gas` is greater than the `max_fee_per_gas` - return Err(RpcInvalidTransactionError::FeeCapTooLow.into()) - } - if max_fee < max_priority_fee_per_gas.unwrap_or(U256::ZERO) { - return Err( - // `max_priority_fee_per_gas` is greater than the `max_fee_per_gas` - RpcInvalidTransactionError::TipAboveFeeCap.into(), - ) - } - Ok(min( - max_fee, - block_base_fee - .checked_add(max_priority_fee_per_gas.unwrap_or(U256::ZERO)) - .ok_or_else(|| { - EthApiError::from(RpcInvalidTransactionError::TipVeryHigh) - })?, - )) - } - None => Ok(block_base_fee - .checked_add(max_priority_fee_per_gas.unwrap_or(U256::ZERO)) - .ok_or_else(|| EthApiError::from(RpcInvalidTransactionError::TipVeryHigh))?), - } - } - - let has_blob_hashes = - blob_versioned_hashes.as_ref().map(|blobs| !blobs.is_empty()).unwrap_or(false); - - match (call_gas_price, call_max_fee, call_priority_fee, max_fee_per_blob_gas) { - (gas_price, None, None, None) => { - // either legacy transaction or no fee fields are specified - // when no fields are specified, set gas price to zero - let gas_price = gas_price.unwrap_or(U256::ZERO); - Ok(Self { - gas_price, - max_priority_fee_per_gas: None, - max_fee_per_blob_gas: has_blob_hashes.then_some(block_blob_fee).flatten(), - }) - } - (None, max_fee_per_gas, max_priority_fee_per_gas, None) => { - // request for eip-1559 transaction - let effective_gas_price = get_effective_gas_price( - max_fee_per_gas, - max_priority_fee_per_gas, - block_base_fee, - )?; - let max_fee_per_blob_gas = has_blob_hashes.then_some(block_blob_fee).flatten(); - - Ok(Self { - gas_price: effective_gas_price, - max_priority_fee_per_gas, - max_fee_per_blob_gas, - }) - } - (None, max_fee_per_gas, max_priority_fee_per_gas, Some(max_fee_per_blob_gas)) => { - // request for eip-4844 transaction - let effective_gas_price = get_effective_gas_price( - max_fee_per_gas, - max_priority_fee_per_gas, - block_base_fee, - )?; - // Ensure blob_hashes are present - if !has_blob_hashes { - // Blob transaction but no blob hashes - return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into()) - } - - Ok(Self { - gas_price: effective_gas_price, - max_priority_fee_per_gas, - max_fee_per_blob_gas: Some(max_fee_per_blob_gas), - }) - } - _ => { - // this fallback covers incompatible combinations of fields - Err(EthApiError::ConflictingFeeFieldsInRequest) - } - } - } -} - -/// Applies the given block overrides to the env -fn apply_block_overrides(overrides: BlockOverrides, env: &mut BlockEnv) { - let BlockOverrides { - number, - difficulty, - time, - gas_limit, - coinbase, - random, - base_fee, - block_hash: _, - } = overrides; - - if let Some(number) = number { - env.number = number; - } - if let Some(difficulty) = difficulty { - env.difficulty = difficulty; - } - if let Some(time) = time { - env.timestamp = U256::from(time); - } - if let Some(gas_limit) = gas_limit { - env.gas_limit = U256::from(gas_limit); - } - if let Some(coinbase) = coinbase { - env.coinbase = coinbase; - } - if let Some(random) = random { - env.prevrandao = Some(random); - } - if let Some(base_fee) = base_fee { - env.basefee = base_fee; - } -} - /// Applies the given state overrides (a set of [`AccountOverride`]) to the [`CacheDB`]. pub fn apply_state_overrides(overrides: StateOverride, db: &mut CacheDB) -> EthResult<()> where @@ -452,111 +128,3 @@ where Ok(()) } - -#[cfg(test)] -mod tests { - use super::*; - use reth_primitives::constants::GWEI_TO_WEI; - - #[test] - fn test_ensure_0_fallback() { - let CallFees { gas_price, .. } = - CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO)) - .unwrap(); - assert!(gas_price.is_zero()); - } - - #[test] - fn test_blob_fees() { - let CallFees { gas_price, max_fee_per_blob_gas, .. } = - CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO)) - .unwrap(); - assert!(gas_price.is_zero()); - assert_eq!(max_fee_per_blob_gas, None); - - let CallFees { gas_price, max_fee_per_blob_gas, .. } = CallFees::ensure_fees( - None, - None, - None, - U256::from(99), - Some(&[B256::from(U256::ZERO)]), - None, - Some(U256::from(99)), - ) - .unwrap(); - assert!(gas_price.is_zero()); - assert_eq!(max_fee_per_blob_gas, Some(U256::from(99))); - } - - #[test] - fn test_eip_1559_fees() { - let CallFees { gas_price, .. } = CallFees::ensure_fees( - None, - Some(U256::from(25 * GWEI_TO_WEI)), - Some(U256::from(15 * GWEI_TO_WEI)), - U256::from(15 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ) - .unwrap(); - assert_eq!(gas_price, U256::from(25 * GWEI_TO_WEI)); - - let CallFees { gas_price, .. } = CallFees::ensure_fees( - None, - Some(U256::from(25 * GWEI_TO_WEI)), - Some(U256::from(5 * GWEI_TO_WEI)), - U256::from(15 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ) - .unwrap(); - assert_eq!(gas_price, U256::from(20 * GWEI_TO_WEI)); - - let CallFees { gas_price, .. } = CallFees::ensure_fees( - None, - Some(U256::from(30 * GWEI_TO_WEI)), - Some(U256::from(30 * GWEI_TO_WEI)), - U256::from(15 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ) - .unwrap(); - assert_eq!(gas_price, U256::from(30 * GWEI_TO_WEI)); - - let call_fees = CallFees::ensure_fees( - None, - Some(U256::from(30 * GWEI_TO_WEI)), - Some(U256::from(31 * GWEI_TO_WEI)), - U256::from(15 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ); - assert!(call_fees.is_err()); - - let call_fees = CallFees::ensure_fees( - None, - Some(U256::from(5 * GWEI_TO_WEI)), - Some(U256::from(GWEI_TO_WEI)), - U256::from(15 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ); - assert!(call_fees.is_err()); - - let call_fees = CallFees::ensure_fees( - None, - Some(U256::MAX), - Some(U256::MAX), - U256::from(5 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ); - assert!(call_fees.is_err()); - } -} diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 67363dff31c1..e96f3273e5a2 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -16,7 +16,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, @@ -504,7 +504,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, From da68223de61903d9e252480142a271ea424de91e Mon Sep 17 00:00:00 2001 From: nk_ysg Date: Mon, 8 Jul 2024 22:24:46 +0800 Subject: [PATCH 2/7] Fixed build_call_evm_env --- crates/rpc/rpc-eth-api/src/helpers/call.rs | 78 +++++++++++----------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index 762c354adefa..b4948785d736 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -209,7 +209,7 @@ pub trait EthCall: Call + LoadPendingBlock { { let state = self.state_at_block_id(at)?; - let mut env = Self::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 @@ -533,7 +533,7 @@ pub trait Call: LoadState + SpawnBlocking { .unwrap_or(block_env_gas_limit); // Configure the evm env - let mut env = Self::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. @@ -785,6 +785,7 @@ pub trait Call: LoadState + SpawnBlocking { /// /// Note: this does _not_ access the Database to check the sender. fn build_call_evm_env( + &self, cfg: CfgEnvWithHandlerCfg, block: BlockEnv, request: TransactionRequest, @@ -879,41 +880,6 @@ pub trait Call: LoadState + SpawnBlocking { DB: DatabaseRef, EthApiError: From<::Error>, { - /// Applies the given block overrides to the env - fn apply_block_overrides(overrides: BlockOverrides, env: &mut BlockEnv) { - let BlockOverrides { - number, - difficulty, - time, - gas_limit, - coinbase, - random, - base_fee, - block_hash: _, - } = overrides; - - if let Some(number) = number { - env.number = number; - } - if let Some(difficulty) = difficulty { - env.difficulty = difficulty; - } - if let Some(time) = time { - env.timestamp = U256::from(time); - } - if let Some(gas_limit) = gas_limit { - env.gas_limit = U256::from(gas_limit); - } - if let Some(coinbase) = coinbase { - env.coinbase = coinbase; - } - if let Some(random) = random { - env.prevrandao = Some(random); - } - if let Some(base_fee) = base_fee { - env.basefee = base_fee; - } - } // 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; @@ -939,7 +905,7 @@ pub trait Call: LoadState + SpawnBlocking { } let request_gas = request.gas; - let mut env = Self::build_call_evm_env(cfg, block, request)?; + 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; @@ -1097,6 +1063,42 @@ impl CallFees { } } +/// Applies the given block overrides to the env +fn apply_block_overrides(overrides: BlockOverrides, env: &mut BlockEnv) { + let BlockOverrides { + number, + difficulty, + time, + gas_limit, + coinbase, + random, + base_fee, + block_hash: _, + } = overrides; + + if let Some(number) = number { + env.number = number; + } + if let Some(difficulty) = difficulty { + env.difficulty = difficulty; + } + if let Some(time) = time { + env.timestamp = U256::from(time); + } + if let Some(gas_limit) = gas_limit { + env.gas_limit = U256::from(gas_limit); + } + if let Some(coinbase) = coinbase { + env.coinbase = coinbase; + } + if let Some(random) = random { + env.prevrandao = Some(random); + } + if let Some(base_fee) = base_fee { + env.basefee = base_fee; + } +} + #[cfg(test)] mod tests { use super::*; From 6c1b8422213d9fc91c2fb9f803a43038238bd389 Mon Sep 17 00:00:00 2001 From: nk_ysg Date: Tue, 9 Jul 2024 21:39:40 +0800 Subject: [PATCH 3/7] move CallFees to reth-rpc-eth-types --- crates/rpc/rpc-eth-api/src/helpers/call.rs | 281 +-------------------- crates/rpc/rpc-eth-types/src/revm_utils.rs | 281 ++++++++++++++++++++- 2 files changed, 283 insertions(+), 279 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index b4948785d736..2392558df839 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -16,22 +16,21 @@ use reth_rpc_eth_types::{ cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper}, error::ensure_success, revm_utils::{ - apply_state_overrides, caller_gas_allowance, cap_tx_gas_limit_with_caller_allowance, - get_precompiles, + apply_block_overrides, apply_state_overrides, caller_gas_allowance, + cap_tx_gas_limit_with_caller_allowance, get_precompiles, CallFees, }, EthApiError, EthResult, RevertError, RpcInvalidTransactionError, StateCacheDb, }; use reth_rpc_server_types::constants::gas_oracle::{ESTIMATE_GAS_ERROR_RATIO, MIN_TRANSACTION_GAS}; use reth_rpc_types::{ state::{EvmOverrides, StateOverride}, - AccessListWithGasUsed, BlockId, BlockOverrides, Bundle, EthCallResponse, StateContext, - TransactionInfo, TransactionRequest, + AccessListWithGasUsed, BlockId, Bundle, EthCallResponse, StateContext, TransactionInfo, + TransactionRequest, }; use revm::{Database, DatabaseCommit}; use revm_inspectors::access_list::AccessListInspector; #[cfg(feature = "optimism")] use revm_primitives::OptimismFields; -use std::cmp::min; use tracing::trace; use super::{LoadBlock, LoadPendingBlock, LoadState, LoadTransaction, SpawnBlocking, Trace}; @@ -934,275 +933,3 @@ pub trait Call: LoadState + SpawnBlocking { Ok(env) } } - -/// Helper type for representing the fees of a [`TransactionRequest`] -#[derive(Debug)] -pub struct CallFees { - /// EIP-1559 priority fee - 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, - /// Max Fee per Blob gas for EIP-4844 transactions - max_fee_per_blob_gas: Option, -} - -// === impl CallFees === - -impl CallFees { - /// Ensures the fields of a [`TransactionRequest`] are not conflicting. - /// - /// # EIP-4844 transactions - /// - /// Blob transactions have an additional fee parameter `maxFeePerBlobGas`. - /// If the `maxFeePerBlobGas` or `blobVersionedHashes` are set we treat it as an EIP-4844 - /// transaction. - /// - /// Note: Due to the `Default` impl of [`BlockEnv`] (Some(0)) this assumes the `block_blob_fee` - /// is always `Some` - fn ensure_fees( - call_gas_price: Option, - call_max_fee: Option, - call_priority_fee: Option, - block_base_fee: U256, - blob_versioned_hashes: Option<&[B256]>, - max_fee_per_blob_gas: Option, - block_blob_fee: Option, - ) -> EthResult { - /// Get the effective gas price of a transaction as specfified in EIP-1559 with relevant - /// checks. - fn get_effective_gas_price( - max_fee_per_gas: Option, - max_priority_fee_per_gas: Option, - block_base_fee: U256, - ) -> EthResult { - match max_fee_per_gas { - Some(max_fee) => { - if max_fee < block_base_fee { - // `base_fee_per_gas` is greater than the `max_fee_per_gas` - return Err(RpcInvalidTransactionError::FeeCapTooLow.into()) - } - if max_fee < max_priority_fee_per_gas.unwrap_or(U256::ZERO) { - return Err( - // `max_priority_fee_per_gas` is greater than the `max_fee_per_gas` - RpcInvalidTransactionError::TipAboveFeeCap.into(), - ) - } - Ok(min( - max_fee, - block_base_fee - .checked_add(max_priority_fee_per_gas.unwrap_or(U256::ZERO)) - .ok_or_else(|| { - EthApiError::from(RpcInvalidTransactionError::TipVeryHigh) - })?, - )) - } - None => Ok(block_base_fee - .checked_add(max_priority_fee_per_gas.unwrap_or(U256::ZERO)) - .ok_or_else(|| EthApiError::from(RpcInvalidTransactionError::TipVeryHigh))?), - } - } - - let has_blob_hashes = - blob_versioned_hashes.as_ref().map(|blobs| !blobs.is_empty()).unwrap_or(false); - - match (call_gas_price, call_max_fee, call_priority_fee, max_fee_per_blob_gas) { - (gas_price, None, None, None) => { - // either legacy transaction or no fee fields are specified - // when no fields are specified, set gas price to zero - let gas_price = gas_price.unwrap_or(U256::ZERO); - Ok(Self { - gas_price, - max_priority_fee_per_gas: None, - max_fee_per_blob_gas: has_blob_hashes.then_some(block_blob_fee).flatten(), - }) - } - (None, max_fee_per_gas, max_priority_fee_per_gas, None) => { - // request for eip-1559 transaction - let effective_gas_price = get_effective_gas_price( - max_fee_per_gas, - max_priority_fee_per_gas, - block_base_fee, - )?; - let max_fee_per_blob_gas = has_blob_hashes.then_some(block_blob_fee).flatten(); - - Ok(Self { - gas_price: effective_gas_price, - max_priority_fee_per_gas, - max_fee_per_blob_gas, - }) - } - (None, max_fee_per_gas, max_priority_fee_per_gas, Some(max_fee_per_blob_gas)) => { - // request for eip-4844 transaction - let effective_gas_price = get_effective_gas_price( - max_fee_per_gas, - max_priority_fee_per_gas, - block_base_fee, - )?; - // Ensure blob_hashes are present - if !has_blob_hashes { - // Blob transaction but no blob hashes - return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into()) - } - - Ok(Self { - gas_price: effective_gas_price, - max_priority_fee_per_gas, - max_fee_per_blob_gas: Some(max_fee_per_blob_gas), - }) - } - _ => { - // this fallback covers incompatible combinations of fields - Err(EthApiError::ConflictingFeeFieldsInRequest) - } - } - } -} - -/// Applies the given block overrides to the env -fn apply_block_overrides(overrides: BlockOverrides, env: &mut BlockEnv) { - let BlockOverrides { - number, - difficulty, - time, - gas_limit, - coinbase, - random, - base_fee, - block_hash: _, - } = overrides; - - if let Some(number) = number { - env.number = number; - } - if let Some(difficulty) = difficulty { - env.difficulty = difficulty; - } - if let Some(time) = time { - env.timestamp = U256::from(time); - } - if let Some(gas_limit) = gas_limit { - env.gas_limit = U256::from(gas_limit); - } - if let Some(coinbase) = coinbase { - env.coinbase = coinbase; - } - if let Some(random) = random { - env.prevrandao = Some(random); - } - if let Some(base_fee) = base_fee { - env.basefee = base_fee; - } -} - -#[cfg(test)] -mod tests { - use super::*; - use reth_primitives::constants::GWEI_TO_WEI; - - #[test] - fn test_ensure_0_fallback() { - let CallFees { gas_price, .. } = - CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO)) - .unwrap(); - assert!(gas_price.is_zero()); - } - - #[test] - fn test_blob_fees() { - let CallFees { gas_price, max_fee_per_blob_gas, .. } = - CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO)) - .unwrap(); - assert!(gas_price.is_zero()); - assert_eq!(max_fee_per_blob_gas, None); - - let CallFees { gas_price, max_fee_per_blob_gas, .. } = CallFees::ensure_fees( - None, - None, - None, - U256::from(99), - Some(&[B256::from(U256::ZERO)]), - None, - Some(U256::from(99)), - ) - .unwrap(); - assert!(gas_price.is_zero()); - assert_eq!(max_fee_per_blob_gas, Some(U256::from(99))); - } - - #[test] - fn test_eip_1559_fees() { - let CallFees { gas_price, .. } = CallFees::ensure_fees( - None, - Some(U256::from(25 * GWEI_TO_WEI)), - Some(U256::from(15 * GWEI_TO_WEI)), - U256::from(15 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ) - .unwrap(); - assert_eq!(gas_price, U256::from(25 * GWEI_TO_WEI)); - - let CallFees { gas_price, .. } = CallFees::ensure_fees( - None, - Some(U256::from(25 * GWEI_TO_WEI)), - Some(U256::from(5 * GWEI_TO_WEI)), - U256::from(15 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ) - .unwrap(); - assert_eq!(gas_price, U256::from(20 * GWEI_TO_WEI)); - - let CallFees { gas_price, .. } = CallFees::ensure_fees( - None, - Some(U256::from(30 * GWEI_TO_WEI)), - Some(U256::from(30 * GWEI_TO_WEI)), - U256::from(15 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ) - .unwrap(); - assert_eq!(gas_price, U256::from(30 * GWEI_TO_WEI)); - - let call_fees = CallFees::ensure_fees( - None, - Some(U256::from(30 * GWEI_TO_WEI)), - Some(U256::from(31 * GWEI_TO_WEI)), - U256::from(15 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ); - assert!(call_fees.is_err()); - - let call_fees = CallFees::ensure_fees( - None, - Some(U256::from(5 * GWEI_TO_WEI)), - Some(U256::from(GWEI_TO_WEI)), - U256::from(15 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ); - assert!(call_fees.is_err()); - - let call_fees = CallFees::ensure_fees( - None, - Some(U256::MAX), - Some(U256::MAX), - U256::from(5 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ); - assert!(call_fees.is_err()); - } -} diff --git a/crates/rpc/rpc-eth-types/src/revm_utils.rs b/crates/rpc/rpc-eth-types/src/revm_utils.rs index 0d8b7b04b2b2..ec67c0f46ce6 100644 --- a/crates/rpc/rpc-eth-types/src/revm_utils.rs +++ b/crates/rpc/rpc-eth-types/src/revm_utils.rs @@ -1,13 +1,18 @@ //! utilities for working with revm -use reth_primitives::{Address, U256}; -use reth_rpc_types::state::{AccountOverride, StateOverride}; +use reth_primitives::{Address, B256, U256}; +use reth_rpc_types::{ + state::{AccountOverride, StateOverride}, + BlockOverrides, +}; use revm::{ db::CacheDB, precompile::{PrecompileSpecId, Precompiles}, primitives::{db::DatabaseRef, Bytecode, SpecId, TxEnv}, Database, }; +use revm_primitives::BlockEnv; +use std::cmp::min; use super::{EthApiError, EthResult, RpcInvalidTransactionError}; @@ -128,3 +133,275 @@ where Ok(()) } + +/// Helper type for representing the fees of a [`TransactionRequest`] +#[derive(Debug)] +pub struct CallFees { + /// EIP-1559 priority fee + 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 + pub gas_price: U256, + /// Max Fee per Blob gas for EIP-4844 transactions + pub max_fee_per_blob_gas: Option, +} + +// === impl CallFees === + +impl CallFees { + /// Ensures the fields of a [`TransactionRequest`] are not conflicting. + /// + /// # EIP-4844 transactions + /// + /// Blob transactions have an additional fee parameter `maxFeePerBlobGas`. + /// If the `maxFeePerBlobGas` or `blobVersionedHashes` are set we treat it as an EIP-4844 + /// transaction. + /// + /// Note: Due to the `Default` impl of [`BlockEnv`] (Some(0)) this assumes the `block_blob_fee` + /// is always `Some` + pub fn ensure_fees( + call_gas_price: Option, + call_max_fee: Option, + call_priority_fee: Option, + block_base_fee: U256, + blob_versioned_hashes: Option<&[B256]>, + max_fee_per_blob_gas: Option, + block_blob_fee: Option, + ) -> EthResult { + /// Get the effective gas price of a transaction as specfified in EIP-1559 with relevant + /// checks. + fn get_effective_gas_price( + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + block_base_fee: U256, + ) -> EthResult { + match max_fee_per_gas { + Some(max_fee) => { + if max_fee < block_base_fee { + // `base_fee_per_gas` is greater than the `max_fee_per_gas` + return Err(RpcInvalidTransactionError::FeeCapTooLow.into()) + } + if max_fee < max_priority_fee_per_gas.unwrap_or(U256::ZERO) { + return Err( + // `max_priority_fee_per_gas` is greater than the `max_fee_per_gas` + RpcInvalidTransactionError::TipAboveFeeCap.into(), + ) + } + Ok(min( + max_fee, + block_base_fee + .checked_add(max_priority_fee_per_gas.unwrap_or(U256::ZERO)) + .ok_or_else(|| { + EthApiError::from(RpcInvalidTransactionError::TipVeryHigh) + })?, + )) + } + None => Ok(block_base_fee + .checked_add(max_priority_fee_per_gas.unwrap_or(U256::ZERO)) + .ok_or_else(|| EthApiError::from(RpcInvalidTransactionError::TipVeryHigh))?), + } + } + + let has_blob_hashes = + blob_versioned_hashes.as_ref().map(|blobs| !blobs.is_empty()).unwrap_or(false); + + match (call_gas_price, call_max_fee, call_priority_fee, max_fee_per_blob_gas) { + (gas_price, None, None, None) => { + // either legacy transaction or no fee fields are specified + // when no fields are specified, set gas price to zero + let gas_price = gas_price.unwrap_or(U256::ZERO); + Ok(Self { + gas_price, + max_priority_fee_per_gas: None, + max_fee_per_blob_gas: has_blob_hashes.then_some(block_blob_fee).flatten(), + }) + } + (None, max_fee_per_gas, max_priority_fee_per_gas, None) => { + // request for eip-1559 transaction + let effective_gas_price = get_effective_gas_price( + max_fee_per_gas, + max_priority_fee_per_gas, + block_base_fee, + )?; + let max_fee_per_blob_gas = has_blob_hashes.then_some(block_blob_fee).flatten(); + + Ok(Self { + gas_price: effective_gas_price, + max_priority_fee_per_gas, + max_fee_per_blob_gas, + }) + } + (None, max_fee_per_gas, max_priority_fee_per_gas, Some(max_fee_per_blob_gas)) => { + // request for eip-4844 transaction + let effective_gas_price = get_effective_gas_price( + max_fee_per_gas, + max_priority_fee_per_gas, + block_base_fee, + )?; + // Ensure blob_hashes are present + if !has_blob_hashes { + // Blob transaction but no blob hashes + return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into()) + } + + Ok(Self { + gas_price: effective_gas_price, + max_priority_fee_per_gas, + max_fee_per_blob_gas: Some(max_fee_per_blob_gas), + }) + } + _ => { + // this fallback covers incompatible combinations of fields + Err(EthApiError::ConflictingFeeFieldsInRequest) + } + } + } +} + +/// Applies the given block overrides to the env +pub fn apply_block_overrides(overrides: BlockOverrides, env: &mut BlockEnv) { + let BlockOverrides { + number, + difficulty, + time, + gas_limit, + coinbase, + random, + base_fee, + block_hash: _, + } = overrides; + + if let Some(number) = number { + env.number = number; + } + if let Some(difficulty) = difficulty { + env.difficulty = difficulty; + } + if let Some(time) = time { + env.timestamp = U256::from(time); + } + if let Some(gas_limit) = gas_limit { + env.gas_limit = U256::from(gas_limit); + } + if let Some(coinbase) = coinbase { + env.coinbase = coinbase; + } + if let Some(random) = random { + env.prevrandao = Some(random); + } + if let Some(base_fee) = base_fee { + env.basefee = base_fee; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use reth_primitives::constants::GWEI_TO_WEI; + + #[test] + fn test_ensure_0_fallback() { + let CallFees { gas_price, .. } = + CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO)) + .unwrap(); + assert!(gas_price.is_zero()); + } + + #[test] + fn test_blob_fees() { + let CallFees { gas_price, max_fee_per_blob_gas, .. } = + CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO)) + .unwrap(); + assert!(gas_price.is_zero()); + assert_eq!(max_fee_per_blob_gas, None); + + let CallFees { gas_price, max_fee_per_blob_gas, .. } = CallFees::ensure_fees( + None, + None, + None, + U256::from(99), + Some(&[B256::from(U256::ZERO)]), + None, + Some(U256::from(99)), + ) + .unwrap(); + assert!(gas_price.is_zero()); + assert_eq!(max_fee_per_blob_gas, Some(U256::from(99))); + } + + #[test] + fn test_eip_1559_fees() { + let CallFees { gas_price, .. } = CallFees::ensure_fees( + None, + Some(U256::from(25 * GWEI_TO_WEI)), + Some(U256::from(15 * GWEI_TO_WEI)), + U256::from(15 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ) + .unwrap(); + assert_eq!(gas_price, U256::from(25 * GWEI_TO_WEI)); + + let CallFees { gas_price, .. } = CallFees::ensure_fees( + None, + Some(U256::from(25 * GWEI_TO_WEI)), + Some(U256::from(5 * GWEI_TO_WEI)), + U256::from(15 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ) + .unwrap(); + assert_eq!(gas_price, U256::from(20 * GWEI_TO_WEI)); + + let CallFees { gas_price, .. } = CallFees::ensure_fees( + None, + Some(U256::from(30 * GWEI_TO_WEI)), + Some(U256::from(30 * GWEI_TO_WEI)), + U256::from(15 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ) + .unwrap(); + assert_eq!(gas_price, U256::from(30 * GWEI_TO_WEI)); + + let call_fees = CallFees::ensure_fees( + None, + Some(U256::from(30 * GWEI_TO_WEI)), + Some(U256::from(31 * GWEI_TO_WEI)), + U256::from(15 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ); + assert!(call_fees.is_err()); + + let call_fees = CallFees::ensure_fees( + None, + Some(U256::from(5 * GWEI_TO_WEI)), + Some(U256::from(GWEI_TO_WEI)), + U256::from(15 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ); + assert!(call_fees.is_err()); + + let call_fees = CallFees::ensure_fees( + None, + Some(U256::MAX), + Some(U256::MAX), + U256::from(5 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ); + assert!(call_fees.is_err()); + } +} From e698fbbe2c4f4742d46503777a0d8c9686d6cdc9 Mon Sep 17 00:00:00 2001 From: nk_ysg Date: Tue, 9 Jul 2024 21:59:20 +0800 Subject: [PATCH 4/7] move fn create_txn_env to trait Call --- crates/rpc/rpc-eth-api/src/helpers/call.rs | 134 +++++++++++---------- 1 file changed, 69 insertions(+), 65 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index 23dba6a73ea7..a2b45204dcf9 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -779,6 +779,74 @@ 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 + .map(reth_rpc_types::AccessList::into_flattened) + .unwrap_or_default(), + // EIP-4844 fields + blob_hashes: blob_versioned_hashes.unwrap_or_default(), + max_fee_per_blob_gas, + #[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`. /// @@ -789,71 +857,7 @@ pub trait Call: LoadState + SpawnBlocking { block: BlockEnv, request: TransactionRequest, ) -> EthResult { - /// 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(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 - .map(reth_rpc_types::AccessList::into_flattened) - .unwrap_or_default(), - // EIP-4844 fields - blob_hashes: blob_versioned_hashes.unwrap_or_default(), - max_fee_per_blob_gas, - #[cfg(feature = "optimism")] - optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() }, - }; - - Ok(env) - } - let tx = create_txn_env(&block, request)?; + let tx = self.create_txn_env(&block, request)?; Ok(EnvWithHandlerCfg::new_with_cfg_env(cfg, block, tx)) } From 6949fe4957b8bc1faa350a8142588f4655a78de0 Mon Sep 17 00:00:00 2001 From: nk_ysg Date: Tue, 9 Jul 2024 22:51:15 +0800 Subject: [PATCH 5/7] fix merge conflict --- crates/rpc/rpc-eth-api/src/helpers/call.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index a2b45204dcf9..932138f3929d 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -834,12 +834,11 @@ pub trait Call: LoadState + SpawnBlocking { value: value.unwrap_or_default(), data: input.try_into_unique_input()?.unwrap_or_default(), chain_id, - access_list: access_list - .map(reth_rpc_types::AccessList::into_flattened) - .unwrap_or_default(), + 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() }, }; From 9719c8a809860483bbe5cda48897ca841ad08976 Mon Sep 17 00:00:00 2001 From: nk_ysg Date: Tue, 9 Jul 2024 23:05:53 +0800 Subject: [PATCH 6/7] fix fmt --- crates/rpc/rpc-eth-types/src/revm_utils.rs | 142 ++++++++++----------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/crates/rpc/rpc-eth-types/src/revm_utils.rs b/crates/rpc/rpc-eth-types/src/revm_utils.rs index ec67c0f46ce6..05a8d319fbe6 100644 --- a/crates/rpc/rpc-eth-types/src/revm_utils.rs +++ b/crates/rpc/rpc-eth-types/src/revm_utils.rs @@ -63,77 +63,6 @@ where .unwrap_or_default()) } -/// Applies the given state overrides (a set of [`AccountOverride`]) to the [`CacheDB`]. -pub fn apply_state_overrides(overrides: StateOverride, db: &mut CacheDB) -> EthResult<()> -where - DB: DatabaseRef, - EthApiError: From<::Error>, -{ - for (account, account_overrides) in overrides { - apply_account_override(account, account_overrides, db)?; - } - Ok(()) -} - -/// Applies a single [`AccountOverride`] to the [`CacheDB`]. -fn apply_account_override( - account: Address, - account_override: AccountOverride, - db: &mut CacheDB, -) -> EthResult<()> -where - DB: DatabaseRef, - EthApiError: From<::Error>, -{ - // we need to fetch the account via the `DatabaseRef` to not update the state of the account, - // which is modified via `Database::basic_ref` - let mut account_info = DatabaseRef::basic_ref(db, account)?.unwrap_or_default(); - - if let Some(nonce) = account_override.nonce { - account_info.nonce = nonce.to(); - } - if let Some(code) = account_override.code { - account_info.code = Some(Bytecode::new_raw(code)); - } - if let Some(balance) = account_override.balance { - account_info.balance = balance; - } - - db.insert_account_info(account, account_info); - - // We ensure that not both state and state_diff are set. - // If state is set, we must mark the account as "NewlyCreated", so that the old storage - // isn't read from - match (account_override.state, account_override.state_diff) { - (Some(_), Some(_)) => return Err(EthApiError::BothStateAndStateDiffInOverride(account)), - (None, None) => { - // nothing to do - } - (Some(new_account_state), None) => { - db.replace_account_storage( - account, - new_account_state - .into_iter() - .map(|(slot, value)| { - (U256::from_be_bytes(slot.0), U256::from_be_bytes(value.0)) - }) - .collect(), - )?; - } - (None, Some(account_state_diff)) => { - for (slot, value) in account_state_diff { - db.insert_account_storage( - account, - U256::from_be_bytes(slot.0), - U256::from_be_bytes(value.0), - )?; - } - } - }; - - Ok(()) -} - /// Helper type for representing the fees of a [`TransactionRequest`] #[derive(Debug)] pub struct CallFees { @@ -298,6 +227,77 @@ pub fn apply_block_overrides(overrides: BlockOverrides, env: &mut BlockEnv) { } } +/// Applies the given state overrides (a set of [`AccountOverride`]) to the [`CacheDB`]. +pub fn apply_state_overrides(overrides: StateOverride, db: &mut CacheDB) -> EthResult<()> +where + DB: DatabaseRef, + EthApiError: From<::Error>, +{ + for (account, account_overrides) in overrides { + apply_account_override(account, account_overrides, db)?; + } + Ok(()) +} + +/// Applies a single [`AccountOverride`] to the [`CacheDB`]. +fn apply_account_override( + account: Address, + account_override: AccountOverride, + db: &mut CacheDB, +) -> EthResult<()> +where + DB: DatabaseRef, + EthApiError: From<::Error>, +{ + // we need to fetch the account via the `DatabaseRef` to not update the state of the account, + // which is modified via `Database::basic_ref` + let mut account_info = DatabaseRef::basic_ref(db, account)?.unwrap_or_default(); + + if let Some(nonce) = account_override.nonce { + account_info.nonce = nonce.to(); + } + if let Some(code) = account_override.code { + account_info.code = Some(Bytecode::new_raw(code)); + } + if let Some(balance) = account_override.balance { + account_info.balance = balance; + } + + db.insert_account_info(account, account_info); + + // We ensure that not both state and state_diff are set. + // If state is set, we must mark the account as "NewlyCreated", so that the old storage + // isn't read from + match (account_override.state, account_override.state_diff) { + (Some(_), Some(_)) => return Err(EthApiError::BothStateAndStateDiffInOverride(account)), + (None, None) => { + // nothing to do + } + (Some(new_account_state), None) => { + db.replace_account_storage( + account, + new_account_state + .into_iter() + .map(|(slot, value)| { + (U256::from_be_bytes(slot.0), U256::from_be_bytes(value.0)) + }) + .collect(), + )?; + } + (None, Some(account_state_diff)) => { + for (slot, value) in account_state_diff { + db.insert_account_storage( + account, + U256::from_be_bytes(slot.0), + U256::from_be_bytes(value.0), + )?; + } + } + }; + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; From b0977061beca3ce77b39bd1f06a6f66b61c5a700 Mon Sep 17 00:00:00 2001 From: nk_ysg Date: Tue, 9 Jul 2024 23:22:14 +0800 Subject: [PATCH 7/7] fix doc --- crates/rpc/rpc-eth-types/src/revm_utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rpc/rpc-eth-types/src/revm_utils.rs b/crates/rpc/rpc-eth-types/src/revm_utils.rs index 05a8d319fbe6..27ab25021acb 100644 --- a/crates/rpc/rpc-eth-types/src/revm_utils.rs +++ b/crates/rpc/rpc-eth-types/src/revm_utils.rs @@ -63,7 +63,7 @@ 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 @@ -82,7 +82,7 @@ pub struct CallFees { // === 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 ///