github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/adkgo-node/snapshot.go (about)

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