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