@@ -21,6 +21,7 @@ import (
2121 "encoding/json"
2222 "errors"
2323 "fmt"
24+ "math"
2425 "math/big"
2526 "time"
2627
@@ -150,14 +151,47 @@ func (m *simChainHeadReader) GetHeaderByHash(hash common.Hash) *types.Header {
150151 return header
151152}
152153
154+ // gasBudget tracks the remaining gas allowed across all simulated blocks.
155+ // It enforces the RPC-level gas cap to prevent DoS.
156+ type gasBudget struct {
157+ remaining uint64
158+ }
159+
160+ // newGasBudget creates a gas budget with the given cap.
161+ // A cap of 0 is treated as unlimited.
162+ func newGasBudget (cap uint64 ) * gasBudget {
163+ if cap == 0 {
164+ cap = math .MaxUint64
165+ }
166+ return & gasBudget {remaining : cap }
167+ }
168+
169+ // cap returns the given gas value clamped to the remaining budget.
170+ func (b * gasBudget ) cap (gas uint64 ) uint64 {
171+ if gas > b .remaining {
172+ return b .remaining
173+ }
174+ return gas
175+ }
176+
177+ // consume deducts the given amount from the budget.
178+ // Returns an error if the amount exceeds the remaining budget.
179+ func (b * gasBudget ) consume (amount uint64 ) error {
180+ if amount > b .remaining {
181+ return fmt .Errorf ("RPC gas cap exhausted: need %d, remaining %d" , amount , b .remaining )
182+ }
183+ b .remaining -= amount
184+ return nil
185+ }
186+
153187// simulator is a stateful object that simulates a series of blocks.
154188// it is not safe for concurrent use.
155189type simulator struct {
156190 b Backend
157191 state * state.StateDB
158192 base * types.Header
159193 chainConfig * params.ChainConfig
160- gasRemaining uint64
194+ budget * gasBudget
161195 traceTransfers bool
162196 validate bool
163197 fullTx bool
@@ -318,10 +352,9 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
318352
319353 // Make sure the gas cap is still enforced. It's only for
320354 // internally protection.
321- if sim . gasRemaining < result .UsedGas {
322- return nil , nil , nil , fmt . Errorf ( "gas cap reached, required: %d, remaining: %d" , result . UsedGas , sim . gasRemaining )
355+ if err := sim . budget . consume ( result .UsedGas ); err != nil {
356+ return nil , nil , nil , err
323357 }
324- sim .gasRemaining -= result .UsedGas
325358
326359 logs := tracer .Logs ()
327360 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
405438 if remaining < uint64 (* call .Gas ) {
406439 return & blockGasLimitReachedError {fmt .Sprintf ("block gas limit reached: remaining: %d, required: %d" , remaining , * call .Gas )}
407440 }
441+ // Clamp to the cross-block gas budget.
442+ gas := sim .budget .cap (uint64 (* call .Gas ))
443+ call .Gas = (* hexutil .Uint64 )(& gas )
444+
408445 return call .CallDefaults (0 , header .BaseFee , sim .chainConfig .ChainID )
409446}
410447
0 commit comments