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