all: fix rebasing issues

This commit is contained in:
Marius van der Wijden 2026-03-24 09:48:04 +01:00
parent 85671f1c4a
commit b3113baec8
10 changed files with 156 additions and 113 deletions

View file

@ -5,10 +5,10 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz
# version:spec-tests-bal v5.2.0
# version:spec-tests-bal v5.5.1
# https://github.com/ethereum/execution-spec-tests/releases
# https://github.com/ethereum/execution-spec-tests/releases/download/bal%40v5.2.0
a0dc6f4070da4dab4831f4e8fddfc087c10a5120a6a683257e963d9c6713378a fixtures_bal.tar.gz
# https://github.com/ethereum/execution-spec-tests/releases/download/bal%40v5.5.1
79f81379bc456b9f05d4e7298eba939855d0147b525cd2cadd1206513284ab9e fixtures_bal.tar.gz
# version:golang 1.25.7
# https://go.dev/dl/

View file

@ -2402,6 +2402,11 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
bc.reportBadBlock(block, res, err)
return nil, err
}
// EIP-7928: Validate BAL items do not exceed block gas limit
if err := computedAccessList.ValidateGasLimit(block.Header().GasLimit); err != nil {
bc.reportBadBlock(block, res, err)
return nil, err
}
if block.AccessList() == nil {
// attach the computed access list to the block so it gets persisted
// when the block is written to disk

View file

@ -95,28 +95,25 @@ func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, tExecStar
})
var (
// total gas used not applying refunds
blockGas = uint64(0)
// total gas used applying refunds
execGas = uint64(0)
// Per-dimension cumulative sums for 2D block gas (EIP-8037).
sumRegular uint64
sumState uint64
cumulativeReceipt uint64 // cumulative receipt gas (what users pay)
)
var allLogs []*types.Log
var allReceipts []*types.Receipt
for _, result := range results {
blockGas += result.blockGas
execGas += result.execGas
result.receipt.CumulativeGasUsed = blockGas
if blockGas > header.GasLimit {
return &ProcessResultWithMetrics{
ProcessResult: &ProcessResult{Error: fmt.Errorf("gas limit exceeded")},
}
}
sumRegular += result.txRegular
sumState += result.txState
cumulativeReceipt += result.execGas
result.receipt.CumulativeGasUsed = cumulativeReceipt
allLogs = append(allLogs, result.receipt.Logs...)
allReceipts = append(allReceipts, result.receipt)
}
// Block gas limit is enforced against usedGas (pre-refund after Amsterdam, post-refund before).
if blockGas > header.GasLimit {
// Block gas = max(sum_regular, sum_state) per EIP-8037.
blockGasUsed := max(sumRegular, sumState)
if blockGasUsed > header.GasLimit {
return &ProcessResultWithMetrics{
ProcessResult: &ProcessResult{Error: fmt.Errorf("gas limit exceeded")},
}
@ -177,7 +174,7 @@ func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, tExecStar
Receipts: allReceipts,
Requests: requests,
Logs: allLogs,
GasUsed: execGas,
GasUsed: blockGasUsed,
},
PostProcessTime: tPostprocess,
ExecTime: tExec,
@ -191,6 +188,10 @@ type txExecResult struct {
blockGas uint64
execGas uint64
// Per-tx dimensional gas for Amsterdam 2D gas accounting (EIP-8037).
txRegular uint64
txState uint64
stateReads bal.StateAccesses
}
@ -290,7 +291,6 @@ func (p *ParallelStateProcessor) execTx(block *types.Block, tx *types.Transactio
gp := NewGasPool(block.GasLimit())
db.SetTxContext(tx.Hash(), balIdx-1)
var gasUsed uint64
mut, receipt, err := ApplyTransactionWithEVM(msg, gp, db, block.Number(), block.Hash(), context.Time, tx, evm)
if err != nil {
err := fmt.Errorf("could not apply tx %d [%v]: %w", balIdx, tx.Hash().Hex(), err)
@ -303,11 +303,14 @@ func (p *ParallelStateProcessor) execTx(block *types.Block, tx *types.Transactio
return &txExecResult{err: err}
}
txRegular, txState := gp.AmsterdamDimensions()
return &txExecResult{
idx: balIdx,
receipt: receipt,
execGas: receipt.GasUsed,
blockGas: gasUsed,
blockGas: gp.Used(),
txRegular: txRegular,
txState: txState,
stateReads: db.Reader().(state.StateReaderTracker).GetStateAccessList(),
}
}

View file

@ -131,6 +131,30 @@ func (e BlockAccessList) Validate(blockTxCount int) error {
return nil
}
// ValidateGasLimit checks that the number of BAL items does not exceed the
// block gas limit divided by the per-item cost (EIP-7928).
func (e BlockAccessList) ValidateGasLimit(blockGasLimit uint64) error {
var balItems uint64
for _, account := range e {
// Count each address as one item
balItems++
// Count unique storage keys across both reads and writes
uniqueSlots := make(map[common.Hash]struct{})
for _, sc := range account.StorageChanges {
uniqueSlots[sc.Slot.ToHash()] = struct{}{}
}
for _, sr := range account.StorageReads {
uniqueSlots[sr.ToHash()] = struct{}{}
}
balItems += uint64(len(uniqueSlots))
}
limit := blockGasLimit / params.GasBlockAccessListItem
if balItems > limit {
return fmt.Errorf("block access list exceeds gas limit: %d items exceeds limit of %d", balItems, limit)
}
return nil
}
// Hash computes the keccak256 hash of the access list
func (e *BlockAccessList) Hash() common.Hash {
var enc bytes.Buffer
@ -391,7 +415,7 @@ func (e *AccountAccess) validate(blockTxCount int) error {
// validate that code changes could plausibly be correct (none exceed
// max code size of a contract)
for _, codeChange := range e.CodeChanges {
if len(codeChange.Code) > params.MaxCodeSize {
if len(codeChange.Code) > params.MaxCodeSizeAmsterdam {
return fmt.Errorf("code change contained oversized code")
}
}

View file

@ -504,7 +504,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasCosts, value *
}(gas)
}
if err != nil {
return nil, common.Address{}, GasCosts{}, GasUsed{}, err
return nil, common.Address{}, gas, GasUsed{}, err
}
// Charge the contract creation init gas in verkle mode
@ -538,8 +538,10 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasCosts, value *
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
}
// Burn all gas on collision
collisionUsed := GasUsed{RegularGasUsed: gas.RegularGas}
gas.RegularGas = 0
return nil, common.Address{}, gas, GasUsed{}, ErrContractAddressCollision
return nil, common.Address{}, gas, collisionUsed, ErrContractAddressCollision
}
// Create a new account on the state only if the object was not present.
// It might be possible the contract code is deployed to a pre-existent
@ -584,6 +586,9 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasCosts, value *
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(contract.Gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
}
contract.GasUsed.RegularGasUsed += contract.Gas.RegularGas
contract.Gas.RegularGas = 0
}
@ -605,35 +610,39 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
return ret, ErrInvalidCode
}
if !evm.chainRules.IsEIP4762 {
if evm.chainRules.IsAmsterdam {
// EIP-8037: Split code deposit into state gas (code storage) and
// regular gas (keccak256 hashing).
stateGas := GasCosts{StateGas: uint64(len(ret)) * evm.Context.CostPerGasByte}
if !contract.UseGas(stateGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
return ret, ErrCodeStoreOutOfGas
}
regularGas := GasCosts{RegularGas: toWordSize(uint64(len(ret))) * params.Keccak256WordGas}
if !contract.UseGas(regularGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
return ret, ErrCodeStoreOutOfGas
}
} else {
createDataGas := GasCosts{RegularGas: uint64(len(ret)) * params.CreateDataGas}
if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
return ret, ErrCodeStoreOutOfGas
}
if evm.chainRules.IsAmsterdam {
// Check max code size BEFORE charging gas so over-max code
// does not consume state gas (which would inflate tx_state).
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
return ret, err
}
} else {
// EIP-8037: Charge regular gas (keccak256 hash) first, then state gas
// (code storage). Regular-before-state prevents reservoir inflation.
regularGas := GasCosts{RegularGas: toWordSize(uint64(len(ret))) * params.Keccak256WordGas}
if !contract.UseGas(regularGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
return ret, ErrCodeStoreOutOfGas
}
stateGas := GasCosts{StateGas: uint64(len(ret)) * evm.Context.CostPerGasByte}
if !contract.UseGas(stateGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
return ret, ErrCodeStoreOutOfGas
}
} else if evm.chainRules.IsEIP4762 {
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas.RegularGas)
contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
if len(ret) > 0 && (consumed < wanted) {
return ret, ErrCodeStoreOutOfGas
}
}
// Verify max code size after gas calculation.
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
return ret, err
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
return ret, err
}
} else {
createDataGas := GasCosts{RegularGas: uint64(len(ret)) * params.CreateDataGas}
if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
return ret, ErrCodeStoreOutOfGas
}
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
return ret, err
}
}
if len(ret) > 0 {

View file

@ -647,17 +647,13 @@ func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
}
if original == current {
if original == (common.Hash{}) { // create slot (2.1.1)
// EIP-8037: Charge state gas first (before regular gas), matching the
// spec's charge_state_gas → charge_gas ordering. This ensures that
// state_gas_used is recorded even if the subsequent regular gas charge
// fails with OOG.
stateGas := GasCosts{StateGas: params.StorageCreationSize * evm.Context.CostPerGasByte}
if contract.Gas.Underflow(stateGas) {
return GasCosts{}, errors.New("out of gas for state gas")
}
contract.GasUsed.Add(stateGas)
contract.Gas.Sub(stateGas)
return GasCosts{RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929}, nil
// EIP-8037: Return both regular and state gas. The interpreter
// charges regular gas before state gas, preventing reservoir
// inflation when the regular charge OOGs.
return GasCosts{
RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929,
StateGas: params.StorageCreationSize * evm.Context.CostPerGasByte,
}, nil
}
if value == (common.Hash{}) { // delete slot (2.1.2b)
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529)

View file

@ -226,7 +226,20 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
}
// for tracing: this gas consumption event is emitted below in the debug section.
if contract.Gas.Underflow(dynamicCost) {
if evm.chainRules.IsAmsterdam && dynamicCost.StateGas > 0 {
// EIP-8037: charge regular gas before state gas.
if contract.Gas.RegularGas < dynamicCost.RegularGas {
return nil, ErrOutOfGas
}
contract.GasUsed.RegularGasUsed += dynamicCost.RegularGas
contract.Gas.RegularGas -= dynamicCost.RegularGas
stateOnly := GasCosts{StateGas: dynamicCost.StateGas}
if contract.Gas.Underflow(stateOnly) {
return nil, ErrOutOfGas
}
contract.GasUsed.Add(stateOnly)
contract.Gas.Sub(stateOnly)
} else if contract.Gas.Underflow(dynamicCost) {
return nil, ErrOutOfGas
} else {
contract.GasUsed.Add(dynamicCost)

View file

@ -354,6 +354,10 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
// part of the dynamic gas. This will ensure it is correctly reported to
// tracers.
contract.Gas.RegularGas += eip2929Cost + eip7702Cost
// Undo the RegularGasUsed increments from the direct UseGas charges,
// since this gas will be re-charged via the returned cost.
contract.GasUsed.RegularGasUsed -= eip2929Cost
contract.GasUsed.RegularGasUsed -= eip7702Cost
// Aggregate the gas costs from all components, including EIP-2929, EIP-7702,
// the CALL opcode itself, and the cost incurred by nested calls.
@ -385,7 +389,7 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc
eip7702Cost uint64
addr = common.Address(stack.Back(1).Bytes20())
)
// EIP-2929 cold access check.
// EIP-2929 cold access check (regular gas, directly).
if !evm.StateDB.AddressInAccessList(addr) {
evm.StateDB.AddAddressToAccessList(addr)
eip2929Cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
@ -399,18 +403,15 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc
if err != nil {
return GasCosts{}, err
}
// Early OOG check before stateful operations.
if contract.Gas.RegularGas < intrinsicCost {
// Charge intrinsic cost directly (regular gas). This must happen
// BEFORE state gas to prevent reservoir inflation, and also serves
// as the OOG guard before stateful operations.
if !contract.UseGas(GasCosts{RegularGas: intrinsicCost}, evm.Config.Tracer, tracing.GasChangeCallOpCode) {
return GasCosts{}, ErrOutOfGas
}
// Compute state gas (new account creation as state gas).
stateGas, err := stateGasFunc(evm, contract, stack, mem, memorySize)
if err != nil {
return GasCosts{}, err
}
// EIP-7702 delegation check.
// EIP-7702 delegation check (regular gas, directly).
if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
if evm.StateDB.AddressInAccessList(target) {
eip7702Cost = params.WarmStorageReadCostEIP2929
@ -423,8 +424,11 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc
}
}
// Charge state gas directly before callGas computation. State gas that
// spills to regular gas must reduce the gas available for callGasTemp.
// Compute and charge state gas (new account creation) AFTER regular gas.
stateGas, err := stateGasFunc(evm, contract, stack, mem, memorySize)
if err != nil {
return GasCosts{}, err
}
if stateGas.StateGas > 0 {
stateGasCost := GasCosts{StateGas: stateGas.StateGas}
if contract.Gas.Underflow(stateGasCost) {
@ -435,16 +439,15 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc
}
// Calculate the gas budget for the nested call (63/64 rule).
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost, stack.Back(0))
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, 0, stack.Back(0))
if err != nil {
return GasCosts{}, err
}
// Temporarily add gas charges back for tracer reporting.
contract.Gas.RegularGas += eip2929Cost + eip7702Cost
// Undo GasUsed increments from direct UseGas charges.
contract.GasUsed.RegularGasUsed -= eip2929Cost
contract.GasUsed.RegularGasUsed -= eip7702Cost
// Temporarily undo direct regular charges for tracer reporting.
// The interpreter will charge the returned totalCost.
contract.Gas.RegularGas += eip2929Cost + eip7702Cost + intrinsicCost
contract.GasUsed.RegularGasUsed -= eip2929Cost + eip7702Cost + intrinsicCost
// Aggregate total cost.
var (

View file

@ -193,8 +193,7 @@ const (
StorageCreationSize = 32
AuthorizationCreationSize = 23
// TODO: Add when EIP-7928 is implemented
// GasBlockAccessListItem = 2000 // EIP-7928: gas cost per BAL item for gas limit check
GasBlockAccessListItem = 2000 // EIP-7928: gas cost per BAL item for gas limit check
)
// Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation

View file

@ -490,7 +490,7 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
},
},
"Amsterdam": {
@ -518,11 +518,12 @@ var Forks = map[string]*params.ChainConfig{
AmsterdamTime: u64(0),
DepositContractAddress: params.MainnetChainConfig.DepositContractAddress,
BlobScheduleConfig: &params.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
Amsterdam: params.DefaultBPO2BlobConfig,
},
},
"BPO2ToAmsterdamAtTime15k": {
@ -550,11 +551,12 @@ var Forks = map[string]*params.ChainConfig{
AmsterdamTime: u64(15_000),
DepositContractAddress: params.MainnetChainConfig.DepositContractAddress,
BlobScheduleConfig: &params.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
Amsterdam: params.DefaultBPO2BlobConfig,
},
},
"OsakaToBPO1AtTime15k": {
@ -583,7 +585,7 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
},
},
"BPO2": {
@ -613,8 +615,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
},
},
"BPO1ToBPO2AtTime15k": {
@ -644,8 +646,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
},
},
"BPO3": {
@ -676,8 +678,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig,
},
},
@ -709,8 +711,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig,
},
},
@ -743,8 +745,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig,
BPO4: params.DefaultBPO4BlobConfig,
},
@ -778,8 +780,8 @@ var Forks = map[string]*params.ChainConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
BPO1: bpo1BlobConfig,
BPO2: bpo2BlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig,
BPO4: params.DefaultBPO4BlobConfig,
},
@ -805,17 +807,6 @@ var Forks = map[string]*params.ChainConfig{
},
}
var bpo1BlobConfig = &params.BlobConfig{
Target: 9,
Max: 14,
UpdateFraction: 8832827,
}
var bpo2BlobConfig = &params.BlobConfig{
Target: 14,
Max: 21,
UpdateFraction: 13739630,
}
// AvailableForks returns the set of defined fork names
func AvailableForks() []string {