mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 02:12:23 +00:00
251 lines
5.7 KiB
Go
251 lines
5.7 KiB
Go
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 and the `getter`
|
|
// used to access the ExtraPayload() values in a type-safe way.
|
|
getter := 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"
|
|
}
|
|
}`)
|
|
|
|
config := new(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 := getter.FromChainConfig(config)
|
|
rules := config.Rules(nil, false, 0)
|
|
rExtra := getter.FromRules(&rules)
|
|
|
|
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.
|
|
getter := 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 := getter.FromChainConfig(&c)
|
|
|
|
// 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()
|
|
}
|