github.com/BlockABC/godash@v0.0.0-20191112120524-f4aa3a32c566/blockchain/scriptval.go (about)

     1  // Copyright (c) 2013-2016 The btcsuite developers
     2  // Copyright (c) 2016 The Dash 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/BlockABC/godash/txscript"
    14  	"github.com/BlockABC/godash/wire"
    15  	"github.com/BlockABC/godashutil"
    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        *godashutil.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.Sha())
    66  				err := ruleError(ErrMissingTx, 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.Sha(),
    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  			vm, err := txscript.NewEngine(pkScript, txVI.tx.MsgTx(),
    88  				txVI.txInIndex, v.flags, v.sigCache)
    89  			if err != nil {
    90  				str := fmt.Sprintf("failed to parse input "+
    91  					"%s:%d which references output %s:%d - "+
    92  					"%v (input script bytes %x, prev output "+
    93  					"script bytes %x)", txVI.tx.Sha(),
    94  					txVI.txInIndex, originTxHash,
    95  					originTxIndex, err, sigScript, pkScript)
    96  				err := ruleError(ErrScriptMalformed, str)
    97  				v.sendResult(err)
    98  				break out
    99  			}
   100  
   101  			// Execute the script pair.
   102  			if err := vm.Execute(); err != nil {
   103  				str := fmt.Sprintf("failed to validate input "+
   104  					"%s:%d which references output %s:%d - "+
   105  					"%v (input script bytes %x, prev output "+
   106  					"script bytes %x)", txVI.tx.Sha(),
   107  					txVI.txInIndex, originTxHash,
   108  					originTxIndex, err, sigScript, pkScript)
   109  				err := ruleError(ErrScriptValidation, str)
   110  				v.sendResult(err)
   111  				break out
   112  			}
   113  
   114  			// Validation succeeded.
   115  			v.sendResult(nil)
   116  
   117  		case <-v.quitChan:
   118  			break out
   119  		}
   120  	}
   121  }
   122  
   123  // Validate validates the scripts for all of the passed transaction inputs using
   124  // multiple goroutines.
   125  func (v *txValidator) Validate(items []*txValidateItem) error {
   126  	if len(items) == 0 {
   127  		return nil
   128  	}
   129  
   130  	// Limit the number of goroutines to do script validation based on the
   131  	// number of processor cores.  This help ensure the system stays
   132  	// reasonably responsive under heavy load.
   133  	maxGoRoutines := runtime.NumCPU() * 3
   134  	if maxGoRoutines <= 0 {
   135  		maxGoRoutines = 1
   136  	}
   137  	if maxGoRoutines > len(items) {
   138  		maxGoRoutines = len(items)
   139  	}
   140  
   141  	// Start up validation handlers that are used to asynchronously
   142  	// validate each transaction input.
   143  	for i := 0; i < maxGoRoutines; i++ {
   144  		go v.validateHandler()
   145  	}
   146  
   147  	// Validate each of the inputs.  The quit channel is closed when any
   148  	// errors occur so all processing goroutines exit regardless of which
   149  	// input had the validation error.
   150  	numInputs := len(items)
   151  	currentItem := 0
   152  	processedItems := 0
   153  	for processedItems < numInputs {
   154  		// Only send items while there are still items that need to
   155  		// be processed.  The select statement will never select a nil
   156  		// channel.
   157  		var validateChan chan *txValidateItem
   158  		var item *txValidateItem
   159  		if currentItem < numInputs {
   160  			validateChan = v.validateChan
   161  			item = items[currentItem]
   162  		}
   163  
   164  		select {
   165  		case validateChan <- item:
   166  			currentItem++
   167  
   168  		case err := <-v.resultChan:
   169  			processedItems++
   170  			if err != nil {
   171  				close(v.quitChan)
   172  				return err
   173  			}
   174  		}
   175  	}
   176  
   177  	close(v.quitChan)
   178  	return nil
   179  }
   180  
   181  // newTxValidator returns a new instance of txValidator to be used for
   182  // validating transaction scripts asynchronously.
   183  func newTxValidator(utxoView *UtxoViewpoint, flags txscript.ScriptFlags, sigCache *txscript.SigCache) *txValidator {
   184  	return &txValidator{
   185  		validateChan: make(chan *txValidateItem),
   186  		quitChan:     make(chan struct{}),
   187  		resultChan:   make(chan error),
   188  		utxoView:     utxoView,
   189  		sigCache:     sigCache,
   190  		flags:        flags,
   191  	}
   192  }
   193  
   194  // ValidateTransactionScripts validates the scripts for the passed transaction
   195  // using multiple goroutines.
   196  func ValidateTransactionScripts(tx *godashutil.Tx, utxoView *UtxoViewpoint, flags txscript.ScriptFlags, sigCache *txscript.SigCache) error {
   197  	// Collect all of the transaction inputs and required information for
   198  	// validation.
   199  	txIns := tx.MsgTx().TxIn
   200  	txValItems := make([]*txValidateItem, 0, len(txIns))
   201  	for txInIdx, txIn := range txIns {
   202  		// Skip coinbases.
   203  		if txIn.PreviousOutPoint.Index == math.MaxUint32 {
   204  			continue
   205  		}
   206  
   207  		txVI := &txValidateItem{
   208  			txInIndex: txInIdx,
   209  			txIn:      txIn,
   210  			tx:        tx,
   211  		}
   212  		txValItems = append(txValItems, txVI)
   213  	}
   214  
   215  	// Validate all of the inputs.
   216  	validator := newTxValidator(utxoView, flags, sigCache)
   217  	if err := validator.Validate(txValItems); err != nil {
   218  		return err
   219  	}
   220  
   221  	return nil
   222  }
   223  
   224  // checkBlockScripts executes and validates the scripts for all transactions in
   225  // the passed block using multiple goroutines.
   226  func checkBlockScripts(block *godashutil.Block, utxoView *UtxoViewpoint, scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache) error {
   227  	// Collect all of the transaction inputs and required information for
   228  	// validation for all transactions in the block into a single slice.
   229  	numInputs := 0
   230  	for _, tx := range block.Transactions() {
   231  		numInputs += len(tx.MsgTx().TxIn)
   232  	}
   233  	txValItems := make([]*txValidateItem, 0, numInputs)
   234  	for _, tx := range block.Transactions() {
   235  		for txInIdx, txIn := range tx.MsgTx().TxIn {
   236  			// Skip coinbases.
   237  			if txIn.PreviousOutPoint.Index == math.MaxUint32 {
   238  				continue
   239  			}
   240  
   241  			txVI := &txValidateItem{
   242  				txInIndex: txInIdx,
   243  				txIn:      txIn,
   244  				tx:        tx,
   245  			}
   246  			txValItems = append(txValItems, txVI)
   247  		}
   248  	}
   249  
   250  	// Validate all of the inputs.
   251  	validator := newTxValidator(utxoView, scriptFlags, sigCache)
   252  	if err := validator.Validate(txValItems); err != nil {
   253  		return err
   254  	}
   255  
   256  	return nil
   257  }