github.com/klaytn/klaytn@v1.10.2/cmd/utils/nodecmd/snapshot.go (about) 1 // Modifications Copyright 2022 The klaytn Authors 2 // Copyright 2020 The go-ethereum Authors 3 // This file is part of go-ethereum. 4 // 5 // go-ethereum is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // go-ethereum is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from cmd/utils/nodecmd/snapshot.go (2022/07/08). 19 // Modified and improved for the klaytn development. 20 21 package nodecmd 22 23 import ( 24 "bytes" 25 "errors" 26 "fmt" 27 "sync" 28 "time" 29 30 "github.com/klaytn/klaytn/blockchain/state" 31 "github.com/klaytn/klaytn/cmd/utils" 32 "github.com/klaytn/klaytn/common" 33 "github.com/klaytn/klaytn/snapshot" 34 "github.com/klaytn/klaytn/storage/database" 35 "github.com/klaytn/klaytn/storage/statedb" 36 "gopkg.in/urfave/cli.v1" 37 ) 38 39 var SnapshotCommand = cli.Command{ 40 Name: "snapshot", 41 Usage: "A set of commands based on the snapshot", 42 Description: "", 43 Subcommands: []cli.Command{ 44 { 45 Name: "verify-state", 46 Usage: "Recalculate state hash based on the snapshot for verification", 47 ArgsUsage: "<root>", 48 Action: utils.MigrateFlags(verifyState), 49 Flags: []cli.Flag{ 50 utils.DbTypeFlag, 51 utils.SingleDBFlag, 52 utils.NumStateTrieShardsFlag, 53 utils.DynamoDBTableNameFlag, 54 utils.DynamoDBRegionFlag, 55 utils.DynamoDBIsProvisionedFlag, 56 utils.DynamoDBReadCapacityFlag, 57 utils.DynamoDBWriteCapacityFlag, 58 utils.LevelDBCompressionTypeFlag, 59 utils.DataDirFlag, 60 }, 61 Description: ` 62 klay snapshot verify-state <state-root> 63 will traverse the whole accounts and storages set based on the specified 64 snapshot and recalculate the root hash of state for verification. 65 In other words, this command does the snapshot to trie conversion. 66 `, 67 }, 68 { 69 Name: "trace-trie", 70 Usage: "trace all trie nodes for verification", 71 ArgsUsage: "<root>", 72 Action: utils.MigrateFlags(traceTrie), 73 Flags: []cli.Flag{ 74 utils.DbTypeFlag, 75 utils.SingleDBFlag, 76 utils.NumStateTrieShardsFlag, 77 utils.DynamoDBTableNameFlag, 78 utils.DynamoDBRegionFlag, 79 utils.DynamoDBIsProvisionedFlag, 80 utils.DynamoDBReadCapacityFlag, 81 utils.DynamoDBWriteCapacityFlag, 82 utils.LevelDBCompressionTypeFlag, 83 utils.DataDirFlag, 84 }, 85 Description: ` 86 klaytn statedb trace-trie <state-root> 87 trace all account and storage nodes to find missing data 88 during the migration process. 89 Start tracing from the state root of the last block, 90 reading all nodes and logging the missing nodes. 91 `, 92 }, 93 { 94 Name: "iterate-triedb", 95 Usage: "Iterate StateTrie DB for node count", 96 ArgsUsage: "<root>", 97 Action: utils.MigrateFlags(iterateTrie), 98 Flags: []cli.Flag{ 99 utils.DbTypeFlag, 100 utils.SingleDBFlag, 101 utils.NumStateTrieShardsFlag, 102 utils.DynamoDBTableNameFlag, 103 utils.DynamoDBRegionFlag, 104 utils.DynamoDBIsProvisionedFlag, 105 utils.DynamoDBReadCapacityFlag, 106 utils.DynamoDBWriteCapacityFlag, 107 utils.LevelDBCompressionTypeFlag, 108 utils.DataDirFlag, 109 }, 110 Description: ` 111 klaytn statedb iterate-triedb 112 Count the number of nodes in the state-trie db. 113 `, 114 }, 115 }, 116 } 117 118 var ( 119 midAccountCnt = uint64(0) 120 midStorageCnt = uint64(0) 121 codeCnt = uint64(0) 122 leafAccountCnt = uint64(0) 123 leafStorageCnt = uint64(0) 124 unknownCnt = uint64(0) 125 mutex = &sync.Mutex{} 126 ) 127 128 // getConfig returns a database config with the given context. 129 func getConfig(ctx *cli.Context) *database.DBConfig { 130 return &database.DBConfig{ 131 Dir: "chaindata", 132 DBType: database.DBType(ctx.GlobalString(utils.DbTypeFlag.Name)).ToValid(), 133 SingleDB: ctx.GlobalBool(utils.SingleDBFlag.Name), 134 NumStateTrieShards: ctx.GlobalUint(utils.NumStateTrieShardsFlag.Name), 135 OpenFilesLimit: database.GetOpenFilesLimit(), 136 137 LevelDBCacheSize: ctx.GlobalInt(utils.LevelDBCacheSizeFlag.Name), 138 LevelDBCompression: database.LevelDBCompressionType(ctx.GlobalInt(utils.LevelDBCompressionTypeFlag.Name)), 139 EnableDBPerfMetrics: !ctx.IsSet(utils.DBNoPerformanceMetricsFlag.Name), 140 141 DynamoDBConfig: &database.DynamoDBConfig{ 142 TableName: ctx.GlobalString(utils.DynamoDBTableNameFlag.Name), 143 Region: ctx.GlobalString(utils.DynamoDBRegionFlag.Name), 144 IsProvisioned: ctx.GlobalBool(utils.DynamoDBIsProvisionedFlag.Name), 145 ReadCapacityUnits: ctx.GlobalInt64(utils.DynamoDBReadCapacityFlag.Name), 146 WriteCapacityUnits: ctx.GlobalInt64(utils.DynamoDBWriteCapacityFlag.Name), 147 PerfCheck: !ctx.IsSet(utils.DBNoPerformanceMetricsFlag.Name), 148 }, 149 } 150 } 151 152 // parseRoot parse the given hex string to hash. 153 func parseRoot(input string) (common.Hash, error) { 154 var h common.Hash 155 if err := h.UnmarshalText([]byte(input)); err != nil { 156 return h, err 157 } 158 return h, nil 159 } 160 161 // verifyState verifies if the stored snapshot data is correct or not. 162 // if a root hash isn't given, the root hash of current block is investigated. 163 func verifyState(ctx *cli.Context) error { 164 stack := MakeFullNode(ctx) 165 db := stack.OpenDatabase(getConfig(ctx)) 166 head := db.ReadHeadBlockHash() 167 if head == (common.Hash{}) { 168 // Corrupt or empty database, init from scratch 169 return errors.New("empty database") 170 } 171 // Make sure the entire head block is available 172 headBlock := db.ReadBlockByHash(head) 173 if headBlock == nil { 174 return fmt.Errorf("head block missing: %v", head.String()) 175 } 176 177 snaptree, err := snapshot.New(db, statedb.NewDatabase(db), 256, headBlock.Root(), false, false, false) 178 if err != nil { 179 logger.Error("Failed to open snapshot tree", "err", err) 180 return err 181 } 182 if ctx.NArg() > 1 { 183 logger.Error("Too many arguments given") 184 return errors.New("too many arguments") 185 } 186 root := headBlock.Root() 187 if ctx.NArg() == 1 { 188 root, err = parseRoot(ctx.Args().First()) 189 if err != nil { 190 logger.Error("Failed to resolve state root", "err", err) 191 return err 192 } 193 } 194 if err := snaptree.Verify(root); err != nil { 195 logger.Error("Failed to verify state", "root", root, "err", err) 196 return err 197 } 198 logger.Info("Verified the state", "root", root) 199 return nil 200 } 201 202 func traceTrie(ctx *cli.Context) error { 203 var childWait, logWait sync.WaitGroup 204 205 stack := MakeFullNode(ctx) 206 dbm := stack.OpenDatabase(getConfig(ctx)) 207 head := dbm.ReadHeadBlockHash() 208 if head == (common.Hash{}) { 209 // Corrupt or empty database, init from scratch 210 return errors.New("empty database") 211 } 212 // Make sure the entire head block is available 213 tmpHeadBlock := dbm.ReadBlockByHash(head) 214 if tmpHeadBlock == nil { 215 return fmt.Errorf("tmp head block missing: %v", head.String()) 216 } 217 218 blockNumber := (tmpHeadBlock.NumberU64() / 128) * 128 219 headBlock := dbm.ReadBlockByNumber(blockNumber) 220 if headBlock == nil { 221 return fmt.Errorf("head block missing: %v", head.String()) 222 } 223 224 root := headBlock.Root() 225 if root == (common.Hash{}) { 226 // Corrupt or empty database, init from scratch 227 return errors.New("empty root") 228 } 229 230 logger.Info("Trace Start", "BlockNum", blockNumber) 231 232 sdb, err := state.New(root, state.NewDatabase(dbm), nil) 233 if err != nil { 234 return fmt.Errorf("Failed to open newDB trie : %v", err) 235 } 236 trieDB := sdb.Database().TrieDB() 237 238 // Get root-node childrens to create goroutine by number of childrens 239 children, err := trieDB.NodeChildren(root) 240 if err != nil { 241 return fmt.Errorf("Fail get childrens of root : %v", err) 242 } 243 244 midAccountCnt, midStorageCnt, codeCnt, leafAccountCnt, leafStorageCnt, unknownCnt = 0, 0, 0, 0, 0, 0 245 endFlag := false 246 247 childWait.Add(len(children)) 248 logWait.Add(1) 249 // create logging goroutine 250 go func() { 251 defer logWait.Done() 252 for !endFlag { 253 time.Sleep(time.Second * 5) 254 logger.Info("Trie Tracer", "AccNode", midAccountCnt, "AccLeaf", leafAccountCnt, "StrgNode", midStorageCnt, "StrgLeaf", leafStorageCnt, "Unknown", unknownCnt, "CodeAcc", codeCnt) 255 } 256 logger.Info("Trie Tracer Finished", "AccNode", midAccountCnt, "AccLeaf", leafAccountCnt, "StrgNode", midStorageCnt, "StrgLeaf", leafStorageCnt, "Unknown", unknownCnt, "CodeAcc", codeCnt) 257 }() 258 259 // Create goroutine by number of childrens 260 for _, child := range children { 261 go func(child common.Hash) { 262 defer childWait.Done() 263 doTraceTrie(sdb.Database(), child) 264 }(child) 265 } 266 267 childWait.Wait() 268 endFlag = true 269 logWait.Wait() 270 return nil 271 } 272 273 func doTraceTrie(db state.Database, root common.Hash) (resultErr error) { 274 logger.Info("Trie Tracer Start", "Hash Root", root) 275 // Create and iterate a state trie rooted in a sub-node 276 oldState, err := state.New(root, db, nil) 277 if err != nil { 278 logger.Error("can not open trie DB", err.Error()) 279 panic(err) 280 } 281 282 oldIt := state.NewNodeIterator(oldState) 283 284 for oldIt.Next() { 285 mutex.Lock() 286 switch oldIt.Type { 287 case "state": 288 midAccountCnt++ 289 case "storage": 290 midStorageCnt++ 291 case "code": 292 codeCnt++ 293 case "state_leaf": 294 leafAccountCnt++ 295 case "storage_leaf": 296 leafStorageCnt++ 297 default: 298 unknownCnt++ 299 } 300 mutex.Unlock() 301 } 302 if oldIt.Error != nil { 303 logger.Error("Error Finished", "Root Hash", root, "Message", oldIt.Error) 304 } 305 logger.Info("Trie Tracer Finished", "Root Hash", root, "AccNode", midAccountCnt, "AccLeaf", leafAccountCnt, "StrgNode", midStorageCnt, "StrgLeaf", leafStorageCnt, "Unknown", unknownCnt, "CodeAcc", codeCnt) 306 return nil 307 } 308 309 func iterateTrie(ctx *cli.Context) error { 310 stack := MakeFullNode(ctx) 311 dbm := stack.OpenDatabase(getConfig(ctx)) 312 sdb, err := state.New(common.Hash{}, state.NewDatabase(dbm), nil) 313 if err != nil { 314 return fmt.Errorf("Failed to open newDB trie : %v", err) 315 } 316 317 logger.Info("TrieDB Iterator Start", "node count : all node count, nil node count : key or value is nil node count") 318 cnt, nilCnt := uint64(0), uint64(0) 319 go func() { 320 for { 321 time.Sleep(time.Second * 5) 322 logger.Info("TrieDB Iterator", "node count", cnt, "nil node count", nilCnt) 323 } 324 }() 325 326 it := sdb.Database().TrieDB().DiskDB().GetStateTrieDB().NewIterator(nil, nil) 327 defer it.Release() 328 for it.Next() { 329 cnt++ 330 if it.Key() == nil || it.Value() == nil || bytes.Equal(it.Key(), []byte("")) || bytes.Equal(it.Value(), []byte("")) { 331 nilCnt++ 332 } 333 } 334 logger.Info("TrieDB Iterator finished", "total node count", cnt, "nil node count", nilCnt) 335 return nil 336 }