OSDN Git Service

validateBlock unit test (#346)
[bytom/vapor.git] / protocol / validation / tx.go
1 package validation
2
3 import (
4         "fmt"
5         "math"
6         "sync"
7
8         "github.com/vapor/config"
9         "github.com/vapor/consensus"
10         "github.com/vapor/errors"
11         "github.com/vapor/math/checked"
12         "github.com/vapor/protocol/bc"
13         "github.com/vapor/protocol/vm"
14 )
15
16 const (
17         validateWorkerNum = 32
18 )
19
20 // validate transaction error
21 var (
22         ErrTxVersion                 = errors.New("invalid transaction version")
23         ErrWrongTransactionSize      = errors.New("invalid transaction size")
24         ErrBadTimeRange              = errors.New("invalid transaction time range")
25         ErrEmptyInputIDs             = errors.New("got the empty InputIDs")
26         ErrNotStandardTx             = errors.New("not standard transaction")
27         ErrWrongCoinbaseTransaction  = errors.New("wrong coinbase transaction")
28         ErrWrongCoinbaseAsset        = errors.New("wrong coinbase assetID")
29         ErrCoinbaseArbitraryOversize = errors.New("coinbase arbitrary size is larger than limit")
30         ErrEmptyResults              = errors.New("transaction has no results")
31         ErrMismatchedAssetID         = errors.New("mismatched assetID")
32         ErrMismatchedPosition        = errors.New("mismatched value source/dest position")
33         ErrMismatchedReference       = errors.New("mismatched reference")
34         ErrMismatchedValue           = errors.New("mismatched value")
35         ErrMissingField              = errors.New("missing required field")
36         ErrNoSource                  = errors.New("no source for value")
37         ErrOverflow                  = errors.New("arithmetic overflow/underflow")
38         ErrPosition                  = errors.New("invalid source or destination position")
39         ErrUnbalanced                = errors.New("unbalanced asset amount between input and output")
40         ErrOverGasCredit             = errors.New("all gas credit has been spend")
41         ErrGasCalculate              = errors.New("gas usage calculate got a math error")
42         ErrVotePubKey                = errors.New("invalid public key of vote")
43         ErrVoteOutputAmount          = errors.New("invalid vote amount")
44 )
45
46 // GasState record the gas usage status
47 type GasState struct {
48         BTMValue   uint64
49         GasLeft    int64
50         GasUsed    int64
51         GasValid   bool
52         StorageGas int64
53 }
54
55 func (g *GasState) setGas(BTMValue int64, txSize int64) error {
56         if BTMValue < 0 {
57                 return errors.Wrap(ErrGasCalculate, "input BTM is negative")
58         }
59
60         g.BTMValue = uint64(BTMValue)
61         var ok bool
62         if g.GasLeft, ok = checked.DivInt64(BTMValue, consensus.ActiveNetParams.VMGasRate); !ok {
63                 return errors.Wrap(ErrGasCalculate, "setGas calc gas amount")
64         }
65
66         if g.GasLeft, ok = checked.AddInt64(g.GasLeft, consensus.ActiveNetParams.DefaultGasCredit); !ok {
67                 return errors.Wrap(ErrGasCalculate, "setGas calc free gas")
68         }
69
70         if g.GasLeft > consensus.ActiveNetParams.MaxGasAmount {
71                 g.GasLeft = consensus.ActiveNetParams.MaxGasAmount
72         }
73
74         if g.StorageGas, ok = checked.MulInt64(txSize, consensus.ActiveNetParams.StorageGasRate); !ok {
75                 return errors.Wrap(ErrGasCalculate, "setGas calc tx storage gas")
76         }
77         return nil
78 }
79
80 func (g *GasState) setGasValid() error {
81         var ok bool
82         if g.GasLeft, ok = checked.SubInt64(g.GasLeft, g.StorageGas); !ok || g.GasLeft < 0 {
83                 return errors.Wrap(ErrGasCalculate, "setGasValid calc gasLeft")
84         }
85
86         if g.GasUsed, ok = checked.AddInt64(g.GasUsed, g.StorageGas); !ok {
87                 return errors.Wrap(ErrGasCalculate, "setGasValid calc gasUsed")
88         }
89
90         g.GasValid = true
91         return nil
92 }
93
94 func (g *GasState) updateUsage(gasLeft int64) error {
95         if gasLeft < 0 {
96                 return errors.Wrap(ErrGasCalculate, "updateUsage input negative gas")
97         }
98
99         if gasUsed, ok := checked.SubInt64(g.GasLeft, gasLeft); ok {
100                 g.GasUsed += gasUsed
101                 g.GasLeft = gasLeft
102         } else {
103                 return errors.Wrap(ErrGasCalculate, "updateUsage calc gas diff")
104         }
105
106         if !g.GasValid && (g.GasUsed > consensus.ActiveNetParams.DefaultGasCredit || g.StorageGas > g.GasLeft) {
107                 return ErrOverGasCredit
108         }
109         return nil
110 }
111
112 // validationState contains the context that must propagate through
113 // the transaction graph when validating entries.
114 type validationState struct {
115         block     *bc.Block
116         tx        *bc.Tx
117         gasStatus *GasState
118         entryID   bc.Hash           // The ID of the nearest enclosing entry
119         sourcePos uint64            // The source position, for validate ValueSources
120         destPos   uint64            // The destination position, for validate ValueDestinations
121         cache     map[bc.Hash]error // Memoized per-entry validation results
122 }
123
124 func checkValid(vs *validationState, e bc.Entry) (err error) {
125         var ok bool
126         entryID := bc.EntryID(e)
127         if err, ok = vs.cache[entryID]; ok {
128                 return err
129         }
130
131         defer func() {
132                 vs.cache[entryID] = err
133         }()
134
135         switch e := e.(type) {
136         case *bc.TxHeader:
137                 for i, resID := range e.ResultIds {
138                         resultEntry := vs.tx.Entries[*resID]
139                         vs2 := *vs
140                         vs2.entryID = *resID
141                         if err = checkValid(&vs2, resultEntry); err != nil {
142                                 return errors.Wrapf(err, "checking result %d", i)
143                         }
144                 }
145
146                 if e.Version == 1 && len(e.ResultIds) == 0 {
147                         return ErrEmptyResults
148                 }
149
150         case *bc.Mux:
151                 parity := make(map[bc.AssetID]int64)
152                 for i, src := range e.Sources {
153                         if src.Value.Amount > math.MaxInt64 {
154                                 return errors.WithDetailf(ErrOverflow, "amount %d exceeds maximum value 2^63", src.Value.Amount)
155                         }
156                         sum, ok := checked.AddInt64(parity[*src.Value.AssetId], int64(src.Value.Amount))
157                         if !ok {
158                                 return errors.WithDetailf(ErrOverflow, "adding %d units of asset %x from mux source %d to total %d overflows int64", src.Value.Amount, src.Value.AssetId.Bytes(), i, parity[*src.Value.AssetId])
159                         }
160                         parity[*src.Value.AssetId] = sum
161                 }
162
163                 for i, dest := range e.WitnessDestinations {
164                         sum, ok := parity[*dest.Value.AssetId]
165                         if !ok {
166                                 return errors.WithDetailf(ErrNoSource, "mux destination %d, asset %x, has no corresponding source", i, dest.Value.AssetId.Bytes())
167                         }
168                         if dest.Value.Amount > math.MaxInt64 {
169                                 return errors.WithDetailf(ErrOverflow, "amount %d exceeds maximum value 2^63", dest.Value.Amount)
170                         }
171                         diff, ok := checked.SubInt64(sum, int64(dest.Value.Amount))
172                         if !ok {
173                                 return errors.WithDetailf(ErrOverflow, "subtracting %d units of asset %x from mux destination %d from total %d underflows int64", dest.Value.Amount, dest.Value.AssetId.Bytes(), i, sum)
174                         }
175                         parity[*dest.Value.AssetId] = diff
176                 }
177
178                 btmAmount := int64(0)
179                 for assetID, amount := range parity {
180                         if assetID == *consensus.BTMAssetID {
181                                 btmAmount = amount
182                         } else if amount != 0 {
183                                 return errors.WithDetailf(ErrUnbalanced, "asset %x sources - destinations = %d (should be 0)", assetID.Bytes(), amount)
184                         }
185                 }
186
187                 if err = vs.gasStatus.setGas(btmAmount, int64(vs.tx.SerializedSize)); err != nil {
188                         return err
189                 }
190
191                 for _, BTMInputID := range vs.tx.GasInputIDs {
192                         e, ok := vs.tx.Entries[BTMInputID]
193                         if !ok {
194                                 return errors.Wrapf(bc.ErrMissingEntry, "entry for bytom input %x not found", BTMInputID)
195                         }
196
197                         vs2 := *vs
198                         vs2.entryID = BTMInputID
199                         if err := checkValid(&vs2, e); err != nil {
200                                 return errors.Wrap(err, "checking gas input")
201                         }
202                 }
203
204                 for i, dest := range e.WitnessDestinations {
205                         vs2 := *vs
206                         vs2.destPos = uint64(i)
207                         if err = checkValidDest(&vs2, dest); err != nil {
208                                 return errors.Wrapf(err, "checking mux destination %d", i)
209                         }
210                 }
211
212                 if err := vs.gasStatus.setGasValid(); err != nil {
213                         return err
214                 }
215
216                 for i, src := range e.Sources {
217                         vs2 := *vs
218                         vs2.sourcePos = uint64(i)
219                         if err = checkValidSrc(&vs2, src); err != nil {
220                                 return errors.Wrapf(err, "checking mux source %d", i)
221                         }
222                 }
223
224         case *bc.IntraChainOutput:
225                 vs2 := *vs
226                 vs2.sourcePos = 0
227                 if err = checkValidSrc(&vs2, e.Source); err != nil {
228                         return errors.Wrap(err, "checking output source")
229                 }
230
231         case *bc.CrossChainOutput:
232                 vs2 := *vs
233                 vs2.sourcePos = 0
234                 if err = checkValidSrc(&vs2, e.Source); err != nil {
235                         return errors.Wrap(err, "checking output source")
236                 }
237
238         case *bc.VoteOutput:
239                 if len(e.Vote) != 64 {
240                         return ErrVotePubKey
241                 }
242                 vs2 := *vs
243                 vs2.sourcePos = 0
244                 if err = checkValidSrc(&vs2, e.Source); err != nil {
245                         return errors.Wrap(err, "checking vote output source")
246                 }
247
248                 if e.Source.Value.Amount < consensus.ActiveNetParams.MinVoteOutputAmount {
249                         return ErrVoteOutputAmount
250                 }
251
252         case *bc.Retirement:
253                 vs2 := *vs
254                 vs2.sourcePos = 0
255                 if err = checkValidSrc(&vs2, e.Source); err != nil {
256                         return errors.Wrap(err, "checking retirement source")
257                 }
258
259         case *bc.CrossChainInput:
260                 if e.MainchainOutputId == nil {
261                         return errors.Wrap(ErrMissingField, "crosschain input without mainchain output ID")
262                 }
263
264                 mainchainOutput, err := vs.tx.IntraChainOutput(*e.MainchainOutputId)
265                 if err != nil {
266                         return errors.Wrap(err, "getting mainchain output")
267                 }
268
269                 assetID := e.AssetDefinition.ComputeAssetID()
270                 if *mainchainOutput.Source.Value.AssetId != *consensus.BTMAssetID && *mainchainOutput.Source.Value.AssetId != assetID {
271                         return errors.New("incorrect asset_id while checking CrossChainInput")
272                 }
273
274                 prog := &bc.Program{
275                         VmVersion: e.ControlProgram.VmVersion,
276                         Code:      config.FederationWScript(config.CommonConfig),
277                 }
278
279                 if _, err := vm.Verify(NewTxVMContext(vs, e, prog, e.WitnessArguments), consensus.ActiveNetParams.DefaultGasCredit); err != nil {
280                         return errors.Wrap(err, "checking cross-chain input control program")
281                 }
282
283                 eq, err := mainchainOutput.Source.Value.Equal(e.WitnessDestination.Value)
284                 if err != nil {
285                         return err
286                 }
287
288                 if !eq {
289                         return errors.WithDetailf(
290                                 ErrMismatchedValue,
291                                 "previous output is for %d unit(s) of %x, spend wants %d unit(s) of %x",
292                                 mainchainOutput.Source.Value.Amount,
293                                 mainchainOutput.Source.Value.AssetId.Bytes(),
294                                 e.WitnessDestination.Value.Amount,
295                                 e.WitnessDestination.Value.AssetId.Bytes(),
296                         )
297                 }
298
299                 vs2 := *vs
300                 vs2.destPos = 0
301                 if err = checkValidDest(&vs2, e.WitnessDestination); err != nil {
302                         return errors.Wrap(err, "checking cross-chain input destination")
303                 }
304                 vs.gasStatus.StorageGas = 0
305
306         case *bc.Spend:
307                 if e.SpentOutputId == nil {
308                         return errors.Wrap(ErrMissingField, "spend without spent output ID")
309                 }
310
311                 spentOutput, err := vs.tx.IntraChainOutput(*e.SpentOutputId)
312                 if err != nil {
313                         return errors.Wrap(err, "getting spend prevout")
314                 }
315
316                 gasLeft, err := vm.Verify(NewTxVMContext(vs, e, spentOutput.ControlProgram, e.WitnessArguments), vs.gasStatus.GasLeft)
317                 if err != nil {
318                         return errors.Wrap(err, "checking control program")
319                 }
320                 if err = vs.gasStatus.updateUsage(gasLeft); err != nil {
321                         return err
322                 }
323
324                 eq, err := spentOutput.Source.Value.Equal(e.WitnessDestination.Value)
325                 if err != nil {
326                         return err
327                 }
328                 if !eq {
329                         return errors.WithDetailf(
330                                 ErrMismatchedValue,
331                                 "previous output is for %d unit(s) of %x, spend wants %d unit(s) of %x",
332                                 spentOutput.Source.Value.Amount,
333                                 spentOutput.Source.Value.AssetId.Bytes(),
334                                 e.WitnessDestination.Value.Amount,
335                                 e.WitnessDestination.Value.AssetId.Bytes(),
336                         )
337                 }
338                 vs2 := *vs
339                 vs2.destPos = 0
340                 if err = checkValidDest(&vs2, e.WitnessDestination); err != nil {
341                         return errors.Wrap(err, "checking spend destination")
342                 }
343
344         case *bc.VetoInput:
345                 if e.SpentOutputId == nil {
346                         return errors.Wrap(ErrMissingField, "vetoInput without vetoInput output ID")
347                 }
348
349                 voteOutput, err := vs.tx.VoteOutput(*e.SpentOutputId)
350                 if err != nil {
351                         return errors.Wrap(err, "getting vetoInput prevout")
352                 }
353
354                 if len(voteOutput.Vote) != 64 {
355                         return ErrVotePubKey
356                 }
357
358                 gasLeft, err := vm.Verify(NewTxVMContext(vs, e, voteOutput.ControlProgram, e.WitnessArguments), vs.gasStatus.GasLeft)
359                 if err != nil {
360                         return errors.Wrap(err, "checking control program")
361                 }
362                 if err = vs.gasStatus.updateUsage(gasLeft); err != nil {
363                         return err
364                 }
365
366                 eq, err := voteOutput.Source.Value.Equal(e.WitnessDestination.Value)
367                 if err != nil {
368                         return err
369                 }
370                 if !eq {
371                         return errors.WithDetailf(
372                                 ErrMismatchedValue,
373                                 "previous output is for %d unit(s) of %x, vetoInput wants %d unit(s) of %x",
374                                 voteOutput.Source.Value.Amount,
375                                 voteOutput.Source.Value.AssetId.Bytes(),
376                                 e.WitnessDestination.Value.Amount,
377                                 e.WitnessDestination.Value.AssetId.Bytes(),
378                         )
379                 }
380                 vs2 := *vs
381                 vs2.destPos = 0
382                 if err = checkValidDest(&vs2, e.WitnessDestination); err != nil {
383                         return errors.Wrap(err, "checking vetoInput destination")
384                 }
385
386         case *bc.Coinbase:
387                 if vs.block == nil || len(vs.block.Transactions) == 0 || vs.block.Transactions[0] != vs.tx {
388                         return ErrWrongCoinbaseTransaction
389                 }
390
391                 if *e.WitnessDestination.Value.AssetId != *consensus.BTMAssetID {
392                         return ErrWrongCoinbaseAsset
393                 }
394
395                 if e.Arbitrary != nil && len(e.Arbitrary) > consensus.ActiveNetParams.CoinbaseArbitrarySizeLimit {
396                         return ErrCoinbaseArbitraryOversize
397                 }
398
399                 vs2 := *vs
400                 vs2.destPos = 0
401                 if err = checkValidDest(&vs2, e.WitnessDestination); err != nil {
402                         return errors.Wrap(err, "checking coinbase destination")
403                 }
404                 vs.gasStatus.StorageGas = 0
405
406         default:
407                 return fmt.Errorf("entry has unexpected type %T", e)
408         }
409
410         return nil
411 }
412
413 func checkValidSrc(vstate *validationState, vs *bc.ValueSource) error {
414         if vs == nil {
415                 return errors.Wrap(ErrMissingField, "empty value source")
416         }
417         if vs.Ref == nil {
418                 return errors.Wrap(ErrMissingField, "missing ref on value source")
419         }
420         if vs.Value == nil || vs.Value.AssetId == nil {
421                 return errors.Wrap(ErrMissingField, "missing value on value source")
422         }
423
424         e, ok := vstate.tx.Entries[*vs.Ref]
425         if !ok {
426                 return errors.Wrapf(bc.ErrMissingEntry, "entry for value source %x not found", vs.Ref.Bytes())
427         }
428
429         vstate2 := *vstate
430         vstate2.entryID = *vs.Ref
431         if err := checkValid(&vstate2, e); err != nil {
432                 return errors.Wrap(err, "checking value source")
433         }
434
435         var dest *bc.ValueDestination
436         switch ref := e.(type) {
437         case *bc.VetoInput:
438                 if vs.Position != 0 {
439                         return errors.Wrapf(ErrPosition, "invalid position %d for veto-input source", vs.Position)
440                 }
441                 dest = ref.WitnessDestination
442
443         case *bc.Coinbase:
444                 if vs.Position != 0 {
445                         return errors.Wrapf(ErrPosition, "invalid position %d for coinbase source", vs.Position)
446                 }
447                 dest = ref.WitnessDestination
448
449         case *bc.CrossChainInput:
450                 if vs.Position != 0 {
451                         return errors.Wrapf(ErrPosition, "invalid position %d for cross-chain input source", vs.Position)
452                 }
453                 dest = ref.WitnessDestination
454
455         case *bc.Spend:
456                 if vs.Position != 0 {
457                         return errors.Wrapf(ErrPosition, "invalid position %d for spend source", vs.Position)
458                 }
459                 dest = ref.WitnessDestination
460
461         case *bc.Mux:
462                 if vs.Position >= uint64(len(ref.WitnessDestinations)) {
463                         return errors.Wrapf(ErrPosition, "invalid position %d for %d-destination mux source", vs.Position, len(ref.WitnessDestinations))
464                 }
465                 dest = ref.WitnessDestinations[vs.Position]
466
467         default:
468                 return errors.Wrapf(bc.ErrEntryType, "value source is %T, should be coinbase, cross-chain input, spend, or mux", e)
469         }
470
471         if dest.Ref == nil || *dest.Ref != vstate.entryID {
472                 return errors.Wrapf(ErrMismatchedReference, "value source for %x has disagreeing destination %x", vstate.entryID.Bytes(), dest.Ref.Bytes())
473         }
474
475         if dest.Position != vstate.sourcePos {
476                 return errors.Wrapf(ErrMismatchedPosition, "value source position %d disagrees with %d", dest.Position, vstate.sourcePos)
477         }
478
479         eq, err := dest.Value.Equal(vs.Value)
480         if err != nil {
481                 return errors.Sub(ErrMissingField, err)
482         }
483         if !eq {
484                 return errors.Wrapf(ErrMismatchedValue, "source value %v disagrees with %v", dest.Value, vs.Value)
485         }
486
487         return nil
488 }
489
490 func checkValidDest(vs *validationState, vd *bc.ValueDestination) error {
491         if vd == nil {
492                 return errors.Wrap(ErrMissingField, "empty value destination")
493         }
494         if vd.Ref == nil {
495                 return errors.Wrap(ErrMissingField, "missing ref on value destination")
496         }
497         if vd.Value == nil || vd.Value.AssetId == nil {
498                 return errors.Wrap(ErrMissingField, "missing value on value destination")
499         }
500
501         e, ok := vs.tx.Entries[*vd.Ref]
502         if !ok {
503                 return errors.Wrapf(bc.ErrMissingEntry, "entry for value destination %x not found", vd.Ref.Bytes())
504         }
505
506         var src *bc.ValueSource
507         switch ref := e.(type) {
508         case *bc.IntraChainOutput:
509                 if vd.Position != 0 {
510                         return errors.Wrapf(ErrPosition, "invalid position %d for output destination", vd.Position)
511                 }
512                 src = ref.Source
513
514         case *bc.CrossChainOutput:
515                 if vd.Position != 0 {
516                         return errors.Wrapf(ErrPosition, "invalid position %d for output destination", vd.Position)
517                 }
518                 src = ref.Source
519
520         case *bc.VoteOutput:
521                 if vd.Position != 0 {
522                         return errors.Wrapf(ErrPosition, "invalid position %d for output destination", vd.Position)
523                 }
524                 src = ref.Source
525
526         case *bc.Retirement:
527                 if vd.Position != 0 {
528                         return errors.Wrapf(ErrPosition, "invalid position %d for retirement destination", vd.Position)
529                 }
530                 src = ref.Source
531
532         case *bc.Mux:
533                 if vd.Position >= uint64(len(ref.Sources)) {
534                         return errors.Wrapf(ErrPosition, "invalid position %d for %d-source mux destination", vd.Position, len(ref.Sources))
535                 }
536                 src = ref.Sources[vd.Position]
537
538         default:
539                 return errors.Wrapf(bc.ErrEntryType, "value destination is %T, should be intra-chain/cross-chain output, retirement, or mux", e)
540         }
541
542         if src.Ref == nil || *src.Ref != vs.entryID {
543                 return errors.Wrapf(ErrMismatchedReference, "value destination for %x has disagreeing source %x", vs.entryID.Bytes(), src.Ref.Bytes())
544         }
545
546         if src.Position != vs.destPos {
547                 return errors.Wrapf(ErrMismatchedPosition, "value destination position %d disagrees with %d", src.Position, vs.destPos)
548         }
549
550         eq, err := src.Value.Equal(vd.Value)
551         if err != nil {
552                 return errors.Sub(ErrMissingField, err)
553         }
554         if !eq {
555                 return errors.Wrapf(ErrMismatchedValue, "destination value %v disagrees with %v", src.Value, vd.Value)
556         }
557
558         return nil
559 }
560
561 func checkInputID(tx *bc.Tx, blockHeight uint64) error {
562         for _, id := range tx.InputIDs {
563                 if id.IsZero() {
564                         return ErrEmptyInputIDs
565                 }
566         }
567         return nil
568 }
569
570 func checkTimeRange(tx *bc.Tx, block *bc.Block) error {
571         if tx.TimeRange == 0 {
572                 return nil
573         }
574
575         if tx.TimeRange < block.Height {
576                 return ErrBadTimeRange
577         }
578
579         return nil
580 }
581
582 // ValidateTx validates a transaction.
583 func ValidateTx(tx *bc.Tx, block *bc.Block) (*GasState, error) {
584         gasStatus := &GasState{GasValid: false}
585         if block.Version == 1 && tx.Version != 1 {
586                 return gasStatus, errors.WithDetailf(ErrTxVersion, "block version %d, transaction version %d", block.Version, tx.Version)
587         }
588         if tx.SerializedSize == 0 {
589                 return gasStatus, ErrWrongTransactionSize
590         }
591         if err := checkTimeRange(tx, block); err != nil {
592                 return gasStatus, err
593         }
594         if err := checkInputID(tx, block.Height); err != nil {
595                 return gasStatus, err
596         }
597
598         vs := &validationState{
599                 block:     block,
600                 tx:        tx,
601                 entryID:   tx.ID,
602                 gasStatus: gasStatus,
603                 cache:     make(map[bc.Hash]error),
604         }
605         return vs.gasStatus, checkValid(vs, tx.TxHeader)
606 }
607
608 type validateTxWork struct {
609         i     int
610         tx    *bc.Tx
611         block *bc.Block
612 }
613
614 // ValidateTxResult is the result of async tx validate
615 type ValidateTxResult struct {
616         i         int
617         gasStatus *GasState
618         err       error
619 }
620
621 // GetGasState return the gasStatus
622 func (r *ValidateTxResult) GetGasState() *GasState {
623         return r.gasStatus
624 }
625
626 // GetError return the err
627 func (r *ValidateTxResult) GetError() error {
628         return r.err
629 }
630
631 func validateTxWorker(workCh chan *validateTxWork, resultCh chan *ValidateTxResult, closeCh chan struct{}, wg *sync.WaitGroup) {
632         for {
633                 select {
634                 case work := <-workCh:
635                         gasStatus, err := ValidateTx(work.tx, work.block)
636                         resultCh <- &ValidateTxResult{i: work.i, gasStatus: gasStatus, err: err}
637                 case <-closeCh:
638                         wg.Done()
639                         return
640                 }
641         }
642 }
643
644 // ValidateTxs validates txs in async mode
645 func ValidateTxs(txs []*bc.Tx, block *bc.Block) []*ValidateTxResult {
646         txSize := len(txs)
647         //init the goroutine validate worker
648         var wg sync.WaitGroup
649         workCh := make(chan *validateTxWork, txSize)
650         resultCh := make(chan *ValidateTxResult, txSize)
651         closeCh := make(chan struct{})
652         for i := 0; i <= validateWorkerNum && i < txSize; i++ {
653                 wg.Add(1)
654                 go validateTxWorker(workCh, resultCh, closeCh, &wg)
655         }
656
657         //sent the works
658         for i, tx := range txs {
659                 workCh <- &validateTxWork{i: i, tx: tx, block: block}
660         }
661
662         //collect validate results
663         results := make([]*ValidateTxResult, txSize)
664         for i := 0; i < txSize; i++ {
665                 result := <-resultCh
666                 results[result.i] = result
667         }
668
669         close(closeCh)
670         wg.Wait()
671         close(workCh)
672         close(resultCh)
673         return results
674 }