github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/btcutil/blockchain/utxoviewpoint.go (about)

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