// 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 } // 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 }