github.com/phillinzzz/newBsc@v1.1.6/core/state/pruner/pruner.go (about) 1 // Copyright 2020 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser 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 // The go-ethereum library 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 Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package pruner 18 19 import ( 20 "bytes" 21 "encoding/binary" 22 "errors" 23 "fmt" 24 "math" 25 "os" 26 "path/filepath" 27 "strings" 28 "time" 29 30 "github.com/phillinzzz/newBsc/common" 31 "github.com/phillinzzz/newBsc/core/rawdb" 32 "github.com/phillinzzz/newBsc/core/state" 33 "github.com/phillinzzz/newBsc/core/state/snapshot" 34 "github.com/phillinzzz/newBsc/core/types" 35 "github.com/phillinzzz/newBsc/crypto" 36 "github.com/phillinzzz/newBsc/ethdb" 37 "github.com/phillinzzz/newBsc/log" 38 "github.com/phillinzzz/newBsc/rlp" 39 "github.com/phillinzzz/newBsc/trie" 40 ) 41 42 const ( 43 // stateBloomFilePrefix is the filename prefix of state bloom filter. 44 stateBloomFilePrefix = "statebloom" 45 46 // stateBloomFilePrefix is the filename suffix of state bloom filter. 47 stateBloomFileSuffix = "bf.gz" 48 49 // stateBloomFileTempSuffix is the filename suffix of state bloom filter 50 // while it is being written out to detect write aborts. 51 stateBloomFileTempSuffix = ".tmp" 52 53 // rangeCompactionThreshold is the minimal deleted entry number for 54 // triggering range compaction. It's a quite arbitrary number but just 55 // to avoid triggering range compaction because of small deletion. 56 rangeCompactionThreshold = 100000 57 ) 58 59 var ( 60 // emptyRoot is the known root hash of an empty trie. 61 emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") 62 63 // emptyCode is the known hash of the empty EVM bytecode. 64 emptyCode = crypto.Keccak256(nil) 65 ) 66 67 // Pruner is an offline tool to prune the stale state with the 68 // help of the snapshot. The workflow of pruner is very simple: 69 // 70 // - iterate the snapshot, reconstruct the relevant state 71 // - iterate the database, delete all other state entries which 72 // don't belong to the target state and the genesis state 73 // 74 // It can take several hours(around 2 hours for mainnet) to finish 75 // the whole pruning work. It's recommended to run this offline tool 76 // periodically in order to release the disk usage and improve the 77 // disk read performance to some extent. 78 type Pruner struct { 79 db ethdb.Database 80 stateBloom *stateBloom 81 datadir string 82 trieCachePath string 83 headHeader *types.Header 84 snaptree *snapshot.Tree 85 triesInMemory uint64 86 } 87 88 // NewPruner creates the pruner instance. 89 func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize, triesInMemory uint64) (*Pruner, error) { 90 headBlock := rawdb.ReadHeadBlock(db) 91 if headBlock == nil { 92 return nil, errors.New("Failed to load head block") 93 } 94 snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, int(triesInMemory), headBlock.Root(), false, false, false) 95 if err != nil { 96 return nil, err // The relevant snapshot(s) might not exist 97 } 98 // Sanitize the bloom filter size if it's too small. 99 if bloomSize < 256 { 100 log.Warn("Sanitizing bloomfilter size", "provided(MB)", bloomSize, "updated(MB)", 256) 101 bloomSize = 256 102 } 103 stateBloom, err := newStateBloomWithSize(bloomSize) 104 if err != nil { 105 return nil, err 106 } 107 return &Pruner{ 108 db: db, 109 stateBloom: stateBloom, 110 datadir: datadir, 111 trieCachePath: trieCachePath, 112 triesInMemory: triesInMemory, 113 headHeader: headBlock.Header(), 114 snaptree: snaptree, 115 }, nil 116 } 117 118 func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, stateBloom *stateBloom, bloomPath string, middleStateRoots map[common.Hash]struct{}, start time.Time) error { 119 // Delete all stale trie nodes in the disk. With the help of state bloom 120 // the trie nodes(and codes) belong to the active state will be filtered 121 // out. A very small part of stale tries will also be filtered because of 122 // the false-positive rate of bloom filter. But the assumption is held here 123 // that the false-positive is low enough(~0.05%). The probablity of the 124 // dangling node is the state root is super low. So the dangling nodes in 125 // theory will never ever be visited again. 126 var ( 127 count int 128 size common.StorageSize 129 pstart = time.Now() 130 logged = time.Now() 131 batch = maindb.NewBatch() 132 iter = maindb.NewIterator(nil, nil) 133 ) 134 for iter.Next() { 135 key := iter.Key() 136 137 // All state entries don't belong to specific state and genesis are deleted here 138 // - trie node 139 // - legacy contract code 140 // - new-scheme contract code 141 isCode, codeKey := rawdb.IsCodeKey(key) 142 if len(key) == common.HashLength || isCode { 143 checkKey := key 144 if isCode { 145 checkKey = codeKey 146 } 147 if _, exist := middleStateRoots[common.BytesToHash(checkKey)]; exist { 148 log.Debug("Forcibly delete the middle state roots", "hash", common.BytesToHash(checkKey)) 149 } else { 150 if ok, err := stateBloom.Contain(checkKey); err != nil { 151 return err 152 } else if ok { 153 continue 154 } 155 } 156 count += 1 157 size += common.StorageSize(len(key) + len(iter.Value())) 158 batch.Delete(key) 159 160 var eta time.Duration // Realistically will never remain uninited 161 if done := binary.BigEndian.Uint64(key[:8]); done > 0 { 162 var ( 163 left = math.MaxUint64 - binary.BigEndian.Uint64(key[:8]) 164 speed = done/uint64(time.Since(pstart)/time.Millisecond+1) + 1 // +1s to avoid division by zero 165 ) 166 eta = time.Duration(left/speed) * time.Millisecond 167 } 168 if time.Since(logged) > 8*time.Second { 169 log.Info("Pruning state data", "nodes", count, "size", size, 170 "elapsed", common.PrettyDuration(time.Since(pstart)), "eta", common.PrettyDuration(eta)) 171 logged = time.Now() 172 } 173 // Recreate the iterator after every batch commit in order 174 // to allow the underlying compactor to delete the entries. 175 if batch.ValueSize() >= ethdb.IdealBatchSize { 176 batch.Write() 177 batch.Reset() 178 179 iter.Release() 180 iter = maindb.NewIterator(nil, key) 181 } 182 } 183 } 184 if batch.ValueSize() > 0 { 185 batch.Write() 186 batch.Reset() 187 } 188 iter.Release() 189 log.Info("Pruned state data", "nodes", count, "size", size, "elapsed", common.PrettyDuration(time.Since(pstart))) 190 191 // Pruning is done, now drop the "useless" layers from the snapshot. 192 // Firstly, flushing the target layer into the disk. After that all 193 // diff layers below the target will all be merged into the disk. 194 if root != snaptree.DiskRoot() { 195 if err := snaptree.Cap(root, 0); err != nil { 196 return err 197 } 198 } 199 // Secondly, flushing the snapshot journal into the disk. All diff 200 // layers upon are dropped silently. Eventually the entire snapshot 201 // tree is converted into a single disk layer with the pruning target 202 // as the root. 203 if _, err := snaptree.Journal(root); err != nil { 204 return err 205 } 206 // Delete the state bloom, it marks the entire pruning procedure is 207 // finished. If any crashes or manual exit happens before this, 208 // `RecoverPruning` will pick it up in the next restarts to redo all 209 // the things. 210 os.RemoveAll(bloomPath) 211 212 // Start compactions, will remove the deleted data from the disk immediately. 213 // Note for small pruning, the compaction is skipped. 214 if count >= rangeCompactionThreshold { 215 cstart := time.Now() 216 for b := 0x00; b <= 0xf0; b += 0x10 { 217 var ( 218 start = []byte{byte(b)} 219 end = []byte{byte(b + 0x10)} 220 ) 221 if b == 0xf0 { 222 end = nil 223 } 224 log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", start, end), "elapsed", common.PrettyDuration(time.Since(cstart))) 225 if err := maindb.Compact(start, end); err != nil { 226 log.Error("Database compaction failed", "error", err) 227 return err 228 } 229 } 230 log.Info("Database compaction finished", "elapsed", common.PrettyDuration(time.Since(cstart))) 231 } 232 log.Info("State pruning successful", "pruned", size, "elapsed", common.PrettyDuration(time.Since(start))) 233 return nil 234 } 235 236 // Prune deletes all historical state nodes except the nodes belong to the 237 // specified state version. If user doesn't specify the state version, use 238 // the bottom-most snapshot diff layer as the target. 239 func (p *Pruner) Prune(root common.Hash) error { 240 // If the state bloom filter is already committed previously, 241 // reuse it for pruning instead of generating a new one. It's 242 // mandatory because a part of state may already be deleted, 243 // the recovery procedure is necessary. 244 _, stateBloomRoot, err := findBloomFilter(p.datadir) 245 if err != nil { 246 return err 247 } 248 if stateBloomRoot != (common.Hash{}) { 249 return RecoverPruning(p.datadir, p.db, p.trieCachePath, p.triesInMemory) 250 } 251 // If the target state root is not specified, use the HEAD-(n-1) as the 252 // target. The reason for picking it is: 253 // - in most of the normal cases, the related state is available 254 // - the probability of this layer being reorg is very low 255 var layers []snapshot.Snapshot 256 if root == (common.Hash{}) { 257 // Retrieve all snapshot layers from the current HEAD. 258 // In theory there are n difflayers + 1 disk layer present, 259 // so n diff layers are expected to be returned. 260 layers = p.snaptree.Snapshots(p.headHeader.Root, int(p.triesInMemory), true) 261 if len(layers) != int(p.triesInMemory) { 262 // Reject if the accumulated diff layers are less than n. It 263 // means in most of normal cases, there is no associated state 264 // with bottom-most diff layer. 265 return fmt.Errorf("snapshot not old enough yet: need %d more blocks", int(p.triesInMemory)-len(layers)) 266 } 267 // Use the bottom-most diff layer as the target 268 root = layers[len(layers)-1].Root() 269 } 270 // Ensure the root is really present. The weak assumption 271 // is the presence of root can indicate the presence of the 272 // entire trie. 273 if blob := rawdb.ReadTrieNode(p.db, root); len(blob) == 0 { 274 // The special case is for clique based networks(rinkeby, goerli 275 // and some other private networks), it's possible that two 276 // consecutive blocks will have same root. In this case snapshot 277 // difflayer won't be created. So HEAD-(n-1) may not paired with 278 // head-(n-1) layer. Instead the paired layer is higher than the 279 // bottom-most diff layer. Try to find the bottom-most snapshot 280 // layer with state available. 281 // 282 // Note HEAD is ignored. Usually there is the associated 283 // state available, but we don't want to use the topmost state 284 // as the pruning target. 285 var found bool 286 for i := len(layers) - 2; i >= 1; i-- { 287 if blob := rawdb.ReadTrieNode(p.db, layers[i].Root()); len(blob) != 0 { 288 root = layers[i].Root() 289 found = true 290 log.Info("Selecting middle-layer as the pruning target", "root", root, "depth", i) 291 break 292 } 293 } 294 if !found { 295 if blob := rawdb.ReadTrieNode(p.db, p.snaptree.DiskRoot()); len(blob) != 0 { 296 root = p.snaptree.DiskRoot() 297 found = true 298 log.Info("Selecting disk-layer as the pruning target", "root", root) 299 } 300 } 301 if !found { 302 if len(layers) > 0 { 303 return errors.New("no snapshot paired state") 304 } 305 return fmt.Errorf("associated state[%x] is not present", root) 306 } 307 } else { 308 if len(layers) > 0 { 309 log.Info("Selecting bottom-most difflayer as the pruning target", "root", root, "height", p.headHeader.Number.Uint64()-127) 310 } else { 311 log.Info("Selecting user-specified state as the pruning target", "root", root) 312 } 313 } 314 // Before start the pruning, delete the clean trie cache first. 315 // It's necessary otherwise in the next restart we will hit the 316 // deleted state root in the "clean cache" so that the incomplete 317 // state is picked for usage. 318 deleteCleanTrieCache(p.trieCachePath) 319 320 // All the state roots of the middle layer should be forcibly pruned, 321 // otherwise the dangling state will be left. 322 middleRoots := make(map[common.Hash]struct{}) 323 for _, layer := range layers { 324 if layer.Root() == root { 325 break 326 } 327 middleRoots[layer.Root()] = struct{}{} 328 } 329 // Traverse the target state, re-construct the whole state trie and 330 // commit to the given bloom filter. 331 start := time.Now() 332 if err := snapshot.GenerateTrie(p.snaptree, root, p.db, p.stateBloom); err != nil { 333 return err 334 } 335 // Traverse the genesis, put all genesis state entries into the 336 // bloom filter too. 337 if err := extractGenesis(p.db, p.stateBloom); err != nil { 338 return err 339 } 340 filterName := bloomFilterName(p.datadir, root) 341 342 log.Info("Writing state bloom to disk", "name", filterName) 343 if err := p.stateBloom.Commit(filterName, filterName+stateBloomFileTempSuffix); err != nil { 344 return err 345 } 346 log.Info("State bloom filter committed", "name", filterName) 347 return prune(p.snaptree, root, p.db, p.stateBloom, filterName, middleRoots, start) 348 } 349 350 // RecoverPruning will resume the pruning procedure during the system restart. 351 // This function is used in this case: user tries to prune state data, but the 352 // system was interrupted midway because of crash or manual-kill. In this case 353 // if the bloom filter for filtering active state is already constructed, the 354 // pruning can be resumed. What's more if the bloom filter is constructed, the 355 // pruning **has to be resumed**. Otherwise a lot of dangling nodes may be left 356 // in the disk. 357 func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string, triesInMemory uint64) error { 358 stateBloomPath, stateBloomRoot, err := findBloomFilter(datadir) 359 if err != nil { 360 return err 361 } 362 if stateBloomPath == "" { 363 return nil // nothing to recover 364 } 365 headBlock := rawdb.ReadHeadBlock(db) 366 if headBlock == nil { 367 return errors.New("Failed to load head block") 368 } 369 // Initialize the snapshot tree in recovery mode to handle this special case: 370 // - Users run the `prune-state` command multiple times 371 // - Neither these `prune-state` running is finished(e.g. interrupted manually) 372 // - The state bloom filter is already generated, a part of state is deleted, 373 // so that resuming the pruning here is mandatory 374 // - The state HEAD is rewound already because of multiple incomplete `prune-state` 375 // In this case, even the state HEAD is not exactly matched with snapshot, it 376 // still feasible to recover the pruning correctly. 377 snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, int(triesInMemory), headBlock.Root(), false, false, true) 378 if err != nil { 379 return err // The relevant snapshot(s) might not exist 380 } 381 stateBloom, err := NewStateBloomFromDisk(stateBloomPath) 382 if err != nil { 383 return err 384 } 385 log.Info("Loaded state bloom filter", "path", stateBloomPath) 386 387 // Before start the pruning, delete the clean trie cache first. 388 // It's necessary otherwise in the next restart we will hit the 389 // deleted state root in the "clean cache" so that the incomplete 390 // state is picked for usage. 391 deleteCleanTrieCache(trieCachePath) 392 393 // All the state roots of the middle layers should be forcibly pruned, 394 // otherwise the dangling state will be left. 395 var ( 396 found bool 397 layers = snaptree.Snapshots(headBlock.Root(), int(triesInMemory), true) 398 middleRoots = make(map[common.Hash]struct{}) 399 ) 400 for _, layer := range layers { 401 if layer.Root() == stateBloomRoot { 402 found = true 403 break 404 } 405 middleRoots[layer.Root()] = struct{}{} 406 } 407 if !found { 408 log.Error("Pruning target state is not existent") 409 return errors.New("non-existent target state") 410 } 411 return prune(snaptree, stateBloomRoot, db, stateBloom, stateBloomPath, middleRoots, time.Now()) 412 } 413 414 // extractGenesis loads the genesis state and commits all the state entries 415 // into the given bloomfilter. 416 func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { 417 genesisHash := rawdb.ReadCanonicalHash(db, 0) 418 if genesisHash == (common.Hash{}) { 419 return errors.New("missing genesis hash") 420 } 421 genesis := rawdb.ReadBlock(db, genesisHash, 0) 422 if genesis == nil { 423 return errors.New("missing genesis block") 424 } 425 t, err := trie.NewSecure(genesis.Root(), trie.NewDatabase(db)) 426 if err != nil { 427 return err 428 } 429 accIter := t.NodeIterator(nil) 430 for accIter.Next(true) { 431 hash := accIter.Hash() 432 433 // Embedded nodes don't have hash. 434 if hash != (common.Hash{}) { 435 stateBloom.Put(hash.Bytes(), nil) 436 } 437 // If it's a leaf node, yes we are touching an account, 438 // dig into the storage trie further. 439 if accIter.Leaf() { 440 var acc state.Account 441 if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil { 442 return err 443 } 444 if acc.Root != emptyRoot { 445 storageTrie, err := trie.NewSecure(acc.Root, trie.NewDatabase(db)) 446 if err != nil { 447 return err 448 } 449 storageIter := storageTrie.NodeIterator(nil) 450 for storageIter.Next(true) { 451 hash := storageIter.Hash() 452 if hash != (common.Hash{}) { 453 stateBloom.Put(hash.Bytes(), nil) 454 } 455 } 456 if storageIter.Error() != nil { 457 return storageIter.Error() 458 } 459 } 460 if !bytes.Equal(acc.CodeHash, emptyCode) { 461 stateBloom.Put(acc.CodeHash, nil) 462 } 463 } 464 } 465 return accIter.Error() 466 } 467 468 func bloomFilterName(datadir string, hash common.Hash) string { 469 return filepath.Join(datadir, fmt.Sprintf("%s.%s.%s", stateBloomFilePrefix, hash.Hex(), stateBloomFileSuffix)) 470 } 471 472 func isBloomFilter(filename string) (bool, common.Hash) { 473 filename = filepath.Base(filename) 474 if strings.HasPrefix(filename, stateBloomFilePrefix) && strings.HasSuffix(filename, stateBloomFileSuffix) { 475 return true, common.HexToHash(filename[len(stateBloomFilePrefix)+1 : len(filename)-len(stateBloomFileSuffix)-1]) 476 } 477 return false, common.Hash{} 478 } 479 480 func findBloomFilter(datadir string) (string, common.Hash, error) { 481 var ( 482 stateBloomPath string 483 stateBloomRoot common.Hash 484 ) 485 if err := filepath.Walk(datadir, func(path string, info os.FileInfo, err error) error { 486 if info != nil && !info.IsDir() { 487 ok, root := isBloomFilter(path) 488 if ok { 489 stateBloomPath = path 490 stateBloomRoot = root 491 } 492 } 493 return nil 494 }); err != nil { 495 return "", common.Hash{}, err 496 } 497 return stateBloomPath, stateBloomRoot, nil 498 } 499 500 const warningLog = ` 501 502 WARNING! 503 504 The clean trie cache is not found. Please delete it by yourself after the 505 pruning. Remember don't start the Geth without deleting the clean trie cache 506 otherwise the entire database may be damaged! 507 508 Check the command description "geth snapshot prune-state --help" for more details. 509 ` 510 511 func deleteCleanTrieCache(path string) { 512 if _, err := os.Stat(path); os.IsNotExist(err) { 513 log.Warn(warningLog) 514 return 515 } 516 os.RemoveAll(path) 517 log.Info("Deleted trie clean cache", "path", path) 518 }