Skip to content

Commit c70b3e3

Browse files
ARR4NDarioush Jalali
andauthored
feat: CheckConfig{Compatible,ForkOrder} + Description hooks (#29)
* feat: `CheckConfig{Compatible,ForkOrder}` + `Description` hooks * doc: comments on `NOOPHooks` methods * test: all new hooks * chore: `gci` * doc: fix `hookstest.Stub.Description` comment Co-authored-by: Darioush Jalali <[email protected]> Signed-off-by: Arran Schlosberg <[email protected]> --------- Signed-off-by: Arran Schlosberg <[email protected]> Co-authored-by: Darioush Jalali <[email protected]>
1 parent ab357e0 commit c70b3e3

File tree

5 files changed

+123
-11
lines changed

5 files changed

+123
-11
lines changed

libevm/hookstest/stub.go

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,30 @@ import (
1414
// Register clears any registered [params.Extras] and then registers `extras`
1515
// for the lifetime of the current test, clearing them via tb's
1616
// [testing.TB.Cleanup].
17-
func Register[C params.ChainConfigHooks, R params.RulesHooks](tb testing.TB, extras params.Extras[C, R]) {
17+
func Register[C params.ChainConfigHooks, R params.RulesHooks](tb testing.TB, extras params.Extras[C, R]) params.ExtraPayloads[C, R] {
1818
tb.Helper()
1919
params.TestOnlyClearRegisteredExtras()
2020
tb.Cleanup(params.TestOnlyClearRegisteredExtras)
21-
params.RegisterExtras(extras)
21+
return params.RegisterExtras(extras)
2222
}
2323

2424
// A Stub is a test double for [params.ChainConfigHooks] and
2525
// [params.RulesHooks]. Each of the fields, if non-nil, back their respective
2626
// hook methods, which otherwise fall back to the default behaviour.
2727
type Stub struct {
28+
CheckConfigForkOrderFn func() error
29+
CheckConfigCompatibleFn func(*params.ChainConfig, *big.Int, uint64) *params.ConfigCompatError
30+
DescriptionSuffix string
2831
PrecompileOverrides map[common.Address]libevm.PrecompiledContract
2932
CanExecuteTransactionFn func(common.Address, *common.Address, libevm.StateReader) error
3033
CanCreateContractFn func(*libevm.AddressContext, uint64, libevm.StateReader) (uint64, error)
3134
}
3235

3336
// Register is a convenience wrapper for registering s as both the
3437
// [params.ChainConfigHooks] and [params.RulesHooks] via [Register].
35-
func (s *Stub) Register(tb testing.TB) {
38+
func (s *Stub) Register(tb testing.TB) params.ExtraPayloads[*Stub, *Stub] {
3639
tb.Helper()
37-
Register(tb, params.Extras[*Stub, *Stub]{
40+
return Register(tb, params.Extras[*Stub, *Stub]{
3841
NewRules: func(_ *params.ChainConfig, _ *params.Rules, _ *Stub, blockNum *big.Int, isMerge bool, timestamp uint64) *Stub {
3942
return s
4043
},
@@ -52,6 +55,29 @@ func (s Stub) PrecompileOverride(a common.Address) (libevm.PrecompiledContract,
5255
return p, ok
5356
}
5457

58+
// CheckConfigForkOrder proxies arguments to the s.CheckConfigForkOrderFn
59+
// function if non-nil, otherwise it acts as a noop.
60+
func (s Stub) CheckConfigForkOrder() error {
61+
if f := s.CheckConfigForkOrderFn; f != nil {
62+
return f()
63+
}
64+
return nil
65+
}
66+
67+
// CheckConfigCompatible proxies arguments to the s.CheckConfigCompatibleFn
68+
// function if non-nil, otherwise it acts as a noop.
69+
func (s Stub) CheckConfigCompatible(newcfg *params.ChainConfig, headNumber *big.Int, headTimestamp uint64) *params.ConfigCompatError {
70+
if f := s.CheckConfigCompatibleFn; f != nil {
71+
return f(newcfg, headNumber, headTimestamp)
72+
}
73+
return nil
74+
}
75+
76+
// Description returns s.DescriptionSuffix.
77+
func (s Stub) Description() string {
78+
return s.DescriptionSuffix
79+
}
80+
5581
// CanExecuteTransaction proxies arguments to the s.CanExecuteTransactionFn
5682
// function if non-nil, otherwise it acts as a noop.
5783
func (s Stub) CanExecuteTransaction(from common.Address, to *common.Address, sr libevm.StateReader) error {

params/config.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ func (c *ChainConfig) Description() string {
478478
if c.VerkleTime != nil {
479479
banner += fmt.Sprintf(" - Verkle: @%-10v\n", *c.VerkleTime)
480480
}
481-
return banner
481+
return banner + c.Hooks().Description()
482482
}
483483

484484
// IsHomestead returns whether num is either equal to the homestead block or greater.
@@ -671,7 +671,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
671671
lastFork = cur
672672
}
673673
}
674-
return nil
674+
return c.Hooks().CheckConfigForkOrder()
675675
}
676676

677677
func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int, headTimestamp uint64) *ConfigCompatError {
@@ -742,7 +742,7 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int,
742742
if isForkTimestampIncompatible(c.VerkleTime, newcfg.VerkleTime, headTimestamp) {
743743
return newTimestampCompatError("Verkle fork timestamp", c.VerkleTime, newcfg.VerkleTime)
744744
}
745-
return nil
745+
return c.Hooks().CheckConfigCompatible(newcfg, headNumber, headTimestamp)
746746
}
747747

748748
// BaseFeeChangeDenominator bounds the amount the base fee can change between blocks.

params/example.libevm_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ func constructRulesExtra(c *params.ChainConfig, r *params.Rules, cEx ChainConfig
5151
// the standard [params.ChainConfig] struct.
5252
type ChainConfigExtra struct {
5353
MyForkTime *uint64 `json:"myForkTime"`
54+
55+
// (Optional) If not all hooks are desirable then embedding a [NOOPHooks]
56+
// allows the type to satisfy the [ChainConfigHooks] interface, resulting in
57+
// default Ethereum behaviour.
58+
params.NOOPHooks
5459
}
5560

5661
// RulesExtra can be any struct. It too mirrors a common pattern in
@@ -59,9 +64,6 @@ type RulesExtra struct {
5964
IsMyFork bool
6065
timestamp uint64
6166

62-
// (Optional) If not all hooks are desirable then embedding a [NOOPHooks]
63-
// allows the type to satisfy the [RulesHooks] interface, resulting in
64-
// default Ethereum behaviour.
6567
params.NOOPHooks
6668
}
6769

params/hooks.libevm.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
package params
22

33
import (
4+
"math/big"
5+
46
"github.com/ethereum/go-ethereum/common"
57
"github.com/ethereum/go-ethereum/libevm"
68
)
79

810
// ChainConfigHooks are required for all types registered as [Extras] for
911
// [ChainConfig] payloads.
10-
type ChainConfigHooks interface{}
12+
type ChainConfigHooks interface {
13+
CheckConfigForkOrder() error
14+
CheckConfigCompatible(newcfg *ChainConfig, headNumber *big.Int, headTimestamp uint64) *ConfigCompatError
15+
Description() string
16+
}
1117

1218
// TODO(arr4n): given the choice of whether a hook should be defined on a
1319
// ChainConfig or on the Rules, what are the guiding principles? A ChainConfig
@@ -68,6 +74,21 @@ var _ interface {
6874
RulesHooks
6975
} = NOOPHooks{}
7076

77+
// CheckConfigForkOrder verifies all (otherwise valid) fork orders.
78+
func (NOOPHooks) CheckConfigForkOrder() error {
79+
return nil
80+
}
81+
82+
// CheckConfigCompatible verifies all (otherwise valid) new configs.
83+
func (NOOPHooks) CheckConfigCompatible(*ChainConfig, *big.Int, uint64) *ConfigCompatError {
84+
return nil
85+
}
86+
87+
// Description returns the empty string.
88+
func (NOOPHooks) Description() string {
89+
return ""
90+
}
91+
7192
// CanExecuteTransaction allows all (otherwise valid) transactions.
7293
func (NOOPHooks) CanExecuteTransaction(_ common.Address, _ *common.Address, _ libevm.StateReader) error {
7394
return nil

params/hooks.libevm_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package params_test
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"math/big"
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/ethereum/go-ethereum/libevm/ethtest"
12+
"github.com/ethereum/go-ethereum/libevm/hookstest"
13+
"github.com/ethereum/go-ethereum/params"
14+
)
15+
16+
func TestChainConfigHooks_Description(t *testing.T) {
17+
const suffix = "Arran was here"
18+
c := new(params.ChainConfig)
19+
want := c.Description() + suffix
20+
21+
hooks := &hookstest.Stub{
22+
DescriptionSuffix: "Arran was here",
23+
}
24+
hooks.Register(t).SetOnChainConfig(c, hooks)
25+
require.Equal(t, want, c.Description(), "ChainConfigHooks.Description() is appended to non-extras equivalent")
26+
}
27+
28+
func TestChainConfigHooks_CheckConfigForkOrder(t *testing.T) {
29+
err := errors.New("uh oh")
30+
31+
c := new(params.ChainConfig)
32+
require.NoError(t, c.CheckConfigForkOrder(), "CheckConfigForkOrder() with no hooks")
33+
34+
hooks := &hookstest.Stub{
35+
CheckConfigForkOrderFn: func() error { return err },
36+
}
37+
hooks.Register(t).SetOnChainConfig(c, hooks)
38+
require.Equal(t, err, c.CheckConfigForkOrder(), "CheckConfigForkOrder() with error-producing hook")
39+
}
40+
41+
func TestChainConfigHooks_CheckConfigCompatible(t *testing.T) {
42+
rng := ethtest.NewPseudoRand(1234567890)
43+
newcfg := &params.ChainConfig{
44+
ChainID: rng.BigUint64(),
45+
}
46+
headNumber := rng.Uint64()
47+
headTimestamp := rng.Uint64()
48+
49+
c := new(params.ChainConfig)
50+
require.Nil(t, c.CheckCompatible(newcfg, headNumber, headTimestamp), "CheckCompatible() with no hooks")
51+
52+
makeCompatErr := func(newcfg *params.ChainConfig, headNumber *big.Int, headTimestamp uint64) *params.ConfigCompatError {
53+
return &params.ConfigCompatError{
54+
What: fmt.Sprintf("ChainID: %v Head #: %v Head Time: %d", newcfg.ChainID, headNumber, headTimestamp),
55+
}
56+
}
57+
hooks := &hookstest.Stub{
58+
CheckConfigCompatibleFn: makeCompatErr,
59+
}
60+
hooks.Register(t).SetOnChainConfig(c, hooks)
61+
want := makeCompatErr(newcfg, new(big.Int).SetUint64(headNumber), headTimestamp)
62+
require.Equal(t, want, c.CheckCompatible(newcfg, headNumber, headTimestamp), "CheckCompatible() with error-producing hook")
63+
}

0 commit comments

Comments
 (0)