diff --git a/params/chainparam.go b/params/chainparam.go
new file mode 100644
index 0000000000..225558f3f7
--- /dev/null
+++ b/params/chainparam.go
@@ -0,0 +1,58 @@
+package params
+
+import (
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/params/forks"
+)
+
+// ChainID
+type ChainID big.Int
+
+// TerminalTotalDifficulty (TTD) is the total difficulty value where
+type TerminalTotalDifficulty big.Int
+
+func (v *TerminalTotalDifficulty) MarshalText() ([]byte, error) {
+ return (*big.Int)(v).MarshalText()
+}
+
+func (v *TerminalTotalDifficulty) UnmarshalText(input []byte) error {
+ return (*big.Int)(v).UnmarshalText(input)
+}
+
+// DepositContractAddress configures the location of the deposit contract.
+type DepositContractAddress common.Address
+
+// This configures the EIP-4844 parameters across forks.
+// There must be an entry for each fork
+type BlobSchedule map[forks.Fork]BlobConfig
+
+// DAOForkSupport is the chain parameter that configures the DAO fork.
+// true=supports or false=opposes the fork.
+// The default value is true.
+type DAOForkSupport bool
+
+func init() {
+ Define(Parameter[*ChainID]{
+ Name: "chainId",
+ Optional: false,
+ })
+ Define(Parameter[*TerminalTotalDifficulty]{
+ Name: "terminalTotalDifficulty",
+ Optional: false,
+ })
+ Define(Parameter[DAOForkSupport]{
+ Name: "daoForkSupport",
+ Optional: true,
+ Default: true,
+ })
+ Define(Parameter[BlobSchedule]{
+ Name: "blobSchedule",
+ Optional: true,
+ })
+ Define(Parameter[DepositContractAddress]{
+ Name: "depositContractAddress",
+ Optional: true,
+ })
+}
diff --git a/params/config2.go b/params/config2.go
new file mode 100644
index 0000000000..3e4aeb3844
--- /dev/null
+++ b/params/config2.go
@@ -0,0 +1,373 @@
+// Copyright 2025 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package params
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "maps"
+ "math/big"
+ "reflect"
+ "slices"
+ "strings"
+
+ "github.com/ethereum/go-ethereum/params/forks"
+)
+
+// Activations contains the block numbers/timestamps where hard forks activate.
+type Activations map[forks.Fork]uint64
+
+// Config2 represents the chain configuration.
+type Config2 struct {
+ activation Activations
+ param map[reflect.Type]ParameterType
+}
+
+func NewConfig2(activations Activations, param ...ParameterType) *Config2 {
+ cfg := &Config2{
+ activation: maps.Clone(activations),
+ param: make(map[reflect.Type]ParameterType, len(param)),
+ }
+ for _, pv := range param {
+ info, ok := findParam(pv)
+ if !ok {
+ panic(fmt.Sprintf("undefined chain parameter type %T", pv))
+ }
+ cfg.param[info.rtype] = pv
+ }
+ return cfg
+}
+
+// Active reports whether the given fork is active for a block number/time.
+func (cfg *Config2) Active(f forks.Fork, block, timestamp uint64) bool {
+ activation, ok := cfg.activation[f]
+ if f.BlockBased() {
+ return ok && block >= activation
+ }
+ return ok && timestamp >= activation
+}
+
+func (cfg *Config2) Activation(f forks.Fork) (uint64, bool) {
+ a, ok := cfg.activation[f]
+ return a, ok
+}
+
+// Scheduled says whether the fork is scheduled at all.
+func (cfg *Config2) Scheduled(f forks.Fork) bool {
+ _, ok := cfg.activation[f]
+ return ok
+}
+
+// SetActivations returns a new configuration with the given forks activated.
+func (cfg *Config2) SetActivations(forks Activations) *Config2 {
+ newA := maps.Clone(cfg.activation)
+ maps.Copy(newA, forks)
+ return &Config2{activation: newA, param: cfg.param}
+}
+
+// SetParam returns a new configuration with parameters modified.
+func (cfg *Config2) SetParam(p ParameterType) *Config2 {
+ return &Config2{activation: cfg.activation}
+}
+
+// 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) {
+ if f.BlockBased() {
+ break
+ }
+ if cfg.Active(f, londonBlock, time) {
+ return f
+ }
+ }
+ return forks.Paris
+}
+
+// MarshalJSON encodes the config as JSON.
+func (cfg *Config2) MarshalJSON() ([]byte, error) {
+ m := make(map[string]any)
+ // params
+ for _, p := range cfg.param {
+ info, ok := findParam(p)
+ if !ok {
+ panic(fmt.Sprintf("unknown chain parameter %T", p))
+ }
+ m[info.name] = p
+ }
+ // forks
+ for f, act := range cfg.activation {
+ var name string
+ if f.BlockBased() {
+ name = fmt.Sprintf("%sBlock", strings.ToLower(name))
+ } else {
+ name = fmt.Sprintf("%sTime", strings.ToLower(name))
+ }
+ m[name] = act
+ }
+ return json.Marshal(m)
+}
+
+// MarshalJSON encodes the config as JSON.
+func (cfg *Config2) UnmarshalJSON(input []byte) error {
+ dec := json.NewDecoder(bytes.NewReader(input))
+ tok, err := dec.Token()
+ if err != nil {
+ return err
+ }
+ if tok != json.Delim('{') {
+ return fmt.Errorf("expected JSON object for chain configuration")
+ }
+ // Now we're in the object.
+ newcfg := Config2{
+ activation: make(Activations),
+ }
+ for {
+ tok, err = dec.Token()
+ if tok == json.Delim('}') {
+ break
+ } else if key, ok := tok.(string); ok {
+ if strings.HasSuffix(key, "Block") || strings.HasSuffix(key, "Time") {
+ err = newcfg.decodeActivation(key, dec)
+ } else {
+ err = newcfg.decodeParameter(key, dec)
+ }
+ }
+ if err != nil {
+ return err
+ }
+ }
+
+ *cfg = newcfg
+ return nil
+}
+
+func (cfg *Config2) decodeActivation(key string, dec *json.Decoder) error {
+ var num uint64
+ if err := dec.Decode(&num); err != nil {
+ return err
+ }
+
+ var f forks.Fork
+ name, ok := strings.CutSuffix(key, "Block")
+ if ok {
+ f, ok = forks.ByName(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)
+ if !ok || f.BlockBased() {
+ return fmt.Errorf("unknown time-based fork %q", name)
+ }
+ }
+ cfg.activation[f] = num
+ return nil
+}
+
+func (cfg *Config2) decodeParameter(key string, dec *json.Decoder) error {
+ info, ok := paramRegistryByName[key]
+ if !ok {
+ return fmt.Errorf("unknown chain parameter %q", key)
+ }
+ v := reflect.New(info.rtype).Interface()
+ if err := dec.Decode(v); err != nil {
+ return err
+ }
+ cfg.param[info.rtype] = v
+ return nil
+}
+
+// Validate checks the configuration to ensure forks are scheduled in order,
+// and required settings are present.
+func (cfg *Config2) Validate() error {
+ sanityCheckCanonOrder()
+
+ lastFork := forks.CanonOrder[0]
+ for _, f := range forks.CanonOrder[1:] {
+ 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])
+
+ // 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)
+ }
+ 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
+ }
+
+ // Check that all forks with blobs explicitly define the blob schedule configuration.
+ if f.HasBlobs() {
+ schedule := Get[BlobSchedule](cfg)
+ bcfg, defined := schedule[f]
+ if cfg.Scheduled(f) && !defined {
+ return fmt.Errorf("invalid chain configuration: missing entry for fork %q in blobSchedule", f)
+ }
+ if defined {
+ if err := bcfg.validate(); err != nil {
+ return fmt.Errorf("invalid chain configuration in blobSchedule for fork %q: %v", f, err)
+ }
+ }
+ }
+ }
+ return nil
+}
+
+// CheckCompatible validates chain configuration changes.
+// This called before applying changes to the 'stored configuration', the config
+// which is held in the database. The given block number and time represent the current head
+// of the chain in that database.
+//
+// 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()
+
+ // Iterate checkCompatible to find the lowest conflict.
+ var lasterr *ConfigCompatError
+ bhead, btime := blocknum, time
+ for {
+ err := c.checkCompatible(newcfg, bhead, btime)
+ if err == nil || (lasterr != nil && err.RewindToBlock == lasterr.RewindToBlock && err.RewindToTime == lasterr.RewindToTime) {
+ break
+ }
+ lasterr = err
+
+ if err.RewindToTime > 0 {
+ btime = err.RewindToTime
+ } else {
+ bhead = err.RewindToBlock
+ }
+ }
+ return lasterr
+}
+
+// checkCompatible checks config compatibility at a specific block height.
+func (cfg *Config2) checkCompatible(newcfg *Config2, 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:] {
+ if incompatible(f) {
+ if f.BlockBased() {
+ return newBlockCompatError2(fmt.Sprintf("%v fork block", f), f, cfg, newcfg)
+ }
+ return newTimestampCompatError2("%v fork timestamp", f, cfg, newcfg)
+ }
+ // Specialty checks:
+ if cfg.Active(forks.DAO, num, time) && Get[DAOForkSupport](cfg) != Get[DAOForkSupport](newcfg) {
+ return newBlockCompatError2("DAO fork support flag", f, cfg, newcfg)
+ }
+ chainID := (*big.Int)(Get[*ChainID](cfg))
+ newChainID := (*big.Int)(Get[*ChainID](newcfg))
+ if cfg.Active(forks.TangerineWhistle, num, time) && !configBlockEqual(chainID, newChainID) {
+ return newBlockCompatError2("EIP158 chain ID", f, cfg, newcfg)
+ }
+ }
+ return nil
+}
+
+func newBlockCompatError2(what string, f forks.Fork, storedcfg, newcfg *Config2) *ConfigCompatError {
+ err := &ConfigCompatError{What: what}
+ if storedcfg.Scheduled(f) {
+ err.StoredBlock = new(big.Int).SetUint64(storedcfg.activation[f])
+ }
+ if newcfg.Scheduled(f) {
+ err.NewBlock = new(big.Int).SetUint64(newcfg.activation[f])
+ }
+ // Need to rewind to one block before the earliest possible activation.
+ rew, _ := minActivation(f, storedcfg, newcfg)
+ if rew > 0 {
+ err.RewindToBlock = rew - 1
+ }
+ return err
+}
+
+func newTimestampCompatError2(what string, f forks.Fork, storedcfg, newcfg *Config2) *ConfigCompatError {
+ err := &ConfigCompatError{What: what}
+ if storedcfg.Scheduled(f) {
+ t := storedcfg.activation[f]
+ err.StoredTime = &t
+ }
+ if newcfg.Scheduled(f) {
+ t := newcfg.activation[f]
+ err.NewTime = &t
+ }
+ // Need to rewind to before the earliest possible activation.
+ rew, _ := minActivation(f, storedcfg, newcfg)
+ if rew > 0 {
+ err.RewindToTime = rew - 1
+ }
+ return err
+}
+
+// minActivation the earliest possible activation block/time for the given fork
+// across two configurations.
+func minActivation(f forks.Fork, cfg1, cfg2 *Config2) (act uint64, scheduled bool) {
+ switch {
+ case !cfg1.Scheduled(f):
+ act, scheduled = cfg2.activation[f]
+ case !cfg2.Scheduled(f):
+ act, scheduled = cfg1.activation[f]
+ default:
+ act = min(cfg1.activation[f], cfg2.activation[f])
+ scheduled = true
+ }
+ return
+}
+
+// activationEqual checks whether a fork has the same activation two configurations.
+// Note this also returns true when it isn't scheduled at all.
+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)
+}
diff --git a/params/config2_presets.go b/params/config2_presets.go
new file mode 100644
index 0000000000..557cb5d0ac
--- /dev/null
+++ b/params/config2_presets.go
@@ -0,0 +1,115 @@
+// Copyright 2025 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package params
+
+import (
+ "math"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/params/forks"
+)
+
+var (
+ MainnetConfig2 *Config2
+ AllEthashProtocolChanges2 *Config2
+ MergedTestChainConfig2 *Config2
+)
+
+func init() {
+ MainnetConfig2 = NewConfig2(
+ Activations{
+ forks.Homestead: 1_150_000,
+ forks.DAO: 1_920_000,
+ forks.TangerineWhistle: 2_463_000,
+ forks.SpuriousDragon: 2_675_000,
+ forks.Byzantium: 4_370_000,
+ forks.Constantinople: 7_280_000,
+ forks.Petersburg: 7_280_000,
+ forks.Istanbul: 9_069_000,
+ forks.MuirGlacier: 9_200_000,
+ forks.Berlin: 12_244_000,
+ forks.London: 12_965_000,
+ forks.ArrowGlacier: 13_773_000,
+ forks.GrayGlacier: 15_050_000,
+ // time-based forks
+ forks.Shanghai: 1681338455,
+ forks.Cancun: 1710338135,
+ forks.Prague: 1746612311,
+ },
+ (*ChainID)(big.NewInt(1)),
+ (*TerminalTotalDifficulty)(MainnetTerminalTotalDifficulty),
+ DepositContractAddress(common.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa")),
+ DAOForkSupport(true),
+ BlobSchedule{
+ forks.Cancun: *DefaultCancunBlobConfig,
+ forks.Prague: *DefaultPragueBlobConfig,
+ },
+ )
+
+ // AllEthashProtocolChanges2 contains every protocol change (EIPs) introduced
+ // and accepted by the Ethereum core developers into the Ethash consensus.
+ AllEthashProtocolChanges2 = NewConfig2(
+ Activations{
+ forks.Homestead: 0,
+ forks.TangerineWhistle: 0,
+ forks.SpuriousDragon: 0,
+ forks.Byzantium: 0,
+ forks.Constantinople: 0,
+ forks.Petersburg: 0,
+ forks.Istanbul: 0,
+ forks.MuirGlacier: 0,
+ forks.Berlin: 0,
+ forks.London: 0,
+ forks.ArrowGlacier: 0,
+ forks.GrayGlacier: 0,
+ },
+ (*ChainID)(big.NewInt(1337)),
+ (*TerminalTotalDifficulty)(big.NewInt(math.MaxInt64)),
+ )
+
+ // MergedTestChainConfig2 contains every protocol change (EIPs) introduced
+ // and accepted by the Ethereum core developers for testing purposes.
+ MergedTestChainConfig2 = NewConfig2(
+ Activations{
+ forks.Homestead: 0,
+ forks.TangerineWhistle: 0,
+ forks.SpuriousDragon: 0,
+ forks.Byzantium: 0,
+ forks.Constantinople: 0,
+ forks.Petersburg: 0,
+ forks.Istanbul: 0,
+ forks.MuirGlacier: 0,
+ forks.Berlin: 0,
+ forks.London: 0,
+ forks.ArrowGlacier: 0,
+ forks.GrayGlacier: 0,
+ forks.Paris: 0,
+ forks.Shanghai: 0,
+ forks.Cancun: 0,
+ forks.Prague: 0,
+ forks.Osaka: 0,
+ },
+ (*ChainID)(big.NewInt(1)),
+ (*TerminalTotalDifficulty)(big.NewInt(0)),
+ BlobSchedule{
+ forks.Cancun: *DefaultCancunBlobConfig,
+ forks.Prague: *DefaultPragueBlobConfig,
+ forks.Osaka: *DefaultOsakaBlobConfig,
+ },
+ )
+}
diff --git a/params/forks/forks.go b/params/forks/forks.go
index 5c9612a625..9d6a42f68a 100644
--- a/params/forks/forks.go
+++ b/params/forks/forks.go
@@ -16,60 +16,132 @@
package forks
+import "fmt"
+
// Fork is a numerical identifier of specific network upgrades (forks).
-type Fork int
+type Fork uint32
const (
- Frontier Fork = iota
- FrontierThawing
- Homestead
- DAO
- TangerineWhistle // a.k.a. the EIP150 fork
- SpuriousDragon // a.k.a. the EIP155 fork
- Byzantium
- Constantinople
- Petersburg
- Istanbul
- MuirGlacier
- Berlin
- London
- ArrowGlacier
- GrayGlacier
- Paris
- Shanghai
- Cancun
- Prague
- Osaka
+ 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
)
+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
+)
+
+// 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
+}
+
// String implements fmt.Stringer.
func (f Fork) String() string {
s, ok := forkToString[f]
if !ok {
- return "Unknown fork"
+ return fmt.Sprintf("unknownFork(%#x)", f)
}
return s
}
var forkToString = map[Fork]string{
Frontier: "Frontier",
- FrontierThawing: "Frontier Thawing",
Homestead: "Homestead",
- DAO: "DAO",
- TangerineWhistle: "Tangerine Whistle",
- SpuriousDragon: "Spurious Dragon",
+ DAO: "DAOFork",
+ TangerineWhistle: "TangerineWhistle",
+ SpuriousDragon: "SpuriousDragon",
Byzantium: "Byzantium",
Constantinople: "Constantinople",
Petersburg: "Petersburg",
Istanbul: "Istanbul",
- MuirGlacier: "Muir Glacier",
+ MuirGlacier: "MuirGlacier",
Berlin: "Berlin",
London: "London",
- ArrowGlacier: "Arrow Glacier",
- GrayGlacier: "Gray Glacier",
+ 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
+}
diff --git a/params/registry.go b/params/registry.go
new file mode 100644
index 0000000000..aed4888b8b
--- /dev/null
+++ b/params/registry.go
@@ -0,0 +1,85 @@
+// Copyright 2025 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package params
+
+import (
+ "fmt"
+ "reflect"
+)
+
+type ParameterType any
+
+type paramInfo struct {
+ rtype reflect.Type
+ name string
+ optional bool
+ defaultVal any
+}
+
+var (
+ paramRegistry = map[reflect.Type]*paramInfo{}
+ paramRegistryByName = map[string]*paramInfo{}
+)
+
+type Parameter[T ParameterType] struct {
+ Name string
+ Optional bool
+ Default T
+}
+
+// Get retrieves the value of a chain parameter.
+func Get[T ParameterType](cfg *Config2) T {
+ for _, p := range cfg.param {
+ if v, ok := p.(T); ok {
+ return v
+ }
+ }
+ // get default
+ var z T
+ info, ok := findParam(z)
+ if !ok {
+ panic(fmt.Sprintf("unknown parameter type %T", z))
+ }
+ return info.defaultVal.(T)
+}
+
+func Define[T ParameterType](def Parameter[T]) {
+ var z T
+ info, defined := paramRegistryByName[def.Name]
+ if defined {
+ panic(fmt.Sprintf("chain parameter %q already registered with type %v", info.name, info.rtype))
+ }
+ rtype := reflect.TypeOf(z)
+ info, defined = paramRegistry[rtype]
+ if defined {
+ panic(fmt.Sprintf("chain parameter of type %v already registered with name %q", rtype, info.name))
+ }
+ info = ¶mInfo{
+ rtype: rtype,
+ name: def.Name,
+ optional: def.Optional,
+ defaultVal: def.Default,
+ }
+ paramRegistry[rtype] = info
+ paramRegistryByName[def.Name] = info
+}
+
+func findParam(v any) (*paramInfo, bool) {
+ rtype := reflect.TypeOf(v)
+ info, ok := paramRegistry[rtype]
+ return info, ok
+}