github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/core/state/pruner/pruner.go (about)

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