2013-07-18 16:49:28 +02:00
|
|
|
// Copyright (c) 2013 Conformal Systems LLC.
|
|
|
|
// Use of this source code is governed by an ISC
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package btcchain
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"github.com/conformal/btcdb"
|
|
|
|
"github.com/conformal/btcutil"
|
|
|
|
"github.com/conformal/btcwire"
|
|
|
|
)
|
|
|
|
|
2013-09-30 23:21:35 +02:00
|
|
|
// TxData contains contextual information about transactions such as which block
|
2013-07-18 16:49:28 +02:00
|
|
|
// they were found in and whether or not the outputs are spent.
|
2013-09-30 23:21:35 +02:00
|
|
|
type TxData struct {
|
|
|
|
Tx *btcwire.MsgTx
|
|
|
|
Hash *btcwire.ShaHash
|
|
|
|
BlockHeight int64
|
|
|
|
Spent []bool
|
|
|
|
Err error
|
2013-07-18 16:49:28 +02:00
|
|
|
}
|
|
|
|
|
2013-09-30 23:21:35 +02:00
|
|
|
// TxStore is used to store transactions needed by other transactions for things
|
|
|
|
// such as script validation and double spend prevention. This also allows the
|
|
|
|
// transaction data to be treated as a view since it can contain the information
|
|
|
|
// from the point-of-view of different points in the chain.
|
|
|
|
type TxStore map[btcwire.ShaHash]*TxData
|
|
|
|
|
2013-07-18 16:49:28 +02:00
|
|
|
// connectTransactions updates the passed map by applying transaction and
|
2013-08-05 22:20:35 +02:00
|
|
|
// spend information for all the transactions in the passed block. Only
|
2013-07-18 16:49:28 +02:00
|
|
|
// transactions in the passed map are updated.
|
2013-09-30 23:21:35 +02:00
|
|
|
func connectTransactions(txStore TxStore, block *btcutil.Block) error {
|
2013-07-18 16:49:28 +02:00
|
|
|
// Loop through all of the transactions in the block to see if any of
|
|
|
|
// them are ones we need to update and spend based on the results map.
|
|
|
|
for i, tx := range block.MsgBlock().Transactions {
|
|
|
|
txHash, err := block.TxSha(i)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the transaction store with the transaction information
|
|
|
|
// if it's one of the requested transactions.
|
|
|
|
if txD, exists := txStore[*txHash]; exists {
|
2013-09-30 23:21:35 +02:00
|
|
|
txD.Tx = tx
|
|
|
|
txD.BlockHeight = block.Height()
|
|
|
|
txD.Spent = make([]bool, len(tx.TxOut))
|
|
|
|
txD.Err = nil
|
2013-07-18 16:49:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Spend the origin transaction output.
|
|
|
|
for _, txIn := range tx.TxIn {
|
|
|
|
originHash := &txIn.PreviousOutpoint.Hash
|
|
|
|
originIndex := txIn.PreviousOutpoint.Index
|
|
|
|
if originTx, exists := txStore[*originHash]; exists {
|
2013-09-30 23:21:35 +02:00
|
|
|
originTx.Spent[originIndex] = true
|
2013-07-18 16:49:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// disconnectTransactions updates the passed map by undoing transaction and
|
|
|
|
// spend information for all transactions in the passed block. Only
|
|
|
|
// transactions in the passed map are updated.
|
2013-09-30 23:21:35 +02:00
|
|
|
func disconnectTransactions(txStore TxStore, block *btcutil.Block) error {
|
2013-07-18 16:49:28 +02:00
|
|
|
// Loop through all of the transactions in the block to see if any of
|
2013-08-29 15:56:27 +02:00
|
|
|
// them are ones that need to be undone based on the transaction store.
|
2013-07-18 16:49:28 +02:00
|
|
|
for i, tx := range block.MsgBlock().Transactions {
|
|
|
|
txHash, err := block.TxSha(i)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2013-08-29 15:56:27 +02:00
|
|
|
// Clear this transaction from the transaction store if needed.
|
|
|
|
// Only clear it rather than deleting it because the transaction
|
|
|
|
// connect code relies on its presence to decide whether or not
|
|
|
|
// to update the store and any transactions which exist on both
|
|
|
|
// sides of a fork would otherwise not be updated.
|
|
|
|
if txD, exists := txStore[*txHash]; exists {
|
2013-09-30 23:21:35 +02:00
|
|
|
txD.Tx = nil
|
|
|
|
txD.BlockHeight = 0
|
|
|
|
txD.Spent = nil
|
|
|
|
txD.Err = btcdb.TxShaMissing
|
2013-08-29 15:56:27 +02:00
|
|
|
}
|
2013-07-18 16:49:28 +02:00
|
|
|
|
|
|
|
// Unspend the origin transaction output.
|
|
|
|
for _, txIn := range tx.TxIn {
|
|
|
|
originHash := &txIn.PreviousOutpoint.Hash
|
|
|
|
originIndex := txIn.PreviousOutpoint.Index
|
2013-08-29 15:56:27 +02:00
|
|
|
originTx, exists := txStore[*originHash]
|
2013-09-30 23:21:35 +02:00
|
|
|
if exists && originTx.Tx != nil && originTx.Err == nil {
|
|
|
|
originTx.Spent[originIndex] = false
|
2013-07-18 16:49:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-10-04 19:11:58 +02:00
|
|
|
// fetchTxListMain fetches transaction data about the provided list of
|
|
|
|
// transactions from the point of view of the end of the main chain.
|
2013-09-30 23:21:35 +02:00
|
|
|
func fetchTxListMain(db btcdb.Db, txList []*btcwire.ShaHash) TxStore {
|
2013-07-18 16:49:28 +02:00
|
|
|
// The transaction store map needs to have an entry for every requested
|
|
|
|
// transaction. By default, all the transactions are marked as missing.
|
|
|
|
// Each entry will be filled in with the appropriate data below.
|
2013-09-30 23:21:35 +02:00
|
|
|
txStore := make(TxStore)
|
2013-07-18 16:49:28 +02:00
|
|
|
for _, hash := range txList {
|
2013-09-30 23:21:35 +02:00
|
|
|
txStore[*hash] = &TxData{Hash: hash, Err: btcdb.TxShaMissing}
|
2013-07-18 16:49:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ask the database (main chain) for the list of transactions. This
|
|
|
|
// will return the information from the point of view of the end of the
|
|
|
|
// main chain.
|
2013-10-03 22:43:36 +02:00
|
|
|
txReplyList := db.FetchUnSpentTxByShaList(txList)
|
2013-07-18 16:49:28 +02:00
|
|
|
for _, txReply := range txReplyList {
|
|
|
|
// Lookup the existing results entry to modify. Skip
|
|
|
|
// this reply if there is no corresponding entry in
|
|
|
|
// the transaction store map which really should not happen, but
|
|
|
|
// be safe.
|
|
|
|
txD, ok := txStore[*txReply.Sha]
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fill in the transaction details. A copy is used here since
|
|
|
|
// there is no guarantee the returned data isn't cached and
|
|
|
|
// this code modifies the data. A bug caused by modifying the
|
|
|
|
// cached data would likely be difficult to track down and could
|
|
|
|
// cause subtle errors, so avoid the potential altogether.
|
2013-09-30 23:21:35 +02:00
|
|
|
txD.Err = txReply.Err
|
2013-07-18 16:49:28 +02:00
|
|
|
if txReply.Err == nil {
|
2013-09-30 23:21:35 +02:00
|
|
|
txD.Tx = txReply.Tx
|
|
|
|
txD.BlockHeight = txReply.Height
|
|
|
|
txD.Spent = make([]bool, len(txReply.TxSpent))
|
|
|
|
copy(txD.Spent, txReply.TxSpent)
|
2013-07-18 16:49:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-30 23:21:35 +02:00
|
|
|
return txStore
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetchTxList fetches transaction data about the provided list of transactions
|
|
|
|
// from the point of view of the given node. For example, a given node might
|
|
|
|
// be down a side chain where a transaction hasn't been spent from its point of
|
|
|
|
// view even though it might have been spent in the main chain (or another side
|
|
|
|
// chain). Another scenario is where a transaction exists from the point of
|
|
|
|
// view of the main chain, but doesn't exist in a side chain that branches
|
|
|
|
// before the block that contains the transaction on the main chain.
|
|
|
|
func (b *BlockChain) fetchTxList(node *blockNode, txList []*btcwire.ShaHash) (TxStore, error) {
|
|
|
|
// Get the previous block node. This function is used over simply
|
|
|
|
// accessing node.parent directly as it will dynamically create previous
|
|
|
|
// block nodes as needed. This helps allow only the pieces of the chain
|
|
|
|
// that are needed to remain in memory.
|
|
|
|
prevNode, err := b.getPrevNodeFromNode(node)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch the requested list from the point of view of the end of the
|
|
|
|
// main (best) chain.
|
|
|
|
txStore := fetchTxListMain(b.db, txList)
|
|
|
|
|
|
|
|
// If we haven't selected a best chain yet or we are extending the main
|
|
|
|
// (best) chain with a new block, everything is accurate, so return the
|
|
|
|
// results now.
|
2013-07-18 16:49:28 +02:00
|
|
|
if b.bestChain == nil || (prevNode != nil && prevNode.hash.IsEqual(b.bestChain.hash)) {
|
|
|
|
return txStore, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// The requested node is either on a side chain or is a node on the main
|
|
|
|
// chain before the end of it. In either case, we need to undo the
|
|
|
|
// transactions and spend information for the blocks which would be
|
|
|
|
// disconnected during a reorganize to the point of view of the
|
|
|
|
// node just before the requested node.
|
|
|
|
detachNodes, attachNodes := b.getReorganizeNodes(prevNode)
|
|
|
|
for e := detachNodes.Front(); e != nil; e = e.Next() {
|
|
|
|
n := e.Value.(*blockNode)
|
|
|
|
block, err := b.db.FetchBlockBySha(n.hash)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
disconnectTransactions(txStore, block)
|
|
|
|
}
|
|
|
|
|
|
|
|
// The transaction store is now accurate to either the node where the
|
|
|
|
// requested node forks off the main chain (in the case where the
|
|
|
|
// requested node is on a side chain), or the requested node itself if
|
|
|
|
// the requested node is an old node on the main chain. Entries in the
|
|
|
|
// attachNodes list indicate the requested node is on a side chain, so
|
|
|
|
// if there are no nodes to attach, we're done.
|
|
|
|
if attachNodes.Len() == 0 {
|
|
|
|
return txStore, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// The requested node is on a side chain, so we need to apply the
|
|
|
|
// transactions and spend information from each of the nodes to attach.
|
|
|
|
for e := attachNodes.Front(); e != nil; e = e.Next() {
|
|
|
|
n := e.Value.(*blockNode)
|
|
|
|
block, exists := b.blockCache[*n.hash]
|
|
|
|
if !exists {
|
|
|
|
return nil, fmt.Errorf("unable to find block %v in "+
|
|
|
|
"side chain cache for transaction search",
|
|
|
|
n.hash)
|
|
|
|
}
|
|
|
|
|
|
|
|
connectTransactions(txStore, block)
|
|
|
|
}
|
|
|
|
|
|
|
|
return txStore, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetchInputTransactions fetches the input transactions referenced by the
|
|
|
|
// transactions in the given block from its point of view. See fetchTxList
|
|
|
|
// for more details on what the point of view entails.
|
2013-09-30 23:21:35 +02:00
|
|
|
func (b *BlockChain) fetchInputTransactions(node *blockNode, block *btcutil.Block) (TxStore, error) {
|
2013-07-18 16:49:28 +02:00
|
|
|
// Build a map of in-flight transactions because some of the inputs in
|
2013-07-29 22:48:52 +02:00
|
|
|
// this block could be referencing other transactions earlier in this
|
|
|
|
// block which are not yet in the chain.
|
|
|
|
txInFlight := map[btcwire.ShaHash]int{}
|
|
|
|
transactions := block.MsgBlock().Transactions
|
|
|
|
for i := range transactions {
|
2013-07-18 16:49:28 +02:00
|
|
|
// Get transaction hash. It's safe to ignore the error since
|
|
|
|
// it's already cached in the nominal code path and the only
|
|
|
|
// way it can fail is if the index is out of range which is
|
|
|
|
// impossible here.
|
|
|
|
txHash, _ := block.TxSha(i)
|
2013-07-29 22:48:52 +02:00
|
|
|
txInFlight[*txHash] = i
|
2013-07-18 16:49:28 +02:00
|
|
|
}
|
|
|
|
|
2013-09-26 03:20:53 +02:00
|
|
|
// Make a reasonable guess for the maximum number of needed input
|
|
|
|
// transactions to use as the starting point for the needed transactions
|
|
|
|
// array. The array will dynamically grow as needed, but it's much less
|
|
|
|
// overhead to avoid growing and copying the array multiple times in the
|
|
|
|
// common case. Each block usually has no more than ten inputs per
|
|
|
|
// transaction, so use that as a reasonable starting point. A block
|
|
|
|
// with 2,000 transactions would only result in around 156KB on a 64-bit
|
|
|
|
// system using this approach.
|
|
|
|
maxNeededHint := (len(transactions) - 1) * 10
|
|
|
|
txNeededList := make([]*btcwire.ShaHash, 0, maxNeededHint)
|
|
|
|
|
2013-07-18 16:49:28 +02:00
|
|
|
// Loop through all of the transaction inputs (except for the coinbase
|
|
|
|
// which has no inputs) collecting them into lists of what is needed and
|
|
|
|
// what is already known (in-flight).
|
2013-09-30 23:21:35 +02:00
|
|
|
txStore := make(TxStore)
|
2013-07-29 22:48:52 +02:00
|
|
|
for i, tx := range transactions[1:] {
|
2013-07-18 16:49:28 +02:00
|
|
|
for _, txIn := range tx.TxIn {
|
|
|
|
// Add an entry to the transaction store for the needed
|
|
|
|
// transaction with it set to missing by default.
|
|
|
|
originHash := &txIn.PreviousOutpoint.Hash
|
2013-09-30 23:21:35 +02:00
|
|
|
txD := &TxData{Hash: originHash, Err: btcdb.TxShaMissing}
|
2013-07-18 16:49:28 +02:00
|
|
|
txStore[*originHash] = txD
|
|
|
|
|
2013-07-29 22:48:52 +02:00
|
|
|
// 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. Update the transaction
|
|
|
|
// store acccordingly when this is the case. Otherwise,
|
|
|
|
// we still need the transaction.
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
if inFlightIndex, ok := txInFlight[*originHash]; ok &&
|
|
|
|
i >= inFlightIndex {
|
|
|
|
|
|
|
|
originTx := transactions[inFlightIndex]
|
2013-09-30 23:21:35 +02:00
|
|
|
txD.Tx = originTx
|
|
|
|
txD.BlockHeight = node.height
|
|
|
|
txD.Spent = make([]bool, len(originTx.TxOut))
|
|
|
|
txD.Err = nil
|
2013-07-18 16:49:28 +02:00
|
|
|
} else {
|
|
|
|
txNeededList = append(txNeededList, originHash)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-05 22:20:35 +02:00
|
|
|
// Request the input transactions from the point of view of the node.
|
2013-07-18 16:49:28 +02:00
|
|
|
txNeededStore, err := b.fetchTxList(node, txNeededList)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Merge the results of the requested transactions and the in-flight
|
|
|
|
// transactions.
|
|
|
|
for _, txD := range txNeededStore {
|
2013-09-30 23:21:35 +02:00
|
|
|
txStore[*txD.Hash] = txD
|
|
|
|
}
|
|
|
|
|
|
|
|
return txStore, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FetchTransactionStore fetches 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 transaction itself so the returned TxStore can be
|
|
|
|
// examined for duplicate transactions.
|
|
|
|
func (b *BlockChain) FetchTransactionStore(tx *btcwire.MsgTx) (TxStore, error) {
|
|
|
|
txHash, err := tx.TxSha()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create list big
|
|
|
|
txNeededList := make([]*btcwire.ShaHash, 0, len(tx.TxIn)+1)
|
|
|
|
txNeededList = append(txNeededList, &txHash)
|
|
|
|
|
|
|
|
// Loop through all of the transaction inputs collecting them into lists of what is needed and
|
|
|
|
// what is already known (in-flight).
|
|
|
|
txStore := make(TxStore)
|
|
|
|
for _, txIn := range tx.TxIn {
|
|
|
|
// Add an entry to the transaction store for the needed
|
|
|
|
// transaction with it set to missing by default.
|
|
|
|
originHash := &txIn.PreviousOutpoint.Hash
|
|
|
|
txD := &TxData{Hash: originHash, Err: btcdb.TxShaMissing}
|
|
|
|
txStore[*originHash] = txD
|
|
|
|
txNeededList = append(txNeededList, originHash)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Request the input transactions from the point of view of the node.
|
|
|
|
txNeededStore := fetchTxListMain(b.db, txNeededList)
|
|
|
|
|
|
|
|
// Merge the results of the requested transactions and the in-flight
|
|
|
|
// transactions.
|
|
|
|
for _, txD := range txNeededStore {
|
|
|
|
txStore[*txD.Hash] = txD
|
2013-07-18 16:49:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return txStore, nil
|
|
|
|
}
|