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