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