From 0bf463d7d57b3b36eb8317c162b32743d2704137 Mon Sep 17 00:00:00 2001 From: Ryan Staudt Date: Fri, 7 May 2021 11:18:54 -0500 Subject: [PATCH] multi: Flush block DB before UTXO DB. This adds a flushBlockDB function to the UTXO cache. The flushBlockDB function is used to flush the block database 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. --- blockchain/common_test.go | 7 ++++++- blockchain/example_test.go | 5 ++++- blockchain/fullblocks_test.go | 7 ++++++- blockchain/utxocache.go | 28 +++++++++++++++++++++++++++- blockchain/utxocache_test.go | 16 +++++++++++----- blockchain/validate_test.go | 5 ++++- server.go | 5 +++-- 7 files changed, 61 insertions(+), 12 deletions(-) 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{