github.com/daethereum/go-dae@v2.2.3+incompatible/cmd/geth/snapshot.go (about)

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