Skip to content

Commit 8486d85

Browse files
author
Darioush Jalali
committed
snapshot with blockhashes
1 parent 6a3364a commit 8486d85

File tree

2 files changed

+223
-10
lines changed

2 files changed

+223
-10
lines changed

core/state/snapshot/snapshot.go

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ type Tree struct {
174174

175175
// Test hooks
176176
onFlatten func() // Hook invoked when the bottom most diff layers are flattened
177+
178+
// XXX
179+
children map[common.Hash][]common.Hash
180+
initiallyLoaded map[common.Hash]struct{}
177181
}
178182

179183
// New attempts to load an already existing snapshot from a persistent key-value
@@ -195,10 +199,12 @@ type Tree struct {
195199
func New(config Config, diskdb ethdb.KeyValueStore, triedb *triedb.Database, root common.Hash) (*Tree, error) {
196200
// Create a new, empty snapshot tree
197201
snap := &Tree{
198-
config: config,
199-
diskdb: diskdb,
200-
triedb: triedb,
201-
layers: make(map[common.Hash]snapshot),
202+
config: config,
203+
diskdb: diskdb,
204+
triedb: triedb,
205+
layers: make(map[common.Hash]snapshot),
206+
children: make(map[common.Hash][]common.Hash),
207+
initiallyLoaded: map[common.Hash]struct{}{root: {}},
202208
}
203209
// Attempt to load a previously persisted snapshot and rebuild one if failed
204210
head, disabled, err := loadSnapshot(diskdb, triedb, root, config.CacheSize, config.Recovery, config.NoBuild)
@@ -221,6 +227,7 @@ func New(config Config, diskdb ethdb.KeyValueStore, triedb *triedb.Database, roo
221227
// Existing snapshot loaded, seed all the layers
222228
for head != nil {
223229
snap.layers[head.Root()] = head
230+
snap.initiallyLoaded[head.Root()] = struct{}{}
224231
head = head.Parent()
225232
}
226233
return snap, nil
@@ -310,7 +317,17 @@ func (t *Tree) Snapshot(blockRoot common.Hash) Snapshot {
310317
t.lock.RLock()
311318
defer t.lock.RUnlock()
312319

313-
return t.layers[blockRoot]
320+
return t.byRoot(blockRoot)
321+
}
322+
323+
func (t *Tree) byRoot(blockRoot common.Hash) snapshot {
324+
for _, snap := range t.layers {
325+
if snap.Root() == blockRoot {
326+
return snap
327+
}
328+
}
329+
330+
return nil
314331
}
315332

316333
// Snapshots returns all visited layers from the topmost layer with specific
@@ -323,7 +340,7 @@ func (t *Tree) Snapshots(root common.Hash, limits int, nodisk bool) []Snapshot {
323340
if limits == 0 {
324341
return nil
325342
}
326-
layer := t.layers[root]
343+
layer := t.byRoot(root)
327344
if layer == nil {
328345
return nil
329346
}
@@ -348,28 +365,47 @@ func (t *Tree) Snapshots(root common.Hash, limits int, nodisk bool) []Snapshot {
348365

349366
// Update adds a new snapshot into the tree, if that can be linked to an existing
350367
// old parent. It is disallowed to insert a disk layer (the origin of all).
351-
func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error {
368+
func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte,
369+
opts ...LibEVMOption) error {
352370
// Reject noop updates to avoid self-loops in the snapshot tree. This is a
353371
// special case that can only happen for Clique networks where empty blocks
354372
// don't modify the state (0 block subsidy).
355373
//
356374
// Although we could silently ignore this internally, it should be the caller's
357375
// responsibility to avoid even attempting to insert such a snapshot.
358-
if blockRoot == parentRoot {
376+
opt := asLibEVMConfig(opts)
377+
layer := blockRoot
378+
if opt.hash != (common.Hash{}) {
379+
layer = opt.hash
380+
} else {
381+
panic("block hash is required")
382+
}
383+
384+
parentLayer := opt.parentHash
385+
_, parentHashKnown := t.layers[opt.parentHash]
386+
_, isInitiallyLoaded := t.initiallyLoaded[parentRoot]
387+
if !parentHashKnown && isInitiallyLoaded {
388+
// Allow use of parentRoot as the key in the tree if:
389+
// - opt.parentHash is not known,
390+
// - parentRoot is known to be initially loaded.
391+
parentLayer = parentRoot
392+
}
393+
if layer == parentLayer {
359394
return errSnapshotCycle
360395
}
361396
// Generate a new snapshot on top of the parent
362-
parent := t.Snapshot(parentRoot)
397+
parent := t.layers[parentLayer]
363398
if parent == nil {
364399
return fmt.Errorf("parent [%#x] snapshot missing", parentRoot)
365400
}
366401
snap := parent.(snapshot).Update(blockRoot, destructs, accounts, storage)
402+
t.children[parentLayer] = append(t.children[parentLayer], layer)
367403

368404
// Save the new snapshot for later
369405
t.lock.Lock()
370406
defer t.lock.Unlock()
371407

372-
t.layers[snap.root] = snap
408+
t.layers[layer] = snap
373409
return nil
374410
}
375411

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// The libevm additions to go-ethereum are free software: you can redistribute
2+
// them and/or modify them under the terms of the GNU Lesser General Public License
3+
// as published by the Free Software Foundation, either version 3 of the License,
4+
// or (at your option) any later version.
5+
//
6+
// The libevm additions are distributed in the hope that they will be useful,
7+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
8+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
9+
// General Public License for more details.
10+
//
11+
// You should have received a copy of the GNU Lesser General Public License
12+
// along with the go-ethereum library. If not, see
13+
// <http://www.gnu.org/licenses/>.
14+
15+
package snapshot
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/ava-labs/libevm/common"
21+
)
22+
23+
// A LibEVMOption configures default behaviour of this package.
24+
type LibEVMOption interface {
25+
apply(*libevmConfig)
26+
}
27+
28+
type libevmConfig struct {
29+
preserveDescendantsOnCapZero bool
30+
hash, parentHash common.Hash
31+
}
32+
33+
func asLibEVMConfig(opts []LibEVMOption) *libevmConfig {
34+
c := new(libevmConfig)
35+
for _, o := range opts {
36+
o.apply(c)
37+
}
38+
return c
39+
}
40+
41+
type libevmFuncOpt func(*libevmConfig)
42+
43+
func (f libevmFuncOpt) apply(c *libevmConfig) { f(c) }
44+
45+
// PreserveDescendantsOnCapZero signals to [Tree.Cap], if capping to zero layers
46+
// (i.e. flattening), that descendants of the flattened layer must be kept.
47+
// Without this option, the entire tree is replaced with the new base (disk)
48+
// layer.
49+
func PreserveDescendantsOnCapZero() LibEVMOption {
50+
return libevmFuncOpt(func(c *libevmConfig) {
51+
c.preserveDescendantsOnCapZero = true
52+
})
53+
}
54+
55+
func WithBlockHashes(hash, parentHash common.Hash) LibEVMOption {
56+
return libevmFuncOpt(func(c *libevmConfig) {
57+
c.hash = hash
58+
c.parentHash = parentHash
59+
})
60+
}
61+
62+
func (t *Tree) updateLayersAfterCapZero(base *diskLayer, flattened *diffLayer, opts ...LibEVMOption) {
63+
// Original geth behaviour
64+
opt := asLibEVMConfig(opts)
65+
if !opt.preserveDescendantsOnCapZero {
66+
t.layers = map[common.Hash]snapshot{base.root: base}
67+
t.children = make(map[common.Hash][]common.Hash)
68+
return
69+
}
70+
71+
children := t.children
72+
baseHash := base.root
73+
if opt.hash != (common.Hash{}) {
74+
baseHash = opt.hash
75+
}
76+
77+
newLayers := map[common.Hash]snapshot{baseHash: base}
78+
newChildren := make(map[common.Hash][]common.Hash)
79+
var keepChildren func(root common.Hash)
80+
keepChildren = func(root common.Hash) {
81+
for _, child := range children[root] {
82+
childLayer := t.layers[child]
83+
newLayers[child] = childLayer
84+
newChildren[root] = append(newChildren[root], child)
85+
keepChildren(child)
86+
}
87+
}
88+
keepChildren(baseHash)
89+
90+
for _, child := range children[baseHash] {
91+
d, ok := t.layers[child].(*diffLayer)
92+
if !ok {
93+
continue
94+
}
95+
d.lock.Lock()
96+
d.parent = base
97+
d.lock.Unlock()
98+
}
99+
t.layers = newLayers
100+
t.children = newChildren
101+
}
102+
103+
func (t *Tree) Flatten(hash common.Hash) error {
104+
t.lock.Lock()
105+
defer t.lock.Unlock()
106+
107+
// Retrieve the head snapshot to cap from
108+
snap := t.layers[hash]
109+
if snap == nil {
110+
return fmt.Errorf("snapshot [%#x] missing", hash)
111+
}
112+
diff, ok := snap.(*diffLayer)
113+
if !ok {
114+
return fmt.Errorf("snapshot [%#x] is disk layer", hash)
115+
}
116+
117+
diff.lock.RLock()
118+
base := diffToDisk(diff.flatten().(*diffLayer))
119+
diff.lock.RUnlock()
120+
121+
t.updateLayersAfterCapZero(
122+
base,
123+
diff,
124+
PreserveDescendantsOnCapZero(),
125+
WithBlockHashes(hash, common.Hash{}),
126+
)
127+
return nil
128+
}
129+
130+
func (t *Tree) Discard(hash common.Hash) error {
131+
return nil
132+
}
133+
134+
func (t *Tree) AbortGeneration() {
135+
t.lock.Lock()
136+
defer t.lock.Unlock()
137+
138+
dl := t.disklayer()
139+
140+
dl.lock.Lock()
141+
if dl.genAbort == nil {
142+
dl.lock.Unlock()
143+
return
144+
}
145+
if dl.genAbort != nil {
146+
abort := make(chan *generatorStats)
147+
dl.genAbort <- abort
148+
dl.genAbort = nil
149+
dl.lock.Unlock()
150+
<-abort
151+
}
152+
}
153+
154+
func (t *Tree) NumStateLayers() int {
155+
t.lock.RLock()
156+
defer t.lock.RUnlock()
157+
158+
return len(t.layers)
159+
}
160+
161+
func (t *Tree) NumBlockLayers() int {
162+
return t.NumStateLayers()
163+
}
164+
func (t *Tree) DiskAccountIterator(seek common.Hash) AccountIterator {
165+
t.lock.Lock()
166+
defer t.lock.Unlock()
167+
168+
return t.disklayer().AccountIterator(seek)
169+
}
170+
171+
func (t *Tree) DiskStorageIterator(account common.Hash, seek common.Hash) StorageIterator {
172+
t.lock.Lock()
173+
defer t.lock.Unlock()
174+
175+
it, _ := t.disklayer().StorageIterator(account, seek)
176+
return it
177+
}

0 commit comments

Comments
 (0)