github.com/BlockABC/godash@v0.0.0-20191112120524-f4aa3a32c566/blockchain/utxoviewpoint.go (about)

     1  // Copyright (c) 2015-2016 The btcsuite developers
     2  // Copyright (c) 2016 The Dash developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package blockchain
     7  
     8  import (
     9  	"fmt"
    10  
    11  	"github.com/BlockABC/godash/database"
    12  	"github.com/BlockABC/godash/txscript"
    13  	"github.com/BlockABC/godash/wire"
    14  	"github.com/BlockABC/godashutil"
    15  )
    16  
    17  // utxoOutput houses details about an individual unspent transaction output such
    18  // as whether or not it is spent, its public key script, and how much it pays.
    19  //
    20  // Standard public key scripts are stored in the database using a compressed
    21  // format. Since the vast majority of scripts are of the standard form, a fairly
    22  // significant savings is achieved by discarding the portions of the standard
    23  // scripts that can be reconstructed.
    24  //
    25  // Also, since it is common for only a specific output in a given utxo entry to
    26  // be referenced from a redeeming transaction, the script and amount for a given
    27  // output is not uncompressed until the first time it is accessed.  This
    28  // provides a mechanism to avoid the overhead of needlessly uncompressing all
    29  // outputs for a given utxo entry at the time of load.
    30  type utxoOutput struct {
    31  	spent      bool   // Output is spent.
    32  	compressed bool   // The amount and public key script are compressed.
    33  	amount     int64  // The amount of the output.
    34  	pkScript   []byte // The public key script for the output.
    35  }
    36  
    37  // maybeDecompress decompresses the amount and public key script fields of the
    38  // utxo and marks it decompressed if needed.
    39  func (o *utxoOutput) maybeDecompress(version int32) {
    40  	// Nothing to do if it's not compressed.
    41  	if !o.compressed {
    42  		return
    43  	}
    44  
    45  	o.amount = int64(decompressTxOutAmount(uint64(o.amount)))
    46  	o.pkScript = decompressScript(o.pkScript, version)
    47  	o.compressed = false
    48  }
    49  
    50  // UtxoEntry contains contextual information about an unspent transaction such
    51  // as whether or not it is a coinbase transaction, which block it was found in,
    52  // and the spent status of its outputs.
    53  type UtxoEntry struct {
    54  	modified      bool                   // Entry changed since load.
    55  	version       int32                  // The version of this tx.
    56  	isCoinBase    bool                   // Whether entry is a coinbase tx.
    57  	blockHeight   int32                  // Height of block containing tx.
    58  	sparseOutputs map[uint32]*utxoOutput // Sparse map of unspent outputs.
    59  }
    60  
    61  // Version returns the version of the transaction the utxo represents.
    62  func (entry *UtxoEntry) Version() int32 {
    63  	return entry.version
    64  }
    65  
    66  // IsCoinBase returns whether or not the transaction the utxo entry represents
    67  // is a coinbase.
    68  func (entry *UtxoEntry) IsCoinBase() bool {
    69  	return entry.isCoinBase
    70  }
    71  
    72  // BlockHeight returns the height of the block containing the transaction the
    73  // utxo entry represents.
    74  func (entry *UtxoEntry) BlockHeight() int32 {
    75  	return entry.blockHeight
    76  }
    77  
    78  // IsOutputSpent returns whether or not the provided output index has been
    79  // spent based upon the current state of the unspent transaction output view
    80  // the entry was obtained from.
    81  //
    82  // Returns true if the output index references an output that does not exist
    83  // either due to it being invalid or because the output is not part of the view
    84  // due to previously being spent/pruned.
    85  func (entry *UtxoEntry) IsOutputSpent(outputIndex uint32) bool {
    86  	output, ok := entry.sparseOutputs[outputIndex]
    87  	if !ok {
    88  		return true
    89  	}
    90  
    91  	return output.spent
    92  }
    93  
    94  // SpendOutput marks the output at the provided index as spent.  Specifying an
    95  // output index that does not exist will not have any effect.
    96  func (entry *UtxoEntry) SpendOutput(outputIndex uint32) {
    97  	output, ok := entry.sparseOutputs[outputIndex]
    98  	if !ok {
    99  		return
   100  	}
   101  
   102  	// Nothing to do if the output is already spent.
   103  	if output.spent {
   104  		return
   105  	}
   106  
   107  	entry.modified = true
   108  	output.spent = true
   109  	return
   110  }
   111  
   112  // IsFullySpent returns whether or not the transaction the utxo entry represents
   113  // is fully spent.
   114  func (entry *UtxoEntry) IsFullySpent() bool {
   115  	// The entry is not fully spent if any of the outputs are unspent.
   116  	for _, output := range entry.sparseOutputs {
   117  		if !output.spent {
   118  			return false
   119  		}
   120  	}
   121  
   122  	return true
   123  }
   124  
   125  // AmountByIndex returns the amount of the provided output index.
   126  //
   127  // Returns 0 if the output index references an output that does not exist
   128  // either due to it being invalid or because the output is not part of the view
   129  // due to previously being spent/pruned.
   130  func (entry *UtxoEntry) AmountByIndex(outputIndex uint32) int64 {
   131  	output, ok := entry.sparseOutputs[outputIndex]
   132  	if !ok {
   133  		return 0
   134  	}
   135  
   136  	// Ensure the output is decompressed before returning the amount.
   137  	output.maybeDecompress(entry.version)
   138  	return output.amount
   139  }
   140  
   141  // PkScriptByIndex returns the public key script for the provided output index.
   142  //
   143  // Returns nil if the output index references an output that does not exist
   144  // either due to it being invalid or because the output is not part of the view
   145  // due to previously being spent/pruned.
   146  func (entry *UtxoEntry) PkScriptByIndex(outputIndex uint32) []byte {
   147  	output, ok := entry.sparseOutputs[outputIndex]
   148  	if !ok {
   149  		return nil
   150  	}
   151  
   152  	// Ensure the output is decompressed before returning the script.
   153  	output.maybeDecompress(entry.version)
   154  	return output.pkScript
   155  }
   156  
   157  // newUtxoEntry returns a new unspent transaction output entry with the provided
   158  // coinbase flag and block height ready to have unspent outputs added.
   159  func newUtxoEntry(version int32, isCoinBase bool, blockHeight int32) *UtxoEntry {
   160  	return &UtxoEntry{
   161  		version:       version,
   162  		isCoinBase:    isCoinBase,
   163  		blockHeight:   blockHeight,
   164  		sparseOutputs: make(map[uint32]*utxoOutput),
   165  	}
   166  }
   167  
   168  // UtxoViewpoint represents a view into the set of unspent transaction outputs
   169  // from a specific point of view in the chain.  For example, it could be for
   170  // the end of the main chain, some point in the history of the main chain, or
   171  // down a side chain.
   172  //
   173  // The unspent outputs are needed by other transactions for things such as
   174  // script validation and double spend prevention.
   175  type UtxoViewpoint struct {
   176  	entries  map[wire.ShaHash]*UtxoEntry
   177  	bestHash wire.ShaHash
   178  }
   179  
   180  // BestHash returns the hash of the best block in the chain the view currently
   181  // respresents.
   182  func (view *UtxoViewpoint) BestHash() *wire.ShaHash {
   183  	return &view.bestHash
   184  }
   185  
   186  // SetBestHash sets the hash of the best block in the chain the view currently
   187  // respresents.
   188  func (view *UtxoViewpoint) SetBestHash(hash *wire.ShaHash) {
   189  	view.bestHash = *hash
   190  }
   191  
   192  // LookupEntry returns information about a given transaction according to the
   193  // current state of the view.  It will return nil if the passed transaction
   194  // hash does not exist in the view or is otherwise not available such as when
   195  // it has been disconnected during a reorg.
   196  func (view *UtxoViewpoint) LookupEntry(txHash *wire.ShaHash) *UtxoEntry {
   197  	entry, ok := view.entries[*txHash]
   198  	if !ok {
   199  		return nil
   200  	}
   201  
   202  	return entry
   203  }
   204  
   205  // AddTxOuts adds all outputs in the passed transaction which are not provably
   206  // unspendable to the view.  When the view already has entries for any of the
   207  // outputs, they are simply marked unspent.  All fields will be updated for
   208  // existing entries since it's possible it has changed during a reorg.
   209  func (view *UtxoViewpoint) AddTxOuts(tx *godashutil.Tx, blockHeight int32) {
   210  	// When there are not already any utxos associated with the transaction,
   211  	// add a new entry for it to the view.
   212  	entry := view.LookupEntry(tx.Sha())
   213  	if entry == nil {
   214  		entry = newUtxoEntry(tx.MsgTx().Version, IsCoinBase(tx),
   215  			blockHeight)
   216  		view.entries[*tx.Sha()] = entry
   217  	} else {
   218  		entry.blockHeight = blockHeight
   219  	}
   220  	entry.modified = true
   221  
   222  	// Loop all of the transaction outputs and add those which are not
   223  	// provably unspendable.
   224  	for txOutIdx, txOut := range tx.MsgTx().TxOut {
   225  		if txscript.IsUnspendable(txOut.PkScript) {
   226  			continue
   227  		}
   228  
   229  		// Update existing entries.  All fields are updated because it's
   230  		// possible (although extremely unlikely) that the existing
   231  		// entry is being replaced by a different transaction with the
   232  		// same hash.  This is allowed so long as the previous
   233  		// transaction is fully spent.
   234  		if output, ok := entry.sparseOutputs[uint32(txOutIdx)]; ok {
   235  			output.spent = false
   236  			output.compressed = false
   237  			output.amount = txOut.Value
   238  			output.pkScript = txOut.PkScript
   239  			continue
   240  		}
   241  
   242  		// Add the unspent transaction output.
   243  		entry.sparseOutputs[uint32(txOutIdx)] = &utxoOutput{
   244  			spent:      false,
   245  			compressed: false,
   246  			amount:     txOut.Value,
   247  			pkScript:   txOut.PkScript,
   248  		}
   249  	}
   250  	return
   251  }
   252  
   253  // connectTransaction updates the view by adding all new utxos created by the
   254  // passed transaction and marking all utxos that the transactions spend as
   255  // spent.  In addition, when the 'stxos' argument is not nil, it will be updated
   256  // to append an entry for each spent txout.  An error will be returned if the
   257  // view does not contain the required utxos.
   258  func (view *UtxoViewpoint) connectTransaction(tx *godashutil.Tx, blockHeight int32, stxos *[]spentTxOut) error {
   259  	// Coinbase transactions don't have any inputs to spend.
   260  	if IsCoinBase(tx) {
   261  		// Add the transaction's outputs as available utxos.
   262  		view.AddTxOuts(tx, blockHeight)
   263  		return nil
   264  	}
   265  
   266  	// Spend the referenced utxos by marking them spent in the view and,
   267  	// if a slice was provided for the spent txout details, append an entry
   268  	// to it.
   269  	for _, txIn := range tx.MsgTx().TxIn {
   270  		originIndex := txIn.PreviousOutPoint.Index
   271  		entry := view.entries[txIn.PreviousOutPoint.Hash]
   272  
   273  		// Ensure the referenced utxo exists in the view.  This should
   274  		// never happen unless there is a bug is introduced in the code.
   275  		if entry == nil {
   276  			return AssertError(fmt.Sprintf("view missing input %v",
   277  				txIn.PreviousOutPoint))
   278  		}
   279  		entry.SpendOutput(originIndex)
   280  
   281  		// Don't create the stxo details if not requested.
   282  		if stxos == nil {
   283  			continue
   284  		}
   285  
   286  		// Populate the stxo details using the utxo entry.  When the
   287  		// transaction is fully spent, set the additional stxo fields
   288  		// accordingly since those details will no longer be available
   289  		// in the utxo set.
   290  		var stxo = spentTxOut{
   291  			compressed: false,
   292  			version:    entry.Version(),
   293  			amount:     entry.AmountByIndex(originIndex),
   294  			pkScript:   entry.PkScriptByIndex(originIndex),
   295  		}
   296  		if entry.IsFullySpent() {
   297  			stxo.height = entry.BlockHeight()
   298  			stxo.isCoinBase = entry.IsCoinBase()
   299  		}
   300  
   301  		// Append the entry to the provided spent txouts slice.
   302  		*stxos = append(*stxos, stxo)
   303  	}
   304  
   305  	// Add the transaction's outputs as available utxos.
   306  	view.AddTxOuts(tx, blockHeight)
   307  	return nil
   308  }
   309  
   310  // connectTransactions updates the view by adding all new utxos created by all
   311  // of the transactions in the passed block, marking all utxos the transactions
   312  // spend as spent, and setting the best hash for the view to the passed block.
   313  // In addition, when the 'stxos' argument is not nil, it will be updated to
   314  // append an entry for each spent txout.
   315  func (view *UtxoViewpoint) connectTransactions(block *godashutil.Block, stxos *[]spentTxOut) error {
   316  	for _, tx := range block.Transactions() {
   317  		err := view.connectTransaction(tx, block.Height(), stxos)
   318  		if err != nil {
   319  			return err
   320  		}
   321  	}
   322  
   323  	// Update the best hash for view to include this block since all of its
   324  	// transactions have been connected.
   325  	view.SetBestHash(block.Sha())
   326  	return nil
   327  }
   328  
   329  // disconnectTransactions updates the view by removing all of the transactions
   330  // created by the passed block, restoring all utxos the transactions spent by
   331  // using the provided spent txo information, and setting the best hash for the
   332  // view to the block before the passed block.
   333  func (view *UtxoViewpoint) disconnectTransactions(block *godashutil.Block, stxos []spentTxOut) error {
   334  	// Sanity check the correct number of stxos are provided.
   335  	if len(stxos) != countSpentOutputs(block) {
   336  		return AssertError("disconnectTransactions called with bad " +
   337  			"spent transaction out information")
   338  	}
   339  
   340  	// Loop backwards through all transactions so everything is unspent in
   341  	// reverse order.  This is necessary since transactions later in a block
   342  	// can spend from previous ones.
   343  	stxoIdx := len(stxos) - 1
   344  	transactions := block.Transactions()
   345  	for txIdx := len(transactions) - 1; txIdx > -1; txIdx-- {
   346  		tx := transactions[txIdx]
   347  
   348  		// Clear this transaction from the view if it already exists or
   349  		// create a new empty entry for when it does not.  This is done
   350  		// because the code relies on its existence in the view in order
   351  		// to signal modifications have happened.
   352  		isCoinbase := txIdx == 0
   353  		entry := view.entries[*tx.Sha()]
   354  		if entry == nil {
   355  			entry = newUtxoEntry(tx.MsgTx().Version, isCoinbase,
   356  				block.Height())
   357  			view.entries[*tx.Sha()] = entry
   358  		}
   359  		entry.modified = true
   360  		entry.sparseOutputs = make(map[uint32]*utxoOutput)
   361  
   362  		// Loop backwards through all of the transaction inputs (except
   363  		// for the coinbase which has no inputs) and unspend the
   364  		// referenced txos.  This is necessary to match the order of the
   365  		// spent txout entries.
   366  		if isCoinbase {
   367  			continue
   368  		}
   369  		for txInIdx := len(tx.MsgTx().TxIn) - 1; txInIdx > -1; txInIdx-- {
   370  			// Ensure the spent txout index is decremented to stay
   371  			// in sync with the transaction input.
   372  			stxo := &stxos[stxoIdx]
   373  			stxoIdx--
   374  
   375  			// When there is not already an entry for the referenced
   376  			// transaction in the view, it means it was fully spent,
   377  			// so create a new utxo entry in order to resurrect it.
   378  			txIn := tx.MsgTx().TxIn[txInIdx]
   379  			originHash := &txIn.PreviousOutPoint.Hash
   380  			originIndex := txIn.PreviousOutPoint.Index
   381  			entry := view.entries[*originHash]
   382  			if entry == nil {
   383  				entry = newUtxoEntry(stxo.version,
   384  					stxo.isCoinBase, stxo.height)
   385  				view.entries[*originHash] = entry
   386  			}
   387  
   388  			// Mark the entry as modified since it is either new
   389  			// or will be changed below.
   390  			entry.modified = true
   391  
   392  			// Restore the specific utxo using the stxo data from
   393  			// the spend journal if it doesn't already exist in the
   394  			// view.
   395  			output, ok := entry.sparseOutputs[originIndex]
   396  			if !ok {
   397  				// Add the unspent transaction output.
   398  				entry.sparseOutputs[originIndex] = &utxoOutput{
   399  					spent:      false,
   400  					compressed: stxo.compressed,
   401  					amount:     stxo.amount,
   402  					pkScript:   stxo.pkScript,
   403  				}
   404  				continue
   405  			}
   406  
   407  			// Mark the existing referenced transaction output as
   408  			// unspent.
   409  			output.spent = false
   410  		}
   411  	}
   412  
   413  	// Update the best hash for view to the previous block since all of the
   414  	// transactions for the current block have been disconnected.
   415  	view.SetBestHash(&block.MsgBlock().Header.PrevBlock)
   416  	return nil
   417  }
   418  
   419  // Entries returns the underlying map that stores of all the utxo entries.
   420  func (view *UtxoViewpoint) Entries() map[wire.ShaHash]*UtxoEntry {
   421  	return view.entries
   422  }
   423  
   424  // commit prunes all entries marked modified that are now fully spent and marks
   425  // all entries as unmodified.
   426  func (view *UtxoViewpoint) commit() {
   427  	for txHash, entry := range view.entries {
   428  		if entry == nil || (entry.modified && entry.IsFullySpent()) {
   429  			delete(view.entries, txHash)
   430  			continue
   431  		}
   432  
   433  		entry.modified = false
   434  	}
   435  }
   436  
   437  // fetchUtxosMain fetches unspent transaction output data about the provided
   438  // set of transactions from the point of view of the end of the main chain at
   439  // the time of the call.
   440  //
   441  // Upon completion of this function, the view will contain an entry for each
   442  // requested transaction.  Fully spent transactions, or those which otherwise
   443  // don't exist, will result in a nil entry in the view.
   444  func (view *UtxoViewpoint) fetchUtxosMain(db database.DB, txSet map[wire.ShaHash]struct{}) error {
   445  	// Nothing to do if there are no requested hashes.
   446  	if len(txSet) == 0 {
   447  		return nil
   448  	}
   449  
   450  	// Load the unspent transaction output information for the requested set
   451  	// of transactions from the point of view of the end of the main chain.
   452  	//
   453  	// NOTE: Missing entries are not considered an error here and instead
   454  	// will result in nil entries in the view.  This is intentionally done
   455  	// since other code uses the presence of an entry in the store as a way
   456  	// to optimize spend and unspend updates to apply only to the specific
   457  	// utxos that the caller needs access to.
   458  	return db.View(func(dbTx database.Tx) error {
   459  		for hash := range txSet {
   460  			hashCopy := hash
   461  			entry, err := dbFetchUtxoEntry(dbTx, &hashCopy)
   462  			if err != nil {
   463  				return err
   464  			}
   465  
   466  			view.entries[hash] = entry
   467  		}
   468  
   469  		return nil
   470  	})
   471  }
   472  
   473  // fetchUtxos loads utxo details about provided set of transaction hashes into
   474  // the view from the database as needed unless they already exist in the view in
   475  // which case they are ignored.
   476  func (view *UtxoViewpoint) fetchUtxos(db database.DB, txSet map[wire.ShaHash]struct{}) error {
   477  	// Nothing to do if there are no requested hashes.
   478  	if len(txSet) == 0 {
   479  		return nil
   480  	}
   481  
   482  	// Filter entries that are already in the view.
   483  	txNeededSet := make(map[wire.ShaHash]struct{})
   484  	for hash := range txSet {
   485  		// Already loaded into the current view.
   486  		if _, ok := view.entries[hash]; ok {
   487  			continue
   488  		}
   489  
   490  		txNeededSet[hash] = struct{}{}
   491  	}
   492  
   493  	// Request the input utxos from the database.
   494  	return view.fetchUtxosMain(db, txNeededSet)
   495  }
   496  
   497  // fetchInputUtxos loads utxo details about the input transactions referenced
   498  // by the transactions in the given block into the view from the database as
   499  // needed.  In particular, referenced entries that are earlier in the block are
   500  // added to the view and entries that are already in the view are not modified.
   501  func (view *UtxoViewpoint) fetchInputUtxos(db database.DB, block *godashutil.Block) error {
   502  	// Build a map of in-flight transactions because some of the inputs in
   503  	// this block could be referencing other transactions earlier in this
   504  	// block which are not yet in the chain.
   505  	txInFlight := map[wire.ShaHash]int{}
   506  	transactions := block.Transactions()
   507  	for i, tx := range transactions {
   508  		txInFlight[*tx.Sha()] = i
   509  	}
   510  
   511  	// Loop through all of the transaction inputs (except for the coinbase
   512  	// which has no inputs) collecting them into sets of what is needed and
   513  	// what is already known (in-flight).
   514  	txNeededSet := make(map[wire.ShaHash]struct{})
   515  	for i, tx := range transactions[1:] {
   516  		for _, txIn := range tx.MsgTx().TxIn {
   517  			// It is acceptable for a transaction input to reference
   518  			// the output of another transaction in this block only
   519  			// if the referenced transaction comes before the
   520  			// current one in this block.  Add the outputs of the
   521  			// referenced transaction as available utxos when this
   522  			// is the case.  Otherwise, the utxo details are still
   523  			// needed.
   524  			//
   525  			// NOTE: The >= is correct here because i is one less
   526  			// than the actual position of the transaction within
   527  			// the block due to skipping the coinbase.
   528  			originHash := &txIn.PreviousOutPoint.Hash
   529  			if inFlightIndex, ok := txInFlight[*originHash]; ok &&
   530  				i >= inFlightIndex {
   531  
   532  				originTx := transactions[inFlightIndex]
   533  				view.AddTxOuts(originTx, block.Height())
   534  				continue
   535  			}
   536  
   537  			// Don't request entries that are already in the view
   538  			// from the database.
   539  			if _, ok := view.entries[*originHash]; ok {
   540  				continue
   541  			}
   542  
   543  			txNeededSet[*originHash] = struct{}{}
   544  		}
   545  	}
   546  
   547  	// Request the input utxos from the database.
   548  	return view.fetchUtxosMain(db, txNeededSet)
   549  }
   550  
   551  // NewUtxoViewpoint returns a new empty unspent transaction output view.
   552  func NewUtxoViewpoint() *UtxoViewpoint {
   553  	return &UtxoViewpoint{
   554  		entries: make(map[wire.ShaHash]*UtxoEntry),
   555  	}
   556  }
   557  
   558  // FetchUtxoView loads utxo details about the input transactions referenced by
   559  // the passed transaction from the point of view of the end of the main chain.
   560  // It also attempts to fetch the utxo details for the transaction itself so the
   561  // returned view can be examined for duplicate unspent transaction outputs.
   562  //
   563  // This function is safe for concurrent access however the returned view is NOT.
   564  func (b *BlockChain) FetchUtxoView(tx *godashutil.Tx) (*UtxoViewpoint, error) {
   565  	b.chainLock.RLock()
   566  	defer b.chainLock.RUnlock()
   567  
   568  	// Create a set of needed transactions based on those referenced by the
   569  	// inputs of the passed transaction.  Also, add the passed transaction
   570  	// itself as a way for the caller to detect duplicates that are not
   571  	// fully spent.
   572  	txNeededSet := make(map[wire.ShaHash]struct{})
   573  	txNeededSet[*tx.Sha()] = struct{}{}
   574  	if !IsCoinBase(tx) {
   575  		for _, txIn := range tx.MsgTx().TxIn {
   576  			txNeededSet[txIn.PreviousOutPoint.Hash] = struct{}{}
   577  		}
   578  	}
   579  
   580  	// Request the utxos from the point of view of the end of the main
   581  	// chain.
   582  	view := NewUtxoViewpoint()
   583  	err := view.fetchUtxosMain(b.db, txNeededSet)
   584  	return view, err
   585  }
   586  
   587  // FetchUtxoEntry loads and returns the unspent transaction output entry for the
   588  // passed hash from the point of view of the end of the main chain.
   589  //
   590  // NOTE: Requesting a hash for which there is no data will NOT return an error.
   591  // Instead both the entry and the error will be nil.  This is done to allow
   592  // pruning of fully spent transactions.  In practice this means the caller must
   593  // check if the returned entry is nil before invoking methods on it.
   594  //
   595  // This function is safe for concurrent access however the returned entry (if
   596  // any) is NOT.
   597  func (b *BlockChain) FetchUtxoEntry(txHash *wire.ShaHash) (*UtxoEntry, error) {
   598  	b.chainLock.RLock()
   599  	defer b.chainLock.RUnlock()
   600  
   601  	var entry *UtxoEntry
   602  	err := b.db.View(func(dbTx database.Tx) error {
   603  		var err error
   604  		entry, err = dbFetchUtxoEntry(dbTx, txHash)
   605  		return err
   606  	})
   607  	if err != nil {
   608  		return nil, err
   609  	}
   610  
   611  	return entry, nil
   612  }