Skip to content

Commit 9b97d60

Browse files
authored
feat: triedb.Database.Update options via statedb.Commit (#190)
## Why this should be merged To allow more thorough handling of duplicate state roots (or any other info other users would like), additional information can be provided to a call of `statedb.Commit`. This change allows arbitrary types to be sent to `triedb` as well as the `SnapshotTree`. However, this is a breaking change for those using the functionality already, since the snapshot commit option is wrapped with another call. ## How this works See the edited libevm test for usage. ## How this was tested Edited test case to include `TrieDBUpdateOption` and ensures the payload is sent.
1 parent ab5ad25 commit 9b97d60

File tree

6 files changed

+142
-21
lines changed

6 files changed

+142
-21
lines changed

core/state/statedb.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,7 +1152,7 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A
11521152
//
11531153
// The associated block number of the state transition is also provided
11541154
// for more chain context.
1155-
func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, opts ...stateconf.SnapshotUpdateOption) (common.Hash, error) {
1155+
func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, opts ...stateconf.StateDBCommitOption) (common.Hash, error) {
11561156
// Short circuit in case any database failure occurred earlier.
11571157
if s.dbErr != nil {
11581158
return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
@@ -1242,7 +1242,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, opts ...statecon
12421242
start := time.Now()
12431243
// Only update if there's a state transition (skip empty Clique blocks)
12441244
if parent := s.snap.Root(); parent != root {
1245-
if err := s.snaps.Update(root, parent, s.convertAccountSet(s.stateObjectsDestruct), s.accounts, s.storages, opts...); err != nil {
1245+
if err := s.snaps.Update(root, parent, s.convertAccountSet(s.stateObjectsDestruct), s.accounts, s.storages, stateconf.ExtractSnapshotUpdateOpts(opts...)...); err != nil {
12461246
log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err)
12471247
}
12481248
// Keep 128 diff layers in the memory, persistent layer is 129th.
@@ -1268,7 +1268,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, opts ...statecon
12681268
if root != origin {
12691269
start := time.Now()
12701270
set := triestate.New(s.accountsOrigin, s.storagesOrigin, incomplete)
1271-
if err := s.db.TrieDB().Update(root, origin, block, nodes, set); err != nil {
1271+
if err := s.db.TrieDB().Update(root, origin, block, nodes, set, stateconf.ExtractTrieDBUpdateOpts(opts...)...); err != nil {
12721272
return common.Hash{}, err
12731273
}
12741274
s.originalRoot = root

core/state/statedb.libevm_test.go

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,48 @@ import (
2626
"github.com/ava-labs/libevm/core/rawdb"
2727
"github.com/ava-labs/libevm/core/state/snapshot"
2828
"github.com/ava-labs/libevm/core/types"
29+
"github.com/ava-labs/libevm/ethdb"
2930
"github.com/ava-labs/libevm/libevm/stateconf"
31+
"github.com/ava-labs/libevm/trie"
32+
"github.com/ava-labs/libevm/trie/trienode"
33+
"github.com/ava-labs/libevm/trie/triestate"
34+
"github.com/ava-labs/libevm/triedb"
35+
"github.com/ava-labs/libevm/triedb/database"
36+
"github.com/ava-labs/libevm/triedb/hashdb"
3037
)
3138

3239
func TestStateDBCommitPropagatesOptions(t *testing.T) {
33-
var rec snapTreeRecorder
34-
sdb, err := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), &rec)
40+
memdb := rawdb.NewMemoryDatabase()
41+
trieRec := &triedbRecorder{Database: hashdb.New(memdb, nil, &trie.MerkleResolver{})}
42+
triedb := triedb.NewDatabase(
43+
memdb,
44+
&triedb.Config{
45+
DBOverride: func(_ ethdb.Database) triedb.DBOverride {
46+
return trieRec
47+
},
48+
},
49+
)
50+
var snapRec snapTreeRecorder
51+
sdb, err := New(types.EmptyRootHash, NewDatabaseWithNodeDB(memdb, triedb), &snapRec)
3552
require.NoError(t, err, "New()")
3653

3754
// Ensures that rec.Update() will be called.
3855
sdb.SetNonce(common.Address{}, 42)
3956

40-
const payload = "hello world"
41-
opt := stateconf.WithUpdatePayload(payload)
42-
_, err = sdb.Commit(0, false, opt)
43-
require.NoErrorf(t, err, "%T.Commit(..., %T)", sdb, opt)
57+
const snapshotPayload = "hello world"
58+
var (
59+
parentHash = common.HexToHash("0x0102030405060708090a0b0c0d0e0f1011121314151617181920212223242526")
60+
currentHash = common.HexToHash("0x1234567890123456789012345678901234567890123456789012345678901234")
61+
)
62+
snapshotOpt := stateconf.WithSnapshotUpdatePayload(snapshotPayload)
63+
triedbOpt := stateconf.WithTrieDBUpdatePayload(parentHash, currentHash)
64+
_, err = sdb.Commit(0, false, stateconf.WithSnapshotUpdateOpts(snapshotOpt), stateconf.WithTrieDBUpdateOpts(triedbOpt))
4465

45-
assert.Equalf(t, payload, rec.gotPayload, "%T payload propagated via %T.Commit() to %T.Update()", opt, sdb, rec)
66+
require.NoErrorf(t, err, "%T.Commit(..., %T, %T)", sdb, snapshotOpt, triedbOpt)
67+
assert.Equalf(t, snapshotPayload, snapRec.gotPayload, "%T payload propagated via %T.Commit() to %T.Update()", snapshotOpt, sdb, snapRec)
68+
assert.Truef(t, trieRec.exists, "%T exists propagated via %T.Commit() to %T.Update()", triedbOpt, sdb, trieRec)
69+
assert.Equalf(t, parentHash, trieRec.parentBlockHash, "%T parentHash propagated via %T.Commit() to %T.Update()", triedbOpt, sdb, trieRec)
70+
assert.Equalf(t, currentHash, trieRec.currentBlockHash, "%T currentHash propagated via %T.Commit() to %T.Update()", triedbOpt, sdb, trieRec)
4671
}
4772

4873
type snapTreeRecorder struct {
@@ -59,7 +84,7 @@ func (r *snapTreeRecorder) Update(
5984
_ map[common.Hash]struct{}, _ map[common.Hash][]byte, _ map[common.Hash]map[common.Hash][]byte,
6085
opts ...stateconf.SnapshotUpdateOption,
6186
) error {
62-
r.gotPayload = stateconf.ExtractUpdatePayload(opts...)
87+
r.gotPayload = stateconf.ExtractSnapshotUpdatePayload(opts...)
6388
return nil
6489
}
6590

@@ -78,3 +103,26 @@ func (snapshotStub) Account(common.Hash) (*types.SlimAccount, error) {
78103
func (snapshotStub) Root() common.Hash {
79104
return common.Hash{}
80105
}
106+
107+
type triedbRecorder struct {
108+
*hashdb.Database
109+
parentBlockHash common.Hash
110+
currentBlockHash common.Hash
111+
exists bool
112+
}
113+
114+
func (r *triedbRecorder) Update(
115+
root common.Hash,
116+
parent common.Hash,
117+
block uint64,
118+
nodes *trienode.MergedNodeSet,
119+
states *triestate.Set,
120+
opts ...stateconf.TrieDBUpdateOption,
121+
) error {
122+
r.parentBlockHash, r.currentBlockHash, r.exists = stateconf.ExtractTrieDBUpdatePayload(opts...)
123+
return r.Database.Update(root, parent, block, nodes, states)
124+
}
125+
126+
func (r *triedbRecorder) Reader(_ common.Hash) (database.Reader, error) {
127+
return r.Database.Reader(common.Hash{})
128+
}

libevm/stateconf/conf.go

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,48 @@
1717
// Package stateconf configures state management.
1818
package stateconf
1919

20-
import "github.com/ava-labs/libevm/libevm/options"
20+
import (
21+
"github.com/ava-labs/libevm/common"
22+
"github.com/ava-labs/libevm/libevm/options"
23+
)
24+
25+
// A StateDBCommitOption configures the behaviour of state.StateDB.Commit()
26+
type StateDBCommitOption = options.Option[stateDBCommitConfig]
27+
28+
type stateDBCommitConfig struct {
29+
snapshotOpts []SnapshotUpdateOption
30+
triedbOpts []TrieDBUpdateOption
31+
}
32+
33+
// WithSnapshotUpdateOpts returns a StateDBCommitOption carrying a list of
34+
// SnapshotUpdateOptions.
35+
// If multiple such options are used, only the last will be applied as they overwrite each other.
36+
func WithSnapshotUpdateOpts(opts ...SnapshotUpdateOption) StateDBCommitOption {
37+
return options.Func[stateDBCommitConfig](func(c *stateDBCommitConfig) {
38+
c.snapshotOpts = opts
39+
})
40+
}
41+
42+
// ExtractSnapshotUpdateOpts returns the list of SnapshotUpdateOptions carried
43+
// by the provided slice of StateDBCommitOption.
44+
func ExtractSnapshotUpdateOpts(opts ...StateDBCommitOption) []SnapshotUpdateOption {
45+
return options.As(opts...).snapshotOpts
46+
}
47+
48+
// WithTrieDBUpdateOpts returns a StateDBCommitOption carrying a list of
49+
// TrieDBUpdateOptions. If multiple such options are used, only the last will be
50+
// applied as they overwrite each other.
51+
func WithTrieDBUpdateOpts(opts ...TrieDBUpdateOption) StateDBCommitOption {
52+
return options.Func[stateDBCommitConfig](func(c *stateDBCommitConfig) {
53+
c.triedbOpts = opts
54+
})
55+
}
56+
57+
// ExtractTrieDBUpdateOpts returns the list of TrieDBUpdateOptions carried by
58+
// the provided slice of StateDBCommitOption.
59+
func ExtractTrieDBUpdateOpts(opts ...StateDBCommitOption) []TrieDBUpdateOption {
60+
return options.As(opts...).triedbOpts
61+
}
2162

2263
// A SnapshotUpdateOption configures the behaviour of
2364
// state.SnapshotTree.Update() implementations. This will be removed along with
@@ -28,18 +69,47 @@ type snapshotUpdateConfig struct {
2869
payload any
2970
}
3071

31-
// WithUpdatePayload returns a SnapshotUpdateOption carrying an arbitrary
72+
// WithSnapshotUpdatePayload returns a SnapshotUpdateOption carrying an arbitrary
3273
// payload. It acts only as a carrier to exploit existing function plumbing and
3374
// the effect on behaviour is left to the implementation receiving it.
34-
func WithUpdatePayload(p any) SnapshotUpdateOption {
75+
func WithSnapshotUpdatePayload(p any) SnapshotUpdateOption {
3576
return options.Func[snapshotUpdateConfig](func(c *snapshotUpdateConfig) {
3677
c.payload = p
3778
})
3879
}
3980

40-
// ExtractUpdatePayload returns the payload carried by a [WithUpdatePayload]
81+
// ExtractSnapshotUpdatePayload returns the payload carried by a [WithSnapshotUpdatePayload]
4182
// option. Only one such option can be used at once; behaviour is otherwise
4283
// undefined.
43-
func ExtractUpdatePayload(opts ...SnapshotUpdateOption) any {
84+
func ExtractSnapshotUpdatePayload(opts ...SnapshotUpdateOption) any {
4485
return options.As(opts...).payload
4586
}
87+
88+
// A TrieDBUpdateOption configures the behaviour of triedb.Database.Update() implementations.
89+
type TrieDBUpdateOption = options.Option[triedbUpdateConfig]
90+
91+
type triedbUpdateConfig struct {
92+
parentBlockHash *common.Hash
93+
currentBlockHash *common.Hash
94+
}
95+
96+
// WithTrieDBUpdatePayload returns a TrieDBUpdateOption carrying two block hashes.
97+
// It acts only as a carrier to exploit existing function plumbing and
98+
// the effect on behaviour is left to the implementation receiving it.
99+
func WithTrieDBUpdatePayload(parent common.Hash, current common.Hash) TrieDBUpdateOption {
100+
return options.Func[triedbUpdateConfig](func(c *triedbUpdateConfig) {
101+
c.parentBlockHash = &parent
102+
c.currentBlockHash = &current
103+
})
104+
}
105+
106+
// ExtractTrieDBUpdatePayload returns the payload carried by a [WithTrieDBUpdatePayload]
107+
// option. Only one such option can be used at once; behaviour is otherwise
108+
// undefined.
109+
func ExtractTrieDBUpdatePayload(opts ...TrieDBUpdateOption) (common.Hash, common.Hash, bool) {
110+
conf := options.As(opts...)
111+
if conf.parentBlockHash == nil && conf.currentBlockHash == nil {
112+
return common.Hash{}, common.Hash{}, false
113+
}
114+
return *conf.parentBlockHash, *conf.currentBlockHash, true
115+
}

triedb/database.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
"github.com/ava-labs/libevm/common"
2323
"github.com/ava-labs/libevm/ethdb"
24+
"github.com/ava-labs/libevm/libevm/stateconf"
2425
"github.com/ava-labs/libevm/log"
2526
"github.com/ava-labs/libevm/trie"
2627
"github.com/ava-labs/libevm/trie/trienode"
@@ -70,7 +71,7 @@ type backend interface {
7071
//
7172
// The passed in maps(nodes, states) will be retained to avoid copying
7273
// everything. Therefore, these maps must not be changed afterwards.
73-
Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error
74+
Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set, opts ...stateconf.TrieDBUpdateOption) error
7475

7576
// Commit writes all relevant trie nodes belonging to the specified state
7677
// to disk. Report specifies whether logs will be displayed in info level.
@@ -148,11 +149,11 @@ func (db *Database) Reader(blockRoot common.Hash) (database.Reader, error) {
148149
//
149150
// The passed in maps(nodes, states) will be retained to avoid copying everything.
150151
// Therefore, these maps must not be changed afterwards.
151-
func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
152+
func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set, opts ...stateconf.TrieDBUpdateOption) error {
152153
if db.preimages != nil {
153154
db.preimages.commit(false)
154155
}
155-
return db.backend.Update(root, parent, block, nodes, states)
156+
return db.backend.Update(root, parent, block, nodes, states, opts...)
156157
}
157158

158159
// Commit iterates over all the children of a particular node, writes them out

triedb/hashdb/database.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/ava-labs/libevm/core/rawdb"
2929
"github.com/ava-labs/libevm/core/types"
3030
"github.com/ava-labs/libevm/ethdb"
31+
"github.com/ava-labs/libevm/libevm/stateconf"
3132
"github.com/ava-labs/libevm/log"
3233
"github.com/ava-labs/libevm/metrics"
3334
"github.com/ava-labs/libevm/rlp"
@@ -548,7 +549,7 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool {
548549

549550
// Update inserts the dirty nodes in provided nodeset into database and link the
550551
// account trie with multiple storage tries if necessary.
551-
func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
552+
func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set, _ ...stateconf.TrieDBUpdateOption) error {
552553
// Ensure the parent state is present and signal a warning if not.
553554
if parent != types.EmptyRootHash {
554555
if blob, _ := db.node(parent); len(blob) == 0 {

triedb/pathdb/database.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/ava-labs/libevm/core/rawdb"
2828
"github.com/ava-labs/libevm/core/types"
2929
"github.com/ava-labs/libevm/ethdb"
30+
"github.com/ava-labs/libevm/libevm/stateconf"
3031
"github.com/ava-labs/libevm/log"
3132
"github.com/ava-labs/libevm/params"
3233
"github.com/ava-labs/libevm/trie/trienode"
@@ -223,7 +224,7 @@ func (db *Database) Reader(root common.Hash) (layer, error) {
223224
//
224225
// The passed in maps(nodes, states) will be retained to avoid copying everything.
225226
// Therefore, these maps must not be changed afterwards.
226-
func (db *Database) Update(root common.Hash, parentRoot common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
227+
func (db *Database) Update(root common.Hash, parentRoot common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set, _ ...stateconf.TrieDBUpdateOption) error {
227228
// Hold the lock to prevent concurrent mutations.
228229
db.lock.Lock()
229230
defer db.lock.Unlock()

0 commit comments

Comments
 (0)