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