github.com/hardtosaygoodbye/go-ethereum@v1.10.16-0.20220122011429-97003b9e6c15/cmd/geth/snapshot.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 main
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"errors"
    23  	"os"
    24  	"time"
    25  
    26  	"github.com/hardtosaygoodbye/go-ethereum/cmd/utils"
    27  	"github.com/hardtosaygoodbye/go-ethereum/common"
    28  	"github.com/hardtosaygoodbye/go-ethereum/core/rawdb"
    29  	"github.com/hardtosaygoodbye/go-ethereum/core/state"
    30  	"github.com/hardtosaygoodbye/go-ethereum/core/state/pruner"
    31  	"github.com/hardtosaygoodbye/go-ethereum/core/state/snapshot"
    32  	"github.com/hardtosaygoodbye/go-ethereum/core/types"
    33  	"github.com/hardtosaygoodbye/go-ethereum/crypto"
    34  	"github.com/hardtosaygoodbye/go-ethereum/log"
    35  	"github.com/hardtosaygoodbye/go-ethereum/rlp"
    36  	"github.com/hardtosaygoodbye/go-ethereum/trie"
    37  	cli "gopkg.in/urfave/cli.v1"
    38  )
    39  
    40  var (
    41  	// emptyRoot is the known root hash of an empty trie.
    42  	emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
    43  
    44  	// emptyCode is the known hash of the empty EVM bytecode.
    45  	emptyCode = crypto.Keccak256(nil)
    46  )
    47  
    48  var (
    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 ethereum state data based on the snapshot",
    58  				ArgsUsage: "<root>",
    59  				Action:    utils.MigrateFlags(pruneState),
    60  				Category:  "MISCELLANEOUS COMMANDS",
    61  				Flags: []cli.Flag{
    62  					utils.DataDirFlag,
    63  					utils.AncientFlag,
    64  					utils.RopstenFlag,
    65  					utils.SepoliaFlag,
    66  					utils.RinkebyFlag,
    67  					utils.GoerliFlag,
    68  					utils.CacheTrieJournalFlag,
    69  					utils.BloomFilterSizeFlag,
    70  				},
    71  				Description: `
    72  geth snapshot prune-state <state-root>
    73  will prune historical state data with the help of the state snapshot.
    74  All trie nodes and contract codes that do not belong to the specified
    75  version state will be deleted from the database. After pruning, only
    76  two version states are available: genesis and the specific one.
    77  
    78  The default pruning target is the HEAD-127 state.
    79  
    80  WARNING: It's necessary to delete the trie clean cache after the pruning.
    81  If you specify another directory for the trie clean cache via "--cache.trie.journal"
    82  during the use of Geth, please also specify it here for correct deletion. Otherwise
    83  the trie clean cache with default directory will be deleted.
    84  `,
    85  			},
    86  			{
    87  				Name:      "verify-state",
    88  				Usage:     "Recalculate state hash based on the snapshot for verification",
    89  				ArgsUsage: "<root>",
    90  				Action:    utils.MigrateFlags(verifyState),
    91  				Category:  "MISCELLANEOUS COMMANDS",
    92  				Flags: []cli.Flag{
    93  					utils.DataDirFlag,
    94  					utils.AncientFlag,
    95  					utils.RopstenFlag,
    96  					utils.SepoliaFlag,
    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 state with given root hash for verification",
   110  				ArgsUsage: "<root>",
   111  				Action:    utils.MigrateFlags(traverseState),
   112  				Category:  "MISCELLANEOUS COMMANDS",
   113  				Flags: []cli.Flag{
   114  					utils.DataDirFlag,
   115  					utils.AncientFlag,
   116  					utils.RopstenFlag,
   117  					utils.SepoliaFlag,
   118  					utils.RinkebyFlag,
   119  					utils.GoerliFlag,
   120  				},
   121  				Description: `
   122  geth snapshot traverse-state <state-root>
   123  will traverse the whole state from the given state root and will abort if any
   124  referenced trie node or contract code is missing. This command can be used for
   125  state integrity verification. The default checking target is the HEAD state.
   126  
   127  It's also usable without snapshot enabled.
   128  `,
   129  			},
   130  			{
   131  				Name:      "traverse-rawstate",
   132  				Usage:     "Traverse the state with given root hash for verification",
   133  				ArgsUsage: "<root>",
   134  				Action:    utils.MigrateFlags(traverseRawState),
   135  				Category:  "MISCELLANEOUS COMMANDS",
   136  				Flags: []cli.Flag{
   137  					utils.DataDirFlag,
   138  					utils.AncientFlag,
   139  					utils.RopstenFlag,
   140  					utils.SepoliaFlag,
   141  					utils.RinkebyFlag,
   142  					utils.GoerliFlag,
   143  				},
   144  				Description: `
   145  geth snapshot traverse-rawstate <state-root>
   146  will traverse the whole state from the given root and will abort if any referenced
   147  trie node or contract code is missing. This command can be used for state integrity
   148  verification. The default checking target is the HEAD state. It's basically identical
   149  to traverse-state, but the check granularity is smaller. 
   150  
   151  It's also usable without snapshot enabled.
   152  `,
   153  			},
   154  			{
   155  				Name:      "dump",
   156  				Usage:     "Dump a specific block from storage (same as 'geth dump' but using snapshots)",
   157  				ArgsUsage: "[? <blockHash> | <blockNum>]",
   158  				Action:    utils.MigrateFlags(dumpState),
   159  				Category:  "MISCELLANEOUS COMMANDS",
   160  				Flags: []cli.Flag{
   161  					utils.DataDirFlag,
   162  					utils.AncientFlag,
   163  					utils.RopstenFlag,
   164  					utils.SepoliaFlag,
   165  					utils.RinkebyFlag,
   166  					utils.GoerliFlag,
   167  					utils.ExcludeCodeFlag,
   168  					utils.ExcludeStorageFlag,
   169  					utils.StartKeyFlag,
   170  					utils.DumpLimitFlag,
   171  				},
   172  				Description: `
   173  This command is semantically equivalent to 'geth dump', but uses the snapshots
   174  as the backend data source, making this command a lot faster. 
   175  
   176  The argument is interpreted as block number or hash. If none is provided, the latest
   177  block is used.
   178  `,
   179  			},
   180  		},
   181  	}
   182  )
   183  
   184  func pruneState(ctx *cli.Context) error {
   185  	stack, config := makeConfigNode(ctx)
   186  	defer stack.Close()
   187  
   188  	chaindb := utils.MakeChainDatabase(ctx, stack, false)
   189  	pruner, err := pruner.NewPruner(chaindb, stack.ResolvePath(""), stack.ResolvePath(config.Eth.TrieCleanCacheJournal), ctx.GlobalUint64(utils.BloomFilterSizeFlag.Name))
   190  	if err != nil {
   191  		log.Error("Failed to open snapshot tree", "err", err)
   192  		return err
   193  	}
   194  	if ctx.NArg() > 1 {
   195  		log.Error("Too many arguments given")
   196  		return errors.New("too many arguments")
   197  	}
   198  	var targetRoot common.Hash
   199  	if ctx.NArg() == 1 {
   200  		targetRoot, err = parseRoot(ctx.Args()[0])
   201  		if err != nil {
   202  			log.Error("Failed to resolve state root", "err", err)
   203  			return err
   204  		}
   205  	}
   206  	if err = pruner.Prune(targetRoot); err != nil {
   207  		log.Error("Failed to prune state", "err", err)
   208  		return err
   209  	}
   210  	return nil
   211  }
   212  
   213  func verifyState(ctx *cli.Context) error {
   214  	stack, _ := makeConfigNode(ctx)
   215  	defer stack.Close()
   216  
   217  	chaindb := utils.MakeChainDatabase(ctx, stack, true)
   218  	headBlock := rawdb.ReadHeadBlock(chaindb)
   219  	if headBlock == nil {
   220  		log.Error("Failed to load head block")
   221  		return errors.New("no head block")
   222  	}
   223  	snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, headBlock.Root(), false, false, false)
   224  	if err != nil {
   225  		log.Error("Failed to open snapshot tree", "err", err)
   226  		return err
   227  	}
   228  	if ctx.NArg() > 1 {
   229  		log.Error("Too many arguments given")
   230  		return errors.New("too many arguments")
   231  	}
   232  	var root = headBlock.Root()
   233  	if ctx.NArg() == 1 {
   234  		root, err = parseRoot(ctx.Args()[0])
   235  		if err != nil {
   236  			log.Error("Failed to resolve state root", "err", err)
   237  			return err
   238  		}
   239  	}
   240  	if err := snaptree.Verify(root); err != nil {
   241  		log.Error("Failed to verify state", "root", root, "err", err)
   242  		return err
   243  	}
   244  	log.Info("Verified the state", "root", root)
   245  	return nil
   246  }
   247  
   248  // traverseState is a helper function used for pruning verification.
   249  // Basically it just iterates the trie, ensure all nodes and associated
   250  // contract codes are present.
   251  func traverseState(ctx *cli.Context) error {
   252  	stack, _ := makeConfigNode(ctx)
   253  	defer stack.Close()
   254  
   255  	chaindb := utils.MakeChainDatabase(ctx, stack, true)
   256  	headBlock := rawdb.ReadHeadBlock(chaindb)
   257  	if headBlock == nil {
   258  		log.Error("Failed to load head block")
   259  		return errors.New("no head block")
   260  	}
   261  	if ctx.NArg() > 1 {
   262  		log.Error("Too many arguments given")
   263  		return errors.New("too many arguments")
   264  	}
   265  	var (
   266  		root common.Hash
   267  		err  error
   268  	)
   269  	if ctx.NArg() == 1 {
   270  		root, err = parseRoot(ctx.Args()[0])
   271  		if err != nil {
   272  			log.Error("Failed to resolve state root", "err", err)
   273  			return err
   274  		}
   275  		log.Info("Start traversing the state", "root", root)
   276  	} else {
   277  		root = headBlock.Root()
   278  		log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64())
   279  	}
   280  	triedb := trie.NewDatabase(chaindb)
   281  	t, err := trie.NewSecure(root, triedb)
   282  	if err != nil {
   283  		log.Error("Failed to open trie", "root", root, "err", err)
   284  		return err
   285  	}
   286  	var (
   287  		accounts   int
   288  		slots      int
   289  		codes      int
   290  		lastReport time.Time
   291  		start      = time.Now()
   292  	)
   293  	accIter := trie.NewIterator(t.NodeIterator(nil))
   294  	for accIter.Next() {
   295  		accounts += 1
   296  		var acc types.StateAccount
   297  		if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil {
   298  			log.Error("Invalid account encountered during traversal", "err", err)
   299  			return err
   300  		}
   301  		if acc.Root != emptyRoot {
   302  			storageTrie, err := trie.NewSecure(acc.Root, triedb)
   303  			if err != nil {
   304  				log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
   305  				return err
   306  			}
   307  			storageIter := trie.NewIterator(storageTrie.NodeIterator(nil))
   308  			for storageIter.Next() {
   309  				slots += 1
   310  			}
   311  			if storageIter.Err != nil {
   312  				log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err)
   313  				return storageIter.Err
   314  			}
   315  		}
   316  		if !bytes.Equal(acc.CodeHash, emptyCode) {
   317  			code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash))
   318  			if len(code) == 0 {
   319  				log.Error("Code is missing", "hash", common.BytesToHash(acc.CodeHash))
   320  				return errors.New("missing code")
   321  			}
   322  			codes += 1
   323  		}
   324  		if time.Since(lastReport) > time.Second*8 {
   325  			log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
   326  			lastReport = time.Now()
   327  		}
   328  	}
   329  	if accIter.Err != nil {
   330  		log.Error("Failed to traverse state trie", "root", root, "err", accIter.Err)
   331  		return accIter.Err
   332  	}
   333  	log.Info("State is complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
   334  	return nil
   335  }
   336  
   337  // traverseRawState is a helper function used for pruning verification.
   338  // Basically it just iterates the trie, ensure all nodes and associated
   339  // contract codes are present. It's basically identical to traverseState
   340  // but it will check each trie node.
   341  func traverseRawState(ctx *cli.Context) error {
   342  	stack, _ := makeConfigNode(ctx)
   343  	defer stack.Close()
   344  
   345  	chaindb := utils.MakeChainDatabase(ctx, stack, true)
   346  	headBlock := rawdb.ReadHeadBlock(chaindb)
   347  	if headBlock == nil {
   348  		log.Error("Failed to load head block")
   349  		return errors.New("no head block")
   350  	}
   351  	if ctx.NArg() > 1 {
   352  		log.Error("Too many arguments given")
   353  		return errors.New("too many arguments")
   354  	}
   355  	var (
   356  		root common.Hash
   357  		err  error
   358  	)
   359  	if ctx.NArg() == 1 {
   360  		root, err = parseRoot(ctx.Args()[0])
   361  		if err != nil {
   362  			log.Error("Failed to resolve state root", "err", err)
   363  			return err
   364  		}
   365  		log.Info("Start traversing the state", "root", root)
   366  	} else {
   367  		root = headBlock.Root()
   368  		log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64())
   369  	}
   370  	triedb := trie.NewDatabase(chaindb)
   371  	t, err := trie.NewSecure(root, triedb)
   372  	if err != nil {
   373  		log.Error("Failed to open trie", "root", root, "err", err)
   374  		return err
   375  	}
   376  	var (
   377  		nodes      int
   378  		accounts   int
   379  		slots      int
   380  		codes      int
   381  		lastReport time.Time
   382  		start      = time.Now()
   383  	)
   384  	accIter := t.NodeIterator(nil)
   385  	for accIter.Next(true) {
   386  		nodes += 1
   387  		node := accIter.Hash()
   388  
   389  		if node != (common.Hash{}) {
   390  			// Check the present for non-empty hash node(embedded node doesn't
   391  			// have their own hash).
   392  			blob := rawdb.ReadTrieNode(chaindb, node)
   393  			if len(blob) == 0 {
   394  				log.Error("Missing trie node(account)", "hash", node)
   395  				return errors.New("missing account")
   396  			}
   397  		}
   398  		// If it's a leaf node, yes we are touching an account,
   399  		// dig into the storage trie further.
   400  		if accIter.Leaf() {
   401  			accounts += 1
   402  			var acc types.StateAccount
   403  			if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil {
   404  				log.Error("Invalid account encountered during traversal", "err", err)
   405  				return errors.New("invalid account")
   406  			}
   407  			if acc.Root != emptyRoot {
   408  				storageTrie, err := trie.NewSecure(acc.Root, triedb)
   409  				if err != nil {
   410  					log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
   411  					return errors.New("missing storage trie")
   412  				}
   413  				storageIter := storageTrie.NodeIterator(nil)
   414  				for storageIter.Next(true) {
   415  					nodes += 1
   416  					node := storageIter.Hash()
   417  
   418  					// Check the present for non-empty hash node(embedded node doesn't
   419  					// have their own hash).
   420  					if node != (common.Hash{}) {
   421  						if !rawdb.HasTrieNode(chaindb, node) {
   422  							log.Error("Missing trie node(storage)", "hash", node)
   423  							return errors.New("missing storage")
   424  						}
   425  					}
   426  					// Bump the counter if it's leaf node.
   427  					if storageIter.Leaf() {
   428  						slots += 1
   429  					}
   430  				}
   431  				if storageIter.Error() != nil {
   432  					log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error())
   433  					return storageIter.Error()
   434  				}
   435  			}
   436  			if !bytes.Equal(acc.CodeHash, emptyCode) {
   437  				code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash))
   438  				if len(code) == 0 {
   439  					log.Error("Code is missing", "account", common.BytesToHash(accIter.LeafKey()))
   440  					return errors.New("missing code")
   441  				}
   442  				codes += 1
   443  			}
   444  			if time.Since(lastReport) > time.Second*8 {
   445  				log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
   446  				lastReport = time.Now()
   447  			}
   448  		}
   449  	}
   450  	if accIter.Error() != nil {
   451  		log.Error("Failed to traverse state trie", "root", root, "err", accIter.Error())
   452  		return accIter.Error()
   453  	}
   454  	log.Info("State is complete", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
   455  	return nil
   456  }
   457  
   458  func parseRoot(input string) (common.Hash, error) {
   459  	var h common.Hash
   460  	if err := h.UnmarshalText([]byte(input)); err != nil {
   461  		return h, err
   462  	}
   463  	return h, nil
   464  }
   465  
   466  func dumpState(ctx *cli.Context) error {
   467  	stack, _ := makeConfigNode(ctx)
   468  	defer stack.Close()
   469  
   470  	conf, db, root, err := parseDumpConfig(ctx, stack)
   471  	if err != nil {
   472  		return err
   473  	}
   474  	snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, root, false, false, false)
   475  	if err != nil {
   476  		return err
   477  	}
   478  	accIt, err := snaptree.AccountIterator(root, common.BytesToHash(conf.Start))
   479  	if err != nil {
   480  		return err
   481  	}
   482  	defer accIt.Release()
   483  
   484  	log.Info("Snapshot dumping started", "root", root)
   485  	var (
   486  		start    = time.Now()
   487  		logged   = time.Now()
   488  		accounts uint64
   489  	)
   490  	enc := json.NewEncoder(os.Stdout)
   491  	enc.Encode(struct {
   492  		Root common.Hash `json:"root"`
   493  	}{root})
   494  	for accIt.Next() {
   495  		account, err := snapshot.FullAccount(accIt.Account())
   496  		if err != nil {
   497  			return err
   498  		}
   499  		da := &state.DumpAccount{
   500  			Balance:   account.Balance.String(),
   501  			Nonce:     account.Nonce,
   502  			Root:      account.Root,
   503  			CodeHash:  account.CodeHash,
   504  			SecureKey: accIt.Hash().Bytes(),
   505  		}
   506  		if !conf.SkipCode && !bytes.Equal(account.CodeHash, emptyCode) {
   507  			da.Code = rawdb.ReadCode(db, common.BytesToHash(account.CodeHash))
   508  		}
   509  		if !conf.SkipStorage {
   510  			da.Storage = make(map[common.Hash]string)
   511  
   512  			stIt, err := snaptree.StorageIterator(root, accIt.Hash(), common.Hash{})
   513  			if err != nil {
   514  				return err
   515  			}
   516  			for stIt.Next() {
   517  				da.Storage[stIt.Hash()] = common.Bytes2Hex(stIt.Slot())
   518  			}
   519  		}
   520  		enc.Encode(da)
   521  		accounts++
   522  		if time.Since(logged) > 8*time.Second {
   523  			log.Info("Snapshot dumping in progress", "at", accIt.Hash(), "accounts", accounts,
   524  				"elapsed", common.PrettyDuration(time.Since(start)))
   525  			logged = time.Now()
   526  		}
   527  		if conf.Max > 0 && accounts >= conf.Max {
   528  			break
   529  		}
   530  	}
   531  	log.Info("Snapshot dumping complete", "accounts", accounts,
   532  		"elapsed", common.PrettyDuration(time.Since(start)))
   533  	return nil
   534  }