github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/protocol/validation/tx.go (about)

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