github.com/hardtosaygoodbye/go-ethereum@v1.10.16-0.20220122011429-97003b9e6c15/cmd/geth/snapshot.go (about) 1 // Copyright 2020 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/hardtosaygoodbye/go-ethereum/cmd/utils" 27 "github.com/hardtosaygoodbye/go-ethereum/common" 28 "github.com/hardtosaygoodbye/go-ethereum/core/rawdb" 29 "github.com/hardtosaygoodbye/go-ethereum/core/state" 30 "github.com/hardtosaygoodbye/go-ethereum/core/state/pruner" 31 "github.com/hardtosaygoodbye/go-ethereum/core/state/snapshot" 32 "github.com/hardtosaygoodbye/go-ethereum/core/types" 33 "github.com/hardtosaygoodbye/go-ethereum/crypto" 34 "github.com/hardtosaygoodbye/go-ethereum/log" 35 "github.com/hardtosaygoodbye/go-ethereum/rlp" 36 "github.com/hardtosaygoodbye/go-ethereum/trie" 37 cli "gopkg.in/urfave/cli.v1" 38 ) 39 40 var ( 41 // emptyRoot is the known root hash of an empty trie. 42 emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") 43 44 // emptyCode is the known hash of the empty EVM bytecode. 45 emptyCode = crypto.Keccak256(nil) 46 ) 47 48 var ( 49 snapshotCommand = cli.Command{ 50 Name: "snapshot", 51 Usage: "A set of commands based on the snapshot", 52 Category: "MISCELLANEOUS COMMANDS", 53 Description: "", 54 Subcommands: []cli.Command{ 55 { 56 Name: "prune-state", 57 Usage: "Prune stale ethereum state data based on the snapshot", 58 ArgsUsage: "<root>", 59 Action: utils.MigrateFlags(pruneState), 60 Category: "MISCELLANEOUS COMMANDS", 61 Flags: []cli.Flag{ 62 utils.DataDirFlag, 63 utils.AncientFlag, 64 utils.RopstenFlag, 65 utils.SepoliaFlag, 66 utils.RinkebyFlag, 67 utils.GoerliFlag, 68 utils.CacheTrieJournalFlag, 69 utils.BloomFilterSizeFlag, 70 }, 71 Description: ` 72 geth snapshot prune-state <state-root> 73 will prune historical state data with the help of the state snapshot. 74 All trie nodes and contract codes that do not belong to the specified 75 version state will be deleted from the database. After pruning, only 76 two version states are available: genesis and the specific one. 77 78 The default pruning target is the HEAD-127 state. 79 80 WARNING: It's necessary to delete the trie clean cache after the pruning. 81 If you specify another directory for the trie clean cache via "--cache.trie.journal" 82 during the use of Geth, please also specify it here for correct deletion. Otherwise 83 the trie clean cache with default directory will be deleted. 84 `, 85 }, 86 { 87 Name: "verify-state", 88 Usage: "Recalculate state hash based on the snapshot for verification", 89 ArgsUsage: "<root>", 90 Action: utils.MigrateFlags(verifyState), 91 Category: "MISCELLANEOUS COMMANDS", 92 Flags: []cli.Flag{ 93 utils.DataDirFlag, 94 utils.AncientFlag, 95 utils.RopstenFlag, 96 utils.SepoliaFlag, 97 utils.RinkebyFlag, 98 utils.GoerliFlag, 99 }, 100 Description: ` 101 geth snapshot verify-state <state-root> 102 will traverse the whole accounts and storages set based on the specified 103 snapshot and recalculate the root hash of state for verification. 104 In other words, this command does the snapshot to trie conversion. 105 `, 106 }, 107 { 108 Name: "traverse-state", 109 Usage: "Traverse the state with given root hash for verification", 110 ArgsUsage: "<root>", 111 Action: utils.MigrateFlags(traverseState), 112 Category: "MISCELLANEOUS COMMANDS", 113 Flags: []cli.Flag{ 114 utils.DataDirFlag, 115 utils.AncientFlag, 116 utils.RopstenFlag, 117 utils.SepoliaFlag, 118 utils.RinkebyFlag, 119 utils.GoerliFlag, 120 }, 121 Description: ` 122 geth snapshot traverse-state <state-root> 123 will traverse the whole state from the given state root and will abort if any 124 referenced trie node or contract code is missing. This command can be used for 125 state integrity verification. The default checking target is the HEAD state. 126 127 It's also usable without snapshot enabled. 128 `, 129 }, 130 { 131 Name: "traverse-rawstate", 132 Usage: "Traverse the state with given root hash for verification", 133 ArgsUsage: "<root>", 134 Action: utils.MigrateFlags(traverseRawState), 135 Category: "MISCELLANEOUS COMMANDS", 136 Flags: []cli.Flag{ 137 utils.DataDirFlag, 138 utils.AncientFlag, 139 utils.RopstenFlag, 140 utils.SepoliaFlag, 141 utils.RinkebyFlag, 142 utils.GoerliFlag, 143 }, 144 Description: ` 145 geth snapshot traverse-rawstate <state-root> 146 will traverse the whole state from the given root and will abort if any referenced 147 trie node or contract code is missing. This command can be used for state integrity 148 verification. The default checking target is the HEAD state. It's basically identical 149 to traverse-state, but the check granularity is smaller. 150 151 It's also usable without snapshot enabled. 152 `, 153 }, 154 { 155 Name: "dump", 156 Usage: "Dump a specific block from storage (same as 'geth dump' but using snapshots)", 157 ArgsUsage: "[? <blockHash> | <blockNum>]", 158 Action: utils.MigrateFlags(dumpState), 159 Category: "MISCELLANEOUS COMMANDS", 160 Flags: []cli.Flag{ 161 utils.DataDirFlag, 162 utils.AncientFlag, 163 utils.RopstenFlag, 164 utils.SepoliaFlag, 165 utils.RinkebyFlag, 166 utils.GoerliFlag, 167 utils.ExcludeCodeFlag, 168 utils.ExcludeStorageFlag, 169 utils.StartKeyFlag, 170 utils.DumpLimitFlag, 171 }, 172 Description: ` 173 This command is semantically equivalent to 'geth dump', but uses the snapshots 174 as the backend data source, making this command a lot faster. 175 176 The argument is interpreted as block number or hash. If none is provided, the latest 177 block is used. 178 `, 179 }, 180 }, 181 } 182 ) 183 184 func pruneState(ctx *cli.Context) error { 185 stack, config := makeConfigNode(ctx) 186 defer stack.Close() 187 188 chaindb := utils.MakeChainDatabase(ctx, stack, false) 189 pruner, err := pruner.NewPruner(chaindb, stack.ResolvePath(""), stack.ResolvePath(config.Eth.TrieCleanCacheJournal), ctx.GlobalUint64(utils.BloomFilterSizeFlag.Name)) 190 if err != nil { 191 log.Error("Failed to open snapshot tree", "err", err) 192 return err 193 } 194 if ctx.NArg() > 1 { 195 log.Error("Too many arguments given") 196 return errors.New("too many arguments") 197 } 198 var targetRoot common.Hash 199 if ctx.NArg() == 1 { 200 targetRoot, err = parseRoot(ctx.Args()[0]) 201 if err != nil { 202 log.Error("Failed to resolve state root", "err", err) 203 return err 204 } 205 } 206 if err = pruner.Prune(targetRoot); err != nil { 207 log.Error("Failed to prune state", "err", err) 208 return err 209 } 210 return nil 211 } 212 213 func verifyState(ctx *cli.Context) error { 214 stack, _ := makeConfigNode(ctx) 215 defer stack.Close() 216 217 chaindb := utils.MakeChainDatabase(ctx, stack, true) 218 headBlock := rawdb.ReadHeadBlock(chaindb) 219 if headBlock == nil { 220 log.Error("Failed to load head block") 221 return errors.New("no head block") 222 } 223 snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, headBlock.Root(), false, false, false) 224 if err != nil { 225 log.Error("Failed to open snapshot tree", "err", err) 226 return err 227 } 228 if ctx.NArg() > 1 { 229 log.Error("Too many arguments given") 230 return errors.New("too many arguments") 231 } 232 var root = headBlock.Root() 233 if ctx.NArg() == 1 { 234 root, err = parseRoot(ctx.Args()[0]) 235 if err != nil { 236 log.Error("Failed to resolve state root", "err", err) 237 return err 238 } 239 } 240 if err := snaptree.Verify(root); err != nil { 241 log.Error("Failed to verify state", "root", root, "err", err) 242 return err 243 } 244 log.Info("Verified the state", "root", root) 245 return nil 246 } 247 248 // traverseState is a helper function used for pruning verification. 249 // Basically it just iterates the trie, ensure all nodes and associated 250 // contract codes are present. 251 func traverseState(ctx *cli.Context) error { 252 stack, _ := makeConfigNode(ctx) 253 defer stack.Close() 254 255 chaindb := utils.MakeChainDatabase(ctx, stack, true) 256 headBlock := rawdb.ReadHeadBlock(chaindb) 257 if headBlock == nil { 258 log.Error("Failed to load head block") 259 return errors.New("no head block") 260 } 261 if ctx.NArg() > 1 { 262 log.Error("Too many arguments given") 263 return errors.New("too many arguments") 264 } 265 var ( 266 root common.Hash 267 err error 268 ) 269 if ctx.NArg() == 1 { 270 root, err = parseRoot(ctx.Args()[0]) 271 if err != nil { 272 log.Error("Failed to resolve state root", "err", err) 273 return err 274 } 275 log.Info("Start traversing the state", "root", root) 276 } else { 277 root = headBlock.Root() 278 log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) 279 } 280 triedb := trie.NewDatabase(chaindb) 281 t, err := trie.NewSecure(root, triedb) 282 if err != nil { 283 log.Error("Failed to open trie", "root", root, "err", err) 284 return err 285 } 286 var ( 287 accounts int 288 slots int 289 codes int 290 lastReport time.Time 291 start = time.Now() 292 ) 293 accIter := trie.NewIterator(t.NodeIterator(nil)) 294 for accIter.Next() { 295 accounts += 1 296 var acc types.StateAccount 297 if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil { 298 log.Error("Invalid account encountered during traversal", "err", err) 299 return err 300 } 301 if acc.Root != emptyRoot { 302 storageTrie, err := trie.NewSecure(acc.Root, triedb) 303 if err != nil { 304 log.Error("Failed to open storage trie", "root", acc.Root, "err", err) 305 return err 306 } 307 storageIter := trie.NewIterator(storageTrie.NodeIterator(nil)) 308 for storageIter.Next() { 309 slots += 1 310 } 311 if storageIter.Err != nil { 312 log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err) 313 return storageIter.Err 314 } 315 } 316 if !bytes.Equal(acc.CodeHash, emptyCode) { 317 code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash)) 318 if len(code) == 0 { 319 log.Error("Code is missing", "hash", common.BytesToHash(acc.CodeHash)) 320 return errors.New("missing code") 321 } 322 codes += 1 323 } 324 if time.Since(lastReport) > time.Second*8 { 325 log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 326 lastReport = time.Now() 327 } 328 } 329 if accIter.Err != nil { 330 log.Error("Failed to traverse state trie", "root", root, "err", accIter.Err) 331 return accIter.Err 332 } 333 log.Info("State is complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 334 return nil 335 } 336 337 // traverseRawState is a helper function used for pruning verification. 338 // Basically it just iterates the trie, ensure all nodes and associated 339 // contract codes are present. It's basically identical to traverseState 340 // but it will check each trie node. 341 func traverseRawState(ctx *cli.Context) error { 342 stack, _ := makeConfigNode(ctx) 343 defer stack.Close() 344 345 chaindb := utils.MakeChainDatabase(ctx, stack, true) 346 headBlock := rawdb.ReadHeadBlock(chaindb) 347 if headBlock == nil { 348 log.Error("Failed to load head block") 349 return errors.New("no head block") 350 } 351 if ctx.NArg() > 1 { 352 log.Error("Too many arguments given") 353 return errors.New("too many arguments") 354 } 355 var ( 356 root common.Hash 357 err error 358 ) 359 if ctx.NArg() == 1 { 360 root, err = parseRoot(ctx.Args()[0]) 361 if err != nil { 362 log.Error("Failed to resolve state root", "err", err) 363 return err 364 } 365 log.Info("Start traversing the state", "root", root) 366 } else { 367 root = headBlock.Root() 368 log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) 369 } 370 triedb := trie.NewDatabase(chaindb) 371 t, err := trie.NewSecure(root, triedb) 372 if err != nil { 373 log.Error("Failed to open trie", "root", root, "err", err) 374 return err 375 } 376 var ( 377 nodes int 378 accounts int 379 slots int 380 codes int 381 lastReport time.Time 382 start = time.Now() 383 ) 384 accIter := t.NodeIterator(nil) 385 for accIter.Next(true) { 386 nodes += 1 387 node := accIter.Hash() 388 389 if node != (common.Hash{}) { 390 // Check the present for non-empty hash node(embedded node doesn't 391 // have their own hash). 392 blob := rawdb.ReadTrieNode(chaindb, node) 393 if len(blob) == 0 { 394 log.Error("Missing trie node(account)", "hash", node) 395 return errors.New("missing account") 396 } 397 } 398 // If it's a leaf node, yes we are touching an account, 399 // dig into the storage trie further. 400 if accIter.Leaf() { 401 accounts += 1 402 var acc types.StateAccount 403 if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil { 404 log.Error("Invalid account encountered during traversal", "err", err) 405 return errors.New("invalid account") 406 } 407 if acc.Root != emptyRoot { 408 storageTrie, err := trie.NewSecure(acc.Root, triedb) 409 if err != nil { 410 log.Error("Failed to open storage trie", "root", acc.Root, "err", err) 411 return errors.New("missing storage trie") 412 } 413 storageIter := storageTrie.NodeIterator(nil) 414 for storageIter.Next(true) { 415 nodes += 1 416 node := storageIter.Hash() 417 418 // Check the present for non-empty hash node(embedded node doesn't 419 // have their own hash). 420 if node != (common.Hash{}) { 421 if !rawdb.HasTrieNode(chaindb, node) { 422 log.Error("Missing trie node(storage)", "hash", node) 423 return errors.New("missing storage") 424 } 425 } 426 // Bump the counter if it's leaf node. 427 if storageIter.Leaf() { 428 slots += 1 429 } 430 } 431 if storageIter.Error() != nil { 432 log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error()) 433 return storageIter.Error() 434 } 435 } 436 if !bytes.Equal(acc.CodeHash, emptyCode) { 437 code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash)) 438 if len(code) == 0 { 439 log.Error("Code is missing", "account", common.BytesToHash(accIter.LeafKey())) 440 return errors.New("missing code") 441 } 442 codes += 1 443 } 444 if time.Since(lastReport) > time.Second*8 { 445 log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 446 lastReport = time.Now() 447 } 448 } 449 } 450 if accIter.Error() != nil { 451 log.Error("Failed to traverse state trie", "root", root, "err", accIter.Error()) 452 return accIter.Error() 453 } 454 log.Info("State is complete", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 455 return nil 456 } 457 458 func parseRoot(input string) (common.Hash, error) { 459 var h common.Hash 460 if err := h.UnmarshalText([]byte(input)); err != nil { 461 return h, err 462 } 463 return h, nil 464 } 465 466 func dumpState(ctx *cli.Context) error { 467 stack, _ := makeConfigNode(ctx) 468 defer stack.Close() 469 470 conf, db, root, err := parseDumpConfig(ctx, stack) 471 if err != nil { 472 return err 473 } 474 snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, root, false, false, false) 475 if err != nil { 476 return err 477 } 478 accIt, err := snaptree.AccountIterator(root, common.BytesToHash(conf.Start)) 479 if err != nil { 480 return err 481 } 482 defer accIt.Release() 483 484 log.Info("Snapshot dumping started", "root", root) 485 var ( 486 start = time.Now() 487 logged = time.Now() 488 accounts uint64 489 ) 490 enc := json.NewEncoder(os.Stdout) 491 enc.Encode(struct { 492 Root common.Hash `json:"root"` 493 }{root}) 494 for accIt.Next() { 495 account, err := snapshot.FullAccount(accIt.Account()) 496 if err != nil { 497 return err 498 } 499 da := &state.DumpAccount{ 500 Balance: account.Balance.String(), 501 Nonce: account.Nonce, 502 Root: account.Root, 503 CodeHash: account.CodeHash, 504 SecureKey: accIt.Hash().Bytes(), 505 } 506 if !conf.SkipCode && !bytes.Equal(account.CodeHash, emptyCode) { 507 da.Code = rawdb.ReadCode(db, common.BytesToHash(account.CodeHash)) 508 } 509 if !conf.SkipStorage { 510 da.Storage = make(map[common.Hash]string) 511 512 stIt, err := snaptree.StorageIterator(root, accIt.Hash(), common.Hash{}) 513 if err != nil { 514 return err 515 } 516 for stIt.Next() { 517 da.Storage[stIt.Hash()] = common.Bytes2Hex(stIt.Slot()) 518 } 519 } 520 enc.Encode(da) 521 accounts++ 522 if time.Since(logged) > 8*time.Second { 523 log.Info("Snapshot dumping in progress", "at", accIt.Hash(), "accounts", accounts, 524 "elapsed", common.PrettyDuration(time.Since(start))) 525 logged = time.Now() 526 } 527 if conf.Max > 0 && accounts >= conf.Max { 528 break 529 } 530 } 531 log.Info("Snapshot dumping complete", "accounts", accounts, 532 "elapsed", common.PrettyDuration(time.Since(start))) 533 return nil 534 }