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