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