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.
|
// true=supports or false=opposes the fork.
|
||||||
// The default value is true.
|
// The default value is true.
|
||||||
var DAOForkSupport = Define(T[bool]{
|
var DAOForkSupport = Define(T[bool]{
|
||||||
|
Name: "daoForkSupport",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: true,
|
Default: true,
|
||||||
})
|
})
|
||||||
|
|
@ -50,15 +51,15 @@ var BlobSchedule = Define(T[map[forks.Fork]BlobConfig]{
|
||||||
Validate: validateBlobSchedule,
|
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 {
|
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.All() {
|
||||||
for _, f := range forks.CanonOrder {
|
if cfg.Scheduled(f) && f.Requires(forks.Cancun) {
|
||||||
if f.HasBlobs() {
|
|
||||||
bcfg, defined := schedule[f]
|
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)
|
return fmt.Errorf("invalid chain configuration: missing entry for fork %q in blobSchedule", f)
|
||||||
}
|
} else {
|
||||||
if defined {
|
|
||||||
if err := bcfg.validate(); err != nil {
|
if err := bcfg.validate(); err != nil {
|
||||||
return fmt.Errorf("invalid chain configuration in blobSchedule for fork %q: %v", f, err)
|
return fmt.Errorf("invalid chain configuration in blobSchedule for fork %q: %v", f, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package params
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"cmp"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"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.
|
// LatestFork returns the latest time-based fork that would be active for the given time.
|
||||||
func (cfg *Config2) LatestFork(time uint64) forks.Fork {
|
func (cfg *Config2) LatestFork(time uint64) forks.Fork {
|
||||||
londonBlock := cfg.activation[forks.London]
|
var active []forks.Fork
|
||||||
for _, f := range slices.Backward(forks.CanonOrder) {
|
for f := range forks.All() {
|
||||||
if f.BlockBased() {
|
if f.BlockBased() {
|
||||||
break
|
continue
|
||||||
}
|
}
|
||||||
if cfg.Active(f, londonBlock, time) {
|
if a, ok := cfg.activation[f]; ok && a <= time {
|
||||||
return f
|
active = append(active, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return forks.Paris
|
return forks.Paris
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,9 +126,9 @@ func (cfg *Config2) MarshalJSON() ([]byte, error) {
|
||||||
for f, act := range cfg.activation {
|
for f, act := range cfg.activation {
|
||||||
var name string
|
var name string
|
||||||
if f.BlockBased() {
|
if f.BlockBased() {
|
||||||
name = fmt.Sprintf("%sBlock", strings.ToLower(name))
|
name = fmt.Sprintf("%sBlock", f.ConfigName())
|
||||||
} else {
|
} else {
|
||||||
name = fmt.Sprintf("%sTime", strings.ToLower(name))
|
name = fmt.Sprintf("%sTime", f.ConfigName())
|
||||||
}
|
}
|
||||||
m[name] = act
|
m[name] = act
|
||||||
}
|
}
|
||||||
|
|
@ -176,12 +178,12 @@ func (cfg *Config2) decodeActivation(key string, dec *json.Decoder) error {
|
||||||
var f forks.Fork
|
var f forks.Fork
|
||||||
name, ok := strings.CutSuffix(key, "Block")
|
name, ok := strings.CutSuffix(key, "Block")
|
||||||
if ok {
|
if ok {
|
||||||
f, ok = forks.ByName(name)
|
f, ok = forks.ForkByConfigName(name)
|
||||||
if !ok || !f.BlockBased() {
|
if !ok || !f.BlockBased() {
|
||||||
return fmt.Errorf("unknown block-based fork %q", name)
|
return fmt.Errorf("unknown block-based fork %q", name)
|
||||||
}
|
}
|
||||||
} else if name, ok = strings.CutSuffix(key, "Time"); ok {
|
} else if name, ok = strings.CutSuffix(key, "Time"); ok {
|
||||||
f, ok = forks.ByName(name)
|
f, ok = forks.ForkByConfigName(name)
|
||||||
if !ok || f.BlockBased() {
|
if !ok || f.BlockBased() {
|
||||||
return fmt.Errorf("unknown time-based fork %q", name)
|
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,
|
// Validate checks the configuration to ensure forks are scheduled in order,
|
||||||
// and required settings are present.
|
// and required settings are present.
|
||||||
func (cfg *Config2) Validate() error {
|
func (cfg *Config2) Validate() error {
|
||||||
sanityCheckCanonOrder()
|
for f := range forks.All() {
|
||||||
|
|
||||||
// Check forks.
|
|
||||||
lastFork := forks.CanonOrder[0]
|
|
||||||
for _, f := range forks.CanonOrder[1:] {
|
|
||||||
act := "timestamp"
|
act := "timestamp"
|
||||||
if f.BlockBased() {
|
if f.BlockBased() {
|
||||||
act = "block"
|
act = "block"
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
for dep := range f.DirectDependencies() {
|
||||||
// Non-optional forks must all be present in the chain config up to the last defined fork.
|
switch {
|
||||||
case !cfg.Scheduled(lastFork) && cfg.Scheduled(f):
|
// Non-optional forks must all be present in the chain config up to the last defined fork.
|
||||||
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.
|
// 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.
|
// Timestamp based forks can follow block based ones, but not the other way around.
|
||||||
if !lastFork.BlockBased() && f.BlockBased() {
|
if !dep.BlockBased() && f.BlockBased() {
|
||||||
return fmt.Errorf("unsupported fork ordering: %v used timestamp ordering, but %v reverted to block ordering", lastFork, f)
|
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
|
// 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
|
// current chain head. The error contains enough information to rewind the chain to a
|
||||||
// point where the new config can be applied safely.
|
// point where the new config can be applied safely.
|
||||||
func (c *Config2) CheckCompatible(newcfg *Config2, blocknum uint64, time uint64) *ConfigCompatError {
|
func (cfg *Config2) CheckCompatible(newcfg *Config2, blocknum uint64, time uint64) *ConfigCompatError {
|
||||||
sanityCheckCanonOrder()
|
// 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.
|
// Iterate checkCompatible to find the lowest conflict.
|
||||||
var lasterr *ConfigCompatError
|
var lasterr *ConfigCompatError
|
||||||
bhead, btime := blocknum, time
|
bhead, btime := blocknum, time
|
||||||
for {
|
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) {
|
if err == nil || (lasterr != nil && err.RewindToBlock == lasterr.RewindToBlock && err.RewindToTime == lasterr.RewindToTime) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -286,26 +302,28 @@ func (c *Config2) CheckCompatible(newcfg *Config2, blocknum uint64, time uint64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkCompatible checks config compatibility at a specific block height.
|
// 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 {
|
incompatible := func(f forks.Fork) bool {
|
||||||
return (cfg.Active(f, num, time) || newcfg.Active(f, num, time)) && !activationEqual(f, cfg, newcfg)
|
return (cfg.Active(f, num, time) || newcfg.Active(f, num, time)) && !activationEqual(f, cfg, newcfg)
|
||||||
}
|
}
|
||||||
|
for _, fa := range forkList {
|
||||||
for _, f := range forks.CanonOrder[1:] {
|
f := fa.fork
|
||||||
if incompatible(f) {
|
if incompatible(f) {
|
||||||
if f.BlockBased() {
|
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) {
|
if cfg.Active(forks.DAO, num, time) && DAOForkSupport.Get(cfg) != DAOForkSupport.Get(newcfg) {
|
||||||
return newBlockCompatError2("DAO fork support flag", forks.DAO, cfg, 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)) {
|
if cfg.Active(forks.TangerineWhistle, num, time) && !configBlockEqual(ChainID.Get(cfg), ChainID.Get(newcfg)) {
|
||||||
return newBlockCompatError2("EIP158 chain ID", forks.TangerineWhistle, cfg, newcfg)
|
return newBlockCompatError2("EIP158 chain ID", forks.TangerineWhistle, cfg, newcfg)
|
||||||
}
|
}
|
||||||
|
// TODO: Something should be checked here for TTD and blobSchedule.
|
||||||
|
|
||||||
return nil
|
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]
|
return cfg1.Scheduled(f) == cfg2.Scheduled(f) && cfg1.activation[f] == cfg2.activation[f]
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanityCheckCanonOrder verifies forks.CanonOrder is defined sensibly.
|
type forkActivation struct {
|
||||||
// This exists to ensure library code doesn't mess with this slice in an incompatible way.
|
fork forks.Fork
|
||||||
func sanityCheckCanonOrder() {
|
activation uint64
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -16,136 +16,155 @@
|
||||||
|
|
||||||
package forks
|
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).
|
Homestead = Define(Spec{
|
||||||
type Fork uint32
|
Name: "Homestead",
|
||||||
|
BlockBased: true,
|
||||||
|
Requires: []Fork{Frontier},
|
||||||
|
})
|
||||||
|
|
||||||
const (
|
DAO = Define(Spec{
|
||||||
Frontier Fork = iota | blockBased
|
Name: "DAO",
|
||||||
FrontierThawing Fork = iota | blockBased | optional
|
ConfigName: "daoFork",
|
||||||
Homestead Fork = iota | blockBased
|
BlockBased: true,
|
||||||
DAO Fork = iota | blockBased | optional
|
Requires: []Fork{Homestead},
|
||||||
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
|
TangerineWhistle = Define(Spec{
|
||||||
Constantinople Fork = iota | blockBased
|
Name: "TangerineWhistle",
|
||||||
Petersburg Fork = iota | blockBased
|
ConfigName: "eip150",
|
||||||
Istanbul Fork = iota | blockBased
|
BlockBased: true,
|
||||||
MuirGlacier Fork = iota | blockBased | optional
|
Requires: []Fork{Homestead},
|
||||||
Berlin Fork = iota | blockBased
|
})
|
||||||
London Fork = iota | blockBased
|
|
||||||
ArrowGlacier Fork = iota | blockBased | optional
|
SpuriousDragon = Define(Spec{
|
||||||
GrayGlacier Fork = iota | blockBased | optional
|
Name: "SpuriousDragon",
|
||||||
Paris Fork = iota | blockBased | ismerge
|
ConfigName: "eip155",
|
||||||
Shanghai Fork = iota
|
BlockBased: true,
|
||||||
Cancun Fork = iota | hasBlobs
|
Requires: []Fork{TangerineWhistle},
|
||||||
Prague Fork = iota | hasBlobs
|
})
|
||||||
Osaka Fork = iota | hasBlobs
|
|
||||||
Verkle Fork = iota | optional
|
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{
|
// Verkle forks.
|
||||||
Frontier,
|
var (
|
||||||
FrontierThawing,
|
Verkle = Define(Spec{
|
||||||
Homestead,
|
Name: "Verkle",
|
||||||
DAO,
|
Requires: []Fork{Prague},
|
||||||
TangerineWhistle,
|
})
|
||||||
SpuriousDragon,
|
|
||||||
Byzantium,
|
|
||||||
Constantinople,
|
|
||||||
Petersburg,
|
|
||||||
Istanbul,
|
|
||||||
MuirGlacier,
|
|
||||||
Berlin,
|
|
||||||
London,
|
|
||||||
ArrowGlacier,
|
|
||||||
GrayGlacier,
|
|
||||||
Paris,
|
|
||||||
Shanghai,
|
|
||||||
Cancun,
|
|
||||||
Prague,
|
|
||||||
Osaka,
|
|
||||||
Verkle,
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Config bits: these bits are set on specific fork enum values and encode metadata
|
|
||||||
// about the fork.
|
|
||||||
blockBased = 1 << 31
|
|
||||||
ismerge = 1 << 30
|
|
||||||
optional = 1 << 29
|
|
||||||
hasBlobs = 1 << 28
|
|
||||||
|
|
||||||
// The config bits can be stripped using this bit mask.
|
|
||||||
unconfigMask = ^Fork(0) >> 8
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsMerge returns true for the merge fork.
|
// BPOs - 'blob parameter only' forks.
|
||||||
func (f Fork) IsMerge() bool {
|
var (
|
||||||
return f&ismerge != 0
|
BPO1 = Define(Spec{
|
||||||
}
|
Name: "BPO1",
|
||||||
|
ConfigName: "bpo1",
|
||||||
// Optional reports whether the fork can be left out of the config.
|
Requires: []Fork{Osaka},
|
||||||
func (f Fork) Optional() bool {
|
})
|
||||||
return f&optional != 0
|
BPO2 = Define(Spec{
|
||||||
}
|
Name: "BPO2",
|
||||||
|
ConfigName: "bpo2",
|
||||||
// BlockBased reports whether the fork is scheduled by block number instead of timestamp.
|
Requires: []Fork{BPO1},
|
||||||
// This is true for pre-merge forks.
|
})
|
||||||
func (f Fork) BlockBased() bool {
|
BPO3 = Define(Spec{
|
||||||
return f&blockBased != 0
|
Name: "BPO3",
|
||||||
}
|
ConfigName: "bpo3",
|
||||||
|
Requires: []Fork{BPO2},
|
||||||
// HasBlobs reports whether the fork must have a corresponding blob count configuration.
|
})
|
||||||
func (f Fork) HasBlobs() bool {
|
BPO4 = Define(Spec{
|
||||||
return f&hasBlobs != 0
|
Name: "BPO4",
|
||||||
}
|
ConfigName: "bpo4",
|
||||||
|
Requires: []Fork{BPO3},
|
||||||
func (f Fork) After(other Fork) bool {
|
})
|
||||||
return f&unconfigMask >= other&unconfigMask
|
BPO5 = Define(Spec{
|
||||||
}
|
Name: "BPO5",
|
||||||
|
ConfigName: "bpo5",
|
||||||
// String implements fmt.Stringer.
|
Requires: []Fork{BPO4},
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,20 @@ package params
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"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
|
info regInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get retrieves the value of a parameter from a config.
|
||||||
func (p Parameter[V]) Get(cfg *Config2) V {
|
func (p Parameter[V]) Get(cfg *Config2) V {
|
||||||
|
if p.info.id == 0 {
|
||||||
|
panic("zero parameter")
|
||||||
|
}
|
||||||
v, ok := cfg.param[p.info.id]
|
v, ok := cfg.param[p.info.id]
|
||||||
if ok {
|
if ok {
|
||||||
return v.(V)
|
return v.(V)
|
||||||
|
|
@ -32,51 +39,66 @@ func (p Parameter[V]) Get(cfg *Config2) V {
|
||||||
return p.info.defaultValue.(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 {
|
func (p Parameter[V]) V(v V) ParamValue {
|
||||||
|
if p.info.id == 0 {
|
||||||
|
panic("zero parameter")
|
||||||
|
}
|
||||||
return ParamValue{p.info.id, v}
|
return ParamValue{p.info.id, v}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ParamValue struct{
|
// ParamValue contains a chain parameter and its value.
|
||||||
id int
|
// This is created by calling `V` on the parameter.
|
||||||
|
type ParamValue struct {
|
||||||
|
id int
|
||||||
value any
|
value any
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
paramCounter int
|
paramCounter = int(1)
|
||||||
paramRegistry = map[int]regInfo{}
|
paramRegistry = map[int]regInfo{}
|
||||||
paramRegistryByName = map[string]int{}
|
paramRegistryByName = map[string]int{}
|
||||||
)
|
)
|
||||||
|
|
||||||
type T[V any] struct{
|
// T is the definition of a chain parameter type.
|
||||||
Name string
|
type T[V any] struct {
|
||||||
Optional bool
|
Name string // the parameter name
|
||||||
Default V
|
Optional bool // optional says
|
||||||
|
Default V
|
||||||
Validate func(v V, cfg *Config2) error
|
Validate func(v V, cfg *Config2) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type regInfo struct{
|
type regInfo struct {
|
||||||
id int
|
id int
|
||||||
name string
|
name string
|
||||||
optional bool
|
optional bool
|
||||||
defaultValue any
|
defaultValue any
|
||||||
new func() any
|
new func() any
|
||||||
validate func(any, *Config2) error
|
validate func(any, *Config2) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define creates a chain parameter in the registry.
|
// 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] {
|
func Define[V any](def T[V]) Parameter[V] {
|
||||||
|
if def.Name == "" {
|
||||||
|
panic("blank parameter name")
|
||||||
|
}
|
||||||
if id, defined := paramRegistryByName[def.Name]; defined {
|
if id, defined := paramRegistryByName[def.Name]; defined {
|
||||||
info := paramRegistry[id]
|
info := paramRegistry[id]
|
||||||
panic(fmt.Sprintf("chain parameter %q already registered with type %T", def.Name, info.defaultValue))
|
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
|
id := paramCounter
|
||||||
paramCounter++
|
paramCounter++
|
||||||
|
|
||||||
regInfo := regInfo{
|
regInfo := regInfo{
|
||||||
id: id,
|
id: id,
|
||||||
name: def.Name,
|
name: def.Name,
|
||||||
optional: def.Optional,
|
optional: def.Optional,
|
||||||
defaultValue: def.Default,
|
defaultValue: def.Default,
|
||||||
new: func() any {
|
new: func() any {
|
||||||
var z V
|
var z V
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue