@@ -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
3940const (
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
6670type 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.
198202func (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+ }
0 commit comments