feat: params.ExtraPayloads.SetOnChainConfig() + Rules equiv (#14)

* feat: `params.ExtraPayloadGetter.SetOnChainConfig()` + `Rules` equiv

* refactor: rename `ExtraPayloadsGetter` to `ExtraPayloads`
This commit is contained in:
Arran Schlosberg 2024-09-12 19:51:00 +01:00 committed by GitHub
parent d31803a0ee
commit a574ae6ef0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 54 additions and 42 deletions

View file

@ -47,27 +47,27 @@ type Extras[C ChainConfigHooks, R RulesHooks] struct {
// Calls to [ChainConfig.Rules] will call the `NewRules` function of the // Calls to [ChainConfig.Rules] will call the `NewRules` function of the
// registered [Extras] to create a new `R`. // registered [Extras] to create a new `R`.
// //
// The payloads can be accessed via the [ExtraPayloadGetter.FromChainConfig] and // The payloads can be accessed via the [ExtraPayloads.FromChainConfig] and
// [ExtraPayloadGetter.FromRules] methods of the getter returned by // [ExtraPayloads.FromRules] methods of the accessor returned by RegisterExtras.
// RegisterExtras. Where stated in the interface definitions, they will also be // Where stated in the interface definitions, they will also be used as hooks to
// used as hooks to alter Ethereum behaviour; if this isn't desired then they // alter Ethereum behaviour; if this isn't desired then they can embed
// can embed [NOOPHooks] to satisfy either interface. // [NOOPHooks] to satisfy either interface.
func RegisterExtras[C ChainConfigHooks, R RulesHooks](e Extras[C, R]) ExtraPayloadGetter[C, R] { func RegisterExtras[C ChainConfigHooks, R RulesHooks](e Extras[C, R]) ExtraPayloads[C, R] {
if registeredExtras != nil { if registeredExtras != nil {
panic("re-registration of Extras") panic("re-registration of Extras")
} }
mustBeStructOrPointerToOne[C]() mustBeStructOrPointerToOne[C]()
mustBeStructOrPointerToOne[R]() mustBeStructOrPointerToOne[R]()
getter := e.getter() payloads := e.payloads()
registeredExtras = &extraConstructors{ registeredExtras = &extraConstructors{
newChainConfig: pseudo.NewConstructor[C]().Zero, newChainConfig: pseudo.NewConstructor[C]().Zero,
newRules: pseudo.NewConstructor[R]().Zero, newRules: pseudo.NewConstructor[R]().Zero,
reuseJSONRoot: e.ReuseJSONRoot, reuseJSONRoot: e.ReuseJSONRoot,
newForRules: e.newForRules, newForRules: e.newForRules,
getter: getter, payloads: payloads,
} }
return getter return payloads
} }
// TestOnlyClearRegisteredExtras clears the [Extras] previously passed to // TestOnlyClearRegisteredExtras clears the [Extras] previously passed to
@ -102,7 +102,7 @@ type extraConstructors struct {
newForRules func(_ *ChainConfig, _ *Rules, blockNum *big.Int, isMerge bool, timestamp uint64) *pseudo.Type newForRules func(_ *ChainConfig, _ *Rules, blockNum *big.Int, isMerge bool, timestamp uint64) *pseudo.Type
// use top-level hooksFrom<X>() functions instead of these as they handle // use top-level hooksFrom<X>() functions instead of these as they handle
// instances where no [Extras] were registered. // instances where no [Extras] were registered.
getter interface { payloads interface {
hooksFromChainConfig(*ChainConfig) ChainConfigHooks hooksFromChainConfig(*ChainConfig) ChainConfigHooks
hooksFromRules(*Rules) RulesHooks hooksFromRules(*Rules) RulesHooks
} }
@ -112,11 +112,11 @@ func (e *Extras[C, R]) newForRules(c *ChainConfig, r *Rules, blockNum *big.Int,
if e.NewRules == nil { if e.NewRules == nil {
return registeredExtras.newRules() return registeredExtras.newRules()
} }
rExtra := e.NewRules(c, r, e.getter().FromChainConfig(c), blockNum, isMerge, timestamp) rExtra := e.NewRules(c, r, e.payloads().FromChainConfig(c), blockNum, isMerge, timestamp)
return pseudo.From(rExtra).Type return pseudo.From(rExtra).Type
} }
func (*Extras[C, R]) getter() (g ExtraPayloadGetter[C, R]) { return } func (*Extras[C, R]) payloads() (g ExtraPayloads[C, R]) { return }
// mustBeStructOrPointerToOne panics if `T` isn't a struct or a *struct. // mustBeStructOrPointerToOne panics if `T` isn't a struct or a *struct.
func mustBeStructOrPointerToOne[T any]() { func mustBeStructOrPointerToOne[T any]() {
@ -140,44 +140,56 @@ func notStructMessage[T any]() string {
return fmt.Sprintf("%T is not a struct nor a pointer to a struct", x) return fmt.Sprintf("%T is not a struct nor a pointer to a struct", x)
} }
// An ExtraPayloadGettter provides strongly typed access to the extra payloads // ExtraPayloads provides strongly typed access to the extra payloads carried by
// carried by [ChainConfig] and [Rules] structs. The only valid way to construct // [ChainConfig] and [Rules] structs. The only valid way to construct an
// a getter is by a call to [RegisterExtras]. // instance is by a call to [RegisterExtras].
type ExtraPayloadGetter[C ChainConfigHooks, R RulesHooks] struct { type ExtraPayloads[C ChainConfigHooks, R RulesHooks] struct {
_ struct{} // make godoc show unexported fields so nobody tries to make their own getter ;) _ struct{} // make godoc show unexported fields so nobody tries to make their own instance ;)
} }
// FromChainConfig returns the ChainConfig's extra payload. // FromChainConfig returns the ChainConfig's extra payload.
func (ExtraPayloadGetter[C, R]) FromChainConfig(c *ChainConfig) C { func (ExtraPayloads[C, R]) FromChainConfig(c *ChainConfig) C {
return pseudo.MustNewValue[C](c.extraPayload()).Get() return pseudo.MustNewValue[C](c.extraPayload()).Get()
} }
// PointerFromChainConfig returns a pointer to the ChainConfig's extra payload. // PointerFromChainConfig returns a pointer to the ChainConfig's extra payload.
// This is guaranteed to be non-nil. // This is guaranteed to be non-nil.
func (ExtraPayloadGetter[C, R]) PointerFromChainConfig(c *ChainConfig) *C { func (ExtraPayloads[C, R]) PointerFromChainConfig(c *ChainConfig) *C {
return pseudo.MustPointerTo[C](c.extraPayload()).Value.Get() return pseudo.MustPointerTo[C](c.extraPayload()).Value.Get()
} }
// SetOnChainConfig sets the ChainConfig's extra payload. It is equivalent to
// `*e.PointerFromChainConfig(cc) = val`.
func (e ExtraPayloads[C, R]) SetOnChainConfig(cc *ChainConfig, val C) {
*e.PointerFromChainConfig(cc) = val
}
// hooksFromChainConfig is equivalent to FromChainConfig(), but returns an // hooksFromChainConfig is equivalent to FromChainConfig(), but returns an
// interface instead of the concrete type implementing it; this allows it to be // interface instead of the concrete type implementing it; this allows it to be
// used in non-generic code. // used in non-generic code.
func (e ExtraPayloadGetter[C, R]) hooksFromChainConfig(c *ChainConfig) ChainConfigHooks { func (e ExtraPayloads[C, R]) hooksFromChainConfig(c *ChainConfig) ChainConfigHooks {
return e.FromChainConfig(c) return e.FromChainConfig(c)
} }
// FromRules returns the Rules' extra payload. // FromRules returns the Rules' extra payload.
func (ExtraPayloadGetter[C, R]) FromRules(r *Rules) R { func (ExtraPayloads[C, R]) FromRules(r *Rules) R {
return pseudo.MustNewValue[R](r.extraPayload()).Get() return pseudo.MustNewValue[R](r.extraPayload()).Get()
} }
// PointerFromRules returns a pointer to the Rules's extra payload. This is // PointerFromRules returns a pointer to the Rules's extra payload. This is
// guaranteed to be non-nil. // guaranteed to be non-nil.
func (ExtraPayloadGetter[C, R]) PointerFromRules(r *Rules) *R { func (ExtraPayloads[C, R]) PointerFromRules(r *Rules) *R {
return pseudo.MustPointerTo[R](r.extraPayload()).Value.Get() return pseudo.MustPointerTo[R](r.extraPayload()).Value.Get()
} }
// SetOnRules sets the Rules' extra payload. It is equivalent to
// `*e.PointerFromRules(r) = val`.
func (e ExtraPayloads[C, R]) SetOnRules(r *Rules, val R) {
*e.PointerFromRules(r) = val
}
// hooksFromRules is the [RulesHooks] equivalent of hooksFromChainConfig(). // hooksFromRules is the [RulesHooks] equivalent of hooksFromChainConfig().
func (e ExtraPayloadGetter[C, R]) hooksFromRules(r *Rules) RulesHooks { func (e ExtraPayloads[C, R]) hooksFromRules(r *Rules) RulesHooks {
return e.FromRules(r) return e.FromRules(r)
} }
@ -195,7 +207,7 @@ func (c *ChainConfig) addRulesExtra(r *Rules, blockNum *big.Int, isMerge bool, t
// unmarshalling of JSON), a nil value is constructed and returned. // unmarshalling of JSON), a nil value is constructed and returned.
func (c *ChainConfig) extraPayload() *pseudo.Type { func (c *ChainConfig) extraPayload() *pseudo.Type {
if registeredExtras == nil { if registeredExtras == nil {
// This will only happen if someone constructs an [ExtraPayloadGetter] // This will only happen if someone constructs an [ExtraPayloads]
// directly, without a call to [RegisterExtras]. // directly, without a call to [RegisterExtras].
// //
// See https://google.github.io/styleguide/go/best-practices#when-to-panic // See https://google.github.io/styleguide/go/best-practices#when-to-panic

View file

@ -135,7 +135,7 @@ func TestModificationOfZeroExtras(t *testing.T) {
TestOnlyClearRegisteredExtras() TestOnlyClearRegisteredExtras()
t.Cleanup(TestOnlyClearRegisteredExtras) t.Cleanup(TestOnlyClearRegisteredExtras)
getter := RegisterExtras(Extras[ccExtra, rulesExtra]{}) extras := RegisterExtras(Extras[ccExtra, rulesExtra]{})
config := new(ChainConfig) config := new(ChainConfig)
rules := new(Rules) rules := new(Rules)
@ -143,30 +143,30 @@ func TestModificationOfZeroExtras(t *testing.T) {
// closure is demonstrably over the original zero values. // closure is demonstrably over the original zero values.
assertChainConfigExtra := func(t *testing.T, want ccExtra, msg string) { assertChainConfigExtra := func(t *testing.T, want ccExtra, msg string) {
t.Helper() t.Helper()
assert.Equalf(t, want, getter.FromChainConfig(config), "%T: "+msg, &config) assert.Equalf(t, want, extras.FromChainConfig(config), "%T: "+msg, &config)
} }
assertRulesExtra := func(t *testing.T, want rulesExtra, msg string) { assertRulesExtra := func(t *testing.T, want rulesExtra, msg string) {
t.Helper() t.Helper()
assert.Equalf(t, want, getter.FromRules(rules), "%T: "+msg, &rules) assert.Equalf(t, want, extras.FromRules(rules), "%T: "+msg, &rules)
} }
assertChainConfigExtra(t, ccExtra{}, "zero value") assertChainConfigExtra(t, ccExtra{}, "zero value")
assertRulesExtra(t, rulesExtra{}, "zero value") assertRulesExtra(t, rulesExtra{}, "zero value")
const answer = 42 const answer = 42
getter.PointerFromChainConfig(config).X = answer extras.PointerFromChainConfig(config).X = answer
assertChainConfigExtra(t, ccExtra{X: answer}, "after setting via pointer field") assertChainConfigExtra(t, ccExtra{X: answer}, "after setting via pointer field")
const pi = 314159 const pi = 314159
getter.PointerFromRules(rules).X = pi extras.PointerFromRules(rules).X = pi
assertRulesExtra(t, rulesExtra{X: pi}, "after setting via pointer field") assertRulesExtra(t, rulesExtra{X: pi}, "after setting via pointer field")
ccReplace := ccExtra{X: 142857} ccReplace := ccExtra{X: 142857}
*getter.PointerFromChainConfig(config) = ccReplace extras.SetOnChainConfig(config, ccReplace)
assertChainConfigExtra(t, ccReplace, "after replacement of entire extra via `*pointer = x`") assertChainConfigExtra(t, ccReplace, "after replacement of entire extra via `*pointer = x`")
rulesReplace := rulesExtra{X: 18101986} rulesReplace := rulesExtra{X: 18101986}
*getter.PointerFromRules(rules) = rulesReplace extras.SetOnRules(rules, rulesReplace)
assertRulesExtra(t, rulesReplace, "after replacement of entire extra via `*pointer = x`") assertRulesExtra(t, rulesReplace, "after replacement of entire extra via `*pointer = x`")
if t.Failed() { if t.Failed() {
@ -177,15 +177,15 @@ func TestModificationOfZeroExtras(t *testing.T) {
ccCopy := *config ccCopy := *config
rCopy := *rules rCopy := *rules
assert.Equal(t, getter.FromChainConfig(&ccCopy), ccReplace, "ChainConfig extras copied") assert.Equal(t, extras.FromChainConfig(&ccCopy), ccReplace, "ChainConfig extras copied")
assert.Equal(t, getter.FromRules(&rCopy), rulesReplace, "Rules extras copied") assert.Equal(t, extras.FromRules(&rCopy), rulesReplace, "Rules extras copied")
const seqUp = 123456789 const seqUp = 123456789
getter.PointerFromChainConfig(&ccCopy).X = seqUp extras.PointerFromChainConfig(&ccCopy).X = seqUp
assertChainConfigExtra(t, ccExtra{X: seqUp}, "original changed because copy only shallow") assertChainConfigExtra(t, ccExtra{X: seqUp}, "original changed because copy only shallow")
const seqDown = 987654321 const seqDown = 987654321
getter.PointerFromRules(&rCopy).X = seqDown extras.PointerFromRules(&rCopy).X = seqDown
assertRulesExtra(t, rulesExtra{X: seqDown}, "original changed because copy only shallow") assertRulesExtra(t, rulesExtra{X: seqDown}, "original changed because copy only shallow")
}) })
} }

View file

@ -30,12 +30,12 @@ func initFn() {
// This registration makes *all* [params.ChainConfig] and [params.Rules] // This registration makes *all* [params.ChainConfig] and [params.Rules]
// instances respect the payload types. They do not need to be modified to // instances respect the payload types. They do not need to be modified to
// know about `extraparams`. // know about `extraparams`.
getter = params.RegisterExtras(params.Extras[ChainConfigExtra, RulesExtra]{ payloads = params.RegisterExtras(params.Extras[ChainConfigExtra, RulesExtra]{
NewRules: constructRulesExtra, NewRules: constructRulesExtra,
}) })
} }
var getter params.ExtraPayloadGetter[ChainConfigExtra, RulesExtra] var payloads params.ExtraPayloads[ChainConfigExtra, RulesExtra]
// constructRulesExtra acts as an adjunct to the [params.ChainConfig.Rules] // constructRulesExtra acts as an adjunct to the [params.ChainConfig.Rules]
// method. Its primary purpose is to construct the extra payload for the // method. Its primary purpose is to construct the extra payload for the
@ -67,12 +67,12 @@ type RulesExtra struct {
// FromChainConfig returns the extra payload carried by the ChainConfig. // FromChainConfig returns the extra payload carried by the ChainConfig.
func FromChainConfig(c *params.ChainConfig) ChainConfigExtra { func FromChainConfig(c *params.ChainConfig) ChainConfigExtra {
return getter.FromChainConfig(c) return payloads.FromChainConfig(c)
} }
// FromRules returns the extra payload carried by the Rules. // FromRules returns the extra payload carried by the Rules.
func FromRules(r *params.Rules) RulesExtra { func FromRules(r *params.Rules) RulesExtra {
return getter.FromRules(r) return payloads.FromRules(r)
} }
// myForkPrecompiledContracts is analogous to the vm.PrecompiledContracts<Fork> // myForkPrecompiledContracts is analogous to the vm.PrecompiledContracts<Fork>
@ -114,7 +114,7 @@ func (r RulesExtra) CanCreateContract(*libevm.AddressContext, libevm.StateReader
// This example demonstrates how the rest of this file would be used from a // This example demonstrates how the rest of this file would be used from a
// *different* package. // *different* package.
func ExampleExtraPayloadGetter() { func ExampleExtraPayloads() {
initFn() // Outside of an example this is unnecessary as the function will be a regular init(). initFn() // Outside of an example this is unnecessary as the function will be a regular init().
const forkTime = 530003640 const forkTime = 530003640

View file

@ -40,7 +40,7 @@ type RulesAllowlistHooks interface {
// none were registered. // none were registered.
func (c *ChainConfig) Hooks() ChainConfigHooks { func (c *ChainConfig) Hooks() ChainConfigHooks {
if e := registeredExtras; e != nil { if e := registeredExtras; e != nil {
return e.getter.hooksFromChainConfig(c) return e.payloads.hooksFromChainConfig(c)
} }
return NOOPHooks{} return NOOPHooks{}
} }
@ -49,7 +49,7 @@ func (c *ChainConfig) Hooks() ChainConfigHooks {
// none were registered. // none were registered.
func (r *Rules) Hooks() RulesHooks { func (r *Rules) Hooks() RulesHooks {
if e := registeredExtras; e != nil { if e := registeredExtras; e != nil {
return e.getter.hooksFromRules(r) return e.payloads.hooksFromRules(r)
} }
return NOOPHooks{} return NOOPHooks{}
} }