github.com/calmw/ethereum@v0.1.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 "os" 24 "time" 25 26 "github.com/calmw/ethereum/cmd/utils" 27 "github.com/calmw/ethereum/common" 28 "github.com/calmw/ethereum/core/rawdb" 29 "github.com/calmw/ethereum/core/state" 30 "github.com/calmw/ethereum/core/state/pruner" 31 "github.com/calmw/ethereum/core/state/snapshot" 32 "github.com/calmw/ethereum/core/types" 33 "github.com/calmw/ethereum/crypto" 34 "github.com/calmw/ethereum/internal/flags" 35 "github.com/calmw/ethereum/log" 36 "github.com/calmw/ethereum/rlp" 37 "github.com/calmw/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 targetRoot common.Hash 184 if ctx.NArg() == 1 { 185 targetRoot, err = parseRoot(ctx.Args().First()) 186 if err != nil { 187 log.Error("Failed to resolve state root", "err", err) 188 return err 189 } 190 } 191 if err = pruner.Prune(targetRoot); err != nil { 192 log.Error("Failed to prune state", "err", err) 193 return err 194 } 195 return nil 196 } 197 198 func verifyState(ctx *cli.Context) error { 199 stack, _ := makeConfigNode(ctx) 200 defer stack.Close() 201 202 chaindb := utils.MakeChainDatabase(ctx, stack, true) 203 defer chaindb.Close() 204 205 headBlock := rawdb.ReadHeadBlock(chaindb) 206 if headBlock == nil { 207 log.Error("Failed to load head block") 208 return errors.New("no head block") 209 } 210 snapconfig := snapshot.Config{ 211 CacheSize: 256, 212 Recovery: false, 213 NoBuild: true, 214 AsyncBuild: false, 215 } 216 snaptree, err := snapshot.New(snapconfig, chaindb, trie.NewDatabase(chaindb), headBlock.Root()) 217 if err != nil { 218 log.Error("Failed to open snapshot tree", "err", err) 219 return err 220 } 221 if ctx.NArg() > 1 { 222 log.Error("Too many arguments given") 223 return errors.New("too many arguments") 224 } 225 var root = headBlock.Root() 226 if ctx.NArg() == 1 { 227 root, err = parseRoot(ctx.Args().First()) 228 if err != nil { 229 log.Error("Failed to resolve state root", "err", err) 230 return err 231 } 232 } 233 if err := snaptree.Verify(root); err != nil { 234 log.Error("Failed to verify state", "root", root, "err", err) 235 return err 236 } 237 log.Info("Verified the state", "root", root) 238 return snapshot.CheckDanglingStorage(chaindb) 239 } 240 241 // checkDanglingStorage iterates the snap storage data, and verifies that all 242 // storage also has corresponding account data. 243 func checkDanglingStorage(ctx *cli.Context) error { 244 stack, _ := makeConfigNode(ctx) 245 defer stack.Close() 246 247 return snapshot.CheckDanglingStorage(utils.MakeChainDatabase(ctx, stack, true)) 248 } 249 250 // traverseState is a helper function used for pruning verification. 251 // Basically it just iterates the trie, ensure all nodes and associated 252 // contract codes are present. 253 func traverseState(ctx *cli.Context) error { 254 stack, _ := makeConfigNode(ctx) 255 defer stack.Close() 256 257 chaindb := utils.MakeChainDatabase(ctx, stack, true) 258 headBlock := rawdb.ReadHeadBlock(chaindb) 259 if headBlock == nil { 260 log.Error("Failed to load head block") 261 return errors.New("no head block") 262 } 263 if ctx.NArg() > 1 { 264 log.Error("Too many arguments given") 265 return errors.New("too many arguments") 266 } 267 var ( 268 root common.Hash 269 err error 270 ) 271 if ctx.NArg() == 1 { 272 root, err = parseRoot(ctx.Args().First()) 273 if err != nil { 274 log.Error("Failed to resolve state root", "err", err) 275 return err 276 } 277 log.Info("Start traversing the state", "root", root) 278 } else { 279 root = headBlock.Root() 280 log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) 281 } 282 triedb := trie.NewDatabase(chaindb) 283 t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb) 284 if err != nil { 285 log.Error("Failed to open trie", "root", root, "err", err) 286 return err 287 } 288 var ( 289 accounts int 290 slots int 291 codes int 292 lastReport time.Time 293 start = time.Now() 294 ) 295 accIter := trie.NewIterator(t.NodeIterator(nil)) 296 for accIter.Next() { 297 accounts += 1 298 var acc types.StateAccount 299 if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil { 300 log.Error("Invalid account encountered during traversal", "err", err) 301 return err 302 } 303 if acc.Root != types.EmptyRootHash { 304 id := trie.StorageTrieID(root, common.BytesToHash(accIter.Key), acc.Root) 305 storageTrie, err := trie.NewStateTrie(id, triedb) 306 if err != nil { 307 log.Error("Failed to open storage trie", "root", acc.Root, "err", err) 308 return err 309 } 310 storageIter := trie.NewIterator(storageTrie.NodeIterator(nil)) 311 for storageIter.Next() { 312 slots += 1 313 } 314 if storageIter.Err != nil { 315 log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err) 316 return storageIter.Err 317 } 318 } 319 if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) { 320 if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) { 321 log.Error("Code is missing", "hash", common.BytesToHash(acc.CodeHash)) 322 return errors.New("missing code") 323 } 324 codes += 1 325 } 326 if time.Since(lastReport) > time.Second*8 { 327 log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 328 lastReport = time.Now() 329 } 330 } 331 if accIter.Err != nil { 332 log.Error("Failed to traverse state trie", "root", root, "err", accIter.Err) 333 return accIter.Err 334 } 335 log.Info("State is complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 336 return nil 337 } 338 339 // traverseRawState is a helper function used for pruning verification. 340 // Basically it just iterates the trie, ensure all nodes and associated 341 // contract codes are present. It's basically identical to traverseState 342 // but it will check each trie node. 343 func traverseRawState(ctx *cli.Context) error { 344 stack, _ := makeConfigNode(ctx) 345 defer stack.Close() 346 347 chaindb := utils.MakeChainDatabase(ctx, stack, true) 348 headBlock := rawdb.ReadHeadBlock(chaindb) 349 if headBlock == nil { 350 log.Error("Failed to load head block") 351 return errors.New("no head block") 352 } 353 if ctx.NArg() > 1 { 354 log.Error("Too many arguments given") 355 return errors.New("too many arguments") 356 } 357 var ( 358 root common.Hash 359 err error 360 ) 361 if ctx.NArg() == 1 { 362 root, err = parseRoot(ctx.Args().First()) 363 if err != nil { 364 log.Error("Failed to resolve state root", "err", err) 365 return err 366 } 367 log.Info("Start traversing the state", "root", root) 368 } else { 369 root = headBlock.Root() 370 log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) 371 } 372 triedb := trie.NewDatabase(chaindb) 373 t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb) 374 if err != nil { 375 log.Error("Failed to open trie", "root", root, "err", err) 376 return err 377 } 378 var ( 379 nodes int 380 accounts int 381 slots int 382 codes int 383 lastReport time.Time 384 start = time.Now() 385 hasher = crypto.NewKeccakState() 386 got = make([]byte, 32) 387 ) 388 accIter := t.NodeIterator(nil) 389 for accIter.Next(true) { 390 nodes += 1 391 node := accIter.Hash() 392 393 // Check the present for non-empty hash node(embedded node doesn't 394 // have their own hash). 395 if node != (common.Hash{}) { 396 blob := rawdb.ReadLegacyTrieNode(chaindb, node) 397 if len(blob) == 0 { 398 log.Error("Missing trie node(account)", "hash", node) 399 return errors.New("missing account") 400 } 401 hasher.Reset() 402 hasher.Write(blob) 403 hasher.Read(got) 404 if !bytes.Equal(got, node.Bytes()) { 405 log.Error("Invalid trie node(account)", "hash", node.Hex(), "value", blob) 406 return errors.New("invalid account node") 407 } 408 } 409 // If it's a leaf node, yes we are touching an account, 410 // dig into the storage trie further. 411 if accIter.Leaf() { 412 accounts += 1 413 var acc types.StateAccount 414 if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil { 415 log.Error("Invalid account encountered during traversal", "err", err) 416 return errors.New("invalid account") 417 } 418 if acc.Root != types.EmptyRootHash { 419 id := trie.StorageTrieID(root, common.BytesToHash(accIter.LeafKey()), acc.Root) 420 storageTrie, err := trie.NewStateTrie(id, triedb) 421 if err != nil { 422 log.Error("Failed to open storage trie", "root", acc.Root, "err", err) 423 return errors.New("missing storage trie") 424 } 425 storageIter := storageTrie.NodeIterator(nil) 426 for storageIter.Next(true) { 427 nodes += 1 428 node := storageIter.Hash() 429 430 // Check the presence for non-empty hash node(embedded node doesn't 431 // have their own hash). 432 if node != (common.Hash{}) { 433 blob := rawdb.ReadLegacyTrieNode(chaindb, node) 434 if len(blob) == 0 { 435 log.Error("Missing trie node(storage)", "hash", node) 436 return errors.New("missing storage") 437 } 438 hasher.Reset() 439 hasher.Write(blob) 440 hasher.Read(got) 441 if !bytes.Equal(got, node.Bytes()) { 442 log.Error("Invalid trie node(storage)", "hash", node.Hex(), "value", blob) 443 return errors.New("invalid storage node") 444 } 445 } 446 // Bump the counter if it's leaf node. 447 if storageIter.Leaf() { 448 slots += 1 449 } 450 } 451 if storageIter.Error() != nil { 452 log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error()) 453 return storageIter.Error() 454 } 455 } 456 if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) { 457 if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) { 458 log.Error("Code is missing", "account", common.BytesToHash(accIter.LeafKey())) 459 return errors.New("missing code") 460 } 461 codes += 1 462 } 463 if time.Since(lastReport) > time.Second*8 { 464 log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 465 lastReport = time.Now() 466 } 467 } 468 } 469 if accIter.Error() != nil { 470 log.Error("Failed to traverse state trie", "root", root, "err", accIter.Error()) 471 return accIter.Error() 472 } 473 log.Info("State is complete", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 474 return nil 475 } 476 477 func parseRoot(input string) (common.Hash, error) { 478 var h common.Hash 479 if err := h.UnmarshalText([]byte(input)); err != nil { 480 return h, err 481 } 482 return h, nil 483 } 484 485 func dumpState(ctx *cli.Context) error { 486 stack, _ := makeConfigNode(ctx) 487 defer stack.Close() 488 489 conf, db, root, err := parseDumpConfig(ctx, stack) 490 if err != nil { 491 return err 492 } 493 snapConfig := snapshot.Config{ 494 CacheSize: 256, 495 Recovery: false, 496 NoBuild: true, 497 AsyncBuild: false, 498 } 499 snaptree, err := snapshot.New(snapConfig, db, trie.NewDatabase(db), root) 500 if err != nil { 501 return err 502 } 503 accIt, err := snaptree.AccountIterator(root, common.BytesToHash(conf.Start)) 504 if err != nil { 505 return err 506 } 507 defer accIt.Release() 508 509 log.Info("Snapshot dumping started", "root", root) 510 var ( 511 start = time.Now() 512 logged = time.Now() 513 accounts uint64 514 ) 515 enc := json.NewEncoder(os.Stdout) 516 enc.Encode(struct { 517 Root common.Hash `json:"root"` 518 }{root}) 519 for accIt.Next() { 520 account, err := snapshot.FullAccount(accIt.Account()) 521 if err != nil { 522 return err 523 } 524 da := &state.DumpAccount{ 525 Balance: account.Balance.String(), 526 Nonce: account.Nonce, 527 Root: account.Root, 528 CodeHash: account.CodeHash, 529 SecureKey: accIt.Hash().Bytes(), 530 } 531 if !conf.SkipCode && !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) { 532 da.Code = rawdb.ReadCode(db, common.BytesToHash(account.CodeHash)) 533 } 534 if !conf.SkipStorage { 535 da.Storage = make(map[common.Hash]string) 536 537 stIt, err := snaptree.StorageIterator(root, accIt.Hash(), common.Hash{}) 538 if err != nil { 539 return err 540 } 541 for stIt.Next() { 542 da.Storage[stIt.Hash()] = common.Bytes2Hex(stIt.Slot()) 543 } 544 } 545 enc.Encode(da) 546 accounts++ 547 if time.Since(logged) > 8*time.Second { 548 log.Info("Snapshot dumping in progress", "at", accIt.Hash(), "accounts", accounts, 549 "elapsed", common.PrettyDuration(time.Since(start))) 550 logged = time.Now() 551 } 552 if conf.Max > 0 && accounts >= conf.Max { 553 break 554 } 555 } 556 log.Info("Snapshot dumping complete", "accounts", accounts, 557 "elapsed", common.PrettyDuration(time.Since(start))) 558 return nil 559 } 560 561 // checkAccount iterates the snap data layers, and looks up the given account 562 // across all layers. 563 func checkAccount(ctx *cli.Context) error { 564 if ctx.NArg() != 1 { 565 return errors.New("need <address|hash> arg") 566 } 567 var ( 568 hash common.Hash 569 addr common.Address 570 ) 571 switch arg := ctx.Args().First(); len(arg) { 572 case 40, 42: 573 addr = common.HexToAddress(arg) 574 hash = crypto.Keccak256Hash(addr.Bytes()) 575 case 64, 66: 576 hash = common.HexToHash(arg) 577 default: 578 return errors.New("malformed address or hash") 579 } 580 stack, _ := makeConfigNode(ctx) 581 defer stack.Close() 582 chaindb := utils.MakeChainDatabase(ctx, stack, true) 583 defer chaindb.Close() 584 start := time.Now() 585 log.Info("Checking difflayer journal", "address", addr, "hash", hash) 586 if err := snapshot.CheckJournalAccount(chaindb, hash); err != nil { 587 return err 588 } 589 log.Info("Checked the snapshot journalled storage", "time", common.PrettyDuration(time.Since(start))) 590 return nil 591 }