diff --git a/blockchain/common_test.go b/blockchain/common_test.go index d149e880..c1c8daa8 100644 --- a/blockchain/common_test.go +++ b/blockchain/common_test.go @@ -145,7 +145,12 @@ func chainSetup(dbName string, params *chaincfg.Params) (*BlockChain, func(), er TimeSource: NewMedianTime(), SigCache: sigCache, UtxoCache: NewUtxoCache(&UtxoCacheConfig{ - DB: utxoDb, + DB: utxoDb, + FlushBlockDB: func() error { + // Don't flush to disk since it is slow and this is used in a lot of + // tests. + return nil + }, MaxSize: 100 * 1024 * 1024, // 100 MiB }), }) diff --git a/blockchain/example_test.go b/blockchain/example_test.go index be996115..a8f418c2 100644 --- a/blockchain/example_test.go +++ b/blockchain/example_test.go @@ -53,7 +53,10 @@ func ExampleBlockChain_ProcessBlock() { ChainParams: mainNetParams, TimeSource: blockchain.NewMedianTime(), UtxoCache: blockchain.NewUtxoCache(&blockchain.UtxoCacheConfig{ - DB: db, + DB: db, + FlushBlockDB: func() error { + return nil + }, MaxSize: 100 * 1024 * 1024, // 100 MiB }), }) diff --git a/blockchain/fullblocks_test.go b/blockchain/fullblocks_test.go index 79364f58..f2677af7 100644 --- a/blockchain/fullblocks_test.go +++ b/blockchain/fullblocks_test.go @@ -137,7 +137,12 @@ func chainSetup(dbName string, params *chaincfg.Params) (*blockchain.BlockChain, TimeSource: blockchain.NewMedianTime(), SigCache: sigCache, UtxoCache: blockchain.NewUtxoCache(&blockchain.UtxoCacheConfig{ - DB: utxoDb, + DB: utxoDb, + FlushBlockDB: func() error { + // Don't flush to disk since it is slow and this is used in a lot of + // tests. + return nil + }, MaxSize: 100 * 1024 * 1024, // 100 MiB }), }) diff --git a/blockchain/utxocache.go b/blockchain/utxocache.go index 5c61ff1e..cc13d317 100644 --- a/blockchain/utxocache.go +++ b/blockchain/utxocache.go @@ -125,6 +125,13 @@ type UtxoCache struct { // is created and is not changed afterward. db database.DB + // flushBlockDB defines the function to use to flush the block database to + // disk. The block database is always flushed to disk before the UTXO cache + // writes to disk in order to maintain a recoverable state in the event of an + // unclean shutdown. It is set when the instance is created and is not + // changed afterward. + flushBlockDB func() error + // maxSize is the maximum allowed size of the utxo cache, in bytes. It is set // when the instance is created and is not changed afterward. maxSize uint64 @@ -182,6 +189,14 @@ type UtxoCacheConfig struct { // This field is required. DB database.DB + // FlushBlockDB defines the function to use to flush the block database to + // disk. The block database is always flushed to disk before the UTXO cache + // writes to disk in order to maintain a recoverable state in the event of an + // unclean shutdown. + // + // This field is required. + FlushBlockDB func() error + // MaxSize defines the maximum allowed size of the utxo cache, in bytes. // // This field is required. @@ -199,6 +214,7 @@ func NewUtxoCache(config *UtxoCacheConfig) *UtxoCache { return &UtxoCache{ db: config.DB, + flushBlockDB: config.FlushBlockDB, maxSize: config.MaxSize, entries: make(map[wire.OutPoint]*UtxoEntry, uint64(maxEntries)), lastFlushTime: time.Now(), @@ -552,12 +568,22 @@ func (c *UtxoCache) flush(bestHash *chainhash.Hash, bestHeight uint32, logFlush memUsagePercent, hitRatio, bestHeight, evictionLog) } + // Flush the block database to disk. The block database MUST always be + // flushed to disk prior to flushing the UTXO cache to the UTXO database. + // This ensures that the block database is always at least as far along as the + // UTXO database which keeps the UTXO database in a recoverable state in the + // event of an unclean shutdown. + err := c.flushBlockDB() + if err != nil { + return err + } + // Flush the entries in the cache to the database and update the utxo set // state in the database. // // It is important that the utxo set state is always updated in the same // database transaction as the utxo set itself so that it is always in sync. - err := c.db.Update(func(dbTx database.Tx) error { + err = c.db.Update(func(dbTx database.Tx) error { for outpoint, entry := range c.entries { // Write the entry to the database. err := dbPutUtxoEntry(dbTx, outpoint, entry) diff --git a/blockchain/utxocache_test.go b/blockchain/utxocache_test.go index 0d0e5229..f678dc6c 100644 --- a/blockchain/utxocache_test.go +++ b/blockchain/utxocache_test.go @@ -215,7 +215,11 @@ func createTestUtxoDatabase(t *testing.T) database.DB { func createTestUtxoCache(t *testing.T, entries map[wire.OutPoint]*UtxoEntry) *UtxoCache { t.Helper() - utxoCache := NewUtxoCache(&UtxoCacheConfig{}) + utxoCache := NewUtxoCache(&UtxoCacheConfig{ + FlushBlockDB: func() error { + return nil + }, + }) for outpoint, entry := range entries { // Add the entry to the cache. The entry is cloned before being added so // that any modifications that the cache makes to the entry are not @@ -1118,8 +1122,9 @@ func TestInitialize(t *testing.T) { // gets created and initialized at startup. resetTestUtxoCache := func() *testUtxoCache { testUtxoCache := newTestUtxoCache(&UtxoCacheConfig{ - DB: g.chain.utxoDb, - MaxSize: 100 * 1024 * 1024, // 100 MiB + DB: g.chain.utxoDb, + FlushBlockDB: g.chain.db.Flush, + MaxSize: 100 * 1024 * 1024, // 100 MiB }) g.chain.utxoCache = testUtxoCache testUtxoCache.Initialize(g.chain, g.chain.bestChain.Tip()) @@ -1260,8 +1265,9 @@ func TestShutdownUtxoCache(t *testing.T) { // Replace the chain utxo cache with a test cache so that flushing can be // disabled. testUtxoCache := newTestUtxoCache(&UtxoCacheConfig{ - DB: g.chain.utxoDb, - MaxSize: 100 * 1024 * 1024, // 100 MiB + DB: g.chain.utxoDb, + FlushBlockDB: g.chain.db.Flush, + MaxSize: 100 * 1024 * 1024, // 100 MiB }) g.chain.utxoCache = testUtxoCache diff --git a/blockchain/validate_test.go b/blockchain/validate_test.go index 49c6af4a..dcfd9b4d 100644 --- a/blockchain/validate_test.go +++ b/blockchain/validate_test.go @@ -298,7 +298,10 @@ func TestCheckBlockHeaderContext(t *testing.T) { ChainParams: params, TimeSource: NewMedianTime(), UtxoCache: NewUtxoCache(&UtxoCacheConfig{ - DB: utxoDb, + DB: utxoDb, + FlushBlockDB: func() error { + return nil + }, MaxSize: 100 * 1024 * 1024, // 100 MiB }), }) diff --git a/server.go b/server.go index 7cdbb250..4eead56a 100644 --- a/server.go +++ b/server.go @@ -3403,8 +3403,9 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, // Create a new block chain instance with the appropriate configuration. utxoCache := blockchain.NewUtxoCache(&blockchain.UtxoCacheConfig{ - DB: utxoDb, - MaxSize: uint64(cfg.UtxoCacheMaxSize) * 1024 * 1024, + DB: utxoDb, + FlushBlockDB: s.db.Flush, + MaxSize: uint64(cfg.UtxoCacheMaxSize) * 1024 * 1024, }) s.chain, err = blockchain.New(ctx, &blockchain.Config{