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.
This commit is contained in:
Ryan Staudt 2021-05-07 11:18:54 -05:00 committed by Dave Collins
parent bdccd3e3f7
commit 0bf463d7d5
7 changed files with 61 additions and 12 deletions

View File

@ -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
}),
})

View File

@ -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
}),
})

View File

@ -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
}),
})

View File

@ -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)

View File

@ -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

View File

@ -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
}),
})

View File

@ -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{