feat!: types.ExtraPayloads supports SlimAccount (#79)

## Why this should be merged

Provides access to extra payloads registered on `types.SlimAccount`, not
just on regular `types.StateAccount`. This is a breaking syntax change
to have the method reflect the behaviour.

## How this works

Modify existing method to accept the `ExtraPayloadCarrier` interface
instead of `*StateAccount`. The interface is implemented by both
`*StateAccount` and `*SlimAccount`.

## How this was tested

Covered by existing testing.
This commit is contained in:
Arran Schlosberg 2024-11-29 16:02:03 +00:00 committed by GitHub
parent 7d1b45b841
commit c2f1269ba3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 58 additions and 38 deletions

View file

@ -27,7 +27,7 @@ import (
func GetExtra[SA any](s *StateDB, p types.ExtraPayloads[SA], addr common.Address) SA { func GetExtra[SA any](s *StateDB, p types.ExtraPayloads[SA], addr common.Address) SA {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
if stateObject != nil { if stateObject != nil {
return p.FromStateAccount(&stateObject.data) return p.FromPayloadCarrier(&stateObject.data)
} }
var zero SA var zero SA
return zero return zero
@ -45,9 +45,9 @@ func setExtraOnObject[SA any](s *stateObject, p types.ExtraPayloads[SA], addr co
s.db.journal.append(extraChange[SA]{ s.db.journal.append(extraChange[SA]{
payloads: p, payloads: p,
account: &addr, account: &addr,
prev: p.FromStateAccount(&s.data), prev: p.FromPayloadCarrier(&s.data),
}) })
p.SetOnStateAccount(&s.data, extra) p.SetOnPayloadCarrier(&s.data, extra)
} }
// extraChange is a [journalEntry] for [SetExtra] / [setExtraOnObject]. // extraChange is a [journalEntry] for [SetExtra] / [setExtraOnObject].
@ -60,5 +60,5 @@ type extraChange[SA any] struct {
func (e extraChange[SA]) dirtied() *common.Address { return e.account } func (e extraChange[SA]) dirtied() *common.Address { return e.account }
func (e extraChange[SA]) revert(s *StateDB) { func (e extraChange[SA]) revert(s *StateDB) {
e.payloads.SetOnStateAccount(&s.getStateObject(*e.account).data, e.prev) e.payloads.SetOnPayloadCarrier(&s.getStateObject(*e.account).data, e.prev)
} }

View file

@ -87,7 +87,7 @@ func TestGetSetExtra(t *testing.T) {
Root: types.EmptyRootHash, Root: types.EmptyRootHash,
CodeHash: types.EmptyCodeHash[:], CodeHash: types.EmptyCodeHash[:],
} }
payloads.SetOnStateAccount(want, extra) payloads.SetOnPayloadCarrier(want, extra)
if diff := cmp.Diff(want, got); diff != "" { if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("types.FullAccount(%T.Account()) diff (-want +got):\n%s", iter, diff) t.Errorf("types.FullAccount(%T.Account()) diff (-want +got):\n%s", iter, diff)

View file

@ -46,7 +46,7 @@ func TestStateObjectEmpty(t *testing.T) {
{ {
name: "explicit false bool", name: "explicit false bool",
registerAndSet: func(acc *types.StateAccount) { registerAndSet: func(acc *types.StateAccount) {
types.RegisterExtras[bool]().SetOnStateAccount(acc, false) types.RegisterExtras[bool]().SetOnPayloadCarrier(acc, false)
}, },
wantEmpty: true, wantEmpty: true,
}, },
@ -60,7 +60,7 @@ func TestStateObjectEmpty(t *testing.T) {
{ {
name: "true bool", name: "true bool",
registerAndSet: func(acc *types.StateAccount) { registerAndSet: func(acc *types.StateAccount) {
types.RegisterExtras[bool]().SetOnStateAccount(acc, true) types.RegisterExtras[bool]().SetOnPayloadCarrier(acc, true)
}, },
wantEmpty: false, wantEmpty: false,
}, },

View file

@ -26,16 +26,16 @@ import (
) )
// RegisterExtras registers the type `SA` to be carried as an extra payload in // RegisterExtras registers the type `SA` to be carried as an extra payload in
// [StateAccount] structs. It is expected to be called in an `init()` function // [StateAccount] and [SlimAccount] structs. It is expected to be called in an
// and MUST NOT be called more than once. // `init()` function and MUST NOT be called more than once.
// //
// The payload will be treated as an extra struct field for the purposes of RLP // The payload will be treated as an extra struct field for the purposes of RLP
// encoding and decoding. RLP handling is plumbed through to the `SA` via the // encoding and decoding. RLP handling is plumbed through to the `SA` via the
// [StateAccountExtra] that holds it such that it acts as if there were a field // [StateAccountExtra] that holds it such that it acts as if there were a field
// of type `SA` in all StateAccount structs. // of type `SA` in all StateAccount and SlimAccount structs.
// //
// The payload can be acced via the [ExtraPayloads.FromStateAccount] method of // The payload can be accessed via the [ExtraPayloads.FromPayloadCarrier] method
// the accessor returned by RegisterExtras. // of the accessor returned by RegisterExtras.
func RegisterExtras[SA any]() ExtraPayloads[SA] { func RegisterExtras[SA any]() ExtraPayloads[SA] {
if registeredExtras != nil { if registeredExtras != nil {
panic("re-registration of Extras") panic("re-registration of Extras")
@ -82,8 +82,8 @@ func (e *StateAccountExtra) clone() *StateAccountExtra {
} }
// ExtraPayloads provides strongly typed access to the extra payload carried by // ExtraPayloads provides strongly typed access to the extra payload carried by
// [StateAccount] structs. The only valid way to construct an instance is by a // [StateAccount] and [SlimAccount] structs. The only valid way to construct an
// call to [RegisterExtras]. // instance is by a call to [RegisterExtras].
type ExtraPayloads[SA any] struct { type ExtraPayloads[SA any] struct {
_ struct{} // make godoc show unexported fields so nobody tries to make their own instance ;) _ struct{} // make godoc show unexported fields so nobody tries to make their own instance ;)
} }
@ -95,24 +95,36 @@ func (ExtraPayloads[SA]) cloneStateAccount(s *StateAccountExtra) *StateAccountEx
} }
} }
// FromStateAccount returns the StateAccount's payload. // ExtraPayloadCarrier is implemented by both [StateAccount] and [SlimAccount],
func (ExtraPayloads[SA]) FromStateAccount(a *StateAccount) SA { // allowing for their [StateAccountExtra] payloads to be accessed in a type-safe
// manner by [ExtraPayloads] instances.
type ExtraPayloadCarrier interface {
extra() *StateAccountExtra
}
var _ = []ExtraPayloadCarrier{
(*StateAccount)(nil),
(*SlimAccount)(nil),
}
// FromPayloadCarrier returns the carriers's payload.
func (ExtraPayloads[SA]) FromPayloadCarrier(a ExtraPayloadCarrier) SA {
return pseudo.MustNewValue[SA](a.extra().payload()).Get() return pseudo.MustNewValue[SA](a.extra().payload()).Get()
} }
// PointerFromStateAccount returns a pointer to the StateAccounts's extra // PointerFromPayloadCarrier returns a pointer to the carriers's extra payload.
// payload. This is guaranteed to be non-nil. // This is guaranteed to be non-nil.
// //
// Note that copying a StateAccount by dereferencing a pointer will result in a // Note that copying a [StateAccount] or [SlimAccount] by dereferencing a
// shallow copy and that the *SA returned here will therefore be shared by all // pointer will result in a shallow copy and that the *SA returned here will
// copies. If this is not the desired behaviour, use // therefore be shared by all copies. If this is not the desired behaviour, use
// [StateAccount.Copy] or [ExtraPayloads.SetOnStateAccount]. // [StateAccount.Copy] or [ExtraPayloads.SetOnPayloadCarrier].
func (ExtraPayloads[SA]) PointerFromStateAccount(a *StateAccount) *SA { func (ExtraPayloads[SA]) PointerFromPayloadCarrier(a ExtraPayloadCarrier) *SA {
return pseudo.MustPointerTo[SA](a.extra().payload()).Value.Get() return pseudo.MustPointerTo[SA](a.extra().payload()).Value.Get()
} }
// SetOnStateAccount sets the StateAccount's payload. // SetOnPayloadCarrier sets the carriers's payload.
func (ExtraPayloads[SA]) SetOnStateAccount(a *StateAccount, val SA) { func (ExtraPayloads[SA]) SetOnPayloadCarrier(a ExtraPayloadCarrier, val SA) {
a.extra().t = pseudo.From(val).Type a.extra().t = pseudo.From(val).Type
} }
@ -124,12 +136,20 @@ type StateAccountExtra struct {
} }
func (a *StateAccount) extra() *StateAccountExtra { func (a *StateAccount) extra() *StateAccountExtra {
if a.Extra == nil { return getOrSetNewStateAccountExtra(&a.Extra)
a.Extra = &StateAccountExtra{ }
func (a *SlimAccount) extra() *StateAccountExtra {
return getOrSetNewStateAccountExtra(&a.Extra)
}
func getOrSetNewStateAccountExtra(curr **StateAccountExtra) *StateAccountExtra {
if *curr == nil {
*curr = &StateAccountExtra{
t: registeredExtras.newStateAccount(), t: registeredExtras.newStateAccount(),
} }
} }
return a.Extra return *curr
} }
func (e *StateAccountExtra) payload() *pseudo.Type { func (e *StateAccountExtra) payload() *pseudo.Type {
@ -143,7 +163,7 @@ func (e *StateAccountExtra) payload() *pseudo.Type {
// of tests. // of tests.
// //
// Equal MUST NOT be used in production. Instead, compare values returned by // Equal MUST NOT be used in production. Instead, compare values returned by
// [ExtraPayloads.FromStateAccount]. // [ExtraPayloads.FromPayloadCarrier].
func (e *StateAccountExtra) Equal(f *StateAccountExtra) bool { func (e *StateAccountExtra) Equal(f *StateAccountExtra) bool {
if false { if false {
// TODO(arr4n): calling this results in an error from cmp.Diff(): // TODO(arr4n): calling this results in an error from cmp.Diff():

View file

@ -35,7 +35,7 @@ type StateAccount struct {
Root common.Hash // merkle root of the storage trie Root common.Hash // merkle root of the storage trie
CodeHash []byte CodeHash []byte
Extra *StateAccountExtra Extra *StateAccountExtra // only access via [ExtraPayloads] instances
} }
// NewEmptyStateAccount constructs an empty state account. // NewEmptyStateAccount constructs an empty state account.
@ -71,7 +71,7 @@ type SlimAccount struct {
Root []byte // Nil if root equals to types.EmptyRootHash Root []byte // Nil if root equals to types.EmptyRootHash
CodeHash []byte // Nil if hash equals to types.EmptyCodeHash CodeHash []byte // Nil if hash equals to types.EmptyCodeHash
Extra *StateAccountExtra Extra *StateAccountExtra // only access via [ExtraPayloads] instances
} }
// SlimAccountRLP encodes the state account in 'slim RLP' format. // SlimAccountRLP encodes the state account in 'slim RLP' format.

View file

@ -71,12 +71,12 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) {
{ {
name: "true-boolean payload", name: "true-boolean payload",
registerAndSetExtra: func(a *types.StateAccount) *types.StateAccount { registerAndSetExtra: func(a *types.StateAccount) *types.StateAccount {
types.RegisterExtras[bool]().SetOnStateAccount(a, true) types.RegisterExtras[bool]().SetOnPayloadCarrier(a, true)
return a return a
}, },
assertExtra: func(t *testing.T, sa *types.StateAccount) { assertExtra: func(t *testing.T, sa *types.StateAccount) {
t.Helper() t.Helper()
assert.Truef(t, types.ExtraPayloads[bool]{}.FromStateAccount(sa), "") assert.Truef(t, types.ExtraPayloads[bool]{}.FromPayloadCarrier(sa), "")
}, },
wantTrieHash: trueBool, wantTrieHash: trueBool,
}, },
@ -84,12 +84,12 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) {
name: "explicit false-boolean payload", name: "explicit false-boolean payload",
registerAndSetExtra: func(a *types.StateAccount) *types.StateAccount { registerAndSetExtra: func(a *types.StateAccount) *types.StateAccount {
p := types.RegisterExtras[bool]() p := types.RegisterExtras[bool]()
p.SetOnStateAccount(a, false) // the explicit part p.SetOnPayloadCarrier(a, false) // the explicit part
return a return a
}, },
assertExtra: func(t *testing.T, sa *types.StateAccount) { assertExtra: func(t *testing.T, sa *types.StateAccount) {
t.Helper() t.Helper()
assert.Falsef(t, types.ExtraPayloads[bool]{}.FromStateAccount(sa), "") assert.Falsef(t, types.ExtraPayloads[bool]{}.FromPayloadCarrier(sa), "")
}, },
wantTrieHash: falseBool, wantTrieHash: falseBool,
}, },
@ -102,7 +102,7 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) {
}, },
assertExtra: func(t *testing.T, sa *types.StateAccount) { assertExtra: func(t *testing.T, sa *types.StateAccount) {
t.Helper() t.Helper()
assert.Falsef(t, types.ExtraPayloads[bool]{}.FromStateAccount(sa), "") assert.Falsef(t, types.ExtraPayloads[bool]{}.FromPayloadCarrier(sa), "")
}, },
wantTrieHash: falseBool, wantTrieHash: falseBool,
}, },
@ -110,12 +110,12 @@ func TestStateAccountExtraViaTrieStorage(t *testing.T) {
name: "arbitrary payload", name: "arbitrary payload",
registerAndSetExtra: func(a *types.StateAccount) *types.StateAccount { registerAndSetExtra: func(a *types.StateAccount) *types.StateAccount {
p := arbitraryPayload{arbitraryData} p := arbitraryPayload{arbitraryData}
types.RegisterExtras[arbitraryPayload]().SetOnStateAccount(a, p) types.RegisterExtras[arbitraryPayload]().SetOnPayloadCarrier(a, p)
return a return a
}, },
assertExtra: func(t *testing.T, sa *types.StateAccount) { assertExtra: func(t *testing.T, sa *types.StateAccount) {
t.Helper() t.Helper()
got := types.ExtraPayloads[arbitraryPayload]{}.FromStateAccount(sa) got := types.ExtraPayloads[arbitraryPayload]{}.FromPayloadCarrier(sa)
assert.Equalf(t, arbitraryPayload{arbitraryData}, got, "") assert.Equalf(t, arbitraryPayload{arbitraryData}, got, "")
}, },
wantTrieHash: arbitrary, wantTrieHash: arbitrary,