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
|
|
@ -96,7 +96,9 @@ type BlockChain struct {
|
||||||
cacheConfig *CacheConfig // Cache configuration for pruning
|
cacheConfig *CacheConfig // Cache configuration for pruning
|
||||||
|
|
||||||
db ethdb.Database // Low level persistent database to store final content in
|
db ethdb.Database // Low level persistent database to store final content in
|
||||||
triegc *prque.Prque // Priority queue mapping block numbers to tries to gc
|
|
||||||
|
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
|
gcproc time.Duration // Accumulates canonical block processing for trie dumping
|
||||||
|
|
||||||
hc *HeaderChain
|
hc *HeaderChain
|
||||||
|
|
@ -159,7 +161,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||||
chainConfig: chainConfig,
|
chainConfig: chainConfig,
|
||||||
cacheConfig: cacheConfig,
|
cacheConfig: cacheConfig,
|
||||||
db: db,
|
db: db,
|
||||||
triegc: prque.New(nil),
|
gcqueue: prque.New(nil),
|
||||||
stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit),
|
stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit),
|
||||||
quit: make(chan struct{}),
|
quit: make(chan struct{}),
|
||||||
shouldPreserve: shouldPreserve,
|
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
|
// Take ownership of this particular state
|
||||||
go bc.update()
|
go bc.update()
|
||||||
return bc, nil
|
return bc, nil
|
||||||
|
|
@ -718,8 +726,8 @@ func (bc *BlockChain) Stop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for !bc.triegc.Empty() {
|
for !bc.gcqueue.Empty() {
|
||||||
triedb.Dereference(bc.triegc.PopItem().(common.Hash), false)
|
triedb.Dereference(bc.gcqueue.PopItem().(common.Hash), false)
|
||||||
}
|
}
|
||||||
if size, _ := triedb.Size(); size != 0 {
|
if size, _ := triedb.Size(); size != 0 {
|
||||||
log.Error("Dangling trie nodes after full cleanup", "size", size)
|
log.Error("Dangling trie nodes after full cleanup", "size", size)
|
||||||
|
|
@ -967,7 +975,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
||||||
} else {
|
} else {
|
||||||
// Full but not archive node, do proper garbage collection
|
// Full but not archive node, do proper garbage collection
|
||||||
triedb.Reference(common.Hash{}, root, common.Hash{}) // metadata reference to keep trie alive
|
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 current := block.NumberU64(); current > triesInMemory {
|
||||||
// If we exceeded our memory allowance, flush matured singleton nodes to disk
|
// 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)
|
triedb.Commit(header.Root, true)
|
||||||
lastWrite = chosen
|
lastWrite = chosen
|
||||||
bc.gcproc = 0
|
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
|
// Garbage collect anything below our required write retention
|
||||||
for !bc.triegc.Empty() {
|
for !bc.gcqueue.Empty() {
|
||||||
root, number := bc.triegc.Pop()
|
root, number := bc.gcqueue.Pop()
|
||||||
if uint64(-number) > chosen {
|
if uint64(-number) > chosen {
|
||||||
bc.triegc.Push(root, number)
|
bc.gcqueue.Push(root, number)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
triedb.Dereference(root.(common.Hash), true)
|
triedb.Dereference(root.(common.Hash), true)
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,7 @@ func splitNodeKey(key string) (common.Hash, common.Hash) {
|
||||||
// periodically flush a couple tries to disk, garbage collecting the remainder.
|
// periodically flush a couple tries to disk, garbage collecting the remainder.
|
||||||
type Database struct {
|
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
|
cleans *bigcache.BigCache // GC friendly memory cache of clean node RLPs
|
||||||
dirties map[string]*cachedNode // Data and references relationships of dirty nodes
|
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{
|
return &Database{
|
||||||
diskdb: diskdb,
|
diskdb: diskdb,
|
||||||
|
noprune: make(map[common.Hash]struct{}),
|
||||||
cleans: cleans,
|
cleans: cleans,
|
||||||
dirties: map[string]*cachedNode{metaRoot: {}},
|
dirties: map[string]*cachedNode{metaRoot: {}},
|
||||||
preimages: make(map[common.Hash][]byte),
|
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.
|
// DiskDB retrieves the persistent storage backing the trie database.
|
||||||
func (db *Database) DiskDB() DatabaseReader {
|
func (db *Database) DiskDB() DatabaseReader {
|
||||||
return db.diskdb
|
return db.diskdb
|
||||||
|
|
@ -459,7 +482,7 @@ func (db *Database) node(owner common.Hash, hash common.Hash, cachegen uint16) n
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if db.cleans != nil {
|
if db.cleans != nil {
|
||||||
db.cleans.Set(string(hash[:]), enc)
|
db.cleans.Set(key, enc)
|
||||||
memcacheCleanMissMeter.Mark(1)
|
memcacheCleanMissMeter.Mark(1)
|
||||||
memcacheCleanWriteMeter.Mark(int64(len(enc)))
|
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
|
// Iterate over all the nodes marked for pruning and delete them
|
||||||
for _, mark := range p.marks {
|
for _, mark := range p.marks {
|
||||||
p.prune(mark.owner, mark.hash, mark.path)
|
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
|
// 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
|
// 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).
|
// seen either, so no point in checkin upwards further than the first hash).
|
||||||
state := t.state
|
for state := t.state; state != nil; state = state.parent {
|
||||||
for state != nil {
|
|
||||||
// If we've found a hash node, check if it's an already known result
|
|
||||||
if state.hash != (common.Hash{}) {
|
if state.hash != (common.Hash{}) {
|
||||||
if unrefs[state.hash] {
|
if unrefs[state.hash] {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// Not a hash node, traverse further up
|
|
||||||
state = state.parent
|
|
||||||
}
|
}
|
||||||
// Traverse downward until the prefix matches the path completely
|
// Traverse downward until the prefix matches the path completely
|
||||||
path = path[len(t.state.prefix):]
|
path = path[len(t.state.prefix):]
|
||||||
|
|
@ -206,7 +212,7 @@ func (t *traverser) live(owner common.Hash, hash common.Hash, path []byte, unref
|
||||||
} else {
|
} else {
|
||||||
blob, err := t.db.diskdb.Get([]byte(key))
|
blob, err := t.db.diskdb.Get([]byte(key))
|
||||||
if blob == nil || err != nil {
|
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
|
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))
|
//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