Skip to content

Commit 05148d9

Browse files
authored
triedb/pathdb: track flat state changes in pathdb (snapshot integration pt 2) (ethereum#30643)
This pull request ports some changes from the main state snapshot integration one, specifically introducing the flat state tracking in pathdb. Note, the tracked flat state changes are only held in memory and won't be persisted in the disk. Meanwhile, the correspoding state retrieval in persistent state is also not supported yet. The states management in disk is more complicated and will be implemented in a separate pull request. Part 1: ethereum#30752
1 parent c7a8bce commit 05148d9

File tree

15 files changed

+1149
-50
lines changed

15 files changed

+1149
-50
lines changed

triedb/database.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ type backend interface {
6060
// An error will be returned if the specified state is not available.
6161
NodeReader(root common.Hash) (database.NodeReader, error)
6262

63+
// StateReader returns a reader for accessing flat states within the specified
64+
// state. An error will be returned if the specified state is not available.
65+
StateReader(root common.Hash) (database.StateReader, error)
66+
6367
// Initialized returns an indicator if the state data is already initialized
6468
// according to the state scheme.
6569
Initialized(genesisRoot common.Hash) bool
@@ -122,6 +126,13 @@ func (db *Database) NodeReader(blockRoot common.Hash) (database.NodeReader, erro
122126
return db.backend.NodeReader(blockRoot)
123127
}
124128

129+
// StateReader returns a reader that allows access to the state data associated
130+
// with the specified state. An error will be returned if the specified state is
131+
// not available.
132+
func (db *Database) StateReader(blockRoot common.Hash) (database.StateReader, error) {
133+
return db.backend.StateReader(blockRoot)
134+
}
135+
125136
// Update performs a state transition by committing dirty nodes contained in the
126137
// given set in order to update state from the specified parent to the specified
127138
// root. The held pre-images accumulated up to this point will be flushed in case

triedb/hashdb/database.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,3 +635,9 @@ func (reader *reader) Node(owner common.Hash, path []byte, hash common.Hash) ([]
635635
blob, _ := reader.db.node(hash)
636636
return blob, nil
637637
}
638+
639+
// StateReader returns a reader that allows access to the state data associated
640+
// with the specified state.
641+
func (db *Database) StateReader(root common.Hash) (database.StateReader, error) {
642+
return nil, errors.New("not implemented")
643+
}

triedb/pathdb/buffer.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,40 +33,56 @@ import (
3333
// must be checked before diving into disk (since it basically is not yet written
3434
// data).
3535
type buffer struct {
36-
layers uint64 // The number of diff layers aggregated inside
37-
limit uint64 // The maximum memory allowance in bytes
38-
nodes *nodeSet // Aggregated trie node set
36+
layers uint64 // The number of diff layers aggregated inside
37+
limit uint64 // The maximum memory allowance in bytes
38+
nodes *nodeSet // Aggregated trie node set
39+
states *stateSet // Aggregated state set
3940
}
4041

4142
// newBuffer initializes the buffer with the provided states and trie nodes.
42-
func newBuffer(limit int, nodes *nodeSet, layers uint64) *buffer {
43+
func newBuffer(limit int, nodes *nodeSet, states *stateSet, layers uint64) *buffer {
4344
// Don't panic for lazy users if any provided set is nil
4445
if nodes == nil {
4546
nodes = newNodeSet(nil)
4647
}
48+
if states == nil {
49+
states = newStates(nil, nil)
50+
}
4751
return &buffer{
4852
layers: layers,
4953
limit: uint64(limit),
5054
nodes: nodes,
55+
states: states,
5156
}
5257
}
5358

59+
// account retrieves the account blob with account address hash.
60+
func (b *buffer) account(hash common.Hash) ([]byte, bool) {
61+
return b.states.account(hash)
62+
}
63+
64+
// storage retrieves the storage slot with account address hash and slot key.
65+
func (b *buffer) storage(addrHash common.Hash, storageHash common.Hash) ([]byte, bool) {
66+
return b.states.storage(addrHash, storageHash)
67+
}
68+
5469
// node retrieves the trie node with node path and its trie identifier.
5570
func (b *buffer) node(owner common.Hash, path []byte) (*trienode.Node, bool) {
5671
return b.nodes.node(owner, path)
5772
}
5873

5974
// commit merges the provided states and trie nodes into the buffer.
60-
func (b *buffer) commit(nodes *nodeSet) *buffer {
75+
func (b *buffer) commit(nodes *nodeSet, states *stateSet) *buffer {
6176
b.layers++
6277
b.nodes.merge(nodes)
78+
b.states.merge(states)
6379
return b
6480
}
6581

66-
// revert is the reverse operation of commit. It also merges the provided states
82+
// revertTo is the reverse operation of commit. It also merges the provided states
6783
// and trie nodes into the buffer. The key difference is that the provided state
6884
// set should reverse the changes made by the most recent state transition.
69-
func (b *buffer) revert(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) error {
85+
func (b *buffer) revertTo(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node, accounts map[common.Hash][]byte, storages map[common.Hash]map[common.Hash][]byte) error {
7086
// Short circuit if no embedded state transition to revert
7187
if b.layers == 0 {
7288
return errStateUnrecoverable
@@ -78,14 +94,16 @@ func (b *buffer) revert(db ethdb.KeyValueReader, nodes map[common.Hash]map[strin
7894
b.reset()
7995
return nil
8096
}
81-
b.nodes.revert(db, nodes)
97+
b.nodes.revertTo(db, nodes)
98+
b.states.revertTo(accounts, storages)
8299
return nil
83100
}
84101

85102
// reset cleans up the disk cache.
86103
func (b *buffer) reset() {
87104
b.layers = 0
88105
b.nodes.reset()
106+
b.states.reset()
89107
}
90108

91109
// empty returns an indicator if buffer is empty.
@@ -101,7 +119,7 @@ func (b *buffer) full() bool {
101119

102120
// size returns the approximate memory size of the held content.
103121
func (b *buffer) size() uint64 {
104-
return b.nodes.size
122+
return b.states.size + b.nodes.size
105123
}
106124

107125
// flush persists the in-memory dirty trie node into the disk if the configured

triedb/pathdb/database.go

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,24 @@ type layer interface {
6868
// - no error will be returned if the requested node is not found in database.
6969
node(owner common.Hash, path []byte, depth int) ([]byte, common.Hash, *nodeLoc, error)
7070

71+
// account directly retrieves the account RLP associated with a particular
72+
// hash in the slim data format. An error will be returned if the read
73+
// operation exits abnormally. Specifically, if the layer is already stale.
74+
//
75+
// Note:
76+
// - the returned account is not a copy, please don't modify it.
77+
// - no error will be returned if the requested account is not found in database.
78+
account(hash common.Hash, depth int) ([]byte, error)
79+
80+
// storage directly retrieves the storage data associated with a particular hash,
81+
// within a particular account. An error will be returned if the read operation
82+
// exits abnormally. Specifically, if the layer is already stale.
83+
//
84+
// Note:
85+
// - the returned storage data is not a copy, please don't modify it.
86+
// - no error will be returned if the requested slot is not found in database.
87+
storage(accountHash, storageHash common.Hash, depth int) ([]byte, error)
88+
7189
// rootHash returns the root hash for which this layer was made.
7290
rootHash() common.Hash
7391

@@ -130,17 +148,18 @@ var Defaults = &Config{
130148
// ReadOnly is the config in order to open database in read only mode.
131149
var ReadOnly = &Config{ReadOnly: true}
132150

133-
// Database is a multiple-layered structure for maintaining in-memory trie nodes.
134-
// It consists of one persistent base layer backed by a key-value store, on top
135-
// of which arbitrarily many in-memory diff layers are stacked. The memory diffs
136-
// can form a tree with branching, but the disk layer is singleton and common to
137-
// all. If a reorg goes deeper than the disk layer, a batch of reverse diffs can
138-
// be applied to rollback. The deepest reorg that can be handled depends on the
139-
// amount of state histories tracked in the disk.
151+
// Database is a multiple-layered structure for maintaining in-memory states
152+
// along with its dirty trie nodes. It consists of one persistent base layer
153+
// backed by a key-value store, on top of which arbitrarily many in-memory diff
154+
// layers are stacked. The memory diffs can form a tree with branching, but the
155+
// disk layer is singleton and common to all. If a reorg goes deeper than the
156+
// disk layer, a batch of reverse diffs can be applied to rollback. The deepest
157+
// reorg that can be handled depends on the amount of state histories tracked
158+
// in the disk.
140159
//
141160
// At most one readable and writable database can be opened at the same time in
142-
// the whole system which ensures that only one database writer can operate disk
143-
// state. Unexpected open operations can cause the system to panic.
161+
// the whole system which ensures that only one database writer can operate the
162+
// persistent state. Unexpected open operations can cause the system to panic.
144163
type Database struct {
145164
// readOnly is the flag whether the mutation is allowed to be applied.
146165
// It will be set automatically when the database is journaled during
@@ -358,7 +377,7 @@ func (db *Database) Enable(root common.Hash) error {
358377
}
359378
// Re-construct a new disk layer backed by persistent state
360379
// with **empty clean cache and node buffer**.
361-
db.tree.reset(newDiskLayer(root, 0, db, nil, newBuffer(db.config.WriteBufferSize, nil, 0)))
380+
db.tree.reset(newDiskLayer(root, 0, db, nil, newBuffer(db.config.WriteBufferSize, nil, nil, 0)))
362381

363382
// Re-enable the database as the final step.
364383
db.waitSync = false

triedb/pathdb/database_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ func (t *tester) generate(parent common.Hash) (common.Hash, *trienode.MergedNode
309309
delete(t.storages, addrHash)
310310
}
311311
}
312-
return root, ctx.nodes, NewStateSetWithOrigin(ctx.accountOrigin, ctx.storageOrigin)
312+
return root, ctx.nodes, NewStateSetWithOrigin(ctx.accounts, ctx.storages, ctx.accountOrigin, ctx.storageOrigin)
313313
}
314314

315315
// lastHash returns the latest root hash, or empty if nothing is cached.

triedb/pathdb/difflayer.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ func newDiffLayer(parent layer, root common.Hash, id uint64, block uint64, nodes
5252
states: states,
5353
}
5454
dirtyNodeWriteMeter.Mark(int64(nodes.size))
55+
dirtyStateWriteMeter.Mark(int64(states.size))
5556
log.Debug("Created new diff layer", "id", id, "block", block, "nodesize", common.StorageSize(nodes.size), "statesize", common.StorageSize(states.size))
5657
return dl
5758
}
@@ -96,6 +97,58 @@ func (dl *diffLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co
9697
return dl.parent.node(owner, path, depth+1)
9798
}
9899

100+
// account directly retrieves the account RLP associated with a particular
101+
// hash in the slim data format.
102+
//
103+
// Note the returned account is not a copy, please don't modify it.
104+
func (dl *diffLayer) account(hash common.Hash, depth int) ([]byte, error) {
105+
// Hold the lock, ensure the parent won't be changed during the
106+
// state accessing.
107+
dl.lock.RLock()
108+
defer dl.lock.RUnlock()
109+
110+
if blob, found := dl.states.account(hash); found {
111+
dirtyStateHitMeter.Mark(1)
112+
dirtyStateHitDepthHist.Update(int64(depth))
113+
dirtyStateReadMeter.Mark(int64(len(blob)))
114+
115+
if len(blob) == 0 {
116+
stateAccountInexMeter.Mark(1)
117+
} else {
118+
stateAccountExistMeter.Mark(1)
119+
}
120+
return blob, nil
121+
}
122+
// Account is unknown to this layer, resolve from parent
123+
return dl.parent.account(hash, depth+1)
124+
}
125+
126+
// storage directly retrieves the storage data associated with a particular hash,
127+
// within a particular account.
128+
//
129+
// Note the returned storage slot is not a copy, please don't modify it.
130+
func (dl *diffLayer) storage(accountHash, storageHash common.Hash, depth int) ([]byte, error) {
131+
// Hold the lock, ensure the parent won't be changed during the
132+
// state accessing.
133+
dl.lock.RLock()
134+
defer dl.lock.RUnlock()
135+
136+
if blob, found := dl.states.storage(accountHash, storageHash); found {
137+
dirtyStateHitMeter.Mark(1)
138+
dirtyStateHitDepthHist.Update(int64(depth))
139+
dirtyStateReadMeter.Mark(int64(len(blob)))
140+
141+
if len(blob) == 0 {
142+
stateStorageInexMeter.Mark(1)
143+
} else {
144+
stateStorageExistMeter.Mark(1)
145+
}
146+
return blob, nil
147+
}
148+
// storage slot is unknown to this layer, resolve from parent
149+
return dl.parent.storage(accountHash, storageHash, depth+1)
150+
}
151+
99152
// update implements the layer interface, creating a new layer on top of the
100153
// existing layer tree with the specified data items.
101154
func (dl *diffLayer) update(root common.Hash, id uint64, block uint64, nodes *nodeSet, states *StateSetWithOrigin) *diffLayer {

triedb/pathdb/difflayer_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import (
3030
func emptyLayer() *diskLayer {
3131
return &diskLayer{
3232
db: New(rawdb.NewMemoryDatabase(), nil, false),
33-
buffer: newBuffer(defaultBufferSize, nil, 0),
33+
buffer: newBuffer(defaultBufferSize, nil, nil, 0),
3434
}
3535
}
3636

@@ -76,7 +76,7 @@ func benchmarkSearch(b *testing.B, depth int, total int) {
7676
nblob = common.CopyBytes(blob)
7777
}
7878
}
79-
return newDiffLayer(parent, common.Hash{}, 0, 0, newNodeSet(nodes), NewStateSetWithOrigin(nil, nil))
79+
return newDiffLayer(parent, common.Hash{}, 0, 0, newNodeSet(nodes), NewStateSetWithOrigin(nil, nil, nil, nil))
8080
}
8181
var layer layer
8282
layer = emptyLayer()
@@ -118,7 +118,7 @@ func BenchmarkPersist(b *testing.B) {
118118
)
119119
nodes[common.Hash{}][string(path)] = node
120120
}
121-
return newDiffLayer(parent, common.Hash{}, 0, 0, newNodeSet(nodes), NewStateSetWithOrigin(nil, nil))
121+
return newDiffLayer(parent, common.Hash{}, 0, 0, newNodeSet(nodes), NewStateSetWithOrigin(nil, nil, nil, nil))
122122
}
123123
for i := 0; i < b.N; i++ {
124124
b.StopTimer()
@@ -156,7 +156,7 @@ func BenchmarkJournal(b *testing.B) {
156156
)
157157
nodes[common.Hash{}][string(path)] = node
158158
}
159-
return newDiffLayer(parent, common.Hash{}, 0, 0, newNodeSet(nodes), new(StateSetWithOrigin))
159+
return newDiffLayer(parent, common.Hash{}, 0, 0, newNodeSet(nodes), NewStateSetWithOrigin(nil, nil, nil, nil))
160160
}
161161
var layer layer
162162
layer = emptyLayer()

0 commit comments

Comments
 (0)