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