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