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