diff --git a/blockchain/stakeext.go b/blockchain/stakeext.go index d09e1290..f16df3ca 100644 --- a/blockchain/stakeext.go +++ b/blockchain/stakeext.go @@ -7,7 +7,6 @@ package blockchain import ( "github.com/decred/dcrd/chaincfg/chainhash" - "github.com/decred/dcrd/database/v2" "github.com/decred/dcrd/dcrutil/v4" "github.com/decred/dcrd/txscript/v4" "github.com/decred/dcrd/txscript/v4/stdaddr" @@ -111,27 +110,22 @@ func (b *BlockChain) TicketsWithAddress(address stdaddr.Address, isTreasuryEnabl encodedAddr := address.String() var ticketsWithAddr []chainhash.Hash - err := b.utxoDb.View(func(dbTx database.Tx) error { - for _, hash := range tickets { - outpoint := wire.OutPoint{Hash: hash, Index: 0, Tree: wire.TxTreeStake} - utxo, err := b.utxoCache.FetchEntry(dbTx, outpoint) - if err != nil { - return err - } - _, addrs, _, err := txscript.ExtractPkScriptAddrs(utxo.ScriptVersion(), - utxo.PkScript(), b.chainParams, isTreasuryEnabled) - if err != nil { - return err - } - if addrs[0].String() == encodedAddr { - ticketsWithAddr = append(ticketsWithAddr, hash) - } + for _, hash := range tickets { + outpoint := wire.OutPoint{Hash: hash, Index: 0, Tree: wire.TxTreeStake} + utxo, err := b.utxoCache.FetchEntry(outpoint) + if err != nil { + return nil, err + } + + _, addrs, _, err := txscript.ExtractPkScriptAddrs(utxo.ScriptVersion(), + utxo.PkScript(), b.chainParams, isTreasuryEnabled) + if err != nil { + return nil, err + } + if addrs[0].String() == encodedAddr { + ticketsWithAddr = append(ticketsWithAddr, hash) } - return nil - }) - if err != nil { - return nil, err } return ticketsWithAddr, nil @@ -223,20 +217,14 @@ func (b *BlockChain) TicketPoolValue() (dcrutil.Amount, error) { b.chainLock.RUnlock() var amt int64 - err := b.utxoDb.View(func(dbTx database.Tx) error { - for _, hash := range sn.LiveTickets() { - outpoint := wire.OutPoint{Hash: hash, Index: 0, Tree: wire.TxTreeStake} - utxo, err := b.utxoCache.FetchEntry(dbTx, outpoint) - if err != nil { - return err - } - - amt += utxo.Amount() + for _, hash := range sn.LiveTickets() { + outpoint := wire.OutPoint{Hash: hash, Index: 0, Tree: wire.TxTreeStake} + utxo, err := b.utxoCache.FetchEntry(outpoint) + if err != nil { + return 0, err } - return nil - }) - if err != nil { - return 0, err + + amt += utxo.Amount() } return dcrutil.Amount(amt), nil } diff --git a/blockchain/utxocache.go b/blockchain/utxocache.go index a33cc9ef..eb894a21 100644 --- a/blockchain/utxocache.go +++ b/blockchain/utxocache.go @@ -58,7 +58,7 @@ const ( periodicFlushInterval = time.Minute * 2 ) -// UtxoCacher represents a utxo cache that sits on top of the utxo set database. +// UtxoCacher represents a utxo cache that sits on top of a utxo set backend. // // The interface contract requires that all of these methods are safe for // concurrent access. @@ -73,7 +73,7 @@ type UtxoCacher interface { // 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. + // in the cache, it will fetch it from the backend. // // Upon completion of this function, the view will contain an entry for each // requested outpoint. Spent outputs, or those which otherwise don't exist, @@ -82,20 +82,19 @@ type UtxoCacher interface { // 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. + // fetches the output from the backend, caches it, and returns 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) + FetchEntry(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 + // MaybeFlush conditionally flushes the cache to the backend. A flush can be // forced by setting the force flush parameter. MaybeFlush(bestHash *chainhash.Hash, bestHeight uint32, forceFlush bool, logFlush bool) error @@ -335,19 +334,19 @@ func (c *UtxoCache) SpendEntry(outpoint wire.OutPoint) { // 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. A cloned copy of the entry is -// returned so it can safely be mutated by the caller without invalidating the -// cache. +// fetches the output from the database, caches it, and returns it to the +// caller. A cloned copy of the entry is returned so it 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. // // This function MUST be called with the cache lock held. -func (c *UtxoCache) fetchEntry(dbTx database.Tx, outpoint wire.OutPoint) (*UtxoEntry, error) { +func (c *UtxoCache) fetchEntry(outpoint wire.OutPoint) (*UtxoEntry, error) { // If the cache already has the entry, return it immediately. A cloned copy // of the entry is returned so it can safely be mutated by the caller without // invalidating the cache. + var entry *UtxoEntry if entry, found := c.entries[outpoint]; found { c.hits++ return entry.Clone(), nil @@ -362,7 +361,11 @@ func (c *UtxoCache) fetchEntry(dbTx database.Tx, outpoint wire.OutPoint) (*UtxoE // will result in nil entries in the view. This is intentionally done // so other code can use the presence of an entry in the view as a way // to unnecessarily avoid attempting to reload it from the database. - entry, err := dbFetchUtxoEntry(dbTx, outpoint) + err := c.db.View(func(dbTx database.Tx) error { + var err error + entry, err = dbFetchUtxoEntry(dbTx, outpoint) + return err + }) if err != nil { return nil, err } @@ -381,18 +384,17 @@ func (c *UtxoCache) fetchEntry(dbTx database.Tx, outpoint wire.OutPoint) (*UtxoE // 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. A cloned copy of the entry is -// returned so it can safely be mutated by the caller without invalidating the -// cache. +// fetches the output from the database, caches it, and returns it to the +// caller. A cloned copy of the entry is returned so it 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. // // This function is safe for concurrent access. -func (c *UtxoCache) FetchEntry(dbTx database.Tx, outpoint wire.OutPoint) (*UtxoEntry, error) { +func (c *UtxoCache) FetchEntry(outpoint wire.OutPoint) (*UtxoEntry, error) { c.cacheLock.Lock() - entry, err := c.fetchEntry(dbTx, outpoint) + entry, err := c.fetchEntry(outpoint) c.cacheLock.Unlock() return entry, err } @@ -408,25 +410,21 @@ func (c *UtxoCache) FetchEntry(dbTx database.Tx, outpoint wire.OutPoint) (*UtxoE // This function is safe for concurrent access. func (c *UtxoCache) FetchEntries(filteredSet viewFilteredSet, view *UtxoViewpoint) error { c.cacheLock.Lock() - err := c.db.View(func(dbTx database.Tx) error { - for outpoint := range filteredSet { - entry, err := c.fetchEntry(dbTx, outpoint) - if err != nil { - return err - } - - // NOTE: Missing entries are not considered an error here and instead - // will result in nil entries in the view. This is intentionally done - // so other code can use the presence of an entry in the view as a way - // to unnecessarily avoid attempting to reload it from the database. - view.entries[outpoint] = entry + for outpoint := range filteredSet { + entry, err := c.fetchEntry(outpoint) + if err != nil { + return err } - return nil - }) + // NOTE: Missing entries are not considered an error here and instead + // will result in nil entries in the view. This is intentionally done + // so other code can use the presence of an entry in the view as a way + // to unnecessarily avoid attempting to reload it from the database. + view.entries[outpoint] = entry + } c.cacheLock.Unlock() - return err + return nil } // Commit updates the cache based on the state of each entry in the provided @@ -952,19 +950,9 @@ func (b *BlockChain) ShutdownUtxoCache() { // any) is NOT. func (b *BlockChain) FetchUtxoEntry(outpoint wire.OutPoint) (*UtxoEntry, error) { b.chainLock.RLock() - defer b.chainLock.RUnlock() - - var entry *UtxoEntry - err := b.utxoDb.View(func(dbTx database.Tx) error { - var err error - entry, err = b.utxoCache.FetchEntry(dbTx, outpoint) - return err - }) - if err != nil { - return nil, err - } - - return entry, nil + entry, err := b.utxoCache.FetchEntry(outpoint) + b.chainLock.RUnlock() + return entry, err } // UtxoStats represents unspent output statistics on the current utxo set. diff --git a/blockchain/utxocache_test.go b/blockchain/utxocache_test.go index f678dc6c..e1f85c3f 100644 --- a/blockchain/utxocache_test.go +++ b/blockchain/utxocache_test.go @@ -545,6 +545,7 @@ func TestFetchEntry(t *testing.T) { for _, test := range tests { // Create a utxo cache with the cached entries specified by the test. utxoCache := createTestUtxoCache(t, test.cachedEntries) + utxoCache.db = db wantTotalEntrySize := utxoCache.totalEntrySize // Add entries specified by the test to the test database. @@ -563,12 +564,7 @@ func TestFetchEntry(t *testing.T) { } // Attempt to fetch the entry for the outpoint specified by the test. - var entry *UtxoEntry - err = db.View(func(dbTx database.Tx) error { - var err error - entry, err = utxoCache.FetchEntry(dbTx, test.outpoint) - return err - }) + entry, err = utxoCache.FetchEntry(test.outpoint) if err != nil { t.Fatalf("%q: unexpected error fetching entry: %v", test.name, err) }