OSDN Git Service

17664df3034271d6ced8ed6aa32a62a583bf3be0
[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.ActiveNetParams.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.ActiveNetParams.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.ActiveNetParams.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 i, amount := range inputs {
235                         txInput := types.NewSpendInput(nil, *sourceID, *consensus.BTMAssetID, amount, uint64(i), 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.ActiveNetParams.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.NewCoinbaseInput(nil),
716                                                         types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp),
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 TestValidateTxVersion(t *testing.T) {
836         cases := []struct {
837                 desc  string
838                 block *bc.Block
839                 err   error
840         }{
841                 {
842                         desc: "tx version greater than 1 (txtest#1001)",
843                         block: &bc.Block{
844                                 BlockHeader: &bc.BlockHeader{Version: 1},
845                                 Transactions: []*bc.Tx{
846                                         {TxHeader: &bc.TxHeader{Version: 2}},
847                                 },
848                         },
849                         err: ErrTxVersion,
850                 },
851                 {
852                         desc: "tx version equals 0 (txtest#1002)",
853                         block: &bc.Block{
854                                 BlockHeader: &bc.BlockHeader{Version: 1},
855                                 Transactions: []*bc.Tx{
856                                         {TxHeader: &bc.TxHeader{Version: 0}},
857                                 },
858                         },
859                         err: ErrTxVersion,
860                 },
861                 {
862                         desc: "tx version equals max uint64 (txtest#1003)",
863                         block: &bc.Block{
864                                 BlockHeader: &bc.BlockHeader{Version: 1},
865                                 Transactions: []*bc.Tx{
866                                         {TxHeader: &bc.TxHeader{Version: math.MaxUint64}},
867                                 },
868                         },
869                         err: ErrTxVersion,
870                 },
871         }
872
873         for i, c := range cases {
874                 if _, err := ValidateTx(c.block.Transactions[0], c.block); rootErr(err) != c.err {
875                         t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, err, c.err)
876                 }
877         }
878 }
879
880 // A txFixture is returned by sample (below) to produce a sample
881 // transaction, which takes a separate, optional _input_ txFixture to
882 // affect the transaction that's built. The components of the
883 // transaction are the fields of txFixture.
884 type txFixture struct {
885         initialBlockID bc.Hash
886         issuanceProg   bc.Program
887         issuanceArgs   [][]byte
888         assetDef       []byte
889         assetID        bc.AssetID
890         txVersion      uint64
891         txInputs       []*types.TxInput
892         txOutputs      []*types.TxOutput
893         tx             *types.TxData
894 }
895
896 // Produces a sample transaction in a txFixture object (see above). A
897 // separate input txFixture can be used to alter the transaction
898 // that's created.
899 //
900 // The output of this function can be used as the input to a
901 // subsequent call to make iterative refinements to a test object.
902 //
903 // The default transaction produced is valid and has three inputs:
904 //  - an issuance of 10 units
905 //  - a spend of 20 units
906 //  - a spend of 40 units
907 // and two outputs, one of 25 units and one of 45 units.
908 // All amounts are denominated in the same asset.
909 //
910 // The issuance program for the asset requires two numbers as
911 // arguments that add up to 5. The prevout control programs require
912 // two numbers each, adding to 9 and 13, respectively.
913 //
914 // The min and max times for the transaction are now +/- one minute.
915 func sample(tb testing.TB, in *txFixture) *txFixture {
916         var result txFixture
917         if in != nil {
918                 result = *in
919         }
920
921         if result.initialBlockID.IsZero() {
922                 result.initialBlockID = *newHash(1)
923         }
924         if testutil.DeepEqual(result.issuanceProg, bc.Program{}) {
925                 prog, err := vm.Assemble("ADD 5 NUMEQUAL")
926                 if err != nil {
927                         tb.Fatal(err)
928                 }
929                 result.issuanceProg = bc.Program{VmVersion: 1, Code: prog}
930         }
931         if len(result.issuanceArgs) == 0 {
932                 result.issuanceArgs = [][]byte{{2}, {3}}
933         }
934         if len(result.assetDef) == 0 {
935                 result.assetDef = []byte{2}
936         }
937         if result.assetID.IsZero() {
938                 result.assetID = bc.AssetID{V0: 9999}
939         }
940
941         if result.txVersion == 0 {
942                 result.txVersion = 1
943         }
944         if len(result.txInputs) == 0 {
945                 cp1, err := vm.Assemble("ADD 9 NUMEQUAL")
946                 if err != nil {
947                         tb.Fatal(err)
948                 }
949                 args1 := [][]byte{{4}, {5}}
950
951                 cp2, err := vm.Assemble("ADD 13 NUMEQUAL")
952                 if err != nil {
953                         tb.Fatal(err)
954                 }
955                 args2 := [][]byte{{6}, {7}}
956
957                 result.txInputs = []*types.TxInput{
958                         types.NewSpendInput(nil, *newHash(9), result.assetID, 10, 0, []byte{byte(vm.OP_TRUE)}),
959                         types.NewSpendInput(args1, *newHash(5), result.assetID, 20, 0, cp1),
960                         types.NewSpendInput(args2, *newHash(8), result.assetID, 40, 0, cp2),
961                 }
962         }
963
964         result.txInputs = append(result.txInputs, mockGasTxInput())
965
966         if len(result.txOutputs) == 0 {
967                 cp1, err := vm.Assemble("ADD 17 NUMEQUAL")
968                 if err != nil {
969                         tb.Fatal(err)
970                 }
971                 cp2, err := vm.Assemble("ADD 21 NUMEQUAL")
972                 if err != nil {
973                         tb.Fatal(err)
974                 }
975
976                 result.txOutputs = []*types.TxOutput{
977                         types.NewIntraChainOutput(result.assetID, 25, cp1),
978                         types.NewIntraChainOutput(result.assetID, 45, cp2),
979                 }
980         }
981
982         result.tx = &types.TxData{
983                 Version: result.txVersion,
984                 Inputs:  result.txInputs,
985                 Outputs: result.txOutputs,
986         }
987
988         return &result
989 }
990
991 func mockBlock() *bc.Block {
992         return &bc.Block{
993                 BlockHeader: &bc.BlockHeader{
994                         Height: 666,
995                 },
996         }
997 }
998
999 func mockGasTxInput() *types.TxInput {
1000         cp, _ := vmutil.DefaultCoinbaseProgram()
1001         return types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp)
1002 }
1003
1004 // Like errors.Root, but also unwraps vm.Error objects.
1005 func rootErr(e error) error {
1006         return errors.Root(e)
1007 }
1008
1009 func hashData(data []byte) bc.Hash {
1010         var b32 [32]byte
1011         sha3pool.Sum256(b32[:], data)
1012         return bc.NewHash(b32)
1013 }
1014
1015 func newHash(n byte) *bc.Hash {
1016         h := bc.NewHash([32]byte{n})
1017         return &h
1018 }
1019
1020 func newAssetID(n byte) *bc.AssetID {
1021         a := bc.NewAssetID([32]byte{n})
1022         return &a
1023 }
1024
1025 func txSpend(t *testing.T, tx *bc.Tx, index int) *bc.Spend {
1026         id := tx.InputIDs[index]
1027         res, err := tx.Spend(id)
1028         if err != nil {
1029                 t.Fatal(err)
1030         }
1031         return res
1032 }
1033
1034 func getMuxID(tx *bc.Tx) *bc.Hash {
1035         out := tx.Entries[*tx.ResultIds[0]]
1036         switch result := out.(type) {
1037         case *bc.IntraChainOutput:
1038                 return result.Source.Ref
1039         case *bc.Retirement:
1040                 return result.Source.Ref
1041         }
1042         return nil
1043 }