diff --git a/params/chainparam.go b/params/chainparam.go index 9e20c03348..b11390a7f4 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 248c74fc07..4fefcad717 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,35 +208,28 @@ 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]) + 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(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) + // 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 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 } } @@ -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 0000000000..ad00865a33 --- /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 0000000000..5598e6ff64 --- /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 0000000000..1bbe47ff66 --- /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 8fed40f223..96e9499fd5 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" +// Ethereum mainnet forks. +var ( + Frontier = Define(Spec{ + Name: "Frontier", + BlockBased: true, + }) -// Fork is a numerical identifier of specific network upgrades (forks). -type Fork uint32 + Homestead = Define(Spec{ + Name: "Homestead", + BlockBased: true, + Requires: []Fork{Frontier}, + }) -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 + 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 17dc838a67..c1203246fd 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