github.com/fff-chain/go-fff@v0.0.0-20220726032732-1c84420b8a99/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 "errors" 22 "fmt" 23 "os" 24 "path/filepath" 25 "time" 26 27 "github.com/prometheus/tsdb/fileutil" 28 cli "gopkg.in/urfave/cli.v1" 29 30 "github.com/fff-chain/go-fff/cmd/utils" 31 "github.com/fff-chain/go-fff/common" 32 "github.com/fff-chain/go-fff/core/rawdb" 33 "github.com/fff-chain/go-fff/core/state" 34 "github.com/fff-chain/go-fff/core/state/pruner" 35 "github.com/fff-chain/go-fff/core/state/snapshot" 36 "github.com/fff-chain/go-fff/crypto" 37 "github.com/fff-chain/go-fff/ethdb" 38 "github.com/fff-chain/go-fff/log" 39 "github.com/fff-chain/go-fff/node" 40 "github.com/fff-chain/go-fff/rlp" 41 "github.com/fff-chain/go-fff/trie" 42 ) 43 44 var ( 45 // emptyRoot is the known root hash of an empty trie. 46 emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") 47 48 // emptyCode is the known hash of the empty EVM bytecode. 49 emptyCode = crypto.Keccak256(nil) 50 ) 51 52 var ( 53 snapshotCommand = cli.Command{ 54 Name: "snapshot", 55 Usage: "A set of commands based on the snapshot", 56 Category: "MISCELLANEOUS COMMANDS", 57 Description: "", 58 Subcommands: []cli.Command{ 59 { 60 Name: "prune-state", 61 Usage: "Prune stale ethereum state data based on the snapshot", 62 ArgsUsage: "<root>", 63 Action: utils.MigrateFlags(pruneState), 64 Category: "MISCELLANEOUS COMMANDS", 65 Flags: []cli.Flag{ 66 utils.DataDirFlag, 67 utils.AncientFlag, 68 utils.RopstenFlag, 69 utils.RinkebyFlag, 70 utils.GoerliFlag, 71 utils.CacheTrieJournalFlag, 72 utils.BloomFilterSizeFlag, 73 utils.TriesInMemoryFlag, 74 }, 75 Description: ` 76 geth snapshot prune-state <state-root> 77 will prune historical state data with the help of the state snapshot. 78 All trie nodes and contract codes that do not belong to the specified 79 version state will be deleted from the database. After pruning, only 80 two version states are available: genesis and the specific one. 81 82 The default pruning target is the HEAD-127 state. 83 84 WARNING: It's necessary to delete the trie clean cache after the pruning. 85 If you specify another directory for the trie clean cache via "--cache.trie.journal" 86 during the use of Geth, please also specify it here for correct deletion. Otherwise 87 the trie clean cache with default directory will be deleted. 88 `, 89 }, 90 { 91 Name: "prune-block", 92 Usage: "Prune block data offline", 93 Action: utils.MigrateFlags(pruneBlock), 94 Category: "MISCELLANEOUS COMMANDS", 95 Flags: []cli.Flag{ 96 utils.DataDirFlag, 97 utils.AncientFlag, 98 utils.BlockAmountReserved, 99 utils.TriesInMemoryFlag, 100 utils.CheckSnapshotWithMPT, 101 }, 102 Description: ` 103 geth offline prune-block for block data in ancientdb. 104 The amount of blocks expected for remaining after prune can be specified via block-amount-reserved in this command, 105 will prune and only remain the specified amount of old block data in ancientdb. 106 the brief workflow is to backup the the number of this specified amount blocks backward in original ancientdb 107 into new ancient_backup, then delete the original ancientdb dir and rename the ancient_backup to original one for replacement, 108 finally assemble the statedb and new ancientDb together. 109 The purpose of doing it is because the block data will be moved into the ancient store when it 110 becomes old enough(exceed the Threshold 90000), the disk usage will be very large over time, and is occupied mainly by ancientDb, 111 so it's very necessary to do block data prune, this feature will handle it. 112 `, 113 }, 114 { 115 Name: "verify-state", 116 Usage: "Recalculate state hash based on the snapshot for verification", 117 ArgsUsage: "<root>", 118 Action: utils.MigrateFlags(verifyState), 119 Category: "MISCELLANEOUS COMMANDS", 120 Flags: []cli.Flag{ 121 utils.DataDirFlag, 122 utils.AncientFlag, 123 utils.RopstenFlag, 124 utils.RinkebyFlag, 125 utils.GoerliFlag, 126 }, 127 Description: ` 128 geth snapshot verify-state <state-root> 129 will traverse the whole accounts and storages set based on the specified 130 snapshot and recalculate the root hash of state for verification. 131 In other words, this command does the snapshot to trie conversion. 132 `, 133 }, 134 { 135 Name: "traverse-state", 136 Usage: "Traverse the state with given root hash for verification", 137 ArgsUsage: "<root>", 138 Action: utils.MigrateFlags(traverseState), 139 Category: "MISCELLANEOUS COMMANDS", 140 Flags: []cli.Flag{ 141 utils.DataDirFlag, 142 utils.AncientFlag, 143 utils.RopstenFlag, 144 utils.RinkebyFlag, 145 utils.GoerliFlag, 146 }, 147 Description: ` 148 geth snapshot traverse-state <state-root> 149 will traverse the whole state from the given state root and will abort if any 150 referenced trie node or contract code is missing. This command can be used for 151 state integrity verification. The default checking target is the HEAD state. 152 153 It's also usable without snapshot enabled. 154 `, 155 }, 156 { 157 Name: "traverse-rawstate", 158 Usage: "Traverse the state with given root hash for verification", 159 ArgsUsage: "<root>", 160 Action: utils.MigrateFlags(traverseRawState), 161 Category: "MISCELLANEOUS COMMANDS", 162 Flags: []cli.Flag{ 163 utils.DataDirFlag, 164 utils.AncientFlag, 165 utils.RopstenFlag, 166 utils.RinkebyFlag, 167 utils.GoerliFlag, 168 }, 169 Description: ` 170 geth snapshot traverse-rawstate <state-root> 171 will traverse the whole state from the given root and will abort if any referenced 172 trie node or contract code is missing. This command can be used for state integrity 173 verification. The default checking target is the HEAD state. It's basically identical 174 to traverse-state, but the check granularity is smaller. 175 176 It's also usable without snapshot enabled. 177 `, 178 }, 179 }, 180 } 181 ) 182 183 func accessDb(ctx *cli.Context, stack *node.Node) (ethdb.Database, error) { 184 //The layer of tries trees that keep in memory. 185 TriesInMemory := int(ctx.GlobalUint64(utils.TriesInMemoryFlag.Name)) 186 chaindb := utils.MakeChainDatabase(ctx, stack, false, true) 187 defer chaindb.Close() 188 189 if !ctx.GlobalBool(utils.CheckSnapshotWithMPT.Name) { 190 return chaindb, nil 191 } 192 headBlock := rawdb.ReadHeadBlock(chaindb) 193 if headBlock == nil { 194 return nil, errors.New("failed to load head block") 195 } 196 headHeader := headBlock.Header() 197 //Make sure the MPT and snapshot matches before pruning, otherwise the node can not start. 198 snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, TriesInMemory, headBlock.Root(), false, false, false) 199 if err != nil { 200 log.Error("snaptree error", "err", err) 201 return nil, err // The relevant snapshot(s) might not exist 202 } 203 204 // Use the HEAD-(n-1) as the target root. The reason for picking it is: 205 // - in most of the normal cases, the related state is available 206 // - the probability of this layer being reorg is very low 207 208 // Retrieve all snapshot layers from the current HEAD. 209 // In theory there are n difflayers + 1 disk layer present, 210 // so n diff layers are expected to be returned. 211 layers := snaptree.Snapshots(headHeader.Root, TriesInMemory, true) 212 if len(layers) != TriesInMemory { 213 // Reject if the accumulated diff layers are less than n. It 214 // means in most of normal cases, there is no associated state 215 // with bottom-most diff layer. 216 log.Error("snapshot layers != TriesInMemory", "err", err) 217 return nil, fmt.Errorf("snapshot not old enough yet: need %d more blocks", TriesInMemory-len(layers)) 218 } 219 // Use the bottom-most diff layer as the target 220 targetRoot := layers[len(layers)-1].Root() 221 222 // Ensure the root is really present. The weak assumption 223 // is the presence of root can indicate the presence of the 224 // entire trie. 225 if blob := rawdb.ReadTrieNode(chaindb, targetRoot); len(blob) == 0 { 226 // The special case is for clique based networks(rinkeby, goerli 227 // and some other private networks), it's possible that two 228 // consecutive blocks will have same root. In this case snapshot 229 // difflayer won't be created. So HEAD-(n-1) may not paired with 230 // head-(n-1) layer. Instead the paired layer is higher than the 231 // bottom-most diff layer. Try to find the bottom-most snapshot 232 // layer with state available. 233 // 234 // Note HEAD is ignored. Usually there is the associated 235 // state available, but we don't want to use the topmost state 236 // as the pruning target. 237 var found bool 238 for i := len(layers) - 2; i >= 1; i-- { 239 if blob := rawdb.ReadTrieNode(chaindb, layers[i].Root()); len(blob) != 0 { 240 targetRoot = layers[i].Root() 241 found = true 242 log.Info("Selecting middle-layer as the pruning target", "root", targetRoot, "depth", i) 243 break 244 } 245 } 246 if !found { 247 if blob := rawdb.ReadTrieNode(chaindb, snaptree.DiskRoot()); len(blob) != 0 { 248 targetRoot = snaptree.DiskRoot() 249 found = true 250 log.Info("Selecting disk-layer as the pruning target", "root", targetRoot) 251 } 252 } 253 if !found { 254 if len(layers) > 0 { 255 log.Error("no snapshot paired state") 256 return nil, errors.New("no snapshot paired state") 257 } 258 return nil, fmt.Errorf("associated state[%x] is not present", targetRoot) 259 } 260 } else { 261 if len(layers) > 0 { 262 log.Info("Selecting bottom-most difflayer as the pruning target", "root", targetRoot, "height", headHeader.Number.Uint64()-uint64(len(layers)-1)) 263 } else { 264 log.Info("Selecting user-specified state as the pruning target", "root", targetRoot) 265 } 266 } 267 return chaindb, nil 268 } 269 270 func pruneBlock(ctx *cli.Context) error { 271 stack, config := makeConfigNode(ctx) 272 defer stack.Close() 273 blockAmountReserved := ctx.GlobalUint64(utils.BlockAmountReserved.Name) 274 chaindb, err := accessDb(ctx, stack) 275 if err != nil { 276 return err 277 } 278 var newAncientPath string 279 oldAncientPath := ctx.GlobalString(utils.AncientFlag.Name) 280 if !filepath.IsAbs(oldAncientPath) { 281 oldAncientPath = stack.ResolvePath(oldAncientPath) 282 } 283 284 path, _ := filepath.Split(oldAncientPath) 285 if path == "" { 286 return errors.New("prune failed, did not specify the AncientPath") 287 } 288 newAncientPath = filepath.Join(path, "ancient_back") 289 290 blockpruner := pruner.NewBlockPruner(chaindb, stack, oldAncientPath, newAncientPath, blockAmountReserved) 291 292 lock, exist, err := fileutil.Flock(filepath.Join(oldAncientPath, "PRUNEFLOCK")) 293 if err != nil { 294 log.Error("file lock error", "err", err) 295 return err 296 } 297 if exist { 298 defer lock.Release() 299 log.Info("file lock existed, waiting for prune recovery and continue", "err", err) 300 if err := blockpruner.RecoverInterruption("chaindata", config.Eth.DatabaseCache, utils.MakeDatabaseHandles(), "", false); err != nil { 301 log.Error("Pruning failed", "err", err) 302 return err 303 } 304 log.Info("Block prune successfully") 305 return nil 306 } 307 308 if _, err := os.Stat(newAncientPath); err == nil { 309 // No file lock found for old ancientDB but new ancientDB exsisted, indicating the geth was interrupted 310 // after old ancientDB removal, this happened after backup successfully, so just rename the new ancientDB 311 if err := blockpruner.AncientDbReplacer(); err != nil { 312 log.Error("Failed to rename new ancient directory") 313 return err 314 } 315 log.Info("Block prune successfully") 316 return nil 317 } 318 name := "chaindata" 319 if err := blockpruner.BlockPruneBackUp(name, config.Eth.DatabaseCache, utils.MakeDatabaseHandles(), "", false, false); err != nil { 320 log.Error("Failed to back up block", "err", err) 321 return err 322 } 323 324 log.Info("backup block successfully") 325 326 //After backing up successfully, rename the new ancientdb name to the original one, and delete the old ancientdb 327 if err := blockpruner.AncientDbReplacer(); err != nil { 328 return err 329 } 330 331 lock.Release() 332 log.Info("Block prune successfully") 333 return nil 334 } 335 336 func pruneState(ctx *cli.Context) error { 337 stack, config := makeConfigNode(ctx) 338 defer stack.Close() 339 340 chaindb := utils.MakeChainDatabase(ctx, stack, false, false) 341 pruner, err := pruner.NewPruner(chaindb, stack.ResolvePath(""), stack.ResolvePath(config.Eth.TrieCleanCacheJournal), ctx.GlobalUint64(utils.BloomFilterSizeFlag.Name), ctx.GlobalUint64(utils.TriesInMemoryFlag.Name)) 342 if err != nil { 343 log.Error("Failed to open snapshot tree", "err", err) 344 return err 345 } 346 if ctx.NArg() > 1 { 347 log.Error("Too many arguments given") 348 return errors.New("too many arguments") 349 } 350 var targetRoot common.Hash 351 if ctx.NArg() == 1 { 352 targetRoot, err = parseRoot(ctx.Args()[0]) 353 if err != nil { 354 log.Error("Failed to resolve state root", "err", err) 355 return err 356 } 357 } 358 if err = pruner.Prune(targetRoot); err != nil { 359 log.Error("Failed to prune state", "err", err) 360 return err 361 } 362 return nil 363 } 364 365 func verifyState(ctx *cli.Context) error { 366 stack, _ := makeConfigNode(ctx) 367 defer stack.Close() 368 369 chaindb := utils.MakeChainDatabase(ctx, stack, true, false) 370 headBlock := rawdb.ReadHeadBlock(chaindb) 371 if headBlock == nil { 372 log.Error("Failed to load head block") 373 return errors.New("no head block") 374 } 375 snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, 128, headBlock.Root(), false, false, false) 376 if err != nil { 377 log.Error("Failed to open snapshot tree", "err", err) 378 return err 379 } 380 if ctx.NArg() > 1 { 381 log.Error("Too many arguments given") 382 return errors.New("too many arguments") 383 } 384 var root = headBlock.Root() 385 if ctx.NArg() == 1 { 386 root, err = parseRoot(ctx.Args()[0]) 387 if err != nil { 388 log.Error("Failed to resolve state root", "err", err) 389 return err 390 } 391 } 392 if err := snaptree.Verify(root); err != nil { 393 log.Error("Failed to verfiy state", "root", root, "err", err) 394 return err 395 } 396 log.Info("Verified the state", "root", root) 397 return nil 398 } 399 400 // traverseState is a helper function used for pruning verification. 401 // Basically it just iterates the trie, ensure all nodes and associated 402 // contract codes are present. 403 func traverseState(ctx *cli.Context) error { 404 stack, _ := makeConfigNode(ctx) 405 defer stack.Close() 406 407 chaindb := utils.MakeChainDatabase(ctx, stack, true, false) 408 headBlock := rawdb.ReadHeadBlock(chaindb) 409 if headBlock == nil { 410 log.Error("Failed to load head block") 411 return errors.New("no head block") 412 } 413 if ctx.NArg() > 1 { 414 log.Error("Too many arguments given") 415 return errors.New("too many arguments") 416 } 417 var ( 418 root common.Hash 419 err error 420 ) 421 if ctx.NArg() == 1 { 422 root, err = parseRoot(ctx.Args()[0]) 423 if err != nil { 424 log.Error("Failed to resolve state root", "err", err) 425 return err 426 } 427 log.Info("Start traversing the state", "root", root) 428 } else { 429 root = headBlock.Root() 430 log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) 431 } 432 triedb := trie.NewDatabase(chaindb) 433 t, err := trie.NewSecure(root, triedb) 434 if err != nil { 435 log.Error("Failed to open trie", "root", root, "err", err) 436 return err 437 } 438 var ( 439 accounts int 440 slots int 441 codes int 442 lastReport time.Time 443 start = time.Now() 444 ) 445 accIter := trie.NewIterator(t.NodeIterator(nil)) 446 for accIter.Next() { 447 accounts += 1 448 var acc state.Account 449 if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil { 450 log.Error("Invalid account encountered during traversal", "err", err) 451 return err 452 } 453 if acc.Root != emptyRoot { 454 storageTrie, err := trie.NewSecure(acc.Root, triedb) 455 if err != nil { 456 log.Error("Failed to open storage trie", "root", acc.Root, "err", err) 457 return err 458 } 459 storageIter := trie.NewIterator(storageTrie.NodeIterator(nil)) 460 for storageIter.Next() { 461 slots += 1 462 } 463 if storageIter.Err != nil { 464 log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err) 465 return storageIter.Err 466 } 467 } 468 if !bytes.Equal(acc.CodeHash, emptyCode) { 469 code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash)) 470 if len(code) == 0 { 471 log.Error("Code is missing", "hash", common.BytesToHash(acc.CodeHash)) 472 return errors.New("missing code") 473 } 474 codes += 1 475 } 476 if time.Since(lastReport) > time.Second*8 { 477 log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 478 lastReport = time.Now() 479 } 480 } 481 if accIter.Err != nil { 482 log.Error("Failed to traverse state trie", "root", root, "err", accIter.Err) 483 return accIter.Err 484 } 485 log.Info("State is complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 486 return nil 487 } 488 489 // traverseRawState is a helper function used for pruning verification. 490 // Basically it just iterates the trie, ensure all nodes and associated 491 // contract codes are present. It's basically identical to traverseState 492 // but it will check each trie node. 493 func traverseRawState(ctx *cli.Context) error { 494 stack, _ := makeConfigNode(ctx) 495 defer stack.Close() 496 497 chaindb := utils.MakeChainDatabase(ctx, stack, true, false) 498 headBlock := rawdb.ReadHeadBlock(chaindb) 499 if headBlock == nil { 500 log.Error("Failed to load head block") 501 return errors.New("no head block") 502 } 503 if ctx.NArg() > 1 { 504 log.Error("Too many arguments given") 505 return errors.New("too many arguments") 506 } 507 var ( 508 root common.Hash 509 err error 510 ) 511 if ctx.NArg() == 1 { 512 root, err = parseRoot(ctx.Args()[0]) 513 if err != nil { 514 log.Error("Failed to resolve state root", "err", err) 515 return err 516 } 517 log.Info("Start traversing the state", "root", root) 518 } else { 519 root = headBlock.Root() 520 log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) 521 } 522 triedb := trie.NewDatabase(chaindb) 523 t, err := trie.NewSecure(root, triedb) 524 if err != nil { 525 log.Error("Failed to open trie", "root", root, "err", err) 526 return err 527 } 528 var ( 529 nodes int 530 accounts int 531 slots int 532 codes int 533 lastReport time.Time 534 start = time.Now() 535 ) 536 accIter := t.NodeIterator(nil) 537 for accIter.Next(true) { 538 nodes += 1 539 node := accIter.Hash() 540 541 if node != (common.Hash{}) { 542 // Check the present for non-empty hash node(embedded node doesn't 543 // have their own hash). 544 blob := rawdb.ReadTrieNode(chaindb, node) 545 if len(blob) == 0 { 546 log.Error("Missing trie node(account)", "hash", node) 547 return errors.New("missing account") 548 } 549 } 550 // If it's a leaf node, yes we are touching an account, 551 // dig into the storage trie further. 552 if accIter.Leaf() { 553 accounts += 1 554 var acc state.Account 555 if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil { 556 log.Error("Invalid account encountered during traversal", "err", err) 557 return errors.New("invalid account") 558 } 559 if acc.Root != emptyRoot { 560 storageTrie, err := trie.NewSecure(acc.Root, triedb) 561 if err != nil { 562 log.Error("Failed to open storage trie", "root", acc.Root, "err", err) 563 return errors.New("missing storage trie") 564 } 565 storageIter := storageTrie.NodeIterator(nil) 566 for storageIter.Next(true) { 567 nodes += 1 568 node := storageIter.Hash() 569 570 // Check the present for non-empty hash node(embedded node doesn't 571 // have their own hash). 572 if node != (common.Hash{}) { 573 blob := rawdb.ReadTrieNode(chaindb, node) 574 if len(blob) == 0 { 575 log.Error("Missing trie node(storage)", "hash", node) 576 return errors.New("missing storage") 577 } 578 } 579 // Bump the counter if it's leaf node. 580 if storageIter.Leaf() { 581 slots += 1 582 } 583 } 584 if storageIter.Error() != nil { 585 log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error()) 586 return storageIter.Error() 587 } 588 } 589 if !bytes.Equal(acc.CodeHash, emptyCode) { 590 code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash)) 591 if len(code) == 0 { 592 log.Error("Code is missing", "account", common.BytesToHash(accIter.LeafKey())) 593 return errors.New("missing code") 594 } 595 codes += 1 596 } 597 if time.Since(lastReport) > time.Second*8 { 598 log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 599 lastReport = time.Now() 600 } 601 } 602 } 603 if accIter.Error() != nil { 604 log.Error("Failed to traverse state trie", "root", root, "err", accIter.Error()) 605 return accIter.Error() 606 } 607 log.Info("State is complete", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 608 return nil 609 } 610 611 func parseRoot(input string) (common.Hash, error) { 612 var h common.Hash 613 if err := h.UnmarshalText([]byte(input)); err != nil { 614 return h, err 615 } 616 return h, nil 617 }