diff --git a/blockchain/chain.go b/blockchain/chain.go index 84d4a0f3..60420022 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -642,6 +642,24 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, if err != nil { return err } + + // We may need to flush if the prune will delete blocks that + // are past our last flush block. + // + // NOTE: the database will never be inconsistent here as the + // actual blocks are not deleted until the db.Update returns. + needsFlush, err := b.flushNeededAfterPrune(deletedHashes) + if err != nil { + return err + } + if needsFlush { + // Since the deleted hashes are past our last + // flush block, flush the utxo cache now. + err = b.utxoCache.flush(dbTx, FlushRequired, state) + if err != nil { + return err + } + } } } @@ -719,7 +737,7 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, // Since we may have changed the UTXO cache, we make sure it didn't exceed its // maximum size. If we're pruned and have flushed already, this will be a no-op. return b.db.Update(func(dbTx database.Tx) error { - return b.utxoCache.flush(dbTx, FlushIfNeeded, b.BestSnapshot()) + return b.utxoCache.flush(dbTx, FlushIfNeeded, state) }) } diff --git a/blockchain/testdata/blk_0_to_14131.dat b/blockchain/testdata/blk_0_to_14131.dat new file mode 100644 index 00000000..cc1a789e Binary files /dev/null and b/blockchain/testdata/blk_0_to_14131.dat differ diff --git a/blockchain/utxocache_test.go b/blockchain/utxocache_test.go index 536c2054..aa58c021 100644 --- a/blockchain/utxocache_test.go +++ b/blockchain/utxocache_test.go @@ -16,6 +16,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/database" + "github.com/btcsuite/btcd/database/ffldb" "github.com/btcsuite/btcd/wire" ) @@ -704,3 +705,96 @@ func TestFlushNeededAfterPrune(t *testing.T) { } } } + +func TestFlushOnPrune(t *testing.T) { + chain, tearDown, err := chainSetup("TestFlushOnPrune", &chaincfg.MainNetParams) + if err != nil { + panic(fmt.Sprintf("error loading blockchain with database: %v", err)) + } + defer tearDown() + + chain.utxoCache.maxTotalMemoryUsage = 10 * 1024 * 1024 + chain.utxoCache.cachedEntries.maxTotalMemoryUsage = chain.utxoCache.maxTotalMemoryUsage + + // Set the maxBlockFileSize and the prune target small so that we can trigger a + // prune to happen. + maxBlockFileSize := uint32(8192) + chain.pruneTarget = uint64(maxBlockFileSize) * 2 + + // Read blocks from the file. + blocks, err := loadBlocks("blk_0_to_14131.dat") + if err != nil { + t.Fatalf("failed to read block from file. %v", err) + } + + syncBlocks := func() { + for i, block := range blocks { + if i == 0 { + // Skip the genesis block. + continue + } + isMainChain, _, err := chain.ProcessBlock(block, BFNone) + if err != nil { + t.Fatal(err) + } + + if !isMainChain { + t.Fatalf("expected block %s to be on the main chain", block.Hash()) + } + } + } + + // Sync the chain. + ffldb.TstRunWithMaxBlockFileSize(chain.db, maxBlockFileSize, syncBlocks) + + // Function that errors out if the block that should exist doesn't exist. + shouldExist := func(dbTx database.Tx, blockHash *chainhash.Hash) { + bytes, err := dbTx.FetchBlock(blockHash) + if err != nil { + t.Fatal(err) + } + block, err := btcutil.NewBlockFromBytes(bytes) + if err != nil { + t.Fatalf("didn't find block %v. %v", blockHash, err) + } + + if !block.Hash().IsEqual(blockHash) { + t.Fatalf("expected to find block %v but got %v", + blockHash, block.Hash()) + } + } + + // Function that errors out if the block that shouldn't exist exists. + shouldNotExist := func(dbTx database.Tx, blockHash *chainhash.Hash) { + bytes, err := dbTx.FetchBlock(chaincfg.MainNetParams.GenesisHash) + if err == nil { + t.Fatalf("expected block %s to be pruned", blockHash) + } + if len(bytes) != 0 { + t.Fatalf("expected block %s to be pruned but got %v", + blockHash, bytes) + } + } + + // The below code checks that the correct blocks were pruned. + chain.db.View(func(dbTx database.Tx) error { + exist := false + for _, block := range blocks { + // Blocks up to the last flush hash should not exist. + // The utxocache is big enough so that it shouldn't flush + // on it being full. It should only flush on prunes. + if block.Hash().IsEqual(&chain.utxoCache.lastFlushHash) { + exist = true + } + + if exist { + shouldExist(dbTx, block.Hash()) + } else { + shouldNotExist(dbTx, block.Hash()) + } + + } + + return nil + }) +}