mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 19:00:46 +00:00
internal/ethapi: fix gas cap for eth_simulateV1 (#33952)
Fixes a regression in #33593 where a block gas limit > gasCap resulted in more execution than the gas cap.
This commit is contained in:
parent
fc8c10476d
commit
ce64ab44ed
4 changed files with 46 additions and 15 deletions
|
|
@ -849,16 +849,12 @@ func (api *BlockChainAPI) SimulateV1(ctx context.Context, opts simOpts, blockNrO
|
||||||
if state == nil || err != nil {
|
if state == nil || err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
gasCap := api.b.RPCGasCap()
|
|
||||||
if gasCap == 0 {
|
|
||||||
gasCap = gomath.MaxUint64
|
|
||||||
}
|
|
||||||
sim := &simulator{
|
sim := &simulator{
|
||||||
b: api.b,
|
b: api.b,
|
||||||
state: state,
|
state: state,
|
||||||
base: base,
|
base: base,
|
||||||
chainConfig: api.b.ChainConfig(),
|
chainConfig: api.b.ChainConfig(),
|
||||||
gasRemaining: gasCap,
|
budget: newGasBudget(api.b.RPCGasCap()),
|
||||||
traceTransfers: opts.TraceTransfers,
|
traceTransfers: opts.TraceTransfers,
|
||||||
validate: opts.Validation,
|
validate: opts.Validation,
|
||||||
fullTx: opts.ReturnFullTransactions,
|
fullTx: opts.ReturnFullTransactions,
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -2507,7 +2506,7 @@ func TestSimulateV1ChainLinkage(t *testing.T) {
|
||||||
state: stateDB,
|
state: stateDB,
|
||||||
base: baseHeader,
|
base: baseHeader,
|
||||||
chainConfig: backend.ChainConfig(),
|
chainConfig: backend.ChainConfig(),
|
||||||
gasRemaining: math.MaxUint64,
|
budget: newGasBudget(0),
|
||||||
traceTransfers: false,
|
traceTransfers: false,
|
||||||
validate: false,
|
validate: false,
|
||||||
fullTx: false,
|
fullTx: false,
|
||||||
|
|
@ -2592,7 +2591,7 @@ func TestSimulateV1TxSender(t *testing.T) {
|
||||||
state: stateDB,
|
state: stateDB,
|
||||||
base: baseHeader,
|
base: baseHeader,
|
||||||
chainConfig: backend.ChainConfig(),
|
chainConfig: backend.ChainConfig(),
|
||||||
gasRemaining: math.MaxUint64,
|
budget: newGasBudget(0),
|
||||||
traceTransfers: false,
|
traceTransfers: false,
|
||||||
validate: false,
|
validate: false,
|
||||||
fullTx: true,
|
fullTx: true,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -150,6 +151,39 @@ func (m *simChainHeadReader) GetHeaderByHash(hash common.Hash) *types.Header {
|
||||||
return header
|
return header
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gasBudget tracks the remaining gas allowed across all simulated blocks.
|
||||||
|
// It enforces the RPC-level gas cap to prevent DoS.
|
||||||
|
type gasBudget struct {
|
||||||
|
remaining uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// newGasBudget creates a gas budget with the given cap.
|
||||||
|
// A cap of 0 is treated as unlimited.
|
||||||
|
func newGasBudget(cap uint64) *gasBudget {
|
||||||
|
if cap == 0 {
|
||||||
|
cap = math.MaxUint64
|
||||||
|
}
|
||||||
|
return &gasBudget{remaining: cap}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cap returns the given gas value clamped to the remaining budget.
|
||||||
|
func (b *gasBudget) cap(gas uint64) uint64 {
|
||||||
|
if gas > b.remaining {
|
||||||
|
return b.remaining
|
||||||
|
}
|
||||||
|
return gas
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume deducts the given amount from the budget.
|
||||||
|
// Returns an error if the amount exceeds the remaining budget.
|
||||||
|
func (b *gasBudget) consume(amount uint64) error {
|
||||||
|
if amount > b.remaining {
|
||||||
|
return fmt.Errorf("RPC gas cap exhausted: need %d, remaining %d", amount, b.remaining)
|
||||||
|
}
|
||||||
|
b.remaining -= amount
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// simulator is a stateful object that simulates a series of blocks.
|
// simulator is a stateful object that simulates a series of blocks.
|
||||||
// it is not safe for concurrent use.
|
// it is not safe for concurrent use.
|
||||||
type simulator struct {
|
type simulator struct {
|
||||||
|
|
@ -157,7 +191,7 @@ type simulator struct {
|
||||||
state *state.StateDB
|
state *state.StateDB
|
||||||
base *types.Header
|
base *types.Header
|
||||||
chainConfig *params.ChainConfig
|
chainConfig *params.ChainConfig
|
||||||
gasRemaining uint64
|
budget *gasBudget
|
||||||
traceTransfers bool
|
traceTransfers bool
|
||||||
validate bool
|
validate bool
|
||||||
fullTx bool
|
fullTx bool
|
||||||
|
|
@ -318,10 +352,9 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
||||||
|
|
||||||
// Make sure the gas cap is still enforced. It's only for
|
// Make sure the gas cap is still enforced. It's only for
|
||||||
// internally protection.
|
// internally protection.
|
||||||
if sim.gasRemaining < result.UsedGas {
|
if err := sim.budget.consume(result.UsedGas); err != nil {
|
||||||
return nil, nil, nil, fmt.Errorf("gas cap reached, required: %d, remaining: %d", result.UsedGas, sim.gasRemaining)
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
sim.gasRemaining -= result.UsedGas
|
|
||||||
|
|
||||||
logs := tracer.Logs()
|
logs := tracer.Logs()
|
||||||
callRes := simCallResult{ReturnValue: result.Return(), Logs: logs, GasUsed: hexutil.Uint64(result.UsedGas), MaxUsedGas: hexutil.Uint64(result.MaxUsedGas)}
|
callRes := simCallResult{ReturnValue: result.Return(), Logs: logs, GasUsed: hexutil.Uint64(result.UsedGas), MaxUsedGas: hexutil.Uint64(result.MaxUsedGas)}
|
||||||
|
|
@ -405,6 +438,10 @@ func (sim *simulator) sanitizeCall(call *TransactionArgs, state vm.StateDB, head
|
||||||
if remaining < uint64(*call.Gas) {
|
if remaining < uint64(*call.Gas) {
|
||||||
return &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: remaining: %d, required: %d", remaining, *call.Gas)}
|
return &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: remaining: %d, required: %d", remaining, *call.Gas)}
|
||||||
}
|
}
|
||||||
|
// Clamp to the cross-block gas budget.
|
||||||
|
gas := sim.budget.cap(uint64(*call.Gas))
|
||||||
|
call.Gas = (*hexutil.Uint64)(&gas)
|
||||||
|
|
||||||
return call.CallDefaults(0, header.BaseFee, sim.chainConfig.ChainID)
|
return call.CallDefaults(0, header.BaseFee, sim.chainConfig.ChainID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package ethapi
|
package ethapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -82,8 +81,8 @@ func TestSimulateSanitizeBlockOrder(t *testing.T) {
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
sim := &simulator{
|
sim := &simulator{
|
||||||
base: &types.Header{Number: big.NewInt(int64(tc.baseNumber)), Time: tc.baseTimestamp},
|
base: &types.Header{Number: big.NewInt(int64(tc.baseNumber)), Time: tc.baseTimestamp},
|
||||||
gasRemaining: math.MaxUint64,
|
budget: newGasBudget(0),
|
||||||
}
|
}
|
||||||
res, err := sim.sanitizeChain(tc.blocks)
|
res, err := sim.sanitizeChain(tc.blocks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue