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