github.com/juliankolbe/go-ethereum@v1.9.992/core/state/pruner/pruner.go (about)

     1  // Copyright 2020 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package pruner
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/binary"
    22  	"errors"
    23  	"fmt"
    24  	"math"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/juliankolbe/go-ethereum/common"
    31  	"github.com/juliankolbe/go-ethereum/core/rawdb"
    32  	"github.com/juliankolbe/go-ethereum/core/state"
    33  	"github.com/juliankolbe/go-ethereum/core/state/snapshot"
    34  	"github.com/juliankolbe/go-ethereum/core/types"
    35  	"github.com/juliankolbe/go-ethereum/crypto"
    36  	"github.com/juliankolbe/go-ethereum/ethdb"
    37  	"github.com/juliankolbe/go-ethereum/log"
    38  	"github.com/juliankolbe/go-ethereum/rlp"
    39  	"github.com/juliankolbe/go-ethereum/trie"
    40  )
    41  
    42  const (
    43  	// stateBloomFilePrefix is the filename prefix of state bloom filter.
    44  	stateBloomFilePrefix = "statebloom"
    45  
    46  	// stateBloomFilePrefix is the filename suffix of state bloom filter.
    47  	stateBloomFileSuffix = "bf.gz"
    48  
    49  	// stateBloomFileTempSuffix is the filename suffix of state bloom filter
    50  	// while it is being written out to detect write aborts.
    51  	stateBloomFileTempSuffix = ".tmp"
    52  
    53  	// rangeCompactionThreshold is the minimal deleted entry number for
    54  	// triggering range compaction. It's a quite arbitrary number but just
    55  	// to avoid triggering range compaction because of small deletion.
    56  	rangeCompactionThreshold = 100000
    57  )
    58  
    59  var (
    60  	// emptyRoot is the known root hash of an empty trie.
    61  	emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
    62  
    63  	// emptyCode is the known hash of the empty EVM bytecode.
    64  	emptyCode = crypto.Keccak256(nil)
    65  )
    66  
    67  // Pruner is an offline tool to prune the stale state with the
    68  // help of the snapshot. The workflow of pruner is very simple:
    69  //
    70  // - iterate the snapshot, reconstruct the relevant state
    71  // - iterate the database, delete all other state entries which
    72  //   don't belong to the target state and the genesis state
    73  //
    74  // It can take several hours(around 2 hours for mainnet) to finish
    75  // the whole pruning work. It's recommended to run this offline tool
    76  // periodically in order to release the disk usage and improve the
    77  // disk read performance to some extent.
    78  type Pruner struct {
    79  	db            ethdb.Database
    80  	stateBloom    *stateBloom
    81  	datadir       string
    82  	trieCachePath string
    83  	headHeader    *types.Header
    84  	snaptree      *snapshot.Tree
    85  }
    86  
    87  // NewPruner creates the pruner instance.
    88  func NewPruner(db ethdb.Database, headHeader *types.Header, datadir, trieCachePath string, bloomSize uint64) (*Pruner, error) {
    89  	snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headHeader.Root, false, false, false)
    90  	if err != nil {
    91  		return nil, err // The relevant snapshot(s) might not exist
    92  	}
    93  	// Sanitize the bloom filter size if it's too small.
    94  	if bloomSize < 256 {
    95  		log.Warn("Sanitizing bloomfilter size", "provided(MB)", bloomSize, "updated(MB)", 256)
    96  		bloomSize = 256
    97  	}
    98  	stateBloom, err := newStateBloomWithSize(bloomSize)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	return &Pruner{
   103  		db:            db,
   104  		stateBloom:    stateBloom,
   105  		datadir:       datadir,
   106  		trieCachePath: trieCachePath,
   107  		headHeader:    headHeader,
   108  		snaptree:      snaptree,
   109  	}, nil
   110  }
   111  
   112  func prune(maindb ethdb.Database, stateBloom *stateBloom, middleStateRoots map[common.Hash]struct{}, start time.Time) error {
   113  	// Delete all stale trie nodes in the disk. With the help of state bloom
   114  	// the trie nodes(and codes) belong to the active state will be filtered
   115  	// out. A very small part of stale tries will also be filtered because of
   116  	// the false-positive rate of bloom filter. But the assumption is held here
   117  	// that the false-positive is low enough(~0.05%). The probablity of the
   118  	// dangling node is the state root is super low. So the dangling nodes in
   119  	// theory will never ever be visited again.
   120  	var (
   121  		count  int
   122  		size   common.StorageSize
   123  		pstart = time.Now()
   124  		logged = time.Now()
   125  		batch  = maindb.NewBatch()
   126  		iter   = maindb.NewIterator(nil, nil)
   127  	)
   128  	for iter.Next() {
   129  		key := iter.Key()
   130  
   131  		// All state entries don't belong to specific state and genesis are deleted here
   132  		// - trie node
   133  		// - legacy contract code
   134  		// - new-scheme contract code
   135  		isCode, codeKey := rawdb.IsCodeKey(key)
   136  		if len(key) == common.HashLength || isCode {
   137  			checkKey := key
   138  			if isCode {
   139  				checkKey = codeKey
   140  			}
   141  			if _, exist := middleStateRoots[common.BytesToHash(checkKey)]; exist {
   142  				log.Debug("Forcibly delete the middle state roots", "hash", common.BytesToHash(checkKey))
   143  			} else {
   144  				if ok, err := stateBloom.Contain(checkKey); err != nil {
   145  					return err
   146  				} else if ok {
   147  					continue
   148  				}
   149  			}
   150  			count += 1
   151  			size += common.StorageSize(len(key) + len(iter.Value()))
   152  			batch.Delete(key)
   153  
   154  			var eta time.Duration // Realistically will never remain uninited
   155  			if done := binary.BigEndian.Uint64(key[:8]); done > 0 {
   156  				var (
   157  					left  = math.MaxUint64 - binary.BigEndian.Uint64(key[:8])
   158  					speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero
   159  				)
   160  				eta = time.Duration(left/speed) * time.Millisecond
   161  			}
   162  			if time.Since(logged) > 8*time.Second {
   163  				log.Info("Pruning state data", "nodes", count, "size", size,
   164  					"elapsed", common.PrettyDuration(time.Since(pstart)), "eta", common.PrettyDuration(eta))
   165  				logged = time.Now()
   166  			}
   167  			// Recreate the iterator after every batch commit in order
   168  			// to allow the underlying compactor to delete the entries.
   169  			if batch.ValueSize() >= ethdb.IdealBatchSize {
   170  				batch.Write()
   171  				batch.Reset()
   172  
   173  				iter.Release()
   174  				iter = maindb.NewIterator(nil, key)
   175  			}
   176  		}
   177  	}
   178  	if batch.ValueSize() > 0 {
   179  		batch.Write()
   180  		batch.Reset()
   181  	}
   182  	iter.Release()
   183  	log.Info("Pruned state data", "nodes", count, "size", size, "elapsed", common.PrettyDuration(time.Since(pstart)))
   184  
   185  	// Start compactions, will remove the deleted data from the disk immediately.
   186  	// Note for small pruning, the compaction is skipped.
   187  	if count >= rangeCompactionThreshold {
   188  		cstart := time.Now()
   189  		for b := 0x00; b <= 0xf0; b += 0x10 {
   190  			var (
   191  				start = []byte{byte(b)}
   192  				end   = []byte{byte(b + 0x10)}
   193  			)
   194  			if b == 0xf0 {
   195  				end = nil
   196  			}
   197  			log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", start, end), "elapsed", common.PrettyDuration(time.Since(cstart)))
   198  			if err := maindb.Compact(start, end); err != nil {
   199  				log.Error("Database compaction failed", "error", err)
   200  				return err
   201  			}
   202  		}
   203  		log.Info("Database compaction finished", "elapsed", common.PrettyDuration(time.Since(cstart)))
   204  	}
   205  	log.Info("State pruning successful", "pruned", size, "elapsed", common.PrettyDuration(time.Since(start)))
   206  	return nil
   207  }
   208  
   209  // Prune deletes all historical state nodes except the nodes belong to the
   210  // specified state version. If user doesn't specify the state version, use
   211  // the bottom-most snapshot diff layer as the target.
   212  func (p *Pruner) Prune(root common.Hash) error {
   213  	// If the state bloom filter is already committed previously,
   214  	// reuse it for pruning instead of generating a new one. It's
   215  	// mandatory because a part of state may already be deleted,
   216  	// the recovery procedure is necessary.
   217  	_, stateBloomRoot, err := findBloomFilter(p.datadir)
   218  	if err != nil {
   219  		return err
   220  	}
   221  	if stateBloomRoot != (common.Hash{}) {
   222  		return RecoverPruning(p.datadir, p.db, p.trieCachePath)
   223  	}
   224  	// If the target state root is not specified, use the HEAD-127 as the
   225  	// target. The reason for picking it is:
   226  	// - in most of the normal cases, the related state is available
   227  	// - the probability of this layer being reorg is very low
   228  	var layers []snapshot.Snapshot
   229  	if root == (common.Hash{}) {
   230  		// Retrieve all snapshot layers from the current HEAD.
   231  		// In theory there are 128 difflayers + 1 disk layer present,
   232  		// so 128 diff layers are expected to be returned.
   233  		layers = p.snaptree.Snapshots(p.headHeader.Root, 128, true)
   234  		if len(layers) != 128 {
   235  			// Reject if the accumulated diff layers are less than 128. It
   236  			// means in most of normal cases, there is no associated state
   237  			// with bottom-most diff layer.
   238  			return fmt.Errorf("snapshot not old enough yet: need %d more blocks", 128-len(layers))
   239  		}
   240  		// Use the bottom-most diff layer as the target
   241  		root = layers[len(layers)-1].Root()
   242  	}
   243  	// Ensure the root is really present. The weak assumption
   244  	// is the presence of root can indicate the presence of the
   245  	// entire trie.
   246  	if blob := rawdb.ReadTrieNode(p.db, root); len(blob) == 0 {
   247  		// The special case is for clique based networks(rinkeby, goerli
   248  		// and some other private networks), it's possible that two
   249  		// consecutive blocks will have same root. In this case snapshot
   250  		// difflayer won't be created. So HEAD-127 may not paired with
   251  		// head-127 layer. Instead the paired layer is higher than the
   252  		// bottom-most diff layer. Try to find the bottom-most snapshot
   253  		// layer with state available.
   254  		//
   255  		// Note HEAD and HEAD-1 is ignored. Usually there is the associated
   256  		// state available, but we don't want to use the topmost state
   257  		// as the pruning target.
   258  		var found bool
   259  		for i := len(layers) - 2; i >= 2; i-- {
   260  			if blob := rawdb.ReadTrieNode(p.db, layers[i].Root()); len(blob) != 0 {
   261  				root = layers[i].Root()
   262  				found = true
   263  				log.Info("Selecting middle-layer as the pruning target", "root", root, "depth", i)
   264  				break
   265  			}
   266  		}
   267  		if !found {
   268  			if len(layers) > 0 {
   269  				return errors.New("no snapshot paired state")
   270  			}
   271  			return fmt.Errorf("associated state[%x] is not present", root)
   272  		}
   273  	} else {
   274  		if len(layers) > 0 {
   275  			log.Info("Selecting bottom-most difflayer as the pruning target", "root", root, "height", p.headHeader.Number.Uint64()-127)
   276  		} else {
   277  			log.Info("Selecting user-specified state as the pruning target", "root", root)
   278  		}
   279  	}
   280  	// Before start the pruning, delete the clean trie cache first.
   281  	// It's necessary otherwise in the next restart we will hit the
   282  	// deleted state root in the "clean cache" so that the incomplete
   283  	// state is picked for usage.
   284  	deleteCleanTrieCache(p.trieCachePath)
   285  
   286  	// All the state roots of the middle layer should be forcibly pruned,
   287  	// otherwise the dangling state will be left.
   288  	middleRoots := make(map[common.Hash]struct{})
   289  	for _, layer := range layers {
   290  		if layer.Root() == root {
   291  			break
   292  		}
   293  		middleRoots[layer.Root()] = struct{}{}
   294  	}
   295  	// Traverse the target state, re-construct the whole state trie and
   296  	// commit to the given bloom filter.
   297  	start := time.Now()
   298  	if err := snapshot.GenerateTrie(p.snaptree, root, p.db, p.stateBloom); err != nil {
   299  		return err
   300  	}
   301  	// Traverse the genesis, put all genesis state entries into the
   302  	// bloom filter too.
   303  	if err := extractGenesis(p.db, p.stateBloom); err != nil {
   304  		return err
   305  	}
   306  	filterName := bloomFilterName(p.datadir, root)
   307  
   308  	log.Info("Writing state bloom to disk", "name", filterName)
   309  	if err := p.stateBloom.Commit(filterName, filterName+stateBloomFileTempSuffix); err != nil {
   310  		return err
   311  	}
   312  	log.Info("State bloom filter committed", "name", filterName)
   313  
   314  	if err := prune(p.db, p.stateBloom, middleRoots, start); err != nil {
   315  		return err
   316  	}
   317  	// Pruning is done, now drop the "useless" layers from the snapshot.
   318  	// Firstly, flushing the target layer into the disk. After that all
   319  	// diff layers below the target will all be merged into the disk.
   320  	if err := p.snaptree.Cap(root, 0); err != nil {
   321  		return err
   322  	}
   323  	// Secondly, flushing the snapshot journal into the disk. All diff
   324  	// layers upon the target layer are dropped silently. Eventually the
   325  	// entire snapshot tree is converted into a single disk layer with
   326  	// the pruning target as the root.
   327  	if _, err := p.snaptree.Journal(root); err != nil {
   328  		return err
   329  	}
   330  	// Delete the state bloom, it marks the entire pruning procedure is
   331  	// finished. If any crashes or manual exit happens before this,
   332  	// `RecoverPruning` will pick it up in the next restarts to redo all
   333  	// the things.
   334  	os.RemoveAll(filterName)
   335  	return nil
   336  }
   337  
   338  // RecoverPruning will resume the pruning procedure during the system restart.
   339  // This function is used in this case: user tries to prune state data, but the
   340  // system was interrupted midway because of crash or manual-kill. In this case
   341  // if the bloom filter for filtering active state is already constructed, the
   342  // pruning can be resumed. What's more if the bloom filter is constructed, the
   343  // pruning **has to be resumed**. Otherwise a lot of dangling nodes may be left
   344  // in the disk.
   345  func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) error {
   346  	stateBloomPath, stateBloomRoot, err := findBloomFilter(datadir)
   347  	if err != nil {
   348  		return err
   349  	}
   350  	if stateBloomPath == "" {
   351  		return nil // nothing to recover
   352  	}
   353  	headHeader, err := getHeadHeader(db)
   354  	if err != nil {
   355  		return err
   356  	}
   357  	// Initialize the snapshot tree in recovery mode to handle this special case:
   358  	// - Users run the `prune-state` command multiple times
   359  	// - Neither these `prune-state` running is finished(e.g. interrupted manually)
   360  	// - The state bloom filter is already generated, a part of state is deleted,
   361  	//   so that resuming the pruning here is mandatory
   362  	// - The state HEAD is rewound already because of multiple incomplete `prune-state`
   363  	// In this case, even the state HEAD is not exactly matched with snapshot, it
   364  	// still feasible to recover the pruning correctly.
   365  	snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headHeader.Root, false, false, true)
   366  	if err != nil {
   367  		return err // The relevant snapshot(s) might not exist
   368  	}
   369  	stateBloom, err := NewStateBloomFromDisk(stateBloomPath)
   370  	if err != nil {
   371  		return err
   372  	}
   373  	log.Info("Loaded state bloom filter", "path", stateBloomPath)
   374  
   375  	// Before start the pruning, delete the clean trie cache first.
   376  	// It's necessary otherwise in the next restart we will hit the
   377  	// deleted state root in the "clean cache" so that the incomplete
   378  	// state is picked for usage.
   379  	deleteCleanTrieCache(trieCachePath)
   380  
   381  	// All the state roots of the middle layers should be forcibly pruned,
   382  	// otherwise the dangling state will be left.
   383  	var (
   384  		found       bool
   385  		layers      = snaptree.Snapshots(headHeader.Root, 128, true)
   386  		middleRoots = make(map[common.Hash]struct{})
   387  	)
   388  	for _, layer := range layers {
   389  		if layer.Root() == stateBloomRoot {
   390  			found = true
   391  			break
   392  		}
   393  		middleRoots[layer.Root()] = struct{}{}
   394  	}
   395  	if !found {
   396  		log.Error("Pruning target state is not existent")
   397  		return errors.New("non-existent target state")
   398  	}
   399  	if err := prune(db, stateBloom, middleRoots, time.Now()); err != nil {
   400  		return err
   401  	}
   402  	// Pruning is done, now drop the "useless" layers from the snapshot.
   403  	// Firstly, flushing the target layer into the disk. After that all
   404  	// diff layers below the target will all be merged into the disk.
   405  	if err := snaptree.Cap(stateBloomRoot, 0); err != nil {
   406  		return err
   407  	}
   408  	// Secondly, flushing the snapshot journal into the disk. All diff
   409  	// layers upon are dropped silently. Eventually the entire snapshot
   410  	// tree is converted into a single disk layer with the pruning target
   411  	// as the root.
   412  	if _, err := snaptree.Journal(stateBloomRoot); err != nil {
   413  		return err
   414  	}
   415  	// Delete the state bloom, it marks the entire pruning procedure is
   416  	// finished. If any crashes or manual exit happens before this,
   417  	// `RecoverPruning` will pick it up in the next restarts to redo all
   418  	// the things.
   419  	os.RemoveAll(stateBloomPath)
   420  	return nil
   421  }
   422  
   423  // extractGenesis loads the genesis state and commits all the state entries
   424  // into the given bloomfilter.
   425  func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error {
   426  	genesisHash := rawdb.ReadCanonicalHash(db, 0)
   427  	if genesisHash == (common.Hash{}) {
   428  		return errors.New("missing genesis hash")
   429  	}
   430  	genesis := rawdb.ReadBlock(db, genesisHash, 0)
   431  	if genesis == nil {
   432  		return errors.New("missing genesis block")
   433  	}
   434  	t, err := trie.NewSecure(genesis.Root(), trie.NewDatabase(db))
   435  	if err != nil {
   436  		return err
   437  	}
   438  	accIter := t.NodeIterator(nil)
   439  	for accIter.Next(true) {
   440  		hash := accIter.Hash()
   441  
   442  		// Embedded nodes don't have hash.
   443  		if hash != (common.Hash{}) {
   444  			stateBloom.Put(hash.Bytes(), nil)
   445  		}
   446  		// If it's a leaf node, yes we are touching an account,
   447  		// dig into the storage trie further.
   448  		if accIter.Leaf() {
   449  			var acc state.Account
   450  			if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil {
   451  				return err
   452  			}
   453  			if acc.Root != emptyRoot {
   454  				storageTrie, err := trie.NewSecure(acc.Root, trie.NewDatabase(db))
   455  				if err != nil {
   456  					return err
   457  				}
   458  				storageIter := storageTrie.NodeIterator(nil)
   459  				for storageIter.Next(true) {
   460  					hash := storageIter.Hash()
   461  					if hash != (common.Hash{}) {
   462  						stateBloom.Put(hash.Bytes(), nil)
   463  					}
   464  				}
   465  				if storageIter.Error() != nil {
   466  					return storageIter.Error()
   467  				}
   468  			}
   469  			if !bytes.Equal(acc.CodeHash, emptyCode) {
   470  				stateBloom.Put(acc.CodeHash, nil)
   471  			}
   472  		}
   473  	}
   474  	return accIter.Error()
   475  }
   476  
   477  func bloomFilterName(datadir string, hash common.Hash) string {
   478  	return filepath.Join(datadir, fmt.Sprintf("%s.%s.%s", stateBloomFilePrefix, hash.Hex(), stateBloomFileSuffix))
   479  }
   480  
   481  func isBloomFilter(filename string) (bool, common.Hash) {
   482  	filename = filepath.Base(filename)
   483  	if strings.HasPrefix(filename, stateBloomFilePrefix) && strings.HasSuffix(filename, stateBloomFileSuffix) {
   484  		return true, common.HexToHash(filename[len(stateBloomFilePrefix)+1 : len(filename)-len(stateBloomFileSuffix)-1])
   485  	}
   486  	return false, common.Hash{}
   487  }
   488  
   489  func findBloomFilter(datadir string) (string, common.Hash, error) {
   490  	var (
   491  		stateBloomPath string
   492  		stateBloomRoot common.Hash
   493  	)
   494  	if err := filepath.Walk(datadir, func(path string, info os.FileInfo, err error) error {
   495  		if info != nil && !info.IsDir() {
   496  			ok, root := isBloomFilter(path)
   497  			if ok {
   498  				stateBloomPath = path
   499  				stateBloomRoot = root
   500  			}
   501  		}
   502  		return nil
   503  	}); err != nil {
   504  		return "", common.Hash{}, err
   505  	}
   506  	return stateBloomPath, stateBloomRoot, nil
   507  }
   508  
   509  func getHeadHeader(db ethdb.Database) (*types.Header, error) {
   510  	headHeaderHash := rawdb.ReadHeadBlockHash(db)
   511  	if headHeaderHash == (common.Hash{}) {
   512  		return nil, errors.New("empty head block hash")
   513  	}
   514  	headHeaderNumber := rawdb.ReadHeaderNumber(db, headHeaderHash)
   515  	if headHeaderNumber == nil {
   516  		return nil, errors.New("empty head block number")
   517  	}
   518  	headHeader := rawdb.ReadHeader(db, headHeaderHash, *headHeaderNumber)
   519  	if headHeader == nil {
   520  		return nil, errors.New("empty head header")
   521  	}
   522  	return headHeader, nil
   523  }
   524  
   525  const warningLog = `
   526  
   527  WARNING!
   528  
   529  The clean trie cache is not found. Please delete it by yourself after the 
   530  pruning. Remember don't start the Geth without deleting the clean trie cache
   531  otherwise the entire database may be damaged!
   532  
   533  Check the command description "geth snapshot prune-state --help" for more details.
   534  `
   535  
   536  func deleteCleanTrieCache(path string) {
   537  	if _, err := os.Stat(path); os.IsNotExist(err) {
   538  		log.Warn(warningLog)
   539  		return
   540  	}
   541  	os.RemoveAll(path)
   542  	log.Info("Deleted trie clean cache", "path", path)
   543  }