mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 13:46:43 +00:00
params: new fork & parameter registry
This commit is contained in:
parent
c1954d280b
commit
8da6d3759b
7 changed files with 606 additions and 207 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
138
params/config2_test.go
Normal file
138
params/config2_test.go
Normal 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: ¶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
|
||||
}
|
||||
164
params/forks/forkreg.go
Normal file
164
params/forks/forkreg.go
Normal 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
|
||||
}
|
||||
50
params/forks/forkreg_test.go
Normal file
50
params/forks/forkreg_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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},
|
||||
})
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue