diff --git a/crates/rpc/rpc/src/eth/api/call.rs b/crates/rpc/rpc/src/eth/api/call.rs index e467b7bf65..b1644c55d5 100644 --- a/crates/rpc/rpc/src/eth/api/call.rs +++ b/crates/rpc/rpc/src/eth/api/call.rs @@ -2,7 +2,7 @@ use crate::{ eth::{ - error::{EthResult, InvalidTransactionError, RevertError}, + error::{EthApiError, EthResult, InvalidTransactionError, RevertError}, revm_utils::{ build_call_evm_env, cap_tx_gas_limit_with_caller_allowance, get_precompiles, inspect, prepare_call_env, transact, @@ -21,7 +21,7 @@ use reth_revm::{ use reth_rpc_types::{state::StateOverride, CallRequest}; use reth_transaction_pool::TransactionPool; use revm::{ - db::DatabaseRef, + db::{CacheDB, DatabaseRef}, primitives::{BlockEnv, CfgEnv, Env, ExecutionResult, Halt, ResultAndState, TransactTo}, }; use tracing::trace; @@ -141,8 +141,22 @@ where let gas_limit = std::cmp::min(U256::from(env.tx.gas_limit), highest_gas_limit); env.block.gas_limit = gas_limit; + trace!(target: "rpc::eth::estimate", ?env, "Starting gas estimation"); + // execute the call without writing to db - let (res, mut env) = transact(&mut db, env)?; + let ethres = transact(&mut db, env.clone()); + + // Exceptional case: init used too much gas, we need to increase the gas limit and try + // again + if let Err(EthApiError::InvalidTransaction(InvalidTransactionError::GasTooHigh)) = ethres { + // if price or limit was included in the request then we can execute the request + // again with the block's gas limit to check if revert is gas related or not + if request_gas.is_some() || request_gas_price.is_some() { + return Err(map_out_of_gas_err(env_gas_limit, env, &mut db)) + } + } + + let (res, env) = ethres?; match res.result { ExecutionResult::Success { .. } => { // succeeded @@ -154,24 +168,7 @@ where // if price or limit was included in the request then we can execute the request // again with the block's gas limit to check if revert is gas related or not return if request_gas.is_some() || request_gas_price.is_some() { - let req_gas_limit = env.tx.gas_limit; - env.tx.gas_limit = env_gas_limit.try_into().unwrap_or(u64::MAX); - let (res, _) = transact(&mut db, env)?; - match res.result { - ExecutionResult::Success { .. } => { - // transaction succeeded by manually increasing the gas limit to - // highest, which means the caller lacks funds to pay for the tx - Err(InvalidTransactionError::BasicOutOfGas(U256::from(req_gas_limit)) - .into()) - } - ExecutionResult::Revert { .. } => { - // reverted again after bumping the limit - Err(InvalidTransactionError::Revert(RevertError::new(output)).into()) - } - ExecutionResult::Halt { reason, .. } => { - Err(InvalidTransactionError::EvmHalt(reason).into()) - } - } + Err(map_out_of_gas_err(env_gas_limit, env, &mut db)) } else { // the transaction did revert Err(InvalidTransactionError::Revert(RevertError::new(output)).into()) @@ -194,11 +191,30 @@ where ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64, ); + let _last_highest_gas_limit = highest_gas_limit; + + trace!(target: "rpc::eth::estimate", ?env, ?highest_gas_limit, ?lowest_gas_limit, ?mid_gas_limit, "Starting binary search for gas"); + // binary search while (highest_gas_limit - lowest_gas_limit) > 1 { let mut env = env.clone(); env.tx.gas_limit = mid_gas_limit; - let (res, _) = transact(&mut db, env)?; + let ethres = transact(&mut db, env); + + // Exceptional case: init used too much gas, we need to increase the gas limit and try + // again + if let Err(EthApiError::InvalidTransaction(InvalidTransactionError::GasTooHigh)) = + ethres + { + // increase the lowest gas limit + lowest_gas_limit = mid_gas_limit; + + // new midpoint + mid_gas_limit = ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64; + continue + } + + let (res, _) = ethres?; match res.result { ExecutionResult::Success { .. } => { // cap the highest gas limit with succeeding gas limit @@ -283,3 +299,34 @@ where Ok(inspector.into_access_list()) } } + +/// Executes the requests again after an out of gas error to check if the error is gas related or +/// not +#[inline] +fn map_out_of_gas_err( + env_gas_limit: U256, + mut env: Env, + mut db: &mut CacheDB>, +) -> EthApiError +where + S: StateProvider, +{ + let req_gas_limit = env.tx.gas_limit; + env.tx.gas_limit = env_gas_limit.try_into().unwrap_or(u64::MAX); + let (res, _) = match transact(&mut db, env) { + Ok(res) => res, + Err(err) => return err, + }; + match res.result { + ExecutionResult::Success { .. } => { + // transaction succeeded by manually increasing the gas limit to + // highest, which means the caller lacks funds to pay for the tx + InvalidTransactionError::BasicOutOfGas(U256::from(req_gas_limit)).into() + } + ExecutionResult::Revert { output, .. } => { + // reverted again after bumping the limit + InvalidTransactionError::Revert(RevertError::new(output)).into() + } + ExecutionResult::Halt { reason, .. } => InvalidTransactionError::EvmHalt(reason).into(), + } +}