1 package blockchain
2 3 import (
4 "fmt"
5 "github.com/p9c/p9/pkg/block"
6 "math"
7 "runtime"
8 9 "github.com/p9c/p9/pkg/qu"
10 11 "github.com/p9c/p9/pkg/hardfork"
12 "github.com/p9c/p9/pkg/txscript"
13 "github.com/p9c/p9/pkg/util"
14 "github.com/p9c/p9/pkg/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 *util.Tx
22 sigHashes *txscript.TxSigHashes
23 }
24 25 // txValidator provides a type which asynchronously validates transaction inputs. It provides several channels for
26 // communication and a processing function that is intended to be in run multiple goroutines.
27 type txValidator struct {
28 validateChan chan *txValidateItem
29 quitChan qu.C
30 resultChan chan error
31 utxoView *UtxoViewpoint
32 flags txscript.ScriptFlags
33 sigCache *txscript.SigCache
34 hashCache *txscript.HashCache
35 }
36 37 // sendResult sends the result of a script pair validation on the internal result channel while respecting the quit
38 // channel.
39 //
40 // This allows orderly shutdown when the validation process is aborted early due to a validation error in one of the
41 // other goroutines.
42 func (v *txValidator) sendResult(result error) {
43 select {
44 case v.resultChan <- result:
45 case <-v.quitChan.Wait():
46 }
47 }
48 49 // validateHandler consumes items to validate from the internal validate channel and returns the result of the
50 // validation on the internal result channel. It 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 utxo is available.
57 txIn := txVI.txIn
58 utxo := v.utxoView.LookupEntry(txIn.PreviousOutPoint)
59 if utxo == nil {
60 str := fmt.Sprintf(
61 "unable to find unspent "+
62 "output %v referenced from "+
63 "transaction %s:%d",
64 txIn.PreviousOutPoint, txVI.tx.Hash(),
65 txVI.txInIndex,
66 )
67 var e RuleError
68 e = ruleError(ErrMissingTxOut, str)
69 v.sendResult(e)
70 break out
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, e := txscript.NewEngine(
78 pkScript, txVI.tx.MsgTx(),
79 txVI.txInIndex, v.flags, v.sigCache, txVI.sigHashes,
80 inputAmount,
81 )
82 if e != nil {
83 str := fmt.Sprintf(
84 "failed to parse input "+
85 "%s:%d which references output %v - "+
86 "%v (input witness x, input script "+
87 "bytes %x, prev output script bytes %x)",
88 txVI.tx.Hash(), txVI.txInIndex,
89 txIn.PreviousOutPoint, e, // witness,
90 sigScript, pkScript,
91 )
92 e = ruleError(ErrScriptMalformed, str)
93 v.sendResult(e)
94 break out
95 }
96 // Execute the script pair.
97 if e := vm.Execute(); E.Chk(e) {
98 str := fmt.Sprintf(
99 "failed to validate input "+
100 "%s:%d which references output %v - "+
101 "%v (input witness x, input script "+
102 "bytes %x, prev output script bytes %x)",
103 txVI.tx.Hash(), txVI.txInIndex,
104 txIn.PreviousOutPoint, e, // witness,
105 sigScript, pkScript,
106 )
107 e = ruleError(ErrScriptValidation, str)
108 v.sendResult(e)
109 break out
110 }
111 // Validation succeeded.
112 v.sendResult(nil)
113 case <-v.quitChan.Wait():
114 break out
115 }
116 }
117 }
118 119 // Validate validates the scripts for all of the passed transaction inputs using multiple goroutines.
120 func (v *txValidator) Validate(items []*txValidateItem) (e error) {
121 if len(items) == 0 {
122 return nil
123 }
124 // Limit the number of goroutines to do script validation based on the number of processor cores.
125 //
126 // This helps ensure the system stays reasonably responsive under heavy load.
127 maxGoRoutines := runtime.NumCPU() * 3
128 if maxGoRoutines <= 0 {
129 maxGoRoutines = 1
130 }
131 if maxGoRoutines > len(items)*3 {
132 maxGoRoutines = len(items) * 3
133 }
134 // maxGoRoutines = 1
135 //
136 // Start up validation handlers that are used to asynchronously validate each transaction input.
137 //
138 // TODO: this creates an insane amount of goroutines that run for tens of milliseconds each and... well... parallelize...
139 for i := 0; i < maxGoRoutines; i++ {
140 go v.validateHandler()
141 }
142 // Validate each of the inputs.
143 //
144 // The quit channel is closed when any errors occur so all processing goroutines exit regardless of which input had
145 // the validation error.
146 numInputs := len(items)
147 currentItem := 0
148 processedItems := 0
149 for processedItems < numInputs {
150 // Only send items while there are still items that need to be processed. The select statement will never select
151 // a nil channel.
152 validateChan := make(chan *txValidateItem)
153 var item *txValidateItem
154 if currentItem < numInputs {
155 validateChan = v.validateChan
156 item = items[currentItem]
157 }
158 select {
159 case validateChan <- item:
160 currentItem++
161 case e := <-v.resultChan:
162 processedItems++
163 if e != nil {
164 v.quitChan.Q()
165 return e
166 }
167 }
168 }
169 v.quitChan.Q()
170 return nil
171 }
172 173 // newTxValidator returns a new instance of txValidator to be used for validating transaction scripts asynchronously.
174 func newTxValidator(
175 utxoView *UtxoViewpoint, flags txscript.ScriptFlags,
176 sigCache *txscript.SigCache, hashCache *txscript.HashCache,
177 ) *txValidator {
178 return &txValidator{
179 validateChan: make(chan *txValidateItem),
180 quitChan: qu.T(),
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 using multiple goroutines.
190 func ValidateTransactionScripts(
191 b *BlockChain, tx *util.Tx, utxoView *UtxoViewpoint, flags txscript.ScriptFlags, sigCache *txscript.SigCache,
192 hashCache *txscript.HashCache,
193 ) (e error) {
194 // // First determine if segwit is active according to the scriptFlags. If it isn't then we don't need to interact with
195 // // the HashCache.
196 // segwitActive := flags&txscript.ScriptVerifyWitness == txscript.ScriptVerifyWitness
197 // // If the hashcache doesn't yet has the sighash midstate for this transaction, then we'll compute them now so we can
198 // // re-use them amongst all worker validation goroutines.
199 // if segwitActive && tx.MsgTx().HasWitness() &&
200 // !hashCache.ContainsHashes(tx.Hash()) {
201 // hashCache.AddSigHashes(tx.MsgTx())
202 // }
203 var cachedHashes *txscript.TxSigHashes
204 // if segwitActive && tx.MsgTx().HasWitness() {
205 // // The same pointer to the transaction's sighash midstate will be re -used amongst all validation goroutines. By
206 // // pre-computing the sighash here instead of during validation, we ensure the sighashes are only computed once.
207 // cachedHashes, _ = hashCache.GetSigHashes(tx.Hash())
208 // }
209 if ContainsBlacklisted(b, tx, hardfork.Blacklist) {
210 return ruleError(ErrBlacklisted, "transaction contains blacklisted address ")
211 }
212 // Collect all of the transaction inputs and required information for
213 // validation.
214 txIns := tx.MsgTx().TxIn
215 txValItems := make([]*txValidateItem, 0, len(txIns))
216 for txInIdx, txIn := range txIns {
217 // Skip coinbases.
218 if txIn.PreviousOutPoint.Index == math.MaxUint32 {
219 continue
220 }
221 txVI := &txValidateItem{
222 txInIndex: txInIdx,
223 txIn: txIn,
224 tx: tx,
225 sigHashes: cachedHashes,
226 }
227 txValItems = append(txValItems, txVI)
228 }
229 // Validate all of the inputs.
230 validator := newTxValidator(utxoView, flags, sigCache, hashCache)
231 return validator.Validate(txValItems)
232 }
233 234 // checkBlockScripts executes and validates the scripts for all transactions in
235 // the passed block using multiple goroutines.
236 func checkBlockScripts(
237 block *block.Block, utxoView *UtxoViewpoint,
238 scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache,
239 hashCache *txscript.HashCache,
240 ) (e error) {
241 // // First determine if segwit is active according to the scriptFlags. If it isn't
242 // // then we don't need to interact with the HashCache.
243 // segwitActive := scriptFlags&txscript.ScriptVerifyWitness == txscript.ScriptVerifyWitness
244 // Collect all of the transaction inputs and required information for validation
245 // for all transactions in the block into a single slice.
246 numInputs := 0
247 for _, tx := range block.Transactions() {
248 numInputs += len(tx.MsgTx().TxIn)
249 }
250 txValItems := make([]*txValidateItem, 0, numInputs)
251 for _, tx := range block.Transactions() {
252 // hash := tx.Hash()
253 // If the HashCache is present, and it doesn't yet contain the partial sighashes for this transaction, then we
254 // add the sighashes for the transaction. This allows us to take advantage of the potential speed savings due to
255 // the new digest algorithm (BIP0143).
256 // if segwitActive && tx.HasWitness() && hashCache != nil &&
257 // !hashCache.ContainsHashes(hash) {
258 // hashCache.AddSigHashes(tx.MsgTx())
259 // }
260 var cachedHashes *txscript.TxSigHashes
261 // if segwitActive && tx.HasWitness() {
262 // if hashCache != nil {
263 // cachedHashes, _ = hashCache.GetSigHashes(hash)
264 // } else {
265 // cachedHashes = txscript.NewTxSigHashes(tx.MsgTx())
266 // }
267 // }
268 for txInIdx, txIn := range tx.MsgTx().TxIn {
269 // Skip coinbases.
270 if txIn.PreviousOutPoint.Index == math.MaxUint32 {
271 continue
272 }
273 txVI := &txValidateItem{
274 txInIndex: txInIdx,
275 txIn: txIn,
276 tx: tx,
277 sigHashes: cachedHashes,
278 }
279 txValItems = append(txValItems, txVI)
280 }
281 }
282 // Validate all of the inputs.
283 validator := newTxValidator(utxoView, scriptFlags, sigCache, hashCache)
284 // start := time.Now()
285 if e = validator.Validate(txValItems); E.Chk(e) {
286 return e
287 }
288 // elapsed := time.Since(start)
289 // Tracec(func() string {
290 // return fmt.Sprintf("block %v took %v to verify", block.Hash(), elapsed)
291 // })
292 //
293 // // If the HashCache is present, once we have validated the block, we no longer need the cached hashes for these
294 // // transactions, so we purge them from the cache.
295 // if segwitActive && hashCache != nil {
296 // for _, tx := range block.Transactions() {
297 // if tx.MsgTx().HasWitness() {
298 // hashCache.PurgeSigHashes(tx.Hash())
299 // }
300 // }
301 // }
302 return nil
303 }
304