github.com/lbryio/lbcd@v0.22.119/blockchain/scriptval.go (about)

     1  // Copyright (c) 2013-2016 The btcsuite developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package blockchain
     6  
     7  import (
     8  	"fmt"
     9  	"math"
    10  	"runtime"
    11  	"time"
    12  
    13  	"github.com/lbryio/lbcd/txscript"
    14  	"github.com/lbryio/lbcd/wire"
    15  	btcutil "github.com/lbryio/lbcutil"
    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        *btcutil.Tx
    23  	sigHashes *txscript.TxSigHashes
    24  }
    25  
    26  // txValidator provides a type which asynchronously validates transaction
    27  // inputs.  It provides several channels for communication and a processing
    28  // function that is intended to be in run multiple goroutines.
    29  type txValidator struct {
    30  	validateChan chan *txValidateItem
    31  	quitChan     chan struct{}
    32  	resultChan   chan error
    33  	utxoView     *UtxoViewpoint
    34  	flags        txscript.ScriptFlags
    35  	sigCache     *txscript.SigCache
    36  	hashCache    *txscript.HashCache
    37  }
    38  
    39  // sendResult sends the result of a script pair validation on the internal
    40  // result channel while respecting the quit channel.  This allows orderly
    41  // shutdown when the validation process is aborted early due to a validation
    42  // error in one of the other goroutines.
    43  func (v *txValidator) sendResult(result error) {
    44  	select {
    45  	case v.resultChan <- result:
    46  	case <-v.quitChan:
    47  	}
    48  }
    49  
    50  // validateHandler consumes items to validate from the internal validate channel
    51  // and returns the result of the validation on the internal result channel. It
    52  // must be run as a goroutine.
    53  func (v *txValidator) validateHandler() {
    54  out:
    55  	for {
    56  		select {
    57  		case txVI := <-v.validateChan:
    58  			// Ensure the referenced input utxo is available.
    59  			txIn := txVI.txIn
    60  			utxo := v.utxoView.LookupEntry(txIn.PreviousOutPoint)
    61  			if utxo == nil {
    62  				str := fmt.Sprintf("unable to find unspent "+
    63  					"output %v referenced from "+
    64  					"transaction %s:%d",
    65  					txIn.PreviousOutPoint, txVI.tx.Hash(),
    66  					txVI.txInIndex)
    67  				err := ruleError(ErrMissingTxOut, str)
    68  				v.sendResult(err)
    69  				break out
    70  			}
    71  
    72  			// Create a new script engine for the script pair.
    73  			sigScript := txIn.SignatureScript
    74  			witness := txIn.Witness
    75  			pkScript := utxo.PkScript()
    76  			inputAmount := utxo.Amount()
    77  			vm, err := txscript.NewEngine(pkScript, txVI.tx.MsgTx(),
    78  				txVI.txInIndex, v.flags, v.sigCache, txVI.sigHashes,
    79  				inputAmount)
    80  			if err != nil {
    81  				str := fmt.Sprintf("failed to parse input "+
    82  					"%s:%d which references output %v - "+
    83  					"%v (input witness %x, input script "+
    84  					"bytes %x, prev output script bytes %x)",
    85  					txVI.tx.Hash(), txVI.txInIndex,
    86  					txIn.PreviousOutPoint, err, witness,
    87  					sigScript, pkScript)
    88  				err := ruleError(ErrScriptMalformed, str)
    89  				v.sendResult(err)
    90  				break out
    91  			}
    92  
    93  			// Execute the script pair.
    94  			if err := vm.Execute(); err != nil {
    95  				str := fmt.Sprintf("failed to validate input "+
    96  					"%s:%d which references output %v - "+
    97  					"%v (input witness %x, input script "+
    98  					"bytes %x, prev output script bytes %x)",
    99  					txVI.tx.Hash(), txVI.txInIndex,
   100  					txIn.PreviousOutPoint, err, witness,
   101  					sigScript, pkScript)
   102  				err := ruleError(ErrScriptValidation, str)
   103  				v.sendResult(err)
   104  				break out
   105  			}
   106  
   107  			// Validation succeeded.
   108  			v.sendResult(nil)
   109  
   110  		case <-v.quitChan:
   111  			break out
   112  		}
   113  	}
   114  }
   115  
   116  // Validate validates the scripts for all of the passed transaction inputs using
   117  // multiple goroutines.
   118  func (v *txValidator) Validate(items []*txValidateItem) error {
   119  	if len(items) == 0 {
   120  		return nil
   121  	}
   122  
   123  	// Limit the number of goroutines to do script validation based on the
   124  	// number of processor cores.  This helps ensure the system stays
   125  	// reasonably responsive under heavy load.
   126  	maxGoRoutines := runtime.NumCPU() * 3
   127  	if maxGoRoutines <= 0 {
   128  		maxGoRoutines = 1
   129  	}
   130  	if maxGoRoutines > len(items) {
   131  		maxGoRoutines = len(items)
   132  	}
   133  
   134  	// Start up validation handlers that are used to asynchronously
   135  	// validate each transaction input.
   136  	for i := 0; i < maxGoRoutines; i++ {
   137  		go v.validateHandler()
   138  	}
   139  
   140  	// Validate each of the inputs.  The quit channel is closed when any
   141  	// errors occur so all processing goroutines exit regardless of which
   142  	// input had the validation error.
   143  	numInputs := len(items)
   144  	currentItem := 0
   145  	processedItems := 0
   146  	for processedItems < numInputs {
   147  		// Only send items while there are still items that need to
   148  		// be processed.  The select statement will never select a nil
   149  		// channel.
   150  		var validateChan chan *txValidateItem
   151  		var item *txValidateItem
   152  		if currentItem < numInputs {
   153  			validateChan = v.validateChan
   154  			item = items[currentItem]
   155  		}
   156  
   157  		select {
   158  		case validateChan <- item:
   159  			currentItem++
   160  
   161  		case err := <-v.resultChan:
   162  			processedItems++
   163  			if err != nil {
   164  				close(v.quitChan)
   165  				return err
   166  			}
   167  		}
   168  	}
   169  
   170  	close(v.quitChan)
   171  	return nil
   172  }
   173  
   174  // newTxValidator returns a new instance of txValidator to be used for
   175  // validating transaction scripts asynchronously.
   176  func newTxValidator(utxoView *UtxoViewpoint, flags txscript.ScriptFlags,
   177  	sigCache *txscript.SigCache, hashCache *txscript.HashCache) *txValidator {
   178  	return &txValidator{
   179  		validateChan: make(chan *txValidateItem),
   180  		quitChan:     make(chan struct{}),
   181  		resultChan:   make(chan error),
   182  		utxoView:     utxoView,
   183  		sigCache:     sigCache,
   184  		hashCache:    hashCache,
   185  		flags:        flags,
   186  	}
   187  }
   188  
   189  // ValidateTransactionScripts validates the scripts for the passed transaction
   190  // using multiple goroutines.
   191  func ValidateTransactionScripts(tx *btcutil.Tx, utxoView *UtxoViewpoint,
   192  	flags txscript.ScriptFlags, sigCache *txscript.SigCache,
   193  	hashCache *txscript.HashCache) error {
   194  
   195  	// First determine if segwit is active according to the scriptFlags. If
   196  	// it isn't then we don't need to interact with the HashCache.
   197  	segwitActive := flags&txscript.ScriptVerifyWitness == txscript.ScriptVerifyWitness
   198  
   199  	// If the hashcache doesn't yet has the sighash midstate for this
   200  	// transaction, then we'll compute them now so we can re-use them
   201  	// amongst all worker validation goroutines.
   202  	if segwitActive && tx.MsgTx().HasWitness() &&
   203  		!hashCache.ContainsHashes(tx.Hash()) {
   204  		hashCache.AddSigHashes(tx.MsgTx())
   205  	}
   206  
   207  	var cachedHashes *txscript.TxSigHashes
   208  	if segwitActive && tx.MsgTx().HasWitness() {
   209  		// The same pointer to the transaction's sighash midstate will
   210  		// be re-used amongst all validation goroutines. By
   211  		// pre-computing the sighash here instead of during validation,
   212  		// we ensure the sighashes
   213  		// are only computed once.
   214  		cachedHashes, _ = hashCache.GetSigHashes(tx.Hash())
   215  	}
   216  
   217  	// Collect all of the transaction inputs and required information for
   218  	// validation.
   219  	txIns := tx.MsgTx().TxIn
   220  	txValItems := make([]*txValidateItem, 0, len(txIns))
   221  	for txInIdx, txIn := range txIns {
   222  		// Skip coinbases.
   223  		if txIn.PreviousOutPoint.Index == math.MaxUint32 {
   224  			continue
   225  		}
   226  
   227  		txVI := &txValidateItem{
   228  			txInIndex: txInIdx,
   229  			txIn:      txIn,
   230  			tx:        tx,
   231  			sigHashes: cachedHashes,
   232  		}
   233  		txValItems = append(txValItems, txVI)
   234  	}
   235  
   236  	// Validate all of the inputs.
   237  	validator := newTxValidator(utxoView, flags, sigCache, hashCache)
   238  	return validator.Validate(txValItems)
   239  }
   240  
   241  // checkBlockScripts executes and validates the scripts for all transactions in
   242  // the passed block using multiple goroutines.
   243  func checkBlockScripts(block *btcutil.Block, utxoView *UtxoViewpoint,
   244  	scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache,
   245  	hashCache *txscript.HashCache) error {
   246  
   247  	// First determine if segwit is active according to the scriptFlags. If
   248  	// it isn't then we don't need to interact with the HashCache.
   249  	segwitActive := scriptFlags&txscript.ScriptVerifyWitness == txscript.ScriptVerifyWitness
   250  
   251  	// Collect all of the transaction inputs and required information for
   252  	// validation for all transactions in the block into a single slice.
   253  	numInputs := 0
   254  	for _, tx := range block.Transactions() {
   255  		numInputs += len(tx.MsgTx().TxIn)
   256  	}
   257  	txValItems := make([]*txValidateItem, 0, numInputs)
   258  	for _, tx := range block.Transactions() {
   259  		hash := tx.Hash()
   260  
   261  		// If the HashCache is present, and it doesn't yet contain the
   262  		// partial sighashes for this transaction, then we add the
   263  		// sighashes for the transaction. This allows us to take
   264  		// advantage of the potential speed savings due to the new
   265  		// digest algorithm (BIP0143).
   266  		if segwitActive && tx.HasWitness() && hashCache != nil &&
   267  			!hashCache.ContainsHashes(hash) {
   268  
   269  			hashCache.AddSigHashes(tx.MsgTx())
   270  		}
   271  
   272  		var cachedHashes *txscript.TxSigHashes
   273  		if segwitActive && tx.HasWitness() {
   274  			if hashCache != nil {
   275  				cachedHashes, _ = hashCache.GetSigHashes(hash)
   276  			} else {
   277  				cachedHashes = txscript.NewTxSigHashes(tx.MsgTx())
   278  			}
   279  		}
   280  
   281  		for txInIdx, txIn := range tx.MsgTx().TxIn {
   282  			// Skip coinbases.
   283  			if txIn.PreviousOutPoint.Index == math.MaxUint32 {
   284  				continue
   285  			}
   286  
   287  			txVI := &txValidateItem{
   288  				txInIndex: txInIdx,
   289  				txIn:      txIn,
   290  				tx:        tx,
   291  				sigHashes: cachedHashes,
   292  			}
   293  			txValItems = append(txValItems, txVI)
   294  		}
   295  	}
   296  
   297  	// Validate all of the inputs.
   298  	validator := newTxValidator(utxoView, scriptFlags, sigCache, hashCache)
   299  	start := time.Now()
   300  	if err := validator.Validate(txValItems); err != nil {
   301  		return err
   302  	}
   303  	elapsed := time.Since(start)
   304  
   305  	log.Tracef("block %v took %v to verify", block.Hash(), elapsed)
   306  
   307  	// If the HashCache is present, once we have validated the block, we no
   308  	// longer need the cached hashes for these transactions, so we purge
   309  	// them from the cache.
   310  	if segwitActive && hashCache != nil {
   311  		for _, tx := range block.Transactions() {
   312  			if tx.MsgTx().HasWitness() {
   313  				hashCache.PurgeSigHashes(tx.Hash())
   314  			}
   315  		}
   316  	}
   317  
   318  	return nil
   319  }