Skip to content

Commit 380aa31

Browse files
authored
refactor!: consolidate params and types payload access (#84)
## Why this should be merged There are 3 places at which we perform the same, sensitive logic to access registered payloads and as we modify more types this is likely to expand. (e.g. `types.Header`). ## How this works Introduces `pseudo.Accessor` to abstract the reusable code. ## How this was tested Existing unit tests. Note that the `types.StateAccount` tests needed a minor refactor to provide the assertions with access to the `ExtraPayloads[T]` without introducing generic types anywhere.
1 parent d71677f commit 380aa31

File tree

10 files changed

+135
-135
lines changed

10 files changed

+135
-135
lines changed

core/state/state.libevm.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727
func GetExtra[SA any](s *StateDB, p types.ExtraPayloads[SA], addr common.Address) SA {
2828
stateObject := s.getStateObject(addr)
2929
if stateObject != nil {
30-
return p.FromPayloadCarrier(&stateObject.data)
30+
return p.StateAccount.Get(&stateObject.data)
3131
}
3232
var zero SA
3333
return zero
@@ -45,9 +45,9 @@ func setExtraOnObject[SA any](s *stateObject, p types.ExtraPayloads[SA], addr co
4545
s.db.journal.append(extraChange[SA]{
4646
payloads: p,
4747
account: &addr,
48-
prev: p.FromPayloadCarrier(&s.data),
48+
prev: p.StateAccount.Get(&s.data),
4949
})
50-
p.SetOnPayloadCarrier(&s.data, extra)
50+
p.StateAccount.Set(&s.data, extra)
5151
}
5252

5353
// extraChange is a [journalEntry] for [SetExtra] / [setExtraOnObject].
@@ -60,5 +60,5 @@ type extraChange[SA any] struct {
6060
func (e extraChange[SA]) dirtied() *common.Address { return e.account }
6161

6262
func (e extraChange[SA]) revert(s *StateDB) {
63-
e.payloads.SetOnPayloadCarrier(&s.getStateObject(*e.account).data, e.prev)
63+
e.payloads.StateAccount.Set(&s.getStateObject(*e.account).data, e.prev)
6464
}

core/state/state.libevm_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func TestGetSetExtra(t *testing.T) {
8787
Root: types.EmptyRootHash,
8888
CodeHash: types.EmptyCodeHash[:],
8989
}
90-
payloads.SetOnPayloadCarrier(want, extra)
90+
payloads.StateAccount.Set(want, extra)
9191

9292
if diff := cmp.Diff(want, got); diff != "" {
9393
t.Errorf("types.FullAccount(%T.Account()) diff (-want +got):\n%s", iter, diff)

core/state/state_object.libevm_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func TestStateObjectEmpty(t *testing.T) {
4646
{
4747
name: "explicit false bool",
4848
registerAndSet: func(acc *types.StateAccount) {
49-
types.RegisterExtras[bool]().SetOnPayloadCarrier(acc, false)
49+
types.RegisterExtras[bool]().StateAccount.Set(acc, false)
5050
},
5151
wantEmpty: true,
5252
},
@@ -60,7 +60,7 @@ func TestStateObjectEmpty(t *testing.T) {
6060
{
6161
name: "true bool",
6262
registerAndSet: func(acc *types.StateAccount) {
63-
types.RegisterExtras[bool]().SetOnPayloadCarrier(acc, true)
63+
types.RegisterExtras[bool]().StateAccount.Set(acc, true)
6464
},
6565
wantEmpty: false,
6666
},

core/types/rlp_payload.libevm.go

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ import (
3838
// The payload can be accessed via the [ExtraPayloads.FromPayloadCarrier] method
3939
// of the accessor returned by RegisterExtras.
4040
func RegisterExtras[SA any]() ExtraPayloads[SA] {
41-
var extra ExtraPayloads[SA]
41+
extra := ExtraPayloads[SA]{
42+
StateAccount: pseudo.NewAccessor[ExtraPayloadCarrier, SA](
43+
func(a ExtraPayloadCarrier) *pseudo.Type { return a.extra().payload() },
44+
func(a ExtraPayloadCarrier, t *pseudo.Type) { a.extra().t = t },
45+
),
46+
}
4247
registeredExtras.MustRegister(&extraConstructors{
4348
stateAccountType: func() string {
4449
var x SA
@@ -81,7 +86,7 @@ func (e *StateAccountExtra) clone() *StateAccountExtra {
8186
// [StateAccount] and [SlimAccount] structs. The only valid way to construct an
8287
// instance is by a call to [RegisterExtras].
8388
type ExtraPayloads[SA any] struct {
84-
_ struct{} // make godoc show unexported fields so nobody tries to make their own instance ;)
89+
StateAccount pseudo.Accessor[ExtraPayloadCarrier, SA] // Also provides [SlimAccount] access.
8590
}
8691

8792
func (ExtraPayloads[SA]) cloneStateAccount(s *StateAccountExtra) *StateAccountExtra {
@@ -103,27 +108,6 @@ var _ = []ExtraPayloadCarrier{
103108
(*SlimAccount)(nil),
104109
}
105110

106-
// FromPayloadCarrier returns the carriers's payload.
107-
func (ExtraPayloads[SA]) FromPayloadCarrier(a ExtraPayloadCarrier) SA {
108-
return pseudo.MustNewValue[SA](a.extra().payload()).Get()
109-
}
110-
111-
// PointerFromPayloadCarrier returns a pointer to the carriers's extra payload.
112-
// This is guaranteed to be non-nil.
113-
//
114-
// Note that copying a [StateAccount] or [SlimAccount] by dereferencing a
115-
// pointer will result in a shallow copy and that the *SA returned here will
116-
// therefore be shared by all copies. If this is not the desired behaviour, use
117-
// [StateAccount.Copy] or [ExtraPayloads.SetOnPayloadCarrier].
118-
func (ExtraPayloads[SA]) PointerFromPayloadCarrier(a ExtraPayloadCarrier) *SA {
119-
return pseudo.MustPointerTo[SA](a.extra().payload()).Value.Get()
120-
}
121-
122-
// SetOnPayloadCarrier sets the carriers's payload.
123-
func (ExtraPayloads[SA]) SetOnPayloadCarrier(a ExtraPayloadCarrier, val SA) {
124-
a.extra().t = pseudo.From(val).Type
125-
}
126-
127111
// A StateAccountExtra carries the extra payload, if any, registered with
128112
// [RegisterExtras]. It SHOULD NOT be used directly; instead use the
129113
// [ExtraPayloads] accessor returned by RegisterExtras.

core/types/state_account_storage.libevm_test.go

Lines changed: 36 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -51,72 +51,68 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) {
5151
arbitrary = common.HexToHash("0x94eecff1444ab69437636630918c15596e001b30b973f03e06006ae20aa6e307")
5252
)
5353

54+
// An assertion is the actual test to be performed. It is returned upon
55+
// registration, instead of being a standalone field in the test, because
56+
// each one uses a different generic parameter.
57+
type assertion func(*testing.T, *types.StateAccount)
5458
tests := []struct {
5559
name string
56-
registerAndSetExtra func(*types.StateAccount) *types.StateAccount
57-
assertExtra func(*testing.T, *types.StateAccount)
60+
registerAndSetExtra func(*types.StateAccount) (*types.StateAccount, assertion)
5861
wantTrieHash common.Hash
5962
}{
6063
{
6164
name: "vanilla geth",
62-
registerAndSetExtra: func(a *types.StateAccount) *types.StateAccount {
63-
return a
64-
},
65-
assertExtra: func(t *testing.T, a *types.StateAccount) {
66-
t.Helper()
67-
assert.Truef(t, a.Extra.Equal(nil), "%T.%T.IsEmpty()", a, a.Extra)
65+
registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) {
66+
//nolint:thelper // It's more useful if this test failure shows this line instead of its call site
67+
return a, func(t *testing.T, got *types.StateAccount) {
68+
assert.Truef(t, a.Extra.Equal(nil), "%T.%T.IsEmpty()", a, a.Extra)
69+
}
6870
},
6971
wantTrieHash: vanillaGeth,
7072
},
7173
{
7274
name: "true-boolean payload",
73-
registerAndSetExtra: func(a *types.StateAccount) *types.StateAccount {
74-
types.RegisterExtras[bool]().SetOnPayloadCarrier(a, true)
75-
return a
76-
},
77-
assertExtra: func(t *testing.T, sa *types.StateAccount) {
78-
t.Helper()
79-
assert.Truef(t, types.ExtraPayloads[bool]{}.FromPayloadCarrier(sa), "")
75+
registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) {
76+
e := types.RegisterExtras[bool]()
77+
e.StateAccount.Set(a, true)
78+
return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper
79+
assert.Truef(t, e.StateAccount.Get(got), "")
80+
}
8081
},
8182
wantTrieHash: trueBool,
8283
},
8384
{
8485
name: "explicit false-boolean payload",
85-
registerAndSetExtra: func(a *types.StateAccount) *types.StateAccount {
86-
p := types.RegisterExtras[bool]()
87-
p.SetOnPayloadCarrier(a, false) // the explicit part
88-
return a
89-
},
90-
assertExtra: func(t *testing.T, sa *types.StateAccount) {
91-
t.Helper()
92-
assert.Falsef(t, types.ExtraPayloads[bool]{}.FromPayloadCarrier(sa), "")
86+
registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) {
87+
e := types.RegisterExtras[bool]()
88+
e.StateAccount.Set(a, false) // the explicit part
89+
90+
return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper
91+
assert.Falsef(t, e.StateAccount.Get(got), "")
92+
}
9393
},
9494
wantTrieHash: falseBool,
9595
},
9696
{
9797
name: "implicit false-boolean payload",
98-
registerAndSetExtra: func(a *types.StateAccount) *types.StateAccount {
99-
types.RegisterExtras[bool]()
98+
registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) {
99+
e := types.RegisterExtras[bool]()
100100
// Note that `a` is reflected, unchanged (the implicit part).
101-
return a
102-
},
103-
assertExtra: func(t *testing.T, sa *types.StateAccount) {
104-
t.Helper()
105-
assert.Falsef(t, types.ExtraPayloads[bool]{}.FromPayloadCarrier(sa), "")
101+
return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper
102+
assert.Falsef(t, e.StateAccount.Get(got), "")
103+
}
106104
},
107105
wantTrieHash: falseBool,
108106
},
109107
{
110108
name: "arbitrary payload",
111-
registerAndSetExtra: func(a *types.StateAccount) *types.StateAccount {
109+
registerAndSetExtra: func(a *types.StateAccount) (*types.StateAccount, assertion) {
110+
e := types.RegisterExtras[arbitraryPayload]()
112111
p := arbitraryPayload{arbitraryData}
113-
types.RegisterExtras[arbitraryPayload]().SetOnPayloadCarrier(a, p)
114-
return a
115-
},
116-
assertExtra: func(t *testing.T, sa *types.StateAccount) {
117-
t.Helper()
118-
got := types.ExtraPayloads[arbitraryPayload]{}.FromPayloadCarrier(sa)
119-
assert.Equalf(t, arbitraryPayload{arbitraryData}, got, "")
112+
e.StateAccount.Set(a, p)
113+
return a, func(t *testing.T, got *types.StateAccount) { //nolint:thelper
114+
assert.Equalf(t, arbitraryPayload{arbitraryData}, e.StateAccount.Get(got), "")
115+
}
120116
},
121117
wantTrieHash: arbitrary,
122118
},
@@ -127,7 +123,7 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) {
127123
types.TestOnlyClearRegisteredExtras()
128124
t.Cleanup(types.TestOnlyClearRegisteredExtras)
129125

130-
acct := tt.registerAndSetExtra(&types.StateAccount{
126+
acct, asserter := tt.registerAndSetExtra(&types.StateAccount{
131127
Nonce: 42,
132128
Balance: uint256.NewInt(314159),
133129
Root: types.EmptyRootHash,
@@ -147,7 +143,7 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) {
147143
if diff := cmp.Diff(acct, got); diff != "" {
148144
t.Errorf("%T.GetAccount() not equal to value passed to %[1]T.UpdateAccount(); diff (-want +got):\n%s", state, diff)
149145
}
150-
tt.assertExtra(t, got)
146+
asserter(t, got)
151147
})
152148
}
153149
}

libevm/pseudo/accessor.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2024 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
17+
package pseudo
18+
19+
// An Accessor provides access to T values held in other types.
20+
type Accessor[Container any, T any] struct {
21+
get func(Container) *Type
22+
set func(Container, *Type)
23+
}
24+
25+
// NewAccessor constructs a new [Accessor]. The `get` function MUST return a
26+
// [Type] holding a T.
27+
func NewAccessor[C any, T any](get func(C) *Type, set func(C, *Type)) Accessor[C, T] {
28+
return Accessor[C, T]{get, set}
29+
}
30+
31+
// Get returns the T held by the Container.
32+
func (a Accessor[C, T]) Get(from C) T {
33+
return MustNewValue[T](a.get(from)).Get()
34+
}
35+
36+
// Get returns a pointer to the T held by the Container, which is guaranteed to
37+
// be non-nil. However, if T is itself a pointer, no guarantees are provided.
38+
//
39+
// Note that copying a Container might result in a shallow copy and that the *T
40+
// returned here will therefore be shared by all copies. If this is not the
41+
// desired behaviour, use [Accessor.Set].
42+
func (a Accessor[C, T]) GetPointer(from C) *T {
43+
return MustPointerTo[T](a.get(from)).Value.Get()
44+
}
45+
46+
// Set sets the T carried by the Container.
47+
func (a Accessor[C, T]) Set(on C, val T) {
48+
a.set(on, From(val).Type)
49+
}

params/config.libevm.go

Lines changed: 17 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,22 @@ func (e *Extras[C, R]) newForRules(c *ChainConfig, r *Rules, blockNum *big.Int,
112112
if e.NewRules == nil {
113113
return registeredExtras.Get().newRules()
114114
}
115-
rExtra := e.NewRules(c, r, e.payloads().FromChainConfig(c), blockNum, isMerge, timestamp)
115+
rExtra := e.NewRules(c, r, e.payloads().ChainConfig.Get(c), blockNum, isMerge, timestamp)
116116
return pseudo.From(rExtra).Type
117117
}
118118

119-
func (*Extras[C, R]) payloads() (g ExtraPayloads[C, R]) { return }
119+
func (*Extras[C, R]) payloads() ExtraPayloads[C, R] {
120+
return ExtraPayloads[C, R]{
121+
ChainConfig: pseudo.NewAccessor[*ChainConfig, C](
122+
(*ChainConfig).extraPayload,
123+
func(c *ChainConfig, t *pseudo.Type) { c.extra = t },
124+
),
125+
Rules: pseudo.NewAccessor[*Rules, R](
126+
(*Rules).extraPayload,
127+
func(r *Rules, t *pseudo.Type) { r.extra = t },
128+
),
129+
}
130+
}
120131

121132
// mustBeStructOrPointerToOne panics if `T` isn't a struct or a *struct.
122133
func mustBeStructOrPointerToOne[T any]() {
@@ -144,60 +155,20 @@ func notStructMessage[T any]() string {
144155
// [ChainConfig] and [Rules] structs. The only valid way to construct an
145156
// instance is by a call to [RegisterExtras].
146157
type ExtraPayloads[C ChainConfigHooks, R RulesHooks] struct {
147-
_ struct{} // make godoc show unexported fields so nobody tries to make their own instance ;)
148-
}
149-
150-
// FromChainConfig returns the ChainConfig's extra payload.
151-
func (ExtraPayloads[C, R]) FromChainConfig(c *ChainConfig) C {
152-
return pseudo.MustNewValue[C](c.extraPayload()).Get()
153-
}
154-
155-
// PointerFromChainConfig returns a pointer to the ChainConfig's extra payload.
156-
// This is guaranteed to be non-nil.
157-
//
158-
// Note that copying a ChainConfig by dereferencing a pointer will result in a
159-
// shallow copy and that the *C returned here will therefore be shared by all
160-
// copies. If this is not the desired behaviour, use
161-
// [ExtraPayloads.SetOnChainConfig].
162-
func (ExtraPayloads[C, R]) PointerFromChainConfig(c *ChainConfig) *C {
163-
return pseudo.MustPointerTo[C](c.extraPayload()).Value.Get()
164-
}
165-
166-
// SetOnChainConfig sets the ChainConfig's extra payload.
167-
func (e ExtraPayloads[C, R]) SetOnChainConfig(cc *ChainConfig, val C) {
168-
cc.extra = pseudo.From(val).Type
158+
ChainConfig pseudo.Accessor[*ChainConfig, C]
159+
Rules pseudo.Accessor[*Rules, R]
169160
}
170161

171162
// hooksFromChainConfig is equivalent to FromChainConfig(), but returns an
172163
// interface instead of the concrete type implementing it; this allows it to be
173164
// used in non-generic code.
174165
func (e ExtraPayloads[C, R]) hooksFromChainConfig(c *ChainConfig) ChainConfigHooks {
175-
return e.FromChainConfig(c)
176-
}
177-
178-
// FromRules returns the Rules' extra payload.
179-
func (ExtraPayloads[C, R]) FromRules(r *Rules) R {
180-
return pseudo.MustNewValue[R](r.extraPayload()).Get()
181-
}
182-
183-
// PointerFromRules returns a pointer to the Rules's extra payload. This is
184-
// guaranteed to be non-nil.
185-
//
186-
// Note that copying a Rules by dereferencing a pointer will result in a shallow
187-
// copy and that the *R returned here will therefore be shared by all copies. If
188-
// this is not the desired behaviour, use [ExtraPayloads.SetOnRules].
189-
func (ExtraPayloads[C, R]) PointerFromRules(r *Rules) *R {
190-
return pseudo.MustPointerTo[R](r.extraPayload()).Value.Get()
191-
}
192-
193-
// SetOnRules sets the Rules' extra payload.
194-
func (e ExtraPayloads[C, R]) SetOnRules(r *Rules, val R) {
195-
r.extra = pseudo.From(val).Type
166+
return e.ChainConfig.Get(c)
196167
}
197168

198169
// hooksFromRules is the [RulesHooks] equivalent of hooksFromChainConfig().
199170
func (e ExtraPayloads[C, R]) hooksFromRules(r *Rules) RulesHooks {
200-
return e.FromRules(r)
171+
return e.Rules.Get(r)
201172
}
202173

203174
// addRulesExtra is called at the end of [ChainConfig.Rules]; it exists to

0 commit comments

Comments
 (0)