15bace88dc
This adds a basic test harness infrastructure for the mempool package which aims to make writing tests for it much easier. The harness provides functionality for creating and signing transactions as well as a fake chain that provides utxos for use in generating valid transactions and allows an arbitrary chain height to be set. In order to simplify transaction creation, a single signing key and payment address is used throughout and a convenience type for spendable outputs is provided. The harness is initialized with a spendable coinbase output by default and the fake chain height set to the maturity height needed to ensure the provided output is in fact spendable as well as a policy that is suitable for testing. Since tests are in the same package and each harness provides a unique pool and fake chain instance, the tests can safely reach into the pool policy, or any other state, and change it for a given harness without affecting the others. In order to be able to make use of the existing blockchain.Viewpoint type, a Clone method has been to the UtxoEntry type which allows the fake chain instance to keep a single view with the actual available unspent utxos while the mempool ends up fetching a subset of the view with the specifically requested entries cloned. To demo the harness, this also contains a couple of tests which make use of it: - TestSimpleOrphanChain -- Ensures an entire chain of orphans is properly accepted and connects up when the missing parent transaction is added - TestOrphanRejects -- Ensure orphans are actually rejected when the flag on ProcessTransactions is set to reject them
634 lines
22 KiB
Go
634 lines
22 KiB
Go
// Copyright (c) 2015-2016 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package blockchain
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/database"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcutil"
|
|
)
|
|
|
|
// utxoOutput houses details about an individual unspent transaction output such
|
|
// as whether or not it is spent, its public key script, and how much it pays.
|
|
//
|
|
// Standard public key scripts are stored in the database using a compressed
|
|
// format. Since the vast majority of scripts are of the standard form, a fairly
|
|
// significant savings is achieved by discarding the portions of the standard
|
|
// scripts that can be reconstructed.
|
|
//
|
|
// Also, since it is common for only a specific output in a given utxo entry to
|
|
// be referenced from a redeeming transaction, the script and amount for a given
|
|
// output is not uncompressed until the first time it is accessed. This
|
|
// provides a mechanism to avoid the overhead of needlessly uncompressing all
|
|
// outputs for a given utxo entry at the time of load.
|
|
type utxoOutput struct {
|
|
spent bool // Output is spent.
|
|
compressed bool // The amount and public key script are compressed.
|
|
amount int64 // The amount of the output.
|
|
pkScript []byte // The public key script for the output.
|
|
}
|
|
|
|
// maybeDecompress decompresses the amount and public key script fields of the
|
|
// utxo and marks it decompressed if needed.
|
|
func (o *utxoOutput) maybeDecompress(version int32) {
|
|
// Nothing to do if it's not compressed.
|
|
if !o.compressed {
|
|
return
|
|
}
|
|
|
|
o.amount = int64(decompressTxOutAmount(uint64(o.amount)))
|
|
o.pkScript = decompressScript(o.pkScript, version)
|
|
o.compressed = false
|
|
}
|
|
|
|
// UtxoEntry contains contextual information about an unspent transaction such
|
|
// as whether or not it is a coinbase transaction, which block it was found in,
|
|
// and the spent status of its outputs.
|
|
type UtxoEntry struct {
|
|
modified bool // Entry changed since load.
|
|
version int32 // The version of this tx.
|
|
isCoinBase bool // Whether entry is a coinbase tx.
|
|
blockHeight int32 // Height of block containing tx.
|
|
sparseOutputs map[uint32]*utxoOutput // Sparse map of unspent outputs.
|
|
}
|
|
|
|
// Version returns the version of the transaction the utxo represents.
|
|
func (entry *UtxoEntry) Version() int32 {
|
|
return entry.version
|
|
}
|
|
|
|
// IsCoinBase returns whether or not the transaction the utxo entry represents
|
|
// is a coinbase.
|
|
func (entry *UtxoEntry) IsCoinBase() bool {
|
|
return entry.isCoinBase
|
|
}
|
|
|
|
// BlockHeight returns the height of the block containing the transaction the
|
|
// utxo entry represents.
|
|
func (entry *UtxoEntry) BlockHeight() int32 {
|
|
return entry.blockHeight
|
|
}
|
|
|
|
// IsOutputSpent returns whether or not the provided output index has been
|
|
// spent based upon the current state of the unspent transaction output view
|
|
// the entry was obtained from.
|
|
//
|
|
// Returns true if the output index references an output that does not exist
|
|
// either due to it being invalid or because the output is not part of the view
|
|
// due to previously being spent/pruned.
|
|
func (entry *UtxoEntry) IsOutputSpent(outputIndex uint32) bool {
|
|
output, ok := entry.sparseOutputs[outputIndex]
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
return output.spent
|
|
}
|
|
|
|
// SpendOutput marks the output at the provided index as spent. Specifying an
|
|
// output index that does not exist will not have any effect.
|
|
func (entry *UtxoEntry) SpendOutput(outputIndex uint32) {
|
|
output, ok := entry.sparseOutputs[outputIndex]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Nothing to do if the output is already spent.
|
|
if output.spent {
|
|
return
|
|
}
|
|
|
|
entry.modified = true
|
|
output.spent = true
|
|
return
|
|
}
|
|
|
|
// IsFullySpent returns whether or not the transaction the utxo entry represents
|
|
// is fully spent.
|
|
func (entry *UtxoEntry) IsFullySpent() bool {
|
|
// The entry is not fully spent if any of the outputs are unspent.
|
|
for _, output := range entry.sparseOutputs {
|
|
if !output.spent {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// AmountByIndex returns the amount of the provided output index.
|
|
//
|
|
// Returns 0 if the output index references an output that does not exist
|
|
// either due to it being invalid or because the output is not part of the view
|
|
// due to previously being spent/pruned.
|
|
func (entry *UtxoEntry) AmountByIndex(outputIndex uint32) int64 {
|
|
output, ok := entry.sparseOutputs[outputIndex]
|
|
if !ok {
|
|
return 0
|
|
}
|
|
|
|
// Ensure the output is decompressed before returning the amount.
|
|
output.maybeDecompress(entry.version)
|
|
return output.amount
|
|
}
|
|
|
|
// PkScriptByIndex returns the public key script for the provided output index.
|
|
//
|
|
// Returns nil if the output index references an output that does not exist
|
|
// either due to it being invalid or because the output is not part of the view
|
|
// due to previously being spent/pruned.
|
|
func (entry *UtxoEntry) PkScriptByIndex(outputIndex uint32) []byte {
|
|
output, ok := entry.sparseOutputs[outputIndex]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
// Ensure the output is decompressed before returning the script.
|
|
output.maybeDecompress(entry.version)
|
|
return output.pkScript
|
|
}
|
|
|
|
// Clone returns a deep copy of the utxo entry.
|
|
func (entry *UtxoEntry) Clone() *UtxoEntry {
|
|
if entry == nil {
|
|
return nil
|
|
}
|
|
|
|
newEntry := &UtxoEntry{
|
|
version: entry.version,
|
|
isCoinBase: entry.isCoinBase,
|
|
blockHeight: entry.blockHeight,
|
|
sparseOutputs: make(map[uint32]*utxoOutput),
|
|
}
|
|
for outputIndex, output := range entry.sparseOutputs {
|
|
newEntry.sparseOutputs[outputIndex] = &utxoOutput{
|
|
spent: output.spent,
|
|
compressed: output.compressed,
|
|
amount: output.amount,
|
|
pkScript: output.pkScript,
|
|
}
|
|
}
|
|
return newEntry
|
|
}
|
|
|
|
// newUtxoEntry returns a new unspent transaction output entry with the provided
|
|
// coinbase flag and block height ready to have unspent outputs added.
|
|
func newUtxoEntry(version int32, isCoinBase bool, blockHeight int32) *UtxoEntry {
|
|
return &UtxoEntry{
|
|
version: version,
|
|
isCoinBase: isCoinBase,
|
|
blockHeight: blockHeight,
|
|
sparseOutputs: make(map[uint32]*utxoOutput),
|
|
}
|
|
}
|
|
|
|
// UtxoViewpoint represents a view into the set of unspent transaction outputs
|
|
// from a specific point of view in the chain. For example, it could be for
|
|
// the end of the main chain, some point in the history of the main chain, or
|
|
// down a side chain.
|
|
//
|
|
// The unspent outputs are needed by other transactions for things such as
|
|
// script validation and double spend prevention.
|
|
type UtxoViewpoint struct {
|
|
entries map[chainhash.Hash]*UtxoEntry
|
|
bestHash chainhash.Hash
|
|
}
|
|
|
|
// BestHash returns the hash of the best block in the chain the view currently
|
|
// respresents.
|
|
func (view *UtxoViewpoint) BestHash() *chainhash.Hash {
|
|
return &view.bestHash
|
|
}
|
|
|
|
// SetBestHash sets the hash of the best block in the chain the view currently
|
|
// respresents.
|
|
func (view *UtxoViewpoint) SetBestHash(hash *chainhash.Hash) {
|
|
view.bestHash = *hash
|
|
}
|
|
|
|
// LookupEntry returns information about a given transaction according to the
|
|
// current state of the view. It will return nil if the passed transaction
|
|
// hash does not exist in the view or is otherwise not available such as when
|
|
// it has been disconnected during a reorg.
|
|
func (view *UtxoViewpoint) LookupEntry(txHash *chainhash.Hash) *UtxoEntry {
|
|
entry, ok := view.entries[*txHash]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return entry
|
|
}
|
|
|
|
// AddTxOuts adds all outputs in the passed transaction which are not provably
|
|
// unspendable to the view. When the view already has entries for any of the
|
|
// outputs, they are simply marked unspent. All fields will be updated for
|
|
// existing entries since it's possible it has changed during a reorg.
|
|
func (view *UtxoViewpoint) AddTxOuts(tx *btcutil.Tx, blockHeight int32) {
|
|
// When there are not already any utxos associated with the transaction,
|
|
// add a new entry for it to the view.
|
|
entry := view.LookupEntry(tx.Hash())
|
|
if entry == nil {
|
|
entry = newUtxoEntry(tx.MsgTx().Version, IsCoinBase(tx),
|
|
blockHeight)
|
|
view.entries[*tx.Hash()] = entry
|
|
} else {
|
|
entry.blockHeight = blockHeight
|
|
}
|
|
entry.modified = true
|
|
|
|
// Loop all of the transaction outputs and add those which are not
|
|
// provably unspendable.
|
|
for txOutIdx, txOut := range tx.MsgTx().TxOut {
|
|
if txscript.IsUnspendable(txOut.PkScript) {
|
|
continue
|
|
}
|
|
|
|
// Update existing entries. All fields are updated because it's
|
|
// possible (although extremely unlikely) that the existing
|
|
// entry is being replaced by a different transaction with the
|
|
// same hash. This is allowed so long as the previous
|
|
// transaction is fully spent.
|
|
if output, ok := entry.sparseOutputs[uint32(txOutIdx)]; ok {
|
|
output.spent = false
|
|
output.compressed = false
|
|
output.amount = txOut.Value
|
|
output.pkScript = txOut.PkScript
|
|
continue
|
|
}
|
|
|
|
// Add the unspent transaction output.
|
|
entry.sparseOutputs[uint32(txOutIdx)] = &utxoOutput{
|
|
spent: false,
|
|
compressed: false,
|
|
amount: txOut.Value,
|
|
pkScript: txOut.PkScript,
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// connectTransaction updates the view by adding all new utxos created by the
|
|
// passed transaction and marking all utxos that the transactions spend as
|
|
// spent. In addition, when the 'stxos' argument is not nil, it will be updated
|
|
// to append an entry for each spent txout. An error will be returned if the
|
|
// view does not contain the required utxos.
|
|
func (view *UtxoViewpoint) connectTransaction(tx *btcutil.Tx, blockHeight int32, stxos *[]spentTxOut) error {
|
|
// Coinbase transactions don't have any inputs to spend.
|
|
if IsCoinBase(tx) {
|
|
// Add the transaction's outputs as available utxos.
|
|
view.AddTxOuts(tx, blockHeight)
|
|
return nil
|
|
}
|
|
|
|
// Spend the referenced utxos by marking them spent in the view and,
|
|
// if a slice was provided for the spent txout details, append an entry
|
|
// to it.
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
originIndex := txIn.PreviousOutPoint.Index
|
|
entry := view.entries[txIn.PreviousOutPoint.Hash]
|
|
|
|
// Ensure the referenced utxo exists in the view. This should
|
|
// never happen unless there is a bug is introduced in the code.
|
|
if entry == nil {
|
|
return AssertError(fmt.Sprintf("view missing input %v",
|
|
txIn.PreviousOutPoint))
|
|
}
|
|
entry.SpendOutput(originIndex)
|
|
|
|
// Don't create the stxo details if not requested.
|
|
if stxos == nil {
|
|
continue
|
|
}
|
|
|
|
// Populate the stxo details using the utxo entry. When the
|
|
// transaction is fully spent, set the additional stxo fields
|
|
// accordingly since those details will no longer be available
|
|
// in the utxo set.
|
|
var stxo = spentTxOut{
|
|
compressed: false,
|
|
version: entry.Version(),
|
|
amount: entry.AmountByIndex(originIndex),
|
|
pkScript: entry.PkScriptByIndex(originIndex),
|
|
}
|
|
if entry.IsFullySpent() {
|
|
stxo.height = entry.BlockHeight()
|
|
stxo.isCoinBase = entry.IsCoinBase()
|
|
}
|
|
|
|
// Append the entry to the provided spent txouts slice.
|
|
*stxos = append(*stxos, stxo)
|
|
}
|
|
|
|
// Add the transaction's outputs as available utxos.
|
|
view.AddTxOuts(tx, blockHeight)
|
|
return nil
|
|
}
|
|
|
|
// connectTransactions updates the view by adding all new utxos created by all
|
|
// of the transactions in the passed block, marking all utxos the transactions
|
|
// spend as spent, and setting the best hash for the view to the passed block.
|
|
// In addition, when the 'stxos' argument is not nil, it will be updated to
|
|
// append an entry for each spent txout.
|
|
func (view *UtxoViewpoint) connectTransactions(block *btcutil.Block, stxos *[]spentTxOut) error {
|
|
for _, tx := range block.Transactions() {
|
|
err := view.connectTransaction(tx, block.Height(), stxos)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Update the best hash for view to include this block since all of its
|
|
// transactions have been connected.
|
|
view.SetBestHash(block.Hash())
|
|
return nil
|
|
}
|
|
|
|
// disconnectTransactions updates the view by removing all of the transactions
|
|
// created by the passed block, restoring all utxos the transactions spent by
|
|
// using the provided spent txo information, and setting the best hash for the
|
|
// view to the block before the passed block.
|
|
func (view *UtxoViewpoint) disconnectTransactions(block *btcutil.Block, stxos []spentTxOut) error {
|
|
// Sanity check the correct number of stxos are provided.
|
|
if len(stxos) != countSpentOutputs(block) {
|
|
return AssertError("disconnectTransactions called with bad " +
|
|
"spent transaction out information")
|
|
}
|
|
|
|
// Loop backwards through all transactions so everything is unspent in
|
|
// reverse order. This is necessary since transactions later in a block
|
|
// can spend from previous ones.
|
|
stxoIdx := len(stxos) - 1
|
|
transactions := block.Transactions()
|
|
for txIdx := len(transactions) - 1; txIdx > -1; txIdx-- {
|
|
tx := transactions[txIdx]
|
|
|
|
// Clear this transaction from the view if it already exists or
|
|
// create a new empty entry for when it does not. This is done
|
|
// because the code relies on its existence in the view in order
|
|
// to signal modifications have happened.
|
|
isCoinbase := txIdx == 0
|
|
entry := view.entries[*tx.Hash()]
|
|
if entry == nil {
|
|
entry = newUtxoEntry(tx.MsgTx().Version, isCoinbase,
|
|
block.Height())
|
|
view.entries[*tx.Hash()] = entry
|
|
}
|
|
entry.modified = true
|
|
entry.sparseOutputs = make(map[uint32]*utxoOutput)
|
|
|
|
// Loop backwards through all of the transaction inputs (except
|
|
// for the coinbase which has no inputs) and unspend the
|
|
// referenced txos. This is necessary to match the order of the
|
|
// spent txout entries.
|
|
if isCoinbase {
|
|
continue
|
|
}
|
|
for txInIdx := len(tx.MsgTx().TxIn) - 1; txInIdx > -1; txInIdx-- {
|
|
// Ensure the spent txout index is decremented to stay
|
|
// in sync with the transaction input.
|
|
stxo := &stxos[stxoIdx]
|
|
stxoIdx--
|
|
|
|
// When there is not already an entry for the referenced
|
|
// transaction in the view, it means it was fully spent,
|
|
// so create a new utxo entry in order to resurrect it.
|
|
txIn := tx.MsgTx().TxIn[txInIdx]
|
|
originHash := &txIn.PreviousOutPoint.Hash
|
|
originIndex := txIn.PreviousOutPoint.Index
|
|
entry := view.entries[*originHash]
|
|
if entry == nil {
|
|
entry = newUtxoEntry(stxo.version,
|
|
stxo.isCoinBase, stxo.height)
|
|
view.entries[*originHash] = entry
|
|
}
|
|
|
|
// Mark the entry as modified since it is either new
|
|
// or will be changed below.
|
|
entry.modified = true
|
|
|
|
// Restore the specific utxo using the stxo data from
|
|
// the spend journal if it doesn't already exist in the
|
|
// view.
|
|
output, ok := entry.sparseOutputs[originIndex]
|
|
if !ok {
|
|
// Add the unspent transaction output.
|
|
entry.sparseOutputs[originIndex] = &utxoOutput{
|
|
spent: false,
|
|
compressed: stxo.compressed,
|
|
amount: stxo.amount,
|
|
pkScript: stxo.pkScript,
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Mark the existing referenced transaction output as
|
|
// unspent.
|
|
output.spent = false
|
|
}
|
|
}
|
|
|
|
// Update the best hash for view to the previous block since all of the
|
|
// transactions for the current block have been disconnected.
|
|
view.SetBestHash(&block.MsgBlock().Header.PrevBlock)
|
|
return nil
|
|
}
|
|
|
|
// Entries returns the underlying map that stores of all the utxo entries.
|
|
func (view *UtxoViewpoint) Entries() map[chainhash.Hash]*UtxoEntry {
|
|
return view.entries
|
|
}
|
|
|
|
// commit prunes all entries marked modified that are now fully spent and marks
|
|
// all entries as unmodified.
|
|
func (view *UtxoViewpoint) commit() {
|
|
for txHash, entry := range view.entries {
|
|
if entry == nil || (entry.modified && entry.IsFullySpent()) {
|
|
delete(view.entries, txHash)
|
|
continue
|
|
}
|
|
|
|
entry.modified = false
|
|
}
|
|
}
|
|
|
|
// fetchUtxosMain fetches unspent transaction output data about the provided
|
|
// set of transactions from the point of view of the end of the main chain at
|
|
// the time of the call.
|
|
//
|
|
// Upon completion of this function, the view will contain an entry for each
|
|
// requested transaction. Fully spent transactions, or those which otherwise
|
|
// don't exist, will result in a nil entry in the view.
|
|
func (view *UtxoViewpoint) fetchUtxosMain(db database.DB, txSet map[chainhash.Hash]struct{}) error {
|
|
// Nothing to do if there are no requested hashes.
|
|
if len(txSet) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Load the unspent transaction output information for the requested set
|
|
// of transactions from the point of view of the end of the main chain.
|
|
//
|
|
// NOTE: Missing entries are not considered an error here and instead
|
|
// will result in nil entries in the view. This is intentionally done
|
|
// since other code uses the presence of an entry in the store as a way
|
|
// to optimize spend and unspend updates to apply only to the specific
|
|
// utxos that the caller needs access to.
|
|
return db.View(func(dbTx database.Tx) error {
|
|
for hash := range txSet {
|
|
hashCopy := hash
|
|
entry, err := dbFetchUtxoEntry(dbTx, &hashCopy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
view.entries[hash] = entry
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// fetchUtxos loads utxo details about provided set of transaction hashes into
|
|
// the view from the database as needed unless they already exist in the view in
|
|
// which case they are ignored.
|
|
func (view *UtxoViewpoint) fetchUtxos(db database.DB, txSet map[chainhash.Hash]struct{}) error {
|
|
// Nothing to do if there are no requested hashes.
|
|
if len(txSet) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Filter entries that are already in the view.
|
|
txNeededSet := make(map[chainhash.Hash]struct{})
|
|
for hash := range txSet {
|
|
// Already loaded into the current view.
|
|
if _, ok := view.entries[hash]; ok {
|
|
continue
|
|
}
|
|
|
|
txNeededSet[hash] = struct{}{}
|
|
}
|
|
|
|
// Request the input utxos from the database.
|
|
return view.fetchUtxosMain(db, txNeededSet)
|
|
}
|
|
|
|
// fetchInputUtxos loads utxo details about the input transactions referenced
|
|
// by the transactions in the given block into the view from the database as
|
|
// needed. In particular, referenced entries that are earlier in the block are
|
|
// added to the view and entries that are already in the view are not modified.
|
|
func (view *UtxoViewpoint) fetchInputUtxos(db database.DB, block *btcutil.Block) error {
|
|
// Build a map of in-flight transactions because some of the inputs in
|
|
// this block could be referencing other transactions earlier in this
|
|
// block which are not yet in the chain.
|
|
txInFlight := map[chainhash.Hash]int{}
|
|
transactions := block.Transactions()
|
|
for i, tx := range transactions {
|
|
txInFlight[*tx.Hash()] = i
|
|
}
|
|
|
|
// Loop through all of the transaction inputs (except for the coinbase
|
|
// which has no inputs) collecting them into sets of what is needed and
|
|
// what is already known (in-flight).
|
|
txNeededSet := make(map[chainhash.Hash]struct{})
|
|
for i, tx := range transactions[1:] {
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
// It is acceptable for a transaction input to reference
|
|
// the output of another transaction in this block only
|
|
// if the referenced transaction comes before the
|
|
// current one in this block. Add the outputs of the
|
|
// referenced transaction as available utxos when this
|
|
// is the case. Otherwise, the utxo details are still
|
|
// needed.
|
|
//
|
|
// NOTE: The >= is correct here because i is one less
|
|
// than the actual position of the transaction within
|
|
// the block due to skipping the coinbase.
|
|
originHash := &txIn.PreviousOutPoint.Hash
|
|
if inFlightIndex, ok := txInFlight[*originHash]; ok &&
|
|
i >= inFlightIndex {
|
|
|
|
originTx := transactions[inFlightIndex]
|
|
view.AddTxOuts(originTx, block.Height())
|
|
continue
|
|
}
|
|
|
|
// Don't request entries that are already in the view
|
|
// from the database.
|
|
if _, ok := view.entries[*originHash]; ok {
|
|
continue
|
|
}
|
|
|
|
txNeededSet[*originHash] = struct{}{}
|
|
}
|
|
}
|
|
|
|
// Request the input utxos from the database.
|
|
return view.fetchUtxosMain(db, txNeededSet)
|
|
}
|
|
|
|
// NewUtxoViewpoint returns a new empty unspent transaction output view.
|
|
func NewUtxoViewpoint() *UtxoViewpoint {
|
|
return &UtxoViewpoint{
|
|
entries: make(map[chainhash.Hash]*UtxoEntry),
|
|
}
|
|
}
|
|
|
|
// FetchUtxoView loads utxo details about the input transactions referenced by
|
|
// the passed transaction from the point of view of the end of the main chain.
|
|
// It also attempts to fetch the utxo details for the transaction itself so the
|
|
// returned view can be examined for duplicate unspent transaction outputs.
|
|
//
|
|
// This function is safe for concurrent access however the returned view is NOT.
|
|
func (b *BlockChain) FetchUtxoView(tx *btcutil.Tx) (*UtxoViewpoint, error) {
|
|
b.chainLock.RLock()
|
|
defer b.chainLock.RUnlock()
|
|
|
|
// Create a set of needed transactions based on those referenced by the
|
|
// inputs of the passed transaction. Also, add the passed transaction
|
|
// itself as a way for the caller to detect duplicates that are not
|
|
// fully spent.
|
|
txNeededSet := make(map[chainhash.Hash]struct{})
|
|
txNeededSet[*tx.Hash()] = struct{}{}
|
|
if !IsCoinBase(tx) {
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
txNeededSet[txIn.PreviousOutPoint.Hash] = struct{}{}
|
|
}
|
|
}
|
|
|
|
// Request the utxos from the point of view of the end of the main
|
|
// chain.
|
|
view := NewUtxoViewpoint()
|
|
err := view.fetchUtxosMain(b.db, txNeededSet)
|
|
return view, err
|
|
}
|
|
|
|
// FetchUtxoEntry loads and returns the unspent transaction output entry for the
|
|
// passed hash from the point of view of the end of the main chain.
|
|
//
|
|
// NOTE: Requesting a hash for which there is no data will NOT return an error.
|
|
// Instead both the entry and the error will be nil. This is done to allow
|
|
// pruning of fully spent transactions. In practice this means the caller must
|
|
// check if the returned entry is nil before invoking methods on it.
|
|
//
|
|
// This function is safe for concurrent access however the returned entry (if
|
|
// any) is NOT.
|
|
func (b *BlockChain) FetchUtxoEntry(txHash *chainhash.Hash) (*UtxoEntry, error) {
|
|
b.chainLock.RLock()
|
|
defer b.chainLock.RUnlock()
|
|
|
|
var entry *UtxoEntry
|
|
err := b.db.View(func(dbTx database.Tx) error {
|
|
var err error
|
|
entry, err = dbFetchUtxoEntry(dbTx, txHash)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return entry, nil
|
|
}
|