diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index aea5d35035..a5f8dce178 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -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" @@ -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 ( @@ -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 { diff --git a/core/chain_makers.go b/core/chain_makers.go index b77ff39f76..ad52c626a7 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -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" @@ -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) diff --git a/miner/worker.go b/miner/worker.go index e505cacc34..b149e15444 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -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" @@ -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) diff --git a/plugin/evm/block_verification.go b/plugin/evm/block_verification.go index 423bb8f409..006833735c 100644 --- a/plugin/evm/block_verification.go +++ b/plugin/evm/block_verification.go @@ -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" ) @@ -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 diff --git a/plugin/evm/header/gas_limit.go b/plugin/evm/header/gas_limit.go new file mode 100644 index 0000000000..2733c3ef9c --- /dev/null +++ b/plugin/evm/header/gas_limit.go @@ -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 +} diff --git a/plugin/evm/header/gas_limit_test.go b/plugin/evm/header/gas_limit_test.go new file mode 100644 index 0000000000..f02735848f --- /dev/null +++ b/plugin/evm/header/gas_limit_test.go @@ -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 := ¶ms.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 := ¶ms.ChainConfig{ + NetworkUpgrades: test.upgrades, + } + err := VerifyGasLimit(config, test.parent, test.header) + require.ErrorIs(t, err, test.want) + }) + } +}