scriptval.go raw

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