Skip to content

Commit

Permalink
Move GasLimit handling into the header package (#822)
Browse files Browse the repository at this point in the history
  • Loading branch information
StephenButtolph authored Feb 24, 2025
1 parent 2dd2fb5 commit 3d2037f
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 67 deletions.
25 changes: 2 additions & 23 deletions consensus/dummy/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"math/big"
"time"

"github.com/ava-labs/avalanchego/utils/math"
"github.com/ava-labs/avalanchego/utils/timer/mockable"
"github.com/ava-labs/coreth/consensus"
"github.com/ava-labs/coreth/consensus/misc/eip4844"
Expand All @@ -22,8 +21,6 @@ import (
"github.com/ethereum/go-ethereum/common"

customheader "github.com/ava-labs/coreth/plugin/evm/header"
"github.com/ava-labs/coreth/plugin/evm/upgrade/ap1"
"github.com/ava-labs/coreth/plugin/evm/upgrade/cortina"
)

var (
Expand Down Expand Up @@ -114,31 +111,13 @@ func NewFullFaker() *DummyEngine {
}

func (eng *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, header *types.Header, parent *types.Header) error {
// Verify that the gas limit is <= 2^63-1
if header.GasLimit > params.MaxGasLimit {
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)
}
// Verify that the gasUsed is <= gasLimit
if header.GasUsed > header.GasLimit {
return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
}
if config.IsCortina(header.Time) {
if header.GasLimit != cortina.GasLimit {
return fmt.Errorf("expected gas limit to be %d in Cortina, but found %d", cortina.GasLimit, header.GasLimit)
}
} else if config.IsApricotPhase1(header.Time) {
if header.GasLimit != ap1.GasLimit {
return fmt.Errorf("expected gas limit to be %d in ApricotPhase1, but found %d", ap1.GasLimit, header.GasLimit)
}
} else {
// Verify that the gas limit remains within allowed bounds
diff := math.AbsDiff(parent.GasLimit, header.GasLimit)
limit := parent.GasLimit / params.GasLimitBoundDivisor
if diff >= limit || header.GasLimit < params.MinGasLimit {
return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit)
}
if err := customheader.VerifyGasLimit(config, parent, header); err != nil {
return err
}

// Verify header.Extra matches the expected value.
expectedExtraPrefix, err := customheader.ExtraPrefix(config, parent, header.Time)
if err != nil {
Expand Down
12 changes: 1 addition & 11 deletions core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ import (
"github.com/ava-labs/coreth/core/vm"
"github.com/ava-labs/coreth/params"
"github.com/ava-labs/coreth/plugin/evm/header"
"github.com/ava-labs/coreth/plugin/evm/upgrade/ap1"
"github.com/ava-labs/coreth/plugin/evm/upgrade/cortina"
"github.com/ava-labs/coreth/triedb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
Expand Down Expand Up @@ -375,15 +373,7 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int,
func (cm *chainMaker) makeHeader(parent *types.Block, gap uint64, state *state.StateDB, engine consensus.Engine) *types.Header {
time := parent.Time() + gap // block time is fixed at [gap] seconds

var gasLimit uint64
if cm.config.IsCortina(time) {
gasLimit = cortina.GasLimit
} else if cm.config.IsApricotPhase1(time) {
gasLimit = ap1.GasLimit
} else {
gasLimit = CalcGasLimit(parent.GasUsed(), parent.GasLimit(), parent.GasLimit(), parent.GasLimit())
}

gasLimit := header.GasLimit(cm.config, parent.Header(), time)
baseFee, err := header.BaseFee(cm.config, parent.Header(), time)
if err != nil {
panic(err)
Expand Down
16 changes: 1 addition & 15 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ import (
"github.com/ava-labs/coreth/core/vm"
"github.com/ava-labs/coreth/params"
"github.com/ava-labs/coreth/plugin/evm/header"
"github.com/ava-labs/coreth/plugin/evm/upgrade/ap1"
"github.com/ava-labs/coreth/plugin/evm/upgrade/cortina"
"github.com/ava-labs/coreth/precompile/precompileconfig"
"github.com/ava-labs/coreth/predicate"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -148,19 +146,7 @@ func (w *worker) commitNewWork(predicateContext *precompileconfig.PredicateConte
timestamp = parent.Time
}

var gasLimit uint64
if w.chainConfig.IsCortina(timestamp) {
gasLimit = cortina.GasLimit
} else if w.chainConfig.IsApricotPhase1(timestamp) {
gasLimit = ap1.GasLimit
} else {
// The gas limit is set in phase1 to [ap1.GasLimit] because the ceiling
// and floor were set to the same value such that the gas limit
// converged to it. Since this is hardcoded now, we remove the ability
// to configure it.
gasLimit = core.CalcGasLimit(parent.GasUsed, parent.GasLimit, ap1.GasLimit, ap1.GasLimit)
}

gasLimit := header.GasLimit(w.chainConfig, parent, timestamp)
baseFee, err := header.BaseFee(w.chainConfig, parent, timestamp)
if err != nil {
return nil, fmt.Errorf("failed to calculate new base fee: %w", err)
Expand Down
18 changes: 0 additions & 18 deletions plugin/evm/block_verification.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"github.com/ava-labs/coreth/plugin/evm/upgrade/ap0"
"github.com/ava-labs/coreth/plugin/evm/upgrade/ap1"
"github.com/ava-labs/coreth/plugin/evm/upgrade/ap5"
"github.com/ava-labs/coreth/plugin/evm/upgrade/cortina"
"github.com/ava-labs/coreth/trie"
"github.com/ava-labs/coreth/utils"
)
Expand Down Expand Up @@ -109,23 +108,6 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error {
return fmt.Errorf("invalid mix digest: %v", ethHeader.MixDigest)
}

// Enforce static gas limit after ApricotPhase1 (prior to ApricotPhase1 it's handled in processing).
if rules.IsCortina {
if ethHeader.GasLimit != cortina.GasLimit {
return fmt.Errorf(
"expected gas limit to be %d after cortina but got %d",
cortina.GasLimit, ethHeader.GasLimit,
)
}
} else if rules.IsApricotPhase1 {
if ethHeader.GasLimit != ap1.GasLimit {
return fmt.Errorf(
"expected gas limit to be %d after apricot phase 1 but got %d",
ap1.GasLimit, ethHeader.GasLimit,
)
}
}

// Verify the extra data is well-formed.
if err := header.VerifyExtra(rules.AvalancheRules, ethHeader.Extra); err != nil {
return err
Expand Down
88 changes: 88 additions & 0 deletions plugin/evm/header/gas_limit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// (c) 2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package header

import (
"errors"
"fmt"

"github.com/ava-labs/avalanchego/utils/math"
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/params"
"github.com/ava-labs/coreth/plugin/evm/upgrade/ap1"
"github.com/ava-labs/coreth/plugin/evm/upgrade/cortina"
)

var errInvalidGasLimit = errors.New("invalid gas limit")

// GasLimit takes the previous header and the timestamp of its child block and
// calculates the gas limit for the child block.
func GasLimit(
config *params.ChainConfig,
parent *types.Header,
timestamp uint64,
) uint64 {
switch {
case config.IsCortina(timestamp):
return cortina.GasLimit
case config.IsApricotPhase1(timestamp):
return ap1.GasLimit
default:
// The gas limit prior Apricot Phase 1 started at the genesis value and
// migrated towards the [ap1.GasLimit] following the `core.CalcGasLimit`
// updates. However, since all chains have activated Apricot Phase 1,
// this code is not used in production. To avoid a dependency on the
// `core` package, this code is modified to just return the parent gas
// limit; which was valid to do prior to Apricot Phase 1.
return parent.GasLimit
}
}

// VerifyGasLimit verifies that the gas limit for the header is valid.
func VerifyGasLimit(
config *params.ChainConfig,
parent *types.Header,
header *types.Header,
) error {
switch {
case config.IsCortina(header.Time):
if header.GasLimit != cortina.GasLimit {
return fmt.Errorf("%w: expected to be %d in Cortina, but found %d",
errInvalidGasLimit,
cortina.GasLimit,
header.GasLimit,
)
}
case config.IsApricotPhase1(header.Time):
if header.GasLimit != ap1.GasLimit {
return fmt.Errorf("%w: expected to be %d in ApricotPhase1, but found %d",
errInvalidGasLimit,
ap1.GasLimit,
header.GasLimit,
)
}
default:
if header.GasLimit < params.MinGasLimit || header.GasLimit > params.MaxGasLimit {
return fmt.Errorf("%w: %d not in range [%d, %d]",
errInvalidGasLimit,
header.GasLimit,
params.MinGasLimit,
params.MaxGasLimit,
)
}

// Verify that the gas limit remains within allowed bounds
diff := math.AbsDiff(parent.GasLimit, header.GasLimit)
limit := parent.GasLimit / params.GasLimitBoundDivisor
if diff >= limit {
return fmt.Errorf("%w: have %d, want %d += %d",
errInvalidGasLimit,
header.GasLimit,
parent.GasLimit,
limit,
)
}
}
return nil
}
145 changes: 145 additions & 0 deletions plugin/evm/header/gas_limit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// (c) 2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package header

import (
"testing"

"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/params"
"github.com/ava-labs/coreth/plugin/evm/upgrade/ap1"
"github.com/ava-labs/coreth/plugin/evm/upgrade/cortina"
"github.com/stretchr/testify/require"
)

func TestGasLimit(t *testing.T) {
tests := []struct {
name string
upgrades params.NetworkUpgrades
parent *types.Header
timestamp uint64
want uint64
}{
{
name: "cortina",
upgrades: params.TestCortinaChainConfig.NetworkUpgrades,
want: cortina.GasLimit,
},
{
name: "ap1",
upgrades: params.TestApricotPhase1Config.NetworkUpgrades,
want: ap1.GasLimit,
},
{
name: "launch",
upgrades: params.TestLaunchConfig.NetworkUpgrades,
parent: &types.Header{
GasLimit: 1,
},
want: 1, // Same as parent
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
config := &params.ChainConfig{
NetworkUpgrades: test.upgrades,
}
got := GasLimit(config, test.parent, test.timestamp)
require.Equal(t, test.want, got)
})
}
}

func TestVerifyGasLimit(t *testing.T) {
tests := []struct {
name string
upgrades params.NetworkUpgrades
parent *types.Header
header *types.Header
want error
}{
{
name: "cortina_valid",
upgrades: params.TestCortinaChainConfig.NetworkUpgrades,
header: &types.Header{
GasLimit: cortina.GasLimit,
},
},
{
name: "cortina_invalid",
upgrades: params.TestCortinaChainConfig.NetworkUpgrades,
header: &types.Header{
GasLimit: cortina.GasLimit + 1,
},
want: errInvalidGasLimit,
},
{
name: "ap1_valid",
upgrades: params.TestApricotPhase1Config.NetworkUpgrades,
header: &types.Header{
GasLimit: ap1.GasLimit,
},
},
{
name: "ap1_invalid",
upgrades: params.TestApricotPhase1Config.NetworkUpgrades,
header: &types.Header{
GasLimit: ap1.GasLimit + 1,
},
want: errInvalidGasLimit,
},
{
name: "launch_valid",
upgrades: params.TestLaunchConfig.NetworkUpgrades,
parent: &types.Header{
GasLimit: 50_000,
},
header: &types.Header{
GasLimit: 50_001, // Gas limit is allowed to change by 1/1024
},
},
{
name: "launch_too_low",
upgrades: params.TestLaunchConfig.NetworkUpgrades,
parent: &types.Header{
GasLimit: params.MinGasLimit,
},
header: &types.Header{
GasLimit: params.MinGasLimit - 1,
},
want: errInvalidGasLimit,
},
{
name: "launch_too_high",
upgrades: params.TestLaunchConfig.NetworkUpgrades,
parent: &types.Header{
GasLimit: params.MaxGasLimit,
},
header: &types.Header{
GasLimit: params.MaxGasLimit + 1,
},
want: errInvalidGasLimit,
},
{
name: "change_too_large",
upgrades: params.TestLaunchConfig.NetworkUpgrades,
parent: &types.Header{
GasLimit: params.MinGasLimit,
},
header: &types.Header{
GasLimit: params.MaxGasLimit,
},
want: errInvalidGasLimit,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
config := &params.ChainConfig{
NetworkUpgrades: test.upgrades,
}
err := VerifyGasLimit(config, test.parent, test.header)
require.ErrorIs(t, err, test.want)
})
}
}

0 comments on commit 3d2037f

Please sign in to comment.