github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/cmd/u2u/launcher/snapshotcmd.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 launcher 18 19 import ( 20 "bytes" 21 "errors" 22 "os" 23 "path" 24 "time" 25 26 "github.com/unicornultrafoundation/go-u2u/cmd/utils" 27 "github.com/unicornultrafoundation/go-u2u/common" 28 "github.com/unicornultrafoundation/go-u2u/core/rawdb" 29 "github.com/unicornultrafoundation/go-u2u/core/state" 30 "github.com/unicornultrafoundation/go-u2u/core/types" 31 "github.com/unicornultrafoundation/go-u2u/log" 32 "github.com/unicornultrafoundation/go-u2u/rlp" 33 "github.com/unicornultrafoundation/go-u2u/trie" 34 "gopkg.in/urfave/cli.v1" 35 36 "github.com/unicornultrafoundation/go-u2u/gossip/evmstore" 37 "github.com/unicornultrafoundation/go-u2u/gossip/evmstore/evmpruner" 38 ) 39 40 var ( 41 PruneExactCommand = cli.BoolFlag{ 42 Name: "prune.exact", 43 Usage: `full pruning without usage of bloom filter (false by default)`, 44 } 45 PruneGenesisCommand = cli.BoolTFlag{ 46 Name: "prune.genesis", 47 Usage: `prune genesis state (true by default)`, 48 } 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 EVM state data based on the snapshot", 58 ArgsUsage: "<root> [--prune.exact] [--prune.genesis=false]", 59 Action: utils.MigrateFlags(pruneState), 60 Category: "MISCELLANEOUS COMMANDS", 61 Flags: []cli.Flag{ 62 PruneExactCommand, 63 PruneGenesisCommand, 64 DataDirFlag, 65 utils.AncientFlag, 66 utils.RopstenFlag, 67 utils.RinkebyFlag, 68 utils.GoerliFlag, 69 utils.CacheTrieJournalFlag, 70 utils.BloomFilterSizeFlag, 71 }, 72 Description: ` 73 geth snapshot prune-state <state-root> 74 will prune historical state data with the help of the state snapshot. 75 All trie nodes and contract codes that do not belong to the specified 76 version state will be deleted from the database. After pruning, only 77 two version states are available: genesis and the specific one. 78 79 The default pruning target is the HEAD state. 80 81 WARNING: It's necessary to delete the trie clean cache after the pruning. 82 If you specify another directory for the trie clean cache via "--cache.trie.journal" 83 during the use of Geth, please also specify it here for correct deletion. Otherwise 84 the trie clean cache with default directory will be deleted. 85 `, 86 }, 87 { 88 Name: "verify-state", 89 Usage: "Recalculate state hash based on the snapshot for verification", 90 ArgsUsage: "<root>", 91 Action: utils.MigrateFlags(verifyState), 92 Category: "MISCELLANEOUS COMMANDS", 93 Flags: []cli.Flag{ 94 DataDirFlag, 95 utils.AncientFlag, 96 utils.RopstenFlag, 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 EVM state with given root hash for verification", 110 ArgsUsage: "<root>", 111 Action: utils.MigrateFlags(traverseState), 112 Category: "MISCELLANEOUS COMMANDS", 113 Flags: []cli.Flag{ 114 DataDirFlag, 115 utils.AncientFlag, 116 utils.RopstenFlag, 117 utils.RinkebyFlag, 118 utils.GoerliFlag, 119 }, 120 Description: ` 121 geth snapshot traverse-state <state-root> 122 will traverse the whole state from the given state root and will abort if any 123 referenced trie node or contract code is missing. This command can be used for 124 state integrity verification. The default checking target is the HEAD state. 125 126 It's also usable without snapshot enabled. 127 `, 128 }, 129 { 130 Name: "traverse-rawstate", 131 Usage: "Traverse the EVM state with given root hash for verification", 132 ArgsUsage: "<root>", 133 Action: utils.MigrateFlags(traverseRawState), 134 Category: "MISCELLANEOUS COMMANDS", 135 Flags: []cli.Flag{ 136 DataDirFlag, 137 utils.AncientFlag, 138 utils.RopstenFlag, 139 utils.RinkebyFlag, 140 utils.GoerliFlag, 141 }, 142 Description: ` 143 geth snapshot traverse-rawstate <state-root> 144 will traverse the whole state from the given root and will abort if any referenced 145 trie node or contract code is missing. This command can be used for state integrity 146 verification. The default checking target is the HEAD state. It's basically identical 147 to traverse-state, but the check granularity is smaller. 148 149 It's also usable without snapshot enabled. 150 `, 151 }, 152 }, 153 } 154 ) 155 156 func pruneState(ctx *cli.Context) error { 157 cfg := makeAllConfigs(ctx) 158 rawDbs := makeDirectDBsProducer(cfg) 159 gdb := makeGossipStore(rawDbs, cfg) 160 161 if gdb.GetGenesisID() == nil { 162 return errors.New("failed to open snapshot tree: genesis is not written") 163 } 164 165 tmpDir := path.Join(cfg.Node.DataDir, "tmp") 166 _ = os.MkdirAll(tmpDir, 0700) 167 defer os.RemoveAll(tmpDir) 168 169 genesisBlock := gdb.GetBlock(*gdb.GetGenesisBlockIndex()) 170 genesisRoot := common.Hash{} 171 if !ctx.BoolT(PruneGenesisCommand.Name) && genesisBlock != nil { 172 if gdb.EvmStore().HasStateDB(genesisBlock.Root) { 173 genesisRoot = common.Hash(genesisBlock.Root) 174 log.Info("Excluding genesis state from pruning", "root", genesisRoot) 175 } 176 } 177 root := common.Hash(gdb.GetBlockState().FinalizedStateRoot) 178 var bloom evmpruner.StateBloom 179 var err error 180 if ctx.Bool(PruneExactCommand.Name) { 181 log.Info("Initializing LevelDB storage of in-use-keys") 182 lset, closer, err := evmpruner.NewLevelDBSet(path.Join(tmpDir, "keys-in-use")) 183 if err != nil { 184 log.Error("Failed to create state bloom", "err", err) 185 return err 186 } 187 bloom = lset 188 defer closer.Close() 189 } else { 190 size := ctx.Uint64(utils.BloomFilterSizeFlag.Name) 191 log.Info("Initializing bloom filter of in-use-keys", "size (MB)", size) 192 bloom, err = evmpruner.NewProbabilisticSet(size) 193 if err != nil { 194 log.Error("Failed to create state bloom", "err", err) 195 return err 196 } 197 } 198 pruner, err := evmpruner.NewPruner(gdb.EvmStore().EvmDb, genesisRoot, root, tmpDir, bloom) 199 if err != nil { 200 log.Error("Failed to open snapshot tree", "err", err) 201 return err 202 } 203 if ctx.NArg() > 1 { 204 log.Error("Too many arguments given") 205 return errors.New("too many arguments") 206 } 207 var targetRoot common.Hash 208 if ctx.NArg() == 1 { 209 targetRoot, err = parseRoot(ctx.Args()[0]) 210 if err != nil { 211 log.Error("Failed to resolve state root", "err", err) 212 return err 213 } 214 } 215 if err = pruner.Prune(targetRoot); err != nil { 216 log.Error("Failed to prune state", "err", err) 217 return err 218 } 219 return nil 220 } 221 222 func verifyState(ctx *cli.Context) error { 223 cfg := makeAllConfigs(ctx) 224 rawDbs := makeDirectDBsProducer(cfg) 225 gdb := makeGossipStore(rawDbs, cfg) 226 227 genesis := gdb.GetGenesisID() 228 if genesis == nil { 229 return errors.New("failed to open snapshot tree: genesis is not written") 230 } 231 232 evmStore := gdb.EvmStore() 233 root := common.Hash(gdb.GetBlockState().FinalizedStateRoot) 234 235 err := evmStore.GenerateEvmSnapshot(root, false, false) 236 if err != nil { 237 log.Error("Failed to open snapshot tree", "err", err) 238 return err 239 } 240 if ctx.NArg() > 1 { 241 log.Error("Too many arguments given") 242 return errors.New("too many arguments") 243 } 244 245 if ctx.NArg() == 1 { 246 root, err = parseRoot(ctx.Args()[0]) 247 if err != nil { 248 log.Error("Failed to resolve state root", "err", err) 249 return err 250 } 251 } 252 if err := evmStore.Snapshots().Verify(root); err != nil { 253 log.Error("Failed to verfiy state", "root", root, "err", err) 254 return err 255 } 256 log.Info("Verified the state", "root", root) 257 return nil 258 } 259 260 // traverseState is a helper function used for pruning verification. 261 // Basically it just iterates the trie, ensure all nodes and associated 262 // contract codes are present. 263 func traverseState(ctx *cli.Context) error { 264 cfg := makeAllConfigs(ctx) 265 rawDbs := makeDirectDBsProducer(cfg) 266 gdb := makeGossipStore(rawDbs, cfg) 267 268 if gdb.GetGenesisID() == nil { 269 return errors.New("failed to open snapshot tree: genesis is not written") 270 } 271 chaindb := gdb.EvmStore().EvmDb 272 273 if ctx.NArg() > 1 { 274 log.Error("Too many arguments given") 275 return errors.New("too many arguments") 276 } 277 var ( 278 root common.Hash 279 err error 280 ) 281 if ctx.NArg() == 1 { 282 root, err = parseRoot(ctx.Args()[0]) 283 if err != nil { 284 log.Error("Failed to resolve state root", "err", err) 285 return err 286 } 287 log.Info("Start traversing the state", "root", root) 288 } else { 289 root = common.Hash(gdb.GetBlockState().FinalizedStateRoot) 290 log.Info("Start traversing the state", "root", root, "number", gdb.GetBlockState().LastBlock.Idx) 291 } 292 triedb := trie.NewDatabase(chaindb) 293 t, err := trie.NewSecure(root, triedb) 294 if err != nil { 295 log.Error("Failed to open trie", "root", root, "err", err) 296 return err 297 } 298 var ( 299 accounts int 300 slots int 301 codes int 302 lastReport time.Time 303 start = time.Now() 304 ) 305 accIter := trie.NewIterator(t.NodeIterator(nil)) 306 for accIter.Next() { 307 accounts += 1 308 var acc state.Account 309 if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil { 310 log.Error("Invalid account encountered during traversal", "err", err) 311 return err 312 } 313 if acc.Root != types.EmptyRootHash { 314 storageTrie, err := trie.NewSecure(acc.Root, triedb) 315 if err != nil { 316 log.Error("Failed to open storage trie", "root", acc.Root, "err", err) 317 return err 318 } 319 storageIter := trie.NewIterator(storageTrie.NodeIterator(nil)) 320 for storageIter.Next() { 321 slots += 1 322 } 323 if storageIter.Err != nil { 324 log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err) 325 return storageIter.Err 326 } 327 } 328 if !bytes.Equal(acc.CodeHash, evmstore.EmptyCode) { 329 code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash)) 330 if len(code) == 0 { 331 log.Error("Code is missing", "hash", common.BytesToHash(acc.CodeHash)) 332 return errors.New("missing code") 333 } 334 codes += 1 335 } 336 if time.Since(lastReport) > time.Second*8 { 337 log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 338 lastReport = time.Now() 339 } 340 } 341 if accIter.Err != nil { 342 log.Error("Failed to traverse state trie", "root", root, "err", accIter.Err) 343 return accIter.Err 344 } 345 log.Info("State is complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 346 return nil 347 } 348 349 // traverseRawState is a helper function used for pruning verification. 350 // Basically it just iterates the trie, ensure all nodes and associated 351 // contract codes are present. It's basically identical to traverseState 352 // but it will check each trie node. 353 func traverseRawState(ctx *cli.Context) error { 354 cfg := makeAllConfigs(ctx) 355 rawDbs := makeDirectDBsProducer(cfg) 356 gdb := makeGossipStore(rawDbs, cfg) 357 358 if gdb.GetGenesisID() == nil { 359 return errors.New("failed to open snapshot tree: genesis is not written") 360 } 361 chaindb := gdb.EvmStore().EvmDb 362 363 if ctx.NArg() > 1 { 364 log.Error("Too many arguments given") 365 return errors.New("too many arguments") 366 } 367 var ( 368 root common.Hash 369 err error 370 ) 371 if ctx.NArg() == 1 { 372 root, err = parseRoot(ctx.Args()[0]) 373 if err != nil { 374 log.Error("Failed to resolve state root", "err", err) 375 return err 376 } 377 log.Info("Start traversing the state", "root", root) 378 } else { 379 root = common.Hash(gdb.GetBlockState().FinalizedStateRoot) 380 log.Info("Start traversing the state", "root", root, "number", gdb.GetBlockState().LastBlock.Idx) 381 } 382 triedb := trie.NewDatabase(chaindb) 383 t, err := trie.NewSecure(root, triedb) 384 if err != nil { 385 log.Error("Failed to open trie", "root", root, "err", err) 386 return err 387 } 388 var ( 389 nodes int 390 accounts int 391 slots int 392 codes int 393 lastReport time.Time 394 start = time.Now() 395 ) 396 accIter := t.NodeIterator(nil) 397 for accIter.Next(true) { 398 nodes += 1 399 node := accIter.Hash() 400 401 if node != (common.Hash{}) { 402 // Check the present for non-empty hash node(embedded node doesn't 403 // have their own hash). 404 blob := rawdb.ReadTrieNode(chaindb, node) 405 if len(blob) == 0 { 406 log.Error("Missing trie node(account)", "hash", node) 407 return errors.New("missing account") 408 } 409 } 410 // If it's a leaf node, yes we are touching an account, 411 // dig into the storage trie further. 412 if accIter.Leaf() { 413 accounts += 1 414 var acc state.Account 415 if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil { 416 log.Error("Invalid account encountered during traversal", "err", err) 417 return errors.New("invalid account") 418 } 419 if acc.Root != types.EmptyRootHash { 420 storageTrie, err := trie.NewSecure(acc.Root, 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 present for non-empty hash node(embedded node doesn't 431 // have their own hash). 432 if node != (common.Hash{}) { 433 blob := rawdb.ReadTrieNode(chaindb, node) 434 if len(blob) == 0 { 435 log.Error("Missing trie node(storage)", "hash", node) 436 return errors.New("missing storage") 437 } 438 } 439 // Bump the counter if it's leaf node. 440 if storageIter.Leaf() { 441 slots += 1 442 } 443 } 444 if storageIter.Error() != nil { 445 log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error()) 446 return storageIter.Error() 447 } 448 } 449 if !bytes.Equal(acc.CodeHash, evmstore.EmptyCode) { 450 code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash)) 451 if len(code) == 0 { 452 log.Error("Code is missing", "account", common.BytesToHash(accIter.LeafKey())) 453 return errors.New("missing code") 454 } 455 codes += 1 456 } 457 if time.Since(lastReport) > time.Second*8 { 458 log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 459 lastReport = time.Now() 460 } 461 } 462 } 463 if accIter.Error() != nil { 464 log.Error("Failed to traverse state trie", "root", root, "err", accIter.Error()) 465 return accIter.Error() 466 } 467 log.Info("State is complete", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) 468 return nil 469 } 470 471 func parseRoot(input string) (common.Hash, error) { 472 var h common.Hash 473 if err := h.UnmarshalText([]byte(input)); err != nil { 474 return h, err 475 } 476 return h, nil 477 }