Skip to content

Commit deb27b6

Browse files
committed
triedb/pathdb: introduce lookup structure to optimize state access
1 parent 6ad7414 commit deb27b6

File tree

8 files changed

+1359
-52
lines changed

8 files changed

+1359
-52
lines changed

triedb/pathdb/database.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ func (db *Database) Enable(root common.Hash) error {
477477

478478
// Re-construct a new disk layer backed by persistent state
479479
// and schedule the state snapshot generation if it's permitted.
480-
db.tree.reset(generateSnapshot(db, root, db.isVerkle || db.config.SnapshotNoBuild))
480+
db.tree.init(generateSnapshot(db, root, db.isVerkle || db.config.SnapshotNoBuild))
481481
log.Info("Rebuilt trie database", "root", root)
482482
return nil
483483
}
@@ -519,7 +519,7 @@ func (db *Database) Recover(root common.Hash) error {
519519
// reset layer with newly created disk layer. It must be
520520
// done after each revert operation, otherwise the new
521521
// disk layer won't be accessible from outside.
522-
db.tree.reset(dl)
522+
db.tree.init(dl)
523523
}
524524
rawdb.DeleteTrieJournal(db.diskdb)
525525
_, err := truncateFromHead(db.diskdb, db.freezer, dl.stateID())

triedb/pathdb/difflayer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ func (dl *diffLayer) update(root common.Hash, id uint64, block uint64, nodes *no
156156
}
157157

