feat: params.ExtraPayloadGetter for end-user type safety

This commit is contained in:
Arran Schlosberg 2024-08-22 17:40:15 +01:00
parent bc6da7c964
commit 0bd2bfc667
No known key found for this signature in database
GPG key ID: 8A30F7E4344B4EF3
5 changed files with 32 additions and 16 deletions

View file

@ -3,12 +3,13 @@ package extraparams
import (
"math/big"
"github.com/ethereum/go-ethereum/libevm/pseudo"
"github.com/ethereum/go-ethereum/params"
)
var getter params.ExtraPayloadGetter[ChainConfigExtra, RulesExtra]
func init() {
params.RegisterExtras(params.Extras[ChainConfigExtra, RulesExtra]{
getter = params.RegisterExtras(params.Extras[ChainConfigExtra, RulesExtra]{
NewForRules: constructRulesExtra,
})
}
@ -28,9 +29,9 @@ func constructRulesExtra(c *params.ChainConfig, r *params.Rules, cEx *ChainConfi
}
func FromChainConfig(c *params.ChainConfig) *ChainConfigExtra {
return pseudo.NewValueUnsafe[*ChainConfigExtra](c.ExtraPayload()).Get()
return getter.FromChainConfig(c)
}
func FromRules(r *params.Rules) *RulesExtra {
return pseudo.NewValueUnsafe[*RulesExtra](r.ExtraPayload()).Get()
return getter.FromRules(r)
}

View file

@ -9,5 +9,5 @@ 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
golang.org/x/sys v0.22.0 // indirect
)

View file

@ -8,7 +8,7 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
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=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.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=

View file

@ -41,13 +41,27 @@ type Extras[C any, R any] struct {
// 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]) {
func RegisterExtras[C any, R any](e Extras[C, R]) ExtraPayloadGetter[C, R] {
if registeredExtras != nil {
panic("re-registration of Extras")
}
mustBeStruct[C]()
mustBeStruct[R]()
registeredExtras = &e
return ExtraPayloadGetter[C, R]{}
}
// An ExtraPayloadGettter ...
type ExtraPayloadGetter[C any, R any] struct{}
// FromChainConfig ...
func (ExtraPayloadGetter[C, R]) FromChainConfig(c *ChainConfig) *C {
return pseudo.NewValueUnsafe[*C](c.ExtraPayload()).Get()
}
// FromRules ...
func (ExtraPayloadGetter[C, R]) FromRules(r *Rules) *R {
return pseudo.NewValueUnsafe[*R](r.ExtraPayload()).Get()
}
func mustBeStruct[T any]() {

View file

@ -26,8 +26,9 @@ func ExampleRegisterExtras() {
}
)
// In practice, this would be called inside an init() func.
RegisterExtras(Extras[chainConfigExtra, rulesExtra]{
// 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
@ -49,8 +50,8 @@ func ExampleRegisterExtras() {
}
}`)
var config ChainConfig
if err := json.Unmarshal(buf, &config); err != nil {
config := new(ChainConfig)
if err := json.Unmarshal(buf, config); err != nil {
log.Fatal(err)
}
@ -59,9 +60,9 @@ func ExampleRegisterExtras() {
// 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)
ccExtra := getter.FromChainConfig(config)
rules := config.Rules(nil, false, 0)
rExtra := rules.ExtraPayload().Interface().(*rulesExtra)
rExtra := getter.FromRules(&rules)
if ccExtra != nil {
fmt.Println(ccExtra.Foo)
@ -82,14 +83,14 @@ func ExampleChainConfig_ExtraPayload() {
rulesExtra struct{}
)
// Typically called in an `init()` function.
RegisterExtras(Extras[chainConfigExtra, rulesExtra]{ /*...*/ })
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 := c.ExtraPayload().Interface().(*chainConfigExtra)
extra := getter.FromChainConfig(&c)
// Act on the extra payload...
if extra != nil {