mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
core, trie: prevent genesis and last snapshot from being pruned
This commit is contained in:
parent
25a4d2e067
commit
17e7433725
3 changed files with 63 additions and 18 deletions
|
|
@ -95,9 +95,11 @@ type BlockChain struct {
|
|||
chainConfig *params.ChainConfig // Chain & network configuration
|
||||
cacheConfig *CacheConfig // Cache configuration for pruning
|
||||
|
||||
db ethdb.Database // Low level persistent database to store final content in
|
||||
triegc *prque.Prque // Priority queue mapping block numbers to tries to gc
|
||||
gcproc time.Duration // Accumulates canonical block processing for trie dumping
|
||||
db ethdb.Database // Low level persistent database to store final content in
|
||||
|
||||
gcqueue *prque.Prque // Priority queue mapping block numbers to tries to gc
|
||||
gcsave common.Hash // Root hash of the last trie committed to disk
|
||||
gcproc time.Duration // Accumulates canonical block processing for trie dumping
|
||||
|
||||
hc *HeaderChain
|
||||
rmLogsFeed event.Feed
|
||||
|
|
@ -159,7 +161,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
|||
chainConfig: chainConfig,
|
||||
cacheConfig: cacheConfig,
|
||||
db: db,
|
||||
triegc: prque.New(nil),
|
||||
gcqueue: prque.New(nil),
|
||||
stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit),
|
||||
quit: make(chan struct{}),
|
||||
shouldPreserve: shouldPreserve,
|
||||
|
|
@ -200,6 +202,12 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
|||
}
|
||||
}
|
||||
}
|
||||
// Forbid the genesis state (forever) and latest state (temporarilly) from being pruned
|
||||
bc.stateCache.TrieDB().ForbidPrune(bc.genesisBlock.Root())
|
||||
if head := bc.CurrentBlock(); head.NumberU64() > 0 {
|
||||
bc.gcsave = head.Root()
|
||||
bc.stateCache.TrieDB().ForbidPrune(bc.gcsave)
|
||||
}
|
||||
// Take ownership of this particular state
|
||||
go bc.update()
|
||||
return bc, nil
|
||||
|
|
@ -718,8 +726,8 @@ func (bc *BlockChain) Stop() {
|
|||
}
|
||||
}
|
||||
}
|
||||
for !bc.triegc.Empty() {
|
||||
triedb.Dereference(bc.triegc.PopItem().(common.Hash), false)
|
||||
for !bc.gcqueue.Empty() {
|
||||
triedb.Dereference(bc.gcqueue.PopItem().(common.Hash), false)
|
||||
}
|
||||
if size, _ := triedb.Size(); size != 0 {
|
||||
log.Error("Dangling trie nodes after full cleanup", "size", size)
|
||||
|
|
@ -967,7 +975,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
|||
} else {
|
||||
// Full but not archive node, do proper garbage collection
|
||||
triedb.Reference(common.Hash{}, root, common.Hash{}) // metadata reference to keep trie alive
|
||||
bc.triegc.Push(root, -int64(block.NumberU64()))
|
||||
bc.gcqueue.Push(root, -int64(block.NumberU64()))
|
||||
|
||||
if current := block.NumberU64(); current > triesInMemory {
|
||||
// If we exceeded our memory allowance, flush matured singleton nodes to disk
|
||||
|
|
@ -998,13 +1006,21 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
|||
triedb.Commit(header.Root, true)
|
||||
lastWrite = chosen
|
||||
bc.gcproc = 0
|
||||
|
||||
// A new snapshot was flushed to disk, swap the prune allowance
|
||||
triedb.ForbidPrune(header.Root)
|
||||
if bc.gcsave != (common.Hash{}) {
|
||||
triedb.PermitPrune(bc.gcsave)
|
||||
triedb.Dereference(bc.gcsave, true)
|
||||
}
|
||||
bc.gcsave = header.Root
|
||||
}
|
||||
}
|
||||
// Garbage collect anything below our required write retention
|
||||
for !bc.triegc.Empty() {
|
||||
root, number := bc.triegc.Pop()
|
||||
for !bc.gcqueue.Empty() {
|
||||
root, number := bc.gcqueue.Pop()
|
||||
if uint64(-number) > chosen {
|
||||
bc.triegc.Push(root, number)
|
||||
bc.gcqueue.Push(root, number)
|
||||
break
|
||||
}
|
||||
triedb.Dereference(root.(common.Hash), true)
|
||||
|
|
|
|||
|
|
@ -101,7 +101,8 @@ func splitNodeKey(key string) (common.Hash, common.Hash) {
|
|||
// the disk database. The aim is to accumulate trie writes in-memory and only
|
||||
// periodically flush a couple tries to disk, garbage collecting the remainder.
|
||||
type Database struct {
|
||||
diskdb ethdb.Database // Persistent storage for matured trie nodes
|
||||
diskdb ethdb.Database // Persistent storage for matured trie nodes
|
||||
noprune map[common.Hash]struct{} // Root hashes of the tries that aren't prunable
|
||||
|
||||
cleans *bigcache.BigCache // GC friendly memory cache of clean node RLPs
|
||||
dirties map[string]*cachedNode // Data and references relationships of dirty nodes
|
||||
|
|
@ -363,12 +364,34 @@ func NewDatabaseWithCache(diskdb ethdb.Database, cache int) *Database {
|
|||
}
|
||||
return &Database{
|
||||
diskdb: diskdb,
|
||||
noprune: make(map[common.Hash]struct{}),
|
||||
cleans: cleans,
|
||||
dirties: map[string]*cachedNode{metaRoot: {}},
|
||||
preimages: make(map[common.Hash][]byte),
|
||||
}
|
||||
}
|
||||
|
||||
// ForbidPrune adds a root hash to the list of tries that are disallowed from
|
||||
// being pruned. The only two ever to be used are the genesis trie and the last
|
||||
// committed trie (snapshot).
|
||||
func (db *Database) ForbidPrune(root common.Hash) {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
log.Debug("Forbidding pruner to delete trie", "root", root)
|
||||
db.noprune[root] = struct{}{}
|
||||
}
|
||||
|
||||
// PermitPrune allows a particular trie to be pruned from the disk database. To
|
||||
// actually run the pruner, please call a dereference on the root hash.
|
||||
func (db *Database) PermitPrune(root common.Hash) {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
log.Debug("Permitting pruner to delete trie", "root", root)
|
||||
delete(db.noprune, root)
|
||||
}
|
||||
|
||||
// DiskDB retrieves the persistent storage backing the trie database.
|
||||
func (db *Database) DiskDB() DatabaseReader {
|
||||
return db.diskdb
|
||||
|
|
@ -459,7 +482,7 @@ func (db *Database) node(owner common.Hash, hash common.Hash, cachegen uint16) n
|
|||
return nil
|
||||
}
|
||||
if db.cleans != nil {
|
||||
db.cleans.Set(string(hash[:]), enc)
|
||||
db.cleans.Set(key, enc)
|
||||
memcacheCleanMissMeter.Mark(1)
|
||||
memcacheCleanWriteMeter.Mark(int64(len(enc)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,16 @@ func (p *pruner) execute() {
|
|||
},
|
||||
})
|
||||
}
|
||||
// Beside all the tries kept in memory, keep anything forbidden from pruning
|
||||
for hash := range p.db.noprune {
|
||||
p.tries = append(p.tries, &traverser{
|
||||
db: p.db,
|
||||
state: &traverserState{
|
||||
node: hashNode(common.CopyBytes(hash[:])), // Need closure, take care!!
|
||||
hash: hash,
|
||||
},
|
||||
})
|
||||
}
|
||||
// Iterate over all the nodes marked for pruning and delete them
|
||||
for _, mark := range p.marks {
|
||||
p.prune(mark.owner, mark.hash, mark.path)
|
||||
|
|
@ -169,17 +179,13 @@ func (t *traverser) live(owner common.Hash, hash common.Hash, path []byte, unref
|
|||
// Short circuit the liveness check if we already covered this prefix (if this
|
||||
// prefix path was not yet seen in previous tries, no parent could have been
|
||||
// seen either, so no point in checkin upwards further than the first hash).
|
||||
state := t.state
|
||||
for state != nil {
|
||||
// If we've found a hash node, check if it's an already known result
|
||||
for state := t.state; state != nil; state = state.parent {
|
||||
if state.hash != (common.Hash{}) {
|
||||
if unrefs[state.hash] {
|
||||
return false
|
||||
}
|
||||
break
|
||||
}
|
||||
// Not a hash node, traverse further up
|
||||
state = state.parent
|
||||
}
|
||||
// Traverse downward until the prefix matches the path completely
|
||||
path = path[len(t.state.prefix):]
|
||||
|
|
@ -206,7 +212,7 @@ func (t *traverser) live(owner common.Hash, hash common.Hash, path []byte, unref
|
|||
} else {
|
||||
blob, err := t.db.diskdb.Get([]byte(key))
|
||||
if blob == nil || err != nil {
|
||||
log.Error("Missing referenced node", "owner", owner, "hash", t.state.hash, "path", fmt.Sprintf("%x%x", t.state.prefix, path))
|
||||
log.Error("Missing referenced node", "owner", owner, "hash", t.state.hash.Hex(), "path", fmt.Sprintf("%x%x", t.state.prefix, path))
|
||||
return false
|
||||
//panic(fmt.Sprintf("missing referenced node %x (searching for %x:%x at %x%x)", key, owner, t.state.hash, t.state.prefix, path))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue