mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 13:46:43 +00:00
params: add validation tests and remove LatestFork
This commit is contained in:
parent
57c395f59a
commit
13d14f5ab8
4 changed files with 107 additions and 30 deletions
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"maps"
|
"maps"
|
||||||
"math/big"
|
"math/big"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/params/forks"
|
"github.com/ethereum/go-ethereum/params/forks"
|
||||||
|
|
@ -43,7 +44,6 @@ func NewConfig2(activations Activations, param ...ParamValue) *Config2 {
|
||||||
activation: maps.Clone(activations),
|
activation: maps.Clone(activations),
|
||||||
param: make(map[int]any, len(param)),
|
param: make(map[int]any, len(param)),
|
||||||
}
|
}
|
||||||
cfg.activation[forks.Frontier] = 0
|
|
||||||
for _, pv := range param {
|
for _, pv := range param {
|
||||||
cfg.param[pv.id] = pv.value
|
cfg.param[pv.id] = pv.value
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +52,9 @@ func NewConfig2(activations Activations, param ...ParamValue) *Config2 {
|
||||||
|
|
||||||
// Active reports whether the given fork is active for a block number/time.
|
// Active reports whether the given fork is active for a block number/time.
|
||||||
func (cfg *Config2) Active(f forks.Fork, block, timestamp uint64) bool {
|
func (cfg *Config2) Active(f forks.Fork, block, timestamp uint64) bool {
|
||||||
|
if f == forks.Frontier {
|
||||||
|
return true
|
||||||
|
}
|
||||||
activation, ok := cfg.activation[f]
|
activation, ok := cfg.activation[f]
|
||||||
if f.BlockBased() {
|
if f.BlockBased() {
|
||||||
return ok && block >= activation
|
return ok && block >= activation
|
||||||
|
|
@ -61,6 +64,9 @@ func (cfg *Config2) Active(f forks.Fork, block, timestamp uint64) bool {
|
||||||
|
|
||||||
// ActiveAtBlock reports whether the given fork is active for a block number/time.
|
// ActiveAtBlock reports whether the given fork is active for a block number/time.
|
||||||
func (cfg *Config2) ActiveAtBlock(f forks.Fork, block *big.Int) bool {
|
func (cfg *Config2) ActiveAtBlock(f forks.Fork, block *big.Int) bool {
|
||||||
|
if f == forks.Frontier {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if !f.BlockBased() {
|
if !f.BlockBased() {
|
||||||
panic(fmt.Sprintf("fork %v has time-based scheduling", f))
|
panic(fmt.Sprintf("fork %v has time-based scheduling", f))
|
||||||
}
|
}
|
||||||
|
|
@ -96,19 +102,38 @@ func (cfg *Config2) SetParam(param ...ParamValue) *Config2 {
|
||||||
return cpy
|
return cpy
|
||||||
}
|
}
|
||||||
|
|
||||||
// LatestFork returns the latest time-based fork that would be active for the given time.
|
// String encodes the config in a readable way.
|
||||||
func (cfg *Config2) LatestFork(time uint64) forks.Fork {
|
func (cfg *Config2) String() string {
|
||||||
var active []forks.Fork
|
paramList := slices.Sorted(maps.Keys(cfg.param))
|
||||||
for f := range forks.All() {
|
forkList := make([]forkActivation, 0, len(cfg.activation))
|
||||||
if f.BlockBased() {
|
for f, a := range cfg.activation {
|
||||||
continue
|
forkList = append(forkList, forkActivation{f, a})
|
||||||
}
|
|
||||||
if a, ok := cfg.activation[f]; ok && a <= time {
|
|
||||||
active = append(active, f)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
slices.SortFunc(forkList, forkActivation.compare)
|
||||||
|
|
||||||
return forks.Paris
|
var out strings.Builder
|
||||||
|
var sp bool
|
||||||
|
writeSp := func() {
|
||||||
|
if sp {
|
||||||
|
out.WriteString(" ")
|
||||||
|
}
|
||||||
|
sp = true
|
||||||
|
}
|
||||||
|
out.WriteRune('[')
|
||||||
|
for _, fa := range forkList {
|
||||||
|
writeSp()
|
||||||
|
out.WriteString(fa.fork.ConfigName())
|
||||||
|
out.WriteString(":")
|
||||||
|
out.WriteString(strconv.FormatUint(fa.activation, 10))
|
||||||
|
}
|
||||||
|
for _, p := range paramList {
|
||||||
|
writeSp()
|
||||||
|
out.WriteString(paramRegistry[p].name)
|
||||||
|
out.WriteString(":")
|
||||||
|
fmt.Fprintf(&out, "%v", cfg.param[p])
|
||||||
|
}
|
||||||
|
out.WriteRune(']')
|
||||||
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON encodes the config as JSON.
|
// MarshalJSON encodes the config as JSON.
|
||||||
|
|
@ -146,9 +171,7 @@ func (cfg *Config2) UnmarshalJSON(input []byte) error {
|
||||||
return fmt.Errorf("expected JSON object for chain configuration")
|
return fmt.Errorf("expected JSON object for chain configuration")
|
||||||
}
|
}
|
||||||
// Now we're in the object.
|
// Now we're in the object.
|
||||||
newcfg := Config2{
|
newcfg := Config2{activation: Activations{}}
|
||||||
activation: make(Activations),
|
|
||||||
}
|
|
||||||
for {
|
for {
|
||||||
tok, err = dec.Token()
|
tok, err = dec.Token()
|
||||||
if tok == json.Delim('}') {
|
if tok == json.Delim('}') {
|
||||||
|
|
@ -182,6 +205,9 @@ func (cfg *Config2) decodeActivation(key string, dec *json.Decoder) error {
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
if f == forks.Frontier {
|
||||||
|
return fmt.Errorf("frontier fork cannot be scheduled")
|
||||||
|
}
|
||||||
} else if name, ok = strings.CutSuffix(key, "Time"); ok {
|
} else if name, ok = strings.CutSuffix(key, "Time"); ok {
|
||||||
f, ok = forks.ForkByConfigName(name)
|
f, ok = forks.ForkByConfigName(name)
|
||||||
if !ok || f.BlockBased() {
|
if !ok || f.BlockBased() {
|
||||||
|
|
@ -215,6 +241,9 @@ func (cfg *Config2) Validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for dep := range f.DirectDependencies() {
|
for dep := range f.DirectDependencies() {
|
||||||
|
if dep == forks.Frontier {
|
||||||
|
continue
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
// Non-optional forks must all be present in the chain config up to the last defined fork.
|
// Non-optional forks must all be present in the chain config up to the last defined fork.
|
||||||
case !cfg.Scheduled(dep) && cfg.Scheduled(f):
|
case !cfg.Scheduled(dep) && cfg.Scheduled(f):
|
||||||
|
|
@ -238,7 +267,7 @@ func (cfg *Config2) Validate() error {
|
||||||
v, isSet := cfg.param[id]
|
v, isSet := cfg.param[id]
|
||||||
if !isSet {
|
if !isSet {
|
||||||
if !info.optional {
|
if !info.optional {
|
||||||
return fmt.Errorf("required chain parameter %s is not set", info.name)
|
return fmt.Errorf("required chain parameter %q is not set", info.name)
|
||||||
}
|
}
|
||||||
v = info.defaultValue
|
v = info.defaultValue
|
||||||
}
|
}
|
||||||
|
|
@ -260,8 +289,6 @@ func (cfg *Config2) Validate() error {
|
||||||
// point where the new config can be applied safely.
|
// point where the new config can be applied safely.
|
||||||
func (cfg *Config2) CheckCompatible(newcfg *Config2, blocknum uint64, time uint64) *ConfigCompatError {
|
func (cfg *Config2) CheckCompatible(newcfg *Config2, blocknum uint64, time uint64) *ConfigCompatError {
|
||||||
// Gather forks which are active in either config, and sort them by activation.
|
// 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
|
var forkList []forkActivation
|
||||||
for f := range forks.All() {
|
for f := range forks.All() {
|
||||||
a, ok := minActivation(f, cfg, newcfg)
|
a, ok := minActivation(f, cfg, newcfg)
|
||||||
|
|
@ -269,18 +296,7 @@ func (cfg *Config2) CheckCompatible(newcfg *Config2, blocknum uint64, time uint6
|
||||||
forkList = append(forkList, forkActivation{f, a})
|
forkList = append(forkList, forkActivation{f, a})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
slices.SortFunc(forkList, func(f1, f2 forkActivation) int {
|
slices.SortFunc(forkList, forkActivation.compare)
|
||||||
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
|
||||||
|
|
@ -387,3 +403,18 @@ type forkActivation struct {
|
||||||
fork forks.Fork
|
fork forks.Fork
|
||||||
activation uint64
|
activation uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fa forkActivation) compare(other forkActivation) int {
|
||||||
|
// 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.
|
||||||
|
switch {
|
||||||
|
case fa.fork.BlockBased() && !other.fork.BlockBased():
|
||||||
|
return -1
|
||||||
|
case !fa.fork.BlockBased() && other.fork.BlockBased():
|
||||||
|
return 1
|
||||||
|
case fa.activation == other.activation:
|
||||||
|
return strings.Compare(fa.fork.String(), other.fork.String())
|
||||||
|
default:
|
||||||
|
return cmp.Compare(fa.activation, other.activation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,46 @@ package params_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/params/forks"
|
"github.com/ethereum/go-ethereum/params/forks"
|
||||||
"github.com/ethereum/go-ethereum/params/presets"
|
"github.com/ethereum/go-ethereum/params/presets"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestConfigValidateErrors(t *testing.T) {
|
||||||
|
files, err := filepath.Glob("testdata/invalid-*.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type test struct {
|
||||||
|
Config params.Config2 `json:"config"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
name := filepath.Base(f)
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
var test test
|
||||||
|
if err := common.LoadJSON(f, &test); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err := test.Config.Validate()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected validation error, got none")
|
||||||
|
}
|
||||||
|
if err.Error() != test.Error {
|
||||||
|
t.Fatal("wrong error:\n got:", err.Error(), "want:", test.Error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCheckCompatible2(t *testing.T) {
|
func TestCheckCompatible2(t *testing.T) {
|
||||||
type test struct {
|
type test struct {
|
||||||
stored, new *params.Config2
|
stored, new *params.Config2
|
||||||
|
|
|
||||||
7
params/testdata/invalid-forkorder.json
vendored
Normal file
7
params/testdata/invalid-forkorder.json
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"homesteadBlock": 10,
|
||||||
|
"eip150Block": 8
|
||||||
|
},
|
||||||
|
"error": "unsupported fork ordering: Homestead enabled at block 10, but TangerineWhistle enabled at block 8"
|
||||||
|
}
|
||||||
8
params/testdata/invalid-missingChainID.json
vendored
Normal file
8
params/testdata/invalid-missingChainID.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"homesteadBlock": 7,
|
||||||
|
"eip150Block": 8,
|
||||||
|
"eip155Block": 9
|
||||||
|
},
|
||||||
|
"error": "required chain parameter \"chainId\" is not set"
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue