mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 02:12:23 +00:00
feat: pseudo-generic extra payloads in params.ChainConfig and params.Rules
This commit is contained in:
parent
941ae33d7e
commit
bc6da7c964
8 changed files with 674 additions and 1 deletions
36
libevm/examples/extraparams/extraparams.go
Normal file
36
libevm/examples/extraparams/extraparams.go
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
package extraparams
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/libevm/pseudo"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
params.RegisterExtras(params.Extras[ChainConfigExtra, RulesExtra]{
|
||||||
|
NewForRules: constructRulesExtra,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChainConfigExtra struct {
|
||||||
|
MyFeatureTime *uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type RulesExtra struct {
|
||||||
|
IsMyFeature bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func constructRulesExtra(c *params.ChainConfig, r *params.Rules, cEx *ChainConfigExtra, blockNum *big.Int, isMerge bool, timestamp uint64) *RulesExtra {
|
||||||
|
return &RulesExtra{
|
||||||
|
IsMyFeature: isMerge && cEx.MyFeatureTime != nil && *cEx.MyFeatureTime < timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromChainConfig(c *params.ChainConfig) *ChainConfigExtra {
|
||||||
|
return pseudo.NewValueUnsafe[*ChainConfigExtra](c.ExtraPayload()).Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromRules(r *params.Rules) *RulesExtra {
|
||||||
|
return pseudo.NewValueUnsafe[*RulesExtra](r.ExtraPayload()).Get()
|
||||||
|
}
|
||||||
13
libevm/examples/go.mod
Normal file
13
libevm/examples/go.mod
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
module libevm/examples
|
||||||
|
|
||||||
|
go 1.22.4
|
||||||
|
|
||||||
|
replace github.com/ethereum/go-ethereum => ../../
|
||||||
|
|
||||||
|
require github.com/ethereum/go-ethereum v0.0.0-00010101000000-000000000000
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/holiman/uint256 v1.3.1 // indirect
|
||||||
|
golang.org/x/crypto v0.22.0 // indirect
|
||||||
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
|
)
|
||||||
14
libevm/examples/go.sum
Normal file
14
libevm/examples/go.sum
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs=
|
||||||
|
github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||||
|
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||||
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
121
libevm/pseudo/type.go
Normal file
121
libevm/pseudo/type.go
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
// Package pseudo ...
|
||||||
|
package pseudo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Zero ...
|
||||||
|
func Zero[T any]() (*Type, *Value[T]) {
|
||||||
|
var x T
|
||||||
|
return From[T](x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// From ...
|
||||||
|
func From[T any](x T) (*Type, *Value[T]) {
|
||||||
|
t := &Type{
|
||||||
|
val: &concrete[T]{
|
||||||
|
val: x,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return t, NewValueUnsafe[T](t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyType ...
|
||||||
|
func OnlyType[T any](t *Type, _ *Value[T]) *Type {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Type ...
|
||||||
|
type Type struct {
|
||||||
|
val value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Type) Interface() any { return t.val.get() }
|
||||||
|
|
||||||
|
// func (t *Type) Set(v any) error { return t.val.Set(v) }
|
||||||
|
// func (t *Type) MustSet(v any) { t.val.MustSet(v) }
|
||||||
|
func (t *Type) MarshalJSON() ([]byte, error) { return t.val.MarshalJSON() }
|
||||||
|
func (t *Type) UnmarshalJSON(b []byte) error { return t.val.UnmarshalJSON(b) }
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ json.Marshaler = (*Type)(nil)
|
||||||
|
_ json.Unmarshaler = (*Type)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewValueUnsafe[T any](t *Type) *Value[T] {
|
||||||
|
return &Value[T]{t: t}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewValue[T any](t *Type) (*Value[T], error) {
|
||||||
|
var x T
|
||||||
|
if !t.val.canSetTo(x) {
|
||||||
|
return nil, fmt.Errorf("cannot create *Accessor[%T] with *Type carrying %T", x, t.val.get())
|
||||||
|
}
|
||||||
|
return NewValueUnsafe[T](t), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Value[T any] struct {
|
||||||
|
t *Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Value[T]) Get() T { return a.t.val.get().(T) }
|
||||||
|
func (a *Value[T]) Set(v T) { a.t.val.mustSet(v) }
|
||||||
|
|
||||||
|
type value interface {
|
||||||
|
get() any
|
||||||
|
canSetTo(any) bool
|
||||||
|
set(any) error
|
||||||
|
mustSet(any)
|
||||||
|
|
||||||
|
json.Marshaler
|
||||||
|
json.Unmarshaler
|
||||||
|
}
|
||||||
|
|
||||||
|
type concrete[T any] struct {
|
||||||
|
val T
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *concrete[T]) get() any { return c.val }
|
||||||
|
|
||||||
|
func (c *concrete[T]) canSetTo(v any) bool {
|
||||||
|
_, ok := v.(T)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvalidTypeError[T any] struct {
|
||||||
|
SetTo any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *InvalidTypeError[T]) Error() string {
|
||||||
|
var t T
|
||||||
|
return fmt.Sprintf("cannot set %T to %T", t, e.SetTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *concrete[T]) set(v any) error {
|
||||||
|
vv, ok := v.(T)
|
||||||
|
if !ok {
|
||||||
|
return &InvalidTypeError[T]{SetTo: v}
|
||||||
|
}
|
||||||
|
c.val = vv
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *concrete[T]) mustSet(v any) {
|
||||||
|
if err := c.set(v); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_ = 0 // for happy-path coverage inspection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *concrete[T]) MarshalJSON() ([]byte, error) { return json.Marshal(c.val) }
|
||||||
|
|
||||||
|
func (c *concrete[T]) UnmarshalJSON(b []byte) error {
|
||||||
|
var v T
|
||||||
|
if err := json.Unmarshal(b, &v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.val = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
68
libevm/pseudo/type_test.go
Normal file
68
libevm/pseudo/type_test.go
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
package pseudo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestType(t *testing.T) {
|
||||||
|
testType(t, "Zero[int]", Zero[int], 0, 42, "I'm not an int")
|
||||||
|
testType(t, "Zero[string]", Zero[string], "", "hello, world", 99)
|
||||||
|
|
||||||
|
testType(
|
||||||
|
t, "From[uint](314159)",
|
||||||
|
func() (*Type, *Value[uint]) {
|
||||||
|
return From[uint](314159)
|
||||||
|
},
|
||||||
|
314159, 0, struct{}{},
|
||||||
|
)
|
||||||
|
|
||||||
|
testType(t, "nil pointer", Zero[*float64], (*float64)(nil), new(float64), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testType[T any](t *testing.T, name string, ctor func() (*Type, *Value[T]), init T, setTo T, invalid any) {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
typ, val := ctor()
|
||||||
|
assert.Equal(t, init, val.Get())
|
||||||
|
val.Set(setTo)
|
||||||
|
assert.Equal(t, setTo, val.Get())
|
||||||
|
|
||||||
|
t.Run("set to invalid type", func(t *testing.T) {
|
||||||
|
wantErr := &InvalidTypeError[T]{SetTo: invalid}
|
||||||
|
|
||||||
|
assertError := func(t *testing.T, err any) {
|
||||||
|
t.Helper()
|
||||||
|
switch err := err.(type) {
|
||||||
|
case *InvalidTypeError[T]:
|
||||||
|
assert.Equal(t, wantErr, err)
|
||||||
|
default:
|
||||||
|
t.Errorf("got error %v; want %v", err, wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(fmt.Sprintf("Set(%T{%v})", invalid, invalid), func(t *testing.T) {
|
||||||
|
assertError(t, typ.val.set(invalid))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run(fmt.Sprintf("MustSet(%T{%v})", invalid, invalid), func(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
assertError(t, recover())
|
||||||
|
}()
|
||||||
|
typ.val.mustSet(invalid)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("JSON round trip", func(t *testing.T) {
|
||||||
|
buf, err := json.Marshal(typ)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got, gotVal := Zero[T]()
|
||||||
|
require.NoError(t, json.Unmarshal(buf, &got))
|
||||||
|
assert.Equal(t, val.Get(), gotVal.Get())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/libevm/pseudo"
|
||||||
"github.com/ethereum/go-ethereum/params/forks"
|
"github.com/ethereum/go-ethereum/params/forks"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -340,6 +341,8 @@ type ChainConfig struct {
|
||||||
// Various consensus engines
|
// Various consensus engines
|
||||||
Ethash *EthashConfig `json:"ethash,omitempty"`
|
Ethash *EthashConfig `json:"ethash,omitempty"`
|
||||||
Clique *CliqueConfig `json:"clique,omitempty"`
|
Clique *CliqueConfig `json:"clique,omitempty"`
|
||||||
|
|
||||||
|
extra *pseudo.Type // See RegisterExtras()
|
||||||
}
|
}
|
||||||
|
|
||||||
// EthashConfig is the consensus engine configs for proof-of-work based sealing.
|
// EthashConfig is the consensus engine configs for proof-of-work based sealing.
|
||||||
|
|
@ -891,6 +894,8 @@ type Rules struct {
|
||||||
IsBerlin, IsLondon bool
|
IsBerlin, IsLondon bool
|
||||||
IsMerge, IsShanghai, IsCancun, IsPrague bool
|
IsMerge, IsShanghai, IsCancun, IsPrague bool
|
||||||
IsVerkle bool
|
IsVerkle bool
|
||||||
|
|
||||||
|
extra *pseudo.Type // See RegisterExtras()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rules ensures c's ChainID is not nil.
|
// Rules ensures c's ChainID is not nil.
|
||||||
|
|
@ -902,7 +907,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
|
||||||
// disallow setting Merge out of order
|
// disallow setting Merge out of order
|
||||||
isMerge = isMerge && c.IsLondon(num)
|
isMerge = isMerge && c.IsLondon(num)
|
||||||
isVerkle := isMerge && c.IsVerkle(num, timestamp)
|
isVerkle := isMerge && c.IsVerkle(num, timestamp)
|
||||||
return Rules{
|
r := Rules{
|
||||||
ChainID: new(big.Int).Set(chainID),
|
ChainID: new(big.Int).Set(chainID),
|
||||||
IsHomestead: c.IsHomestead(num),
|
IsHomestead: c.IsHomestead(num),
|
||||||
IsEIP150: c.IsEIP150(num),
|
IsEIP150: c.IsEIP150(num),
|
||||||
|
|
@ -922,4 +927,6 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
|
||||||
IsVerkle: isVerkle,
|
IsVerkle: isVerkle,
|
||||||
IsEIP4762: isVerkle,
|
IsEIP4762: isVerkle,
|
||||||
}
|
}
|
||||||
|
c.addRulesExtra(&r, num, isMerge, timestamp)
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
|
||||||
164
params/config.libevm.go
Normal file
164
params/config.libevm.go
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
package params
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/libevm/pseudo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Extras are arbitrary payloads to be added as extra fields in [ChainConfig]
|
||||||
|
// and [Rules] structs. See [RegisterExtras].
|
||||||
|
type Extras[C any, R any] struct {
|
||||||
|
// NewForRules, if non-nil is called at the end of [ChainConfig.Rules] with
|
||||||
|
// the newly created [Rules] and the [ChainConfig] extra payload. Its
|
||||||
|
// returned value will be the extra payload of the [Rules]. If NewForRules
|
||||||
|
// is nil then so too will the [Rules] extra payload be a nil `*R`.
|
||||||
|
//
|
||||||
|
// NewForRules MAY modify the [Rules] but MUST NOT modify the [ChainConfig].
|
||||||
|
NewForRules func(_ *ChainConfig, _ *Rules, _ *C, blockNum *big.Int, isMerge bool, timestamp uint64) *R
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterExtras registers the types `C` and `R` such that they are carried as
|
||||||
|
// extra payloads in [ChainConfig] and [Rules] structs, respectively. It is
|
||||||
|
// expected to be called in an `init()` function and MUST NOT be called more
|
||||||
|
// than once. Both `C` and `R` MUST be structs.
|
||||||
|
//
|
||||||
|
// After registration, JSON unmarshalling of a [ChainConfig] will create a new
|
||||||
|
// `*C` and unmarshal the JSON key "extra" into it. Conversely, JSON marshalling
|
||||||
|
// will populate the "extra" key with the contents of the `*C`. Calls to
|
||||||
|
// [ChainConfig.Rules] will call the `NewForRules` function of the registered
|
||||||
|
// [Extras] to create a new `*R`.
|
||||||
|
//
|
||||||
|
// The payloads can be accessed via the [ChainConfig.ExtraPayload] and
|
||||||
|
// [Rules.ExtraPayload] methods, which will always return a `*C` or `*R`
|
||||||
|
// respectively however these pointers may themselves be nil.
|
||||||
|
//
|
||||||
|
// As the `ExtraPayload()` methods are not generic and return `any`, their
|
||||||
|
// values MUST be type-asserted to the returned type; failure to do so may
|
||||||
|
// result in a typed-nil bug. This pattern most-closely resembles a fully
|
||||||
|
// generic implementation and users SHOULD wrap the type assertions in a shared
|
||||||
|
// package.
|
||||||
|
func RegisterExtras[C any, R any](e Extras[C, R]) {
|
||||||
|
if registeredExtras != nil {
|
||||||
|
panic("re-registration of Extras")
|
||||||
|
}
|
||||||
|
mustBeStruct[C]()
|
||||||
|
mustBeStruct[R]()
|
||||||
|
registeredExtras = &e
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustBeStruct[T any]() {
|
||||||
|
var x T
|
||||||
|
if k := reflect.TypeOf(x).Kind(); k != reflect.Struct {
|
||||||
|
panic(notStructMessage[T]())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func notStructMessage[T any]() string {
|
||||||
|
var x T
|
||||||
|
return fmt.Sprintf("%T is not a struct", x)
|
||||||
|
}
|
||||||
|
|
||||||
|
var registeredExtras interface {
|
||||||
|
nilForChainConfig() *pseudo.Type
|
||||||
|
nilForRules() *pseudo.Type
|
||||||
|
newForChainConfig() *pseudo.Type
|
||||||
|
newForRules(_ *ChainConfig, _ *Rules, blockNum *big.Int, isMerge bool, timestamp uint64) *pseudo.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ json.Unmarshaler = (*ChainConfig)(nil)
|
||||||
|
_ json.Marshaler = (*ChainConfig)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnmarshalJSON ... TODO
|
||||||
|
func (c *ChainConfig) UnmarshalJSON(data []byte) error {
|
||||||
|
// We need to bypass this UnmarshalJSON() method when we again call
|
||||||
|
// json.Unmarshal(). The `raw` type won't inherit the method.
|
||||||
|
type raw ChainConfig
|
||||||
|
cc := &struct {
|
||||||
|
*raw
|
||||||
|
Extra json.RawMessage `json:"extra"`
|
||||||
|
}{raw: (*raw)(c)}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, cc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if registeredExtras == nil || len(cc.Extra) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
extra := registeredExtras.newForChainConfig()
|
||||||
|
if err := json.Unmarshal(cc.Extra, extra); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.extra = extra
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON ... TODO
|
||||||
|
func (c *ChainConfig) MarshalJSON() ([]byte, error) {
|
||||||
|
type raw ChainConfig
|
||||||
|
cc := &struct {
|
||||||
|
*raw
|
||||||
|
Extra any `json:"extra"`
|
||||||
|
}{raw: (*raw)(c), Extra: c.extra}
|
||||||
|
return json.Marshal(cc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChainConfig) addRulesExtra(r *Rules, blockNum *big.Int, isMerge bool, timestamp uint64) {
|
||||||
|
r.extra = nil
|
||||||
|
if registeredExtras != nil {
|
||||||
|
r.extra = registeredExtras.newForRules(c, r, blockNum, isMerge, timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtraPayload returns the extra payload carried by the ChainConfig and can
|
||||||
|
// only be called if [RegisterExtras] was called. The returned value is always
|
||||||
|
// of type `*C` as registered, but may be nil. Callers MUST immediately
|
||||||
|
// type-assert the returned value to `*C` to avoid typed-nil bugs. See the
|
||||||
|
// example for the intended usage pattern.
|
||||||
|
func (c *ChainConfig) ExtraPayload() *pseudo.Type {
|
||||||
|
if registeredExtras == nil {
|
||||||
|
panic(fmt.Sprintf("%T.ExtraPayload() called before RegisterExtras()", c))
|
||||||
|
}
|
||||||
|
if c.extra == nil {
|
||||||
|
c.extra = registeredExtras.nilForChainConfig()
|
||||||
|
}
|
||||||
|
return c.extra
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtraPayload returns the extra payload carried by the Rules and can only be
|
||||||
|
// called if [RegisterExtras] was called. The returned value is always of type
|
||||||
|
// `*R` as registered, but may be nil. Callers MUST immediately type-assert the
|
||||||
|
// returned value to `*R` to avoid typed-nil bugs. See the example on
|
||||||
|
// [ChainConfig.ExtraPayload] for the intended usage pattern.
|
||||||
|
func (r *Rules) ExtraPayload() *pseudo.Type {
|
||||||
|
if registeredExtras == nil {
|
||||||
|
panic(fmt.Sprintf("%T.ExtraPayload() called before RegisterExtras()", r))
|
||||||
|
}
|
||||||
|
if r.extra == nil {
|
||||||
|
r.extra = registeredExtras.nilForRules()
|
||||||
|
}
|
||||||
|
return r.extra
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Extras[C, R]) nilForChainConfig() *pseudo.Type { return pseudo.OnlyType(pseudo.Zero[*C]()) }
|
||||||
|
func (Extras[C, R]) nilForRules() *pseudo.Type { return pseudo.OnlyType(pseudo.Zero[*R]()) }
|
||||||
|
|
||||||
|
func (*Extras[C, R]) newForChainConfig() *pseudo.Type {
|
||||||
|
var x C
|
||||||
|
return pseudo.OnlyType(pseudo.From(&x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Extras[C, R]) newForRules(c *ChainConfig, r *Rules, blockNum *big.Int, isMerge bool, timestamp uint64) *pseudo.Type {
|
||||||
|
if e.NewForRules == nil {
|
||||||
|
return e.nilForRules()
|
||||||
|
}
|
||||||
|
return pseudo.OnlyType(
|
||||||
|
pseudo.From(e.NewForRules(c, r, c.extra.Interface().(*C), blockNum, isMerge, timestamp)),
|
||||||
|
)
|
||||||
|
}
|
||||||
250
params/config.libevm_test.go
Normal file
250
params/config.libevm_test.go
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
package params
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/libevm/pseudo"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testOnlyClearRegisteredExtras() {
|
||||||
|
registeredExtras = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRegisterExtras() {
|
||||||
|
type (
|
||||||
|
chainConfigExtra struct {
|
||||||
|
Foo string `json:"foo"`
|
||||||
|
}
|
||||||
|
rulesExtra struct {
|
||||||
|
FooCopy string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// In practice, this would be called inside an init() func.
|
||||||
|
RegisterExtras(Extras[chainConfigExtra, rulesExtra]{
|
||||||
|
NewForRules: func(cc *ChainConfig, r *Rules, cEx *chainConfigExtra, blockNum *big.Int, isMerge bool, timestamp uint64) *rulesExtra {
|
||||||
|
// This function is called at the end of ChainConfig.Rules(),
|
||||||
|
// receiving a pointer to the Rules that will be returned. It MAY
|
||||||
|
// modify the Rules but MUST NOT modify the ChainConfig. The value
|
||||||
|
// that it returns will be available via Rules.ExtraPayload().
|
||||||
|
return &rulesExtra{
|
||||||
|
FooCopy: fmt.Sprintf("copy of: %q", cEx.Foo),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer testOnlyClearRegisteredExtras()
|
||||||
|
|
||||||
|
// ChainConfig now unmarshals any JSON field named "extra" into a pointer to
|
||||||
|
// the registered type, which is available via the ExtraPayload() method.
|
||||||
|
buf := []byte(`{
|
||||||
|
"chainId": 1234,
|
||||||
|
"extra": {
|
||||||
|
"foo": "hello, world"
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
var config ChainConfig
|
||||||
|
if err := json.Unmarshal(buf, &config); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(config.ChainID)
|
||||||
|
// The values returned by ExtraPayload() are guaranteed to be pointers to
|
||||||
|
// the registered types. They MAY, however, be nil pointers. In practice,
|
||||||
|
// callers SHOULD abstract the type assertion in a reusable function to
|
||||||
|
// provide a seamless devex.
|
||||||
|
ccExtra := config.ExtraPayload().Interface().(*chainConfigExtra)
|
||||||
|
rules := config.Rules(nil, false, 0)
|
||||||
|
rExtra := rules.ExtraPayload().Interface().(*rulesExtra)
|
||||||
|
|
||||||
|
if ccExtra != nil {
|
||||||
|
fmt.Println(ccExtra.Foo)
|
||||||
|
}
|
||||||
|
if rExtra != nil {
|
||||||
|
fmt.Println(rExtra.FooCopy)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// 1234
|
||||||
|
// hello, world
|
||||||
|
// copy of: "hello, world"
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleChainConfig_ExtraPayload() {
|
||||||
|
type (
|
||||||
|
chainConfigExtra struct{}
|
||||||
|
rulesExtra struct{}
|
||||||
|
)
|
||||||
|
// Typically called in an `init()` function.
|
||||||
|
RegisterExtras(Extras[chainConfigExtra, rulesExtra]{ /*...*/ })
|
||||||
|
defer testOnlyClearRegisteredExtras()
|
||||||
|
|
||||||
|
var c ChainConfig // Sourced from elsewhere, typically unmarshalled from JSON.
|
||||||
|
|
||||||
|
// Both ChainConfig.ExtraPayload() and Rules.ExtraPayload() return `any`
|
||||||
|
// that are guaranteed to be pointers to the registered types.
|
||||||
|
extra := c.ExtraPayload().Interface().(*chainConfigExtra)
|
||||||
|
|
||||||
|
// Act on the extra payload...
|
||||||
|
if extra != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type rawJSON struct {
|
||||||
|
json.RawMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ json.Unmarshaler = (*rawJSON)(nil)
|
||||||
|
_ json.Marshaler = (*rawJSON)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRegisterExtras(t *testing.T) {
|
||||||
|
type (
|
||||||
|
ccExtraA struct {
|
||||||
|
A string `json:"a"`
|
||||||
|
}
|
||||||
|
rulesExtraA struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
ccExtraB struct {
|
||||||
|
B string `json:"b"`
|
||||||
|
}
|
||||||
|
rulesExtraB struct {
|
||||||
|
B string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
register func()
|
||||||
|
ccExtra *pseudo.Type
|
||||||
|
wantRulesExtra any
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Rules payload copied from ChainConfig payload",
|
||||||
|
register: func() {
|
||||||
|
RegisterExtras(Extras[ccExtraA, rulesExtraA]{
|
||||||
|
NewForRules: func(cc *ChainConfig, r *Rules, ex *ccExtraA, _ *big.Int, _ bool, _ uint64) *rulesExtraA {
|
||||||
|
return &rulesExtraA{
|
||||||
|
A: ex.A,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
ccExtra: pseudo.OnlyType(pseudo.From(&ccExtraA{
|
||||||
|
A: "hello",
|
||||||
|
})),
|
||||||
|
wantRulesExtra: &rulesExtraA{
|
||||||
|
A: "hello",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no NewForRules() function results in typed but nil pointer",
|
||||||
|
register: func() {
|
||||||
|
RegisterExtras(Extras[ccExtraB, rulesExtraB]{})
|
||||||
|
},
|
||||||
|
ccExtra: pseudo.OnlyType(pseudo.From(&ccExtraB{
|
||||||
|
B: "world",
|
||||||
|
})),
|
||||||
|
wantRulesExtra: (*rulesExtraB)(nil),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom JSON handling honoured",
|
||||||
|
register: func() {
|
||||||
|
RegisterExtras(Extras[rawJSON, struct{}]{})
|
||||||
|
},
|
||||||
|
ccExtra: pseudo.OnlyType(pseudo.From(&rawJSON{
|
||||||
|
RawMessage: []byte(`"hello, world"`),
|
||||||
|
})),
|
||||||
|
wantRulesExtra: (*struct{})(nil),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.register()
|
||||||
|
defer testOnlyClearRegisteredExtras()
|
||||||
|
|
||||||
|
in := &ChainConfig{
|
||||||
|
ChainID: big.NewInt(142857),
|
||||||
|
extra: tt.ccExtra,
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := json.Marshal(in)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got := new(ChainConfig)
|
||||||
|
require.NoError(t, json.Unmarshal(buf, got))
|
||||||
|
assert.Equal(t, tt.ccExtra.Interface(), got.ExtraPayload().Interface())
|
||||||
|
assert.Equal(t, in, got)
|
||||||
|
// TODO: do we need an explicit test of the JSON output, or is a
|
||||||
|
// Marshal-Unmarshal round trip sufficient?
|
||||||
|
|
||||||
|
gotRules := got.Rules(nil, false, 0)
|
||||||
|
assert.Equal(t, tt.wantRulesExtra, gotRules.ExtraPayload().Interface())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtrasPanic(t *testing.T) {
|
||||||
|
assertPanics(
|
||||||
|
t, func() {
|
||||||
|
RegisterExtras(Extras[int, struct{}]{})
|
||||||
|
},
|
||||||
|
notStructMessage[int](),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertPanics(
|
||||||
|
t, func() {
|
||||||
|
RegisterExtras(Extras[struct{}, bool]{})
|
||||||
|
},
|
||||||
|
notStructMessage[bool](),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertPanics(
|
||||||
|
t, func() {
|
||||||
|
new(ChainConfig).ExtraPayload()
|
||||||
|
},
|
||||||
|
"before RegisterExtras",
|
||||||
|
)
|
||||||
|
|
||||||
|
assertPanics(
|
||||||
|
t, func() {
|
||||||
|
new(Rules).ExtraPayload()
|
||||||
|
},
|
||||||
|
"before RegisterExtras",
|
||||||
|
)
|
||||||
|
|
||||||
|
RegisterExtras(Extras[struct{}, struct{}]{})
|
||||||
|
defer testOnlyClearRegisteredExtras()
|
||||||
|
|
||||||
|
assertPanics(
|
||||||
|
t, func() {
|
||||||
|
RegisterExtras(Extras[struct{}, struct{}]{})
|
||||||
|
},
|
||||||
|
"re-registration",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertPanics(t *testing.T, fn func(), wantContains string) {
|
||||||
|
t.Helper()
|
||||||
|
defer func() {
|
||||||
|
switch r := recover().(type) {
|
||||||
|
case nil:
|
||||||
|
t.Error("function did not panic as expected")
|
||||||
|
case string:
|
||||||
|
assert.Contains(t, r, wantContains)
|
||||||
|
default:
|
||||||
|
t.Fatalf("BAD TEST SETUP: recover() got unsupported type %T", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fn()
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue