+++ /dev/null
-// Copyright (c) 2013-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"
- "math"
- "runtime"
- "time"
-
- "github.com/btcsuite/btcd/txscript"
- "github.com/btcsuite/btcd/wire"
- "github.com/btcsuite/btcutil"
-)
-
-// txValidateItem holds a transaction along with which input to validate.
-type txValidateItem struct {
- txInIndex int
- txIn *wire.TxIn
- tx *btcutil.Tx
- sigHashes *txscript.TxSigHashes
-}
-
-// txValidator provides a type which asynchronously validates transaction
-// inputs. It provides several channels for communication and a processing
-// function that is intended to be in run multiple goroutines.
-type txValidator struct {
- validateChan chan *txValidateItem
- quitChan chan struct{}
- resultChan chan error
- utxoView *UtxoViewpoint
- flags txscript.ScriptFlags
- sigCache *txscript.SigCache
- hashCache *txscript.HashCache
-}
-
-// sendResult sends the result of a script pair validation on the internal
-// result channel while respecting the quit channel. This allows orderly
-// shutdown when the validation process is aborted early due to a validation
-// error in one of the other goroutines.
-func (v *txValidator) sendResult(result error) {
- select {
- case v.resultChan <- result:
- case <-v.quitChan:
- }
-}
-
-// validateHandler consumes items to validate from the internal validate channel
-// and returns the result of the validation on the internal result channel. It
-// must be run as a goroutine.
-func (v *txValidator) validateHandler() {
-out:
- for {
- select {
- case txVI := <-v.validateChan:
- // Ensure the referenced input transaction is available.
- txIn := txVI.txIn
- originTxHash := &txIn.PreviousOutPoint.Hash
- originTxIndex := txIn.PreviousOutPoint.Index
- txEntry := v.utxoView.LookupEntry(originTxHash)
- if txEntry == nil {
- str := fmt.Sprintf("unable to find input "+
- "transaction %v referenced from "+
- "transaction %v", originTxHash,
- txVI.tx.Hash())
- err := ruleError(ErrMissingTxOut, str)
- v.sendResult(err)
- break out
- }
-
- // Ensure the referenced input transaction public key
- // script is available.
- pkScript := txEntry.PkScriptByIndex(originTxIndex)
- if pkScript == nil {
- str := fmt.Sprintf("unable to find unspent "+
- "output %v script referenced from "+
- "transaction %s:%d",
- txIn.PreviousOutPoint, txVI.tx.Hash(),
- txVI.txInIndex)
- err := ruleError(ErrBadTxInput, str)
- v.sendResult(err)
- break out
- }
-
- // Create a new script engine for the script pair.
- sigScript := txIn.SignatureScript
- witness := txIn.Witness
- inputAmount := txEntry.AmountByIndex(originTxIndex)
- vm, err := txscript.NewEngine(pkScript, txVI.tx.MsgTx(),
- txVI.txInIndex, v.flags, v.sigCache, txVI.sigHashes,
- inputAmount)
- if err != nil {
- str := fmt.Sprintf("failed to parse input "+
- "%s:%d which references output %s:%d - "+
- "%v (input witness %x, input script "+
- "bytes %x, prev output script bytes %x)",
- txVI.tx.Hash(), txVI.txInIndex, originTxHash,
- originTxIndex, err, witness, sigScript,
- pkScript)
- err := ruleError(ErrScriptMalformed, str)
- v.sendResult(err)
- break out
- }
-
- // Execute the script pair.
- if err := vm.Execute(); err != nil {
- str := fmt.Sprintf("failed to validate input "+
- "%s:%d which references output %s:%d - "+
- "%v (input witness %x, input script "+
- "bytes %x, prev output script bytes %x)",
- txVI.tx.Hash(), txVI.txInIndex,
- originTxHash, originTxIndex, err,
- witness, sigScript, pkScript)
- err := ruleError(ErrScriptValidation, str)
- v.sendResult(err)
- break out
- }
-
- // Validation succeeded.
- v.sendResult(nil)
-
- case <-v.quitChan:
- break out
- }
- }
-}
-
-// Validate validates the scripts for all of the passed transaction inputs using
-// multiple goroutines.
-func (v *txValidator) Validate(items []*txValidateItem) error {
- if len(items) == 0 {
- return nil
- }
-
- // Limit the number of goroutines to do script validation based on the
- // number of processor cores. This helps ensure the system stays
- // reasonably responsive under heavy load.
- maxGoRoutines := runtime.NumCPU() * 3
- if maxGoRoutines <= 0 {
- maxGoRoutines = 1
- }
- if maxGoRoutines > len(items) {
- maxGoRoutines = len(items)
- }
-
- // Start up validation handlers that are used to asynchronously
- // validate each transaction input.
- for i := 0; i < maxGoRoutines; i++ {
- go v.validateHandler()
- }
-
- // Validate each of the inputs. The quit channel is closed when any
- // errors occur so all processing goroutines exit regardless of which
- // input had the validation error.
- numInputs := len(items)
- currentItem := 0
- processedItems := 0
- for processedItems < numInputs {
- // Only send items while there are still items that need to
- // be processed. The select statement will never select a nil
- // channel.
- var validateChan chan *txValidateItem
- var item *txValidateItem
- if currentItem < numInputs {
- validateChan = v.validateChan
- item = items[currentItem]
- }
-
- select {
- case validateChan <- item:
- currentItem++
-
- case err := <-v.resultChan:
- processedItems++
- if err != nil {
- close(v.quitChan)
- return err
- }
- }
- }
-
- close(v.quitChan)
- return nil
-}
-
-// newTxValidator returns a new instance of txValidator to be used for
-// validating transaction scripts asynchronously.
-func newTxValidator(utxoView *UtxoViewpoint, flags txscript.ScriptFlags,
- sigCache *txscript.SigCache, hashCache *txscript.HashCache) *txValidator {
- return &txValidator{
- validateChan: make(chan *txValidateItem),
- quitChan: make(chan struct{}),
- resultChan: make(chan error),
- utxoView: utxoView,
- sigCache: sigCache,
- hashCache: hashCache,
- flags: flags,
- }
-}
-
-// ValidateTransactionScripts validates the scripts for the passed transaction
-// using multiple goroutines.
-func ValidateTransactionScripts(tx *btcutil.Tx, utxoView *UtxoViewpoint,
- flags txscript.ScriptFlags, sigCache *txscript.SigCache,
- hashCache *txscript.HashCache) error {
-
- // First determine if segwit is active according to the scriptFlags. If
- // it isn't then we don't need to interact with the HashCache.
- segwitActive := flags&txscript.ScriptVerifyWitness == txscript.ScriptVerifyWitness
-
- // If the hashcache doesn't yet has the sighash midstate for this
- // transaction, then we'll compute them now so we can re-use them
- // amongst all worker validation goroutines.
- if segwitActive && tx.MsgTx().HasWitness() &&
- !hashCache.ContainsHashes(tx.Hash()) {
- hashCache.AddSigHashes(tx.MsgTx())
- }
-
- var cachedHashes *txscript.TxSigHashes
- if segwitActive && tx.MsgTx().HasWitness() {
- // The same pointer to the transaction's sighash midstate will
- // be re-used amongst all validation goroutines. By
- // pre-computing the sighash here instead of during validation,
- // we ensure the sighashes
- // are only computed once.
- cachedHashes, _ = hashCache.GetSigHashes(tx.Hash())
- }
-
- // Collect all of the transaction inputs and required information for
- // validation.
- txIns := tx.MsgTx().TxIn
- txValItems := make([]*txValidateItem, 0, len(txIns))
- for txInIdx, txIn := range txIns {
- // Skip coinbases.
- if txIn.PreviousOutPoint.Index == math.MaxUint32 {
- continue
- }
-
- txVI := &txValidateItem{
- txInIndex: txInIdx,
- txIn: txIn,
- tx: tx,
- sigHashes: cachedHashes,
- }
- txValItems = append(txValItems, txVI)
- }
-
- // Validate all of the inputs.
- validator := newTxValidator(utxoView, flags, sigCache, hashCache)
- return validator.Validate(txValItems)
-}
-
-// checkBlockScripts executes and validates the scripts for all transactions in
-// the passed block using multiple goroutines.
-func checkBlockScripts(block *btcutil.Block, utxoView *UtxoViewpoint,
- scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache,
- hashCache *txscript.HashCache) error {
-
- // First determine if segwit is active according to the scriptFlags. If
- // it isn't then we don't need to interact with the HashCache.
- segwitActive := scriptFlags&txscript.ScriptVerifyWitness == txscript.ScriptVerifyWitness
-
- // Collect all of the transaction inputs and required information for
- // validation for all transactions in the block into a single slice.
- numInputs := 0
- for _, tx := range block.Transactions() {
- numInputs += len(tx.MsgTx().TxIn)
- }
- txValItems := make([]*txValidateItem, 0, numInputs)
- for _, tx := range block.Transactions() {
- hash := tx.Hash()
-
- // If the HashCache is present, and it doesn't yet contain the
- // partial sighashes for this transaction, then we add the
- // sighashes for the transaction. This allows us to take
- // advantage of the potential speed savings due to the new
- // digest algorithm (BIP0143).
- if segwitActive && tx.HasWitness() && hashCache != nil &&
- !hashCache.ContainsHashes(hash) {
-
- hashCache.AddSigHashes(tx.MsgTx())
- }
-
- var cachedHashes *txscript.TxSigHashes
- if segwitActive && tx.HasWitness() {
- if hashCache != nil {
- cachedHashes, _ = hashCache.GetSigHashes(hash)
- } else {
- cachedHashes = txscript.NewTxSigHashes(tx.MsgTx())
- }
- }
-
- for txInIdx, txIn := range tx.MsgTx().TxIn {
- // Skip coinbases.
- if txIn.PreviousOutPoint.Index == math.MaxUint32 {
- continue
- }
-
- txVI := &txValidateItem{
- txInIndex: txInIdx,
- txIn: txIn,
- tx: tx,
- sigHashes: cachedHashes,
- }
- txValItems = append(txValItems, txVI)
- }
- }
-
- // Validate all of the inputs.
- validator := newTxValidator(utxoView, scriptFlags, sigCache, hashCache)
- start := time.Now()
- if err := validator.Validate(txValItems); err != nil {
- return err
- }
- elapsed := time.Since(start)
-
- log.Tracef("block %v took %v to verify", block.Hash(), elapsed)
-
- // If the HashCache is present, once we have validated the block, we no
- // longer need the cached hashes for these transactions, so we purge
- // them from the cache.
- if segwitActive && hashCache != nil {
- for _, tx := range block.Transactions() {
- if tx.MsgTx().HasWitness() {
- hashCache.PurgeSigHashes(tx.Hash())
- }
- }
- }
-
- return nil
-}