0280fa0264
This commit converts all block height references to int32 instead of int64. The current target block production rate is 10 mins per block which means it will take roughly 40,800 years to reach the maximum height an int32 affords. Even if the target rate were lowered to one block per minute, it would still take roughly another 4,080 years to reach the maximum. In the mean time, there is no reason to use a larger type which results in higher memory and disk space usage. However, for now, in order to avoid having to reserialize a bunch of database information, the heights are still serialized to the database as 8-byte uint64s. This is being mainly being done in preparation for further upcoming infrastructure changes which will use the smaller and more efficient 4-byte serialization in the database as well.
319 lines
12 KiB
Go
319 lines
12 KiB
Go
// Copyright (c) 2013-2014 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/database"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
)
|
|
|
|
// TxData contains contextual information about transactions such as which block
|
|
// they were found in and whether or not the outputs are spent.
|
|
type TxData struct {
|
|
Tx *btcutil.Tx
|
|
Hash *wire.ShaHash
|
|
BlockHeight int32
|
|
Spent []bool
|
|
Err error
|
|
}
|
|
|
|
// 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[wire.ShaHash]*TxData
|
|
|
|
// connectTransactions updates the passed map by applying transaction and
|
|
// spend information for all the transactions in the passed block. Only
|
|
// transactions in the passed map are updated.
|
|
func connectTransactions(txStore TxStore, block *btcutil.Block) error {
|
|
// 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 _, tx := range block.Transactions() {
|
|
// Update the transaction store with the transaction information
|
|
// if it's one of the requested transactions.
|
|
msgTx := tx.MsgTx()
|
|
if txD, exists := txStore[*tx.Sha()]; exists {
|
|
txD.Tx = tx
|
|
txD.BlockHeight = block.Height()
|
|
txD.Spent = make([]bool, len(msgTx.TxOut))
|
|
txD.Err = nil
|
|
}
|
|
|
|
// Spend the origin transaction output.
|
|
for _, txIn := range msgTx.TxIn {
|
|
originHash := &txIn.PreviousOutPoint.Hash
|
|
originIndex := txIn.PreviousOutPoint.Index
|
|
if originTx, exists := txStore[*originHash]; exists {
|
|
if originIndex > uint32(len(originTx.Spent)) {
|
|
continue
|
|
}
|
|
originTx.Spent[originIndex] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
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.
|
|
func disconnectTransactions(txStore TxStore, block *btcutil.Block) error {
|
|
// Loop through all of the transactions in the block to see if any of
|
|
// them are ones that need to be undone based on the transaction store.
|
|
for _, tx := range block.Transactions() {
|
|
// 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[*tx.Sha()]; exists {
|
|
txD.Tx = nil
|
|
txD.BlockHeight = 0
|
|
txD.Spent = nil
|
|
txD.Err = database.ErrTxShaMissing
|
|
}
|
|
|
|
// Unspend the origin transaction output.
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
originHash := &txIn.PreviousOutPoint.Hash
|
|
originIndex := txIn.PreviousOutPoint.Index
|
|
originTx, exists := txStore[*originHash]
|
|
if exists && originTx.Tx != nil && originTx.Err == nil {
|
|
if originIndex > uint32(len(originTx.Spent)) {
|
|
continue
|
|
}
|
|
originTx.Spent[originIndex] = false
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// fetchTxStoreMain fetches transaction data about the provided set of
|
|
// transactions from the point of view of the end of the main chain. It takes
|
|
// a flag which specifies whether or not fully spent transaction should be
|
|
// included in the results.
|
|
func fetchTxStoreMain(db database.Db, txSet map[wire.ShaHash]struct{}, includeSpent bool) TxStore {
|
|
// Just return an empty store now if there are no requested hashes.
|
|
txStore := make(TxStore)
|
|
if len(txSet) == 0 {
|
|
return txStore
|
|
}
|
|
|
|
// 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.
|
|
txList := make([]*wire.ShaHash, 0, len(txSet))
|
|
for hash := range txSet {
|
|
hashCopy := hash
|
|
txStore[hash] = &TxData{Hash: &hashCopy, Err: database.ErrTxShaMissing}
|
|
txList = append(txList, &hashCopy)
|
|
}
|
|
|
|
// 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. Choose whether or not to include fully spent
|
|
// transactions depending on the passed flag.
|
|
var txReplyList []*database.TxListReply
|
|
if includeSpent {
|
|
txReplyList = db.FetchTxByShaList(txList)
|
|
} else {
|
|
txReplyList = db.FetchUnSpentTxByShaList(txList)
|
|
}
|
|
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.
|
|
txD.Err = txReply.Err
|
|
if txReply.Err == nil {
|
|
txD.Tx = btcutil.NewTx(txReply.Tx)
|
|
txD.BlockHeight = txReply.Height
|
|
txD.Spent = make([]bool, len(txReply.TxSpent))
|
|
copy(txD.Spent, txReply.TxSpent)
|
|
}
|
|
}
|
|
|
|
return txStore
|
|
}
|
|
|
|
// fetchTxStore fetches transaction data about the provided set 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) fetchTxStore(node *blockNode, txSet map[wire.ShaHash]struct{}) (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
|
|
}
|
|
|
|
// If we haven't selected a best chain yet or we are extending the main
|
|
// (best) chain with a new block, fetch the requested set from the point
|
|
// of view of the end of the main (best) chain without including fully
|
|
// spent transactions in the results. This is a little more efficient
|
|
// since it means less transaction lookups are needed.
|
|
if b.bestChain == nil || (prevNode != nil && prevNode.hash.IsEqual(b.bestChain.hash)) {
|
|
txStore := fetchTxStoreMain(b.db, txSet, false)
|
|
return txStore, nil
|
|
}
|
|
|
|
// Fetch the requested set from the point of view of the end of the
|
|
// main (best) chain including fully spent transactions. The fully
|
|
// spent transactions are needed because the following code unspends
|
|
// them to get the correct point of view.
|
|
txStore := fetchTxStoreMain(b.db, txSet, true)
|
|
|
|
// 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.
|
|
func (b *BlockChain) fetchInputTransactions(node *blockNode, block *btcutil.Block) (TxStore, 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[wire.ShaHash]int{}
|
|
transactions := block.Transactions()
|
|
for i, tx := range transactions {
|
|
txInFlight[*tx.Sha()] = 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[wire.ShaHash]struct{})
|
|
txStore := make(TxStore)
|
|
for i, tx := range transactions[1:] {
|
|
for _, txIn := range tx.MsgTx().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: database.ErrTxShaMissing}
|
|
txStore[*originHash] = txD
|
|
|
|
// 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]
|
|
txD.Tx = originTx
|
|
txD.BlockHeight = node.height
|
|
txD.Spent = make([]bool, len(originTx.MsgTx().TxOut))
|
|
txD.Err = nil
|
|
} else {
|
|
txNeededSet[*originHash] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Request the input transactions from the point of view of the node.
|
|
txNeededStore, err := b.fetchTxStore(node, txNeededSet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Merge the results of the requested transactions and the in-flight
|
|
// transactions.
|
|
for _, txD := range txNeededStore {
|
|
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 *btcutil.Tx) (TxStore, error) {
|
|
// Create a set of needed transactions from the transactions referenced
|
|
// by the inputs of the passed transaction. Also, add the passed
|
|
// transaction itself as a way for the caller to detect duplicates.
|
|
txNeededSet := make(map[wire.ShaHash]struct{})
|
|
txNeededSet[*tx.Sha()] = struct{}{}
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
txNeededSet[txIn.PreviousOutPoint.Hash] = struct{}{}
|
|
}
|
|
|
|
// Request the input transactions from the point of view of the end of
|
|
// the main chain without including fully spent trasactions in the
|
|
// results. Fully spent transactions are only needed for chain
|
|
// reorganization which does not apply here.
|
|
txStore := fetchTxStoreMain(b.db, txNeededSet, false)
|
|
return txStore, nil
|
|
}
|