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 }