diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 2ce5935c5..f1f56b0fc 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -81,6 +81,10 @@ type Pruner struct { trieCachePath string headHeader *types.Header snaptree *snapshot.Tree + // snapDiskRoot is set when the snapshot journal was missing and we fell + // back to the persisted snapshot disk-layer root. Prune() uses it as the + // pruning target when no explicit root is provided. + snapDiskRoot common.Hash } // NewPruner creates the pruner instance. @@ -90,8 +94,28 @@ func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize uint6 return nil, errors.New("Failed to load head block") } snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headBlock.Root(), false, false, false) + var snapDiskRoot common.Hash if err != nil { - return nil, err // The relevant snapshot(s) might not exist + // The snapshot journal may be missing because geth was not shut down + // cleanly (SIGKILL before BlockChain.Stop could write the journal). + // Fall back: initialise the snapshot tree with the persisted disk + // snapshot root so that Prune() can still target that state. + snapDiskRoot = rawdb.ReadSnapshotRoot(db) + if snapDiskRoot == (common.Hash{}) { + return nil, err // No snapshot at all — nothing we can do. + } + log.Warn("Snapshot journal missing, falling back to snapshot disk-layer root", + "snapDiskRoot", snapDiskRoot, "chainHead", headBlock.Root()) + // If the snapshot was mid-generation when the node was killed, New will + // resume and wait for generation to finish (async=false). This can take + // a long time for large state; the log below makes that visible. + log.Info("Loading snapshot from disk-layer root (may wait for snapshot generation to finish)...", + "snapDiskRoot", snapDiskRoot) + snaptree, err = snapshot.New(db, trie.NewDatabase(db), 256, snapDiskRoot, false, false, false) + if err != nil { + return nil, err + } + log.Info("Snapshot ready", "snapDiskRoot", snapDiskRoot) } // Sanitize the bloom filter size if it's too small. if bloomSize < 256 { @@ -109,6 +133,7 @@ func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize uint6 trieCachePath: trieCachePath, headHeader: headBlock.Header(), snaptree: snaptree, + snapDiskRoot: snapDiskRoot, }, nil } @@ -188,8 +213,15 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta // Pruning is done, now drop the "useless" layers from the snapshot. // Firstly, flushing the target layer into the disk. After that all // diff layers below the target will all be merged into the disk. - if err := snaptree.Cap(root, 0); err != nil { - return err + // + // Skip Cap when the root is already the disk layer (no diff layers exist). + // This happens in the fallback path where the snapshot journal was missing + // and we initialised the tree directly from the persisted disk root — Cap + // would otherwise return "snapshot is disk layer" and abort needlessly. + if snaptree.DiskRoot() != root { + if err := snaptree.Cap(root, 0); err != nil { + return err + } } // Secondly, flushing the snapshot journal into the disk. All diff // layers upon are dropped silently. Eventually the entire snapshot @@ -249,18 +281,28 @@ func (p *Pruner) Prune(root common.Hash) error { // - the probability of this layer being reorg is very low var layers []snapshot.Snapshot if root == (common.Hash{}) { - // Retrieve all snapshot layers from the current HEAD. - // In theory there are 128 difflayers + 1 disk layer present, - // so 128 diff layers are expected to be returned. - layers = p.snaptree.Snapshots(p.headHeader.Root, 128, true) - if len(layers) != 128 { - // Reject if the accumulated diff layers are less than 128. It - // means in most of normal cases, there is no associated state - // with bottom-most diff layer. - return fmt.Errorf("snapshot not old enough yet: need %d more blocks", 128-len(layers)) + // When the snapshot journal was missing (unclean shutdown), we fell + // back to the persisted disk snapshot root in NewPruner. Use that + // root directly as the pruning target instead of requiring 128 diff + // layers that don't exist. + if p.snapDiskRoot != (common.Hash{}) { + log.Info("Using snapshot disk-layer root as pruning target (journal was missing)", + "snapDiskRoot", p.snapDiskRoot) + root = p.snapDiskRoot + } else { + // Retrieve all snapshot layers from the current HEAD. + // In theory there are 128 difflayers + 1 disk layer present, + // so 128 diff layers are expected to be returned. + layers = p.snaptree.Snapshots(p.headHeader.Root, 128, true) + if len(layers) != 128 { + // Reject if the accumulated diff layers are less than 128. It + // means in most of normal cases, there is no associated state + // with bottom-most diff layer. + return fmt.Errorf("snapshot not old enough yet: need %d more blocks", 128-len(layers)) + } + // Use the bottom-most diff layer as the target + root = layers[len(layers)-1].Root() } - // Use the bottom-most diff layer as the target - root = layers[len(layers)-1].Root() } // Ensure the root is really present. The weak assumption // is the presence of root can indicate the presence of the