From 8cc54f2160c37e7fc2f05b4a5f3e28f1dcedf26f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 15 Jul 2025 10:12:40 +0200 Subject: [PATCH 01/32] params: WIP config2 --- params/chainparam.go | 58 ++++++ params/config2.go | 373 ++++++++++++++++++++++++++++++++++++++ params/config2_presets.go | 115 ++++++++++++ params/forks/forks.go | 130 ++++++++++--- params/registry.go | 85 +++++++++ 5 files changed, 732 insertions(+), 29 deletions(-) create mode 100644 params/chainparam.go create mode 100644 params/config2.go create mode 100644 params/config2_presets.go create mode 100644 params/registry.go diff --git a/params/chainparam.go b/params/chainparam.go new file mode 100644 index 00000000000..225558f3f7b --- /dev/null +++ b/params/chainparam.go @@ -0,0 +1,58 @@ +package params + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params/forks" +) + +// ChainID +type ChainID big.Int + +// TerminalTotalDifficulty (TTD) is the total difficulty value where +type TerminalTotalDifficulty big.Int + +func (v *TerminalTotalDifficulty) MarshalText() ([]byte, error) { + return (*big.Int)(v).MarshalText() +} + +func (v *TerminalTotalDifficulty) UnmarshalText(input []byte) error { + return (*big.Int)(v).UnmarshalText(input) +} + +// DepositContractAddress configures the location of the deposit contract. +type DepositContractAddress common.Address + +// This configures the EIP-4844 parameters across forks. +// There must be an entry for each fork +type BlobSchedule map[forks.Fork]BlobConfig + +// DAOForkSupport is the chain parameter that configures the DAO fork. +// true=supports or false=opposes the fork. +// The default value is true. +type DAOForkSupport bool + +func init() { + Define(Parameter[*ChainID]{ + Name: "chainId", + Optional: false, + }) + Define(Parameter[*TerminalTotalDifficulty]{ + Name: "terminalTotalDifficulty", + Optional: false, + }) + Define(Parameter[DAOForkSupport]{ + Name: "daoForkSupport", + Optional: true, + Default: true, + }) + Define(Parameter[BlobSchedule]{ + Name: "blobSchedule", + Optional: true, + }) + Define(Parameter[DepositContractAddress]{ + Name: "depositContractAddress", + Optional: true, + }) +} diff --git a/params/config2.go b/params/config2.go new file mode 100644 index 00000000000..3e4aeb3844e --- /dev/null +++ b/params/config2.go @@ -0,0 +1,373 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package params + +import ( + "bytes" + "encoding/json" + "fmt" + "maps" + "math/big" + "reflect" + "slices" + "strings" + + "github.com/ethereum/go-ethereum/params/forks" +) + +// Activations contains the block numbers/timestamps where hard forks activate. +type Activations map[forks.Fork]uint64 + +// Config2 represents the chain configuration. +type Config2 struct { + activation Activations + param map[reflect.Type]ParameterType +} + +func NewConfig2(activations Activations, param ...ParameterType) *Config2 { + cfg := &Config2{ + activation: maps.Clone(activations), + param: make(map[reflect.Type]ParameterType, len(param)), + } + for _, pv := range param { + info, ok := findParam(pv) + if !ok { + panic(fmt.Sprintf("undefined chain parameter type %T", pv)) + } + cfg.param[info.rtype] = pv + } + return cfg +} + +// Active reports whether the given fork is active for a block number/time. +func (cfg *Config2) Active(f forks.Fork, block, timestamp uint64) bool { + activation, ok := cfg.activation[f] + if f.BlockBased() { + return ok && block >= activation + } + return ok && timestamp >= activation +} + +func (cfg *Config2) Activation(f forks.Fork) (uint64, bool) { + a, ok := cfg.activation[f] + return a, ok +} + +// Scheduled says whether the fork is scheduled at all. +func (cfg *Config2) Scheduled(f forks.Fork) bool { + _, ok := cfg.activation[f] + return ok +} + +// SetActivations returns a new configuration with the given forks activated. +func (cfg *Config2) SetActivations(forks Activations) *Config2 { + newA := maps.Clone(cfg.activation) + maps.Copy(newA, forks) + return &Config2{activation: newA, param: cfg.param} +} + +// SetParam returns a new configuration with parameters modified. +func (cfg *Config2) SetParam(p ParameterType) *Config2 { + return &Config2{activation: cfg.activation} +} + +// LatestFork returns the latest time-based fork that would be active for the given time. +func (cfg *Config2) LatestFork(time uint64) forks.Fork { + londonBlock := cfg.activation[forks.London] + for _, f := range slices.Backward(forks.CanonOrder) { + if f.BlockBased() { + break + } + if cfg.Active(f, londonBlock, time) { + return f + } + } + return forks.Paris +} + +// MarshalJSON encodes the config as JSON. +func (cfg *Config2) MarshalJSON() ([]byte, error) { + m := make(map[string]any) + // params + for _, p := range cfg.param { + info, ok := findParam(p) + if !ok { + panic(fmt.Sprintf("unknown chain parameter %T", p)) + } + m[info.name] = p + } + // forks + for f, act := range cfg.activation { + var name string + if f.BlockBased() { + name = fmt.Sprintf("%sBlock", strings.ToLower(name)) + } else { + name = fmt.Sprintf("%sTime", strings.ToLower(name)) + } + m[name] = act + } + return json.Marshal(m) +} + +// MarshalJSON encodes the config as JSON. +func (cfg *Config2) UnmarshalJSON(input []byte) error { + dec := json.NewDecoder(bytes.NewReader(input)) + tok, err := dec.Token() + if err != nil { + return err + } + if tok != json.Delim('{') { + return fmt.Errorf("expected JSON object for chain configuration") + } + // Now we're in the object. + newcfg := Config2{ + activation: make(Activations), + } + for { + tok, err = dec.Token() + if tok == json.Delim('}') { + break + } else if key, ok := tok.(string); ok { + if strings.HasSuffix(key, "Block") || strings.HasSuffix(key, "Time") { + err = newcfg.decodeActivation(key, dec) + } else { + err = newcfg.decodeParameter(key, dec) + } + } + if err != nil { + return err + } + } + + *cfg = newcfg + return nil +} + +func (cfg *Config2) decodeActivation(key string, dec *json.Decoder) error { + var num uint64 + if err := dec.Decode(&num); err != nil { + return err + } + + var f forks.Fork + name, ok := strings.CutSuffix(key, "Block") + if ok { + f, ok = forks.ByName(name) + if !ok || !f.BlockBased() { + return fmt.Errorf("unknown block-based fork %q", name) + } + } else if name, ok = strings.CutSuffix(key, "Time"); ok { + f, ok = forks.ByName(name) + if !ok || f.BlockBased() { + return fmt.Errorf("unknown time-based fork %q", name) + } + } + cfg.activation[f] = num + return nil +} + +func (cfg *Config2) decodeParameter(key string, dec *json.Decoder) error { + info, ok := paramRegistryByName[key] + if !ok { + return fmt.Errorf("unknown chain parameter %q", key) + } + v := reflect.New(info.rtype).Interface() + if err := dec.Decode(v); err != nil { + return err + } + cfg.param[info.rtype] = v + return nil +} + +// Validate checks the configuration to ensure forks are scheduled in order, +// and required settings are present. +func (cfg *Config2) Validate() error { + sanityCheckCanonOrder() + + lastFork := forks.CanonOrder[0] + for _, f := range forks.CanonOrder[1:] { + act := "timestamp" + if f.BlockBased() { + act = "block" + } + + switch { + // Non-optional forks must all be present in the chain config up to the last defined fork. + case !cfg.Scheduled(lastFork) && cfg.Scheduled(f): + return fmt.Errorf("unsupported fork ordering: %v not enabled, but %v enabled at %s %v", lastFork, f, act, cfg.activation[f]) + + // Fork (whether defined by block or timestamp) must follow the fork definition sequence. + case cfg.Scheduled(lastFork) && cfg.Scheduled(f): + // Timestamp based forks can follow block based ones, but not the other way around. + if !lastFork.BlockBased() && f.BlockBased() { + return fmt.Errorf("unsupported fork ordering: %v used timestamp ordering, but %v reverted to block ordering", lastFork, f) + } + if lastFork.BlockBased() == f.BlockBased() && cfg.activation[lastFork] > cfg.activation[f] { + return fmt.Errorf("unsupported fork ordering: %v enabled at %s %v, but %v enabled at %s %v", lastFork, act, cfg.activation[lastFork], f, act, cfg.activation[f]) + } + } + + // If it was optional and not set, then ignore it. + if !f.Optional() || cfg.Scheduled(f) { + lastFork = f + } + + // Check that all forks with blobs explicitly define the blob schedule configuration. + if f.HasBlobs() { + schedule := Get[BlobSchedule](cfg) + bcfg, defined := schedule[f] + if cfg.Scheduled(f) && !defined { + return fmt.Errorf("invalid chain configuration: missing entry for fork %q in blobSchedule", f) + } + if defined { + if err := bcfg.validate(); err != nil { + return fmt.Errorf("invalid chain configuration in blobSchedule for fork %q: %v", f, err) + } + } + } + } + return nil +} + +// CheckCompatible validates chain configuration changes. +// This called before applying changes to the 'stored configuration', the config +// which is held in the database. The given block number and time represent the current head +// of the chain in that database. +// +// An error is returned when the new configuration attempts to schedule a fork below the +// current chain head. The error contains enough information to rewind the chain to a +// point where the new config can be applied safely. +func (c *Config2) CheckCompatible(newcfg *Config2, blocknum uint64, time uint64) *ConfigCompatError { + sanityCheckCanonOrder() + + // Iterate checkCompatible to find the lowest conflict. + var lasterr *ConfigCompatError + bhead, btime := blocknum, time + for { + err := c.checkCompatible(newcfg, bhead, btime) + if err == nil || (lasterr != nil && err.RewindToBlock == lasterr.RewindToBlock && err.RewindToTime == lasterr.RewindToTime) { + break + } + lasterr = err + + if err.RewindToTime > 0 { + btime = err.RewindToTime + } else { + bhead = err.RewindToBlock + } + } + return lasterr +} + +// checkCompatible checks config compatibility at a specific block height. +func (cfg *Config2) checkCompatible(newcfg *Config2, num uint64, time uint64) *ConfigCompatError { + incompatible := func(f forks.Fork) bool { + return (cfg.Active(f, num, time) || newcfg.Active(f, num, time)) && !activationEqual(f, cfg, newcfg) + } + + for _, f := range forks.CanonOrder[1:] { + if incompatible(f) { + if f.BlockBased() { + return newBlockCompatError2(fmt.Sprintf("%v fork block", f), f, cfg, newcfg) + } + return newTimestampCompatError2("%v fork timestamp", f, cfg, newcfg) + } + // Specialty checks: + if cfg.Active(forks.DAO, num, time) && Get[DAOForkSupport](cfg) != Get[DAOForkSupport](newcfg) { + return newBlockCompatError2("DAO fork support flag", f, cfg, newcfg) + } + chainID := (*big.Int)(Get[*ChainID](cfg)) + newChainID := (*big.Int)(Get[*ChainID](newcfg)) + if cfg.Active(forks.TangerineWhistle, num, time) && !configBlockEqual(chainID, newChainID) { + return newBlockCompatError2("EIP158 chain ID", f, cfg, newcfg) + } + } + return nil +} + +func newBlockCompatError2(what string, f forks.Fork, storedcfg, newcfg *Config2) *ConfigCompatError { + err := &ConfigCompatError{What: what} + if storedcfg.Scheduled(f) { + err.StoredBlock = new(big.Int).SetUint64(storedcfg.activation[f]) + } + if newcfg.Scheduled(f) { + err.NewBlock = new(big.Int).SetUint64(newcfg.activation[f]) + } + // Need to rewind to one block before the earliest possible activation. + rew, _ := minActivation(f, storedcfg, newcfg) + if rew > 0 { + err.RewindToBlock = rew - 1 + } + return err +} + +func newTimestampCompatError2(what string, f forks.Fork, storedcfg, newcfg *Config2) *ConfigCompatError { + err := &ConfigCompatError{What: what} + if storedcfg.Scheduled(f) { + t := storedcfg.activation[f] + err.StoredTime = &t + } + if newcfg.Scheduled(f) { + t := newcfg.activation[f] + err.NewTime = &t + } + // Need to rewind to before the earliest possible activation. + rew, _ := minActivation(f, storedcfg, newcfg) + if rew > 0 { + err.RewindToTime = rew - 1 + } + return err +} + +// minActivation the earliest possible activation block/time for the given fork +// across two configurations. +func minActivation(f forks.Fork, cfg1, cfg2 *Config2) (act uint64, scheduled bool) { + switch { + case !cfg1.Scheduled(f): + act, scheduled = cfg2.activation[f] + case !cfg2.Scheduled(f): + act, scheduled = cfg1.activation[f] + default: + act = min(cfg1.activation[f], cfg2.activation[f]) + scheduled = true + } + return +} + +// activationEqual checks whether a fork has the same activation two configurations. +// Note this also returns true when it isn't scheduled at all. +func activationEqual(f forks.Fork, cfg1, cfg2 *Config2) bool { + return cfg1.Scheduled(f) == cfg2.Scheduled(f) && cfg1.activation[f] == cfg2.activation[f] +} + +// sanityCheckCanonOrder verifies forks.CanonOrder is defined sensibly. +// This exists to ensure library code doesn't mess with this slice in an incompatible way. +func sanityCheckCanonOrder() { + if len(forks.CanonOrder) == 0 { + panic("forks.CanonOrder is empty") + } + if forks.CanonOrder[0] != forks.Frontier { + panic("forks.CanonOrder must start with Frontier") + } +} + +func cloneBig(x *big.Int) *big.Int { + if x == nil { + return nil + } + return new(big.Int).Set(x) +} diff --git a/params/config2_presets.go b/params/config2_presets.go new file mode 100644 index 00000000000..557cb5d0ac6 --- /dev/null +++ b/params/config2_presets.go @@ -0,0 +1,115 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package params + +import ( + "math" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params/forks" +) + +var ( + MainnetConfig2 *Config2 + AllEthashProtocolChanges2 *Config2 + MergedTestChainConfig2 *Config2 +) + +func init() { + MainnetConfig2 = NewConfig2( + Activations{ + forks.Homestead: 1_150_000, + forks.DAO: 1_920_000, + forks.TangerineWhistle: 2_463_000, + forks.SpuriousDragon: 2_675_000, + forks.Byzantium: 4_370_000, + forks.Constantinople: 7_280_000, + forks.Petersburg: 7_280_000, + forks.Istanbul: 9_069_000, + forks.MuirGlacier: 9_200_000, + forks.Berlin: 12_244_000, + forks.London: 12_965_000, + forks.ArrowGlacier: 13_773_000, + forks.GrayGlacier: 15_050_000, + // time-based forks + forks.Shanghai: 1681338455, + forks.Cancun: 1710338135, + forks.Prague: 1746612311, + }, + (*ChainID)(big.NewInt(1)), + (*TerminalTotalDifficulty)(MainnetTerminalTotalDifficulty), + DepositContractAddress(common.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa")), + DAOForkSupport(true), + BlobSchedule{ + forks.Cancun: *DefaultCancunBlobConfig, + forks.Prague: *DefaultPragueBlobConfig, + }, + ) + + // AllEthashProtocolChanges2 contains every protocol change (EIPs) introduced + // and accepted by the Ethereum core developers into the Ethash consensus. + AllEthashProtocolChanges2 = NewConfig2( + Activations{ + forks.Homestead: 0, + forks.TangerineWhistle: 0, + forks.SpuriousDragon: 0, + forks.Byzantium: 0, + forks.Constantinople: 0, + forks.Petersburg: 0, + forks.Istanbul: 0, + forks.MuirGlacier: 0, + forks.Berlin: 0, + forks.London: 0, + forks.ArrowGlacier: 0, + forks.GrayGlacier: 0, + }, + (*ChainID)(big.NewInt(1337)), + (*TerminalTotalDifficulty)(big.NewInt(math.MaxInt64)), + ) + + // MergedTestChainConfig2 contains every protocol change (EIPs) introduced + // and accepted by the Ethereum core developers for testing purposes. + MergedTestChainConfig2 = NewConfig2( + Activations{ + forks.Homestead: 0, + forks.TangerineWhistle: 0, + forks.SpuriousDragon: 0, + forks.Byzantium: 0, + forks.Constantinople: 0, + forks.Petersburg: 0, + forks.Istanbul: 0, + forks.MuirGlacier: 0, + forks.Berlin: 0, + forks.London: 0, + forks.ArrowGlacier: 0, + forks.GrayGlacier: 0, + forks.Paris: 0, + forks.Shanghai: 0, + forks.Cancun: 0, + forks.Prague: 0, + forks.Osaka: 0, + }, + (*ChainID)(big.NewInt(1)), + (*TerminalTotalDifficulty)(big.NewInt(0)), + BlobSchedule{ + forks.Cancun: *DefaultCancunBlobConfig, + forks.Prague: *DefaultPragueBlobConfig, + forks.Osaka: *DefaultOsakaBlobConfig, + }, + ) +} diff --git a/params/forks/forks.go b/params/forks/forks.go index 5c9612a625a..9d6a42f68a3 100644 --- a/params/forks/forks.go +++ b/params/forks/forks.go @@ -16,60 +16,132 @@ package forks +import "fmt" + // Fork is a numerical identifier of specific network upgrades (forks). -type Fork int +type Fork uint32 const ( - Frontier Fork = iota - FrontierThawing - Homestead - DAO - TangerineWhistle // a.k.a. the EIP150 fork - SpuriousDragon // a.k.a. the EIP155 fork - Byzantium - Constantinople - Petersburg - Istanbul - MuirGlacier - Berlin - London - ArrowGlacier - GrayGlacier - Paris - Shanghai - Cancun - Prague - Osaka + Frontier Fork = iota | blockBased + FrontierThawing Fork = iota | blockBased | optional + Homestead Fork = iota | blockBased + DAO Fork = iota | blockBased | optional + TangerineWhistle Fork = iota | blockBased // a.k.a. the EIP150 fork + SpuriousDragon Fork = iota | blockBased // a.k.a. the EIP155/EIP158 fork + Byzantium Fork = iota | blockBased + Constantinople Fork = iota | blockBased + Petersburg Fork = iota | blockBased + Istanbul Fork = iota | blockBased + MuirGlacier Fork = iota | blockBased | optional + Berlin Fork = iota | blockBased + London Fork = iota | blockBased + ArrowGlacier Fork = iota | blockBased | optional + GrayGlacier Fork = iota | blockBased | optional + Paris Fork = iota | blockBased | ismerge + Shanghai Fork = iota + Cancun Fork = iota | hasBlobs + Prague Fork = iota | hasBlobs + Osaka Fork = iota | hasBlobs + Verkle Fork = iota | optional ) +var CanonOrder = []Fork{ + Frontier, + FrontierThawing, + Homestead, + DAO, + TangerineWhistle, + SpuriousDragon, + Byzantium, + Constantinople, + Petersburg, + Istanbul, + MuirGlacier, + Berlin, + London, + ArrowGlacier, + GrayGlacier, + Paris, + Shanghai, + Cancun, + Prague, + Osaka, + Verkle, +} + +const ( + // Config bits: these bits are set on specific fork enum values and encode metadata + // about the fork. + blockBased = 1 << 31 + ismerge = 1 << 30 + optional = 1 << 29 + hasBlobs = 1 << 28 + + // The config bits can be stripped using this bit mask. + unconfigMask = ^Fork(0) >> 8 +) + +// IsMerge returns true for the merge fork. +func (f Fork) IsMerge() bool { + return f&ismerge != 0 +} + +// Optional reports whether the fork can be left out of the config. +func (f Fork) Optional() bool { + return f&optional != 0 +} + +// BlockBased reports whether the fork is scheduled by block number instead of timestamp. +// This is true for pre-merge forks. +func (f Fork) BlockBased() bool { + return f&blockBased != 0 +} + +// HasBlobs reports whether the fork must have a corresponding blob count configuration. +func (f Fork) HasBlobs() bool { + return f&hasBlobs != 0 +} + // String implements fmt.Stringer. func (f Fork) String() string { s, ok := forkToString[f] if !ok { - return "Unknown fork" + return fmt.Sprintf("unknownFork(%#x)", f) } return s } var forkToString = map[Fork]string{ Frontier: "Frontier", - FrontierThawing: "Frontier Thawing", Homestead: "Homestead", - DAO: "DAO", - TangerineWhistle: "Tangerine Whistle", - SpuriousDragon: "Spurious Dragon", + DAO: "DAOFork", + TangerineWhistle: "TangerineWhistle", + SpuriousDragon: "SpuriousDragon", Byzantium: "Byzantium", Constantinople: "Constantinople", Petersburg: "Petersburg", Istanbul: "Istanbul", - MuirGlacier: "Muir Glacier", + MuirGlacier: "MuirGlacier", Berlin: "Berlin", London: "London", - ArrowGlacier: "Arrow Glacier", - GrayGlacier: "Gray Glacier", + ArrowGlacier: "ArrowGlacier", + GrayGlacier: "GrayGlacier", Paris: "Paris", Shanghai: "Shanghai", Cancun: "Cancun", Prague: "Prague", Osaka: "Osaka", } + +var forkFromString = make(map[string]Fork, len(forkToString)) + +func init() { + for f, name := range forkToString { + forkFromString[name] = f + } +} + +func ByName(name string) (Fork, bool) { + f, ok := forkFromString[name] + return f, ok +} diff --git a/params/registry.go b/params/registry.go new file mode 100644 index 00000000000..aed4888b8be --- /dev/null +++ b/params/registry.go @@ -0,0 +1,85 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package params + +import ( + "fmt" + "reflect" +) + +type ParameterType any + +type paramInfo struct { + rtype reflect.Type + name string + optional bool + defaultVal any +} + +var ( + paramRegistry = map[reflect.Type]*paramInfo{} + paramRegistryByName = map[string]*paramInfo{} +) + +type Parameter[T ParameterType] struct { + Name string + Optional bool + Default T +} + +// Get retrieves the value of a chain parameter. +func Get[T ParameterType](cfg *Config2) T { + for _, p := range cfg.param { + if v, ok := p.(T); ok { + return v + } + } + // get default + var z T + info, ok := findParam(z) + if !ok { + panic(fmt.Sprintf("unknown parameter type %T", z)) + } + return info.defaultVal.(T) +} + +func Define[T ParameterType](def Parameter[T]) { + var z T + info, defined := paramRegistryByName[def.Name] + if defined { + panic(fmt.Sprintf("chain parameter %q already registered with type %v", info.name, info.rtype)) + } + rtype := reflect.TypeOf(z) + info, defined = paramRegistry[rtype] + if defined { + panic(fmt.Sprintf("chain parameter of type %v already registered with name %q", rtype, info.name)) + } + info = ¶mInfo{ + rtype: rtype, + name: def.Name, + optional: def.Optional, + defaultVal: def.Default, + } + paramRegistry[rtype] = info + paramRegistryByName[def.Name] = info +} + +func findParam(v any) (*paramInfo, bool) { + rtype := reflect.TypeOf(v) + info, ok := paramRegistry[rtype] + return info, ok +} From cab21e0b0ac1d8ad31b83f4ca2d2fb81deb9f390 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 16 Jul 2025 01:15:31 +0200 Subject: [PATCH 02/32] params: update --- params/chainparam.go | 69 +++++++++++++++++ params/config2.go | 61 ++++++++------- params/config2_presets.go | 115 ---------------------------- params/presets/presets.go | 134 +++++++++++++++++++++++++++++++++ params/presets/presets_test.go | 12 +++ params/registry.go | 20 +++-- 6 files changed, 262 insertions(+), 149 deletions(-) delete mode 100644 params/config2_presets.go create mode 100644 params/presets/presets.go create mode 100644 params/presets/presets_test.go diff --git a/params/chainparam.go b/params/chainparam.go index 225558f3f7b..925e5b0d52b 100644 --- a/params/chainparam.go +++ b/params/chainparam.go @@ -1,6 +1,7 @@ package params import ( + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -10,9 +11,46 @@ import ( // ChainID type ChainID big.Int +func NewChainID(input string) *ChainID { + b, ok := new(big.Int).SetString(input, 0) + if !ok { + panic("invalid chainID: " + input) + } + return (*ChainID)(b) +} + +func (v *ChainID) MarshalText() ([]byte, error) { + return (*big.Int)(v).MarshalText() +} + +func (v *ChainID) UnmarshalText(input []byte) error { + return (*big.Int)(v).UnmarshalText(input) +} + +func (v *ChainID) BigInt() *big.Int { + return (*big.Int)(v) +} + +func (v *ChainID) Validate(cfg *Config2) error { + b := (*big.Int)(v) + if b.Sign() <= 0 { + return fmt.Errorf("invalid chainID value %v", b) + } + + return nil +} + // TerminalTotalDifficulty (TTD) is the total difficulty value where type TerminalTotalDifficulty big.Int +func NewTerminalTotalDifficulty(input string) *TerminalTotalDifficulty { + b, ok := new(big.Int).SetString(input, 0) + if !ok { + panic("invalid terminal total difficulty: " + input) + } + return (*TerminalTotalDifficulty)(b) +} + func (v *TerminalTotalDifficulty) MarshalText() ([]byte, error) { return (*big.Int)(v).MarshalText() } @@ -21,18 +59,49 @@ func (v *TerminalTotalDifficulty) UnmarshalText(input []byte) error { return (*big.Int)(v).UnmarshalText(input) } +func (v *TerminalTotalDifficulty) Validate(cfg *Config2) error { + return nil +} + // DepositContractAddress configures the location of the deposit contract. type DepositContractAddress common.Address +func (v DepositContractAddress) Validate(cfg *Config2) error { + return nil +} + // This configures the EIP-4844 parameters across forks. // There must be an entry for each fork type BlobSchedule map[forks.Fork]BlobConfig +// Validate checks that all forks with blobs explicitly define the blob schedule configuration. +func (v BlobSchedule) Validate(cfg *Config2) error { + for _, f := range forks.CanonOrder { + if f.HasBlobs() { + schedule := Get[BlobSchedule](cfg) + bcfg, defined := schedule[f] + if cfg.Scheduled(f) && !defined { + return fmt.Errorf("invalid chain configuration: missing entry for fork %q in blobSchedule", f) + } + if defined { + if err := bcfg.validate(); err != nil { + return fmt.Errorf("invalid chain configuration in blobSchedule for fork %q: %v", f, err) + } + } + } + } + return nil +} + // DAOForkSupport is the chain parameter that configures the DAO fork. // true=supports or false=opposes the fork. // The default value is true. type DAOForkSupport bool +func (v DAOForkSupport) Validate(cfg *Config2) error { + return nil +} + func init() { Define(Parameter[*ChainID]{ Name: "chainId", diff --git a/params/config2.go b/params/config2.go index 3e4aeb3844e..79c74060814 100644 --- a/params/config2.go +++ b/params/config2.go @@ -43,6 +43,7 @@ func NewConfig2(activations Activations, param ...ParameterType) *Config2 { activation: maps.Clone(activations), param: make(map[reflect.Type]ParameterType, len(param)), } + cfg.activation[forks.Frontier] = 0 for _, pv := range param { info, ok := findParam(pv) if !ok { @@ -62,6 +63,16 @@ func (cfg *Config2) Active(f forks.Fork, block, timestamp uint64) bool { return ok && timestamp >= activation } +// ActiveAtBlock reports whether the given fork is active for a block number/time. +func (cfg *Config2) ActiveAtBlock(f forks.Fork, block *big.Int) bool { + if !f.BlockBased() { + panic(fmt.Sprintf("fork %v has time-based scheduling", f)) + } + activation, ok := cfg.activation[f] + return ok && block.Uint64() >= activation +} + +// Activation returns the activation block/number of a fork. func (cfg *Config2) Activation(f forks.Fork) (uint64, bool) { a, ok := cfg.activation[f] return a, ok @@ -80,11 +91,6 @@ func (cfg *Config2) SetActivations(forks Activations) *Config2 { return &Config2{activation: newA, param: cfg.param} } -// SetParam returns a new configuration with parameters modified. -func (cfg *Config2) SetParam(p ParameterType) *Config2 { - return &Config2{activation: cfg.activation} -} - // LatestFork returns the latest time-based fork that would be active for the given time. func (cfg *Config2) LatestFork(time uint64) forks.Fork { londonBlock := cfg.activation[forks.London] @@ -189,7 +195,7 @@ func (cfg *Config2) decodeParameter(key string, dec *json.Decoder) error { if err := dec.Decode(v); err != nil { return err } - cfg.param[info.rtype] = v + cfg.param[info.rtype] = v.(ParameterType) return nil } @@ -198,6 +204,7 @@ func (cfg *Config2) decodeParameter(key string, dec *json.Decoder) error { func (cfg *Config2) Validate() error { sanityCheckCanonOrder() + // Check forks. lastFork := forks.CanonOrder[0] for _, f := range forks.CanonOrder[1:] { act := "timestamp" @@ -225,21 +232,22 @@ func (cfg *Config2) Validate() error { if !f.Optional() || cfg.Scheduled(f) { lastFork = f } + } - // Check that all forks with blobs explicitly define the blob schedule configuration. - if f.HasBlobs() { - schedule := Get[BlobSchedule](cfg) - bcfg, defined := schedule[f] - if cfg.Scheduled(f) && !defined { - return fmt.Errorf("invalid chain configuration: missing entry for fork %q in blobSchedule", f) - } - if defined { - if err := bcfg.validate(); err != nil { - return fmt.Errorf("invalid chain configuration in blobSchedule for fork %q: %v", f, err) - } + // Check parameters. + for rtype, info := range paramRegistry { + v, isSet := cfg.param[rtype] + if !isSet { + if !info.optional { + return fmt.Errorf("required chain parameter %s is not set", info.name) } + v = info.defaultVal + } + if err := v.Validate(cfg); err != nil { + return fmt.Errorf("invalid %s: %w", info.name, err) } } + return nil } @@ -286,16 +294,17 @@ func (cfg *Config2) checkCompatible(newcfg *Config2, num uint64, time uint64) *C } return newTimestampCompatError2("%v fork timestamp", f, cfg, newcfg) } - // Specialty checks: - if cfg.Active(forks.DAO, num, time) && Get[DAOForkSupport](cfg) != Get[DAOForkSupport](newcfg) { - return newBlockCompatError2("DAO fork support flag", f, cfg, newcfg) - } - chainID := (*big.Int)(Get[*ChainID](cfg)) - newChainID := (*big.Int)(Get[*ChainID](newcfg)) - if cfg.Active(forks.TangerineWhistle, num, time) && !configBlockEqual(chainID, newChainID) { - return newBlockCompatError2("EIP158 chain ID", f, cfg, newcfg) - } } + + if cfg.Active(forks.DAO, num, time) && Get[DAOForkSupport](cfg) != Get[DAOForkSupport](newcfg) { + return newBlockCompatError2("DAO fork support flag", forks.DAO, cfg, newcfg) + } + chainID := (*big.Int)(Get[*ChainID](cfg)) + newChainID := (*big.Int)(Get[*ChainID](newcfg)) + if cfg.Active(forks.TangerineWhistle, num, time) && !configBlockEqual(chainID, newChainID) { + return newBlockCompatError2("EIP158 chain ID", forks.TangerineWhistle, cfg, newcfg) + } + return nil } diff --git a/params/config2_presets.go b/params/config2_presets.go deleted file mode 100644 index 557cb5d0ac6..00000000000 --- a/params/config2_presets.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2025 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package params - -import ( - "math" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/params/forks" -) - -var ( - MainnetConfig2 *Config2 - AllEthashProtocolChanges2 *Config2 - MergedTestChainConfig2 *Config2 -) - -func init() { - MainnetConfig2 = NewConfig2( - Activations{ - forks.Homestead: 1_150_000, - forks.DAO: 1_920_000, - forks.TangerineWhistle: 2_463_000, - forks.SpuriousDragon: 2_675_000, - forks.Byzantium: 4_370_000, - forks.Constantinople: 7_280_000, - forks.Petersburg: 7_280_000, - forks.Istanbul: 9_069_000, - forks.MuirGlacier: 9_200_000, - forks.Berlin: 12_244_000, - forks.London: 12_965_000, - forks.ArrowGlacier: 13_773_000, - forks.GrayGlacier: 15_050_000, - // time-based forks - forks.Shanghai: 1681338455, - forks.Cancun: 1710338135, - forks.Prague: 1746612311, - }, - (*ChainID)(big.NewInt(1)), - (*TerminalTotalDifficulty)(MainnetTerminalTotalDifficulty), - DepositContractAddress(common.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa")), - DAOForkSupport(true), - BlobSchedule{ - forks.Cancun: *DefaultCancunBlobConfig, - forks.Prague: *DefaultPragueBlobConfig, - }, - ) - - // AllEthashProtocolChanges2 contains every protocol change (EIPs) introduced - // and accepted by the Ethereum core developers into the Ethash consensus. - AllEthashProtocolChanges2 = NewConfig2( - Activations{ - forks.Homestead: 0, - forks.TangerineWhistle: 0, - forks.SpuriousDragon: 0, - forks.Byzantium: 0, - forks.Constantinople: 0, - forks.Petersburg: 0, - forks.Istanbul: 0, - forks.MuirGlacier: 0, - forks.Berlin: 0, - forks.London: 0, - forks.ArrowGlacier: 0, - forks.GrayGlacier: 0, - }, - (*ChainID)(big.NewInt(1337)), - (*TerminalTotalDifficulty)(big.NewInt(math.MaxInt64)), - ) - - // MergedTestChainConfig2 contains every protocol change (EIPs) introduced - // and accepted by the Ethereum core developers for testing purposes. - MergedTestChainConfig2 = NewConfig2( - Activations{ - forks.Homestead: 0, - forks.TangerineWhistle: 0, - forks.SpuriousDragon: 0, - forks.Byzantium: 0, - forks.Constantinople: 0, - forks.Petersburg: 0, - forks.Istanbul: 0, - forks.MuirGlacier: 0, - forks.Berlin: 0, - forks.London: 0, - forks.ArrowGlacier: 0, - forks.GrayGlacier: 0, - forks.Paris: 0, - forks.Shanghai: 0, - forks.Cancun: 0, - forks.Prague: 0, - forks.Osaka: 0, - }, - (*ChainID)(big.NewInt(1)), - (*TerminalTotalDifficulty)(big.NewInt(0)), - BlobSchedule{ - forks.Cancun: *DefaultCancunBlobConfig, - forks.Prague: *DefaultPragueBlobConfig, - forks.Osaka: *DefaultOsakaBlobConfig, - }, - ) -} diff --git a/params/presets/presets.go b/params/presets/presets.go new file mode 100644 index 00000000000..8af1aab366c --- /dev/null +++ b/params/presets/presets.go @@ -0,0 +1,134 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package presets + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" +) + +var Mainnet = params.NewConfig2( + params.Activations{ + forks.Homestead: 1_150_000, + forks.DAO: 1_920_000, + forks.TangerineWhistle: 2_463_000, + forks.SpuriousDragon: 2_675_000, + forks.Byzantium: 4_370_000, + forks.Constantinople: 7_280_000, + forks.Petersburg: 7_280_000, + forks.Istanbul: 9_069_000, + forks.MuirGlacier: 9_200_000, + forks.Berlin: 12_244_000, + forks.London: 12_965_000, + forks.ArrowGlacier: 13_773_000, + forks.GrayGlacier: 15_050_000, + forks.Paris: 15_537_393, + // time-based forks + forks.Shanghai: 1681338455, + forks.Cancun: 1710338135, + forks.Prague: 1746612311, + }, + params.NewChainID("1"), + params.NewTerminalTotalDifficulty("58_750_000_000_000_000_000_000"), + params.DepositContractAddress(common.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa")), + params.DAOForkSupport(true), + params.BlobSchedule{ + forks.Cancun: *params.DefaultCancunBlobConfig, + forks.Prague: *params.DefaultPragueBlobConfig, + }, +) + +// SepoliaChainConfig contains the chain parameters to run a node on the Sepolia test network. +var Sepolia = params.NewConfig2( + params.Activations{ + forks.Homestead: 0, + forks.TangerineWhistle: 0, + forks.SpuriousDragon: 0, + forks.Byzantium: 0, + forks.Constantinople: 0, + forks.Petersburg: 0, + forks.Istanbul: 0, + forks.MuirGlacier: 0, + forks.Berlin: 0, + forks.London: 0, + forks.Paris: 1735371, + // time-based forks + forks.Shanghai: 1677557088, + forks.Cancun: 1706655072, + forks.Prague: 1741159776, + }, + params.NewChainID("11155111"), + params.NewTerminalTotalDifficulty("17_000_000_000_000_000"), + params.DepositContractAddress(common.HexToAddress("0x7f02c3e3c98b133055b8b348b2ac625669ed295d")), + params.BlobSchedule{ + forks.Cancun: *params.DefaultCancunBlobConfig, + forks.Prague: *params.DefaultPragueBlobConfig, + }, +) + +// AllEthashProtocolChanges2 contains every protocol change (EIPs) introduced +// and accepted by the Ethereum core developers into the Ethash consensus. +var AllEthashProtocolChanges = params.NewConfig2( + params.Activations{ + forks.Homestead: 0, + forks.TangerineWhistle: 0, + forks.SpuriousDragon: 0, + forks.Byzantium: 0, + forks.Constantinople: 0, + forks.Petersburg: 0, + forks.Istanbul: 0, + forks.MuirGlacier: 0, + forks.Berlin: 0, + forks.London: 0, + forks.ArrowGlacier: 0, + forks.GrayGlacier: 0, + }, + params.NewChainID("1337"), + params.NewTerminalTotalDifficulty("0xffffffffffffff"), +) + +// MergedTestChainConfig2 contains every protocol change (EIPs) introduced +// and accepted by the Ethereum core developers for testing purposes. +var MergedTestChainConfig = params.NewConfig2( + params.Activations{ + forks.Homestead: 0, + forks.TangerineWhistle: 0, + forks.SpuriousDragon: 0, + forks.Byzantium: 0, + forks.Constantinople: 0, + forks.Petersburg: 0, + forks.Istanbul: 0, + forks.MuirGlacier: 0, + forks.Berlin: 0, + forks.London: 0, + forks.ArrowGlacier: 0, + forks.GrayGlacier: 0, + forks.Paris: 0, + forks.Shanghai: 0, + forks.Cancun: 0, + forks.Prague: 0, + forks.Osaka: 0, + }, + params.NewChainID("1"), + params.NewTerminalTotalDifficulty("0"), + params.BlobSchedule{ + forks.Cancun: *params.DefaultCancunBlobConfig, + forks.Prague: *params.DefaultPragueBlobConfig, + forks.Osaka: *params.DefaultOsakaBlobConfig, + }, +) diff --git a/params/presets/presets_test.go b/params/presets/presets_test.go new file mode 100644 index 00000000000..e077e9494a3 --- /dev/null +++ b/params/presets/presets_test.go @@ -0,0 +1,12 @@ +package presets + +import "testing" + +func TestPresetsValidate(t *testing.T) { + if err := Mainnet.Validate(); err != nil { + t.Fatal("mainnet is invalid:", err) + } + if err := Sepolia.Validate(); err != nil { + t.Fatal("sepolia is invalid:", err) + } +} diff --git a/params/registry.go b/params/registry.go index aed4888b8be..1fd71c71798 100644 --- a/params/registry.go +++ b/params/registry.go @@ -21,13 +21,15 @@ import ( "reflect" ) -type ParameterType any +type ParameterType interface{ + Validate(*Config2) error +} type paramInfo struct { rtype reflect.Type name string optional bool - defaultVal any + defaultVal ParameterType } var ( @@ -35,12 +37,6 @@ var ( paramRegistryByName = map[string]*paramInfo{} ) -type Parameter[T ParameterType] struct { - Name string - Optional bool - Default T -} - // Get retrieves the value of a chain parameter. func Get[T ParameterType](cfg *Config2) T { for _, p := range cfg.param { @@ -57,6 +53,14 @@ func Get[T ParameterType](cfg *Config2) T { return info.defaultVal.(T) } +// Parameter is the definition of a chain parameter. +type Parameter[T ParameterType] struct { + Name string + Optional bool + Default T +} + +// Define creates a chain parameter in the registry. func Define[T ParameterType](def Parameter[T]) { var z T info, defined := paramRegistryByName[def.Name] From 9268b841421b740195407ef9502c6c67a183367f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 16 Jul 2025 01:15:41 +0200 Subject: [PATCH 03/32] consensus/misc/eip4844: port to config2 --- consensus/misc/eip4844/eip4844.go | 111 ++++++++----------------- consensus/misc/eip4844/eip4844_test.go | 30 +++++-- 2 files changed, 57 insertions(+), 84 deletions(-) diff --git a/consensus/misc/eip4844/eip4844.go b/consensus/misc/eip4844/eip4844.go index fc143027dd9..5bcb7803985 100644 --- a/consensus/misc/eip4844/eip4844.go +++ b/consensus/misc/eip4844/eip4844.go @@ -20,9 +20,11 @@ import ( "errors" "fmt" "math/big" + "slices" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" ) var ( @@ -32,7 +34,7 @@ var ( // VerifyEIP4844Header verifies the presence of the excessBlobGas field and that // if the current block contains no transactions, the excessBlobGas is updated // accordingly. -func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Header) error { +func VerifyEIP4844Header(config *params.Config2, parent, header *types.Header) error { if header.Number.Uint64() != parent.Number.Uint64()+1 { panic("bad header pair") } @@ -61,7 +63,7 @@ func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Heade // CalcExcessBlobGas calculates the excess blob gas after applying the set of // blobs on top of the excess blob gas. -func CalcExcessBlobGas(config *params.ChainConfig, parent *types.Header, headTimestamp uint64) uint64 { +func CalcExcessBlobGas(config *params.Config2, parent *types.Header, headTimestamp uint64) uint64 { var ( parentExcessBlobGas uint64 parentBlobGasUsed uint64 @@ -78,7 +80,7 @@ func CalcExcessBlobGas(config *params.ChainConfig, parent *types.Header, headTim if excessBlobGas < targetGas { return 0 } - if !config.IsOsaka(config.LondonBlock, headTimestamp) { + if !config.Active(forks.Osaka, parent.Number.Uint64()+1, headTimestamp) { // Pre-Osaka, we use the formula defined by EIP-4844. return excessBlobGas - targetGas } @@ -98,94 +100,53 @@ func CalcExcessBlobGas(config *params.ChainConfig, parent *types.Header, headTim } // CalcBlobFee calculates the blobfee from the header's excess blob gas field. -func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int { - blobConfig := latestBlobConfig(config, header.Time) - if blobConfig == nil { - panic("calculating blob fee on unsupported fork") - } - return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(*header.ExcessBlobGas), new(big.Int).SetUint64(blobConfig.UpdateFraction)) +func CalcBlobFee(config *params.Config2, header *types.Header) *big.Int { + schedule := params.Get[params.BlobSchedule](config) + if schedule == nil { + return new(big.Int) + } + f := config.LatestFork(header.Time) + frac := schedule[f].UpdateFraction + return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(*header.ExcessBlobGas), new(big.Int).SetUint64(frac)) } // MaxBlobsPerBlock returns the max blobs per block for a block at the given timestamp. -func MaxBlobsPerBlock(cfg *params.ChainConfig, time uint64) int { - blobConfig := latestBlobConfig(cfg, time) - if blobConfig == nil { - return 0 - } - return blobConfig.Max +func MaxBlobsPerBlock(cfg *params.Config2, time uint64) int { + return scheduleAtTime(cfg, time).Max } -func latestBlobConfig(cfg *params.ChainConfig, time uint64) *params.BlobConfig { - if cfg.BlobScheduleConfig == nil { - return nil - } - var ( - london = cfg.LondonBlock - s = cfg.BlobScheduleConfig - ) - switch { - case cfg.IsBPO5(london, time) && s.BPO5 != nil: - return s.BPO5 - case cfg.IsBPO4(london, time) && s.BPO4 != nil: - return s.BPO4 - case cfg.IsBPO3(london, time) && s.BPO3 != nil: - return s.BPO3 - case cfg.IsBPO2(london, time) && s.BPO2 != nil: - return s.BPO2 - case cfg.IsBPO1(london, time) && s.BPO1 != nil: - return s.BPO1 - case cfg.IsOsaka(london, time) && s.Osaka != nil: - return s.Osaka - case cfg.IsPrague(london, time) && s.Prague != nil: - return s.Prague - case cfg.IsCancun(london, time) && s.Cancun != nil: - return s.Cancun - default: - return nil - } -} - -// MaxBlobGasPerBlock returns the maximum blob gas that can be spent in a block at the given timestamp. -func MaxBlobGasPerBlock(cfg *params.ChainConfig, time uint64) uint64 { +// MaxBlobsPerBlock returns the maximum blob gas that can be spent in a block at the given timestamp. +func MaxBlobGasPerBlock(cfg *params.Config2, time uint64) uint64 { return uint64(MaxBlobsPerBlock(cfg, time)) * params.BlobTxBlobGasPerBlob } // LatestMaxBlobsPerBlock returns the latest max blobs per block defined by the // configuration, regardless of the currently active fork. -func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int { - s := cfg.BlobScheduleConfig - if s == nil { +func LatestMaxBlobsPerBlock(cfg *params.Config2) int { + schedule := params.Get[params.BlobSchedule](cfg) + if schedule == nil { return 0 } - switch { - case s.BPO5 != nil: - return s.BPO5.Max - case s.BPO4 != nil: - return s.BPO4.Max - case s.BPO3 != nil: - return s.BPO3.Max - case s.BPO2 != nil: - return s.BPO2.Max - case s.BPO1 != nil: - return s.BPO1.Max - case s.Osaka != nil: - return s.Osaka.Max - case s.Prague != nil: - return s.Prague.Max - case s.Cancun != nil: - return s.Cancun.Max - default: - return 0 + for _, f := range slices.Backward(forks.CanonOrder) { + if f.HasBlobs() && cfg.Scheduled(f) { + return schedule[f].Max + } } + return 0 } // targetBlobsPerBlock returns the target number of blobs in a block at the given timestamp. -func targetBlobsPerBlock(cfg *params.ChainConfig, time uint64) int { - blobConfig := latestBlobConfig(cfg, time) - if blobConfig == nil { - return 0 +func targetBlobsPerBlock(cfg *params.Config2, time uint64) int { + return scheduleAtTime(cfg, time).Target +} + +func scheduleAtTime(cfg *params.Config2, time uint64) params.BlobConfig { + schedule := params.Get[params.BlobSchedule](cfg) + if schedule == nil { + return params.BlobConfig{} } - return blobConfig.Target + f := cfg.LatestFork(time) + return schedule[f] } // fakeExponential approximates factor * e ** (numerator / denominator) using @@ -206,7 +167,7 @@ func fakeExponential(factor, numerator, denominator *big.Int) *big.Int { } // calcBlobPrice calculates the blob price for a block. -func calcBlobPrice(config *params.ChainConfig, header *types.Header) *big.Int { +func calcBlobPrice(config *params.Config2, header *types.Header) *big.Int { blobBaseFee := CalcBlobFee(config, header) return new(big.Int).Mul(blobBaseFee, big.NewInt(params.BlobTxBlobGasPerBlob)) } diff --git a/consensus/misc/eip4844/eip4844_test.go b/consensus/misc/eip4844/eip4844_test.go index 555324db656..e018b86955e 100644 --- a/consensus/misc/eip4844/eip4844_test.go +++ b/consensus/misc/eip4844/eip4844_test.go @@ -23,12 +23,15 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" + "github.com/ethereum/go-ethereum/params/presets" ) func TestCalcExcessBlobGas(t *testing.T) { var ( - config = params.MainnetChainConfig - targetBlobs = targetBlobsPerBlock(config, *config.CancunTime) + config = presets.Mainnet + cancunTime, _ = config.Activation(forks.Cancun) + targetBlobs = targetBlobsPerBlock(config, cancunTime) targetBlobGas = uint64(targetBlobs) * params.BlobTxBlobGasPerBlob ) var tests = []struct { @@ -58,10 +61,11 @@ func TestCalcExcessBlobGas(t *testing.T) { for i, tt := range tests { blobGasUsed := uint64(tt.blobs) * params.BlobTxBlobGasPerBlob header := &types.Header{ + Number: big.NewInt(1), ExcessBlobGas: &tt.excess, BlobGasUsed: &blobGasUsed, } - result := CalcExcessBlobGas(config, header, *config.CancunTime) + result := CalcExcessBlobGas(config, header, cancunTime) if result != tt.want { t.Errorf("test %d: excess blob gas mismatch: have %v, want %v", i, result, tt.want) } @@ -69,8 +73,6 @@ func TestCalcExcessBlobGas(t *testing.T) { } func TestCalcBlobFee(t *testing.T) { - zero := uint64(0) - tests := []struct { excessBlobGas uint64 blobfee int64 @@ -81,7 +83,15 @@ func TestCalcBlobFee(t *testing.T) { {10 * 1024 * 1024, 23}, } for i, tt := range tests { - config := ¶ms.ChainConfig{LondonBlock: big.NewInt(0), CancunTime: &zero, BlobScheduleConfig: params.DefaultBlobSchedule} + config := params.NewConfig2( + params.Activations{ + forks.London: 0, + forks.Cancun: 0, + }, + params.BlobSchedule{ + forks.Cancun: *params.DefaultCancunBlobConfig, + }, + ) header := &types.Header{ExcessBlobGas: &tt.excessBlobGas} have := CalcBlobFee(config, header) if have.Int64() != tt.blobfee { @@ -130,13 +140,15 @@ func TestFakeExponential(t *testing.T) { func TestCalcExcessBlobGasEIP7918(t *testing.T) { var ( - cfg = params.MergedTestChainConfig - targetBlobs = targetBlobsPerBlock(cfg, *cfg.CancunTime) + cfg = presets.MergedTestChainConfig + cancunTime, _ = cfg.Activation(forks.Cancun) + targetBlobs = targetBlobsPerBlock(cfg, cancunTime) blobGasTarget = uint64(targetBlobs) * params.BlobTxBlobGasPerBlob ) makeHeader := func(parentExcess, parentBaseFee uint64, blobsUsed int) *types.Header { blobGasUsed := uint64(blobsUsed) * params.BlobTxBlobGasPerBlob return &types.Header{ + Number: big.NewInt(1), BaseFee: big.NewInt(int64(parentBaseFee)), ExcessBlobGas: &parentExcess, BlobGasUsed: &blobGasUsed, @@ -160,7 +172,7 @@ func TestCalcExcessBlobGasEIP7918(t *testing.T) { }, } for _, tc := range tests { - got := CalcExcessBlobGas(cfg, tc.header, *cfg.CancunTime) + got := CalcExcessBlobGas(cfg, tc.header, cancunTime) if got != tc.wantExcessGas { t.Fatalf("%s: excess-blob-gas mismatch – have %d, want %d", tc.name, got, tc.wantExcessGas) From d0a36a349a622e9e691e0ac204d45d8953fd3713 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 16 Jul 2025 01:15:54 +0200 Subject: [PATCH 04/32] consensus/misc/eip1559: port to config2 --- consensus/misc/eip1559/eip1559.go | 41 +++++++++++++++++++++----- consensus/misc/eip1559/eip1559_test.go | 34 +++++---------------- 2 files changed, 40 insertions(+), 35 deletions(-) diff --git a/consensus/misc/eip1559/eip1559.go b/consensus/misc/eip1559/eip1559.go index a90bd744b27..a9981d19580 100644 --- a/consensus/misc/eip1559/eip1559.go +++ b/consensus/misc/eip1559/eip1559.go @@ -25,16 +25,39 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" ) +type FeeConfig struct { + ElasticityMultiplier uint64 `json:"elasticityMultiplier"` + BaseFeeChangeDenominator uint64 `json:"baseFeeChangeDenominator"` +} + +func (fc FeeConfig) Validate(config *params.Config2) error { + return nil +} + +func init() { + params.Define(params.Parameter[FeeConfig]{ + Name: "eip1559Config", + Optional: true, + Default: FeeConfig{ + ElasticityMultiplier: params.DefaultElasticityMultiplier, + BaseFeeChangeDenominator: params.DefaultBaseFeeChangeDenominator, + }, + }) +} + // VerifyEIP1559Header verifies some header attributes which were changed in EIP-1559, // - gas limit check // - basefee check -func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Header) error { +func VerifyEIP1559Header(config *params.Config2, parent, header *types.Header) error { + feecfg := params.Get[FeeConfig](config) + // Verify that the gas limit remains within allowed bounds parentGasLimit := parent.GasLimit - if !config.IsLondon(parent.Number) { - parentGasLimit = parent.GasLimit * config.ElasticityMultiplier() + if !config.Active(forks.London, parent.Number.Uint64(), parent.Time) { + parentGasLimit = parent.GasLimit * feecfg.ElasticityMultiplier } if err := misc.VerifyGaslimit(parentGasLimit, header.GasLimit); err != nil { return err @@ -53,13 +76,15 @@ func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Heade } // CalcBaseFee calculates the basefee of the header. -func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int { +func CalcBaseFee(config *params.Config2, parent *types.Header) *big.Int { + feecfg := params.Get[FeeConfig](config) + // If the current block is the first EIP-1559 block, return the InitialBaseFee. - if !config.IsLondon(parent.Number) { + if !config.Active(forks.London, parent.Number.Uint64(), parent.Time) { return new(big.Int).SetUint64(params.InitialBaseFee) } - parentGasTarget := parent.GasLimit / config.ElasticityMultiplier() + parentGasTarget := parent.GasLimit / feecfg.ElasticityMultiplier // If the parent gasUsed is the same as the target, the baseFee remains unchanged. if parent.GasUsed == parentGasTarget { return new(big.Int).Set(parent.BaseFee) @@ -76,7 +101,7 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int { num.SetUint64(parent.GasUsed - parentGasTarget) num.Mul(num, parent.BaseFee) num.Div(num, denom.SetUint64(parentGasTarget)) - num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator())) + num.Div(num, denom.SetUint64(feecfg.BaseFeeChangeDenominator)) if num.Cmp(common.Big1) < 0 { return num.Add(parent.BaseFee, common.Big1) } @@ -87,7 +112,7 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int { num.SetUint64(parentGasTarget - parent.GasUsed) num.Mul(num, parent.BaseFee) num.Div(num, denom.SetUint64(parentGasTarget)) - num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator())) + num.Div(num, denom.SetUint64(feecfg.BaseFeeChangeDenominator)) baseFee := num.Sub(parent.BaseFee, num) if baseFee.Cmp(common.Big0) < 0 { diff --git a/consensus/misc/eip1559/eip1559_test.go b/consensus/misc/eip1559/eip1559_test.go index b5afdf0fe5e..2a9841b234c 100644 --- a/consensus/misc/eip1559/eip1559_test.go +++ b/consensus/misc/eip1559/eip1559_test.go @@ -23,35 +23,15 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" + "github.com/ethereum/go-ethereum/params/presets" ) -// copyConfig does a _shallow_ copy of a given config. Safe to set new values, but -// do not use e.g. SetInt() on the numbers. For testing only -func copyConfig(original *params.ChainConfig) *params.ChainConfig { - return ¶ms.ChainConfig{ - ChainID: original.ChainID, - HomesteadBlock: original.HomesteadBlock, - DAOForkBlock: original.DAOForkBlock, - DAOForkSupport: original.DAOForkSupport, - EIP150Block: original.EIP150Block, - EIP155Block: original.EIP155Block, - EIP158Block: original.EIP158Block, - ByzantiumBlock: original.ByzantiumBlock, - ConstantinopleBlock: original.ConstantinopleBlock, - PetersburgBlock: original.PetersburgBlock, - IstanbulBlock: original.IstanbulBlock, - MuirGlacierBlock: original.MuirGlacierBlock, - BerlinBlock: original.BerlinBlock, - LondonBlock: original.LondonBlock, - TerminalTotalDifficulty: original.TerminalTotalDifficulty, - Ethash: original.Ethash, - Clique: original.Clique, - } -} - -func config() *params.ChainConfig { - config := copyConfig(params.TestChainConfig) - config.LondonBlock = big.NewInt(5) +func config() *params.Config2 { + config := presets.AllEthashProtocolChanges + config = config.SetActivations(params.Activations{ + forks.London: 5, + }) return config } From 46adeca48a233ba1813873c8959448fafb00db81 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 16 Jul 2025 01:16:25 +0200 Subject: [PATCH 05/32] consensus/misc: update DAO fork for config2 --- consensus/misc/dao.go | 15 +++++++++------ params/dao.go | 4 +--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/consensus/misc/dao.go b/consensus/misc/dao.go index b80c1b833a4..32932b9bb89 100644 --- a/consensus/misc/dao.go +++ b/consensus/misc/dao.go @@ -19,12 +19,12 @@ package misc import ( "bytes" "errors" - "math/big" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" ) var ( @@ -46,18 +46,21 @@ var ( // with the fork specific extra-data set. // - if the node is pro-fork, require blocks in the specific range to have the // unique extra-data set. -func VerifyDAOHeaderExtraData(config *params.ChainConfig, header *types.Header) error { +func VerifyDAOHeaderExtraData(config *params.Config2, header *types.Header) error { // Short circuit validation if the node doesn't care about the DAO fork - if config.DAOForkBlock == nil { + if !config.Scheduled(forks.DAO) { return nil } + // Make sure the block is within the fork's modified extra-data range - limit := new(big.Int).Add(config.DAOForkBlock, params.DAOForkExtraRange) - if header.Number.Cmp(config.DAOForkBlock) < 0 || header.Number.Cmp(limit) >= 0 { + activation, _ := config.Activation(forks.DAO) + limit := activation + uint64(params.DAOForkExtraRange) + if header.Number.Uint64() < activation || header.Number.Uint64() >= limit { return nil } + // Depending on whether we support or oppose the fork, validate the extra-data contents - if config.DAOForkSupport { + if params.Get[params.DAOForkSupport](config) { if !bytes.Equal(header.Extra, params.DAOForkBlockExtra) { return ErrBadProDAOExtra } diff --git a/params/dao.go b/params/dao.go index da3c8dfc992..b6c476fbc10 100644 --- a/params/dao.go +++ b/params/dao.go @@ -17,8 +17,6 @@ package params import ( - "math/big" - "github.com/ethereum/go-ethereum/common" ) @@ -29,7 +27,7 @@ var DAOForkBlockExtra = common.FromHex("0x64616f2d686172642d666f726b") // DAOForkExtraRange is the number of consecutive blocks from the DAO fork point // to override the extra-data in to prevent no-fork attacks. -var DAOForkExtraRange = big.NewInt(10) +var DAOForkExtraRange = uint64(10) // DAORefundContract is the address of the refund contract to send DAO balances to. var DAORefundContract = common.HexToAddress("0xbf4ed7b27f1d666546e30d74d50d173d20bca754") From 3f128f6e7fd41287cc85b4c9841754cfee422cc8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 16 Jul 2025 01:19:22 +0200 Subject: [PATCH 06/32] consensus/ethash: port to config2 --- consensus/ethash/consensus.go | 31 +++++++++++++++--------------- consensus/ethash/consensus_test.go | 3 ++- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 4f92f1282b9..aa21e91e72e 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" "github.com/holiman/uint256" @@ -249,7 +250,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) } // Verify the block's gas usage and (if applicable) verify the base fee. - if !chain.Config().IsLondon(header.Number) { + if !chain.Config().ActiveAtBlock(forks.London, header.Number) { // Verify BaseFee not present before EIP-1559 fork. if header.BaseFee != nil { return fmt.Errorf("invalid baseFee before fork: have %d, expected 'nil'", header.BaseFee) @@ -265,14 +266,14 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 { return consensus.ErrInvalidNumber } - if chain.Config().IsShanghai(header.Number, header.Time) { + if chain.Config().Active(forks.Shanghai, header.Number.Uint64(), header.Time) { return errors.New("ethash does not support shanghai fork") } // Verify the non-existence of withdrawalsHash. if header.WithdrawalsHash != nil { return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash) } - if chain.Config().IsCancun(header.Number, header.Time) { + if chain.Config().Active(forks.Cancun, header.Number.Uint64(), header.Time) { return errors.New("ethash does not support cancun fork") } // Verify the non-existence of cancun-specific header fields @@ -308,22 +309,22 @@ func (ethash *Ethash) CalcDifficulty(chain consensus.ChainHeaderReader, time uin // CalcDifficulty is the difficulty adjustment algorithm. It returns // the difficulty that a new block should have when created at time // given the parent block's time and difficulty. -func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int { +func CalcDifficulty(config *params.Config2, time uint64, parent *types.Header) *big.Int { next := new(big.Int).Add(parent.Number, big1) switch { - case config.IsGrayGlacier(next): + case config.ActiveAtBlock(forks.GrayGlacier, next): return calcDifficultyEip5133(time, parent) - case config.IsArrowGlacier(next): + case config.ActiveAtBlock(forks.ArrowGlacier, next): return calcDifficultyEip4345(time, parent) - case config.IsLondon(next): + case config.ActiveAtBlock(forks.London, next): return calcDifficultyEip3554(time, parent) - case config.IsMuirGlacier(next): + case config.ActiveAtBlock(forks.MuirGlacier, next): return calcDifficultyEip2384(time, parent) - case config.IsConstantinople(next): + case config.ActiveAtBlock(forks.Constantinople, next): return calcDifficultyConstantinople(time, parent) - case config.IsByzantium(next): + case config.ActiveAtBlock(forks.Byzantium, next): return calcDifficultyByzantium(time, parent) - case config.IsHomestead(next): + case config.ActiveAtBlock(forks.Homestead, next): return calcDifficultyHomestead(time, parent) default: return calcDifficultyFrontier(time, parent) @@ -519,7 +520,7 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea ethash.Finalize(chain, header, state, body) // Assign the final state root to header. - header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + header.Root = state.IntermediateRoot(chain.Config().ActiveAtBlock(forks.SpuriousDragon, header.Number)) // Header seems complete, assemble into a block and return return types.NewBlock(header, &types.Body{Transactions: body.Transactions, Uncles: body.Uncles}, receipts, trie.NewStackTrie(nil)), nil @@ -567,13 +568,13 @@ func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) { // accumulateRewards credits the coinbase of the given block with the mining // reward. The total reward consists of the static block reward and rewards for // included uncles. The coinbase of each uncle block is also rewarded. -func accumulateRewards(config *params.ChainConfig, stateDB vm.StateDB, header *types.Header, uncles []*types.Header) { +func accumulateRewards(config *params.Config2, stateDB vm.StateDB, header *types.Header, uncles []*types.Header) { // Select the correct block reward based on chain progression blockReward := FrontierBlockReward - if config.IsByzantium(header.Number) { + if config.ActiveAtBlock(forks.Byzantium, header.Number) { blockReward = ByzantiumBlockReward } - if config.IsConstantinople(header.Number) { + if config.ActiveAtBlock(forks.Constantinople, header.Number) { blockReward = ConstantinopleBlockReward } // Accumulate the rewards for the miner and any included uncles diff --git a/consensus/ethash/consensus_test.go b/consensus/ethash/consensus_test.go index e3793cd1b01..a82013b7b4e 100644 --- a/consensus/ethash/consensus_test.go +++ b/consensus/ethash/consensus_test.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" ) type diffTest struct { @@ -74,7 +75,7 @@ func TestCalcDifficulty(t *testing.T) { t.Fatal(err) } - config := ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1150000)} + config := params.NewConfig2(params.Activations{forks.Homestead: 1150000}) for name, test := range tests { number := new(big.Int).Sub(test.CurrentBlocknumber, big.NewInt(1)) From f79354d556995231a2a554ed2ca7688b720a180b Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 16 Jul 2025 01:24:58 +0200 Subject: [PATCH 07/32] consensus/beacon: port to config2 --- consensus/beacon/consensus.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 196cbc857ce..cfe1b270b82 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" "github.com/ethereum/go-ethereum/trie" "github.com/holiman/uint256" ) @@ -74,11 +75,12 @@ func New(ethone consensus.Engine) *Beacon { // isPostMerge reports whether the given block number is assumed to be post-merge. // Here we check the MergeNetsplitBlock to allow configuring networks with a PoW or // PoA chain for unit testing purposes. -func isPostMerge(config *params.ChainConfig, blockNum uint64, timestamp uint64) bool { - mergedAtGenesis := config.TerminalTotalDifficulty != nil && config.TerminalTotalDifficulty.Sign() == 0 +func isPostMerge(config *params.Config2, blockNum uint64, timestamp uint64) bool { + ttd := params.Get[*params.TerminalTotalDifficulty](config).BigInt() + mergedAtGenesis := ttd != nil && ttd.Sign() == 0 return mergedAtGenesis || - config.MergeNetsplitBlock != nil && blockNum >= config.MergeNetsplitBlock.Uint64() || - config.ShanghaiTime != nil && timestamp >= *config.ShanghaiTime + config.Active(forks.Paris, blockNum, timestamp) || + config.Active(forks.Shanghai, blockNum, timestamp) } // Author implements consensus.Engine, returning the verified author of the block. @@ -256,7 +258,7 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa return err } // Verify existence / non-existence of withdrawalsHash. - shanghai := chain.Config().IsShanghai(header.Number, header.Time) + shanghai := chain.Config().Active(forks.Shanghai, header.Number.Uint64(), header.Time) if shanghai && header.WithdrawalsHash == nil { return errors.New("missing withdrawalsHash") } @@ -264,7 +266,7 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash) } // Verify the existence / non-existence of cancun-specific header fields - cancun := chain.Config().IsCancun(header.Number, header.Time) + cancun := chain.Config().Active(forks.Cancun, header.Number.Uint64(), header.Time) if !cancun { switch { case header.ExcessBlobGas != nil: @@ -357,7 +359,7 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea if !beacon.IsPoSHeader(header) { return beacon.ethone.FinalizeAndAssemble(chain, header, state, body, receipts) } - shanghai := chain.Config().IsShanghai(header.Number, header.Time) + shanghai := chain.Config().Active(forks.Shanghai, header.Number.Uint64(), header.Time) if shanghai { // All blocks after Shanghai must include a withdrawals root. if body.Withdrawals == nil { @@ -379,7 +381,7 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea // Create the block witness and attach to block. // This step needs to happen as late as possible to catch all access events. - if chain.Config().IsVerkle(header.Number, header.Time) { + if chain.Config().Active(forks.Verkle, header.Number.Uint64(), header.Time) { keys := state.AccessEvents().Keys() // Open the pre-tree to prove the pre-state against From 8725dece93ebe8ac00cd15b169981cb2254ee01e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 16 Jul 2025 01:25:26 +0200 Subject: [PATCH 08/32] params: add BigInt to TerminalTotalDifficulty --- params/chainparam.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/params/chainparam.go b/params/chainparam.go index 925e5b0d52b..d85ea4f812b 100644 --- a/params/chainparam.go +++ b/params/chainparam.go @@ -36,7 +36,6 @@ func (v *ChainID) Validate(cfg *Config2) error { if b.Sign() <= 0 { return fmt.Errorf("invalid chainID value %v", b) } - return nil } @@ -59,6 +58,10 @@ func (v *TerminalTotalDifficulty) UnmarshalText(input []byte) error { return (*big.Int)(v).UnmarshalText(input) } +func (v *TerminalTotalDifficulty) BigInt() *big.Int { + return (*big.Int)(v) +} + func (v *TerminalTotalDifficulty) Validate(cfg *Config2) error { return nil } From 31b636098f56650f5109afc73b9e676d650451a2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 16 Jul 2025 01:25:43 +0200 Subject: [PATCH 09/32] consensus: update for config2 --- consensus/consensus.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/consensus.go b/consensus/consensus.go index a68351f7ffa..8068af0a150 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -31,7 +31,7 @@ import ( // blockchain during header verification. type ChainHeaderReader interface { // Config retrieves the blockchain's chain configuration. - Config() *params.ChainConfig + Config() *params.Config2 // CurrentHeader retrieves the current header from the local chain. CurrentHeader() *types.Header From 74f62ced68ccb6bc05208900dae9f2860de24ec0 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 16 Jul 2025 02:35:10 +0200 Subject: [PATCH 10/32] params/forks: add After --- params/forks/forks.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/params/forks/forks.go b/params/forks/forks.go index 9d6a42f68a3..8fed40f2234 100644 --- a/params/forks/forks.go +++ b/params/forks/forks.go @@ -102,6 +102,10 @@ func (f Fork) HasBlobs() bool { return f&hasBlobs != 0 } +func (f Fork) After(other Fork) bool { + return f&unconfigMask >= other&unconfigMask +} + // String implements fmt.Stringer. func (f Fork) String() string { s, ok := forkToString[f] From e3725af2b14098757a9dc6bd60af811a77738ae8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 16 Jul 2025 02:35:18 +0200 Subject: [PATCH 11/32] params/presets: add TestChainConfig --- params/presets/presets.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/params/presets/presets.go b/params/presets/presets.go index 8af1aab366c..0903ccffcca 100644 --- a/params/presets/presets.go +++ b/params/presets/presets.go @@ -102,6 +102,27 @@ var AllEthashProtocolChanges = params.NewConfig2( params.NewTerminalTotalDifficulty("0xffffffffffffff"), ) +// TestChainConfig contains every protocol change (EIPs) introduced +// and accepted by the Ethereum core developers for testing purposes. +var TestChainConfig = params.NewConfig2( + params.Activations{ + forks.Homestead: 0, + forks.TangerineWhistle: 0, + forks.SpuriousDragon: 0, + forks.Byzantium: 0, + forks.Constantinople: 0, + forks.Petersburg: 0, + forks.Istanbul: 0, + forks.MuirGlacier: 0, + forks.Berlin: 0, + forks.London: 0, + forks.ArrowGlacier: 0, + forks.GrayGlacier: 0, + }, + params.NewChainID("1"), + params.NewTerminalTotalDifficulty("0xffffffffffff"), +) + // MergedTestChainConfig2 contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers for testing purposes. var MergedTestChainConfig = params.NewConfig2( From fcf3b4329c300e8b5270c10508966096dbe49045 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 16 Jul 2025 02:35:32 +0200 Subject: [PATCH 12/32] core/types: port to config2 --- core/types/block_test.go | 3 +- core/types/receipt.go | 2 +- core/types/receipt_test.go | 4 +- core/types/transaction_signing.go | 66 +++++++++++++++++-------------- core/types/transaction_test.go | 6 +-- 5 files changed, 45 insertions(+), 36 deletions(-) diff --git a/core/types/block_test.go b/core/types/block_test.go index 2130a2fcf3b..3b11f1cade4 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/blocktest" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/presets" "github.com/ethereum/go-ethereum/rlp" "github.com/holiman/uint256" ) @@ -278,7 +279,7 @@ func makeBenchBlock() *Block { key, _ = crypto.GenerateKey() txs = make([]*Transaction, 70) receipts = make([]*Receipt, len(txs)) - signer = LatestSigner(params.TestChainConfig) + signer = LatestSigner(presets.TestChainConfig) uncles = make([]*Header, 3) ) header := &Header{ diff --git a/core/types/receipt.go b/core/types/receipt.go index 5b6669f2741..2d02262bcde 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -379,7 +379,7 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { // DeriveFields fills the receipts with their computed fields based on consensus // data and contextual infos like containing block and transactions. -func (rs Receipts) DeriveFields(config *params.ChainConfig, blockHash common.Hash, blockNumber uint64, blockTime uint64, baseFee *big.Int, blobGasPrice *big.Int, txs []*Transaction) error { +func (rs Receipts) DeriveFields(config *params.Config2, blockHash common.Hash, blockNumber uint64, blockTime uint64, baseFee *big.Int, blobGasPrice *big.Int, txs []*Transaction) error { signer := MakeSigner(config, new(big.Int).SetUint64(blockNumber), blockTime) logIndex := uint(0) diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 8f805ff0961..53f892b7ad2 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/presets" "github.com/ethereum/go-ethereum/rlp" "github.com/holiman/uint256" "github.com/kylelemons/godebug/diff" @@ -330,7 +331,8 @@ func TestDeriveFields(t *testing.T) { blobGasPrice := big.NewInt(920) receipts := getTestReceipts() derivedReceipts := clearComputedFieldsOnReceipts(receipts) - err := Receipts(derivedReceipts).DeriveFields(params.TestChainConfig, blockHash, blockNumber.Uint64(), blockTime, basefee, blobGasPrice, txs) + config := presets.AllEthashProtocolChanges + err := Receipts(derivedReceipts).DeriveFields(config, blockHash, blockNumber.Uint64(), blockTime, basefee, blobGasPrice, txs) if err != nil { t.Fatalf("DeriveFields(...) = %v, want ", err) } diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 01aa67c6ba4..c37a0f7e1ff 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -39,20 +39,23 @@ type sigCache struct { } // MakeSigner returns a Signer based on the given chain config and block number. -func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer { +func MakeSigner(config *params.Config2, blockNumber *big.Int, blockTime uint64) Signer { + number := blockNumber.Uint64() + chainID := params.Get[*params.ChainID](config).BigInt() + var signer Signer switch { - case config.IsPrague(blockNumber, blockTime): - signer = NewPragueSigner(config.ChainID) - case config.IsCancun(blockNumber, blockTime): - signer = NewCancunSigner(config.ChainID) - case config.IsLondon(blockNumber): - signer = NewLondonSigner(config.ChainID) - case config.IsBerlin(blockNumber): - signer = NewEIP2930Signer(config.ChainID) - case config.IsEIP155(blockNumber): - signer = NewEIP155Signer(config.ChainID) - case config.IsHomestead(blockNumber): + case config.Active(forks.Prague, number, blockTime): + signer = NewPragueSigner(chainID) + case config.Active(forks.Cancun, number, blockTime): + signer = NewCancunSigner(chainID) + case config.Active(forks.London, number, blockTime): + signer = NewLondonSigner(chainID) + case config.Active(forks.Berlin, number, blockTime): + signer = NewEIP2930Signer(chainID) + case config.Active(forks.SpuriousDragon, number, blockTime): + signer = NewEIP155Signer(chainID) + case config.Active(forks.Homestead, number, blockTime): signer = HomesteadSigner{} default: signer = FrontierSigner{} @@ -67,20 +70,22 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint // // Use this in transaction-handling code where the current block number is unknown. If you // have the current block number available, use MakeSigner instead. -func LatestSigner(config *params.ChainConfig) Signer { +func LatestSigner(config *params.Config2) Signer { + chainID := params.Get[*params.ChainID](config).BigInt() + var signer Signer - if config.ChainID != nil { + if chainID != nil { switch { - case config.PragueTime != nil: - signer = NewPragueSigner(config.ChainID) - case config.CancunTime != nil: - signer = NewCancunSigner(config.ChainID) - case config.LondonBlock != nil: - signer = NewLondonSigner(config.ChainID) - case config.BerlinBlock != nil: - signer = NewEIP2930Signer(config.ChainID) - case config.EIP155Block != nil: - signer = NewEIP155Signer(config.ChainID) + case config.Scheduled(forks.Prague): + signer = NewPragueSigner(chainID) + case config.Scheduled(forks.Cancun): + signer = NewCancunSigner(chainID) + case config.Scheduled(forks.London): + signer = NewLondonSigner(chainID) + case config.Scheduled(forks.Berlin): + signer = NewEIP2930Signer(chainID) + case config.Scheduled(forks.SpuriousDragon): + signer = NewEIP155Signer(chainID) default: signer = HomesteadSigner{} } @@ -198,25 +203,26 @@ func newModernSigner(chainID *big.Int, fork forks.Fork) Signer { } // configure legacy signer switch { - case fork >= forks.SpuriousDragon: + case fork.After(forks.SpuriousDragon): s.legacy = NewEIP155Signer(chainID) - case fork >= forks.Homestead: + case fork.After(forks.Homestead): s.legacy = HomesteadSigner{} default: s.legacy = FrontierSigner{} } s.txtypes[LegacyTxType] = struct{}{} // configure tx types - if fork >= forks.Berlin { + // TODO: fix this + if fork.After(forks.Berlin) { s.txtypes[AccessListTxType] = struct{}{} } - if fork >= forks.London { + if fork.After(forks.London) { s.txtypes[DynamicFeeTxType] = struct{}{} } - if fork >= forks.Cancun { + if fork.After(forks.Cancun) { s.txtypes[BlobTxType] = struct{}{} } - if fork >= forks.Prague { + if fork.After(forks.Prague) { s.txtypes[SetCodeTxType] = struct{}{} } return s diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index 7d5e2f058af..23636206a13 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -29,7 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/presets" "github.com/ethereum/go-ethereum/rlp" ) @@ -596,7 +596,7 @@ func BenchmarkHash(b *testing.B) { } func BenchmarkEffectiveGasTip(b *testing.B) { - signer := LatestSigner(params.TestChainConfig) + signer := LatestSigner(presets.TestChainConfig) key, _ := crypto.GenerateKey() txdata := &DynamicFeeTx{ ChainID: big.NewInt(1), @@ -634,7 +634,7 @@ func BenchmarkEffectiveGasTip(b *testing.B) { } func TestEffectiveGasTipInto(t *testing.T) { - signer := LatestSigner(params.TestChainConfig) + signer := LatestSigner(presets.TestChainConfig) key, _ := crypto.GenerateKey() testCases := []struct { From 6b267917527718ae9117667535b8aea91030c269 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 16 Jul 2025 13:09:21 +0200 Subject: [PATCH 13/32] core/rawdb: port to config2 --- core/rawdb/accessors_chain.go | 2 +- core/rawdb/accessors_chain_test.go | 14 +++++++------- core/rawdb/accessors_indexes.go | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 0782a0e7dac..e9abda6eea0 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -601,7 +601,7 @@ func ReadRawReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Rec // The current implementation populates these metadata fields by reading the receipts' // corresponding block body, so if the block body is not found it will return nil even // if the receipt itself is stored. -func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64, time uint64, config *params.ChainConfig) types.Receipts { +func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64, time uint64, config *params.Config2) types.Receipts { // We're deriving many fields from the block body, retrieve beside the receipt receipts := ReadRawReceipts(db, hash, number) if receipts == nil { diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 196f3dac8f3..3a73fed69f0 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -29,7 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/presets" "github.com/ethereum/go-ethereum/rlp" "golang.org/x/crypto/sha3" ) @@ -356,7 +356,7 @@ func TestBlockReceiptStorage(t *testing.T) { // Check that no receipt entries are in a pristine database hash := common.BytesToHash([]byte{0x03, 0x14}) - if rs := ReadReceipts(db, hash, 0, 0, params.TestChainConfig); len(rs) != 0 { + if rs := ReadReceipts(db, hash, 0, 0, presets.TestChainConfig); len(rs) != 0 { t.Fatalf("non existent receipts returned: %v", rs) } // Insert the body that corresponds to the receipts @@ -364,7 +364,7 @@ func TestBlockReceiptStorage(t *testing.T) { // Insert the receipt slice into the database and check presence WriteReceipts(db, hash, 0, receipts) - if rs := ReadReceipts(db, hash, 0, 0, params.TestChainConfig); len(rs) == 0 { + if rs := ReadReceipts(db, hash, 0, 0, presets.TestChainConfig); len(rs) == 0 { t.Fatal("no receipts returned") } else { if err := checkReceiptsRLP(rs, receipts); err != nil { @@ -373,7 +373,7 @@ func TestBlockReceiptStorage(t *testing.T) { } // Delete the body and ensure that the receipts are no longer returned (metadata can't be recomputed) DeleteBody(db, hash, 0) - if rs := ReadReceipts(db, hash, 0, 0, params.TestChainConfig); rs != nil { + if rs := ReadReceipts(db, hash, 0, 0, presets.TestChainConfig); rs != nil { t.Fatalf("receipts returned when body was deleted: %v", rs) } // Ensure that receipts without metadata can be returned without the block body too @@ -388,7 +388,7 @@ func TestBlockReceiptStorage(t *testing.T) { WriteBody(db, hash, 0, body) DeleteReceipts(db, hash, 0) - if rs := ReadReceipts(db, hash, 0, 0, params.TestChainConfig); len(rs) != 0 { + if rs := ReadReceipts(db, hash, 0, 0, presets.TestChainConfig); len(rs) != 0 { t.Fatalf("deleted receipts returned: %v", rs) } } @@ -751,7 +751,7 @@ func TestReadLogs(t *testing.T) { hash := common.BytesToHash([]byte{0x03, 0x14}) // Check that no receipt entries are in a pristine database - if rs := ReadReceipts(db, hash, 0, 0, params.TestChainConfig); len(rs) != 0 { + if rs := ReadReceipts(db, hash, 0, 0, presets.TestChainConfig); len(rs) != 0 { t.Fatalf("non existent receipts returned: %v", rs) } // Insert the body that corresponds to the receipts @@ -842,7 +842,7 @@ func TestDeriveLogFields(t *testing.T) { // Derive log metadata fields number := big.NewInt(1) hash := common.BytesToHash([]byte{0x03, 0x14}) - types.Receipts(receipts).DeriveFields(params.TestChainConfig, hash, number.Uint64(), 12, big.NewInt(0), big.NewInt(0), txs) + types.Receipts(receipts).DeriveFields(presets.TestChainConfig, hash, number.Uint64(), 12, big.NewInt(0), big.NewInt(0), txs) // Iterate over all the computed fields and check that they're correct logIndex := uint(0) diff --git a/core/rawdb/accessors_indexes.go b/core/rawdb/accessors_indexes.go index a725f144d46..e4eb1a282f4 100644 --- a/core/rawdb/accessors_indexes.go +++ b/core/rawdb/accessors_indexes.go @@ -202,7 +202,7 @@ func ReadCanonicalTransaction(db ethdb.Reader, hash common.Hash) (*types.Transac // ReadCanonicalReceipt retrieves a specific transaction receipt from the database, // along with its added positional metadata. Notably, only the receipt in the canonical // chain is visible. -func ReadCanonicalReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig) (*types.Receipt, common.Hash, uint64, uint64) { +func ReadCanonicalReceipt(db ethdb.Reader, hash common.Hash, config *params.Config2) (*types.Receipt, common.Hash, uint64, uint64) { // Retrieve the context of the receipt based on the transaction hash blockNumber := ReadTxLookupEntry(db, hash) if blockNumber == nil { From b25e44a8414bc8aa4a8841fbc363b39a8b33dd5e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 18 Jul 2025 13:00:36 +0200 Subject: [PATCH 14/32] params: new param system --- params/chainparam.go | 122 ++++++++++--------------------------------- params/config2.go | 41 ++++++--------- params/registry.go | 110 ++++++++++++++++++++------------------ 3 files changed, 104 insertions(+), 169 deletions(-) diff --git a/params/chainparam.go b/params/chainparam.go index d85ea4f812b..36a2a89712d 100644 --- a/params/chainparam.go +++ b/params/chainparam.go @@ -9,79 +9,40 @@ import ( ) // ChainID -type ChainID big.Int - -func NewChainID(input string) *ChainID { - b, ok := new(big.Int).SetString(input, 0) - if !ok { - panic("invalid chainID: " + input) - } - return (*ChainID)(b) -} - -func (v *ChainID) MarshalText() ([]byte, error) { - return (*big.Int)(v).MarshalText() -} - -func (v *ChainID) UnmarshalText(input []byte) error { - return (*big.Int)(v).UnmarshalText(input) -} - -func (v *ChainID) BigInt() *big.Int { - return (*big.Int)(v) -} - -func (v *ChainID) Validate(cfg *Config2) error { - b := (*big.Int)(v) - if b.Sign() <= 0 { - return fmt.Errorf("invalid chainID value %v", b) - } - return nil -} +var ChainID = Define(T[*big.Int]{ + Name: "chainId", + Validate: func(v *big.Int, cfg *Config2) error { + if v.Sign() <= 0 { + return fmt.Errorf("invalid chainID value %v", v) + } + return nil + }, +}) // TerminalTotalDifficulty (TTD) is the total difficulty value where -type TerminalTotalDifficulty big.Int - -func NewTerminalTotalDifficulty(input string) *TerminalTotalDifficulty { - b, ok := new(big.Int).SetString(input, 0) - if !ok { - panic("invalid terminal total difficulty: " + input) - } - return (*TerminalTotalDifficulty)(b) -} - -func (v *TerminalTotalDifficulty) MarshalText() ([]byte, error) { - return (*big.Int)(v).MarshalText() -} - -func (v *TerminalTotalDifficulty) UnmarshalText(input []byte) error { - return (*big.Int)(v).UnmarshalText(input) -} - -func (v *TerminalTotalDifficulty) BigInt() *big.Int { - return (*big.Int)(v) -} - -func (v *TerminalTotalDifficulty) Validate(cfg *Config2) error { - return nil -} +var TerminalTotalDifficulty = Define(T[*big.Int]{ + Name: "terminalTotalDifficulty", + Optional: true, +}) // DepositContractAddress configures the location of the deposit contract. -type DepositContractAddress common.Address - -func (v DepositContractAddress) Validate(cfg *Config2) error { - return nil -} +var DepositContractAddress = Define(T[common.Address]{ + Name: "depositContractAddress", + Optional: true, +}) // This configures the EIP-4844 parameters across forks. // There must be an entry for each fork -type BlobSchedule map[forks.Fork]BlobConfig - -// Validate checks that all forks with blobs explicitly define the blob schedule configuration. -func (v BlobSchedule) Validate(cfg *Config2) error { +var BlobSchedule = Define(T[map[forks.Fork]BlobConfig]{ + Name: "blobSchedule", + Optional: true, + Validate: validateBlobSchedule, +}) + +func validateBlobSchedule(schedule map[forks.Fork]BlobConfig, cfg *Config2) error { + // Check that all forks with blobs explicitly define the blob schedule configuration. for _, f := range forks.CanonOrder { if f.HasBlobs() { - schedule := Get[BlobSchedule](cfg) bcfg, defined := schedule[f] if cfg.Scheduled(f) && !defined { return fmt.Errorf("invalid chain configuration: missing entry for fork %q in blobSchedule", f) @@ -99,32 +60,7 @@ func (v BlobSchedule) Validate(cfg *Config2) error { // DAOForkSupport is the chain parameter that configures the DAO fork. // true=supports or false=opposes the fork. // The default value is true. -type DAOForkSupport bool - -func (v DAOForkSupport) Validate(cfg *Config2) error { - return nil -} - -func init() { - Define(Parameter[*ChainID]{ - Name: "chainId", - Optional: false, - }) - Define(Parameter[*TerminalTotalDifficulty]{ - Name: "terminalTotalDifficulty", - Optional: false, - }) - Define(Parameter[DAOForkSupport]{ - Name: "daoForkSupport", - Optional: true, - Default: true, - }) - Define(Parameter[BlobSchedule]{ - Name: "blobSchedule", - Optional: true, - }) - Define(Parameter[DepositContractAddress]{ - Name: "depositContractAddress", - Optional: true, - }) -} +var DAOForkSupport = Define(T[bool]{ + Optional: true, + Default: true, +}) diff --git a/params/config2.go b/params/config2.go index 79c74060814..8dfcbba3c43 100644 --- a/params/config2.go +++ b/params/config2.go @@ -22,7 +22,6 @@ import ( "fmt" "maps" "math/big" - "reflect" "slices" "strings" @@ -35,21 +34,17 @@ type Activations map[forks.Fork]uint64 // Config2 represents the chain configuration. type Config2 struct { activation Activations - param map[reflect.Type]ParameterType + param map[int]any } -func NewConfig2(activations Activations, param ...ParameterType) *Config2 { +func NewConfig2(activations Activations, param ...ParamValue) *Config2 { cfg := &Config2{ activation: maps.Clone(activations), - param: make(map[reflect.Type]ParameterType, len(param)), + param: make(map[int]any, len(param)), } cfg.activation[forks.Frontier] = 0 for _, pv := range param { - info, ok := findParam(pv) - if !ok { - panic(fmt.Sprintf("undefined chain parameter type %T", pv)) - } - cfg.param[info.rtype] = pv + cfg.param[pv.id] = pv.value } return cfg } @@ -109,12 +104,12 @@ func (cfg *Config2) LatestFork(time uint64) forks.Fork { func (cfg *Config2) MarshalJSON() ([]byte, error) { m := make(map[string]any) // params - for _, p := range cfg.param { - info, ok := findParam(p) + for id, value := range cfg.param { + info, ok := paramRegistry[id] if !ok { - panic(fmt.Sprintf("unknown chain parameter %T", p)) + panic(fmt.Sprintf("unknown chain parameter id %v", id)) } - m[info.name] = p + m[info.name] = value } // forks for f, act := range cfg.activation { @@ -187,15 +182,15 @@ func (cfg *Config2) decodeActivation(key string, dec *json.Decoder) error { } func (cfg *Config2) decodeParameter(key string, dec *json.Decoder) error { - info, ok := paramRegistryByName[key] + id, ok := paramRegistryByName[key] if !ok { return fmt.Errorf("unknown chain parameter %q", key) } - v := reflect.New(info.rtype).Interface() + v := paramRegistry[id].new() if err := dec.Decode(v); err != nil { return err } - cfg.param[info.rtype] = v.(ParameterType) + cfg.param[id] = v return nil } @@ -235,15 +230,15 @@ func (cfg *Config2) Validate() error { } // Check parameters. - for rtype, info := range paramRegistry { - v, isSet := cfg.param[rtype] + for id, info := range paramRegistry { + v, isSet := cfg.param[id] if !isSet { if !info.optional { return fmt.Errorf("required chain parameter %s is not set", info.name) } - v = info.defaultVal + v = info.defaultValue } - if err := v.Validate(cfg); err != nil { + if err := info.validate(v, cfg); err != nil { return fmt.Errorf("invalid %s: %w", info.name, err) } } @@ -296,12 +291,10 @@ func (cfg *Config2) checkCompatible(newcfg *Config2, num uint64, time uint64) *C } } - if cfg.Active(forks.DAO, num, time) && Get[DAOForkSupport](cfg) != Get[DAOForkSupport](newcfg) { + if cfg.Active(forks.DAO, num, time) && DAOForkSupport.Get(cfg) != DAOForkSupport.Get(newcfg) { return newBlockCompatError2("DAO fork support flag", forks.DAO, cfg, newcfg) } - chainID := (*big.Int)(Get[*ChainID](cfg)) - newChainID := (*big.Int)(Get[*ChainID](newcfg)) - if cfg.Active(forks.TangerineWhistle, num, time) && !configBlockEqual(chainID, newChainID) { + if cfg.Active(forks.TangerineWhistle, num, time) && !configBlockEqual(ChainID.Get(cfg), ChainID.Get(newcfg)) { return newBlockCompatError2("EIP158 chain ID", forks.TangerineWhistle, cfg, newcfg) } diff --git a/params/registry.go b/params/registry.go index 1fd71c71798..17dc838a67c 100644 --- a/params/registry.go +++ b/params/registry.go @@ -18,72 +18,78 @@ package params import ( "fmt" - "reflect" ) -type ParameterType interface{ - Validate(*Config2) error +type Parameter[V any] struct{ + info regInfo } -type paramInfo struct { - rtype reflect.Type - name string - optional bool - defaultVal ParameterType +func (p Parameter[V]) Get(cfg *Config2) V { + v, ok := cfg.param[p.info.id] + if ok { + return v.(V) + } + return p.info.defaultValue.(V) +} + +func (p Parameter[V]) V(v V) ParamValue { + return ParamValue{p.info.id, v} +} + +type ParamValue struct{ + id int + value any } var ( - paramRegistry = map[reflect.Type]*paramInfo{} - paramRegistryByName = map[string]*paramInfo{} + paramCounter int + paramRegistry = map[int]regInfo{} + paramRegistryByName = map[string]int{} ) -// Get retrieves the value of a chain parameter. -func Get[T ParameterType](cfg *Config2) T { - for _, p := range cfg.param { - if v, ok := p.(T); ok { - return v - } - } - // get default - var z T - info, ok := findParam(z) - if !ok { - panic(fmt.Sprintf("unknown parameter type %T", z)) - } - return info.defaultVal.(T) +type T[V any] struct{ + Name string + Optional bool + Default V + Validate func(v V, cfg *Config2) error } -// Parameter is the definition of a chain parameter. -type Parameter[T ParameterType] struct { - Name string - Optional bool - Default T +type regInfo struct{ + id int + name string + optional bool + defaultValue any + new func() any + validate func(any, *Config2) error } // Define creates a chain parameter in the registry. -func Define[T ParameterType](def Parameter[T]) { - var z T - info, defined := paramRegistryByName[def.Name] - if defined { - panic(fmt.Sprintf("chain parameter %q already registered with type %v", info.name, info.rtype)) - } - rtype := reflect.TypeOf(z) - info, defined = paramRegistry[rtype] - if defined { - panic(fmt.Sprintf("chain parameter of type %v already registered with name %q", rtype, info.name)) - } - info = ¶mInfo{ - rtype: rtype, - name: def.Name, - optional: def.Optional, - defaultVal: def.Default, +func Define[V any](def T[V]) Parameter[V] { + if id, defined := paramRegistryByName[def.Name]; defined { + info := paramRegistry[id] + panic(fmt.Sprintf("chain parameter %q already registered with type %T", def.Name, info.defaultValue)) } - paramRegistry[rtype] = info - paramRegistryByName[def.Name] = info -} -func findParam(v any) (*paramInfo, bool) { - rtype := reflect.TypeOf(v) - info, ok := paramRegistry[rtype] - return info, ok + id := paramCounter + paramCounter++ + + regInfo := regInfo{ + id: id, + name: def.Name, + optional: def.Optional, + defaultValue: def.Default, + new: func() any { + var z V + return z + }, + validate: func(v any, cfg *Config2) error { + if def.Validate == nil { + return nil + } + return def.Validate(v.(V), cfg) + }, + } + paramRegistry[id] = regInfo + paramRegistryByName[def.Name] = id + return Parameter[V]{info: regInfo} } From 73710ff3c52d85664df0dc1b7e85883a0e172670 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 18 Jul 2025 13:17:25 +0200 Subject: [PATCH 15/32] params: move some code around --- params/chainparam.go | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/params/chainparam.go b/params/chainparam.go index 36a2a89712d..9e20c03348e 100644 --- a/params/chainparam.go +++ b/params/chainparam.go @@ -10,13 +10,24 @@ import ( // ChainID var ChainID = Define(T[*big.Int]{ - Name: "chainId", - Validate: func(v *big.Int, cfg *Config2) error { - if v.Sign() <= 0 { - return fmt.Errorf("invalid chainID value %v", v) - } - return nil - }, + Name: "chainId", + Optional: false, + Validate: validateChainID, +}) + +func validateChainID(v *big.Int, cfg *Config2) error { + if v.Sign() <= 0 { + return fmt.Errorf("invalid chainID value %v", v) + } + return nil +} + +// DAOForkSupport is the chain parameter that configures the DAO fork. +// true=supports or false=opposes the fork. +// The default value is true. +var DAOForkSupport = Define(T[bool]{ + Optional: true, + Default: true, }) // TerminalTotalDifficulty (TTD) is the total difficulty value where @@ -56,11 +67,3 @@ func validateBlobSchedule(schedule map[forks.Fork]BlobConfig, cfg *Config2) erro } return nil } - -// DAOForkSupport is the chain parameter that configures the DAO fork. -// true=supports or false=opposes the fork. -// The default value is true. -var DAOForkSupport = Define(T[bool]{ - Optional: true, - Default: true, -}) From c2849490995d55ceb341a78ab518070a20235a6c Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 18 Jul 2025 13:17:33 +0200 Subject: [PATCH 16/32] params/presets: update for new system --- params/presets/presets.go | 45 ++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/params/presets/presets.go b/params/presets/presets.go index 0903ccffcca..0f464cb5264 100644 --- a/params/presets/presets.go +++ b/params/presets/presets.go @@ -17,11 +17,16 @@ package presets import ( + "math" + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params/forks" ) +var mainnetTD, _ = new(big.Int).SetString("58_750_000_000_000_000_000_000", 0) + var Mainnet = params.NewConfig2( params.Activations{ forks.Homestead: 1_150_000, @@ -43,16 +48,18 @@ var Mainnet = params.NewConfig2( forks.Cancun: 1710338135, forks.Prague: 1746612311, }, - params.NewChainID("1"), - params.NewTerminalTotalDifficulty("58_750_000_000_000_000_000_000"), - params.DepositContractAddress(common.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa")), - params.DAOForkSupport(true), - params.BlobSchedule{ + params.ChainID.V(big.NewInt(1)), + params.TerminalTotalDifficulty.V(mainnetTD), + params.DepositContractAddress.V(common.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa")), + params.DAOForkSupport.V(true), + params.BlobSchedule.V(map[forks.Fork]params.BlobConfig{ forks.Cancun: *params.DefaultCancunBlobConfig, forks.Prague: *params.DefaultPragueBlobConfig, - }, + }), ) +var sepoliaTD, _ = new(big.Int).SetString("17_000_000_000_000_000", 0) + // SepoliaChainConfig contains the chain parameters to run a node on the Sepolia test network. var Sepolia = params.NewConfig2( params.Activations{ @@ -72,13 +79,13 @@ var Sepolia = params.NewConfig2( forks.Cancun: 1706655072, forks.Prague: 1741159776, }, - params.NewChainID("11155111"), - params.NewTerminalTotalDifficulty("17_000_000_000_000_000"), - params.DepositContractAddress(common.HexToAddress("0x7f02c3e3c98b133055b8b348b2ac625669ed295d")), - params.BlobSchedule{ + params.ChainID.V(big.NewInt(11155111)), + params.TerminalTotalDifficulty.V(sepoliaTD), + params.DepositContractAddress.V(common.HexToAddress("0x7f02c3e3c98b133055b8b348b2ac625669ed295d")), + params.BlobSchedule.V(map[forks.Fork]params.BlobConfig{ forks.Cancun: *params.DefaultCancunBlobConfig, forks.Prague: *params.DefaultPragueBlobConfig, - }, + }), ) // AllEthashProtocolChanges2 contains every protocol change (EIPs) introduced @@ -98,8 +105,8 @@ var AllEthashProtocolChanges = params.NewConfig2( forks.ArrowGlacier: 0, forks.GrayGlacier: 0, }, - params.NewChainID("1337"), - params.NewTerminalTotalDifficulty("0xffffffffffffff"), + params.ChainID.V(big.NewInt(1337)), + params.TerminalTotalDifficulty.V(big.NewInt(math.MaxInt64)), ) // TestChainConfig contains every protocol change (EIPs) introduced @@ -119,8 +126,8 @@ var TestChainConfig = params.NewConfig2( forks.ArrowGlacier: 0, forks.GrayGlacier: 0, }, - params.NewChainID("1"), - params.NewTerminalTotalDifficulty("0xffffffffffff"), + params.ChainID.V(big.NewInt(1)), + params.TerminalTotalDifficulty.V(big.NewInt(math.MaxInt64)), ) // MergedTestChainConfig2 contains every protocol change (EIPs) introduced @@ -145,11 +152,11 @@ var MergedTestChainConfig = params.NewConfig2( forks.Prague: 0, forks.Osaka: 0, }, - params.NewChainID("1"), - params.NewTerminalTotalDifficulty("0"), - params.BlobSchedule{ + params.ChainID.V(big.NewInt(1)), + params.TerminalTotalDifficulty.V(big.NewInt(0)), + params.BlobSchedule.V(map[forks.Fork]params.BlobConfig{ forks.Cancun: *params.DefaultCancunBlobConfig, forks.Prague: *params.DefaultPragueBlobConfig, forks.Osaka: *params.DefaultOsakaBlobConfig, - }, + }), ) From ca4ba1f0411f03c4e7c4714e667eb64fa903a3c2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 18 Jul 2025 13:41:59 +0200 Subject: [PATCH 17/32] params: add SetParam --- params/config2.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/params/config2.go b/params/config2.go index 8dfcbba3c43..248c74fc079 100644 --- a/params/config2.go +++ b/params/config2.go @@ -86,6 +86,15 @@ func (cfg *Config2) SetActivations(forks Activations) *Config2 { return &Config2{activation: newA, param: cfg.param} } +// SetParam returns a new configuration with the given parameter values set. +func (cfg *Config2) SetParam(param ...ParamValue) *Config2 { + cpy := &Config2{activation: cfg.activation, param: maps.Clone(cfg.param)} + for _, pv := range param { + cpy.param[pv.id] = pv.value + } + return cpy +} + // LatestFork returns the latest time-based fork that would be active for the given time. func (cfg *Config2) LatestFork(time uint64) forks.Fork { londonBlock := cfg.activation[forks.London] From 2a6cf8b3eaff23ce452c1569accafe30e235282c Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 18 Jul 2025 13:42:14 +0200 Subject: [PATCH 18/32] consensus/misc/eip4844: update --- consensus/misc/eip4844/eip4844.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/consensus/misc/eip4844/eip4844.go b/consensus/misc/eip4844/eip4844.go index 5bcb7803985..e24db3ab04f 100644 --- a/consensus/misc/eip4844/eip4844.go +++ b/consensus/misc/eip4844/eip4844.go @@ -101,7 +101,7 @@ func CalcExcessBlobGas(config *params.Config2, parent *types.Header, headTimesta // CalcBlobFee calculates the blobfee from the header's excess blob gas field. func CalcBlobFee(config *params.Config2, header *types.Header) *big.Int { - schedule := params.Get[params.BlobSchedule](config) + schedule := params.BlobSchedule.Get(config) if schedule == nil { return new(big.Int) } @@ -123,7 +123,7 @@ func MaxBlobGasPerBlock(cfg *params.Config2, time uint64) uint64 { // LatestMaxBlobsPerBlock returns the latest max blobs per block defined by the // configuration, regardless of the currently active fork. func LatestMaxBlobsPerBlock(cfg *params.Config2) int { - schedule := params.Get[params.BlobSchedule](cfg) + schedule := params.BlobSchedule.Get(cfg) if schedule == nil { return 0 } @@ -141,7 +141,7 @@ func targetBlobsPerBlock(cfg *params.Config2, time uint64) int { } func scheduleAtTime(cfg *params.Config2, time uint64) params.BlobConfig { - schedule := params.Get[params.BlobSchedule](cfg) + schedule := params.BlobSchedule.Get(cfg) if schedule == nil { return params.BlobConfig{} } From d031689977ad2e21500d7954d7da1c179dfe3b8d Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 18 Jul 2025 13:42:29 +0200 Subject: [PATCH 19/32] consensus/misc/eip1559: update --- consensus/misc/eip1559/eip1559.go | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/consensus/misc/eip1559/eip1559.go b/consensus/misc/eip1559/eip1559.go index a9981d19580..65dfca34365 100644 --- a/consensus/misc/eip1559/eip1559.go +++ b/consensus/misc/eip1559/eip1559.go @@ -28,31 +28,25 @@ import ( "github.com/ethereum/go-ethereum/params/forks" ) +var FeeConfigParam = params.Define(params.T[FeeConfig]{ + Name: "eip1559Config", + Optional: true, + Default: FeeConfig{ + ElasticityMultiplier: params.DefaultElasticityMultiplier, + BaseFeeChangeDenominator: params.DefaultBaseFeeChangeDenominator, + }, +}) + type FeeConfig struct { ElasticityMultiplier uint64 `json:"elasticityMultiplier"` BaseFeeChangeDenominator uint64 `json:"baseFeeChangeDenominator"` } -func (fc FeeConfig) Validate(config *params.Config2) error { - return nil -} - -func init() { - params.Define(params.Parameter[FeeConfig]{ - Name: "eip1559Config", - Optional: true, - Default: FeeConfig{ - ElasticityMultiplier: params.DefaultElasticityMultiplier, - BaseFeeChangeDenominator: params.DefaultBaseFeeChangeDenominator, - }, - }) -} - // VerifyEIP1559Header verifies some header attributes which were changed in EIP-1559, // - gas limit check // - basefee check func VerifyEIP1559Header(config *params.Config2, parent, header *types.Header) error { - feecfg := params.Get[FeeConfig](config) + feecfg := FeeConfigParam.Get(config) // Verify that the gas limit remains within allowed bounds parentGasLimit := parent.GasLimit @@ -77,7 +71,7 @@ func VerifyEIP1559Header(config *params.Config2, parent, header *types.Header) e // CalcBaseFee calculates the basefee of the header. func CalcBaseFee(config *params.Config2, parent *types.Header) *big.Int { - feecfg := params.Get[FeeConfig](config) + feecfg := FeeConfigParam.Get(config) // If the current block is the first EIP-1559 block, return the InitialBaseFee. if !config.Active(forks.London, parent.Number.Uint64(), parent.Time) { From 3e675b18f0411547135954b2a8e47921ae586b03 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 18 Jul 2025 13:42:38 +0200 Subject: [PATCH 20/32] consensus/misc: update --- consensus/misc/dao.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/misc/dao.go b/consensus/misc/dao.go index 32932b9bb89..717f9da9b74 100644 --- a/consensus/misc/dao.go +++ b/consensus/misc/dao.go @@ -60,7 +60,7 @@ func VerifyDAOHeaderExtraData(config *params.Config2, header *types.Header) erro } // Depending on whether we support or oppose the fork, validate the extra-data contents - if params.Get[params.DAOForkSupport](config) { + if params.DAOForkSupport.Get(config) { if !bytes.Equal(header.Extra, params.DAOForkBlockExtra) { return ErrBadProDAOExtra } From c1954d280b14a8edb616c19e34f2a99e6f97e65c Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 18 Jul 2025 13:42:48 +0200 Subject: [PATCH 21/32] consensus/beacon: update --- consensus/beacon/consensus.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index cfe1b270b82..b5af7b77a42 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -76,7 +76,7 @@ func New(ethone consensus.Engine) *Beacon { // Here we check the MergeNetsplitBlock to allow configuring networks with a PoW or // PoA chain for unit testing purposes. func isPostMerge(config *params.Config2, blockNum uint64, timestamp uint64) bool { - ttd := params.Get[*params.TerminalTotalDifficulty](config).BigInt() + ttd := params.TerminalTotalDifficulty.Get(config) mergedAtGenesis := ttd != nil && ttd.Sign() == 0 return mergedAtGenesis || config.Active(forks.Paris, blockNum, timestamp) || From 8da6d3759bb531477d30843efc20c5d2d7ef3b43 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 24 Jul 2025 01:06:11 +0200 Subject: [PATCH 22/32] params: new fork & parameter registry --- params/chainparam.go | 13 +- params/config2.go | 117 ++++++++------- params/config2_test.go | 138 +++++++++++++++++ params/forks/forkreg.go | 164 ++++++++++++++++++++ params/forks/forkreg_test.go | 50 +++++++ params/forks/forks.go | 279 +++++++++++++++++++---------------- params/registry.go | 58 +++++--- 7 files changed, 609 insertions(+), 210 deletions(-) create mode 100644 params/config2_test.go create mode 100644 params/forks/forkreg.go create mode 100644 params/forks/forkreg_test.go diff --git a/params/chainparam.go b/params/chainparam.go index 9e20c03348e..b11390a7f4b 100644 --- a/params/chainparam.go +++ b/params/chainparam.go @@ -26,6 +26,7 @@ func validateChainID(v *big.Int, cfg *Config2) error { // true=supports or false=opposes the fork. // The default value is true. var DAOForkSupport = Define(T[bool]{ + Name: "daoForkSupport", Optional: true, Default: true, }) @@ -50,15 +51,15 @@ var BlobSchedule = Define(T[map[forks.Fork]BlobConfig]{ Validate: validateBlobSchedule, }) +// validateBlobSchedule verifies that all forks after cancun explicitly define a blob +// schedule configuration. func validateBlobSchedule(schedule map[forks.Fork]BlobConfig, cfg *Config2) error { - // Check that all forks with blobs explicitly define the blob schedule configuration. - for _, f := range forks.CanonOrder { - if f.HasBlobs() { + for f := range forks.All() { + if cfg.Scheduled(f) && f.Requires(forks.Cancun) { bcfg, defined := schedule[f] - if cfg.Scheduled(f) && !defined { + if !defined { return fmt.Errorf("invalid chain configuration: missing entry for fork %q in blobSchedule", f) - } - if defined { + } else { if err := bcfg.validate(); err != nil { return fmt.Errorf("invalid chain configuration in blobSchedule for fork %q: %v", f, err) } diff --git a/params/config2.go b/params/config2.go index 248c74fc079..4fefcad7174 100644 --- a/params/config2.go +++ b/params/config2.go @@ -18,6 +18,7 @@ package params import ( "bytes" + "cmp" "encoding/json" "fmt" "maps" @@ -97,15 +98,16 @@ func (cfg *Config2) SetParam(param ...ParamValue) *Config2 { // LatestFork returns the latest time-based fork that would be active for the given time. func (cfg *Config2) LatestFork(time uint64) forks.Fork { - londonBlock := cfg.activation[forks.London] - for _, f := range slices.Backward(forks.CanonOrder) { + var active []forks.Fork + for f := range forks.All() { if f.BlockBased() { - break + continue } - if cfg.Active(f, londonBlock, time) { - return f + if a, ok := cfg.activation[f]; ok && a <= time { + active = append(active, f) } } + return forks.Paris } @@ -124,9 +126,9 @@ func (cfg *Config2) MarshalJSON() ([]byte, error) { for f, act := range cfg.activation { var name string if f.BlockBased() { - name = fmt.Sprintf("%sBlock", strings.ToLower(name)) + name = fmt.Sprintf("%sBlock", f.ConfigName()) } else { - name = fmt.Sprintf("%sTime", strings.ToLower(name)) + name = fmt.Sprintf("%sTime", f.ConfigName()) } m[name] = act } @@ -176,12 +178,12 @@ func (cfg *Config2) decodeActivation(key string, dec *json.Decoder) error { var f forks.Fork name, ok := strings.CutSuffix(key, "Block") if ok { - f, ok = forks.ByName(name) + f, ok = forks.ForkByConfigName(name) if !ok || !f.BlockBased() { return fmt.Errorf("unknown block-based fork %q", name) } } else if name, ok = strings.CutSuffix(key, "Time"); ok { - f, ok = forks.ByName(name) + f, ok = forks.ForkByConfigName(name) if !ok || f.BlockBased() { return fmt.Errorf("unknown time-based fork %q", name) } @@ -206,36 +208,29 @@ func (cfg *Config2) decodeParameter(key string, dec *json.Decoder) error { // Validate checks the configuration to ensure forks are scheduled in order, // and required settings are present. func (cfg *Config2) Validate() error { - sanityCheckCanonOrder() - - // Check forks. - lastFork := forks.CanonOrder[0] - for _, f := range forks.CanonOrder[1:] { + for f := range forks.All() { act := "timestamp" if f.BlockBased() { act = "block" } - switch { - // Non-optional forks must all be present in the chain config up to the last defined fork. - case !cfg.Scheduled(lastFork) && cfg.Scheduled(f): - return fmt.Errorf("unsupported fork ordering: %v not enabled, but %v enabled at %s %v", lastFork, f, act, cfg.activation[f]) - - // Fork (whether defined by block or timestamp) must follow the fork definition sequence. - case cfg.Scheduled(lastFork) && cfg.Scheduled(f): - // Timestamp based forks can follow block based ones, but not the other way around. - if !lastFork.BlockBased() && f.BlockBased() { - return fmt.Errorf("unsupported fork ordering: %v used timestamp ordering, but %v reverted to block ordering", lastFork, f) - } - if lastFork.BlockBased() == f.BlockBased() && cfg.activation[lastFork] > cfg.activation[f] { - return fmt.Errorf("unsupported fork ordering: %v enabled at %s %v, but %v enabled at %s %v", lastFork, act, cfg.activation[lastFork], f, act, cfg.activation[f]) + for dep := range f.DirectDependencies() { + switch { + // Non-optional forks must all be present in the chain config up to the last defined fork. + case !cfg.Scheduled(dep) && cfg.Scheduled(f): + return fmt.Errorf("unsupported fork ordering: %v not enabled, but %v enabled at %s %v", dep, f, act, cfg.activation[f]) + + // Fork (whether defined by block or timestamp) must follow the fork definition sequence. + case cfg.Scheduled(dep) && cfg.Scheduled(f): + // Timestamp based forks can follow block based ones, but not the other way around. + if !dep.BlockBased() && f.BlockBased() { + return fmt.Errorf("unsupported fork ordering: %v used timestamp ordering, but %v reverted to block ordering", dep, f) + } + if dep.BlockBased() == f.BlockBased() && cfg.activation[dep] > cfg.activation[f] { + return fmt.Errorf("unsupported fork ordering: %v enabled at %s %v, but %v enabled at %s %v", dep, act, cfg.activation[dep], f, act, cfg.activation[f]) + } } } - - // If it was optional and not set, then ignore it. - if !f.Optional() || cfg.Scheduled(f) { - lastFork = f - } } // Check parameters. @@ -263,14 +258,35 @@ func (cfg *Config2) Validate() error { // An error is returned when the new configuration attempts to schedule a fork below the // current chain head. The error contains enough information to rewind the chain to a // point where the new config can be applied safely. -func (c *Config2) CheckCompatible(newcfg *Config2, blocknum uint64, time uint64) *ConfigCompatError { - sanityCheckCanonOrder() +func (cfg *Config2) CheckCompatible(newcfg *Config2, blocknum uint64, time uint64) *ConfigCompatError { + // Gather forks which are active in either config, and sort them by activation. + // Here we assume block-based forks activate before time-based ones. + // For forks with equal activation, the ordering is based on fork name to ensure a consistent result. + var forkList []forkActivation + for f := range forks.All() { + a, ok := minActivation(f, cfg, newcfg) + if ok { + forkList = append(forkList, forkActivation{f, a}) + } + } + slices.SortFunc(forkList, func(f1, f2 forkActivation) int { + switch { + case f1.fork.BlockBased() && !f2.fork.BlockBased(): + return -1 + case !f2.fork.BlockBased() && f2.fork.BlockBased(): + return 1 + case f1.activation == f2.activation: + return strings.Compare(f1.fork.String(), f2.fork.String()) + default: + return cmp.Compare(f1.activation, f2.activation) + } + }) // Iterate checkCompatible to find the lowest conflict. var lasterr *ConfigCompatError bhead, btime := blocknum, time for { - err := c.checkCompatible(newcfg, bhead, btime) + err := cfg.checkCompatible(newcfg, forkList, bhead, btime) if err == nil || (lasterr != nil && err.RewindToBlock == lasterr.RewindToBlock && err.RewindToTime == lasterr.RewindToTime) { break } @@ -286,26 +302,28 @@ func (c *Config2) CheckCompatible(newcfg *Config2, blocknum uint64, time uint64) } // checkCompatible checks config compatibility at a specific block height. -func (cfg *Config2) checkCompatible(newcfg *Config2, num uint64, time uint64) *ConfigCompatError { +func (cfg *Config2) checkCompatible(newcfg *Config2, forkList []forkActivation, num uint64, time uint64) *ConfigCompatError { incompatible := func(f forks.Fork) bool { return (cfg.Active(f, num, time) || newcfg.Active(f, num, time)) && !activationEqual(f, cfg, newcfg) } - - for _, f := range forks.CanonOrder[1:] { + for _, fa := range forkList { + f := fa.fork if incompatible(f) { if f.BlockBased() { - return newBlockCompatError2(fmt.Sprintf("%v fork block", f), f, cfg, newcfg) + return newBlockCompatError2(f.ConfigName()+"Block", f, cfg, newcfg) } - return newTimestampCompatError2("%v fork timestamp", f, cfg, newcfg) + return newTimestampCompatError2(f.ConfigName()+"Time", f, cfg, newcfg) } } + // Specialty checks. if cfg.Active(forks.DAO, num, time) && DAOForkSupport.Get(cfg) != DAOForkSupport.Get(newcfg) { return newBlockCompatError2("DAO fork support flag", forks.DAO, cfg, newcfg) } if cfg.Active(forks.TangerineWhistle, num, time) && !configBlockEqual(ChainID.Get(cfg), ChainID.Get(newcfg)) { return newBlockCompatError2("EIP158 chain ID", forks.TangerineWhistle, cfg, newcfg) } + // TODO: Something should be checked here for TTD and blobSchedule. return nil } @@ -365,20 +383,7 @@ func activationEqual(f forks.Fork, cfg1, cfg2 *Config2) bool { return cfg1.Scheduled(f) == cfg2.Scheduled(f) && cfg1.activation[f] == cfg2.activation[f] } -// sanityCheckCanonOrder verifies forks.CanonOrder is defined sensibly. -// This exists to ensure library code doesn't mess with this slice in an incompatible way. -func sanityCheckCanonOrder() { - if len(forks.CanonOrder) == 0 { - panic("forks.CanonOrder is empty") - } - if forks.CanonOrder[0] != forks.Frontier { - panic("forks.CanonOrder must start with Frontier") - } -} - -func cloneBig(x *big.Int) *big.Int { - if x == nil { - return nil - } - return new(big.Int).Set(x) +type forkActivation struct { + fork forks.Fork + activation uint64 } diff --git a/params/config2_test.go b/params/config2_test.go new file mode 100644 index 00000000000..ad00865a33d --- /dev/null +++ b/params/config2_test.go @@ -0,0 +1,138 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package params_test + +import ( + "math/big" + "reflect" + "testing" + "time" + + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" + "github.com/ethereum/go-ethereum/params/presets" +) + +func TestCheckCompatible2(t *testing.T) { + type test struct { + stored, new *params.Config2 + headBlock uint64 + headTimestamp uint64 + wantErr *params.ConfigCompatError + } + tests := []test{ + {stored: presets.AllEthashProtocolChanges, new: presets.AllEthashProtocolChanges, headBlock: 0, headTimestamp: 0, wantErr: nil}, + {stored: presets.AllEthashProtocolChanges, new: presets.AllEthashProtocolChanges, headBlock: 0, headTimestamp: uint64(time.Now().Unix()), wantErr: nil}, + {stored: presets.AllEthashProtocolChanges, new: presets.AllEthashProtocolChanges, headBlock: 100, wantErr: nil}, + { + // Here we check that it's OK to reschedule a time-based fork that's still in the future. + stored: params.NewConfig2(params.Activations{forks.SpuriousDragon: 10}), + new: params.NewConfig2(params.Activations{forks.SpuriousDragon: 20}), + headBlock: 9, + wantErr: nil, + }, + { + stored: presets.AllEthashProtocolChanges, + new: params.NewConfig2(params.Activations{}), + headBlock: 3, + wantErr: ¶ms.ConfigCompatError{ + What: "arrowGlacierBlock", + StoredBlock: big.NewInt(0), + NewBlock: nil, + RewindToBlock: 0, + }, + }, + { + stored: presets.AllEthashProtocolChanges, + new: params.NewConfig2(params.Activations{forks.ArrowGlacier: 1}), + headBlock: 3, + wantErr: ¶ms.ConfigCompatError{ + What: "arrowGlacierBlock", + StoredBlock: big.NewInt(0), + NewBlock: big.NewInt(1), + RewindToBlock: 0, + }, + }, + { + stored: params.NewConfig2(params.Activations{ + forks.Homestead: 30, + forks.TangerineWhistle: 10, + }), + new: params.NewConfig2(params.Activations{ + forks.Homestead: 25, + forks.TangerineWhistle: 20, + }), + headBlock: 25, + wantErr: ¶ms.ConfigCompatError{ + What: "eip150Block", + StoredBlock: big.NewInt(10), + NewBlock: big.NewInt(20), + RewindToBlock: 9, + }, + }, + { + // Special case for Petersburg, which activates with Constantinople if undefined. + stored: params.NewConfig2(params.Activations{forks.Constantinople: 30}), + new: params.NewConfig2(params.Activations{forks.Constantinople: 30, forks.Petersburg: 30}), + headBlock: 40, + wantErr: nil, + }, + { + // If Petersburg and Constantinople are scheduled to different blocks, the compatibility check is stricter. + stored: params.NewConfig2(params.Activations{forks.Constantinople: 30}), + new: params.NewConfig2(params.Activations{forks.Constantinople: 30, forks.Petersburg: 31}), + headBlock: 40, + wantErr: ¶ms.ConfigCompatError{ + What: "petersburgBlock", + StoredBlock: nil, + NewBlock: big.NewInt(31), + RewindToBlock: 30, + }, + }, + { + // This one checks that it's OK to reschedule a time-based fork that's still in the future. + stored: params.NewConfig2(params.Activations{forks.Shanghai: 10}), + new: params.NewConfig2(params.Activations{forks.Shanghai: 20}), + headTimestamp: 9, + wantErr: nil, + }, + { + // Here's an error for the config from the previous test, the chain has passed the + // fork in the stored configuration, so it cannot be rescheduled. + stored: params.NewConfig2(params.Activations{forks.Shanghai: 10}), + new: params.NewConfig2(params.Activations{forks.Shanghai: 20}), + headTimestamp: 25, + wantErr: ¶ms.ConfigCompatError{ + What: "shanghaiTime", + StoredTime: newUint64(10), + NewTime: newUint64(20), + RewindToTime: 9, + }, + }, + } + + for _, test := range tests { + err := test.stored.CheckCompatible(test.new, test.headBlock, test.headTimestamp) + if !reflect.DeepEqual(err, test.wantErr) { + t.Errorf("error mismatch:\nstored: %v\nnew: %v\nheadBlock: %v\nheadTimestamp: %v\nerr: %v\nwant: %v", test.stored, test.new, test.headBlock, test.headTimestamp, err, test.wantErr) + } + } +} + +func newUint64(i uint64) *uint64 { + return &i +} diff --git a/params/forks/forkreg.go b/params/forks/forkreg.go new file mode 100644 index 00000000000..5598e6ff64f --- /dev/null +++ b/params/forks/forkreg.go @@ -0,0 +1,164 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package forks + +import ( + "fmt" + "iter" + "maps" + "slices" + "strings" +) + +// Fork identifies a specific network upgrade (hard fork). +type Fork struct { + *forkProperties +} + +// BlockBased reports whether the fork is scheduled by block number. +// If false, it is assumed to be scheduled based on block timestamp. +func (d Fork) BlockBased() bool { + return d.blockBased +} + +// String returns the fork name. +func (d Fork) String() string { + return d.name +} + +// ConfigName returns the fork config name. +func (d Fork) ConfigName() string { + return d.configName +} + +// DirectDependencies iterates the fork's direct dependencies. +func (f Fork) DirectDependencies() iter.Seq[Fork] { + return slices.Values(f.directDeps) +} + +// Requires says whether a fork (transitively) depends on the fork given as parameter. +func (f Fork) Requires(other Fork) bool { + _, ok := f.deps[other] + return ok +} + +func (f Fork) After(other Fork) bool { + return f == other || f.Requires(other) +} + +// UnmarshalText parses a fork name. Note this uses the config name. +func (f *Fork) UnmarshalText(v []byte) error { + df, ok := ForkByConfigName(string(v)) + if !ok { + return fmt.Errorf("unknown fork %q", v) + } + *f = df + return nil +} + +// MarshalText encodes the fork config name. +func (f *Fork) MarshalText() ([]byte, error) { + return []byte(f.configName), nil +} + +type forkProperties struct { + name string + configName string + blockBased bool + directDeps []Fork + deps map[Fork]struct{} +} + +// Spec is the definition of a fork. +type Spec struct { + Name string // the canonical name + ConfigName string // the name used in genesis.json + BlockBased bool // whether scheduling is based on block number (false == scheduled by timestamp) + Requires []Fork // list of forks that must activate at or before this one +} + +var ( + registry = map[Fork]struct{}{} + registryByName = map[string]Fork{} + registryByConfigName = map[string]Fork{} +) + +// Define creates a fork definition in the registry. +// This is meant to be called at package initialization time. +func Define(ft Spec) Fork { + if ft.Name == "" { + panic("blank fork name") + } + if _, ok := registryByName[ft.Name]; ok { + panic(fmt.Sprintf("fork %q already defined", ft.Name)) + } + cname := ft.ConfigName + if cname == "" { + cname = strings.ToLower(ft.Name[:1]) + ft.Name[1:] + } + if _, ok := registryByConfigName[cname]; ok { + panic(fmt.Sprintf("fork config name %q already defined", cname)) + } + + f := Fork{ + forkProperties: &forkProperties{ + name: ft.Name, + configName: cname, + blockBased: ft.BlockBased, + directDeps: slices.Clone(ft.Requires), + deps: make(map[Fork]struct{}), + }, + } + + // Build the dependency set. + for _, dep := range ft.Requires { + if dep == f { + panic("fork depends on itself") + } + if dep.Requires(f) { + panic(fmt.Sprintf("fork dependency cycle: %v requires %v", dep, f)) + } + maps.Copy(f.deps, dep.deps) + f.deps[dep] = struct{}{} + } + + // Add to registry. + registry[f] = struct{}{} + registryByName[f.name] = f + registryByConfigName[cname] = f + return f +} + +// All iterates over defined forks in order of their names. +func All() iter.Seq[Fork] { + sorted := slices.SortedFunc(maps.Keys(registry), func(f1, f2 Fork) int { + return strings.Compare(f1.name, f2.name) + }) + return slices.Values(sorted) +} + +// ForkByName returns a fork by its canonical name. +func ForkByName(name string) (Fork, bool) { + f, ok := registryByName[name] + return f, ok +} + +// ForkByConfigName returns a fork by its configuration name. +func ForkByConfigName(name string) (Fork, bool) { + f, ok := registryByConfigName[name] + return f, ok +} diff --git a/params/forks/forkreg_test.go b/params/forks/forkreg_test.go new file mode 100644 index 00000000000..1bbe47ff662 --- /dev/null +++ b/params/forks/forkreg_test.go @@ -0,0 +1,50 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package forks + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestForkDepends(t *testing.T) { + assert.True(t, Cancun.Requires(London)) + assert.True(t, Cancun.Requires(Frontier)) + assert.False(t, London.Requires(Cancun)) +} + +func TestForkName(t *testing.T) { + assert.Equal(t, "Cancun", Cancun.String()) + assert.Equal(t, "cancun", Cancun.ConfigName()) + + f, ok := ForkByName("Cancun") + if !ok { + t.Fatal("cancun fork not found by name") + } + if f != Cancun { + t.Fatal("wrong fork found by name cancun") + } + + f, ok = ForkByConfigName("cancun") + if !ok { + t.Fatal("cancun fork not found by name") + } + if f != Cancun { + t.Fatal("wrong fork found by name cancun") + } +} diff --git a/params/forks/forks.go b/params/forks/forks.go index 8fed40f2234..96e9499fd53 100644 --- a/params/forks/forks.go +++ b/params/forks/forks.go @@ -1,4 +1,4 @@ -// Copyright 2023 The go-ethereum Authors +// Copyright 2025 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -16,136 +16,155 @@ package forks -import "fmt" - -// Fork is a numerical identifier of specific network upgrades (forks). -type Fork uint32 - -const ( - Frontier Fork = iota | blockBased - FrontierThawing Fork = iota | blockBased | optional - Homestead Fork = iota | blockBased - DAO Fork = iota | blockBased | optional - TangerineWhistle Fork = iota | blockBased // a.k.a. the EIP150 fork - SpuriousDragon Fork = iota | blockBased // a.k.a. the EIP155/EIP158 fork - Byzantium Fork = iota | blockBased - Constantinople Fork = iota | blockBased - Petersburg Fork = iota | blockBased - Istanbul Fork = iota | blockBased - MuirGlacier Fork = iota | blockBased | optional - Berlin Fork = iota | blockBased - London Fork = iota | blockBased - ArrowGlacier Fork = iota | blockBased | optional - GrayGlacier Fork = iota | blockBased | optional - Paris Fork = iota | blockBased | ismerge - Shanghai Fork = iota - Cancun Fork = iota | hasBlobs - Prague Fork = iota | hasBlobs - Osaka Fork = iota | hasBlobs - Verkle Fork = iota | optional +// Ethereum mainnet forks. +var ( + Frontier = Define(Spec{ + Name: "Frontier", + BlockBased: true, + }) + + Homestead = Define(Spec{ + Name: "Homestead", + BlockBased: true, + Requires: []Fork{Frontier}, + }) + + DAO = Define(Spec{ + Name: "DAO", + ConfigName: "daoFork", + BlockBased: true, + Requires: []Fork{Homestead}, + }) + + TangerineWhistle = Define(Spec{ + Name: "TangerineWhistle", + ConfigName: "eip150", + BlockBased: true, + Requires: []Fork{Homestead}, + }) + + SpuriousDragon = Define(Spec{ + Name: "SpuriousDragon", + ConfigName: "eip155", + BlockBased: true, + Requires: []Fork{TangerineWhistle}, + }) + + Byzantium = Define(Spec{ + Name: "Byzantium", + BlockBased: true, + Requires: []Fork{SpuriousDragon}, + }) + + Constantinople = Define(Spec{ + Name: "Constantinople", + BlockBased: true, + Requires: []Fork{Byzantium}, + }) + + Petersburg = Define(Spec{ + Name: "Petersburg", + BlockBased: true, + Requires: []Fork{Constantinople}, + }) + + Istanbul = Define(Spec{ + Name: "Istanbul", + BlockBased: true, + Requires: []Fork{Petersburg}, + }) + + MuirGlacier = Define(Spec{ + Name: "MuirGlacier", + BlockBased: true, + Requires: []Fork{Istanbul}, + }) + + Berlin = Define(Spec{ + Name: "Berlin", + BlockBased: true, + Requires: []Fork{Istanbul}, + }) + + London = Define(Spec{ + Name: "London", + BlockBased: true, + Requires: []Fork{Berlin}, + }) + + ArrowGlacier = Define(Spec{ + Name: "ArrowGlacier", + BlockBased: true, + Requires: []Fork{London, MuirGlacier}, + }) + + GrayGlacier = Define(Spec{ + Name: "GrayGlacier", + BlockBased: true, + Requires: []Fork{London, ArrowGlacier}, + }) + + Paris = Define(Spec{ + Name: "Paris", + ConfigName: "mergeNetsplit", + BlockBased: true, + Requires: []Fork{London}, + }) + + Shanghai = Define(Spec{ + Name: "Shanghai", + Requires: []Fork{Paris}, + }) + + Cancun = Define(Spec{ + Name: "Cancun", + Requires: []Fork{Shanghai}, + }) + + Prague = Define(Spec{ + Name: "Prague", + Requires: []Fork{Cancun}, + }) + + Osaka = Define(Spec{ + Name: "Osaka", + Requires: []Fork{Prague}, + }) ) -var CanonOrder = []Fork{ - Frontier, - FrontierThawing, - Homestead, - DAO, - TangerineWhistle, - SpuriousDragon, - Byzantium, - Constantinople, - Petersburg, - Istanbul, - MuirGlacier, - Berlin, - London, - ArrowGlacier, - GrayGlacier, - Paris, - Shanghai, - Cancun, - Prague, - Osaka, - Verkle, -} - -const ( - // Config bits: these bits are set on specific fork enum values and encode metadata - // about the fork. - blockBased = 1 << 31 - ismerge = 1 << 30 - optional = 1 << 29 - hasBlobs = 1 << 28 - - // The config bits can be stripped using this bit mask. - unconfigMask = ^Fork(0) >> 8 +// Verkle forks. +var ( + Verkle = Define(Spec{ + Name: "Verkle", + Requires: []Fork{Prague}, + }) ) -// IsMerge returns true for the merge fork. -func (f Fork) IsMerge() bool { - return f&ismerge != 0 -} - -// Optional reports whether the fork can be left out of the config. -func (f Fork) Optional() bool { - return f&optional != 0 -} - -// BlockBased reports whether the fork is scheduled by block number instead of timestamp. -// This is true for pre-merge forks. -func (f Fork) BlockBased() bool { - return f&blockBased != 0 -} - -// HasBlobs reports whether the fork must have a corresponding blob count configuration. -func (f Fork) HasBlobs() bool { - return f&hasBlobs != 0 -} - -func (f Fork) After(other Fork) bool { - return f&unconfigMask >= other&unconfigMask -} - -// String implements fmt.Stringer. -func (f Fork) String() string { - s, ok := forkToString[f] - if !ok { - return fmt.Sprintf("unknownFork(%#x)", f) - } - return s -} - -var forkToString = map[Fork]string{ - Frontier: "Frontier", - Homestead: "Homestead", - DAO: "DAOFork", - TangerineWhistle: "TangerineWhistle", - SpuriousDragon: "SpuriousDragon", - Byzantium: "Byzantium", - Constantinople: "Constantinople", - Petersburg: "Petersburg", - Istanbul: "Istanbul", - MuirGlacier: "MuirGlacier", - Berlin: "Berlin", - London: "London", - ArrowGlacier: "ArrowGlacier", - GrayGlacier: "GrayGlacier", - Paris: "Paris", - Shanghai: "Shanghai", - Cancun: "Cancun", - Prague: "Prague", - Osaka: "Osaka", -} - -var forkFromString = make(map[string]Fork, len(forkToString)) - -func init() { - for f, name := range forkToString { - forkFromString[name] = f - } -} - -func ByName(name string) (Fork, bool) { - f, ok := forkFromString[name] - return f, ok -} +// BPOs - 'blob parameter only' forks. +var ( + BPO1 = Define(Spec{ + Name: "BPO1", + ConfigName: "bpo1", + Requires: []Fork{Osaka}, + }) + BPO2 = Define(Spec{ + Name: "BPO2", + ConfigName: "bpo2", + Requires: []Fork{BPO1}, + }) + BPO3 = Define(Spec{ + Name: "BPO3", + ConfigName: "bpo3", + Requires: []Fork{BPO2}, + }) + BPO4 = Define(Spec{ + Name: "BPO4", + ConfigName: "bpo4", + Requires: []Fork{BPO3}, + }) + BPO5 = Define(Spec{ + Name: "BPO5", + ConfigName: "bpo5", + Requires: []Fork{BPO4}, + }) +) diff --git a/params/registry.go b/params/registry.go index 17dc838a67c..c1203246fdb 100644 --- a/params/registry.go +++ b/params/registry.go @@ -18,13 +18,20 @@ package params import ( "fmt" + "strings" ) -type Parameter[V any] struct{ +// Parameter represents a chain parameter. +// Parameters are globally registered using `Define`. +type Parameter[V any] struct { info regInfo } +// Get retrieves the value of a parameter from a config. func (p Parameter[V]) Get(cfg *Config2) V { + if p.info.id == 0 { + panic("zero parameter") + } v, ok := cfg.param[p.info.id] if ok { return v.(V) @@ -32,51 +39,66 @@ func (p Parameter[V]) Get(cfg *Config2) V { return p.info.defaultValue.(V) } +// V creates a ParamValue with the given value. You need this to +// specify parameter values when constructing a Config in code. func (p Parameter[V]) V(v V) ParamValue { + if p.info.id == 0 { + panic("zero parameter") + } return ParamValue{p.info.id, v} } -type ParamValue struct{ - id int +// ParamValue contains a chain parameter and its value. +// This is created by calling `V` on the parameter. +type ParamValue struct { + id int value any } var ( - paramCounter int - paramRegistry = map[int]regInfo{} + paramCounter = int(1) + paramRegistry = map[int]regInfo{} paramRegistryByName = map[string]int{} ) -type T[V any] struct{ - Name string - Optional bool - Default V +// T is the definition of a chain parameter type. +type T[V any] struct { + Name string // the parameter name + Optional bool // optional says + Default V Validate func(v V, cfg *Config2) error } -type regInfo struct{ - id int - name string - optional bool +type regInfo struct { + id int + name string + optional bool defaultValue any - new func() any - validate func(any, *Config2) error + new func() any + validate func(any, *Config2) error } // Define creates a chain parameter in the registry. +// This is meant to be called at package initialization time. func Define[V any](def T[V]) Parameter[V] { + if def.Name == "" { + panic("blank parameter name") + } if id, defined := paramRegistryByName[def.Name]; defined { info := paramRegistry[id] panic(fmt.Sprintf("chain parameter %q already registered with type %T", def.Name, info.defaultValue)) } + if strings.HasSuffix(def.Name, "Block") || strings.HasSuffix(def.Name, "Time") { + panic("chain parameter name cannot end in 'Block' or 'Time'") + } id := paramCounter paramCounter++ regInfo := regInfo{ - id: id, - name: def.Name, - optional: def.Optional, + id: id, + name: def.Name, + optional: def.Optional, defaultValue: def.Default, new: func() any { var z V From 57c395f59a38d37e308818ba7eab62f961bb1e2c Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 24 Jul 2025 01:06:29 +0200 Subject: [PATCH 23/32] core/types: fix parameter access --- core/types/transaction_signing.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index c37a0f7e1ff..b062d35e4e8 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -41,7 +41,7 @@ type sigCache struct { // MakeSigner returns a Signer based on the given chain config and block number. func MakeSigner(config *params.Config2, blockNumber *big.Int, blockTime uint64) Signer { number := blockNumber.Uint64() - chainID := params.Get[*params.ChainID](config).BigInt() + chainID := params.ChainID.Get(config) var signer Signer switch { @@ -71,7 +71,7 @@ func MakeSigner(config *params.Config2, blockNumber *big.Int, blockTime uint64) // Use this in transaction-handling code where the current block number is unknown. If you // have the current block number available, use MakeSigner instead. func LatestSigner(config *params.Config2) Signer { - chainID := params.Get[*params.ChainID](config).BigInt() + chainID := params.ChainID.Get(config) var signer Signer if chainID != nil { @@ -212,7 +212,6 @@ func newModernSigner(chainID *big.Int, fork forks.Fork) Signer { } s.txtypes[LegacyTxType] = struct{}{} // configure tx types - // TODO: fix this if fork.After(forks.Berlin) { s.txtypes[AccessListTxType] = struct{}{} } From 13d14f5ab8ebc89881fdf294f280b410d35027b6 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 24 Jul 2025 11:46:13 +0200 Subject: [PATCH 24/32] params: add validation tests and remove LatestFork --- params/config2.go | 91 ++++++++++++++------- params/config2_test.go | 31 +++++++ params/testdata/invalid-forkorder.json | 7 ++ params/testdata/invalid-missingChainID.json | 8 ++ 4 files changed, 107 insertions(+), 30 deletions(-) create mode 100644 params/testdata/invalid-forkorder.json create mode 100644 params/testdata/invalid-missingChainID.json diff --git a/params/config2.go b/params/config2.go index 4fefcad7174..f53438d032d 100644 --- a/params/config2.go +++ b/params/config2.go @@ -24,6 +24,7 @@ import ( "maps" "math/big" "slices" + "strconv" "strings" "github.com/ethereum/go-ethereum/params/forks" @@ -43,7 +44,6 @@ func NewConfig2(activations Activations, param ...ParamValue) *Config2 { activation: maps.Clone(activations), param: make(map[int]any, len(param)), } - cfg.activation[forks.Frontier] = 0 for _, pv := range param { cfg.param[pv.id] = pv.value } @@ -52,6 +52,9 @@ func NewConfig2(activations Activations, param ...ParamValue) *Config2 { // Active reports whether the given fork is active for a block number/time. func (cfg *Config2) Active(f forks.Fork, block, timestamp uint64) bool { + if f == forks.Frontier { + return true + } activation, ok := cfg.activation[f] if f.BlockBased() { return ok && block >= activation @@ -61,6 +64,9 @@ func (cfg *Config2) Active(f forks.Fork, block, timestamp uint64) bool { // ActiveAtBlock reports whether the given fork is active for a block number/time. func (cfg *Config2) ActiveAtBlock(f forks.Fork, block *big.Int) bool { + if f == forks.Frontier { + return true + } if !f.BlockBased() { panic(fmt.Sprintf("fork %v has time-based scheduling", f)) } @@ -96,19 +102,38 @@ func (cfg *Config2) SetParam(param ...ParamValue) *Config2 { return cpy } -// LatestFork returns the latest time-based fork that would be active for the given time. -func (cfg *Config2) LatestFork(time uint64) forks.Fork { - var active []forks.Fork - for f := range forks.All() { - if f.BlockBased() { - continue - } - if a, ok := cfg.activation[f]; ok && a <= time { - active = append(active, f) - } +// String encodes the config in a readable way. +func (cfg *Config2) String() string { + paramList := slices.Sorted(maps.Keys(cfg.param)) + forkList := make([]forkActivation, 0, len(cfg.activation)) + for f, a := range cfg.activation { + forkList = append(forkList, forkActivation{f, a}) } + slices.SortFunc(forkList, forkActivation.compare) - return forks.Paris + var out strings.Builder + var sp bool + writeSp := func() { + if sp { + out.WriteString(" ") + } + sp = true + } + out.WriteRune('[') + for _, fa := range forkList { + writeSp() + out.WriteString(fa.fork.ConfigName()) + out.WriteString(":") + out.WriteString(strconv.FormatUint(fa.activation, 10)) + } + for _, p := range paramList { + writeSp() + out.WriteString(paramRegistry[p].name) + out.WriteString(":") + fmt.Fprintf(&out, "%v", cfg.param[p]) + } + out.WriteRune(']') + return out.String() } // MarshalJSON encodes the config as JSON. @@ -146,9 +171,7 @@ func (cfg *Config2) UnmarshalJSON(input []byte) error { return fmt.Errorf("expected JSON object for chain configuration") } // Now we're in the object. - newcfg := Config2{ - activation: make(Activations), - } + newcfg := Config2{activation: Activations{}} for { tok, err = dec.Token() if tok == json.Delim('}') { @@ -182,6 +205,9 @@ func (cfg *Config2) decodeActivation(key string, dec *json.Decoder) error { if !ok || !f.BlockBased() { return fmt.Errorf("unknown block-based fork %q", name) } + if f == forks.Frontier { + return fmt.Errorf("frontier fork cannot be scheduled") + } } else if name, ok = strings.CutSuffix(key, "Time"); ok { f, ok = forks.ForkByConfigName(name) if !ok || f.BlockBased() { @@ -215,6 +241,9 @@ func (cfg *Config2) Validate() error { } for dep := range f.DirectDependencies() { + if dep == forks.Frontier { + continue + } switch { // Non-optional forks must all be present in the chain config up to the last defined fork. case !cfg.Scheduled(dep) && cfg.Scheduled(f): @@ -238,7 +267,7 @@ func (cfg *Config2) Validate() error { v, isSet := cfg.param[id] if !isSet { if !info.optional { - return fmt.Errorf("required chain parameter %s is not set", info.name) + return fmt.Errorf("required chain parameter %q is not set", info.name) } v = info.defaultValue } @@ -260,8 +289,6 @@ func (cfg *Config2) Validate() error { // point where the new config can be applied safely. func (cfg *Config2) CheckCompatible(newcfg *Config2, blocknum uint64, time uint64) *ConfigCompatError { // Gather forks which are active in either config, and sort them by activation. - // Here we assume block-based forks activate before time-based ones. - // For forks with equal activation, the ordering is based on fork name to ensure a consistent result. var forkList []forkActivation for f := range forks.All() { a, ok := minActivation(f, cfg, newcfg) @@ -269,18 +296,7 @@ func (cfg *Config2) CheckCompatible(newcfg *Config2, blocknum uint64, time uint6 forkList = append(forkList, forkActivation{f, a}) } } - slices.SortFunc(forkList, func(f1, f2 forkActivation) int { - switch { - case f1.fork.BlockBased() && !f2.fork.BlockBased(): - return -1 - case !f2.fork.BlockBased() && f2.fork.BlockBased(): - return 1 - case f1.activation == f2.activation: - return strings.Compare(f1.fork.String(), f2.fork.String()) - default: - return cmp.Compare(f1.activation, f2.activation) - } - }) + slices.SortFunc(forkList, forkActivation.compare) // Iterate checkCompatible to find the lowest conflict. var lasterr *ConfigCompatError @@ -387,3 +403,18 @@ type forkActivation struct { fork forks.Fork activation uint64 } + +func (fa forkActivation) compare(other forkActivation) int { + // Here we assume block-based forks activate before time-based ones. For forks with + // equal activation, the ordering is based on fork name to ensure a consistent result. + switch { + case fa.fork.BlockBased() && !other.fork.BlockBased(): + return -1 + case !fa.fork.BlockBased() && other.fork.BlockBased(): + return 1 + case fa.activation == other.activation: + return strings.Compare(fa.fork.String(), other.fork.String()) + default: + return cmp.Compare(fa.activation, other.activation) + } +} diff --git a/params/config2_test.go b/params/config2_test.go index ad00865a33d..55a47000c03 100644 --- a/params/config2_test.go +++ b/params/config2_test.go @@ -18,15 +18,46 @@ package params_test import ( "math/big" + "path/filepath" "reflect" "testing" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params/forks" "github.com/ethereum/go-ethereum/params/presets" ) +func TestConfigValidateErrors(t *testing.T) { + files, err := filepath.Glob("testdata/invalid-*.json") + if err != nil { + t.Fatal(err) + } + + type test struct { + Config params.Config2 `json:"config"` + Error string `json:"error"` + } + + for _, f := range files { + name := filepath.Base(f) + t.Run(name, func(t *testing.T) { + var test test + if err := common.LoadJSON(f, &test); err != nil { + t.Fatal(err) + } + err := test.Config.Validate() + if err == nil { + t.Fatal("expected validation error, got none") + } + if err.Error() != test.Error { + t.Fatal("wrong error:\n got:", err.Error(), "want:", test.Error) + } + }) + } +} + func TestCheckCompatible2(t *testing.T) { type test struct { stored, new *params.Config2 diff --git a/params/testdata/invalid-forkorder.json b/params/testdata/invalid-forkorder.json new file mode 100644 index 00000000000..8f2a5d46833 --- /dev/null +++ b/params/testdata/invalid-forkorder.json @@ -0,0 +1,7 @@ +{ + "config": { + "homesteadBlock": 10, + "eip150Block": 8 + }, + "error": "unsupported fork ordering: Homestead enabled at block 10, but TangerineWhistle enabled at block 8" +} diff --git a/params/testdata/invalid-missingChainID.json b/params/testdata/invalid-missingChainID.json new file mode 100644 index 00000000000..46343adf78d --- /dev/null +++ b/params/testdata/invalid-missingChainID.json @@ -0,0 +1,8 @@ +{ + "config": { + "homesteadBlock": 7, + "eip150Block": 8, + "eip155Block": 9 + }, + "error": "required chain parameter \"chainId\" is not set" +} From cf8af42646ee3d2476772d77f31089782d2445ec Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 24 Jul 2025 12:30:40 +0200 Subject: [PATCH 25/32] params: add DependencyOrder function --- params/forks/forkreg.go | 37 ++++++++++++++++++++++++++++++++++++ params/forks/forkreg_test.go | 27 ++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/params/forks/forkreg.go b/params/forks/forkreg.go index 5598e6ff64f..d5b140e046d 100644 --- a/params/forks/forkreg.go +++ b/params/forks/forkreg.go @@ -75,6 +75,43 @@ func (f *Fork) MarshalText() ([]byte, error) { return []byte(f.configName), nil } +// DependencyOrder computes an ordering of the given forks, according to their dependencies. +// Note the resulting ordering may not be unique! +func DependencyOrder(list []Fork) []Fork { + var ( + inList = make(map[Fork]bool, len(list)) + visiting = make(map[Fork]bool, len(list)) + mark = make(map[Fork]bool, len(list)) + result = make([]Fork, 0, len(list)) + ) + for _, f := range list { + inList[f] = true + } + + var visit func(Fork) + visit = func(f Fork) { + if mark[f] { + return + } + if visiting[f] { + // This can't happen because we check for cycles at definition time. + panic("fork dependency cycle") + } + visiting[f] = true + for _, dep := range f.directDeps { + visit(dep) + } + mark[f] = true + if inList[f] { + result = append(result, f) + } + } + for _, l := range list { + visit(l) + } + return result +} + type forkProperties struct { name string configName string diff --git a/params/forks/forkreg_test.go b/params/forks/forkreg_test.go index 1bbe47ff662..850296e299d 100644 --- a/params/forks/forkreg_test.go +++ b/params/forks/forkreg_test.go @@ -17,6 +17,7 @@ package forks import ( + "slices" "testing" "github.com/stretchr/testify/assert" @@ -48,3 +49,29 @@ func TestForkName(t *testing.T) { t.Fatal("wrong fork found by name cancun") } } + +func TestForkDependencyOrder(t *testing.T) { + tests := []struct { + list, result []Fork + }{ + { + list: []Fork{}, + result: []Fork{}, + }, + { + list: []Fork{BPO2, Homestead, Cancun, London, Paris}, + result: []Fork{Homestead, London, Paris, Cancun, BPO2}, + }, + { + list: []Fork{BPO3, Osaka, Cancun, Prague, BPO1, BPO2}, + result: []Fork{Cancun, Prague, Osaka, BPO1, BPO2, BPO3}, + }, + } + + for _, test := range tests { + res := DependencyOrder(test.list) + if !slices.Equal(res, test.result) { + t.Errorf("DependencyOrder(%v) -> %v\n want: %v", test.list, res, test.result) + } + } +} From 336a2dab566af5bc123819227beff28b2ddea646 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 24 Jul 2025 12:51:20 +0200 Subject: [PATCH 26/32] params: fix param value decoding --- params/config2.go | 8 ++++++-- params/registry.go | 3 +-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/params/config2.go b/params/config2.go index f53438d032d..bf38396ecf2 100644 --- a/params/config2.go +++ b/params/config2.go @@ -23,6 +23,7 @@ import ( "fmt" "maps" "math/big" + "reflect" "slices" "strconv" "strings" @@ -171,7 +172,10 @@ func (cfg *Config2) UnmarshalJSON(input []byte) error { return fmt.Errorf("expected JSON object for chain configuration") } // Now we're in the object. - newcfg := Config2{activation: Activations{}} + newcfg := Config2{ + activation: make(Activations), + param: make(map[int]any), + } for { tok, err = dec.Token() if tok == json.Delim('}') { @@ -227,7 +231,7 @@ func (cfg *Config2) decodeParameter(key string, dec *json.Decoder) error { if err := dec.Decode(v); err != nil { return err } - cfg.param[id] = v + cfg.param[id] = reflect.ValueOf(v).Elem().Interface() return nil } diff --git a/params/registry.go b/params/registry.go index c1203246fdb..074a198c998 100644 --- a/params/registry.go +++ b/params/registry.go @@ -101,8 +101,7 @@ func Define[V any](def T[V]) Parameter[V] { optional: def.Optional, defaultValue: def.Default, new: func() any { - var z V - return z + return new(V) }, validate: func(v any, cfg *Config2) error { if def.Validate == nil { From 7b5c1696e52cd69dce3a5abacd0c3dea596013d0 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 24 Jul 2025 12:57:32 +0200 Subject: [PATCH 27/32] params: add tests for blobschedule validation --- params/chainparam.go | 9 ++++--- .../invalid-blobschedule-blockbased.json | 24 +++++++++++++++++ .../invalid-blobschedule-missing.json | 26 +++++++++++++++++++ .../invalid-blobschedule-validation.json | 24 +++++++++++++++++ 4 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 params/testdata/invalid-blobschedule-blockbased.json create mode 100644 params/testdata/invalid-blobschedule-missing.json create mode 100644 params/testdata/invalid-blobschedule-validation.json diff --git a/params/chainparam.go b/params/chainparam.go index b11390a7f4b..c0b72c8df68 100644 --- a/params/chainparam.go +++ b/params/chainparam.go @@ -55,13 +55,16 @@ var BlobSchedule = Define(T[map[forks.Fork]BlobConfig]{ // schedule configuration. func validateBlobSchedule(schedule map[forks.Fork]BlobConfig, cfg *Config2) error { for f := range forks.All() { - if cfg.Scheduled(f) && f.Requires(forks.Cancun) { + if _, defined := schedule[f]; f.BlockBased() && defined { + return fmt.Errorf("contains fork %q with block-number based scheduling", f.ConfigName()) + } + if cfg.Scheduled(f) && f.After(forks.Cancun) { bcfg, defined := schedule[f] if !defined { - return fmt.Errorf("invalid chain configuration: missing entry for fork %q in blobSchedule", f) + return fmt.Errorf("missing entry for fork %q", f.ConfigName()) } else { if err := bcfg.validate(); err != nil { - return fmt.Errorf("invalid chain configuration in blobSchedule for fork %q: %v", f, err) + return fmt.Errorf("invalid blob config for fork %q: %v", f.ConfigName(), err) } } } diff --git a/params/testdata/invalid-blobschedule-blockbased.json b/params/testdata/invalid-blobschedule-blockbased.json new file mode 100644 index 00000000000..72641f2d05a --- /dev/null +++ b/params/testdata/invalid-blobschedule-blockbased.json @@ -0,0 +1,24 @@ +{ + "config": { + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "mergeNetsplitBlock": 0, + "shanghaiTime": 900, + "chainId": 99, + "blobSchedule": { + "berlin": { + "target": 8, + "max": 5, + "baseFeeUpdateFraction": 3338477 + } + } + }, + "error": "invalid blobSchedule: contains fork \"berlin\" with block-number based scheduling" +} diff --git a/params/testdata/invalid-blobschedule-missing.json b/params/testdata/invalid-blobschedule-missing.json new file mode 100644 index 00000000000..19d8bc1298f --- /dev/null +++ b/params/testdata/invalid-blobschedule-missing.json @@ -0,0 +1,26 @@ +{ + "config": { + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "mergeNetsplitBlock": 0, + "shanghaiTime": 900, + "cancunTime": 1000, + "pragueTime": 1100, + "chainId": 99, + "blobSchedule": { + "cancun": { + "target": 8, + "max": 5, + "baseFeeUpdateFraction": 3338477 + } + } + }, + "error": "invalid blobSchedule: missing entry for fork \"prague\"" +} diff --git a/params/testdata/invalid-blobschedule-validation.json b/params/testdata/invalid-blobschedule-validation.json new file mode 100644 index 00000000000..8db36e56ba6 --- /dev/null +++ b/params/testdata/invalid-blobschedule-validation.json @@ -0,0 +1,24 @@ +{ + "config": { + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "mergeNetsplitBlock": 0, + "shanghaiTime": 900, + "cancunTime": 1000, + "chainId": 99, + "blobSchedule": { + "cancun": { + "target": 8, + "max": 10 + } + } + }, + "error": "invalid blobSchedule: invalid blob config for fork \"cancun\": update fraction must be defined and non-zero" +} From 11ac2cc80fa3536b48cc99c4d1c3a74e790a7ff2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 24 Jul 2025 23:27:00 +0200 Subject: [PATCH 28/32] consensus/misc/eip4844: make it work It's not great to call scheduleAtTime so many times. The function is much more expensive now since it has to establish the fork order every time. A later refactoring needs to turn it around to compute the order only once. --- consensus/misc/eip4844/eip4844.go | 60 +++++++++++++++++--------- consensus/misc/eip4844/eip4844_test.go | 4 +- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/consensus/misc/eip4844/eip4844.go b/consensus/misc/eip4844/eip4844.go index e24db3ab04f..49ee498fdef 100644 --- a/consensus/misc/eip4844/eip4844.go +++ b/consensus/misc/eip4844/eip4844.go @@ -19,8 +19,8 @@ package eip4844 import ( "errors" "fmt" + "math" "math/big" - "slices" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -38,6 +38,7 @@ func VerifyEIP4844Header(config *params.Config2, parent, header *types.Header) e if header.Number.Uint64() != parent.Number.Uint64()+1 { panic("bad header pair") } + // Verify the header is not malformed if header.ExcessBlobGas == nil { return errors.New("header is missing excessBlobGas") @@ -45,8 +46,14 @@ func VerifyEIP4844Header(config *params.Config2, parent, header *types.Header) e if header.BlobGasUsed == nil { return errors.New("header is missing blobGasUsed") } + + blobcfg := scheduleAtTime(config, header.Time) + if blobcfg == nil { + return fmt.Errorf("blob schedule is undefined at time %d", header.Time) + } + // Verify that the blob gas used remains within reasonable limits. - maxBlobGas := MaxBlobGasPerBlock(config, header.Time) + maxBlobGas := uint64(blobcfg.Max) * params.BlobTxBlobGasPerBlob if *header.BlobGasUsed > maxBlobGas { return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, maxBlobGas) } @@ -101,18 +108,21 @@ func CalcExcessBlobGas(config *params.Config2, parent *types.Header, headTimesta // CalcBlobFee calculates the blobfee from the header's excess blob gas field. func CalcBlobFee(config *params.Config2, header *types.Header) *big.Int { - schedule := params.BlobSchedule.Get(config) - if schedule == nil { + blobcfg := scheduleAtTime(config, header.Time) + if blobcfg == nil { return new(big.Int) } - f := config.LatestFork(header.Time) - frac := schedule[f].UpdateFraction + frac := blobcfg.UpdateFraction return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(*header.ExcessBlobGas), new(big.Int).SetUint64(frac)) } // MaxBlobsPerBlock returns the max blobs per block for a block at the given timestamp. func MaxBlobsPerBlock(cfg *params.Config2, time uint64) int { - return scheduleAtTime(cfg, time).Max + blobcfg := scheduleAtTime(cfg, time) + if blobcfg == nil { + return 0 + } + return blobcfg.Max } // MaxBlobsPerBlock returns the maximum blob gas that can be spent in a block at the given timestamp. @@ -123,16 +133,8 @@ func MaxBlobGasPerBlock(cfg *params.Config2, time uint64) uint64 { // LatestMaxBlobsPerBlock returns the latest max blobs per block defined by the // configuration, regardless of the currently active fork. func LatestMaxBlobsPerBlock(cfg *params.Config2) int { - schedule := params.BlobSchedule.Get(cfg) - if schedule == nil { - return 0 - } - for _, f := range slices.Backward(forks.CanonOrder) { - if f.HasBlobs() && cfg.Scheduled(f) { - return schedule[f].Max - } - } - return 0 + blobcfg := scheduleAtTime(cfg, math.MaxUint64) + return blobcfg.Max } // targetBlobsPerBlock returns the target number of blobs in a block at the given timestamp. @@ -140,13 +142,29 @@ func targetBlobsPerBlock(cfg *params.Config2, time uint64) int { return scheduleAtTime(cfg, time).Target } -func scheduleAtTime(cfg *params.Config2, time uint64) params.BlobConfig { +// scheduleAtTime resolves the blob schedule at the given timestamp. +func scheduleAtTime(cfg *params.Config2, time uint64) *params.BlobConfig { schedule := params.BlobSchedule.Get(cfg) if schedule == nil { - return params.BlobConfig{} + return nil + } + + // Find the latest fork defined by the schedule. + forkList := make([]forks.Fork, 0, len(schedule)) + for f := range schedule { + act, ok := cfg.Activation(f) + if ok && act <= time { + forkList = append(forkList, f) + } + } + forkList = forks.DependencyOrder(forkList) + + // Return the blob config of the last available fork. + if len(forkList) == 0 { + return nil } - f := cfg.LatestFork(time) - return schedule[f] + blobcfg := schedule[forkList[len(forkList)-1]] + return &blobcfg } // fakeExponential approximates factor * e ** (numerator / denominator) using diff --git a/consensus/misc/eip4844/eip4844_test.go b/consensus/misc/eip4844/eip4844_test.go index e018b86955e..73a9f2ca254 100644 --- a/consensus/misc/eip4844/eip4844_test.go +++ b/consensus/misc/eip4844/eip4844_test.go @@ -88,9 +88,9 @@ func TestCalcBlobFee(t *testing.T) { forks.London: 0, forks.Cancun: 0, }, - params.BlobSchedule{ + params.BlobSchedule.V(map[forks.Fork]params.BlobConfig{ forks.Cancun: *params.DefaultCancunBlobConfig, - }, + }), ) header := &types.Header{ExcessBlobGas: &tt.excessBlobGas} have := CalcBlobFee(config, header) From ee78ae41722875ea3bd55977e3f0a6b763937327 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 6 Aug 2025 23:01:51 +0200 Subject: [PATCH 29/32] params: add Defined --- params/registry.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/params/registry.go b/params/registry.go index 074a198c998..790122aac57 100644 --- a/params/registry.go +++ b/params/registry.go @@ -39,6 +39,15 @@ func (p Parameter[V]) Get(cfg *Config2) V { return p.info.defaultValue.(V) } +// Defined reports whether the parameter is set in the config. +func (p Parameter[V]) Defined(cfg *Config2) bool { + if p.info.id == 0 { + panic("zero parameter") + } + _, ok := cfg.param[p.info.id] + return ok +} + // V creates a ParamValue with the given value. You need this to // specify parameter values when constructing a Config in code. func (p Parameter[V]) V(v V) ParamValue { From 0101ffeee9ddaedc76ba751dd51bd83bd2cc82c2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 6 Aug 2025 23:41:04 +0200 Subject: [PATCH 30/32] core/vm: port to config2 --- core/vm/contracts.go | 19 ++++++++-------- core/vm/eips.go | 2 +- core/vm/evm.go | 44 ++++++++++++++++++++---------------- core/vm/gas_table.go | 21 +++++++++-------- core/vm/gas_table_test.go | 6 ++--- core/vm/instructions.go | 5 ++-- core/vm/instructions_test.go | 26 ++++++++++----------- core/vm/interpreter.go | 31 +++++++++++++------------ core/vm/interpreter_test.go | 5 ++-- 9 files changed, 85 insertions(+), 74 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 21307ff5ace..47cacfc5233 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -37,6 +37,7 @@ import ( "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/secp256r1" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" "golang.org/x/crypto/ripemd160" ) @@ -206,21 +207,21 @@ func init() { } } -func activePrecompiledContracts(rules params.Rules) PrecompiledContracts { +func activePrecompiledContracts(rules params.Rules2) PrecompiledContracts { switch { - case rules.IsVerkle: + case rules.Active(forks.Verkle): return PrecompiledContractsVerkle - case rules.IsOsaka: + case rules.Active(forks.Osaka): return PrecompiledContractsOsaka - case rules.IsPrague: + case rules.Active(forks.Prague): return PrecompiledContractsPrague - case rules.IsCancun: + case rules.Active(forks.Cancun): return PrecompiledContractsCancun - case rules.IsBerlin: + case rules.Active(forks.Berlin): return PrecompiledContractsBerlin - case rules.IsIstanbul: + case rules.Active(forks.Istanbul): return PrecompiledContractsIstanbul - case rules.IsByzantium: + case rules.Active(forks.Byzantium): return PrecompiledContractsByzantium default: return PrecompiledContractsHomestead @@ -228,7 +229,7 @@ func activePrecompiledContracts(rules params.Rules) PrecompiledContracts { } // ActivePrecompiledContracts returns a copy of precompiled contracts enabled with the current configuration. -func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts { +func ActivePrecompiledContracts(rules params.Rules2) PrecompiledContracts { return maps.Clone(activePrecompiledContracts(rules)) } diff --git a/core/vm/eips.go b/core/vm/eips.go index 7764bd20b62..f993a19022d 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -109,7 +109,7 @@ func enable1344(jt *JumpTable) { // opChainID implements CHAINID opcode func opChainID(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - chainId, _ := uint256.FromBig(interpreter.evm.chainConfig.ChainID) + chainId, _ := uint256.FromBig(params.ChainID.Get(interpreter.evm.chainConfig)) scope.Stack.push(chainId) return nil, nil } diff --git a/core/vm/evm.go b/core/vm/evm.go index 143b7e08a22..0a552ea4522 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" "github.com/holiman/uint256" ) @@ -99,10 +100,10 @@ type EVM struct { depth int // chainConfig contains information about the current chain - chainConfig *params.ChainConfig + chainConfig *params.Config2 // chain rules contains the chain rules for the current epoch - chainRules params.Rules + chainRules params.Rules2 // virtual machine configuration options used to initialise the evm Config Config @@ -130,13 +131,18 @@ type EVM struct { // database and several configs. It meant to be used throughout the entire // state transition of a block, with the transaction context switched as // needed by calling evm.SetTxContext. -func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainConfig, config Config) *EVM { +func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.Config2, config Config) *EVM { + blocknum := uint64(0) + if blockCtx.BlockNumber != nil { + blocknum = blockCtx.BlockNumber.Uint64() + } + evm := &EVM{ Context: blockCtx, StateDB: statedb, Config: config, chainConfig: chainConfig, - chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), + chainRules: chainConfig.Rules(blocknum, blockCtx.Time), jumpDests: newMapJumpDests(), } evm.precompiles = activePrecompiledContracts(evm.chainRules) @@ -159,7 +165,7 @@ func (evm *EVM) SetJumpDestCache(jumpDests JumpDestCache) { // SetTxContext resets the EVM with a new transaction context. // This is not threadsafe and should only be done very cautiously. func (evm *EVM) SetTxContext(txCtx TxContext) { - if evm.chainRules.IsEIP4762 { + if evm.chainRules.Active(forks.Verkle) { txCtx.AccessEvents = state.NewAccessEvents(evm.StateDB.PointCache()) } evm.TxContext = txCtx @@ -209,7 +215,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g p, isPrecompile := evm.precompile(addr) if !evm.StateDB.Exist(addr) { - if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) { + if !isPrecompile && evm.chainRules.Active(forks.Verkle) && !isSystemCall(caller) { // Add proof of absence to witness // At this point, the read costs have already been charged, either because this // is a direct tx call, in which case it's covered by the intrinsic gas, or because @@ -225,7 +231,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g gas -= wgas } - if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() { + if !isPrecompile && evm.chainRules.Active(forks.SpuriousDragon) && value.IsZero() { // Calling a non-existing account, don't do anything. return nil, gas, nil } @@ -442,7 +448,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator) // Charge the contract creation init gas in verkle mode - if evm.chainRules.IsEIP4762 { + if evm.chainRules.Active(forks.Verkle) { statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas) if statelessGas > gas { return nil, common.Address{}, 0, ErrOutOfGas @@ -455,7 +461,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui // We add this to the access list _before_ taking a snapshot. Even if the // creation fails, the access-list change should not be rolled back. - if evm.chainRules.IsEIP2929 { + if evm.chainRules.Active(forks.Berlin) && !evm.chainRules.Active(forks.Verkle) { evm.StateDB.AddAddressToAccessList(address) } // Ensure there's no existing contract already at the designated address. @@ -486,11 +492,11 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui // acts inside that account. evm.StateDB.CreateContract(address) - if evm.chainRules.IsEIP158 { + if evm.chainRules.Active(forks.SpuriousDragon) { evm.StateDB.SetNonce(address, 1, tracing.NonceChangeNewContract) } // Charge the contract creation init gas in verkle mode - if evm.chainRules.IsEIP4762 { + if evm.chainRules.Active(forks.Verkle) { consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas) if consumed < wanted { return nil, common.Address{}, 0, ErrOutOfGas @@ -512,7 +518,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui contract.IsDeployment = true ret, err = evm.initNewContract(contract, address) - if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { + if err != nil && (evm.chainRules.Active(forks.Homestead) || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { contract.UseGas(contract.Gas, evm.Config.Tracer, tracing.GasChangeCallFailedExecution) @@ -530,16 +536,16 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b } // Check whether the max code size has been exceeded, assign err if the case. - if evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { + if evm.chainRules.Active(forks.SpuriousDragon) && len(ret) > params.MaxCodeSize { return ret, ErrMaxCodeSizeExceeded } // Reject code starting with 0xEF if EIP-3541 is enabled. - if len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon { + if len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.Active(forks.London) { return ret, ErrInvalidCode } - if !evm.chainRules.IsEIP4762 { + if !evm.chainRules.Active(forks.Verkle) { createDataGas := uint64(len(ret)) * params.CreateDataGas if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { return ret, ErrCodeStoreOutOfGas @@ -576,7 +582,7 @@ func (evm *EVM) Create2(caller common.Address, code []byte, gas uint64, endowmen // Prague, it can also resolve code pointed to by a delegation designator. func (evm *EVM) resolveCode(addr common.Address) []byte { code := evm.StateDB.GetCode(addr) - if !evm.chainRules.IsPrague { + if !evm.chainRules.Active(forks.Prague) { return code } if target, ok := types.ParseDelegation(code); ok { @@ -591,7 +597,7 @@ func (evm *EVM) resolveCode(addr common.Address) []byte { // delegation designator. Although this is not accessible in the EVM it is used // internally to associate jumpdest analysis to code. func (evm *EVM) resolveCodeHash(addr common.Address) common.Hash { - if evm.chainRules.IsPrague { + if evm.chainRules.Active(forks.Prague) { code := evm.StateDB.GetCode(addr) if target, ok := types.ParseDelegation(code); ok { // Note we only follow one level of delegation. @@ -602,7 +608,7 @@ func (evm *EVM) resolveCodeHash(addr common.Address) common.Hash { } // ChainConfig returns the environment's chain configuration -func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } +func (evm *EVM) ChainConfig() *params.Config2 { return evm.chainConfig } func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to common.Address, input []byte, startGas uint64, value *big.Int) { tracer := evm.Config.Tracer @@ -623,7 +629,7 @@ func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret [ if err != nil { reverted = true } - if !evm.chainRules.IsHomestead && errors.Is(err, ErrCodeStoreOutOfGas) { + if !evm.chainRules.Active(forks.Homestead) && errors.Is(err, ErrCodeStoreOutOfGas) { reverted = false } if tracer.OnExit != nil { diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index c7c1274bf2f..46ed104b5c5 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" ) // memoryGasCost calculates the quadratic gas for memory expansion. It does so @@ -104,7 +105,7 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi // The legacy gas metering only takes into consideration the current state // Legacy rules should be applied if we are in Petersburg (removal of EIP-1283) // OR Constantinople is not active - if evm.chainRules.IsPetersburg || !evm.chainRules.IsConstantinople { + if evm.chainRules.Active(forks.Petersburg) || !evm.chainRules.Active(forks.Constantinople) { // This checks for 3 scenarios and calculates gas accordingly: // // 1. From a zero-value address to a non-zero value (NEW VALUE) @@ -374,14 +375,14 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize transfersValue = !stack.Back(2).IsZero() address = common.Address(stack.Back(1).Bytes20()) ) - if evm.chainRules.IsEIP158 { + if evm.chainRules.Active(forks.SpuriousDragon) { if transfersValue && evm.StateDB.Empty(address) { gas += params.CallNewAccountGas } } else if !evm.StateDB.Exist(address) { gas += params.CallNewAccountGas } - if transfersValue && !evm.chainRules.IsEIP4762 { + if transfersValue && !evm.chainRules.Active(forks.Verkle) { gas += params.CallValueTransferGas } memoryGas, err := memoryGasCost(mem, memorySize) @@ -393,7 +394,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize return 0, ErrGasUintOverflow } - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + evm.callGasTemp, err = callGas(evm.chainRules.Active(forks.TangerineWhistle), contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } @@ -413,13 +414,13 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory gas uint64 overflow bool ) - if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 { + if stack.Back(2).Sign() != 0 && !evm.chainRules.Active(forks.Verkle) { gas += params.CallValueTransferGas } if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { return 0, ErrGasUintOverflow } - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + evm.callGasTemp, err = callGas(evm.chainRules.Active(forks.TangerineWhistle), contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } @@ -434,7 +435,7 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me if err != nil { return 0, err } - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + evm.callGasTemp, err = callGas(evm.chainRules.Active(forks.TangerineWhistle), contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } @@ -450,7 +451,7 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo if err != nil { return 0, err } - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + evm.callGasTemp, err = callGas(evm.chainRules.Active(forks.TangerineWhistle), contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } @@ -464,11 +465,11 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var gas uint64 // EIP150 homestead gas reprice fork: - if evm.chainRules.IsEIP150 { + if evm.chainRules.Active(forks.TangerineWhistle) { gas = params.SelfdestructGasEIP150 var address = common.Address(stack.Back(0).Bytes20()) - if evm.chainRules.IsEIP158 { + if evm.chainRules.Active(forks.SpuriousDragon) { // if empty and transfers value if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { gas += params.CreateBySelfdestructGas diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go index cb6143c0b56..4c7476ae42c 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -28,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/presets" "github.com/holiman/uint256" ) @@ -95,7 +95,7 @@ func TestEIP2200(t *testing.T) { CanTransfer: func(StateDB, common.Address, *uint256.Int) bool { return true }, Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {}, } - evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) + evm := NewEVM(vmctx, statedb, presets.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) _, gas, err := evm.Call(common.Address{}, address, nil, tt.gaspool, new(uint256.Int)) if !errors.Is(err, tt.failure) { @@ -151,7 +151,7 @@ func TestCreateGas(t *testing.T) { config.ExtraEips = []int{3860} } - evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, config) + evm := NewEVM(vmctx, statedb, presets.AllEthashProtocolChanges, config) var startGas = uint64(testGas) ret, gas, err := evm.Call(common.Address{}, address, nil, startGas, new(uint256.Int)) if err != nil { diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 63bb6d2d51c..a1c89f85cdb 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" "github.com/holiman/uint256" ) @@ -663,7 +664,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64()) gas = scope.Contract.Gas ) - if interpreter.evm.chainRules.IsEIP150 { + if interpreter.evm.chainRules.Active(forks.TangerineWhistle) { gas -= gas / 64 } @@ -677,7 +678,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b // homestead we must check for CodeStoreOutOfGasError (homestead only // rule) and treat as an error, if the ruleset is frontier we must // ignore this error and pretend the operation was successful. - if interpreter.evm.chainRules.IsHomestead && suberr == ErrCodeStoreOutOfGas { + if interpreter.evm.chainRules.Active(forks.Homestead) && suberr == ErrCodeStoreOutOfGas { stackvalue.Clear() } else if suberr != nil && suberr != ErrCodeStoreOutOfGas { stackvalue.Clear() diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 8a82de5d8b1..5aaf360c820 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -30,7 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/presets" "github.com/holiman/uint256" ) @@ -96,7 +96,7 @@ func init() { func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFunc, name string) { var ( - evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) + evm = NewEVM(BlockContext{}, nil, presets.TestChainConfig, Config{}) stack = newstack() pc = uint64(0) ) @@ -194,7 +194,7 @@ func TestSAR(t *testing.T) { func TestAddMod(t *testing.T) { var ( - evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) + evm = NewEVM(BlockContext{}, nil, presets.TestChainConfig, Config{}) stack = newstack() pc = uint64(0) ) @@ -237,7 +237,7 @@ func TestWriteExpectedValues(t *testing.T) { // getResult is a convenience function to generate the expected values getResult := func(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase { var ( - evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) + evm = NewEVM(BlockContext{}, nil, presets.TestChainConfig, Config{}) stack = newstack() pc = uint64(0) ) @@ -281,7 +281,7 @@ func TestJsonTestcases(t *testing.T) { func opBenchmark(bench *testing.B, op executionFunc, args ...string) { var ( - evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) + evm = NewEVM(BlockContext{}, nil, presets.TestChainConfig, Config{}) stack = newstack() scope = &ScopeContext{nil, stack, nil} ) @@ -519,7 +519,7 @@ func BenchmarkOpIsZero(b *testing.B) { func TestOpMstore(t *testing.T) { var ( - evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) + evm = NewEVM(BlockContext{}, nil, presets.TestChainConfig, Config{}) stack = newstack() mem = NewMemory() ) @@ -542,7 +542,7 @@ func TestOpMstore(t *testing.T) { func BenchmarkOpMstore(bench *testing.B) { var ( - evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) + evm = NewEVM(BlockContext{}, nil, presets.TestChainConfig, Config{}) stack = newstack() mem = NewMemory() ) @@ -562,7 +562,7 @@ func BenchmarkOpMstore(bench *testing.B) { func TestOpTstore(t *testing.T) { var ( statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) - evm = NewEVM(BlockContext{}, statedb, params.TestChainConfig, Config{}) + evm = NewEVM(BlockContext{}, statedb, presets.TestChainConfig, Config{}) stack = newstack() mem = NewMemory() caller = common.Address{} @@ -601,7 +601,7 @@ func TestOpTstore(t *testing.T) { func BenchmarkOpKeccak256(bench *testing.B) { var ( - evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) + evm = NewEVM(BlockContext{}, nil, presets.TestChainConfig, Config{}) stack = newstack() mem = NewMemory() ) @@ -703,7 +703,7 @@ func TestRandom(t *testing.T) { {name: "hash(0x010203)", random: crypto.Keccak256Hash([]byte{0x01, 0x02, 0x03})}, } { var ( - evm = NewEVM(BlockContext{Random: &tt.random}, nil, params.TestChainConfig, Config{}) + evm = NewEVM(BlockContext{Random: &tt.random}, nil, presets.TestChainConfig, Config{}) stack = newstack() pc = uint64(0) ) @@ -743,7 +743,7 @@ func TestBlobHash(t *testing.T) { {name: "out-of-bounds (nil)", idx: 25, expect: zero, hashes: nil}, } { var ( - evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) + evm = NewEVM(BlockContext{}, nil, presets.TestChainConfig, Config{}) stack = newstack() pc = uint64(0) ) @@ -846,7 +846,7 @@ func TestOpMCopy(t *testing.T) { }, } { var ( - evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) + evm = NewEVM(BlockContext{}, nil, presets.TestChainConfig, Config{}) stack = newstack() pc = uint64(0) ) @@ -974,7 +974,7 @@ func TestPush(t *testing.T) { } func TestOpCLZ(t *testing.T) { - evm := NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) + evm := NewEVM(BlockContext{}, nil, presets.TestChainConfig, Config{}) tests := []struct { inputHex string diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 34d19008da7..907d9677afa 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params/forks" "github.com/holiman/uint256" ) @@ -106,34 +107,34 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { // If jump table was not initialised we set the default one. var table *JumpTable switch { - case evm.chainRules.IsOsaka: + case evm.chainRules.Active(forks.Osaka): table = &osakaInstructionSet - case evm.chainRules.IsVerkle: + case evm.chainRules.Active(forks.Verkle): // TODO replace with proper instruction set when fork is specified table = &verkleInstructionSet - case evm.chainRules.IsPrague: + case evm.chainRules.Active(forks.Prague): table = &pragueInstructionSet - case evm.chainRules.IsCancun: + case evm.chainRules.Active(forks.Cancun): table = &cancunInstructionSet - case evm.chainRules.IsShanghai: + case evm.chainRules.Active(forks.Shanghai): table = &shanghaiInstructionSet - case evm.chainRules.IsMerge: + case evm.chainRules.Active(forks.Paris): table = &mergeInstructionSet - case evm.chainRules.IsLondon: + case evm.chainRules.Active(forks.London): table = &londonInstructionSet - case evm.chainRules.IsBerlin: + case evm.chainRules.Active(forks.Berlin): table = &berlinInstructionSet - case evm.chainRules.IsIstanbul: + case evm.chainRules.Active(forks.Istanbul): table = &istanbulInstructionSet - case evm.chainRules.IsConstantinople: + case evm.chainRules.Active(forks.Constantinople): table = &constantinopleInstructionSet - case evm.chainRules.IsByzantium: + case evm.chainRules.Active(forks.Byzantium): table = &byzantiumInstructionSet - case evm.chainRules.IsEIP158: + case evm.chainRules.Active(forks.SpuriousDragon): table = &spuriousDragonInstructionSet - case evm.chainRules.IsEIP150: + case evm.chainRules.Active(forks.TangerineWhistle): table = &tangerineWhistleInstructionSet - case evm.chainRules.IsHomestead: + case evm.chainRules.Active(forks.Homestead): table = &homesteadInstructionSet default: table = &frontierInstructionSet @@ -237,7 +238,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( logged, pcCopy, gasCopy = false, pc, contract.Gas } - if in.evm.chainRules.IsEIP4762 && !contract.IsDeployment && !contract.IsSystemCall { + if in.evm.chainRules.Active(forks.Verkle) && !contract.IsDeployment && !contract.IsSystemCall { // if the PC ends up in a new "chunk" of verkleized code, charge the // associated costs. contractAddr := contract.Address() diff --git a/core/vm/interpreter_test.go b/core/vm/interpreter_test.go index 8ed512316bc..953001d8675 100644 --- a/core/vm/interpreter_test.go +++ b/core/vm/interpreter_test.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/presets" "github.com/holiman/uint256" ) @@ -48,7 +49,7 @@ func TestLoopInterrupt(t *testing.T) { statedb.SetCode(address, common.Hex2Bytes(tt)) statedb.Finalise(true) - evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{}) + evm := NewEVM(vmctx, statedb, presets.AllEthashProtocolChanges, Config{}) errChannel := make(chan error) timeout := make(chan bool) @@ -79,7 +80,7 @@ func TestLoopInterrupt(t *testing.T) { func BenchmarkInterpreter(b *testing.B) { var ( statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) - evm = NewEVM(BlockContext{BlockNumber: big.NewInt(1), Time: 1, Random: &common.Hash{}}, statedb, params.MergedTestChainConfig, Config{}) + evm = NewEVM(BlockContext{BlockNumber: big.NewInt(1), Time: 1, Random: &common.Hash{}}, statedb, presets.MergedTestChainConfig, Config{}) startGas uint64 = 100_000_000 value = uint256.NewInt(0) stack = newstack() From 1a63295458b5c9a5be40008187752a950c28bde2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 6 Aug 2025 23:41:19 +0200 Subject: [PATCH 31/32] params: add Rules2 (WIP) --- params/rules2.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 params/rules2.go diff --git a/params/rules2.go b/params/rules2.go new file mode 100644 index 00000000000..a76cc092210 --- /dev/null +++ b/params/rules2.go @@ -0,0 +1,38 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package params + +import ( + "github.com/ethereum/go-ethereum/params/forks" +) + +// Rules captures the activations of forks at a particular block height. +type Rules2 struct { + active map[forks.Fork]bool +} + +func (cfg *Config2) Rules(blockNum uint64, blockTime uint64) Rules2 { + r := Rules2{ + active: make(map[forks.Fork]bool, len(cfg.activation)), + } + return r +} + +// Active reports whether the given fork is active. +func (r *Rules2) Active(f forks.Fork) bool { + return r.active[f] +} From 215787789bc502ae84c712fc96cc0f7488fe8289 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 6 Aug 2025 23:42:57 +0200 Subject: [PATCH 32/32] consensus/clique: partial port to config2 --- consensus/clique/clique.go | 64 +++++++++++++++++-------------- consensus/clique/clique_test.go | 14 ++++--- consensus/clique/config.go | 31 +++++++++++++++ consensus/clique/snapshot.go | 9 ++--- consensus/clique/snapshot_test.go | 2 +- 5 files changed, 79 insertions(+), 41 deletions(-) create mode 100644 consensus/clique/config.go diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index b593d2117d2..3fd4c3c634e 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -40,6 +40,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" "golang.org/x/crypto/sha3" @@ -51,20 +52,25 @@ const ( inmemorySignatures = 4096 // Number of recent block signatures to keep in memory ) +const ( + ExtraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity + ExtraSeal = crypto.SignatureLength // Fixed number of extra-data suffix bytes reserved for signer seal + + DiffInTurn = 2 // Block difficulty for in-turn signatures + DiffNoTurn = 1 // Block difficulty for out-of-turn signatures +) + // Clique proof-of-authority protocol constants. var ( epochLength = uint64(30000) // Default number of blocks after which to checkpoint and reset the pending votes - extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity - extraSeal = crypto.SignatureLength // Fixed number of extra-data suffix bytes reserved for signer seal - nonceAuthVote = hexutil.MustDecode("0xffffffffffffffff") // Magic nonce number to vote on adding a new signer nonceDropVote = hexutil.MustDecode("0x0000000000000000") // Magic nonce number to vote on removing a signer. uncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW. - diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures - diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures + diffInTurn = big.NewInt(DiffInTurn) + diffNoTurn = big.NewInt(DiffNoTurn) ) // Various error messages to mark blocks invalid. These should be private to @@ -145,10 +151,10 @@ func ecrecover(header *types.Header, sigcache *sigLRU) (common.Address, error) { return address, nil } // Retrieve the signature from the header extra-data - if len(header.Extra) < extraSeal { + if len(header.Extra) < ExtraSeal { return common.Address{}, errMissingSignature } - signature := header.Extra[len(header.Extra)-extraSeal:] + signature := header.Extra[len(header.Extra)-ExtraSeal:] // Recover the public key and the Ethereum address pubkey, err := crypto.Ecrecover(SealHash(header).Bytes(), signature) @@ -165,8 +171,8 @@ func ecrecover(header *types.Header, sigcache *sigLRU) (common.Address, error) { // Clique is the proof-of-authority consensus engine proposed to support the // Ethereum testnet following the Ropsten attacks. type Clique struct { - config *params.CliqueConfig // Consensus engine configuration parameters - db ethdb.Database // Database to store and retrieve snapshot checkpoints + config *Config // Consensus engine configuration parameters + db ethdb.Database // Database to store and retrieve snapshot checkpoints recents *lru.Cache[common.Hash, *Snapshot] // Snapshots for recent block to speed up reorgs signatures *sigLRU // Signatures of recent blocks to speed up mining @@ -182,18 +188,17 @@ type Clique struct { // New creates a Clique proof-of-authority consensus engine with the initial // signers set to the ones provided by the user. -func New(config *params.CliqueConfig, db ethdb.Database) *Clique { +func New(config Config, db ethdb.Database) *Clique { // Set any missing consensus parameters to their defaults - conf := *config - if conf.Epoch == 0 { - conf.Epoch = epochLength + if config.Epoch == 0 { + config.Epoch = epochLength } // Allocate the snapshot caches and create the engine recents := lru.NewCache[common.Hash, *Snapshot](inmemorySnapshots) signatures := lru.NewCache[common.Hash, common.Address](inmemorySignatures) return &Clique{ - config: &conf, + config: &config, db: db, recents: recents, signatures: signatures, @@ -260,14 +265,14 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H return errInvalidCheckpointVote } // Check that the extra-data contains both the vanity and signature - if len(header.Extra) < extraVanity { + if len(header.Extra) < ExtraVanity { return errMissingVanity } - if len(header.Extra) < extraVanity+extraSeal { + if len(header.Extra) < ExtraVanity+ExtraSeal { return errMissingSignature } // Ensure that the extra-data contains a signer list on checkpoint, but none otherwise - signersBytes := len(header.Extra) - extraVanity - extraSeal + signersBytes := len(header.Extra) - ExtraVanity - ExtraSeal if !checkpoint && signersBytes != 0 { return errExtraSigners } @@ -292,14 +297,14 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H if header.GasLimit > params.MaxGasLimit { return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit) } - if chain.Config().IsShanghai(header.Number, header.Time) { + if chain.Config().Active(forks.Shanghai, header.Number.Uint64(), header.Time) { return errors.New("clique does not support shanghai fork") } // Verify the non-existence of withdrawalsHash. if header.WithdrawalsHash != nil { return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash) } - if chain.Config().IsCancun(header.Number, header.Time) { + if chain.Config().Active(forks.Cancun, header.Number.Uint64(), header.Time) { return errors.New("clique does not support cancun fork") } // Verify the non-existence of cancun-specific header fields @@ -342,7 +347,7 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header if header.GasUsed > header.GasLimit { return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) } - if !chain.Config().IsLondon(header.Number) { + if !chain.Config().Active(forks.London, header.Number.Uint64(), header.Time) { // Verify BaseFee not present before EIP-1559 fork. if header.BaseFee != nil { return fmt.Errorf("invalid baseFee before fork: have %d, want ", header.BaseFee) @@ -365,8 +370,8 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header for i, signer := range snap.signers() { copy(signers[i*common.AddressLength:], signer[:]) } - extraSuffix := len(header.Extra) - extraSeal - if !bytes.Equal(header.Extra[extraVanity:extraSuffix], signers) { + extraSuffix := len(header.Extra) - ExtraSeal + if !bytes.Equal(header.Extra[ExtraVanity:extraSuffix], signers) { return errMismatchingCheckpointSigners } } @@ -404,9 +409,9 @@ func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash if checkpoint != nil { hash := checkpoint.Hash() - signers := make([]common.Address, (len(checkpoint.Extra)-extraVanity-extraSeal)/common.AddressLength) + signers := make([]common.Address, (len(checkpoint.Extra)-ExtraVanity-ExtraSeal)/common.AddressLength) for i := 0; i < len(signers); i++ { - copy(signers[i][:], checkpoint.Extra[extraVanity+i*common.AddressLength:]) + copy(signers[i][:], checkpoint.Extra[ExtraVanity+i*common.AddressLength:]) } snap = newSnapshot(c.config, c.signatures, number, hash, signers) if err := snap.store(c.db); err != nil { @@ -544,17 +549,17 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header header.Difficulty = calcDifficulty(snap, signer) // Ensure the extra data has all its components - if len(header.Extra) < extraVanity { - header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...) + if len(header.Extra) < ExtraVanity { + header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, ExtraVanity-len(header.Extra))...) } - header.Extra = header.Extra[:extraVanity] + header.Extra = header.Extra[:ExtraVanity] if number%c.config.Epoch == 0 { for _, signer := range snap.signers() { header.Extra = append(header.Extra, signer[:]...) } } - header.Extra = append(header.Extra, make([]byte, extraSeal)...) + header.Extra = append(header.Extra, make([]byte, ExtraSeal)...) // Mix digest is reserved for now, set to empty header.MixDigest = common.Hash{} @@ -587,7 +592,8 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header * c.Finalize(chain, header, state, body) // Assign the final state root to header. - header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + deleteEmptyObjects := chain.Config().Active(forks.SpuriousDragon, header.Number.Uint64(), header.Time) + header.Root = state.IntermediateRoot(deleteEmptyObjects) // Assemble and return the final block for sealing. return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil)), nil diff --git a/consensus/clique/clique_test.go b/consensus/clique/clique_test.go index afcab1d1f76..2bb57bb8705 100644 --- a/consensus/clique/clique_test.go +++ b/consensus/clique/clique_test.go @@ -14,13 +14,14 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package clique +package clique_test import ( "math/big" "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" @@ -40,18 +41,19 @@ func TestReimportMirroredState(t *testing.T) { db = rawdb.NewMemoryDatabase() key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr = crypto.PubkeyToAddress(key.PublicKey) - engine = New(params.AllCliqueProtocolChanges.Clique, db) + config = clique.ConfigParam.Get(presets.AllCliqueProtocolChanges) + engine = clique.New(config, db) signer = new(types.HomesteadSigner) ) genspec := &core.Genesis{ Config: params.AllCliqueProtocolChanges, - ExtraData: make([]byte, extraVanity+common.AddressLength+extraSeal), + ExtraData: make([]byte, clique.ExtraVanity+common.AddressLength+clique.ExtraSeal), Alloc: map[common.Address]types.Account{ addr: {Balance: big.NewInt(10000000000000000)}, }, BaseFee: big.NewInt(params.InitialBaseFee), } - copy(genspec.ExtraData[extraVanity:], addr[:]) + copy(genspec.ExtraData[clique.ExtraVanity:], addr[:]) // Generate a batch of blocks, each properly signed chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), genspec, engine, nil) @@ -60,7 +62,7 @@ func TestReimportMirroredState(t *testing.T) { _, blocks, _ := core.GenerateChainWithGenesis(genspec, engine, 3, func(i int, block *core.BlockGen) { // The chain maker doesn't have access to a chain, so the difficulty will be // lets unset (nil). Set it here to the correct value. - block.SetDifficulty(diffInTurn) + block.SetDifficulty(big.NewInt(clique.DiffInTurn)) // We want to simulate an empty middle block, having the same state as the // first one. The last is needs a state change again to force a reorg. @@ -80,7 +82,7 @@ func TestReimportMirroredState(t *testing.T) { header.Extra = make([]byte, extraVanity+extraSeal) header.Difficulty = diffInTurn - sig, _ := crypto.Sign(SealHash(header).Bytes(), key) + sig, _ := crypto.Sign(clique.SealHash(header).Bytes(), key) copy(header.Extra[len(header.Extra)-extraSeal:], sig) blocks[i] = block.WithSeal(header) } diff --git a/consensus/clique/config.go b/consensus/clique/config.go new file mode 100644 index 00000000000..6bbbe2bb3df --- /dev/null +++ b/consensus/clique/config.go @@ -0,0 +1,31 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package clique + +import "github.com/ethereum/go-ethereum/params" + +var ConfigParam = params.Define(params.T[Config]{ + Name: "clique", + Optional: true, // optional says + Default: Config{}, +}) + +// Config is the consensus engine configs for proof-of-authority based sealing. +type Config struct { + Period uint64 `json:"period"` // Number of seconds between blocks to enforce + Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint +} diff --git a/consensus/clique/snapshot.go b/consensus/clique/snapshot.go index d0b15e9489c..9029cbde64a 100644 --- a/consensus/clique/snapshot.go +++ b/consensus/clique/snapshot.go @@ -29,7 +29,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" ) // Vote represents a single vote that an authorized signer made to modify the @@ -52,8 +51,8 @@ type sigLRU = lru.Cache[common.Hash, common.Address] // Snapshot is the state of the authorization voting at a given point in time. type Snapshot struct { - config *params.CliqueConfig // Consensus engine parameters to fine tune behavior - sigcache *sigLRU // Cache of recent block signatures to speed up ecrecover + config *Config // Consensus engine parameters to fine tune behavior + sigcache *sigLRU // Cache of recent block signatures to speed up ecrecover Number uint64 `json:"number"` // Block number where the snapshot was created Hash common.Hash `json:"hash"` // Block hash where the snapshot was created @@ -66,7 +65,7 @@ type Snapshot struct { // newSnapshot creates a new snapshot with the specified startup parameters. This // method does not initialize the set of recent signers, so only ever use if for // the genesis block. -func newSnapshot(config *params.CliqueConfig, sigcache *sigLRU, number uint64, hash common.Hash, signers []common.Address) *Snapshot { +func newSnapshot(config *Config, sigcache *sigLRU, number uint64, hash common.Hash, signers []common.Address) *Snapshot { snap := &Snapshot{ config: config, sigcache: sigcache, @@ -83,7 +82,7 @@ func newSnapshot(config *params.CliqueConfig, sigcache *sigLRU, number uint64, h } // loadSnapshot loads an existing snapshot from the database. -func loadSnapshot(config *params.CliqueConfig, sigcache *sigLRU, db ethdb.Database, hash common.Hash) (*Snapshot, error) { +func loadSnapshot(config *Config, sigcache *sigLRU, db ethdb.Database, hash common.Hash) (*Snapshot, error) { blob, err := db.Get(append(rawdb.CliqueSnapshotPrefix, hash[:]...)) if err != nil { return nil, err diff --git a/consensus/clique/snapshot_test.go b/consensus/clique/snapshot_test.go index ac2355c730c..aaecb1d98c2 100644 --- a/consensus/clique/snapshot_test.go +++ b/consensus/clique/snapshot_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package clique +package clique_test import ( "bytes"