From 15d922368f520b3d67035f5012c93bc266a2a638 Mon Sep 17 00:00:00 2001 From: Minhyuk Kim Date: Mon, 24 Jun 2024 17:32:59 +0900 Subject: [PATCH] Fix gas fee calculation for debug calls (#10825) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hi guys, we had an issue come up on our fork of erigon regarding some gas fee calculation: https://github.com/testinprod-io/op-erigon/issues/183 We thought this would apply to upstream erigon as well, so brought the changes here too. I'd appreciate any feedbacks or concerns for this 😄 ## Problem This PR fixes gas fee calculation for rpc calls. It mostly borrows changes from geth and resolves previous issues. ```bash curl "https://localhost:8545 " -X POST -H "Content-Type: application/json" -d ' {"jsonrpc": "2.0", "id": "1", "method": "eth_estimateGas", "params": [{"from": "0xa5B8492D8223D255dB279C7c3ebdA34Be5eC9D85","data": "0x00", "to": "0xa5B8492D8223D255dB279C7c3ebdA34Be5eC9D85" }, "pending"]}' ``` Results in: ``` "code":-32000,"message":"insufficient funds for gas * price + value: address 0xa5B8492D8223D255dB279C7c3ebdA34Be5eC9D85 have 0 want 56956314575938201239799312" ``` Instead, it should return a valid RPC response. ## Detail Currently, erigon sets the gasFeeCap to header's baseFee by default. Setting gasFeeCap to baseFee will prevent calling eth_estimateGas with 0-balance accounts because the account's balance has to be bigger than the baseFee. However, in practice, many debug calls and estimateGas calls are called on 0-balance accounts, and without any gas-related fields set. Previous version of erigon and Geth allowed debug calls to be called on 0-balance accounts without setting gas parameters, so this PR attempts to bring back such behavior. ## Solution There is `evm.Config().NoBaseFee` which is used to signify that base fee is not required for this evm call. When this field is set AND gasPrice is not assigned, we set the block context's baseFee to 0. Because the baseFee is configured to 0 for these debug rpc calls, the feeCap will be always bigger than baseFee which passes the eip1599 validation. Also, 0-account balances will be able to call rpcs since the required gasFee is 0. This allows any debug calls without explicit gasPrice to also ignore the baseFee when calculating account balance/eip1559 check/etc. Also, currently, `NoBaseFee` is set to `true` for calls like `estimateGas`/`createAccessList`/`eth_call`, but not for `debug_traceCall`. This PR also sets `NoBaseFee` to `true` for `debug_traceCall` so that they can also ignore the base fee rule when needed. This PR from geth addresses similar concern: https://github.com/ethereum/go-ethereum/pull/23027 --- core/state_transition.go | 5 +++-- core/vm/evm.go | 6 ++++++ turbo/adapter/ethapi/api.go | 2 +- turbo/jsonrpc/eth_call.go | 2 -- turbo/transactions/tracing.go | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index bbb73167b16..60d5f95d3f4 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -268,7 +268,8 @@ func (st *StateTransition) preCheck(gasBailout bool) error { // Make sure the transaction gasFeeCap is greater than the block's baseFee. if st.evm.ChainRules().IsLondon { // Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call) - if !st.evm.Config().NoBaseFee || !st.gasFeeCap.IsZero() || !st.tip.IsZero() { + skipCheck := st.evm.Config().NoBaseFee && st.gasFeeCap.IsZero() && st.tip.IsZero() + if !skipCheck { if err := CheckEip1559TxGasFeeCap(st.msg.From(), st.gasFeeCap, st.tip, st.evm.Context.BaseFee, st.msg.IsFree()); err != nil { return err } @@ -280,7 +281,7 @@ func (st *StateTransition) preCheck(gasBailout bool) error { return fmt.Errorf("%w: Cancun is active but ExcessBlobGas is nil", ErrInternalFailure) } maxFeePerBlobGas := st.msg.MaxFeePerBlobGas() - if blobGasPrice.Cmp(maxFeePerBlobGas) > 0 { + if !st.evm.Config().NoBaseFee && blobGasPrice.Cmp(maxFeePerBlobGas) > 0 { return fmt.Errorf("%w: address %v, maxFeePerBlobGas: %v < blobGasPrice: %v", ErrMaxFeePerBlobGas, st.msg.From().Hex(), st.msg.MaxFeePerBlobGas(), blobGasPrice) } diff --git a/core/vm/evm.go b/core/vm/evm.go index 66bcb608c67..a133392cc22 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -100,6 +100,12 @@ type EVM struct { // NewEVM returns a new EVM. The returned EVM is not thread safe and should // only ever be used *once*. func NewEVM(blockCtx evmtypes.BlockContext, txCtx evmtypes.TxContext, state evmtypes.IntraBlockState, chainConfig *chain.Config, vmConfig Config) *EVM { + if vmConfig.NoBaseFee { + if txCtx.GasPrice.IsZero() { + blockCtx.BaseFee = new(uint256.Int) + } + } + evm := &EVM{ Context: blockCtx, TxContext: txCtx, diff --git a/turbo/adapter/ethapi/api.go b/turbo/adapter/ethapi/api.go index 1a0fd726909..b05a34f0f55 100644 --- a/turbo/adapter/ethapi/api.go +++ b/turbo/adapter/ethapi/api.go @@ -111,7 +111,7 @@ func (args *CallArgs) ToMessage(globalGasCap uint64, baseFee *uint256.Int) (type gasFeeCap, gasTipCap = gasPrice, gasPrice } else { // User specified 1559 gas fields (or none), use those - gasFeeCap = new(uint256.Int).Set(baseFee) + gasFeeCap = new(uint256.Int) if args.MaxFeePerGas != nil { overflow := gasFeeCap.SetFromBig(args.MaxFeePerGas.ToInt()) if overflow { diff --git a/turbo/jsonrpc/eth_call.go b/turbo/jsonrpc/eth_call.go index 8527f81f2cd..92c2fb6fc6b 100644 --- a/turbo/jsonrpc/eth_call.go +++ b/turbo/jsonrpc/eth_call.go @@ -177,8 +177,6 @@ func (api *APIImpl) EstimateGas(ctx context.Context, argsOrNil *ethapi2.CallArgs feeCap = args.GasPrice.ToInt() } else if args.MaxFeePerGas != nil { feeCap = args.MaxFeePerGas.ToInt() - } else if header.BaseFee != nil { - feeCap = new(big.Int).Set(header.BaseFee) } else { feeCap = libcommon.Big0 } diff --git a/turbo/transactions/tracing.go b/turbo/transactions/tracing.go index eeb57244517..2ef0836913a 100644 --- a/turbo/transactions/tracing.go +++ b/turbo/transactions/tracing.go @@ -160,7 +160,7 @@ func ExecuteTraceTx( execCb func(evm *vm.EVM, refunds bool) (*evmtypes.ExecutionResult, error), ) error { // Run the transaction with tracing enabled. - evm := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vm.Config{Debug: true, Tracer: tracer}) + evm := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true}) var refunds = true if config != nil && config.NoRefunds != nil && *config.NoRefunds {