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