mirror of
https://github.com/btcsuite/btcd.git
synced 2024-11-19 01:40:07 +01:00
blockchain: add flushNeededAfterPrune
flushNeededAfterPrune returns true if the utxocache needs to be flushed after the pruning of the given slice of block hashes. For the utxo cache to be recoverable while pruning is enabled, we need to make sure that there exists blocks since the last utxo cache flush. If there are blocks that are deleted after the last utxo cache flush, the utxo set is irrecoverable. The added method provides a way to tell if a flush is needed.
This commit is contained in:
parent
d387d162f3
commit
dd37dfa80b
@ -711,3 +711,34 @@ func (b *BlockChain) InitConsistentState(tip *blockNode, interrupt <-chan struct
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// flushNeededAfterPrune returns true if the utxo cache needs to be flushed after a prune
|
||||
// of the block storage. In the case of an unexpected shutdown, the utxo cache needs
|
||||
// to be reconstructed from where the utxo cache was last flushed. In order for the
|
||||
// utxo cache to be reconstructed, we always need to have the blocks since the utxo cache
|
||||
// flush last happened.
|
||||
//
|
||||
// Example: if the last flush hash was at height 100 and one of the deleted blocks was at
|
||||
// height 98, this function will return true.
|
||||
func (b *BlockChain) flushNeededAfterPrune(deletedBlockHashes []chainhash.Hash) (bool, error) {
|
||||
lastFlushHeight, err := b.BlockHeightByHash(&b.utxoCache.lastFlushHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Loop through all the block hashes and find out what the highest block height
|
||||
// among the deleted hashes is.
|
||||
highestDeletedHeight := int32(-1)
|
||||
for _, deletedBlockHash := range deletedBlockHashes {
|
||||
height, err := b.BlockHeightByHash(&deletedBlockHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if height > highestDeletedHeight {
|
||||
highestDeletedHeight = height
|
||||
}
|
||||
}
|
||||
|
||||
return highestDeletedHeight >= lastFlushHeight, nil
|
||||
}
|
||||
|
@ -587,3 +587,120 @@ func TestUtxoCacheFlush(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlushNeededAfterPrune(t *testing.T) {
|
||||
// Construct a synthetic block chain with a block index consisting of
|
||||
// the following structure.
|
||||
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
|
||||
tip := tstTip
|
||||
chain := newFakeChain(&chaincfg.MainNetParams)
|
||||
chain.utxoCache = newUtxoCache(nil, 0)
|
||||
branchNodes := chainedNodes(chain.bestChain.Genesis(), 18)
|
||||
for _, node := range branchNodes {
|
||||
chain.index.SetStatusFlags(node, statusValid)
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
chain.bestChain.SetTip(tip(branchNodes))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
lastFlushHash chainhash.Hash
|
||||
delHashes []chainhash.Hash
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "deleted block up to height 9, last flush hash at block 10",
|
||||
delHashes: func() []chainhash.Hash {
|
||||
delBlockHashes := make([]chainhash.Hash, 0, 9)
|
||||
for i := range branchNodes {
|
||||
if branchNodes[i].height < 10 {
|
||||
delBlockHashes = append(delBlockHashes, branchNodes[i].hash)
|
||||
}
|
||||
}
|
||||
|
||||
return delBlockHashes
|
||||
}(),
|
||||
lastFlushHash: func() chainhash.Hash {
|
||||
// Just some sanity checking to make sure the height is 10.
|
||||
if branchNodes[9].height != 10 {
|
||||
panic("was looking for height 10")
|
||||
}
|
||||
return branchNodes[9].hash
|
||||
}(),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "deleted blocks up to height 10, last flush hash at block 10",
|
||||
delHashes: func() []chainhash.Hash {
|
||||
delBlockHashes := make([]chainhash.Hash, 0, 10)
|
||||
for i := range branchNodes {
|
||||
if branchNodes[i].height < 11 {
|
||||
delBlockHashes = append(delBlockHashes, branchNodes[i].hash)
|
||||
}
|
||||
}
|
||||
return delBlockHashes
|
||||
}(),
|
||||
lastFlushHash: func() chainhash.Hash {
|
||||
// Just some sanity checking to make sure the height is 10.
|
||||
if branchNodes[9].height != 10 {
|
||||
panic("was looking for height 10")
|
||||
}
|
||||
return branchNodes[9].hash
|
||||
}(),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "deleted block height 17, last flush hash at block 5",
|
||||
delHashes: func() []chainhash.Hash {
|
||||
delBlockHashes := make([]chainhash.Hash, 1)
|
||||
delBlockHashes[0] = branchNodes[16].hash
|
||||
// Just some sanity checking to make sure the height is 10.
|
||||
if branchNodes[16].height != 17 {
|
||||
panic("was looking for height 17")
|
||||
}
|
||||
return delBlockHashes
|
||||
}(),
|
||||
lastFlushHash: func() chainhash.Hash {
|
||||
// Just some sanity checking to make sure the height is 10.
|
||||
if branchNodes[4].height != 5 {
|
||||
panic("was looking for height 5")
|
||||
}
|
||||
return branchNodes[4].hash
|
||||
}(),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "deleted block height 3, last flush hash at block 4",
|
||||
delHashes: func() []chainhash.Hash {
|
||||
delBlockHashes := make([]chainhash.Hash, 1)
|
||||
delBlockHashes[0] = branchNodes[2].hash
|
||||
// Just some sanity checking to make sure the height is 10.
|
||||
if branchNodes[2].height != 3 {
|
||||
panic("was looking for height 3")
|
||||
}
|
||||
return delBlockHashes
|
||||
}(),
|
||||
lastFlushHash: func() chainhash.Hash {
|
||||
// Just some sanity checking to make sure the height is 10.
|
||||
if branchNodes[3].height != 4 {
|
||||
panic("was looking for height 4")
|
||||
}
|
||||
return branchNodes[3].hash
|
||||
}(),
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
chain.utxoCache.lastFlushHash = test.lastFlushHash
|
||||
got, err := chain.flushNeededAfterPrune(test.delHashes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if got != test.expected {
|
||||
t.Fatalf("for test %s, expected need flush to return %v but got %v",
|
||||
test.name, test.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user