From 45648a7a986633f8d4637529bfb360a1d5e85244 Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Mon, 15 Sep 2025 02:19:42 -0400 Subject: [PATCH] fix: Apply precompiles for eth_call and eth_estimateGas --- Cargo.lock | 5 + Cargo.toml | 5 + src/node/rpc/call.rs | 61 ++++++++--- src/node/rpc/estimate.rs | 215 +++++++++++++++++++++++++++++++++++++++ src/node/rpc/mod.rs | 1 + 5 files changed, 271 insertions(+), 16 deletions(-) create mode 100644 src/node/rpc/estimate.rs diff --git a/Cargo.lock b/Cargo.lock index 8ab802654..b2c7995fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9350,6 +9350,7 @@ dependencies = [ "reth-db-api", "reth-discv4", "reth-engine-primitives", + "reth-errors", "reth-eth-wire", "reth-eth-wire-types", "reth-ethereum-forks", @@ -9370,9 +9371,13 @@ dependencies = [ "reth-provider", "reth-revm", "reth-rpc", + "reth-rpc-convert", "reth-rpc-engine-api", "reth-rpc-eth-api", + "reth-rpc-eth-types", + "reth-rpc-server-types", "reth-stages-types", + "reth-storage-api", "reth-tracing", "reth-transaction-pool", "reth-trie-common", diff --git a/Cargo.toml b/Cargo.toml index fa21911ae..7265e5b85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,11 @@ reth-trie-db = { git = "https://github.com/sprites0/reth", rev = "d26fd2e25b57d6 reth-codecs = { git = "https://github.com/sprites0/reth", rev = "d26fd2e25b57d695aa453c93f15a8cd158a1f505" } reth-transaction-pool = { git = "https://github.com/sprites0/reth", rev = "d26fd2e25b57d695aa453c93f15a8cd158a1f505" } reth-stages-types = { git = "https://github.com/sprites0/reth", rev = "d26fd2e25b57d695aa453c93f15a8cd158a1f505" } +reth-storage-api = { git = "https://github.com/sprites0/reth", rev = "d26fd2e25b57d695aa453c93f15a8cd158a1f505" } +reth-errors = { git = "https://github.com/sprites0/reth", rev = "d26fd2e25b57d695aa453c93f15a8cd158a1f505" } +reth-rpc-convert = { git = "https://github.com/sprites0/reth", rev = "d26fd2e25b57d695aa453c93f15a8cd158a1f505" } +reth-rpc-eth-types = { git = "https://github.com/sprites0/reth", rev = "d26fd2e25b57d695aa453c93f15a8cd158a1f505" } +reth-rpc-server-types = { git = "https://github.com/sprites0/reth", rev = "d26fd2e25b57d695aa453c93f15a8cd158a1f505" } reth-metrics = { git = "https://github.com/sprites0/reth", rev = "d26fd2e25b57d695aa453c93f15a8cd158a1f505" } revm = { version = "29.0.0", default-features = false } diff --git a/src/node/rpc/call.rs b/src/node/rpc/call.rs index 3cdc9c130..eb3dcf345 100644 --- a/src/node/rpc/call.rs +++ b/src/node/rpc/call.rs @@ -1,17 +1,19 @@ +use core::fmt; + use super::{HlEthApi, HlRpcNodeCore}; use crate::{node::evm::apply_precompiles, HlBlock}; use alloy_evm::Evm; use alloy_primitives::B256; use reth::rpc::server_types::eth::EthApiError; -use reth_evm::{ConfigureEvm, Database, EvmEnvFor, SpecFor, TxEnvFor}; +use reth_evm::{ConfigureEvm, Database, EvmEnvFor, HaltReasonFor, InspectorFor, SpecFor, TxEnvFor}; use reth_primitives::{NodePrimitives, Recovered}; use reth_primitives_traits::SignedTransaction; use reth_provider::{ProviderError, ProviderTx}; use reth_rpc_eth_api::{ - helpers::{estimate::EstimateCall, Call, EthCall}, + helpers::{Call, EthCall}, FromEvmError, RpcConvert, RpcNodeCore, }; -use revm::DatabaseCommit; +use revm::{context::result::ResultAndState, DatabaseCommit}; impl HlRpcNodeCore for N where N: RpcNodeCore> {} @@ -28,19 +30,6 @@ where { } -impl EstimateCall for HlEthApi -where - N: HlRpcNodeCore, - EthApiError: FromEvmError, - Rpc: RpcConvert< - Primitives = N::Primitives, - Error = EthApiError, - TxEnv = TxEnvFor, - Spec = SpecFor, - >, -{ -} - impl Call for HlEthApi where N: HlRpcNodeCore, @@ -62,6 +51,46 @@ where self.inner.eth_api.max_simulate_blocks() } + fn transact( + &self, + db: DB, + evm_env: EvmEnvFor, + tx_env: TxEnvFor, + ) -> Result>, Self::Error> + where + DB: Database + fmt::Debug, + { + let block_number = evm_env.block_env().number; + let hl_extras = self.get_hl_extras(block_number.try_into().unwrap())?; + + let mut evm = self.evm_config().evm_with_env(db, evm_env); + apply_precompiles(&mut evm, &hl_extras); + let res = evm.transact(tx_env).map_err(Self::Error::from_evm_err)?; + + Ok(res) + } + + fn transact_with_inspector( + &self, + db: DB, + evm_env: EvmEnvFor, + tx_env: TxEnvFor, + inspector: I, + ) -> Result>, Self::Error> + where + DB: Database + fmt::Debug, + I: InspectorFor, + { + let block_number = evm_env.block_env().number; + let hl_extras = self.get_hl_extras(block_number.try_into().unwrap())?; + + let mut evm = self.evm_config().evm_with_env_and_inspector(db, evm_env, inspector); + apply_precompiles(&mut evm, &hl_extras); + let res = evm.transact(tx_env).map_err(Self::Error::from_evm_err)?; + + Ok(res) + } + fn replay_transactions_until<'a, DB, I>( &self, db: &mut DB, diff --git a/src/node/rpc/estimate.rs b/src/node/rpc/estimate.rs new file mode 100644 index 000000000..cd99dea95 --- /dev/null +++ b/src/node/rpc/estimate.rs @@ -0,0 +1,215 @@ +use super::{apply_precompiles, HlEthApi, HlRpcNodeCore}; +use alloy_evm::overrides::{apply_state_overrides, StateOverrideError}; +use alloy_network::TransactionBuilder; +use alloy_primitives::{TxKind, U256}; +use alloy_rpc_types_eth::state::StateOverride; +use reth_chainspec::MIN_TRANSACTION_GAS; +use reth_errors::ProviderError; +use reth_evm::{ConfigureEvm, Evm, EvmEnvFor, SpecFor, TransactionEnv, TxEnvFor}; +use reth_revm::{database::StateProviderDatabase, db::CacheDB}; +use reth_rpc_convert::{RpcConvert, RpcTxReq}; +use reth_rpc_eth_api::{ + helpers::{ + estimate::{update_estimated_gas_range, EstimateCall}, + Call, + }, + AsEthApiError, IntoEthApiError, RpcNodeCore, +}; +use reth_rpc_eth_types::{ + error::{api::FromEvmHalt, FromEvmError}, + EthApiError, RevertError, RpcInvalidTransactionError, +}; +use reth_rpc_server_types::constants::gas_oracle::{CALL_STIPEND_GAS, ESTIMATE_GAS_ERROR_RATIO}; +use reth_storage_api::StateProvider; +use revm::context_interface::{result::ExecutionResult, Transaction}; +use tracing::trace; + +impl EstimateCall for HlEthApi +where + Self: Call, + N: HlRpcNodeCore, + EthApiError: FromEvmError + From>, + Rpc: RpcConvert< + Primitives = N::Primitives, + Error = EthApiError, + TxEnv = TxEnvFor, + Spec = SpecFor, + >, +{ + // Modified version that adds `apply_precompiles`; comments are stripped out. + fn estimate_gas_with( + &self, + mut evm_env: EvmEnvFor, + mut request: RpcTxReq<::Network>, + state: S, + state_override: Option, + ) -> Result + where + S: StateProvider, + { + evm_env.cfg_env.disable_eip3607 = true; + evm_env.cfg_env.disable_base_fee = true; + + request.as_mut().take_nonce(); + + let tx_request_gas_limit = request.as_ref().gas_limit(); + let tx_request_gas_price = request.as_ref().gas_price(); + let max_gas_limit = evm_env + .cfg_env + .tx_gas_limit_cap + .map_or(evm_env.block_env.gas_limit, |cap| cap.min(evm_env.block_env.gas_limit)); + + let mut highest_gas_limit = tx_request_gas_limit + .map(|mut tx_gas_limit| { + if max_gas_limit < tx_gas_limit { + tx_gas_limit = max_gas_limit; + } + tx_gas_limit + }) + .unwrap_or(max_gas_limit); + + let mut db = CacheDB::new(StateProviderDatabase::new(state)); + + if let Some(state_override) = state_override { + apply_state_overrides(state_override, &mut db).map_err( + |err: StateOverrideError| { + let eth_api_error: EthApiError = EthApiError::from(err); + Self::Error::from(eth_api_error) + }, + )?; + } + + let mut tx_env = self.create_txn_env(&evm_env, request, &mut db)?; + + let mut is_basic_transfer = false; + if tx_env.input().is_empty() { + if let TxKind::Call(to) = tx_env.kind() { + if let Ok(code) = db.db.account_code(&to) { + is_basic_transfer = code.map(|code| code.is_empty()).unwrap_or(true); + } + } + } + + if tx_env.gas_price() > 0 { + highest_gas_limit = + highest_gas_limit.min(self.caller_gas_allowance(&mut db, &evm_env, &tx_env)?); + } + + tx_env.set_gas_limit(tx_env.gas_limit().min(highest_gas_limit)); + + let block_number = evm_env.block_env().number; + let hl_extras = self.get_hl_extras(block_number.try_into().unwrap())?; + + let mut evm = self.evm_config().evm_with_env(&mut db, evm_env); + apply_precompiles(&mut evm, &hl_extras); + + if is_basic_transfer { + let mut min_tx_env = tx_env.clone(); + min_tx_env.set_gas_limit(MIN_TRANSACTION_GAS); + + if let Ok(res) = evm.transact(min_tx_env).map_err(Self::Error::from_evm_err) { + if res.result.is_success() { + return Ok(U256::from(MIN_TRANSACTION_GAS)); + } + } + } + + trace!(target: "rpc::eth::estimate", ?tx_env, gas_limit = tx_env.gas_limit(), is_basic_transfer, "Starting gas estimation"); + + let mut res = match evm.transact(tx_env.clone()).map_err(Self::Error::from_evm_err) { + Err(err) + if err.is_gas_too_high() && + (tx_request_gas_limit.is_some() || tx_request_gas_price.is_some()) => + { + return Self::map_out_of_gas_err(&mut evm, tx_env, max_gas_limit); + } + Err(err) if err.is_gas_too_low() => { + return Err(RpcInvalidTransactionError::GasRequiredExceedsAllowance { + gas_limit: tx_env.gas_limit(), + } + .into_eth_err()); + } + + ethres => ethres?, + }; + + let gas_refund = match res.result { + ExecutionResult::Success { gas_refunded, .. } => gas_refunded, + ExecutionResult::Halt { reason, .. } => { + return Err(Self::Error::from_evm_halt(reason, tx_env.gas_limit())); + } + ExecutionResult::Revert { output, .. } => { + return if tx_request_gas_limit.is_some() || tx_request_gas_price.is_some() { + Self::map_out_of_gas_err(&mut evm, tx_env, max_gas_limit) + } else { + Err(RpcInvalidTransactionError::Revert(RevertError::new(output)).into_eth_err()) + }; + } + }; + + highest_gas_limit = tx_env.gas_limit(); + + let mut gas_used = res.result.gas_used(); + + let mut lowest_gas_limit = gas_used.saturating_sub(1); + + let optimistic_gas_limit = (gas_used + gas_refund + CALL_STIPEND_GAS) * 64 / 63; + if optimistic_gas_limit < highest_gas_limit { + let mut optimistic_tx_env = tx_env.clone(); + optimistic_tx_env.set_gas_limit(optimistic_gas_limit); + + res = evm.transact(optimistic_tx_env).map_err(Self::Error::from_evm_err)?; + + gas_used = res.result.gas_used(); + + update_estimated_gas_range( + res.result, + optimistic_gas_limit, + &mut highest_gas_limit, + &mut lowest_gas_limit, + )?; + }; + + let mut mid_gas_limit = std::cmp::min( + gas_used * 3, + ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64, + ); + + trace!(target: "rpc::eth::estimate", ?highest_gas_limit, ?lowest_gas_limit, ?mid_gas_limit, "Starting binary search for gas"); + + while lowest_gas_limit + 1 < highest_gas_limit { + if (highest_gas_limit - lowest_gas_limit) as f64 / (highest_gas_limit as f64) < + ESTIMATE_GAS_ERROR_RATIO + { + break; + }; + + let mut mid_tx_env = tx_env.clone(); + mid_tx_env.set_gas_limit(mid_gas_limit); + + match evm.transact(mid_tx_env).map_err(Self::Error::from_evm_err) { + Err(err) if err.is_gas_too_high() => { + highest_gas_limit = mid_gas_limit; + } + Err(err) if err.is_gas_too_low() => { + lowest_gas_limit = mid_gas_limit; + } + + ethres => { + res = ethres?; + + update_estimated_gas_range( + res.result, + mid_gas_limit, + &mut highest_gas_limit, + &mut lowest_gas_limit, + )?; + } + } + + mid_gas_limit = ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64; + } + + Ok(U256::from(highest_gas_limit)) + } +} diff --git a/src/node/rpc/mod.rs b/src/node/rpc/mod.rs index 0cc0ba122..30c8754f4 100644 --- a/src/node/rpc/mod.rs +++ b/src/node/rpc/mod.rs @@ -42,6 +42,7 @@ use std::{fmt, marker::PhantomData, sync::Arc}; mod block; mod call; pub mod engine_api; +mod estimate; mod transaction; pub trait HlRpcNodeCore: RpcNodeCore> {}