158158
// persist flushes the diff layer and all its parent layers to disk layer.
159-
func (dl *diffLayer) persist(force bool) (layer, error) {
159+
func (dl *diffLayer) persist(force bool) (*diskLayer, error) {
160160
if parent, ok := dl.parentLayer().(*diffLayer); ok {
161161
// Hold the lock to prevent any read operation until the new
162162
// parent is linked correctly.
@@ -183,7 +183,7 @@ func (dl *diffLayer) size() uint64 {
183183

184184
// diffToDisk merges a bottom-most diff into the persistent disk layer underneath
185185
// it. The method will panic if called onto a non-bottom-most diff layer.
186-
func diffToDisk(layer *diffLayer, force bool) (layer, error) {
186+
func diffToDisk(layer *diffLayer, force bool) (*diskLayer, error) {
187187
disk, ok := layer.parentLayer().(*diskLayer)
188188
if !ok {
189189
panic(fmt.Sprintf("unknown layer type: %T", layer.parentLayer()))

triedb/pathdb/disklayer.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,6 @@ func (dl *diskLayer) setGenerator(generator *generator) {
8787
dl.generator = generator
8888
}
8989

90-
// isStale return whether this layer has become stale (was flattened across) or if
91-
// it's still live.
92-
func (dl *diskLayer) isStale() bool {
93-
dl.lock.RLock()
94-
defer dl.lock.RUnlock()
95-
96-
return dl.stale
97-
}
98-
9990
// markStale sets the stale flag as true.
10091
func (dl *diskLayer) markStale() {
10192
dl.lock.Lock()

triedb/pathdb/layertree.go

Lines changed: 145 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,41 @@ import (
3131
// thread-safe to use. However, callers need to ensure the thread-safety
3232
// of the referenced layer by themselves.
3333
type layerTree struct {
34-
lock sync.RWMutex
35-
layers map[common.Hash]layer
34+
base *diskLayer
35+
layers map[common.Hash]layer
36+
descendants map[common.Hash]map[common.Hash]struct{}
37+
lookup *lookup
38+
lock sync.RWMutex
3639
}
3740

3841
// newLayerTree constructs the layerTree with the given head layer.
3942
func newLayerTree(head layer) *layerTree {
4043
tree := new(layerTree)
41-
tree.reset(head)
44+
tree.init(head)
4245
return tree
4346
}
4447

45-
// reset initializes the layerTree by the given head layer.
46-
// All the ancestors will be iterated out and linked in the tree.
47-
func (tree *layerTree) reset(head layer) {
48+
// init initializes the layerTree by the given head layer.
49+
func (tree *layerTree) init(head layer) {
4850
tree.lock.Lock()
4951
defer tree.lock.Unlock()
5052

51-
var layers = make(map[common.Hash]layer)
52-
for head != nil {
53-
layers[head.rootHash()] = head
54-
head = head.parentLayer()
53+
current := head
54+
tree.layers = make(map[common.Hash]layer)
55+
tree.descendants = make(map[common.Hash]map[common.Hash]struct{})
56+
57+
for {
58+
tree.layers[current.rootHash()] = current
59+
tree.fillAncestors(current)
60+
61+
parent := current.parentLayer()
62+
if parent == nil {
63+
break
64+
}
65+
current = parent
5566
}
56-
tree.layers = layers
67+
tree.base = current.(*diskLayer) // panic if it's not a disk layer
68+
tree.lookup = newLookup(head, tree.isDescendant)
5769
}
5870

5971
// get retrieves a layer belonging to the given state root.
@@ -64,6 +76,43 @@ func (tree *layerTree) get(root common.Hash) layer {
6476
return tree.layers[root]
6577
}
6678

79+
// isDescendant returns whether the specified layer with given root is a
80+
// descendant of a specific ancestor.
81+
//
82+
// This function assumes the read lock has been held.
83+
func (tree *layerTree) isDescendant(root common.Hash, ancestor common.Hash) bool {
84+
subset := tree.descendants[ancestor]
85+
if subset == nil {
86+
return false
87+
}
88+
_, ok := subset[root]
89+
return ok
90+
}
91+
92+
// fillAncestors identifies the ancestors of the given layer and populates the
93+
// descendants set. The ancestors include the diff layers below the supplied
94+
// layer and also the disk layer.
95+
//
96+
// This function assumes the write lock has been held.
97+
func (tree *layerTree) fillAncestors(layer layer) {
98+
hash := layer.rootHash()
99+
for {
100+
parent := layer.parentLayer()
101+
if parent == nil {
102+
break
103+
}
104+
layer = parent
105+
106+
phash := parent.rootHash()
107+
subset := tree.descendants[phash]
108+
if subset == nil {
109+
subset = make(map[common.Hash]struct{})
110+
tree.descendants[phash] = subset
111+
}
112+
subset[hash] = struct{}{}
113+
}
114+
}
115+
67116
// forEach iterates the stored layers inside and applies the
68117
// given callback on them.
69118
func (tree *layerTree) forEach(onLayer func(layer)) {
@@ -101,8 +150,11 @@ func (tree *layerTree) add(root common.Hash, parentRoot common.Hash, block uint6
101150
l := parent.update(root, parent.stateID()+1, block, newNodeSet(nodes.Flatten()), states)
102151

103152
tree.lock.Lock()
153+
defer tree.lock.Unlock()
154+
104155
tree.layers[l.rootHash()] = l
105-
tree.lock.Unlock()
156+
tree.fillAncestors(l)
157+
tree.lookup.addLayer(l)
106158
return nil
107159
}
108160

@@ -127,8 +179,14 @@ func (tree *layerTree) cap(root common.Hash, layers int) error {
127179
if err != nil {
128180
return err
129181
}
130-
// Replace the entire layer tree with the flat base
131-
tree.layers = map[common.Hash]layer{base.rootHash(): base}
182+
tree.base = base
183+
184+
// Reset the layer tree with the single new disk layer
185+
tree.layers = map[common.Hash]layer{
186+
base.rootHash(): base,
187+
}
188+
tree.descendants = make(map[common.Hash]map[common.Hash]struct{})
189+
tree.lookup = newLookup(base, tree.isDescendant)
132190
return nil
133191
}
134192
// Dive until we run out of layers or reach the persistent database
@@ -143,6 +201,11 @@ func (tree *layerTree) cap(root common.Hash, layers int) error {
143201
}
144202
// We're out of layers, flatten anything below, stopping if it's the disk or if
145203
// the memory limit is not yet exceeded.
204+
var (
205+
err error
206+
replaced layer
207+
newBase *diskLayer
208+
)
146209
switch parent := diff.parentLayer().(type) {
147210
case *diskLayer:
148211
return nil
@@ -152,14 +215,33 @@ func (tree *layerTree) cap(root common.Hash, layers int) error {
152215
// parent is linked correctly.
153216
diff.lock.Lock()
154217

155-
base, err := parent.persist(false)
218+
// Hold the reference of the original layer being replaced
219+
replaced = parent
220+
221+
// Replace the original parent layer with new disk layer. The procedure
222+
// can be illustrated as below:
223+
//
224+
// Before change:
225+
// Chain:
226+
// C1->C2->C3->C4 (HEAD)
227+
// ->C2'->C3'->C4'
228+
//
229+
// After change:
230+
// Chain:
231+
// (a) C3->C4 (HEAD)
232+
// (b) C1->C2
233+
// ->C2'->C3'->C4'
234+
// The original C3 is replaced by the new base (with root C3)
235+
// Dangling layers in (b) will be removed later
236+
newBase, err = parent.persist(false)
156237
if err != nil {
157238
diff.lock.Unlock()
158239
return err
159240
}
160-
tree.layers[base.rootHash()] = base
161-
diff.parent = base
241+
tree.layers[newBase.rootHash()] = newBase
162242

243+
// Link the new parent and release the lock
244+
diff.parent = newBase
163245
diff.lock.Unlock()
164246

165247
default:
@@ -173,19 +255,28 @@ func (tree *layerTree) cap(root common.Hash, layers int) error {
173255
children[parent] = append(children[parent], root)
174256
}
175257
}
258+
clearDiff := func(layer layer) {
259+
diff, ok := layer.(*diffLayer)
260+
if !ok {
261+
return
262+
}
263+
tree.lookup.removeLayer(diff)
264+
}
176265
var remove func(root common.Hash)
177266
remove = func(root common.Hash) {
267+
clearDiff(tree.layers[root])
268+
269+
// Unlink the layer from the layer tree and cascade to its children
270+
delete(tree.descendants, root)
178271
delete(tree.layers, root)
179272
for _, child := range children[root] {
180273
remove(child)
181274
}
182275
delete(children, root)
183276
}
184-
for root, layer := range tree.layers {
185-
if dl, ok := layer.(*diskLayer); ok && dl.isStale() {
186-
remove(root)
187-
}
188-
}
277+
remove(tree.base.rootHash()) // remove the old/stale disk layer
278+
clearDiff(replaced) // remove the lookup data of the stale parent being replaced
279+
tree.base = newBase // update the base layer with newly constructed one
189280
return nil
190281
}
191282

@@ -194,17 +285,39 @@ func (tree *layerTree) bottom() *diskLayer {
194285
tree.lock.RLock()
195286
defer tree.lock.RUnlock()
196287

197-
if len(tree.layers) == 0 {
198-
return nil // Shouldn't happen, empty tree
288+
return tree.base
289+
}
290+
291+
// lookupAccount returns the layer that is confirmed to contain the account data
292+
// being searched for.
293+
func (tree *layerTree) lookupAccount(accountHash common.Hash, state common.Hash) (layer, error) {
294+
tree.lock.RLock()
295+
defer tree.lock.RUnlock()
296+
297+
tip := tree.lookup.accountTip(accountHash, state, tree.base.root)
298+
if tip == (common.Hash{}) {
299+
return nil, fmt.Errorf("[%#x] %w", state, errSnapshotStale)
199300
}
200-
// pick a random one as the entry point
201-
var current layer
202-
for _, layer := range tree.layers {
203-
current = layer
204-
break
301+
l := tree.layers[tip]
302+
if l == nil {
303+
return nil, fmt.Errorf("triedb layer [%#x] missing", tip)
205304
}
206-
for current.parentLayer() != nil {
207-
current = current.parentLayer()
305+
return l, nil
306+
}
307+
308+
// lookupStorage returns the layer that is confirmed to contain the storage slot
309+
// data being searched for.
310+
func (tree *layerTree) lookupStorage(accountHash common.Hash, slotHash common.Hash, state common.Hash) (layer, error) {
311+
tree.lock.RLock()
312+
defer tree.lock.RUnlock()
313+
314+
tip := tree.lookup.storageTip(accountHash, slotHash, state, tree.base.root)
315+
if tip == (common.Hash{}) {
316+
return nil, fmt.Errorf("[%#x] %w", state, errSnapshotStale)
317+
}
318+
l := tree.layers[tip]
319+
if l == nil {
320+
return nil, fmt.Errorf("triedb layer [%#x] missing", tip)
208321
}
209-
return current.(*diskLayer)
322+
return l, nil
210323
}

0 commit comments

Comments
 (0)