From dcd22306a1223017c5f299df95682a85e2d2c096 Mon Sep 17 00:00:00 2001 From: Ryan Staudt Date: Thu, 18 Feb 2021 09:57:00 -0600 Subject: [PATCH] blockchain: Add UtxoCacher interface. This adds a UtxoCacher interface so that alternative utxo cache implementations can be provided. In particular, this will be used to provide a mock implementation for testing in order to more easily simulate various scenarios. In addition to introducing the UtxoCacher interface, this updates BlockChain and UtxoViewpoint to use the interface rather than the concrete type. --- blockchain/chain.go | 4 +-- blockchain/utxocache.go | 50 +++++++++++++++++++++++++++++++++++-- blockchain/utxoviewpoint.go | 4 +-- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/blockchain/chain.go b/blockchain/chain.go index 3c22a9ae..700a6f54 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -143,7 +143,7 @@ type BlockChain struct { sigCache *txscript.SigCache indexManager indexers.IndexManager interrupt <-chan struct{} - utxoCache *UtxoCache + utxoCache UtxoCacher // subsidyCache is the cache that provides quick lookup of subsidy // values. @@ -2152,7 +2152,7 @@ type Config struct { // the database directly. // // This field is required. - UtxoCache *UtxoCache + UtxoCache UtxoCacher } // New returns a BlockChain instance using the provided configuration details. diff --git a/blockchain/utxocache.go b/blockchain/utxocache.go index 0560379b..1b7236c8 100644 --- a/blockchain/utxocache.go +++ b/blockchain/utxocache.go @@ -58,6 +58,49 @@ const ( periodicFlushInterval = time.Minute * 2 ) +// UtxoCacher represents a utxo cache that sits on top of the utxo set database. +// +// The interface contract requires that all of these methods are safe for +// concurrent access. +type UtxoCacher interface { + // Commit updates the cache based on the state of each entry in the provided + // view. + // + // All entries in the provided view that are marked as modified and spent are + // removed from the view. Additionally, all entries that are added to the + // cache are removed from the provided view. + Commit(view *UtxoViewpoint) error + + // FetchEntries adds the requested transaction outputs to the provided view. + // It first checks the cache for each output, and if an output does not exist + // in the cache, it will fetch it from the database. + // + // Upon completion of this function, the view will contain an entry for each + // requested outpoint. Spent outputs, or those which otherwise don't exist, + // will result in a nil entry in the view. + FetchEntries(filteredSet viewFilteredSet, view *UtxoViewpoint) error + + // FetchEntry returns the specified transaction output from the utxo set. If + // the output exists in the cache, it is returned immediately. Otherwise, it + // uses an existing database transaction to fetch the output from the + // database, cache it, and return it to the caller. The entry that is + // returned can safely be mutated by the caller without invalidating the + // cache. + // + // When there is no entry for the provided output, nil will be returned for + // both the entry and the error. + FetchEntry(dbTx database.Tx, outpoint wire.OutPoint) (*UtxoEntry, error) + + // Initialize initializes the utxo cache by ensuring that the utxo set is + // caught up to the tip of the best chain. + Initialize(b *BlockChain, tip *blockNode) error + + // MaybeFlush conditionally flushes the cache to the database. A flush can be + // forced by setting the force flush parameter. + MaybeFlush(bestHash *chainhash.Hash, bestHeight uint32, forceFlush bool, + logFlush bool) error +} + // UtxoCache is an unspent transaction output cache that sits on top of the // utxo set database and provides significant runtime performance benefits at // the cost of some additional memory usage. It drastically reduces the amount @@ -128,6 +171,9 @@ type UtxoCache struct { timeNow func() time.Time } +// Ensure UtxoCache implements the UtxoCacher interface. +var _ UtxoCacher = (*UtxoCache)(nil) + // UtxoCacheConfig is a descriptor which specifies the utxo cache instance // configuration. type UtxoCacheConfig struct { @@ -367,8 +413,8 @@ func (c *UtxoCache) FetchEntries(filteredSet viewFilteredSet, view *UtxoViewpoin return err } -// Commit updates all entries in the cache based on the state of each entry in -// the provided view. +// Commit updates the cache based on the state of each entry in the provided +// view. // // All entries in the provided view that are marked as modified and spent are // removed from the view. Additionally, all entries that are added to the cache diff --git a/blockchain/utxoviewpoint.go b/blockchain/utxoviewpoint.go index 4c7c4783..3fba529b 100644 --- a/blockchain/utxoviewpoint.go +++ b/blockchain/utxoviewpoint.go @@ -25,7 +25,7 @@ import ( // The unspent outputs are needed by other transactions for things such as // script validation and double spend prevention. type UtxoViewpoint struct { - cache *UtxoCache + cache UtxoCacher entries map[wire.OutPoint]*UtxoEntry bestHash chainhash.Hash } @@ -757,7 +757,7 @@ func (view *UtxoViewpoint) clone() *UtxoViewpoint { } // NewUtxoViewpoint returns a new empty unspent transaction output view. -func NewUtxoViewpoint(cache *UtxoCache) *UtxoViewpoint { +func NewUtxoViewpoint(cache UtxoCacher) *UtxoViewpoint { return &UtxoViewpoint{ cache: cache, entries: make(map[wire.OutPoint]*UtxoEntry),