params: new fork & parameter registry

This commit is contained in:
Felix Lange 2025-07-24 01:06:11 +02:00
parent c1954d280b
commit 8da6d3759b
7 changed files with 606 additions and 207 deletions

View file

@ -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)
}

View file

@ -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"
}
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(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])
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):
case cfg.Scheduled(dep) && 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 !dep.BlockBased() && f.BlockBased() {
return fmt.Errorf("unsupported fork ordering: %v used timestamp ordering, but %v reverted to block ordering", dep, 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 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
}
}
@ -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
}

138
params/config2_test.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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: &params.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: &params.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: &params.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: &params.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: &params.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
}

164
params/forks/forkreg.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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")
}
}

View file

@ -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},
})
)

View file

@ -18,13 +18,20 @@ package params
import (
"fmt"
"strings"
)
// 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,24 +39,32 @@ 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}
}
// 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
paramCounter = int(1)
paramRegistry = map[int]regInfo{}
paramRegistryByName = map[string]int{}
)
// T is the definition of a chain parameter type.
type T[V any] struct {
Name string
Optional bool
Name string // the parameter name
Optional bool // optional says
Default V
Validate func(v V, cfg *Config2) error
}
@ -64,11 +79,18 @@ type regInfo struct{
}
// 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++