diff --git a/scriptval.go b/scriptval.go index 139fc630..e4335110 100644 --- a/scriptval.go +++ b/scriptval.go @@ -60,10 +60,11 @@ func validateTxIn(txInIdx int, txin *btcwire.TxIn, txSha *btcwire.ShaHash, tx *b return nil } -// validateAllTxIn validates the scripts for all of the passed transaction -// inputs using multiple goroutines. -func validateAllTxIn(txsha *btcwire.ShaHash, txValidator *btcwire.MsgTx, timestamp time.Time, job []*btcwire.TxIn, txStore map[btcwire.ShaHash]*txData) (err error) { +// validateAllTxIn validates the scripts for the passed transaction using +// multiple goroutines. +func validateAllTxIn(tx *btcwire.MsgTx, txHash *btcwire.ShaHash, timestamp time.Time, txStore TxStore) (err error) { c := make(chan txValidate) + job := tx.TxIn resultErrors := make([]error, len(job)) var currentItem int @@ -71,7 +72,7 @@ func validateAllTxIn(txsha *btcwire.ShaHash, txValidator *btcwire.MsgTx, timesta processFunc := func(txInIdx int) { log.Tracef("validating tx %v input %v len %v", - txsha, currentItem, len(job)) + txHash, currentItem, len(job)) txin := job[txInIdx] originTxSha := &txin.PreviousOutpoint.Hash origintxidx := txin.PreviousOutpoint.Index @@ -84,10 +85,10 @@ func validateAllTxIn(txsha *btcwire.ShaHash, txValidator *btcwire.MsgTx, timesta fmt.Printf("obj not found in txStore %v", originTxSha) } - originTx = txInfo.tx + originTx = txInfo.Tx } - err := validateTxIn(txInIdx, job[txInIdx], txsha, txValidator, - timestamp, originTx) + err := validateTxIn(txInIdx, job[txInIdx], txHash, tx, timestamp, + originTx) r := txValidate{txInIdx, err} c <- r } @@ -113,7 +114,7 @@ func validateAllTxIn(txsha *btcwire.ShaHash, txValidator *btcwire.MsgTx, timesta } for i := 0; i < len(job); i++ { if resultErrors[i] != nil { - log.Warnf("tx %v failed input %v, err %v", txsha, i, resultErrors[i]) + log.Warnf("tx %v failed input %v, err %v", txHash, i, resultErrors[i]) } } return @@ -121,11 +122,11 @@ func validateAllTxIn(txsha *btcwire.ShaHash, txValidator *btcwire.MsgTx, timesta // checkBlockScripts executes and validates the scripts for all transactions in // the passed block. -func checkBlockScripts(block *btcutil.Block, txStore map[btcwire.ShaHash]*txData) error { +func checkBlockScripts(block *btcutil.Block, txStore TxStore) error { timestamp := block.MsgBlock().Header.Timestamp for i, tx := range block.MsgBlock().Transactions { txHash, _ := block.TxSha(i) - err := validateAllTxIn(txHash, tx, timestamp, tx.TxIn, txStore) + err := validateAllTxIn(tx, txHash, timestamp, txStore) if err != nil { return err } diff --git a/txlookup.go b/txlookup.go index 0cc08a88..af9cf876 100644 --- a/txlookup.go +++ b/txlookup.go @@ -11,20 +11,26 @@ import ( "github.com/conformal/btcwire" ) -// txData contains contextual information about transactions such as which block +// 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 *btcwire.MsgTx - hash *btcwire.ShaHash - blockHeight int64 - spent []bool - err error +type TxData struct { + Tx *btcwire.MsgTx + Hash *btcwire.ShaHash + BlockHeight int64 + 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[btcwire.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 map[btcwire.ShaHash]*txData, block *btcutil.Block) error { +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 i, tx := range block.MsgBlock().Transactions { @@ -36,10 +42,10 @@ func connectTransactions(txStore map[btcwire.ShaHash]*txData, block *btcutil.Blo // Update the transaction store with the transaction information // if it's one of the requested transactions. if txD, exists := txStore[*txHash]; exists { - txD.tx = tx - txD.blockHeight = block.Height() - txD.spent = make([]bool, len(tx.TxOut)) - txD.err = nil + txD.Tx = tx + txD.BlockHeight = block.Height() + txD.Spent = make([]bool, len(tx.TxOut)) + txD.Err = nil } // Spend the origin transaction output. @@ -47,7 +53,7 @@ func connectTransactions(txStore map[btcwire.ShaHash]*txData, block *btcutil.Blo originHash := &txIn.PreviousOutpoint.Hash originIndex := txIn.PreviousOutpoint.Index if originTx, exists := txStore[*originHash]; exists { - originTx.spent[originIndex] = true + originTx.Spent[originIndex] = true } } } @@ -58,7 +64,7 @@ func connectTransactions(txStore map[btcwire.ShaHash]*txData, block *btcutil.Blo // 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 map[btcwire.ShaHash]*txData, block *btcutil.Block) error { +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 i, tx := range block.MsgBlock().Transactions { @@ -73,10 +79,10 @@ func disconnectTransactions(txStore map[btcwire.ShaHash]*txData, block *btcutil. // 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 { - txD.tx = nil - txD.blockHeight = 0 - txD.spent = nil - txD.err = btcdb.TxShaMissing + txD.Tx = nil + txD.BlockHeight = 0 + txD.Spent = nil + txD.Err = btcdb.TxShaMissing } // Unspend the origin transaction output. @@ -84,8 +90,8 @@ func disconnectTransactions(txStore map[btcwire.ShaHash]*txData, block *btcutil. originHash := &txIn.PreviousOutpoint.Hash originIndex := txIn.PreviousOutpoint.Index originTx, exists := txStore[*originHash] - if exists && originTx.tx != nil && originTx.err == nil { - originTx.spent[originIndex] = false + if exists && originTx.Tx != nil && originTx.Err == nil { + originTx.Spent[originIndex] = false } } } @@ -94,34 +100,20 @@ func disconnectTransactions(txStore map[btcwire.ShaHash]*txData, block *btcutil. } // 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) (map[btcwire.ShaHash]*txData, 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 - } - +// from the point of view of the end of the main chain. +func fetchTxListMain(db btcdb.Db, txList []*btcwire.ShaHash) 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. - txStore := make(map[btcwire.ShaHash]*txData) + txStore := make(TxStore) for _, hash := range txList { - txStore[*hash] = &txData{hash: hash, err: btcdb.TxShaMissing} + txStore[*hash] = &TxData{Hash: hash, Err: btcdb.TxShaMissing} } // 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. - txReplyList := b.db.FetchTxByShaList(txList) + txReplyList := db.FetchTxByShaList(txList) for _, txReply := range txReplyList { // Lookup the existing results entry to modify. Skip // this reply if there is no corresponding entry in @@ -137,19 +129,42 @@ func (b *BlockChain) fetchTxList(node *blockNode, txList []*btcwire.ShaHash) (ma // 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 + txD.Err = txReply.Err if txReply.Err == nil { - txD.tx = txReply.Tx - txD.blockHeight = txReply.Height - txD.spent = make([]bool, len(txReply.TxSpent)) - copy(txD.spent, txReply.TxSpent) + txD.Tx = txReply.Tx + txD.BlockHeight = txReply.Height + txD.Spent = make([]bool, len(txReply.TxSpent)) + copy(txD.Spent, txReply.TxSpent) } } - // At this point, we have the transaction data from the point of view - // of the end of the main (best) chain. 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. + 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. if b.bestChain == nil || (prevNode != nil && prevNode.hash.IsEqual(b.bestChain.hash)) { return txStore, nil } @@ -200,7 +215,7 @@ func (b *BlockChain) fetchTxList(node *blockNode, txList []*btcwire.ShaHash) (ma // 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) (map[btcwire.ShaHash]*txData, error) { +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. @@ -229,13 +244,13 @@ func (b *BlockChain) fetchInputTransactions(node *blockNode, block *btcutil.Bloc // 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). - txStore := make(map[btcwire.ShaHash]*txData) + txStore := make(TxStore) for i, tx := range transactions[1:] { 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} + txD := &TxData{Hash: originHash, Err: btcdb.TxShaMissing} txStore[*originHash] = txD // It is acceptable for a transaction input to reference @@ -252,10 +267,10 @@ func (b *BlockChain) fetchInputTransactions(node *blockNode, block *btcutil.Bloc i >= inFlightIndex { originTx := transactions[inFlightIndex] - txD.tx = originTx - txD.blockHeight = node.height - txD.spent = make([]bool, len(originTx.TxOut)) - txD.err = nil + txD.Tx = originTx + txD.BlockHeight = node.height + txD.Spent = make([]bool, len(originTx.TxOut)) + txD.Err = nil } else { txNeededList = append(txNeededList, originHash) } @@ -271,7 +286,45 @@ func (b *BlockChain) fetchInputTransactions(node *blockNode, block *btcutil.Bloc // Merge the results of the requested transactions and the in-flight // transactions. for _, txD := range txNeededStore { - txStore[*txD.hash] = txD + 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 } return txStore, nil diff --git a/validate.go b/validate.go index c072b00f..c6dcf535 100644 --- a/validate.go +++ b/validate.go @@ -327,7 +327,7 @@ func countSigOps(msgTx *btcwire.MsgTx) int { // transactions which are of the pay-to-script-hash type. This uses the // precise, signature operation counting mechanism from btcscript which requires // access to the input transaction scripts. -func countP2SHSigOps(msgTx *btcwire.MsgTx, isCoinBaseTx bool, txStore map[btcwire.ShaHash]*txData) (int, error) { +func countP2SHSigOps(msgTx *btcwire.MsgTx, isCoinBaseTx bool, txStore TxStore) (int, error) { // Coinbase transactions have no interesting inputs. if isCoinBaseTx { return 0, nil @@ -346,7 +346,7 @@ func countP2SHSigOps(msgTx *btcwire.MsgTx, isCoinBaseTx bool, txStore map[btcwir // Ensure the referenced input transaction is available. txInHash := &txIn.PreviousOutpoint.Hash originTx, exists := txStore[*txInHash] - if !exists || originTx.err != nil || originTx.tx == nil { + if !exists || originTx.Err != nil || originTx.Tx == nil { return 0, fmt.Errorf("unable to find input transaction "+ "%v referenced from transaction %v", txInHash, txHash) @@ -355,7 +355,7 @@ func countP2SHSigOps(msgTx *btcwire.MsgTx, isCoinBaseTx bool, txStore map[btcwir // Ensure the output index in the referenced transaction is // available. originTxIndex := txIn.PreviousOutpoint.Index - if originTxIndex >= uint32(len(originTx.tx.TxOut)) { + if originTxIndex >= uint32(len(originTx.Tx.TxOut)) { return 0, fmt.Errorf("out of bounds input index %d in "+ "transaction %v referenced from transaction %v", originTxIndex, txInHash, txHash) @@ -363,7 +363,7 @@ func countP2SHSigOps(msgTx *btcwire.MsgTx, isCoinBaseTx bool, txStore map[btcwir // We're only interested in pay-to-script-hash types, so skip // this input if it's not one. - pkScript := originTx.tx.TxOut[originTxIndex].PkScript + pkScript := originTx.Tx.TxOut[originTxIndex].PkScript if !btcscript.IsPayToScriptHash(pkScript) { continue } @@ -519,10 +519,11 @@ func checkSerializedHeight(coinbaseTx *btcwire.MsgTx, wantHeight int64) error { return nil } -// isTransactionSpent returns whether or not the provided transaction is fully -// spent. A fully spent transaction is one where all outputs have been spent. -func isTransactionSpent(tx *txData) bool { - for _, isOutputSpent := range tx.spent { +// isTransactionSpent returns whether or not the provided transaction data +// describes a fully spent transaction. A fully spent transaction is one where +// all outputs have been spent. +func isTransactionSpent(txD *TxData) bool { + for _, isOutputSpent := range txD.Spent { if !isOutputSpent { return false } @@ -552,7 +553,7 @@ func (b *BlockChain) checkBIP0030(node *blockNode, block *btcutil.Block) error { // Examine the resulting data about the requested transactions. for _, txD := range txResults { - switch txD.err { + switch txD.Err { // A duplicate transaction was not found. This is the most // common case. case btcdb.TxShaMissing: @@ -564,14 +565,14 @@ func (b *BlockChain) checkBIP0030(node *blockNode, block *btcutil.Block) error { if !isTransactionSpent(txD) { str := fmt.Sprintf("tried to overwrite "+ "transaction %v at block height %d "+ - "that is not fully spent", txD.hash, - txD.blockHeight) + "that is not fully spent", txD.Hash, + txD.BlockHeight) return RuleError(str) } // Some other unexpected error occurred. Return it now. default: - return txD.err + return txD.Err } } @@ -586,7 +587,7 @@ func (b *BlockChain) checkBIP0030(node *blockNode, block *btcutil.Block) error { // amount, and verifying the signatures to prove the spender was the owner of // the bitcoins and therefore allowed to spend them. As it checks the inputs, // it also calculates the total fees for the transaction and returns that value. -func checkTransactionInputs(tx *btcwire.MsgTx, txHeight int64, txStore map[btcwire.ShaHash]*txData) (int64, error) { +func checkTransactionInputs(tx *btcwire.MsgTx, txHeight int64, txStore TxStore) (int64, error) { // Coinbase transactions have no inputs. if isCoinBase(tx) { return 0, nil @@ -611,8 +612,8 @@ func checkTransactionInputs(tx *btcwire.MsgTx, txHeight int64, txStore map[btcwi // Ensure the transaction is not spending coins which have not // yet reached the required coinbase maturity. - if isCoinBase(originTx.tx) { - originHeight := originTx.blockHeight + if isCoinBase(originTx.Tx) { + originHeight := originTx.BlockHeight blocksSincePrev := txHeight - originHeight if blocksSincePrev < coinbaseMaturity { str := fmt.Sprintf("tried to spend coinbase "+ @@ -626,12 +627,12 @@ func checkTransactionInputs(tx *btcwire.MsgTx, txHeight int64, txStore map[btcwi // Ensure the transaction is not double spending coins. originTxIndex := txIn.PreviousOutpoint.Index - if originTxIndex >= uint32(len(originTx.spent)) { + if originTxIndex >= uint32(len(originTx.Spent)) { return 0, fmt.Errorf("out of bounds input index %d in "+ "transaction %v referenced from transaction %v", originTxIndex, txInHash, txHash) } - if originTx.spent[originTxIndex] { + if originTx.Spent[originTxIndex] { str := fmt.Sprintf("transaction %v tried to double "+ "spend coins from transaction %v", txHash, txInHash) @@ -644,7 +645,7 @@ func checkTransactionInputs(tx *btcwire.MsgTx, txHeight int64, txStore map[btcwi // a transaction are in a unit value known as a satoshi. One // bitcoin is a quantity of satoshi as defined by the // satoshiPerBitcoin constant. - originTxSatoshi := originTx.tx.TxOut[originTxIndex].Value + originTxSatoshi := originTx.Tx.TxOut[originTxIndex].Value if originTxSatoshi < 0 { str := fmt.Sprintf("transaction output has negative "+ "value of %v", originTxSatoshi) @@ -671,7 +672,7 @@ func checkTransactionInputs(tx *btcwire.MsgTx, txHeight int64, txStore map[btcwi } // Mark the referenced output as spent. - originTx.spent[originTxIndex] = true + originTx.Spent[originTxIndex] = true } // Calculate the total output amount for this transaction. It is safe