Skip to content

Commit 01bf6c2

Browse files
committed
feat: state.{Get,Set}Extra[SA any](*StateDB,types.ExtraPayloads,...)
1 parent 5ec080f commit 01bf6c2

File tree

2 files changed

+194
-0
lines changed

2 files changed

+194
-0
lines changed

core/state/state.libevm.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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 state
18+
19+
import (
20+
"github.com/ethereum/go-ethereum/common"
21+
"github.com/ethereum/go-ethereum/core/types"
22+
)
23+
24+
// GetExtra returns the extra payload from the [types.StateAccount] associated
25+
// with the address, or a zero-value `SA` if not found. The
26+
// [types.ExtraPayloads] MUST be sourced from [types.RegisterExtras].
27+
func GetExtra[SA any](s *StateDB, p types.ExtraPayloads[SA], addr common.Address) SA {
28+
stateObject := s.getStateObject(addr)
29+
if stateObject != nil {
30+
return p.FromStateAccount(&stateObject.data)
31+
}
32+
var zero SA
33+
return zero
34+
}
35+
36+
// SetExtra sets the extra payload for the address. See [GetExtra] for details.
37+
func SetExtra[SA any](s *StateDB, p types.ExtraPayloads[SA], addr common.Address, extra SA) {
38+
stateObject := s.getOrNewStateObject(addr)
39+
if stateObject != nil {
40+
setExtraOnObject(stateObject, p, addr, extra)
41+
}
42+
}
43+
44+
func setExtraOnObject[SA any](s *stateObject, p types.ExtraPayloads[SA], addr common.Address, extra SA) {
45+
s.db.journal.append(extraChange[SA]{
46+
payloads: p,
47+
account: &addr,
48+
prev: p.FromStateAccount(&s.data),
49+
})
50+
p.SetOnStateAccount(&s.data, extra)
51+
}
52+
53+
// extraChange is a [journalEntry] for [SetExtra] / [setExtraOnObject].
54+
type extraChange[SA any] struct {
55+
payloads types.ExtraPayloads[SA]
56+
account *common.Address
57+
prev SA
58+
}
59+
60+
func (e extraChange[SA]) dirtied() *common.Address { return e.account }
61+
62+
func (e extraChange[SA]) revert(s *StateDB) {
63+
e.payloads.SetOnStateAccount(&s.getStateObject(*e.account).data, e.prev)
64+
}

core/state/state.libevm_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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 state_test
18+
19+
import (
20+
"fmt"
21+
"testing"
22+
23+
"github.com/google/go-cmp/cmp"
24+
"github.com/stretchr/testify/assert"
25+
"github.com/stretchr/testify/require"
26+
27+
"github.com/ethereum/go-ethereum/common"
28+
"github.com/ethereum/go-ethereum/core/rawdb"
29+
"github.com/ethereum/go-ethereum/core/state"
30+
"github.com/ethereum/go-ethereum/core/state/snapshot"
31+
"github.com/ethereum/go-ethereum/core/types"
32+
"github.com/ethereum/go-ethereum/ethdb/memorydb"
33+
"github.com/ethereum/go-ethereum/libevm/ethtest"
34+
"github.com/ethereum/go-ethereum/triedb"
35+
)
36+
37+
func TestGetSetExtra(t *testing.T) {
38+
types.TestOnlyClearRegisteredExtras()
39+
t.Cleanup(types.TestOnlyClearRegisteredExtras)
40+
payloads := types.RegisterExtras[[]byte]()
41+
42+
rng := ethtest.NewPseudoRand(42)
43+
addr := rng.Address()
44+
nonce := rng.Uint64()
45+
balance := rng.Uint256()
46+
extra := rng.Bytes(8)
47+
48+
views := newWithSnaps(t)
49+
stateDB := views.stateDB
50+
assert.Nilf(t, state.GetExtra(stateDB, payloads, addr), "state.GetExtra() returns zero-value %T if before SetExtra()", extra)
51+
stateDB.CreateAccount(addr)
52+
stateDB.SetNonce(addr, nonce)
53+
stateDB.SetBalance(addr, balance)
54+
state.SetExtra(stateDB, payloads, addr, extra)
55+
56+
root, err := stateDB.Commit(1, false) // arbitrary block number
57+
require.NoErrorf(t, err, "%T.Commit(1, false)", stateDB)
58+
require.NotEqualf(t, types.EmptyRootHash, root, "root hash returned by %T.Commit() is not the empty root", stateDB)
59+
60+
t.Run(fmt.Sprintf("retrieve from %T", views.snaps), func(t *testing.T) {
61+
iter, err := views.snaps.AccountIterator(root, common.Hash{})
62+
require.NoErrorf(t, err, "%T.AccountIterator(...)", views.snaps)
63+
defer iter.Release()
64+
65+
require.Truef(t, iter.Next(), "%T.Next() (i.e. at least one account)", iter)
66+
require.NoErrorf(t, iter.Error(), "%T.Error()", iter)
67+
68+
t.Run("types.FullAccount()", func(t *testing.T) {
69+
got, err := types.FullAccount(iter.Account())
70+
require.NoErrorf(t, err, "types.FullAccount(%T.Account())", iter)
71+
72+
want := &types.StateAccount{
73+
Nonce: nonce,
74+
Balance: balance,
75+
Root: types.EmptyRootHash,
76+
CodeHash: types.EmptyCodeHash[:],
77+
}
78+
payloads.SetOnStateAccount(want, extra)
79+
80+
if diff := cmp.Diff(want, got); diff != "" {
81+
t.Errorf("types.FullAccount(%T.Account()) diff (-want +got):\n%s", iter, diff)
82+
}
83+
})
84+
85+
require.Falsef(t, iter.Next(), "%T.Next() after first account (i.e. only one)", iter)
86+
})
87+
88+
t.Run(fmt.Sprintf("retrieve from new %T", views.stateDB), func(t *testing.T) {
89+
stateDB, err := state.New(root, views.database, views.snaps)
90+
require.NoError(t, err, "state.New()")
91+
92+
// triggers SlimAccount RLP decoding
93+
assert.Equalf(t, nonce, stateDB.GetNonce(addr), "%T.GetNonce()", stateDB)
94+
assert.Equalf(t, balance, stateDB.GetBalance(addr), "%T.GetBalance()", stateDB)
95+
assert.Equal(t, extra, state.GetExtra(stateDB, payloads, addr), "state.GetExtra()")
96+
})
97+
}
98+
99+
// stateViews are different ways to access the same data.
100+
type stateViews struct {
101+
stateDB *state.StateDB
102+
snaps *snapshot.Tree
103+
database state.Database
104+
}
105+
106+
func newWithSnaps(t *testing.T) stateViews {
107+
t.Helper()
108+
empty := types.EmptyRootHash
109+
kvStore := memorydb.New()
110+
ethDB := rawdb.NewDatabase(kvStore)
111+
snaps, err := snapshot.New(
112+
snapshot.Config{
113+
CacheSize: 16, // Mb (arbitrary but non-zero)
114+
},
115+
kvStore,
116+
triedb.NewDatabase(ethDB, nil),
117+
empty,
118+
)
119+
require.NoError(t, err, "snapshot.New()")
120+
121+
database := state.NewDatabase(ethDB)
122+
stateDB, err := state.New(empty, database, snaps)
123+
require.NoError(t, err, "state.New()")
124+
125+
return stateViews{
126+
stateDB: stateDB,
127+
snaps: snaps,
128+
database: database,
129+
}
130+
}

0 commit comments

Comments
 (0)