OSDN Git Service

ts to ms (#71)
[bytom/vapor.git] / protocol / validation / tx_test.go
1 package validation
2
3 import (
4         "math"
5         "testing"
6
7         "github.com/davecgh/go-spew/spew"
8
9         "github.com/vapor/consensus"
10         "github.com/vapor/crypto/sha3pool"
11         "github.com/vapor/errors"
12         "github.com/vapor/protocol/bc"
13         "github.com/vapor/protocol/bc/types"
14         "github.com/vapor/protocol/vm"
15         "github.com/vapor/protocol/vm/vmutil"
16         "github.com/vapor/testutil"
17 )
18
19 func init() {
20         spew.Config.DisableMethods = true
21 }
22
23 func TestGasStatus(t *testing.T) {
24         cases := []struct {
25                 input  *GasState
26                 output *GasState
27                 f      func(*GasState) error
28                 err    error
29         }{
30                 {
31                         input: &GasState{
32                                 GasLeft:  10000,
33                                 GasUsed:  0,
34                                 BTMValue: 0,
35                         },
36                         output: &GasState{
37                                 GasLeft:  10000 / consensus.VMGasRate,
38                                 GasUsed:  0,
39                                 BTMValue: 10000,
40                         },
41                         f: func(input *GasState) error {
42                                 return input.setGas(10000, 0)
43                         },
44                         err: nil,
45                 },
46                 {
47                         input: &GasState{
48                                 GasLeft:  10000,
49                                 GasUsed:  0,
50                                 BTMValue: 0,
51                         },
52                         output: &GasState{
53                                 GasLeft:  10000,
54                                 GasUsed:  0,
55                                 BTMValue: 0,
56                         },
57                         f: func(input *GasState) error {
58                                 return input.setGas(-10000, 0)
59                         },
60                         err: ErrGasCalculate,
61                 },
62                 {
63                         input: &GasState{
64                                 GasLeft:  consensus.DefaultGasCredit,
65                                 GasUsed:  0,
66                                 BTMValue: 0,
67                         },
68                         output: &GasState{
69                                 GasLeft:  200000,
70                                 GasUsed:  0,
71                                 BTMValue: 80000000000,
72                         },
73                         f: func(input *GasState) error {
74                                 return input.setGas(80000000000, 0)
75                         },
76                         err: nil,
77                 },
78                 {
79                         input: &GasState{
80                                 GasLeft:  consensus.DefaultGasCredit,
81                                 GasUsed:  0,
82                                 BTMValue: 0,
83                         },
84                         output: &GasState{
85                                 GasLeft:  200000,
86                                 GasUsed:  0,
87                                 BTMValue: math.MaxInt64,
88                         },
89                         f: func(input *GasState) error {
90                                 return input.setGas(math.MaxInt64, 0)
91                         },
92                         err: nil,
93                 },
94                 {
95                         input: &GasState{
96                                 GasLeft:  10000,
97                                 GasUsed:  0,
98                                 BTMValue: 0,
99                         },
100                         output: &GasState{
101                                 GasLeft:  10000,
102                                 GasUsed:  0,
103                                 BTMValue: 0,
104                         },
105                         f: func(input *GasState) error {
106                                 return input.updateUsage(-1)
107                         },
108                         err: ErrGasCalculate,
109                 },
110                 {
111                         input: &GasState{
112                                 GasLeft:  10000,
113                                 GasUsed:  0,
114                                 BTMValue: 0,
115                         },
116                         output: &GasState{
117                                 GasLeft:  9999,
118                                 GasUsed:  1,
119                                 BTMValue: 0,
120                         },
121                         f: func(input *GasState) error {
122                                 return input.updateUsage(9999)
123                         },
124                         err: nil,
125                 },
126                 {
127                         input: &GasState{
128                                 GasLeft:  -10000,
129                                 GasUsed:  0,
130                                 BTMValue: 0,
131                         },
132                         output: &GasState{
133                                 GasLeft:  -10000,
134                                 GasUsed:  0,
135                                 BTMValue: 0,
136                         },
137                         f: func(input *GasState) error {
138                                 return input.updateUsage(math.MaxInt64)
139                         },
140                         err: ErrGasCalculate,
141                 },
142                 {
143                         input: &GasState{
144                                 GasLeft:    1000,
145                                 GasUsed:    10,
146                                 StorageGas: 1000,
147                                 GasValid:   false,
148                         },
149                         output: &GasState{
150                                 GasLeft:    0,
151                                 GasUsed:    1010,
152                                 StorageGas: 1000,
153                                 GasValid:   true,
154                         },
155                         f: func(input *GasState) error {
156                                 return input.setGasValid()
157                         },
158                         err: nil,
159                 },
160                 {
161                         input: &GasState{
162                                 GasLeft:    900,
163                                 GasUsed:    10,
164                                 StorageGas: 1000,
165                                 GasValid:   false,
166                         },
167                         output: &GasState{
168                                 GasLeft:    -100,
169                                 GasUsed:    10,
170                                 StorageGas: 1000,
171                                 GasValid:   false,
172                         },
173                         f: func(input *GasState) error {
174                                 return input.setGasValid()
175                         },
176                         err: ErrGasCalculate,
177                 },
178                 {
179                         input: &GasState{
180                                 GasLeft:    1000,
181                                 GasUsed:    math.MaxInt64,
182                                 StorageGas: 1000,
183                                 GasValid:   false,
184                         },
185                         output: &GasState{
186                                 GasLeft:    0,
187                                 GasUsed:    0,
188                                 StorageGas: 1000,
189                                 GasValid:   false,
190                         },
191                         f: func(input *GasState) error {
192                                 return input.setGasValid()
193                         },
194                         err: ErrGasCalculate,
195                 },
196                 {
197                         input: &GasState{
198                                 GasLeft:    math.MinInt64,
199                                 GasUsed:    0,
200                                 StorageGas: 1000,
201                                 GasValid:   false,
202                         },
203                         output: &GasState{
204                                 GasLeft:    0,
205                                 GasUsed:    0,
206                                 StorageGas: 1000,
207                                 GasValid:   false,
208                         },
209                         f: func(input *GasState) error {
210                                 return input.setGasValid()
211                         },
212                         err: ErrGasCalculate,
213                 },
214         }
215
216         for i, c := range cases {
217                 err := c.f(c.input)
218
219                 if rootErr(err) != c.err {
220                         t.Errorf("case %d: got error %s, want %s", i, err, c.err)
221                 } else if *c.input != *c.output {
222                         t.Errorf("case %d: gasStatus %v, want %v;", i, c.input, c.output)
223                 }
224         }
225 }
226
227 func TestOverflow(t *testing.T) {
228         sourceID := &bc.Hash{V0: 9999}
229         ctrlProgram := []byte{byte(vm.OP_TRUE)}
230         newTx := func(inputs []uint64, outputs []uint64) *bc.Tx {
231                 txInputs := make([]*types.TxInput, 0, len(inputs))
232                 txOutputs := make([]*types.TxOutput, 0, len(outputs))
233
234                 for _, amount := range inputs {
235                         txInput := types.NewSpendInput(nil, *sourceID, *consensus.BTMAssetID, amount, 0, ctrlProgram)
236                         txInputs = append(txInputs, txInput)
237                 }
238
239                 for _, amount := range outputs {
240                         txOutput := types.NewIntraChainOutput(*consensus.BTMAssetID, amount, ctrlProgram)
241                         txOutputs = append(txOutputs, txOutput)
242                 }
243
244                 txData := &types.TxData{
245                         Version:        1,
246                         SerializedSize: 100,
247                         TimeRange:      0,
248                         Inputs:         txInputs,
249                         Outputs:        txOutputs,
250                 }
251                 return types.MapTx(txData)
252         }
253
254         cases := []struct {
255                 inputs  []uint64
256                 outputs []uint64
257                 err     error
258         }{
259                 {
260                         inputs:  []uint64{math.MaxUint64, 1},
261                         outputs: []uint64{0},
262                         err:     ErrOverflow,
263                 },
264                 {
265                         inputs:  []uint64{math.MaxUint64, math.MaxUint64},
266                         outputs: []uint64{0},
267                         err:     ErrOverflow,
268                 },
269                 {
270                         inputs:  []uint64{math.MaxUint64, math.MaxUint64 - 1},
271                         outputs: []uint64{0},
272                         err:     ErrOverflow,
273                 },
274                 {
275                         inputs:  []uint64{math.MaxInt64, 1},
276                         outputs: []uint64{0},
277                         err:     ErrOverflow,
278                 },
279                 {
280                         inputs:  []uint64{math.MaxInt64, math.MaxInt64},
281                         outputs: []uint64{0},
282                         err:     ErrOverflow,
283                 },
284                 {
285                         inputs:  []uint64{math.MaxInt64, math.MaxInt64 - 1},
286                         outputs: []uint64{0},
287                         err:     ErrOverflow,
288                 },
289                 {
290                         inputs:  []uint64{0},
291                         outputs: []uint64{math.MaxUint64},
292                         err:     ErrOverflow,
293                 },
294                 {
295                         inputs:  []uint64{0},
296                         outputs: []uint64{math.MaxInt64},
297                         err:     ErrGasCalculate,
298                 },
299                 {
300                         inputs:  []uint64{math.MaxInt64 - 1},
301                         outputs: []uint64{math.MaxInt64},
302                         err:     ErrGasCalculate,
303                 },
304         }
305
306         for i, c := range cases {
307                 tx := newTx(c.inputs, c.outputs)
308                 if _, err := ValidateTx(tx, mockBlock()); rootErr(err) != c.err {
309                         t.Fatalf("case %d test failed, want %s, have %s", i, c.err, rootErr(err))
310                 }
311         }
312 }
313
314 func TestTxValidation(t *testing.T) {
315         var (
316                 tx      *bc.Tx
317                 vs      *validationState
318                 fixture *txFixture
319
320                 // the mux from tx, pulled out for convenience
321                 mux *bc.Mux
322         )
323
324         addCoinbase := func(assetID *bc.AssetID, amount uint64, arbitrary []byte) {
325                 coinbase := bc.NewCoinbase(arbitrary)
326                 txOutput := types.NewIntraChainOutput(*assetID, amount, []byte{byte(vm.OP_TRUE)})
327                 assetAmount := txOutput.AssetAmount()
328                 muxID := getMuxID(tx)
329                 coinbase.SetDestination(muxID, &assetAmount, uint64(len(mux.Sources)))
330                 coinbaseID := bc.EntryID(coinbase)
331                 tx.Entries[coinbaseID] = coinbase
332
333                 mux.Sources = append(mux.Sources, &bc.ValueSource{
334                         Ref:   &coinbaseID,
335                         Value: &assetAmount,
336                 })
337
338                 src := &bc.ValueSource{
339                         Ref:      muxID,
340                         Value:    &assetAmount,
341                         Position: uint64(len(tx.ResultIds)),
342                 }
343                 prog := &bc.Program{txOutput.VMVersion(), txOutput.ControlProgram()}
344                 output := bc.NewIntraChainOutput(src, prog, uint64(len(tx.ResultIds)))
345                 outputID := bc.EntryID(output)
346                 tx.Entries[outputID] = output
347
348                 dest := &bc.ValueDestination{
349                         Value:    src.Value,
350                         Ref:      &outputID,
351                         Position: 0,
352                 }
353                 mux.WitnessDestinations = append(mux.WitnessDestinations, dest)
354                 tx.ResultIds = append(tx.ResultIds, &outputID)
355                 vs.block.Transactions = append(vs.block.Transactions, vs.tx)
356         }
357
358         cases := []struct {
359                 desc string // description of the test case
360                 f    func() // function to adjust tx, vs, and/or mux
361                 err  error  // expected error
362         }{
363                 {
364                         desc: "base case",
365                 },
366                 {
367                         desc: "unbalanced mux amounts",
368                         f: func() {
369                                 mux.WitnessDestinations[0].Value.Amount++
370                         },
371                         err: ErrUnbalanced,
372                 },
373                 {
374                         desc: "balanced mux amounts",
375                         f: func() {
376                                 mux.Sources[1].Value.Amount++
377                                 mux.WitnessDestinations[0].Value.Amount++
378                         },
379                         err: nil,
380                 },
381                 {
382                         desc: "underflowing mux destination amounts",
383                         f: func() {
384                                 mux.WitnessDestinations[0].Value.Amount = math.MaxInt64
385                                 out := tx.Entries[*mux.WitnessDestinations[0].Ref].(*bc.IntraChainOutput)
386                                 out.Source.Value.Amount = math.MaxInt64
387                                 mux.WitnessDestinations[1].Value.Amount = math.MaxInt64
388                                 out = tx.Entries[*mux.WitnessDestinations[1].Ref].(*bc.IntraChainOutput)
389                                 out.Source.Value.Amount = math.MaxInt64
390                         },
391                         err: ErrOverflow,
392                 },
393                 {
394                         desc: "unbalanced mux assets",
395                         f: func() {
396                                 mux.Sources[1].Value.AssetId = newAssetID(255)
397                                 sp := tx.Entries[*mux.Sources[1].Ref].(*bc.Spend)
398                                 sp.WitnessDestination.Value.AssetId = newAssetID(255)
399                         },
400                         err: ErrUnbalanced,
401                 },
402                 {
403                         desc: "mismatched output source / mux dest position",
404                         f: func() {
405                                 tx.Entries[*tx.ResultIds[0]].(*bc.IntraChainOutput).Source.Position = 1
406                         },
407                         err: ErrMismatchedPosition,
408                 },
409                 {
410                         desc: "mismatched input dest / mux source position",
411                         f: func() {
412                                 mux.Sources[0].Position = 1
413                         },
414                         err: ErrMismatchedPosition,
415                 },
416                 {
417                         desc: "mismatched output source and mux dest",
418                         f: func() {
419                                 // For this test, it's necessary to construct a mostly
420                                 // identical second transaction in order to get a similar but
421                                 // not equal output entry for the mux to falsely point
422                                 // to. That entry must be added to the first tx's Entries map.
423                                 fixture2 := sample(t, fixture)
424                                 tx2 := types.NewTx(*fixture2.tx).Tx
425                                 out2ID := tx2.ResultIds[0]
426                                 out2 := tx2.Entries[*out2ID].(*bc.IntraChainOutput)
427                                 tx.Entries[*out2ID] = out2
428                                 mux.WitnessDestinations[0].Ref = out2ID
429                         },
430                         err: ErrMismatchedReference,
431                 },
432                 {
433                         desc: "invalid mux destination position",
434                         f: func() {
435                                 mux.WitnessDestinations[0].Position = 1
436                         },
437                         err: ErrPosition,
438                 },
439                 {
440                         desc: "mismatched mux dest value / output source value",
441                         f: func() {
442                                 outID := tx.ResultIds[0]
443                                 out := tx.Entries[*outID].(*bc.IntraChainOutput)
444                                 mux.WitnessDestinations[0].Value = &bc.AssetAmount{
445                                         AssetId: out.Source.Value.AssetId,
446                                         Amount:  out.Source.Value.Amount + 1,
447                                 }
448                                 mux.Sources[0].Value.Amount++ // the mux must still balance
449                         },
450                         err: ErrMismatchedValue,
451                 },
452                 {
453                         desc: "empty tx results",
454                         f: func() {
455                                 tx.ResultIds = nil
456                         },
457                         err: ErrEmptyResults,
458                 },
459                 {
460                         desc: "empty tx results, but that's OK",
461                         f: func() {
462                                 tx.Version = 2
463                                 tx.ResultIds = nil
464                         },
465                 },
466                 {
467                         desc: "spend control program failure",
468                         f: func() {
469                                 spend := txSpend(t, tx, 1)
470                                 spend.WitnessArguments[0] = []byte{}
471                         },
472                         err: vm.ErrFalseVMResult,
473                 },
474                 {
475                         desc: "mismatched spent source/witness value",
476                         f: func() {
477                                 spend := txSpend(t, tx, 1)
478                                 spentOutput := tx.Entries[*spend.SpentOutputId].(*bc.IntraChainOutput)
479                                 spentOutput.Source.Value = &bc.AssetAmount{
480                                         AssetId: spend.WitnessDestination.Value.AssetId,
481                                         Amount:  spend.WitnessDestination.Value.Amount + 1,
482                                 }
483                         },
484                         err: ErrMismatchedValue,
485                 },
486                 {
487                         desc: "gas out of limit",
488                         f: func() {
489                                 vs.tx.SerializedSize = 10000000
490                         },
491                         err: ErrOverGasCredit,
492                 },
493                 {
494                         desc: "can't find gas spend input in entries",
495                         f: func() {
496                                 spendID := mux.Sources[len(mux.Sources)-1].Ref
497                                 delete(tx.Entries, *spendID)
498                                 mux.Sources = mux.Sources[:len(mux.Sources)-1]
499                         },
500                         err: bc.ErrMissingEntry,
501                 },
502                 {
503                         desc: "no gas spend input",
504                         f: func() {
505                                 spendID := mux.Sources[len(mux.Sources)-1].Ref
506                                 delete(tx.Entries, *spendID)
507                                 mux.Sources = mux.Sources[:len(mux.Sources)-1]
508                                 tx.GasInputIDs = nil
509                                 vs.gasStatus.GasLeft = 0
510                         },
511                         err: vm.ErrRunLimitExceeded,
512                 },
513                 {
514                         desc: "no gas spend input, but set gas left, so it's ok",
515                         f: func() {
516                                 spendID := mux.Sources[len(mux.Sources)-1].Ref
517                                 delete(tx.Entries, *spendID)
518                                 mux.Sources = mux.Sources[:len(mux.Sources)-1]
519                                 tx.GasInputIDs = nil
520                         },
521                         err: nil,
522                 },
523                 {
524                         desc: "mismatched gas spend input destination amount/prevout source amount",
525                         f: func() {
526                                 spendID := mux.Sources[len(mux.Sources)-1].Ref
527                                 spend := tx.Entries[*spendID].(*bc.Spend)
528                                 spend.WitnessDestination.Value = &bc.AssetAmount{
529                                         AssetId: spend.WitnessDestination.Value.AssetId,
530                                         Amount:  spend.WitnessDestination.Value.Amount + 1,
531                                 }
532                         },
533                         err: ErrMismatchedValue,
534                 },
535                 {
536                         desc: "normal coinbase tx",
537                         f: func() {
538                                 addCoinbase(consensus.BTMAssetID, 100000, nil)
539                         },
540                         err: nil,
541                 },
542                 {
543                         desc: "invalid coinbase tx asset id",
544                         f: func() {
545                                 addCoinbase(&bc.AssetID{V1: 100}, 100000, nil)
546                         },
547                         err: ErrWrongCoinbaseAsset,
548                 },
549                 {
550                         desc: "coinbase tx is not first tx in block",
551                         f: func() {
552                                 addCoinbase(consensus.BTMAssetID, 100000, nil)
553                                 vs.block.Transactions[0] = nil
554                         },
555                         err: ErrWrongCoinbaseTransaction,
556                 },
557                 {
558                         desc: "coinbase arbitrary size out of limit",
559                         f: func() {
560                                 arbitrary := make([]byte, consensus.CoinbaseArbitrarySizeLimit+1)
561                                 addCoinbase(consensus.BTMAssetID, 100000, arbitrary)
562                         },
563                         err: ErrCoinbaseArbitraryOversize,
564                 },
565                 {
566                         desc: "normal retirement output",
567                         f: func() {
568                                 outputID := tx.ResultIds[0]
569                                 output := tx.Entries[*outputID].(*bc.IntraChainOutput)
570                                 retirement := bc.NewRetirement(output.Source, output.Ordinal)
571                                 retirementID := bc.EntryID(retirement)
572                                 tx.Entries[retirementID] = retirement
573                                 delete(tx.Entries, *outputID)
574                                 tx.ResultIds[0] = &retirementID
575                                 mux.WitnessDestinations[0].Ref = &retirementID
576                         },
577                         err: nil,
578                 },
579                 {
580                         desc: "ordinal doesn't matter for prevouts",
581                         f: func() {
582                                 spend := txSpend(t, tx, 1)
583                                 prevout := tx.Entries[*spend.SpentOutputId].(*bc.IntraChainOutput)
584                                 newPrevout := bc.NewIntraChainOutput(prevout.Source, prevout.ControlProgram, 10)
585                                 hash := bc.EntryID(newPrevout)
586                                 spend.SpentOutputId = &hash
587                         },
588                         err: nil,
589                 },
590                 {
591                         desc: "mux witness destination have no source",
592                         f: func() {
593                                 dest := &bc.ValueDestination{
594                                         Value: &bc.AssetAmount{
595                                                 AssetId: &bc.AssetID{V2: 1000},
596                                                 Amount:  100,
597                                         },
598                                         Ref:      mux.WitnessDestinations[0].Ref,
599                                         Position: 0,
600                                 }
601                                 mux.WitnessDestinations = append(mux.WitnessDestinations, dest)
602                         },
603                         err: ErrNoSource,
604                 },
605         }
606
607         for i, c := range cases {
608                 t.Run(c.desc, func(t *testing.T) {
609                         fixture = sample(t, nil)
610                         tx = types.NewTx(*fixture.tx).Tx
611                         vs = &validationState{
612                                 block:   mockBlock(),
613                                 tx:      tx,
614                                 entryID: tx.ID,
615                                 gasStatus: &GasState{
616                                         GasLeft: int64(80000),
617                                         GasUsed: 0,
618                                 },
619                                 cache: make(map[bc.Hash]error),
620                         }
621                         muxID := getMuxID(tx)
622                         mux = tx.Entries[*muxID].(*bc.Mux)
623
624                         if c.f != nil {
625                                 c.f()
626                         }
627                         err := checkValid(vs, tx.TxHeader)
628
629                         if rootErr(err) != c.err {
630                                 t.Errorf("case #%d (%s) got error %s, want %s; validationState is:\n%s", i, c.desc, err, c.err, spew.Sdump(vs))
631                         }
632                 })
633         }
634 }
635
636 // TestCoinbase test the coinbase transaction is valid (txtest#1016)
637 func TestCoinbase(t *testing.T) {
638         cp, _ := vmutil.DefaultCoinbaseProgram()
639         retire, _ := vmutil.RetireProgram([]byte{})
640         CbTx := types.MapTx(&types.TxData{
641                 SerializedSize: 1,
642                 Inputs: []*types.TxInput{
643                         types.NewCoinbaseInput(nil),
644                 },
645                 Outputs: []*types.TxOutput{
646                         types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
647                 },
648         })
649
650         cases := []struct {
651                 block    *bc.Block
652                 txIndex  int
653                 GasValid bool
654                 err      error
655         }{
656                 {
657                         block: &bc.Block{
658                                 BlockHeader:  &bc.BlockHeader{Height: 666},
659                                 Transactions: []*bc.Tx{CbTx},
660                         },
661                         txIndex:  0,
662                         GasValid: true,
663                         err:      nil,
664                 },
665                 {
666                         block: &bc.Block{
667                                 BlockHeader: &bc.BlockHeader{Height: 666},
668                                 Transactions: []*bc.Tx{
669                                         CbTx,
670                                         types.MapTx(&types.TxData{
671                                                 SerializedSize: 1,
672                                                 Inputs: []*types.TxInput{
673                                                         types.NewCoinbaseInput(nil),
674                                                 },
675                                                 Outputs: []*types.TxOutput{
676                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
677                                                 },
678                                         }),
679                                 },
680                         },
681                         txIndex:  1,
682                         GasValid: false,
683                         err:      ErrWrongCoinbaseTransaction,
684                 },
685                 {
686                         block: &bc.Block{
687                                 BlockHeader: &bc.BlockHeader{Height: 666},
688                                 Transactions: []*bc.Tx{
689                                         CbTx,
690                                         types.MapTx(&types.TxData{
691                                                 SerializedSize: 1,
692                                                 Inputs: []*types.TxInput{
693                                                         types.NewCoinbaseInput(nil),
694                                                         types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp),
695                                                 },
696                                                 Outputs: []*types.TxOutput{
697                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
698                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 90000000, cp),
699                                                 },
700                                         }),
701                                 },
702                         },
703                         txIndex:  1,
704                         GasValid: false,
705                         err:      ErrWrongCoinbaseTransaction,
706                 },
707                 {
708                         block: &bc.Block{
709                                 BlockHeader: &bc.BlockHeader{Height: 666},
710                                 Transactions: []*bc.Tx{
711                                         CbTx,
712                                         types.MapTx(&types.TxData{
713                                                 SerializedSize: 1,
714                                                 Inputs: []*types.TxInput{
715                                                         types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp),
716                                                         types.NewCoinbaseInput(nil),
717                                                 },
718                                                 Outputs: []*types.TxOutput{
719                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
720                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 90000000, cp),
721                                                 },
722                                         }),
723                                 },
724                         },
725                         txIndex:  1,
726                         GasValid: false,
727                         err:      ErrWrongCoinbaseTransaction,
728                 },
729                 {
730                         block: &bc.Block{
731                                 BlockHeader: &bc.BlockHeader{Height: 666},
732                                 Transactions: []*bc.Tx{
733                                         types.MapTx(&types.TxData{
734                                                 SerializedSize: 1,
735                                                 Inputs: []*types.TxInput{
736                                                         types.NewCoinbaseInput(nil),
737                                                         types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp),
738                                                 },
739                                                 Outputs: []*types.TxOutput{
740                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
741                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 90000000, cp),
742                                                 },
743                                         }),
744                                 },
745                         },
746                         txIndex:  0,
747                         GasValid: true,
748                         err:      nil,
749                 },
750                 {
751                         block: &bc.Block{
752                                 BlockHeader: &bc.BlockHeader{Height: 666},
753                                 Transactions: []*bc.Tx{
754                                         types.MapTx(&types.TxData{
755                                                 SerializedSize: 1,
756                                                 Inputs: []*types.TxInput{
757                                                         types.NewCoinbaseInput(nil),
758                                                         types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, retire),
759                                                 },
760                                                 Outputs: []*types.TxOutput{
761                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
762                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 90000000, cp),
763                                                 },
764                                         }),
765                                 },
766                         },
767                         txIndex:  0,
768                         GasValid: false,
769                         err:      vm.ErrReturn,
770                 },
771         }
772
773         for i, c := range cases {
774                 gasStatus, err := ValidateTx(c.block.Transactions[c.txIndex], c.block)
775
776                 if rootErr(err) != c.err {
777                         t.Errorf("#%d got error %s, want %s", i, err, c.err)
778                 }
779                 if c.GasValid != gasStatus.GasValid {
780                         t.Errorf("#%d got GasValid %t, want %t", i, gasStatus.GasValid, c.GasValid)
781                 }
782         }
783 }
784
785 // TestTimeRange test the checkTimeRange function (txtest#1004)
786 func TestTimeRange(t *testing.T) {
787         cases := []struct {
788                 timeRange uint64
789                 err       bool
790         }{
791                 {
792                         timeRange: 0,
793                         err:       false,
794                 },
795                 {
796                         timeRange: 334,
797                         err:       false,
798                 },
799                 {
800                         timeRange: 332,
801                         err:       true,
802                 },
803                 {
804                         timeRange: 1521625824,
805                         err:       false,
806                 },
807         }
808
809         block := &bc.Block{
810                 BlockHeader: &bc.BlockHeader{
811                         Height:    333,
812                         Timestamp: 1521625823000,
813                 },
814         }
815
816         tx := types.MapTx(&types.TxData{
817                 SerializedSize: 1,
818                 TimeRange:      0,
819                 Inputs: []*types.TxInput{
820                         mockGasTxInput(),
821                 },
822                 Outputs: []*types.TxOutput{
823                         types.NewIntraChainOutput(*consensus.BTMAssetID, 1, []byte{0x6a}),
824                 },
825         })
826
827         for i, c := range cases {
828                 tx.TimeRange = c.timeRange
829                 if _, err := ValidateTx(tx, block); (err != nil) != c.err {
830                         t.Errorf("#%d got error %t, want %t", i, !c.err, c.err)
831                 }
832         }
833 }
834
835 func TestStandardTx(t *testing.T) {
836         fixture := sample(t, nil)
837         tx := types.NewTx(*fixture.tx).Tx
838
839         cases := []struct {
840                 desc string
841                 f    func()
842                 err  error
843         }{
844                 {
845                         desc: "normal standard tx",
846                         err:  nil,
847                 },
848                 {
849                         desc: "not standard tx in spend input",
850                         f: func() {
851                                 inputID := tx.GasInputIDs[0]
852                                 spend := tx.Entries[inputID].(*bc.Spend)
853                                 spentOutput, err := tx.IntraChainOutput(*spend.SpentOutputId)
854                                 if err != nil {
855                                         t.Fatal(err)
856                                 }
857                                 spentOutput.ControlProgram = &bc.Program{Code: []byte{0}}
858                         },
859                         err: ErrNotStandardTx,
860                 },
861                 {
862                         desc: "not standard tx in output",
863                         f: func() {
864                                 outputID := tx.ResultIds[0]
865                                 output := tx.Entries[*outputID].(*bc.IntraChainOutput)
866                                 output.ControlProgram = &bc.Program{Code: []byte{0}}
867                         },
868                         err: ErrNotStandardTx,
869                 },
870         }
871
872         for i, c := range cases {
873                 if c.f != nil {
874                         c.f()
875                 }
876                 if err := checkStandardTx(tx, 0); err != c.err {
877                         t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, err, c.err)
878                 }
879         }
880 }
881
882 func TestValidateTxVersion(t *testing.T) {
883         cases := []struct {
884                 desc  string
885                 block *bc.Block
886                 err   error
887         }{
888                 {
889                         desc: "tx version greater than 1 (txtest#1001)",
890                         block: &bc.Block{
891                                 BlockHeader: &bc.BlockHeader{Version: 1},
892                                 Transactions: []*bc.Tx{
893                                         {TxHeader: &bc.TxHeader{Version: 2}},
894                                 },
895                         },
896                         err: ErrTxVersion,
897                 },
898                 {
899                         desc: "tx version equals 0 (txtest#1002)",
900                         block: &bc.Block{
901                                 BlockHeader: &bc.BlockHeader{Version: 1},
902                                 Transactions: []*bc.Tx{
903                                         {TxHeader: &bc.TxHeader{Version: 0}},
904                                 },
905                         },
906                         err: ErrTxVersion,
907                 },
908                 {
909                         desc: "tx version equals max uint64 (txtest#1003)",
910                         block: &bc.Block{
911                                 BlockHeader: &bc.BlockHeader{Version: 1},
912                                 Transactions: []*bc.Tx{
913                                         {TxHeader: &bc.TxHeader{Version: math.MaxUint64}},
914                                 },
915                         },
916                         err: ErrTxVersion,
917                 },
918         }
919
920         for i, c := range cases {
921                 if _, err := ValidateTx(c.block.Transactions[0], c.block); rootErr(err) != c.err {
922                         t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, err, c.err)
923                 }
924         }
925 }
926
927 // A txFixture is returned by sample (below) to produce a sample
928 // transaction, which takes a separate, optional _input_ txFixture to
929 // affect the transaction that's built. The components of the
930 // transaction are the fields of txFixture.
931 type txFixture struct {
932         initialBlockID bc.Hash
933         issuanceProg   bc.Program
934         issuanceArgs   [][]byte
935         assetDef       []byte
936         assetID        bc.AssetID
937         txVersion      uint64
938         txInputs       []*types.TxInput
939         txOutputs      []*types.TxOutput
940         tx             *types.TxData
941 }
942
943 // Produces a sample transaction in a txFixture object (see above). A
944 // separate input txFixture can be used to alter the transaction
945 // that's created.
946 //
947 // The output of this function can be used as the input to a
948 // subsequent call to make iterative refinements to a test object.
949 //
950 // The default transaction produced is valid and has three inputs:
951 //  - an issuance of 10 units
952 //  - a spend of 20 units
953 //  - a spend of 40 units
954 // and two outputs, one of 25 units and one of 45 units.
955 // All amounts are denominated in the same asset.
956 //
957 // The issuance program for the asset requires two numbers as
958 // arguments that add up to 5. The prevout control programs require
959 // two numbers each, adding to 9 and 13, respectively.
960 //
961 // The min and max times for the transaction are now +/- one minute.
962 func sample(tb testing.TB, in *txFixture) *txFixture {
963         var result txFixture
964         if in != nil {
965                 result = *in
966         }
967
968         if result.initialBlockID.IsZero() {
969                 result.initialBlockID = *newHash(1)
970         }
971         if testutil.DeepEqual(result.issuanceProg, bc.Program{}) {
972                 prog, err := vm.Assemble("ADD 5 NUMEQUAL")
973                 if err != nil {
974                         tb.Fatal(err)
975                 }
976                 result.issuanceProg = bc.Program{VmVersion: 1, Code: prog}
977         }
978         if len(result.issuanceArgs) == 0 {
979                 result.issuanceArgs = [][]byte{{2}, {3}}
980         }
981         if len(result.assetDef) == 0 {
982                 result.assetDef = []byte{2}
983         }
984         if result.assetID.IsZero() {
985                 result.assetID = bc.AssetID{V0: 9999}
986         }
987
988         if result.txVersion == 0 {
989                 result.txVersion = 1
990         }
991         if len(result.txInputs) == 0 {
992                 cp1, err := vm.Assemble("ADD 9 NUMEQUAL")
993                 if err != nil {
994                         tb.Fatal(err)
995                 }
996                 args1 := [][]byte{{4}, {5}}
997
998                 cp2, err := vm.Assemble("ADD 13 NUMEQUAL")
999                 if err != nil {
1000                         tb.Fatal(err)
1001                 }
1002                 args2 := [][]byte{{6}, {7}}
1003
1004                 result.txInputs = []*types.TxInput{
1005                         types.NewSpendInput(nil, *newHash(9), result.assetID, 10, 0, []byte{byte(vm.OP_TRUE)}),
1006                         types.NewSpendInput(args1, *newHash(5), result.assetID, 20, 0, cp1),
1007                         types.NewSpendInput(args2, *newHash(8), result.assetID, 40, 0, cp2),
1008                 }
1009         }
1010
1011         result.txInputs = append(result.txInputs, mockGasTxInput())
1012
1013         if len(result.txOutputs) == 0 {
1014                 cp1, err := vm.Assemble("ADD 17 NUMEQUAL")
1015                 if err != nil {
1016                         tb.Fatal(err)
1017                 }
1018                 cp2, err := vm.Assemble("ADD 21 NUMEQUAL")
1019                 if err != nil {
1020                         tb.Fatal(err)
1021                 }
1022
1023                 result.txOutputs = []*types.TxOutput{
1024                         types.NewIntraChainOutput(result.assetID, 25, cp1),
1025                         types.NewIntraChainOutput(result.assetID, 45, cp2),
1026                 }
1027         }
1028
1029         result.tx = &types.TxData{
1030                 Version: result.txVersion,
1031                 Inputs:  result.txInputs,
1032                 Outputs: result.txOutputs,
1033         }
1034
1035         return &result
1036 }
1037
1038 func mockBlock() *bc.Block {
1039         return &bc.Block{
1040                 BlockHeader: &bc.BlockHeader{
1041                         Height: 666,
1042                 },
1043         }
1044 }
1045
1046 func mockGasTxInput() *types.TxInput {
1047         cp, _ := vmutil.DefaultCoinbaseProgram()
1048         return types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp)
1049 }
1050
1051 // Like errors.Root, but also unwraps vm.Error objects.
1052 func rootErr(e error) error {
1053         return errors.Root(e)
1054 }
1055
1056 func hashData(data []byte) bc.Hash {
1057         var b32 [32]byte
1058         sha3pool.Sum256(b32[:], data)
1059         return bc.NewHash(b32)
1060 }
1061
1062 func newHash(n byte) *bc.Hash {
1063         h := bc.NewHash([32]byte{n})
1064         return &h
1065 }
1066
1067 func newAssetID(n byte) *bc.AssetID {
1068         a := bc.NewAssetID([32]byte{n})
1069         return &a
1070 }
1071
1072 func txSpend(t *testing.T, tx *bc.Tx, index int) *bc.Spend {
1073         id := tx.InputIDs[index]
1074         res, err := tx.Spend(id)
1075         if err != nil {
1076                 t.Fatal(err)
1077         }
1078         return res
1079 }
1080
1081 func getMuxID(tx *bc.Tx) *bc.Hash {
1082         out := tx.Entries[*tx.ResultIds[0]]
1083         switch result := out.(type) {
1084         case *bc.IntraChainOutput:
1085                 return result.Source.Ref
1086         case *bc.Retirement:
1087                 return result.Source.Ref
1088         }
1089         return nil
1090 }