github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/cmd/u2u/launcher/snapshotcmd.go (about)

     1  // Copyright 2020 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU 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  // go-ethereum 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 General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package launcher
    18  
    19  import (
    20  	"bytes"
    21  	"errors"
    22  	"os"
    23  	"path"
    24  	"time"
    25  
    26  	"github.com/unicornultrafoundation/go-u2u/cmd/utils"
    27  	"github.com/unicornultrafoundation/go-u2u/common"
    28  	"github.com/unicornultrafoundation/go-u2u/core/rawdb"
    29  	"github.com/unicornultrafoundation/go-u2u/core/state"
    30  	"github.com/unicornultrafoundation/go-u2u/core/types"
    31  	"github.com/unicornultrafoundation/go-u2u/log"
    32  	"github.com/unicornultrafoundation/go-u2u/rlp"
    33  	"github.com/unicornultrafoundation/go-u2u/trie"
    34  	"gopkg.in/urfave/cli.v1"
    35  
    36  	"github.com/unicornultrafoundation/go-u2u/gossip/evmstore"
    37  	"github.com/unicornultrafoundation/go-u2u/gossip/evmstore/evmpruner"
    38  )
    39  
    40  var (
    41  	PruneExactCommand = cli.BoolFlag{
    42  		Name:  "prune.exact",
    43  		Usage: `full pruning without usage of bloom filter (false by default)`,
    44  	}
    45  	PruneGenesisCommand = cli.BoolTFlag{
    46  		Name:  "prune.genesis",
    47  		Usage: `prune genesis state (true by default)`,
    48  	}
    49  	snapshotCommand = cli.Command{
    50  		Name:        "snapshot",
    51  		Usage:       "A set of commands based on the snapshot",
    52  		Category:    "MISCELLANEOUS COMMANDS",
    53  		Description: "",
    54  		Subcommands: []cli.Command{
    55  			{
    56  				Name:      "prune-state",
    57  				Usage:     "Prune stale EVM state data based on the snapshot",
    58  				ArgsUsage: "<root> [--prune.exact] [--prune.genesis=false]",
    59  				Action:    utils.MigrateFlags(pruneState),
    60  				Category:  "MISCELLANEOUS COMMANDS",
    61  				Flags: []cli.Flag{
    62  					PruneExactCommand,
    63  					PruneGenesisCommand,
    64  					DataDirFlag,
    65  					utils.AncientFlag,
    66  					utils.RopstenFlag,
    67  					utils.RinkebyFlag,
    68  					utils.GoerliFlag,
    69  					utils.CacheTrieJournalFlag,
    70  					utils.BloomFilterSizeFlag,
    71  				},
    72  				Description: `
    73  geth snapshot prune-state <state-root>
    74  will prune historical state data with the help of the state snapshot.
    75  All trie nodes and contract codes that do not belong to the specified
    76  version state will be deleted from the database. After pruning, only
    77  two version states are available: genesis and the specific one.
    78  
    79  The default pruning target is the HEAD state.
    80  
    81  WARNING: It's necessary to delete the trie clean cache after the pruning.
    82  If you specify another directory for the trie clean cache via "--cache.trie.journal"
    83  during the use of Geth, please also specify it here for correct deletion. Otherwise
    84  the trie clean cache with default directory will be deleted.
    85  `,
    86  			},
    87  			{
    88  				Name:      "verify-state",
    89  				Usage:     "Recalculate state hash based on the snapshot for verification",
    90  				ArgsUsage: "<root>",
    91  				Action:    utils.MigrateFlags(verifyState),
    92  				Category:  "MISCELLANEOUS COMMANDS",
    93  				Flags: []cli.Flag{
    94  					DataDirFlag,
    95  					utils.AncientFlag,
    96  					utils.RopstenFlag,
    97  					utils.RinkebyFlag,
    98  					utils.GoerliFlag,
    99  				},
   100  				Description: `
   101  geth snapshot verify-state <state-root>
   102  will traverse the whole accounts and storages set based on the specified
   103  snapshot and recalculate the root hash of state for verification.
   104  In other words, this command does the snapshot to trie conversion.
   105  `,
   106  			},
   107  			{
   108  				Name:      "traverse-state",
   109  				Usage:     "Traverse the EVM state with given root hash for verification",
   110  				ArgsUsage: "<root>",
   111  				Action:    utils.MigrateFlags(traverseState),
   112  				Category:  "MISCELLANEOUS COMMANDS",
   113  				Flags: []cli.Flag{
   114  					DataDirFlag,
   115  					utils.AncientFlag,
   116  					utils.RopstenFlag,
   117  					utils.RinkebyFlag,
   118  					utils.GoerliFlag,
   119  				},
   120  				Description: `
   121  geth snapshot traverse-state <state-root>
   122  will traverse the whole state from the given state root and will abort if any
   123  referenced trie node or contract code is missing. This command can be used for
   124  state integrity verification. The default checking target is the HEAD state.
   125  
   126  It's also usable without snapshot enabled.
   127  `,
   128  			},
   129  			{
   130  				Name:      "traverse-rawstate",
   131  				Usage:     "Traverse the EVM state with given root hash for verification",
   132  				ArgsUsage: "<root>",
   133  				Action:    utils.MigrateFlags(traverseRawState),
   134  				Category:  "MISCELLANEOUS COMMANDS",
   135  				Flags: []cli.Flag{
   136  					DataDirFlag,
   137  					utils.AncientFlag,
   138  					utils.RopstenFlag,
   139  					utils.RinkebyFlag,
   140  					utils.GoerliFlag,
   141  				},
   142  				Description: `
   143  geth snapshot traverse-rawstate <state-root>
   144  will traverse the whole state from the given root and will abort if any referenced
   145  trie node or contract code is missing. This command can be used for state integrity
   146  verification. The default checking target is the HEAD state. It's basically identical
   147  to traverse-state, but the check granularity is smaller. 
   148  
   149  It's also usable without snapshot enabled.
   150  `,
   151  			},
   152  		},
   153  	}
   154  )
   155  
   156  func pruneState(ctx *cli.Context) error {
   157  	cfg := makeAllConfigs(ctx)
   158  	rawDbs := makeDirectDBsProducer(cfg)
   159  	gdb := makeGossipStore(rawDbs, cfg)
   160  
   161  	if gdb.GetGenesisID() == nil {
   162  		return errors.New("failed to open snapshot tree: genesis is not written")
   163  	}
   164  
   165  	tmpDir := path.Join(cfg.Node.DataDir, "tmp")
   166  	_ = os.MkdirAll(tmpDir, 0700)
   167  	defer os.RemoveAll(tmpDir)
   168  
   169  	genesisBlock := gdb.GetBlock(*gdb.GetGenesisBlockIndex())
   170  	genesisRoot := common.Hash{}
   171  	if !ctx.BoolT(PruneGenesisCommand.Name) && genesisBlock != nil {
   172  		if gdb.EvmStore().HasStateDB(genesisBlock.Root) {
   173  			genesisRoot = common.Hash(genesisBlock.Root)
   174  			log.Info("Excluding genesis state from pruning", "root", genesisRoot)
   175  		}
   176  	}
   177  	root := common.Hash(gdb.GetBlockState().FinalizedStateRoot)
   178  	var bloom evmpruner.StateBloom
   179  	var err error
   180  	if ctx.Bool(PruneExactCommand.Name) {
   181  		log.Info("Initializing LevelDB storage of in-use-keys")
   182  		lset, closer, err := evmpruner.NewLevelDBSet(path.Join(tmpDir, "keys-in-use"))
   183  		if err != nil {
   184  			log.Error("Failed to create state bloom", "err", err)
   185  			return err
   186  		}
   187  		bloom = lset
   188  		defer closer.Close()
   189  	} else {
   190  		size := ctx.Uint64(utils.BloomFilterSizeFlag.Name)
   191  		log.Info("Initializing bloom filter of in-use-keys", "size (MB)", size)
   192  		bloom, err = evmpruner.NewProbabilisticSet(size)
   193  		if err != nil {
   194  			log.Error("Failed to create state bloom", "err", err)
   195  			return err
   196  		}
   197  	}
   198  	pruner, err := evmpruner.NewPruner(gdb.EvmStore().EvmDb, genesisRoot, root, tmpDir, bloom)
   199  	if err != nil {
   200  		log.Error("Failed to open snapshot tree", "err", err)
   201  		return err
   202  	}
   203  	if ctx.NArg() > 1 {
   204  		log.Error("Too many arguments given")
   205  		return errors.New("too many arguments")
   206  	}
   207  	var targetRoot common.Hash
   208  	if ctx.NArg() == 1 {
   209  		targetRoot, err = parseRoot(ctx.Args()[0])
   210  		if err != nil {
   211  			log.Error("Failed to resolve state root", "err", err)
   212  			return err
   213  		}
   214  	}
   215  	if err = pruner.Prune(targetRoot); err != nil {
   216  		log.Error("Failed to prune state", "err", err)
   217  		return err
   218  	}
   219  	return nil
   220  }
   221  
   222  func verifyState(ctx *cli.Context) error {
   223  	cfg := makeAllConfigs(ctx)
   224  	rawDbs := makeDirectDBsProducer(cfg)
   225  	gdb := makeGossipStore(rawDbs, cfg)
   226  
   227  	genesis := gdb.GetGenesisID()
   228  	if genesis == nil {
   229  		return errors.New("failed to open snapshot tree: genesis is not written")
   230  	}
   231  
   232  	evmStore := gdb.EvmStore()
   233  	root := common.Hash(gdb.GetBlockState().FinalizedStateRoot)
   234  
   235  	err := evmStore.GenerateEvmSnapshot(root, false, false)
   236  	if err != nil {
   237  		log.Error("Failed to open snapshot tree", "err", err)
   238  		return err
   239  	}
   240  	if ctx.NArg() > 1 {
   241  		log.Error("Too many arguments given")
   242  		return errors.New("too many arguments")
   243  	}
   244  
   245  	if ctx.NArg() == 1 {
   246  		root, err = parseRoot(ctx.Args()[0])
   247  		if err != nil {
   248  			log.Error("Failed to resolve state root", "err", err)
   249  			return err
   250  		}
   251  	}
   252  	if err := evmStore.Snapshots().Verify(root); err != nil {
   253  		log.Error("Failed to verfiy state", "root", root, "err", err)
   254  		return err
   255  	}
   256  	log.Info("Verified the state", "root", root)
   257  	return nil
   258  }
   259  
   260  // traverseState is a helper function used for pruning verification.
   261  // Basically it just iterates the trie, ensure all nodes and associated
   262  // contract codes are present.
   263  func traverseState(ctx *cli.Context) error {
   264  	cfg := makeAllConfigs(ctx)
   265  	rawDbs := makeDirectDBsProducer(cfg)
   266  	gdb := makeGossipStore(rawDbs, cfg)
   267  
   268  	if gdb.GetGenesisID() == nil {
   269  		return errors.New("failed to open snapshot tree: genesis is not written")
   270  	}
   271  	chaindb := gdb.EvmStore().EvmDb
   272  
   273  	if ctx.NArg() > 1 {
   274  		log.Error("Too many arguments given")
   275  		return errors.New("too many arguments")
   276  	}
   277  	var (
   278  		root common.Hash
   279  		err  error
   280  	)
   281  	if ctx.NArg() == 1 {
   282  		root, err = parseRoot(ctx.Args()[0])
   283  		if err != nil {
   284  			log.Error("Failed to resolve state root", "err", err)
   285  			return err
   286  		}
   287  		log.Info("Start traversing the state", "root", root)
   288  	} else {
   289  		root = common.Hash(gdb.GetBlockState().FinalizedStateRoot)
   290  		log.Info("Start traversing the state", "root", root, "number", gdb.GetBlockState().LastBlock.Idx)
   291  	}
   292  	triedb := trie.NewDatabase(chaindb)
   293  	t, err := trie.NewSecure(root, triedb)
   294  	if err != nil {
   295  		log.Error("Failed to open trie", "root", root, "err", err)
   296  		return err
   297  	}
   298  	var (
   299  		accounts   int
   300  		slots      int
   301  		codes      int
   302  		lastReport time.Time
   303  		start      = time.Now()
   304  	)
   305  	accIter := trie.NewIterator(t.NodeIterator(nil))
   306  	for accIter.Next() {
   307  		accounts += 1
   308  		var acc state.Account
   309  		if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil {
   310  			log.Error("Invalid account encountered during traversal", "err", err)
   311  			return err
   312  		}
   313  		if acc.Root != types.EmptyRootHash {
   314  			storageTrie, err := trie.NewSecure(acc.Root, triedb)
   315  			if err != nil {
   316  				log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
   317  				return err
   318  			}
   319  			storageIter := trie.NewIterator(storageTrie.NodeIterator(nil))
   320  			for storageIter.Next() {
   321  				slots += 1
   322  			}
   323  			if storageIter.Err != nil {
   324  				log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err)
   325  				return storageIter.Err
   326  			}
   327  		}
   328  		if !bytes.Equal(acc.CodeHash, evmstore.EmptyCode) {
   329  			code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash))
   330  			if len(code) == 0 {
   331  				log.Error("Code is missing", "hash", common.BytesToHash(acc.CodeHash))
   332  				return errors.New("missing code")
   333  			}
   334  			codes += 1
   335  		}
   336  		if time.Since(lastReport) > time.Second*8 {
   337  			log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
   338  			lastReport = time.Now()
   339  		}
   340  	}
   341  	if accIter.Err != nil {
   342  		log.Error("Failed to traverse state trie", "root", root, "err", accIter.Err)
   343  		return accIter.Err
   344  	}
   345  	log.Info("State is complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
   346  	return nil
   347  }
   348  
   349  // traverseRawState is a helper function used for pruning verification.
   350  // Basically it just iterates the trie, ensure all nodes and associated
   351  // contract codes are present. It's basically identical to traverseState
   352  // but it will check each trie node.
   353  func traverseRawState(ctx *cli.Context) error {
   354  	cfg := makeAllConfigs(ctx)
   355  	rawDbs := makeDirectDBsProducer(cfg)
   356  	gdb := makeGossipStore(rawDbs, cfg)
   357  
   358  	if gdb.GetGenesisID() == nil {
   359  		return errors.New("failed to open snapshot tree: genesis is not written")
   360  	}
   361  	chaindb := gdb.EvmStore().EvmDb
   362  
   363  	if ctx.NArg() > 1 {
   364  		log.Error("Too many arguments given")
   365  		return errors.New("too many arguments")
   366  	}
   367  	var (
   368  		root common.Hash
   369  		err  error
   370  	)
   371  	if ctx.NArg() == 1 {
   372  		root, err = parseRoot(ctx.Args()[0])
   373  		if err != nil {
   374  			log.Error("Failed to resolve state root", "err", err)
   375  			return err
   376  		}
   377  		log.Info("Start traversing the state", "root", root)
   378  	} else {
   379  		root = common.Hash(gdb.GetBlockState().FinalizedStateRoot)
   380  		log.Info("Start traversing the state", "root", root, "number", gdb.GetBlockState().LastBlock.Idx)
   381  	}
   382  	triedb := trie.NewDatabase(chaindb)
   383  	t, err := trie.NewSecure(root, triedb)
   384  	if err != nil {
   385  		log.Error("Failed to open trie", "root", root, "err", err)
   386  		return err
   387  	}
   388  	var (
   389  		nodes      int
   390  		accounts   int
   391  		slots      int
   392  		codes      int
   393  		lastReport time.Time
   394  		start      = time.Now()
   395  	)
   396  	accIter := t.NodeIterator(nil)
   397  	for accIter.Next(true) {
   398  		nodes += 1
   399  		node := accIter.Hash()
   400  
   401  		if node != (common.Hash{}) {
   402  			// Check the present for non-empty hash node(embedded node doesn't
   403  			// have their own hash).
   404  			blob := rawdb.ReadTrieNode(chaindb, node)
   405  			if len(blob) == 0 {
   406  				log.Error("Missing trie node(account)", "hash", node)
   407  				return errors.New("missing account")
   408  			}
   409  		}
   410  		// If it's a leaf node, yes we are touching an account,
   411  		// dig into the storage trie further.
   412  		if accIter.Leaf() {
   413  			accounts += 1
   414  			var acc state.Account
   415  			if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil {
   416  				log.Error("Invalid account encountered during traversal", "err", err)
   417  				return errors.New("invalid account")
   418  			}
   419  			if acc.Root != types.EmptyRootHash {
   420  				storageTrie, err := trie.NewSecure(acc.Root, triedb)
   421  				if err != nil {
   422  					log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
   423  					return errors.New("missing storage trie")
   424  				}
   425  				storageIter := storageTrie.NodeIterator(nil)
   426  				for storageIter.Next(true) {
   427  					nodes += 1
   428  					node := storageIter.Hash()
   429  
   430  					// Check the present for non-empty hash node(embedded node doesn't
   431  					// have their own hash).
   432  					if node != (common.Hash{}) {
   433  						blob := rawdb.ReadTrieNode(chaindb, node)
   434  						if len(blob) == 0 {
   435  							log.Error("Missing trie node(storage)", "hash", node)
   436  							return errors.New("missing storage")
   437  						}
   438  					}
   439  					// Bump the counter if it's leaf node.
   440  					if storageIter.Leaf() {
   441  						slots += 1
   442  					}
   443  				}
   444  				if storageIter.Error() != nil {
   445  					log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error())
   446  					return storageIter.Error()
   447  				}
   448  			}
   449  			if !bytes.Equal(acc.CodeHash, evmstore.EmptyCode) {
   450  				code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash))
   451  				if len(code) == 0 {
   452  					log.Error("Code is missing", "account", common.BytesToHash(accIter.LeafKey()))
   453  					return errors.New("missing code")
   454  				}
   455  				codes += 1
   456  			}
   457  			if time.Since(lastReport) > time.Second*8 {
   458  				log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
   459  				lastReport = time.Now()
   460  			}
   461  		}
   462  	}
   463  	if accIter.Error() != nil {
   464  		log.Error("Failed to traverse state trie", "root", root, "err", accIter.Error())
   465  		return accIter.Error()
   466  	}
   467  	log.Info("State is complete", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
   468  	return nil
   469  }
   470  
   471  func parseRoot(input string) (common.Hash, error) {
   472  	var h common.Hash
   473  	if err := h.UnmarshalText([]byte(input)); err != nil {
   474  		return h, err
   475  	}
   476  	return h, nil
   477  }