Skip to content

Commit d0289cc

Browse files
committed
cmd, core, eth: fixes
1 parent f00c799 commit d0289cc

File tree

3 files changed

+147
-36
lines changed

3 files changed

+147
-36
lines changed

cmd/geth/snapshot.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,12 @@ func pruneState(ctx *cli.Context) error {
149149
chain, chaindb := utils.MakeChain(ctx, stack, true)
150150
defer chaindb.Close()
151151

152-
cachePath := stack.ResolvePath(eth.DefaultConfig.TrieCleanCacheJournal)
152+
trieCacheName := eth.DefaultConfig.TrieCleanCacheJournal
153153
if ctx.GlobalIsSet(utils.CacheTrieJournalFlag.Name) {
154-
cachePath = ctx.GlobalString(utils.CacheTrieJournalFlag.Name)
155-
cachePath = stack.ResolvePath(cachePath)
156-
log.Info("Customized trie clean cache specified", "path", cachePath)
154+
trieCacheName = ctx.GlobalString(utils.CacheTrieJournalFlag.Name)
155+
log.Info("Customized trie clean cache specified", "path", stack.ResolvePath(trieCacheName))
157156
}
158-
headHeader := chain.CurrentBlock().Header()
159-
pruner, err := pruner.NewPruner(chaindb, headHeader, stack.ResolvePath(""), cachePath)
157+
pruner, err := pruner.NewPruner(chaindb, chain.CurrentBlock().Header(), stack.ResolvePath(""), trieCacheName)
160158
if err != nil {
161159
utils.Fatalf("Failed to open snapshot tree %v", err)
162160
}

core/state/pruner/pruner.go

Lines changed: 142 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"math/big"
2424
"os"
2525
"path/filepath"
26+
"strings"
2627
"time"
2728

2829
"github.com/ethereum/go-ethereum/common"
@@ -37,8 +38,11 @@ import (
3738
)
3839

3940
const (
40-
// stateBloomFileName is the filename of state bloom filter.
41-
stateBloomFileName = "statebloom.bf.gz"
41+
// stateBloomFilePrefix is the filename prefix of state bloom filter.
42+
stateBloomFilePrefix = "statebloom"
43+
44+
// stateBloomFilePrefix is the filename suffix of state bloom filter.
45+
stateBloomFileSuffix = "bf.gz"
4246

4347
// bloomFilterEntries is the estimated value of the number of trie nodes
4448
// and codes contained in the state. It's designed for mainnet but also
@@ -64,16 +68,16 @@ var (
6468
)
6569

6670
type Pruner struct {
67-
db ethdb.Database
68-
stateBloom *StateBloom
69-
stateBloomPath string
70-
cachePath string
71-
headHeader *types.Header
72-
snaptree *snapshot.Tree
71+
db ethdb.Database
72+
stateBloom *StateBloom
73+
homeDir string
74+
trieCacheName string
75+
headHeader *types.Header
76+
snaptree *snapshot.Tree
7377
}
7478

7579
// NewPruner creates the pruner instance.
76-
func NewPruner(db ethdb.Database, headHeader *types.Header, homedir, cachePath string) (*Pruner, error) {
80+
func NewPruner(db ethdb.Database, headHeader *types.Header, homeDir, trieCacheName string) (*Pruner, error) {
7781
snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headHeader.Root, false, false, false)
7882
if err != nil {
7983
return nil, err // The relevant snapshot(s) might not exist
@@ -87,12 +91,12 @@ func NewPruner(db ethdb.Database, headHeader *types.Header, homedir, cachePath s
8791
return nil, err
8892
}
8993
return &Pruner{
90-
db: db,
91-
stateBloom: stateBloom,
92-
stateBloomPath: filepath.Join(homedir, stateBloomFileName),
93-
cachePath: cachePath,
94-
headHeader: headHeader,
95-
snaptree: snaptree,
94+
db: db,
95+
stateBloom: stateBloom,
96+
homeDir: homeDir,
97+
trieCacheName: trieCacheName,
98+
headHeader: headHeader,
99+
snaptree: snaptree,
96100
}, nil
97101
}
98102

@@ -196,6 +200,17 @@ func prune(maindb ethdb.Database, stateBloom *StateBloom, start time.Time) error
196200
// specified state version. If user doesn't specify the state version, use
197201
// the persisted snapshot disk layer as the target.
198202
func (p *Pruner) Prune(root common.Hash) error {
203+
// Ensure there is no previously committed state bloom filter.
204+
// If the state bloom filter is already committed, just restart
205+
// the Geth normally, the recovery procedure will resume all
206+
// interrupted operations based on this filter.
207+
stateBloomPath, stateBloomRoot, err := findBloomFilter(p.homeDir)
208+
if err != nil {
209+
return err
210+
}
211+
if stateBloomRoot != (common.Hash{}) {
212+
return fmt.Errorf("state bloom filter exists, root: %x, path: %s", stateBloomRoot, stateBloomPath)
213+
}
199214
// If the target state root is not specified, use the HEAD-127 as the
200215
// target. The reason for picking it is:
201216
// - in most of the normal cases, the related state is available
@@ -256,37 +271,42 @@ func (p *Pruner) Prune(root common.Hash) error {
256271
// It's necessary otherwise in the next restart we will hit the
257272
// deleted state root in the "clean cache" so that the incomplete
258273
// state is picked for usage.
259-
os.RemoveAll(p.cachePath)
260-
log.Info("Deleted trie clean cache", "path", p.cachePath)
274+
cachePath := filepath.Join(p.homeDir, p.trieCacheName)
275+
os.RemoveAll(cachePath)
276+
log.Info("Deleted trie clean cache", "path", cachePath)
261277

262278
start := time.Now()
263279
// Traverse the target state, re-construct the whole state trie and
264280
// commit to the given bloom filter.
265281
if err := snapshot.CommitAndVerifyState(p.snaptree, root, p.db, p.stateBloom); err != nil {
266282
return err
267283
}
268-
if err := p.stateBloom.Commit(p.stateBloomPath); err != nil {
284+
filterName := bloomFilterName(p.homeDir, root)
285+
if err := p.stateBloom.Commit(filterName); err != nil {
269286
return err
270287
}
288+
log.Info("Committed the state bloom filter", "name", filterName)
271289
if err := prune(p.db, p.stateBloom, start); err != nil {
272290
return err
273291
}
274292
// Pruning is done, now drop the "useless" layers from the snapshot.
275293
// Firstly, flushing the target layer into the disk. After that all
276294
// diff layers below the target will all be merged into the disk.
277-
p.snaptree.Cap(root, 0)
278-
295+
if err := p.snaptree.Cap(root, 0); err != nil {
296+
return err
297+
}
279298
// Secondly, flushing the snapshot journal into the disk. All diff
280-
// layers upon are dropped silently. Eventually the entire snapshot
281-
// tree is converted into a single disk layer with the pruning target
282-
// as the root.
283-
p.snaptree.Journal(root)
284-
299+
// layers upon the target layer are dropped silently. Eventually the
300+
// entire snapshot tree is converted into a single disk layer with
301+
// the pruning target as the root.
302+
if _, err := p.snaptree.Journal(root); err != nil {
303+
return err
304+
}
285305
// Delete the state bloom, it marks the entire pruning procedure is
286306
// finished. If any crashes or manual exit happens before this,
287307
// `RecoverPruning` will pick it up in the next restarts to redo all
288308
// the things.
289-
os.RemoveAll(p.stateBloomPath)
309+
os.RemoveAll(filterName)
290310
return nil
291311
}
292312

@@ -297,18 +317,63 @@ func (p *Pruner) Prune(root common.Hash) error {
297317
// pruning can be resumed. What's more if the bloom filter is constructed, the
298318
// pruning **has to be resumed**. Otherwise a lot of dangling nodes may be left
299319
// in the disk.
300-
func RecoverPruning(homedir string, db ethdb.Database) error {
301-
stateBloomPath := filepath.Join(homedir, stateBloomFileName)
302-
if _, err := os.Stat(stateBloomPath); os.IsNotExist(err) {
320+
func RecoverPruning(homedir string, db ethdb.Database, trieCachePath string) error {
321+
stateBloomPath, stateBloomRoot, err := findBloomFilter(homedir)
322+
if err != nil {
323+
return err
324+
}
325+
if stateBloomPath == "" {
303326
return nil // nothing to recover
304327
}
328+
headHeader, err := getHeadHeader(db)
329+
if err != nil {
330+
return err
331+
}
332+
// Initialize the snapshot tree in recovery mode to handle this special case:
333+
// - Users run the `prune-state` command multiple times
334+
// - Neither these `prune-state` running is finished(e.g. interrupted manually)
335+
// - The state bloom filter is already generated, a part of state is deleted,
336+
// so that resuming the pruning here is necessary
337+
// - The state HEAD is rewound already because of multiple incomplete `prune-state`
338+
// In this case, even the state HEAD is not exactly matched with snapshot, it
339+
// still feasible to recover the pruning correctly.
340+
snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headHeader.Root, false, false, true)
341+
if err != nil {
342+
return err // The relevant snapshot(s) might not exist
343+
}
305344
stateBloom, err := NewStateBloomFromDisk(stateBloomPath)
306345
if err != nil {
307346
return err
308347
}
348+
log.Info("Loaded the state bloom filter", "path", stateBloomPath)
349+
350+
// Before start the pruning, delete the clean trie cache first.
351+
// It's necessary otherwise in the next restart we will hit the
352+
// deleted state root in the "clean cache" so that the incomplete
353+
// state is picked for usage.
354+
os.RemoveAll(trieCachePath)
355+
log.Info("Deleted trie clean cache", "path", trieCachePath)
356+
309357
if err := prune(db, stateBloom, time.Now()); err != nil {
310358
return err
311359
}
360+
// Pruning is done, now drop the "useless" layers from the snapshot.
361+
// Firstly, flushing the target layer into the disk. After that all
362+
// diff layers below the target will all be merged into the disk.
363+
if err := snaptree.Cap(stateBloomRoot, 0); err != nil {
364+
return err
365+
}
366+
// Secondly, flushing the snapshot journal into the disk. All diff
367+
// layers upon are dropped silently. Eventually the entire snapshot
368+
// tree is converted into a single disk layer with the pruning target
369+
// as the root.
370+
if _, err := snaptree.Journal(stateBloomRoot); err != nil {
371+
return err
372+
}
373+
// Delete the state bloom, it marks the entire pruning procedure is
374+
// finished. If any crashes or manual exit happens before this,
375+
// `RecoverPruning` will pick it up in the next restarts to redo all
376+
// the things.
312377
os.RemoveAll(stateBloomPath)
313378
return nil
314379
}
@@ -375,3 +440,51 @@ func extractGenesis(db ethdb.Database) (map[common.Hash]struct{}, error) {
375440
}
376441
return marker, nil
377442
}
443+
444+
func bloomFilterName(homedir string, hash common.Hash) string {
445+
return filepath.Join(homedir, fmt.Sprintf("%s.%s.%s", stateBloomFilePrefix, hash.Hex(), stateBloomFileSuffix))
446+
}
447+
448+
func isBloomFilter(filename string) (bool, common.Hash) {
449+
filename = filepath.Base(filename)
450+
if strings.HasPrefix(filename, stateBloomFilePrefix) && strings.HasSuffix(filename, stateBloomFileSuffix) {
451+
return true, common.HexToHash(filename[len(stateBloomFilePrefix)+1 : len(filename)-len(stateBloomFileSuffix)-1])
452+
}
453+
return false, common.Hash{}
454+
}
455+
456+
func findBloomFilter(homedir string) (string, common.Hash, error) {
457+
var (
458+
stateBloomPath string
459+
stateBloomRoot common.Hash
460+
)
461+
if err := filepath.Walk(homedir, func(path string, info os.FileInfo, err error) error {
462+
if !info.IsDir() {
463+
ok, root := isBloomFilter(path)
464+
if ok {
465+
stateBloomPath = path
466+
stateBloomRoot = root
467+
}
468+
}
469+
return nil
470+
}); err != nil {
471+
return "", common.Hash{}, err
472+
}
473+
return stateBloomPath, stateBloomRoot, nil
474+
}
475+
476+
func getHeadHeader(db ethdb.Database) (*types.Header, error) {
477+
headHeaderHash := rawdb.ReadHeadBlockHash(db)
478+
if headHeaderHash == (common.Hash{}) {
479+
return nil, errors.New("empty head block hash")
480+
}
481+
headHeaderNumber := rawdb.ReadHeaderNumber(db, headHeaderHash)
482+
if headHeaderNumber == nil {
483+
return nil, errors.New("empty head block number")
484+
}
485+
headHeader := rawdb.ReadHeader(db, headHeaderHash, *headHeaderNumber)
486+
if headHeader == nil {
487+
return nil, errors.New("empty head header")
488+
}
489+
return headHeader, nil
490+
}

eth/backend.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) {
125125
}
126126
log.Info("Initialised chain configuration", "config", chainConfig)
127127

128-
if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb); err != nil {
128+
if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil {
129129
log.Error("Failed to recover state", "error", err)
130130
}
131131
eth := &Ethereum{

0 commit comments

Comments
 (0)