github.com/klaytn/klaytn@v1.10.2/cmd/utils/nodecmd/snapshot.go (about)

     1  // Modifications Copyright 2022 The klaytn Authors
     2  // Copyright 2020 The go-ethereum Authors
     3  // This file is part of go-ethereum.
     4  //
     5  // go-ethereum is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // go-ethereum is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU General Public License
    16  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from cmd/utils/nodecmd/snapshot.go (2022/07/08).
    19  // Modified and improved for the klaytn development.
    20  
    21  package nodecmd
    22  
    23  import (
    24  	"bytes"
    25  	"errors"
    26  	"fmt"
    27  	"sync"
    28  	"time"
    29  
    30  	"github.com/klaytn/klaytn/blockchain/state"
    31  	"github.com/klaytn/klaytn/cmd/utils"
    32  	"github.com/klaytn/klaytn/common"
    33  	"github.com/klaytn/klaytn/snapshot"
    34  	"github.com/klaytn/klaytn/storage/database"
    35  	"github.com/klaytn/klaytn/storage/statedb"
    36  	"gopkg.in/urfave/cli.v1"
    37  )
    38  
    39  var SnapshotCommand = cli.Command{
    40  	Name:        "snapshot",
    41  	Usage:       "A set of commands based on the snapshot",
    42  	Description: "",
    43  	Subcommands: []cli.Command{
    44  		{
    45  			Name:      "verify-state",
    46  			Usage:     "Recalculate state hash based on the snapshot for verification",
    47  			ArgsUsage: "<root>",
    48  			Action:    utils.MigrateFlags(verifyState),
    49  			Flags: []cli.Flag{
    50  				utils.DbTypeFlag,
    51  				utils.SingleDBFlag,
    52  				utils.NumStateTrieShardsFlag,
    53  				utils.DynamoDBTableNameFlag,
    54  				utils.DynamoDBRegionFlag,
    55  				utils.DynamoDBIsProvisionedFlag,
    56  				utils.DynamoDBReadCapacityFlag,
    57  				utils.DynamoDBWriteCapacityFlag,
    58  				utils.LevelDBCompressionTypeFlag,
    59  				utils.DataDirFlag,
    60  			},
    61  			Description: `
    62  klay snapshot verify-state <state-root>
    63  will traverse the whole accounts and storages set based on the specified
    64  snapshot and recalculate the root hash of state for verification.
    65  In other words, this command does the snapshot to trie conversion.
    66  `,
    67  		},
    68  		{
    69  			Name:      "trace-trie",
    70  			Usage:     "trace all trie nodes for verification",
    71  			ArgsUsage: "<root>",
    72  			Action:    utils.MigrateFlags(traceTrie),
    73  			Flags: []cli.Flag{
    74  				utils.DbTypeFlag,
    75  				utils.SingleDBFlag,
    76  				utils.NumStateTrieShardsFlag,
    77  				utils.DynamoDBTableNameFlag,
    78  				utils.DynamoDBRegionFlag,
    79  				utils.DynamoDBIsProvisionedFlag,
    80  				utils.DynamoDBReadCapacityFlag,
    81  				utils.DynamoDBWriteCapacityFlag,
    82  				utils.LevelDBCompressionTypeFlag,
    83  				utils.DataDirFlag,
    84  			},
    85  			Description: `
    86  klaytn statedb trace-trie <state-root>
    87  trace all account and storage nodes to find missing data
    88  during the migration process.
    89  Start tracing from the state root of the last block,
    90  reading all nodes and logging the missing nodes.
    91  `,
    92  		},
    93  		{
    94  			Name:      "iterate-triedb",
    95  			Usage:     "Iterate StateTrie DB for node count",
    96  			ArgsUsage: "<root>",
    97  			Action:    utils.MigrateFlags(iterateTrie),
    98  			Flags: []cli.Flag{
    99  				utils.DbTypeFlag,
   100  				utils.SingleDBFlag,
   101  				utils.NumStateTrieShardsFlag,
   102  				utils.DynamoDBTableNameFlag,
   103  				utils.DynamoDBRegionFlag,
   104  				utils.DynamoDBIsProvisionedFlag,
   105  				utils.DynamoDBReadCapacityFlag,
   106  				utils.DynamoDBWriteCapacityFlag,
   107  				utils.LevelDBCompressionTypeFlag,
   108  				utils.DataDirFlag,
   109  			},
   110  			Description: `
   111  klaytn statedb iterate-triedb
   112  Count the number of nodes in the state-trie db.
   113  `,
   114  		},
   115  	},
   116  }
   117  
   118  var (
   119  	midAccountCnt  = uint64(0)
   120  	midStorageCnt  = uint64(0)
   121  	codeCnt        = uint64(0)
   122  	leafAccountCnt = uint64(0)
   123  	leafStorageCnt = uint64(0)
   124  	unknownCnt     = uint64(0)
   125  	mutex          = &sync.Mutex{}
   126  )
   127  
   128  // getConfig returns a database config with the given context.
   129  func getConfig(ctx *cli.Context) *database.DBConfig {
   130  	return &database.DBConfig{
   131  		Dir:                "chaindata",
   132  		DBType:             database.DBType(ctx.GlobalString(utils.DbTypeFlag.Name)).ToValid(),
   133  		SingleDB:           ctx.GlobalBool(utils.SingleDBFlag.Name),
   134  		NumStateTrieShards: ctx.GlobalUint(utils.NumStateTrieShardsFlag.Name),
   135  		OpenFilesLimit:     database.GetOpenFilesLimit(),
   136  
   137  		LevelDBCacheSize:    ctx.GlobalInt(utils.LevelDBCacheSizeFlag.Name),
   138  		LevelDBCompression:  database.LevelDBCompressionType(ctx.GlobalInt(utils.LevelDBCompressionTypeFlag.Name)),
   139  		EnableDBPerfMetrics: !ctx.IsSet(utils.DBNoPerformanceMetricsFlag.Name),
   140  
   141  		DynamoDBConfig: &database.DynamoDBConfig{
   142  			TableName:          ctx.GlobalString(utils.DynamoDBTableNameFlag.Name),
   143  			Region:             ctx.GlobalString(utils.DynamoDBRegionFlag.Name),
   144  			IsProvisioned:      ctx.GlobalBool(utils.DynamoDBIsProvisionedFlag.Name),
   145  			ReadCapacityUnits:  ctx.GlobalInt64(utils.DynamoDBReadCapacityFlag.Name),
   146  			WriteCapacityUnits: ctx.GlobalInt64(utils.DynamoDBWriteCapacityFlag.Name),
   147  			PerfCheck:          !ctx.IsSet(utils.DBNoPerformanceMetricsFlag.Name),
   148  		},
   149  	}
   150  }
   151  
   152  // parseRoot parse the given hex string to hash.
   153  func parseRoot(input string) (common.Hash, error) {
   154  	var h common.Hash
   155  	if err := h.UnmarshalText([]byte(input)); err != nil {
   156  		return h, err
   157  	}
   158  	return h, nil
   159  }
   160  
   161  // verifyState verifies if the stored snapshot data is correct or not.
   162  // if a root hash isn't given, the root hash of current block is investigated.
   163  func verifyState(ctx *cli.Context) error {
   164  	stack := MakeFullNode(ctx)
   165  	db := stack.OpenDatabase(getConfig(ctx))
   166  	head := db.ReadHeadBlockHash()
   167  	if head == (common.Hash{}) {
   168  		// Corrupt or empty database, init from scratch
   169  		return errors.New("empty database")
   170  	}
   171  	// Make sure the entire head block is available
   172  	headBlock := db.ReadBlockByHash(head)
   173  	if headBlock == nil {
   174  		return fmt.Errorf("head block missing: %v", head.String())
   175  	}
   176  
   177  	snaptree, err := snapshot.New(db, statedb.NewDatabase(db), 256, headBlock.Root(), false, false, false)
   178  	if err != nil {
   179  		logger.Error("Failed to open snapshot tree", "err", err)
   180  		return err
   181  	}
   182  	if ctx.NArg() > 1 {
   183  		logger.Error("Too many arguments given")
   184  		return errors.New("too many arguments")
   185  	}
   186  	root := headBlock.Root()
   187  	if ctx.NArg() == 1 {
   188  		root, err = parseRoot(ctx.Args().First())
   189  		if err != nil {
   190  			logger.Error("Failed to resolve state root", "err", err)
   191  			return err
   192  		}
   193  	}
   194  	if err := snaptree.Verify(root); err != nil {
   195  		logger.Error("Failed to verify state", "root", root, "err", err)
   196  		return err
   197  	}
   198  	logger.Info("Verified the state", "root", root)
   199  	return nil
   200  }
   201  
   202  func traceTrie(ctx *cli.Context) error {
   203  	var childWait, logWait sync.WaitGroup
   204  
   205  	stack := MakeFullNode(ctx)
   206  	dbm := stack.OpenDatabase(getConfig(ctx))
   207  	head := dbm.ReadHeadBlockHash()
   208  	if head == (common.Hash{}) {
   209  		// Corrupt or empty database, init from scratch
   210  		return errors.New("empty database")
   211  	}
   212  	// Make sure the entire head block is available
   213  	tmpHeadBlock := dbm.ReadBlockByHash(head)
   214  	if tmpHeadBlock == nil {
   215  		return fmt.Errorf("tmp head block missing: %v", head.String())
   216  	}
   217  
   218  	blockNumber := (tmpHeadBlock.NumberU64() / 128) * 128
   219  	headBlock := dbm.ReadBlockByNumber(blockNumber)
   220  	if headBlock == nil {
   221  		return fmt.Errorf("head block missing: %v", head.String())
   222  	}
   223  
   224  	root := headBlock.Root()
   225  	if root == (common.Hash{}) {
   226  		// Corrupt or empty database, init from scratch
   227  		return errors.New("empty root")
   228  	}
   229  
   230  	logger.Info("Trace Start", "BlockNum", blockNumber)
   231  
   232  	sdb, err := state.New(root, state.NewDatabase(dbm), nil)
   233  	if err != nil {
   234  		return fmt.Errorf("Failed to open newDB trie : %v", err)
   235  	}
   236  	trieDB := sdb.Database().TrieDB()
   237  
   238  	// Get root-node childrens to create goroutine by number of childrens
   239  	children, err := trieDB.NodeChildren(root)
   240  	if err != nil {
   241  		return fmt.Errorf("Fail get childrens of root : %v", err)
   242  	}
   243  
   244  	midAccountCnt, midStorageCnt, codeCnt, leafAccountCnt, leafStorageCnt, unknownCnt = 0, 0, 0, 0, 0, 0
   245  	endFlag := false
   246  
   247  	childWait.Add(len(children))
   248  	logWait.Add(1)
   249  	// create logging goroutine
   250  	go func() {
   251  		defer logWait.Done()
   252  		for !endFlag {
   253  			time.Sleep(time.Second * 5)
   254  			logger.Info("Trie Tracer", "AccNode", midAccountCnt, "AccLeaf", leafAccountCnt, "StrgNode", midStorageCnt, "StrgLeaf", leafStorageCnt, "Unknown", unknownCnt, "CodeAcc", codeCnt)
   255  		}
   256  		logger.Info("Trie Tracer Finished", "AccNode", midAccountCnt, "AccLeaf", leafAccountCnt, "StrgNode", midStorageCnt, "StrgLeaf", leafStorageCnt, "Unknown", unknownCnt, "CodeAcc", codeCnt)
   257  	}()
   258  
   259  	// Create goroutine by number of childrens
   260  	for _, child := range children {
   261  		go func(child common.Hash) {
   262  			defer childWait.Done()
   263  			doTraceTrie(sdb.Database(), child)
   264  		}(child)
   265  	}
   266  
   267  	childWait.Wait()
   268  	endFlag = true
   269  	logWait.Wait()
   270  	return nil
   271  }
   272  
   273  func doTraceTrie(db state.Database, root common.Hash) (resultErr error) {
   274  	logger.Info("Trie Tracer Start", "Hash Root", root)
   275  	// Create and iterate a state trie rooted in a sub-node
   276  	oldState, err := state.New(root, db, nil)
   277  	if err != nil {
   278  		logger.Error("can not open trie DB", err.Error())
   279  		panic(err)
   280  	}
   281  
   282  	oldIt := state.NewNodeIterator(oldState)
   283  
   284  	for oldIt.Next() {
   285  		mutex.Lock()
   286  		switch oldIt.Type {
   287  		case "state":
   288  			midAccountCnt++
   289  		case "storage":
   290  			midStorageCnt++
   291  		case "code":
   292  			codeCnt++
   293  		case "state_leaf":
   294  			leafAccountCnt++
   295  		case "storage_leaf":
   296  			leafStorageCnt++
   297  		default:
   298  			unknownCnt++
   299  		}
   300  		mutex.Unlock()
   301  	}
   302  	if oldIt.Error != nil {
   303  		logger.Error("Error Finished", "Root Hash", root, "Message", oldIt.Error)
   304  	}
   305  	logger.Info("Trie Tracer Finished", "Root Hash", root, "AccNode", midAccountCnt, "AccLeaf", leafAccountCnt, "StrgNode", midStorageCnt, "StrgLeaf", leafStorageCnt, "Unknown", unknownCnt, "CodeAcc", codeCnt)
   306  	return nil
   307  }
   308  
   309  func iterateTrie(ctx *cli.Context) error {
   310  	stack := MakeFullNode(ctx)
   311  	dbm := stack.OpenDatabase(getConfig(ctx))
   312  	sdb, err := state.New(common.Hash{}, state.NewDatabase(dbm), nil)
   313  	if err != nil {
   314  		return fmt.Errorf("Failed to open newDB trie : %v", err)
   315  	}
   316  
   317  	logger.Info("TrieDB Iterator Start", "node count : all node count, nil node count : key or value is nil node count")
   318  	cnt, nilCnt := uint64(0), uint64(0)
   319  	go func() {
   320  		for {
   321  			time.Sleep(time.Second * 5)
   322  			logger.Info("TrieDB Iterator", "node count", cnt, "nil node count", nilCnt)
   323  		}
   324  	}()
   325  
   326  	it := sdb.Database().TrieDB().DiskDB().GetStateTrieDB().NewIterator(nil, nil)
   327  	defer it.Release()
   328  	for it.Next() {
   329  		cnt++
   330  		if it.Key() == nil || it.Value() == nil || bytes.Equal(it.Key(), []byte("")) || bytes.Equal(it.Value(), []byte("")) {
   331  			nilCnt++
   332  		}
   333  	}
   334  	logger.Info("TrieDB Iterator finished", "total node count", cnt, "nil node count", nilCnt)
   335  	return nil
   336  }