Skip to content

Commit

Permalink
feat: params.ExtraPayloads.SetOnChainConfig() + Rules equiv (ethe…
Browse files Browse the repository at this point in the history
…reum#14)

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

* refactor: rename `ExtraPayloadsGetter` to `ExtraPayloads`
  • Loading branch information
ARR4N authored Sep 12, 2024
1 parent d31803a commit a574ae6
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 42 deletions.
60 changes: 36 additions & 24 deletions params/config.libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,27 @@ type Extras[C ChainConfigHooks, R RulesHooks] struct {
// Calls to [ChainConfig.Rules] will call the `NewRules` function of the
// registered [Extras] to create a new `R`.
//
// The payloads can be accessed via the [ExtraPayloadGetter.FromChainConfig] and
// [ExtraPayloadGetter.FromRules] methods of the getter returned by
// RegisterExtras. Where stated in the interface definitions, they will also be
// used as hooks to alter Ethereum behaviour; if this isn't desired then they
// can embed [NOOPHooks] to satisfy either interface.
func RegisterExtras[C ChainConfigHooks, R RulesHooks](e Extras[C, R]) ExtraPayloadGetter[C, R] {
// The payloads can be accessed via the [ExtraPayloads.FromChainConfig] and
// [ExtraPayloads.FromRules] methods of the accessor returned by RegisterExtras.
// Where stated in the interface definitions, they will also be used as hooks to
// alter Ethereum behaviour; if this isn't desired then they can embed
// [NOOPHooks] to satisfy either interface.
func RegisterExtras[C ChainConfigHooks, R RulesHooks](e Extras[C, R]) ExtraPayloads[C, R] {
if registeredExtras != nil {
panic("re-registration of Extras")
}
mustBeStructOrPointerToOne[C]()
mustBeStructOrPointerToOne[R]()

getter := e.getter()
payloads := e.payloads()
registeredExtras = &extraConstructors{
newChainConfig: pseudo.NewConstructor[C]().Zero,
newRules: pseudo.NewConstructor[R]().Zero,
reuseJSONRoot: e.ReuseJSONRoot,
newForRules: e.newForRules,
getter: getter,
payloads: payloads,
}
return getter
return payloads
}

// TestOnlyClearRegisteredExtras clears the [Extras] previously passed to
Expand Down Expand Up @@ -102,7 +102,7 @@ type extraConstructors struct {
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
// instances where no [Extras] were registered.
getter interface {
payloads interface {
hooksFromChainConfig(*ChainConfig) ChainConfigHooks
hooksFromRules(*Rules) RulesHooks
}
Expand All @@ -112,11 +112,11 @@ func (e *Extras[C, R]) newForRules(c *ChainConfig, r *Rules, blockNum *big.Int,
if e.NewRules == nil {
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
}

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.
func mustBeStructOrPointerToOne[T any]() {
Expand All @@ -140,44 +140,56 @@ func notStructMessage[T any]() string {
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
// carried by [ChainConfig] and [Rules] structs. The only valid way to construct
// a getter is by a call to [RegisterExtras].
type ExtraPayloadGetter[C ChainConfigHooks, R RulesHooks] struct {
_ struct{} // make godoc show unexported fields so nobody tries to make their own getter ;)
// ExtraPayloads provides strongly typed access to the extra payloads carried by
// [ChainConfig] and [Rules] structs. The only valid way to construct an
// instance is by a call to [RegisterExtras].
type ExtraPayloads[C ChainConfigHooks, R RulesHooks] struct {
_ struct{} // make godoc show unexported fields so nobody tries to make their own instance ;)
}

// 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()
}

// PointerFromChainConfig returns a pointer to the ChainConfig's extra payload.
// 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()
}

// 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
// interface instead of the concrete type implementing it; this allows it to be
// 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)
}

// 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()
}

// PointerFromRules returns a pointer to the Rules's extra payload. This is
// 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()
}

// 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().
func (e ExtraPayloadGetter[C, R]) hooksFromRules(r *Rules) RulesHooks {
func (e ExtraPayloads[C, R]) hooksFromRules(r *Rules) RulesHooks {
return e.FromRules(r)
}

Expand All @@ -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.
func (c *ChainConfig) extraPayload() *pseudo.Type {
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].
//
// See https://google.github.io/styleguide/go/best-practices#when-to-panic
Expand Down
22 changes: 11 additions & 11 deletions params/config.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,38 +135,38 @@ func TestModificationOfZeroExtras(t *testing.T) {

TestOnlyClearRegisteredExtras()
t.Cleanup(TestOnlyClearRegisteredExtras)
getter := RegisterExtras(Extras[ccExtra, rulesExtra]{})
extras := RegisterExtras(Extras[ccExtra, rulesExtra]{})

config := new(ChainConfig)
rules := new(Rules)
// These assertion helpers are defined before any modifications so that the
// closure is demonstrably over the original zero values.
assertChainConfigExtra := func(t *testing.T, want ccExtra, msg string) {
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) {
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")
assertRulesExtra(t, rulesExtra{}, "zero value")

const answer = 42
getter.PointerFromChainConfig(config).X = answer
extras.PointerFromChainConfig(config).X = answer
assertChainConfigExtra(t, ccExtra{X: answer}, "after setting via pointer field")

const pi = 314159
getter.PointerFromRules(rules).X = pi
extras.PointerFromRules(rules).X = pi
assertRulesExtra(t, rulesExtra{X: pi}, "after setting via pointer field")

ccReplace := ccExtra{X: 142857}
*getter.PointerFromChainConfig(config) = ccReplace
extras.SetOnChainConfig(config, ccReplace)
assertChainConfigExtra(t, ccReplace, "after replacement of entire extra via `*pointer = x`")

rulesReplace := rulesExtra{X: 18101986}
*getter.PointerFromRules(rules) = rulesReplace
extras.SetOnRules(rules, rulesReplace)
assertRulesExtra(t, rulesReplace, "after replacement of entire extra via `*pointer = x`")

if t.Failed() {
Expand All @@ -177,15 +177,15 @@ func TestModificationOfZeroExtras(t *testing.T) {
ccCopy := *config
rCopy := *rules

assert.Equal(t, getter.FromChainConfig(&ccCopy), ccReplace, "ChainConfig extras copied")
assert.Equal(t, getter.FromRules(&rCopy), rulesReplace, "Rules extras copied")
assert.Equal(t, extras.FromChainConfig(&ccCopy), ccReplace, "ChainConfig extras copied")
assert.Equal(t, extras.FromRules(&rCopy), rulesReplace, "Rules extras copied")

const seqUp = 123456789
getter.PointerFromChainConfig(&ccCopy).X = seqUp
extras.PointerFromChainConfig(&ccCopy).X = seqUp
assertChainConfigExtra(t, ccExtra{X: seqUp}, "original changed because copy only shallow")

const seqDown = 987654321
getter.PointerFromRules(&rCopy).X = seqDown
extras.PointerFromRules(&rCopy).X = seqDown
assertRulesExtra(t, rulesExtra{X: seqDown}, "original changed because copy only shallow")
})
}
Expand Down
10 changes: 5 additions & 5 deletions params/example.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ func initFn() {
// This registration makes *all* [params.ChainConfig] and [params.Rules]
// instances respect the payload types. They do not need to be modified to
// know about `extraparams`.
getter = params.RegisterExtras(params.Extras[ChainConfigExtra, RulesExtra]{
payloads = params.RegisterExtras(params.Extras[ChainConfigExtra, RulesExtra]{
NewRules: constructRulesExtra,
})
}

var getter params.ExtraPayloadGetter[ChainConfigExtra, RulesExtra]
var payloads params.ExtraPayloads[ChainConfigExtra, RulesExtra]

// constructRulesExtra acts as an adjunct to the [params.ChainConfig.Rules]
// method. Its primary purpose is to construct the extra payload for the
Expand Down Expand Up @@ -67,12 +67,12 @@ type RulesExtra struct {

// FromChainConfig returns the extra payload carried by the ChainConfig.
func FromChainConfig(c *params.ChainConfig) ChainConfigExtra {
return getter.FromChainConfig(c)
return payloads.FromChainConfig(c)
}

// FromRules returns the extra payload carried by the Rules.
func FromRules(r *params.Rules) RulesExtra {
return getter.FromRules(r)
return payloads.FromRules(r)
}

// myForkPrecompiledContracts is analogous to the vm.PrecompiledContracts<Fork>
Expand Down Expand Up @@ -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
// *different* package.
func ExampleExtraPayloadGetter() {
func ExampleExtraPayloads() {
initFn() // Outside of an example this is unnecessary as the function will be a regular init().

const forkTime = 530003640
Expand Down
4 changes: 2 additions & 2 deletions params/hooks.libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type RulesAllowlistHooks interface {
// none were registered.
func (c *ChainConfig) Hooks() ChainConfigHooks {
if e := registeredExtras; e != nil {
return e.getter.hooksFromChainConfig(c)
return e.payloads.hooksFromChainConfig(c)
}
return NOOPHooks{}
}
Expand All @@ -49,7 +49,7 @@ func (c *ChainConfig) Hooks() ChainConfigHooks {
// none were registered.
func (r *Rules) Hooks() RulesHooks {
if e := registeredExtras; e != nil {
return e.getter.hooksFromRules(r)
return e.payloads.hooksFromRules(r)
}
return NOOPHooks{}
}
Expand Down

0 comments on commit a574ae6

Please sign in to comment.