github.com/decred/dcrd/blockchain@v1.2.1/scriptval.go (about)

     1  // Copyright (c) 2013-2016 The btcsuite developers
     2  // Copyright (c) 2015-2016 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package blockchain
     7  
     8  import (
     9  	"fmt"
    10  	"math"
    11  	"runtime"
    12  
    13  	"github.com/decred/dcrd/dcrutil"
    14  	"github.com/decred/dcrd/txscript"
    15  	"github.com/decred/dcrd/wire"
    16  )
    17  
    18  // txValidateItem holds a transaction along with which input to validate.
    19  type txValidateItem struct {
    20  	txInIndex int
    21  	txIn      *wire.TxIn
    22  	tx        *dcrutil.Tx
    23  }
    24  
    25  // txValidator provides a type which asynchronously validates transaction
    26  // inputs.  It provides several channels for communication and a processing
    27  // function that is intended to be in run multiple goroutines.
    28  type txValidator struct {
    29  	validateChan chan *txValidateItem
    30  	quitChan     chan struct{}
    31  	resultChan   chan error
    32  	utxoView     *UtxoViewpoint
    33  	flags        txscript.ScriptFlags
    34  	sigCache     *txscript.SigCache
    35  }
    36  
    37  // sendResult sends the result of a script pair validation on the internal
    38  // result channel while respecting the quit channel.  The allows orderly
    39  // shutdown when the validation process is aborted early due to a validation
    40  // error in one of the other goroutines.
    41  func (v *txValidator) sendResult(result error) {
    42  	select {
    43  	case v.resultChan <- result:
    44  	case <-v.quitChan:
    45  	}
    46  }
    47  
    48  // validateHandler consumes items to validate from the internal validate channel
    49  // and returns the result of the validation on the internal result channel. It
    50  // must be run as a goroutine.
    51  func (v *txValidator) validateHandler() {
    52  out:
    53  	for {
    54  		select {
    55  		case txVI := <-v.validateChan:
    56  			// Ensure the referenced input transaction is available.
    57  			txIn := txVI.txIn
    58  			originTxHash := &txIn.PreviousOutPoint.Hash
    59  			originTxIndex := txIn.PreviousOutPoint.Index
    60  			txEntry := v.utxoView.LookupEntry(originTxHash)
    61  			if txEntry == nil {
    62  				str := fmt.Sprintf("unable to find input "+
    63  					"transaction %v referenced from "+
    64  					"transaction %v", originTxHash,
    65  					txVI.tx.Hash())
    66  				err := ruleError(ErrMissingTxOut, str)
    67  				v.sendResult(err)
    68  				break out
    69  			}
    70  
    71  			// Ensure the referenced input transaction public key
    72  			// script is available.
    73  			pkScript := txEntry.PkScriptByIndex(originTxIndex)
    74  			if pkScript == nil {
    75  				str := fmt.Sprintf("unable to find unspent "+
    76  					"output %v script referenced from "+
    77  					"transaction %s:%d",
    78  					txIn.PreviousOutPoint, txVI.tx.Hash(),
    79  					txVI.txInIndex)
    80  				err := ruleError(ErrBadTxInput, str)
    81  				v.sendResult(err)
    82  				break out
    83  			}
    84  
    85  			// Create a new script engine for the script pair.
    86  			sigScript := txIn.SignatureScript
    87  			version := txEntry.ScriptVersionByIndex(originTxIndex)
    88  
    89  			vm, err := txscript.NewEngine(pkScript, txVI.tx.MsgTx(),
    90  				txVI.txInIndex, v.flags, version, v.sigCache)
    91  			if err != nil {
    92  				str := fmt.Sprintf("failed to parse input "+
    93  					"%s:%d which references output %s:%d - "+
    94  					"%v (input script bytes %x, prev output "+
    95  					"script bytes %x)", txVI.tx.Hash(),
    96  					txVI.txInIndex, originTxHash,
    97  					originTxIndex, err, sigScript, pkScript)
    98  				err := ruleError(ErrScriptMalformed, str)
    99  				v.sendResult(err)
   100  				break out
   101  			}
   102  
   103  			// Execute the script pair.
   104  			if err := vm.Execute(); err != nil {
   105  				str := fmt.Sprintf("failed to validate input "+
   106  					"%s:%d which references output %s:%d - "+
   107  					"%v (input script bytes %x, prev output "+
   108  					"script bytes %x)", txVI.tx.Hash(),
   109  					txVI.txInIndex, originTxHash,
   110  					originTxIndex, err, sigScript, pkScript)
   111  				err := ruleError(ErrScriptValidation, str)
   112  				v.sendResult(err)
   113  				break out
   114  			}
   115  
   116  			// Validation succeeded.
   117  			v.sendResult(nil)
   118  
   119  		case <-v.quitChan:
   120  			break out
   121  		}
   122  	}
   123  }
   124  
   125  // Validate validates the scripts for all of the passed transaction inputs using
   126  // multiple goroutines.
   127  func (v *txValidator) Validate(items []*txValidateItem) error {
   128  	if len(items) == 0 {
   129  		return nil
   130  	}
   131  
   132  	// Limit the number of goroutines to do script validation based on the
   133  	// number of processor cores.  This help ensure the system stays
   134  	// reasonably responsive under heavy load.
   135  	maxGoRoutines := runtime.NumCPU() * 3
   136  	if maxGoRoutines <= 0 {
   137  		maxGoRoutines = 1
   138  	}
   139  	if maxGoRoutines > len(items) {
   140  		maxGoRoutines = len(items)
   141  	}
   142  
   143  	// Start up validation handlers that are used to asynchronously
   144  	// validate each transaction input.
   145  	for i := 0; i < maxGoRoutines; i++ {
   146  		go v.validateHandler()
   147  	}
   148  
   149  	// Validate each of the inputs.  The quit channel is closed when any
   150  	// errors occur so all processing goroutines exit regardless of which
   151  	// input had the validation error.
   152  	numInputs := len(items)
   153  	currentItem := 0
   154  	processedItems := 0
   155  	for processedItems < numInputs {
   156  		// Only send items while there are still items that need to
   157  		// be processed.  The select statement will never select a nil
   158  		// channel.
   159  		var validateChan chan *txValidateItem
   160  		var item *txValidateItem
   161  		if currentItem < numInputs {
   162  			validateChan = v.validateChan
   163  			item = items[currentItem]
   164  		}
   165  
   166  		select {
   167  		case validateChan <- item:
   168  			currentItem++
   169  
   170  		case err := <-v.resultChan:
   171  			processedItems++
   172  			if err != nil {
   173  				close(v.quitChan)
   174  				return err
   175  			}
   176  		}
   177  	}
   178  
   179  	close(v.quitChan)
   180  	return nil
   181  }
   182  
   183  // newTxValidator returns a new instance of txValidator to be used for
   184  // validating transaction scripts asynchronously.
   185  func newTxValidator(utxoView *UtxoViewpoint, flags txscript.ScriptFlags, sigCache *txscript.SigCache) *txValidator {
   186  	return &txValidator{
   187  		validateChan: make(chan *txValidateItem),
   188  		quitChan:     make(chan struct{}),
   189  		resultChan:   make(chan error),
   190  		utxoView:     utxoView,
   191  		sigCache:     sigCache,
   192  		flags:        flags,
   193  	}
   194  }
   195  
   196  // ValidateTransactionScripts validates the scripts for the passed transaction
   197  // using multiple goroutines.
   198  func ValidateTransactionScripts(tx *dcrutil.Tx, utxoView *UtxoViewpoint, flags txscript.ScriptFlags, sigCache *txscript.SigCache) error {
   199  	// Collect all of the transaction inputs and required information for
   200  	// validation.
   201  	txIns := tx.MsgTx().TxIn
   202  	txValItems := make([]*txValidateItem, 0, len(txIns))
   203  	for txInIdx, txIn := range txIns {
   204  		// Skip coinbases.
   205  		if txIn.PreviousOutPoint.Index == math.MaxUint32 {
   206  			continue
   207  		}
   208  
   209  		txVI := &txValidateItem{
   210  			txInIndex: txInIdx,
   211  			txIn:      txIn,
   212  			tx:        tx,
   213  		}
   214  		txValItems = append(txValItems, txVI)
   215  	}
   216  
   217  	// Validate all of the inputs.
   218  	return newTxValidator(utxoView, flags, sigCache).Validate(txValItems)
   219  
   220  }
   221  
   222  // checkBlockScripts executes and validates the scripts for all transactions in
   223  // the passed block using multiple goroutines.
   224  // txTree = true is TxTreeRegular, txTree = false is TxTreeStake.
   225  func checkBlockScripts(block *dcrutil.Block, utxoView *UtxoViewpoint, txTree bool,
   226  	scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache) error {
   227  
   228  	// Collect all of the transaction inputs and required information for
   229  	// validation for all transactions in the block into a single slice.
   230  	numInputs := 0
   231  	var txs []*dcrutil.Tx
   232  
   233  	// TxTreeRegular handling.
   234  	if txTree {
   235  		txs = block.Transactions()
   236  	} else { // TxTreeStake
   237  		txs = block.STransactions()
   238  	}
   239  
   240  	for _, tx := range txs {
   241  		numInputs += len(tx.MsgTx().TxIn)
   242  	}
   243  	txValItems := make([]*txValidateItem, 0, numInputs)
   244  	for _, tx := range txs {
   245  		for txInIdx, txIn := range tx.MsgTx().TxIn {
   246  			// Skip coinbases.
   247  			if txIn.PreviousOutPoint.Index == math.MaxUint32 {
   248  				continue
   249  			}
   250  
   251  			txVI := &txValidateItem{
   252  				txInIndex: txInIdx,
   253  				txIn:      txIn,
   254  				tx:        tx,
   255  			}
   256  			txValItems = append(txValItems, txVI)
   257  		}
   258  	}
   259  
   260  	// Validate all of the inputs.
   261  	return newTxValidator(utxoView, scriptFlags, sigCache).Validate(txValItems)
   262  }