OSDN Git Service

new repo
[bytom/vapor.git] / protocol / validation / tx.go
1 package validation
2
3 import (
4         "bytes"
5         "encoding/json"
6         "fmt"
7         "math"
8         "strconv"
9
10         "github.com/vapor/consensus"
11         "github.com/vapor/consensus/segwit"
12         "github.com/vapor/crypto"
13         "github.com/vapor/errors"
14         "github.com/vapor/math/checked"
15         "github.com/vapor/protocol/bc"
16         "github.com/vapor/protocol/bc/types"
17         "github.com/vapor/protocol/vm"
18         "github.com/vapor/protocol/vm/vmutil"
19         "github.com/vapor/util"
20 )
21
22 // validate transaction error
23 var (
24         ErrTxVersion                 = errors.New("invalid transaction version")
25         ErrWrongTransactionSize      = errors.New("invalid transaction size")
26         ErrBadTimeRange              = errors.New("invalid transaction time range")
27         ErrNotStandardTx             = errors.New("not standard transaction")
28         ErrWrongCoinbaseTransaction  = errors.New("wrong coinbase transaction")
29         ErrWrongCoinbaseAsset        = errors.New("wrong coinbase assetID")
30         ErrCoinbaseArbitraryOversize = errors.New("coinbase arbitrary size is larger than limit")
31         ErrEmptyResults              = errors.New("transaction has no results")
32         ErrMismatchedAssetID         = errors.New("mismatched assetID")
33         ErrMismatchedPosition        = errors.New("mismatched value source/dest position")
34         ErrMismatchedReference       = errors.New("mismatched reference")
35         ErrMismatchedValue           = errors.New("mismatched value")
36         ErrMissingField              = errors.New("missing required field")
37         ErrNoSource                  = errors.New("no source for value")
38         ErrOverflow                  = errors.New("arithmetic overflow/underflow")
39         ErrPosition                  = errors.New("invalid source or destination position")
40         ErrUnbalanced                = errors.New("unbalanced asset amount between input and output")
41         ErrOverGasCredit             = errors.New("all gas credit has been spend")
42         ErrGasCalculate              = errors.New("gas usage calculate got a math error")
43 )
44
45 // GasState record the gas usage status
46 type GasState struct {
47         BTMValue   uint64
48         GasLeft    int64
49         GasUsed    int64
50         GasValid   bool
51         StorageGas int64
52 }
53
54 func (g *GasState) setGas(BTMValue int64, txSize int64) error {
55         if BTMValue < 0 {
56                 return errors.Wrap(ErrGasCalculate, "input BTM is negative")
57         }
58
59         g.BTMValue = uint64(BTMValue)
60
61         var ok bool
62         if g.GasLeft, ok = checked.DivInt64(BTMValue, consensus.VMGasRate); !ok {
63                 return errors.Wrap(ErrGasCalculate, "setGas calc gas amount")
64         }
65
66         if g.GasLeft > consensus.MaxGasAmount {
67                 g.GasLeft = consensus.MaxGasAmount
68         }
69
70         if g.StorageGas, ok = checked.MulInt64(txSize, consensus.StorageGasRate); !ok {
71                 return errors.Wrap(ErrGasCalculate, "setGas calc tx storage gas")
72         }
73         return nil
74 }
75
76 func (g *GasState) setGasValid() error {
77         var ok bool
78         if g.GasLeft, ok = checked.SubInt64(g.GasLeft, g.StorageGas); !ok || g.GasLeft < 0 {
79                 return errors.Wrap(ErrGasCalculate, "setGasValid calc gasLeft")
80         }
81
82         if g.GasUsed, ok = checked.AddInt64(g.GasUsed, g.StorageGas); !ok {
83                 return errors.Wrap(ErrGasCalculate, "setGasValid calc gasUsed")
84         }
85
86         g.GasValid = true
87         return nil
88 }
89
90 func (g *GasState) updateUsage(gasLeft int64) error {
91         if gasLeft < 0 {
92                 return errors.Wrap(ErrGasCalculate, "updateUsage input negative gas")
93         }
94
95         if gasUsed, ok := checked.SubInt64(g.GasLeft, gasLeft); ok {
96                 g.GasUsed += gasUsed
97                 g.GasLeft = gasLeft
98         } else {
99                 return errors.Wrap(ErrGasCalculate, "updateUsage calc gas diff")
100         }
101
102         if !g.GasValid && (g.GasUsed > consensus.DefaultGasCredit || g.StorageGas > g.GasLeft) {
103                 return ErrOverGasCredit
104         }
105         return nil
106 }
107
108 // validationState contains the context that must propagate through
109 // the transaction graph when validating entries.
110 type validationState struct {
111         block     *bc.Block
112         tx        *bc.Tx
113         gasStatus *GasState
114         entryID   bc.Hash           // The ID of the nearest enclosing entry
115         sourcePos uint64            // The source position, for validate ValueSources
116         destPos   uint64            // The destination position, for validate ValueDestinations
117         cache     map[bc.Hash]error // Memoized per-entry validation results
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 _, BTMInputID := range vs.tx.GasInputIDs {
185                         e, ok := vs.tx.Entries[BTMInputID]
186                         if !ok {
187                                 return errors.Wrapf(bc.ErrMissingEntry, "entry for bytom input %x not found", BTMInputID)
188                         }
189
190                         vs2 := *vs
191                         vs2.entryID = BTMInputID
192                         if err := checkValid(&vs2, e); err != nil {
193                                 return errors.Wrap(err, "checking gas input")
194                         }
195                 }
196
197                 for i, dest := range e.WitnessDestinations {
198                         vs2 := *vs
199                         vs2.destPos = uint64(i)
200                         if err = checkValidDest(&vs2, dest); err != nil {
201                                 return errors.Wrapf(err, "checking mux destination %d", i)
202                         }
203                 }
204
205                 if len(vs.tx.GasInputIDs) > 0 {
206                         if err := vs.gasStatus.setGasValid(); err != nil {
207                                 return err
208                         }
209                 }
210
211                 for i, src := range e.Sources {
212                         vs2 := *vs
213                         vs2.sourcePos = uint64(i)
214                         if err = checkValidSrc(&vs2, src); err != nil {
215                                 return errors.Wrapf(err, "checking mux source %d", i)
216                         }
217                 }
218
219         case *bc.Output:
220                 vs2 := *vs
221                 vs2.sourcePos = 0
222                 if err = checkValidSrc(&vs2, e.Source); err != nil {
223                         return errors.Wrap(err, "checking output source")
224                 }
225
226         case *bc.Retirement:
227                 vs2 := *vs
228                 vs2.sourcePos = 0
229                 if err = checkValidSrc(&vs2, e.Source); err != nil {
230                         return errors.Wrap(err, "checking retirement source")
231                 }
232
233         case *bc.Issuance:
234                 computedAssetID := e.WitnessAssetDefinition.ComputeAssetID()
235                 if computedAssetID != *e.Value.AssetId {
236                         return errors.WithDetailf(ErrMismatchedAssetID, "asset ID is %x, issuance wants %x", computedAssetID.Bytes(), e.Value.AssetId.Bytes())
237                 }
238
239                 gasLeft, err := vm.Verify(NewTxVMContext(vs, e, e.WitnessAssetDefinition.IssuanceProgram, e.WitnessArguments), vs.gasStatus.GasLeft)
240                 if err != nil {
241                         return errors.Wrap(err, "checking issuance program")
242                 }
243                 if err = vs.gasStatus.updateUsage(gasLeft); err != nil {
244                         return err
245                 }
246
247                 destVS := *vs
248                 destVS.destPos = 0
249                 if err = checkValidDest(&destVS, e.WitnessDestination); err != nil {
250                         return errors.Wrap(err, "checking issuance destination")
251                 }
252
253         case *bc.Spend:
254                 if e.SpentOutputId == nil {
255                         return errors.Wrap(ErrMissingField, "spend without spent output ID")
256                 }
257                 spentOutput, err := vs.tx.Output(*e.SpentOutputId)
258                 if err != nil {
259                         return errors.Wrap(err, "getting spend prevout")
260                 }
261
262                 gasLeft, err := vm.Verify(NewTxVMContext(vs, e, spentOutput.ControlProgram, e.WitnessArguments), vs.gasStatus.GasLeft)
263                 if err != nil {
264                         return errors.Wrap(err, "checking control program")
265                 }
266                 if err = vs.gasStatus.updateUsage(gasLeft); err != nil {
267                         return err
268                 }
269
270                 eq, err := spentOutput.Source.Value.Equal(e.WitnessDestination.Value)
271                 if err != nil {
272                         return err
273                 }
274                 if !eq {
275                         return errors.WithDetailf(
276                                 ErrMismatchedValue,
277                                 "previous output is for %d unit(s) of %x, spend wants %d unit(s) of %x",
278                                 spentOutput.Source.Value.Amount,
279                                 spentOutput.Source.Value.AssetId.Bytes(),
280                                 e.WitnessDestination.Value.Amount,
281                                 e.WitnessDestination.Value.AssetId.Bytes(),
282                         )
283                 }
284
285                 vs2 := *vs
286                 vs2.destPos = 0
287                 if err = checkValidDest(&vs2, e.WitnessDestination); err != nil {
288                         return errors.Wrap(err, "checking spend destination")
289                 }
290
291         case *bc.Coinbase:
292                 if vs.block == nil || len(vs.block.Transactions) == 0 || vs.block.Transactions[0] != vs.tx {
293                         return ErrWrongCoinbaseTransaction
294                 }
295
296                 if *e.WitnessDestination.Value.AssetId != *consensus.BTMAssetID {
297                         return ErrWrongCoinbaseAsset
298                 }
299
300                 if e.Arbitrary != nil && len(e.Arbitrary) > consensus.CoinbaseArbitrarySizeLimit {
301                         return ErrCoinbaseArbitraryOversize
302                 }
303
304                 vs2 := *vs
305                 vs2.destPos = 0
306                 if err = checkValidDest(&vs2, e.WitnessDestination); err != nil {
307                         return errors.Wrap(err, "checking coinbase destination")
308                 }
309
310                 // special case for coinbase transaction, it's valid unit all the verify has been passed
311                 vs.gasStatus.GasValid = true
312         case *bc.Claim:
313                 // 对交易的合法性进行验证
314                 if e.SpentOutputId == nil {
315                         return errors.Wrap(ErrMissingField, "spend without spent output ID")
316                 }
317                 spentOutput, err := vs.tx.Output(*e.SpentOutputId)
318                 if err != nil {
319                         return errors.Wrap(err, "getting spend prevout")
320                 }
321                 stack := e.GetPeginwitness()
322                 if len(stack) < 5 || stack[1] == nil || spentOutput.Source == nil {
323
324                         return errors.New("pegin-no-witness")
325                 }
326
327                 if IsValidPeginWitness(stack, *spentOutput) == nil {
328                         return errors.New("PeginWitness invalid")
329                 }
330
331                 // 判断cliamd的输入是否已经被用
332
333                 eq, err := spentOutput.Source.Value.Equal(e.WitnessDestination.Value)
334                 if err != nil {
335                         return err
336                 }
337                 if !eq {
338                         return errors.WithDetailf(
339                                 ErrMismatchedValue,
340                                 "previous output is for %d unit(s) of %x, spend wants %d unit(s) of %x",
341                                 spentOutput.Source.Value.Amount,
342                                 spentOutput.Source.Value.AssetId.Bytes(),
343                                 e.WitnessDestination.Value.Amount,
344                                 e.WitnessDestination.Value.AssetId.Bytes(),
345                         )
346                 }
347
348                 vs2 := *vs
349                 vs2.destPos = 0
350                 if err = checkValidDest(&vs2, e.WitnessDestination); err != nil {
351                         return errors.Wrap(err, "checking spend destination")
352                 }
353                 vs.gasStatus.GasValid = true
354         default:
355                 return fmt.Errorf("entry has unexpected type %T", e)
356         }
357
358         return nil
359 }
360
361 type MerkleBlock struct {
362         BlockHeader  types.BlockHeader `json:"block_header"`
363         TxHashes     []*bc.Hash        `json:"tx_hashes"`
364         StatusHashes []*bc.Hash        `json:"status_hashes"`
365         Flags        []uint32          `json:"flags"`
366         MatchedTxIDs []*bc.Hash        `json:"matched_tx_ids"`
367 }
368
369 func IsValidPeginWitness(peginWitness [][]byte, prevout bc.Output) (err error) {
370         if len(peginWitness) != 5 {
371                 return errors.New("peginWitness is error")
372         }
373         amount, err := strconv.ParseUint(string(peginWitness[0]), 10, 64)
374         if err != nil {
375                 return err
376         }
377         if consensus.MoneyRange(amount) {
378                 return errors.New("Amount out of range")
379         }
380
381         if len(peginWitness[1]) != 32 {
382                 return errors.New("The length of gennesisBlockHash is not correct")
383         }
384
385         claimScript := peginWitness[2]
386
387         var rawTx types.Tx
388         err = json.Unmarshal(peginWitness[3], &rawTx)
389         if err != nil {
390                 return err
391         }
392         var merkleBlock MerkleBlock
393         err = json.Unmarshal(peginWitness[4], &merkleBlock)
394         if err != nil {
395                 return err
396         }
397         // proof验证
398         var flags []uint8
399         for flag := range merkleBlock.Flags {
400                 flags = append(flags, uint8(flag))
401         }
402         if !types.ValidateTxMerkleTreeProof(merkleBlock.TxHashes, flags, merkleBlock.MatchedTxIDs, merkleBlock.BlockHeader.BlockCommitment.TransactionsMerkleRoot) {
403                 return errors.New("Merkleblock validation failed")
404         }
405         var txHash bc.Hash = rawTx.ID
406         // 交易进行验证
407         if !checkPeginTx(&rawTx, &prevout, amount, claimScript) {
408                 return errors.New("check PeginTx fail")
409         }
410
411         // Check that the merkle proof corresponds to the txid
412         if txHash != *prevout.Source.Ref {
413                 return errors.New("prevout hash don't match")
414         }
415         // Check the genesis block corresponds to a valid peg (only one for now)
416         if !bytes.Equal(peginWitness[1], []byte(consensus.ActiveNetParams.ParentGenesisBlockHash)) {
417                 return errors.New("ParentGenesisBlockHash don't match")
418         }
419         // TODO Finally, validate peg-in via rpc call
420
421         if util.ValidatePegin {
422                 if err := util.IsConfirmedBytomBlock(merkleBlock.BlockHeader.Height, consensus.ActiveNetParams.PeginMinDepth); err != nil {
423                         return err
424                 }
425         }
426
427         return nil
428 }
429
430 func checkPeginTx(rawTx *types.Tx, prevout *bc.Output, claimAmount uint64, claimScript []byte) bool {
431         // Check that transaction matches txid
432         if rawTx.ID != *prevout.Source.Ref {
433                 return false
434         }
435         // Check the transaction nout/value matches
436         amount := rawTx.Outputs[prevout.Source.Position].Amount
437         if claimAmount != amount {
438                 return false
439         }
440         // Check that the witness program matches the p2ch on the p2sh-p2wsh transaction output
441         federationRedeemScript := vmutil.CalculateContract(consensus.ActiveNetParams.FedpegXPubs, claimScript)
442         scriptHash := crypto.Sha256(federationRedeemScript)
443         controlProg, err := vmutil.P2WSHProgram(scriptHash)
444         if err != nil {
445                 return false
446         }
447         if bytes.Equal(rawTx.Outputs[prevout.Source.Position].ControlProgram, controlProg) {
448                 return false
449         }
450         return true
451 }
452
453 func checkValidSrc(vstate *validationState, vs *bc.ValueSource) error {
454         if vs == nil {
455                 return errors.Wrap(ErrMissingField, "empty value source")
456         }
457         if vs.Ref == nil {
458                 return errors.Wrap(ErrMissingField, "missing ref on value source")
459         }
460         if vs.Value == nil || vs.Value.AssetId == nil {
461                 return errors.Wrap(ErrMissingField, "missing value on value source")
462         }
463
464         e, ok := vstate.tx.Entries[*vs.Ref]
465         if !ok {
466                 return errors.Wrapf(bc.ErrMissingEntry, "entry for value source %x not found", vs.Ref.Bytes())
467         }
468
469         vstate2 := *vstate
470         vstate2.entryID = *vs.Ref
471         if err := checkValid(&vstate2, e); err != nil {
472                 return errors.Wrap(err, "checking value source")
473         }
474
475         var dest *bc.ValueDestination
476         switch ref := e.(type) {
477         case *bc.Coinbase:
478                 if vs.Position != 0 {
479                         return errors.Wrapf(ErrPosition, "invalid position %d for coinbase source", vs.Position)
480                 }
481                 dest = ref.WitnessDestination
482
483         case *bc.Issuance:
484                 if vs.Position != 0 {
485                         return errors.Wrapf(ErrPosition, "invalid position %d for issuance source", vs.Position)
486                 }
487                 dest = ref.WitnessDestination
488
489         case *bc.Spend:
490                 if vs.Position != 0 {
491                         return errors.Wrapf(ErrPosition, "invalid position %d for spend source", vs.Position)
492                 }
493                 dest = ref.WitnessDestination
494
495         case *bc.Mux:
496                 if vs.Position >= uint64(len(ref.WitnessDestinations)) {
497                         return errors.Wrapf(ErrPosition, "invalid position %d for %d-destination mux source", vs.Position, len(ref.WitnessDestinations))
498                 }
499                 dest = ref.WitnessDestinations[vs.Position]
500         case *bc.Claim:
501                 if vs.Position != 0 {
502                         return errors.Wrapf(ErrPosition, "invalid position %d for coinbase source", vs.Position)
503                 }
504                 dest = ref.WitnessDestination
505         default:
506                 return errors.Wrapf(bc.ErrEntryType, "value source is %T, should be coinbase, issuance, spend, or mux", e)
507         }
508
509         if dest.Ref == nil || *dest.Ref != vstate.entryID {
510                 return errors.Wrapf(ErrMismatchedReference, "value source for %x has disagreeing destination %x", vstate.entryID.Bytes(), dest.Ref.Bytes())
511         }
512
513         if dest.Position != vstate.sourcePos {
514                 return errors.Wrapf(ErrMismatchedPosition, "value source position %d disagrees with %d", dest.Position, vstate.sourcePos)
515         }
516
517         eq, err := dest.Value.Equal(vs.Value)
518         if err != nil {
519                 return errors.Sub(ErrMissingField, err)
520         }
521         if !eq {
522                 return errors.Wrapf(ErrMismatchedValue, "source value %v disagrees with %v", dest.Value, vs.Value)
523         }
524
525         return nil
526 }
527
528 func checkValidDest(vs *validationState, vd *bc.ValueDestination) error {
529         if vd == nil {
530                 return errors.Wrap(ErrMissingField, "empty value destination")
531         }
532         if vd.Ref == nil {
533                 return errors.Wrap(ErrMissingField, "missing ref on value destination")
534         }
535         if vd.Value == nil || vd.Value.AssetId == nil {
536                 return errors.Wrap(ErrMissingField, "missing value on value source")
537         }
538
539         e, ok := vs.tx.Entries[*vd.Ref]
540         if !ok {
541                 return errors.Wrapf(bc.ErrMissingEntry, "entry for value destination %x not found", vd.Ref.Bytes())
542         }
543
544         var src *bc.ValueSource
545         switch ref := e.(type) {
546         case *bc.Output:
547                 if vd.Position != 0 {
548                         return errors.Wrapf(ErrPosition, "invalid position %d for output destination", vd.Position)
549                 }
550                 src = ref.Source
551
552         case *bc.Retirement:
553                 if vd.Position != 0 {
554                         return errors.Wrapf(ErrPosition, "invalid position %d for retirement destination", vd.Position)
555                 }
556                 src = ref.Source
557
558         case *bc.Mux:
559                 if vd.Position >= uint64(len(ref.Sources)) {
560                         return errors.Wrapf(ErrPosition, "invalid position %d for %d-source mux destination", vd.Position, len(ref.Sources))
561                 }
562                 src = ref.Sources[vd.Position]
563
564         default:
565                 return errors.Wrapf(bc.ErrEntryType, "value destination is %T, should be output, retirement, or mux", e)
566         }
567
568         if src.Ref == nil || *src.Ref != vs.entryID {
569                 return errors.Wrapf(ErrMismatchedReference, "value destination for %x has disagreeing source %x", vs.entryID.Bytes(), src.Ref.Bytes())
570         }
571
572         if src.Position != vs.destPos {
573                 return errors.Wrapf(ErrMismatchedPosition, "value destination position %d disagrees with %d", src.Position, vs.destPos)
574         }
575
576         eq, err := src.Value.Equal(vd.Value)
577         if err != nil {
578                 return errors.Sub(ErrMissingField, err)
579         }
580         if !eq {
581                 return errors.Wrapf(ErrMismatchedValue, "destination value %v disagrees with %v", src.Value, vd.Value)
582         }
583
584         return nil
585 }
586
587 func checkStandardTx(tx *bc.Tx) error {
588         for _, id := range tx.GasInputIDs {
589                 spend, err := tx.Spend(id)
590                 if err != nil {
591                         return err
592                 }
593                 spentOutput, err := tx.Output(*spend.SpentOutputId)
594                 if err != nil {
595                         return err
596                 }
597
598                 if !segwit.IsP2WScript(spentOutput.ControlProgram.Code) {
599                         return ErrNotStandardTx
600                 }
601         }
602
603         for _, id := range tx.ResultIds {
604                 e, ok := tx.Entries[*id]
605                 if !ok {
606                         return errors.Wrapf(bc.ErrMissingEntry, "id %x", id.Bytes())
607                 }
608
609                 output, ok := e.(*bc.Output)
610                 if !ok || *output.Source.Value.AssetId != *consensus.BTMAssetID {
611                         continue
612                 }
613
614                 if !segwit.IsP2WScript(output.ControlProgram.Code) {
615                         return ErrNotStandardTx
616                 }
617         }
618         return nil
619 }
620
621 func checkTimeRange(tx *bc.Tx, block *bc.Block) error {
622         if tx.TimeRange == 0 {
623                 return nil
624         }
625
626         if tx.TimeRange < block.Height {
627                 return ErrBadTimeRange
628         }
629         return nil
630 }
631
632 // ValidateTx validates a transaction.
633 func ValidateTx(tx *bc.Tx, block *bc.Block) (*GasState, error) {
634         gasStatus := &GasState{GasValid: false}
635         if block.Version == 1 && tx.Version != 1 {
636                 return gasStatus, errors.WithDetailf(ErrTxVersion, "block version %d, transaction version %d", block.Version, tx.Version)
637         }
638         if tx.SerializedSize == 0 {
639                 return gasStatus, ErrWrongTransactionSize
640         }
641         if err := checkTimeRange(tx, block); err != nil {
642                 return gasStatus, err
643         }
644         if err := checkStandardTx(tx); err != nil {
645                 return gasStatus, err
646         }
647         vs := &validationState{
648                 block:     block,
649                 tx:        tx,
650                 entryID:   tx.ID,
651                 gasStatus: gasStatus,
652                 cache:     make(map[bc.Hash]error),
653         }
654         return vs.gasStatus, checkValid(vs, tx.TxHeader)
655 }