github.com/dashpay/godash@v0.0.0-20160726055534-e038a21e0e3d/blockchain/scriptval.go (about) 1 // Copyright (c) 2013-2016 The btcsuite developers 2 // Copyright (c) 2016 The Dash 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/dashpay/godash/txscript" 14 "github.com/dashpay/godash/wire" 15 "github.com/dashpay/godashutil" 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 *godashutil.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.Sha()) 66 err := ruleError(ErrMissingTx, 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.Sha(), 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 vm, err := txscript.NewEngine(pkScript, txVI.tx.MsgTx(), 88 txVI.txInIndex, v.flags, v.sigCache) 89 if err != nil { 90 str := fmt.Sprintf("failed to parse input "+ 91 "%s:%d which references output %s:%d - "+ 92 "%v (input script bytes %x, prev output "+ 93 "script bytes %x)", txVI.tx.Sha(), 94 txVI.txInIndex, originTxHash, 95 originTxIndex, err, sigScript, pkScript) 96 err := ruleError(ErrScriptMalformed, str) 97 v.sendResult(err) 98 break out 99 } 100 101 // Execute the script pair. 102 if err := vm.Execute(); err != nil { 103 str := fmt.Sprintf("failed to validate input "+ 104 "%s:%d which references output %s:%d - "+ 105 "%v (input script bytes %x, prev output "+ 106 "script bytes %x)", txVI.tx.Sha(), 107 txVI.txInIndex, originTxHash, 108 originTxIndex, err, sigScript, pkScript) 109 err := ruleError(ErrScriptValidation, str) 110 v.sendResult(err) 111 break out 112 } 113 114 // Validation succeeded. 115 v.sendResult(nil) 116 117 case <-v.quitChan: 118 break out 119 } 120 } 121 } 122 123 // Validate validates the scripts for all of the passed transaction inputs using 124 // multiple goroutines. 125 func (v *txValidator) Validate(items []*txValidateItem) error { 126 if len(items) == 0 { 127 return nil 128 } 129 130 // Limit the number of goroutines to do script validation based on the 131 // number of processor cores. This help ensure the system stays 132 // reasonably responsive under heavy load. 133 maxGoRoutines := runtime.NumCPU() * 3 134 if maxGoRoutines <= 0 { 135 maxGoRoutines = 1 136 } 137 if maxGoRoutines > len(items) { 138 maxGoRoutines = len(items) 139 } 140 141 // Start up validation handlers that are used to asynchronously 142 // validate each transaction input. 143 for i := 0; i < maxGoRoutines; i++ { 144 go v.validateHandler() 145 } 146 147 // Validate each of the inputs. The quit channel is closed when any 148 // errors occur so all processing goroutines exit regardless of which 149 // input had the validation error. 150 numInputs := len(items) 151 currentItem := 0 152 processedItems := 0 153 for processedItems < numInputs { 154 // Only send items while there are still items that need to 155 // be processed. The select statement will never select a nil 156 // channel. 157 var validateChan chan *txValidateItem 158 var item *txValidateItem 159 if currentItem < numInputs { 160 validateChan = v.validateChan 161 item = items[currentItem] 162 } 163 164 select { 165 case validateChan <- item: 166 currentItem++ 167 168 case err := <-v.resultChan: 169 processedItems++ 170 if err != nil { 171 close(v.quitChan) 172 return err 173 } 174 } 175 } 176 177 close(v.quitChan) 178 return nil 179 } 180 181 // newTxValidator returns a new instance of txValidator to be used for 182 // validating transaction scripts asynchronously. 183 func newTxValidator(utxoView *UtxoViewpoint, flags txscript.ScriptFlags, sigCache *txscript.SigCache) *txValidator { 184 return &txValidator{ 185 validateChan: make(chan *txValidateItem), 186 quitChan: make(chan struct{}), 187 resultChan: make(chan error), 188 utxoView: utxoView, 189 sigCache: sigCache, 190 flags: flags, 191 } 192 } 193 194 // ValidateTransactionScripts validates the scripts for the passed transaction 195 // using multiple goroutines. 196 func ValidateTransactionScripts(tx *godashutil.Tx, utxoView *UtxoViewpoint, flags txscript.ScriptFlags, sigCache *txscript.SigCache) error { 197 // Collect all of the transaction inputs and required information for 198 // validation. 199 txIns := tx.MsgTx().TxIn 200 txValItems := make([]*txValidateItem, 0, len(txIns)) 201 for txInIdx, txIn := range txIns { 202 // Skip coinbases. 203 if txIn.PreviousOutPoint.Index == math.MaxUint32 { 204 continue 205 } 206 207 txVI := &txValidateItem{ 208 txInIndex: txInIdx, 209 txIn: txIn, 210 tx: tx, 211 } 212 txValItems = append(txValItems, txVI) 213 } 214 215 // Validate all of the inputs. 216 validator := newTxValidator(utxoView, flags, sigCache) 217 if err := validator.Validate(txValItems); err != nil { 218 return err 219 } 220 221 return nil 222 } 223 224 // checkBlockScripts executes and validates the scripts for all transactions in 225 // the passed block using multiple goroutines. 226 func checkBlockScripts(block *godashutil.Block, utxoView *UtxoViewpoint, scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache) error { 227 // Collect all of the transaction inputs and required information for 228 // validation for all transactions in the block into a single slice. 229 numInputs := 0 230 for _, tx := range block.Transactions() { 231 numInputs += len(tx.MsgTx().TxIn) 232 } 233 txValItems := make([]*txValidateItem, 0, numInputs) 234 for _, tx := range block.Transactions() { 235 for txInIdx, txIn := range tx.MsgTx().TxIn { 236 // Skip coinbases. 237 if txIn.PreviousOutPoint.Index == math.MaxUint32 { 238 continue 239 } 240 241 txVI := &txValidateItem{ 242 txInIndex: txInIdx, 243 txIn: txIn, 244 tx: tx, 245 } 246 txValItems = append(txValItems, txVI) 247 } 248 } 249 250 // Validate all of the inputs. 251 validator := newTxValidator(utxoView, scriptFlags, sigCache) 252 if err := validator.Validate(txValItems); err != nil { 253 return err 254 } 255 256 return nil 257 }