github.com/ethereum/go-ethereum@v1.14.4-0.20240516095835-473ee8fc07a3/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 "time" 26 27 "github.com/ethereum/go-ethereum/cmd/utils" 28 "github.com/ethereum/go-ethereum/common" 29 "github.com/ethereum/go-ethereum/core/rawdb" 30 "github.com/ethereum/go-ethereum/core/state" 31 "github.com/ethereum/go-ethereum/core/state/pruner" 32 "github.com/ethereum/go-ethereum/core/state/snapshot" 33 "github.com/ethereum/go-ethereum/core/types" 34 "github.com/ethereum/go-ethereum/crypto" 35 "github.com/ethereum/go-ethereum/internal/flags" 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: flags.Merge([]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: flags.Merge(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: flags.Merge(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 a specific account", 95 ArgsUsage: "<address | hash>", 96 Action: checkAccount, 97 Flags: flags.Merge(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: flags.Merge(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: flags.Merge(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: flags.Merge([]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 snapConfig := snapshot.Config{ 224 CacheSize: 256, 225 Recovery: false, 226 NoBuild: true, 227 AsyncBuild: false, 228 } 229 snaptree, err := snapshot.New(snapConfig, chaindb, triedb, headBlock.Root()) 230 if err != nil { 231 log.Error("Failed to open snapshot tree", "err", err) 232 return err 233 } 234 if ctx.NArg() > 1 { 235 log.Error("Too many arguments given") 236 return errors.New("too many arguments") 237 } 238 var root = headBlock.Root() 239 if ctx.NArg() == 1 { 240 root, err = parseRoot(ctx.Args().First()) 241 if err != nil { 242 log.Error("Failed to resolve state root", "err", err) 243 return err 244 } 245 } 246 if err := snaptree.Verify(root); err != nil { 247 log.Error("Failed to verify state", "root", root, "err", err) 248 return err 249 } 250 log.Info("Verified the state", "root", root) 251 return snapshot.CheckDanglingStorage(chaindb) 252 } 253 254 // checkDanglingStorage iterates the snap storage data, and verifies that all 255 // storage also has corresponding account data. 256 func checkDanglingStorage(ctx *cli.Context) error { 257 stack, _ := makeConfigNode(ctx) 258 defer stack.Close() 259 260 db := utils.MakeChainDatabase(ctx, stack, true) 261 defer db.Close() 262 return snapshot.CheckDanglingStorage(db) 263 } 264 265 // traverseState is a helper function used for pruning verification. 266 // Basically it just iterates the trie, ensure all nodes and associated 267 // contract codes are present. 268 func traverseState(ctx *cli.Context) error { 269 stack, _ := makeConfigNode(ctx) 270 defer stack.Close() 271 272 chaindb := utils.MakeChainDatabase(ctx, stack, true) 273 defer chaindb.Close() 274 275 triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true, false) 276 defer triedb.Close() 277 278 headBlock := rawdb.ReadHeadBlock(chaindb) 279 if headBlock == nil { 280 log.Error("Failed to load head block") 281 return errors.New("no head block") 282 } 283 if ctx.NArg() > 1 { 284 log.Error("Too many arguments given") 285 return errors.New("too many arguments") 286 } 287 var ( 288 root common.Hash 289 err error 290 ) 291 if ctx.NArg() == 1 { 292 root, err = parseRoot(ctx.Args().First()) 293 if err != nil { 294 log.Error("Failed to resolve state root", "err", err) 295 return err 296 } 297 log.Info("Start traversing the state", "root", root) 298 } else { 299 root = headBlock.Root() 300 log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) 301 } 302 t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb) 303 if err != nil { 304 log.Error("Failed to open trie", "root", root, "err", err) 305 return err 306 } 307 var ( 308 accounts int 309 slots int 310 codes int 311 lastReport time.Time 312 start = time.Now() 313 ) 314 acctIt, err := t.NodeIterator(nil) 315 if err != nil { 316 log.Error("Failed to open iterator", "root", root, "err", err) 317 return err 318 } 319 accIter := trie.NewIterator(acctIt) 320 for accIter.Next() { 321 accounts += 1 322 var acc types.StateAccount 323 if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil { 324 log.Error("Invalid account encountered during traversal", "err", err) 325 return err 326 } 327 if acc.Root != types.EmptyRootHash { 328 id := trie.StorageTrieID(root, common.BytesToHash(accIter.Key), acc.Root) 329 storageTrie, err := trie.NewStateTrie(id, triedb) 330 if err != nil { 331 log.Error("Failed to open storage trie", "root", acc.Root, "err", err) 332 return err 333 } 334 storageIt, err := storageTrie.NodeIterator(nil) 335 if err != nil { 336 log.Error("Failed to open storage iterator", "root", acc.Root, "err", err) 337 return err 338 } 339 storageIter := trie.NewIterator(storageIt) 340 for storageIter.Next() { 341 slots += 1 342 343 if time.Since(lastReport) > time.Second*8 { 344 log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 345 lastReport = time.Now() 346 } 347 } 348 if storageIter.Err != nil { 349 log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err) 350 return storageIter.Err 351 } 352 } 353 if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) { 354 if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) { 355 log.Error("Code is missing", "hash", common.BytesToHash(acc.CodeHash)) 356 return errors.New("missing code") 357 } 358 codes += 1 359 } 360 if time.Since(lastReport) > time.Second*8 { 361 log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 362 lastReport = time.Now() 363 } 364 } 365 if accIter.Err != nil { 366 log.Error("Failed to traverse state trie", "root", root, "err", accIter.Err) 367 return accIter.Err 368 } 369 log.Info("State is complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 370 return nil 371 } 372 373 // traverseRawState is a helper function used for pruning verification. 374 // Basically it just iterates the trie, ensure all nodes and associated 375 // contract codes are present. It's basically identical to traverseState 376 // but it will check each trie node. 377 func traverseRawState(ctx *cli.Context) error { 378 stack, _ := makeConfigNode(ctx) 379 defer stack.Close() 380 381 chaindb := utils.MakeChainDatabase(ctx, stack, true) 382 defer chaindb.Close() 383 384 triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true, false) 385 defer triedb.Close() 386 387 headBlock := rawdb.ReadHeadBlock(chaindb) 388 if headBlock == nil { 389 log.Error("Failed to load head block") 390 return errors.New("no head block") 391 } 392 if ctx.NArg() > 1 { 393 log.Error("Too many arguments given") 394 return errors.New("too many arguments") 395 } 396 var ( 397 root common.Hash 398 err error 399 ) 400 if ctx.NArg() == 1 { 401 root, err = parseRoot(ctx.Args().First()) 402 if err != nil { 403 log.Error("Failed to resolve state root", "err", err) 404 return err 405 } 406 log.Info("Start traversing the state", "root", root) 407 } else { 408 root = headBlock.Root() 409 log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) 410 } 411 t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb) 412 if err != nil { 413 log.Error("Failed to open trie", "root", root, "err", err) 414 return err 415 } 416 var ( 417 nodes int 418 accounts int 419 slots int 420 codes int 421 lastReport time.Time 422 start = time.Now() 423 hasher = crypto.NewKeccakState() 424 got = make([]byte, 32) 425 ) 426 accIter, err := t.NodeIterator(nil) 427 if err != nil { 428 log.Error("Failed to open iterator", "root", root, "err", err) 429 return err 430 } 431 reader, err := triedb.Reader(root) 432 if err != nil { 433 log.Error("State is non-existent", "root", root) 434 return nil 435 } 436 for accIter.Next(true) { 437 nodes += 1 438 node := accIter.Hash() 439 440 // Check the present for non-empty hash node(embedded node doesn't 441 // have their own hash). 442 if node != (common.Hash{}) { 443 blob, _ := reader.Node(common.Hash{}, accIter.Path(), node) 444 if len(blob) == 0 { 445 log.Error("Missing trie node(account)", "hash", node) 446 return errors.New("missing account") 447 } 448 hasher.Reset() 449 hasher.Write(blob) 450 hasher.Read(got) 451 if !bytes.Equal(got, node.Bytes()) { 452 log.Error("Invalid trie node(account)", "hash", node.Hex(), "value", blob) 453 return errors.New("invalid account node") 454 } 455 } 456 // If it's a leaf node, yes we are touching an account, 457 // dig into the storage trie further. 458 if accIter.Leaf() { 459 accounts += 1 460 var acc types.StateAccount 461 if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil { 462 log.Error("Invalid account encountered during traversal", "err", err) 463 return errors.New("invalid account") 464 } 465 if acc.Root != types.EmptyRootHash { 466 id := trie.StorageTrieID(root, common.BytesToHash(accIter.LeafKey()), acc.Root) 467 storageTrie, err := trie.NewStateTrie(id, triedb) 468 if err != nil { 469 log.Error("Failed to open storage trie", "root", acc.Root, "err", err) 470 return errors.New("missing storage trie") 471 } 472 storageIter, err := storageTrie.NodeIterator(nil) 473 if err != nil { 474 log.Error("Failed to open storage iterator", "root", acc.Root, "err", err) 475 return err 476 } 477 for storageIter.Next(true) { 478 nodes += 1 479 node := storageIter.Hash() 480 481 // Check the presence for non-empty hash node(embedded node doesn't 482 // have their own hash). 483 if node != (common.Hash{}) { 484 blob, _ := reader.Node(common.BytesToHash(accIter.LeafKey()), storageIter.Path(), node) 485 if len(blob) == 0 { 486 log.Error("Missing trie node(storage)", "hash", node) 487 return errors.New("missing storage") 488 } 489 hasher.Reset() 490 hasher.Write(blob) 491 hasher.Read(got) 492 if !bytes.Equal(got, node.Bytes()) { 493 log.Error("Invalid trie node(storage)", "hash", node.Hex(), "value", blob) 494 return errors.New("invalid storage node") 495 } 496 } 497 // Bump the counter if it's leaf node. 498 if storageIter.Leaf() { 499 slots += 1 500 } 501 if time.Since(lastReport) > time.Second*8 { 502 log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 503 lastReport = time.Now() 504 } 505 } 506 if storageIter.Error() != nil { 507 log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error()) 508 return storageIter.Error() 509 } 510 } 511 if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) { 512 if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) { 513 log.Error("Code is missing", "account", common.BytesToHash(accIter.LeafKey())) 514 return errors.New("missing code") 515 } 516 codes += 1 517 } 518 if time.Since(lastReport) > time.Second*8 { 519 log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 520 lastReport = time.Now() 521 } 522 } 523 } 524 if accIter.Error() != nil { 525 log.Error("Failed to traverse state trie", "root", root, "err", accIter.Error()) 526 return accIter.Error() 527 } 528 log.Info("State is complete", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 529 return nil 530 } 531 532 func parseRoot(input string) (common.Hash, error) { 533 var h common.Hash 534 if err := h.UnmarshalText([]byte(input)); err != nil { 535 return h, err 536 } 537 return h, nil 538 } 539 540 func dumpState(ctx *cli.Context) error { 541 stack, _ := makeConfigNode(ctx) 542 defer stack.Close() 543 544 db := utils.MakeChainDatabase(ctx, stack, true) 545 defer db.Close() 546 547 conf, root, err := parseDumpConfig(ctx, stack, db) 548 if err != nil { 549 return err 550 } 551 triedb := utils.MakeTrieDatabase(ctx, db, false, true, false) 552 defer triedb.Close() 553 554 snapConfig := snapshot.Config{ 555 CacheSize: 256, 556 Recovery: false, 557 NoBuild: true, 558 AsyncBuild: false, 559 } 560 snaptree, err := snapshot.New(snapConfig, db, triedb, root) 561 if err != nil { 562 return err 563 } 564 accIt, err := snaptree.AccountIterator(root, common.BytesToHash(conf.Start)) 565 if err != nil { 566 return err 567 } 568 defer accIt.Release() 569 570 log.Info("Snapshot dumping started", "root", root) 571 var ( 572 start = time.Now() 573 logged = time.Now() 574 accounts uint64 575 ) 576 enc := json.NewEncoder(os.Stdout) 577 enc.Encode(struct { 578 Root common.Hash `json:"root"` 579 }{root}) 580 for accIt.Next() { 581 account, err := types.FullAccount(accIt.Account()) 582 if err != nil { 583 return err 584 } 585 da := &state.DumpAccount{ 586 Balance: account.Balance.String(), 587 Nonce: account.Nonce, 588 Root: account.Root.Bytes(), 589 CodeHash: account.CodeHash, 590 AddressHash: accIt.Hash().Bytes(), 591 } 592 if !conf.SkipCode && !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) { 593 da.Code = rawdb.ReadCode(db, common.BytesToHash(account.CodeHash)) 594 } 595 if !conf.SkipStorage { 596 da.Storage = make(map[common.Hash]string) 597 598 stIt, err := snaptree.StorageIterator(root, accIt.Hash(), common.Hash{}) 599 if err != nil { 600 return err 601 } 602 for stIt.Next() { 603 da.Storage[stIt.Hash()] = common.Bytes2Hex(stIt.Slot()) 604 } 605 } 606 enc.Encode(da) 607 accounts++ 608 if time.Since(logged) > 8*time.Second { 609 log.Info("Snapshot dumping in progress", "at", accIt.Hash(), "accounts", accounts, 610 "elapsed", common.PrettyDuration(time.Since(start))) 611 logged = time.Now() 612 } 613 if conf.Max > 0 && accounts >= conf.Max { 614 break 615 } 616 } 617 log.Info("Snapshot dumping complete", "accounts", accounts, 618 "elapsed", common.PrettyDuration(time.Since(start))) 619 return nil 620 } 621 622 // snapshotExportPreimages dumps the preimage data to a flat file. 623 func snapshotExportPreimages(ctx *cli.Context) error { 624 if ctx.NArg() < 1 { 625 utils.Fatalf("This command requires an argument.") 626 } 627 stack, _ := makeConfigNode(ctx) 628 defer stack.Close() 629 630 chaindb := utils.MakeChainDatabase(ctx, stack, true) 631 defer chaindb.Close() 632 633 triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true, false) 634 defer triedb.Close() 635 636 var root common.Hash 637 if ctx.NArg() > 1 { 638 rootBytes := common.FromHex(ctx.Args().Get(1)) 639 if len(rootBytes) != common.HashLength { 640 return fmt.Errorf("invalid hash: %s", ctx.Args().Get(1)) 641 } 642 root = common.BytesToHash(rootBytes) 643 } else { 644 headBlock := rawdb.ReadHeadBlock(chaindb) 645 if headBlock == nil { 646 log.Error("Failed to load head block") 647 return errors.New("no head block") 648 } 649 root = headBlock.Root() 650 } 651 snapConfig := snapshot.Config{ 652 CacheSize: 256, 653 Recovery: false, 654 NoBuild: true, 655 AsyncBuild: false, 656 } 657 snaptree, err := snapshot.New(snapConfig, chaindb, triedb, root) 658 if err != nil { 659 return err 660 } 661 return utils.ExportSnapshotPreimages(chaindb, snaptree, ctx.Args().First(), root) 662 } 663 664 // checkAccount iterates the snap data layers, and looks up the given account 665 // across all layers. 666 func checkAccount(ctx *cli.Context) error { 667 if ctx.NArg() != 1 { 668 return errors.New("need <address|hash> arg") 669 } 670 var ( 671 hash common.Hash 672 addr common.Address 673 ) 674 switch arg := ctx.Args().First(); len(arg) { 675 case 40, 42: 676 addr = common.HexToAddress(arg) 677 hash = crypto.Keccak256Hash(addr.Bytes()) 678 case 64, 66: 679 hash = common.HexToHash(arg) 680 default: 681 return errors.New("malformed address or hash") 682 } 683 stack, _ := makeConfigNode(ctx) 684 defer stack.Close() 685 chaindb := utils.MakeChainDatabase(ctx, stack, true) 686 defer chaindb.Close() 687 start := time.Now() 688 log.Info("Checking difflayer journal", "address", addr, "hash", hash) 689 if err := snapshot.CheckJournalAccount(chaindb, hash); err != nil { 690 return err 691 } 692 log.Info("Checked the snapshot journalled storage", "time", common.PrettyDuration(time.Since(start))) 693 return nil 694 }