OSDN Git Service

modify parameter name (#411)
[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 + consensus.ActiveNetParams.DefaultGasCredit,
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:  640000,
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:  640000,
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: "normal check with 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: nil,
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 func TestMagneticContractTx(t *testing.T) {
881         buyerArgs := vmutil.MagneticContractArgs{
882                 RequestedAsset:   bc.AssetID{V0: 1},
883                 RatioNumerator:   1,
884                 RatioDenominator: 2,
885                 SellerProgram:    []byte{0x51},
886                 SellerKey:        testutil.MustDecodeHexString("960ecabafb88ba460a40912841afecebf0e84884178611ac97210e327c0d1173"),
887         }
888
889         sellerArgs := vmutil.MagneticContractArgs{
890                 RequestedAsset:   bc.AssetID{V0: 2},
891                 RatioNumerator:   2,
892                 RatioDenominator: 1,
893                 SellerProgram:    []byte{0x52},
894                 SellerKey:        testutil.MustDecodeHexString("ad79ec6bd3a6d6dbe4d0ee902afc99a12b9702fb63edce5f651db3081d868b75"),
895         }
896
897         programBuyer, err := vmutil.P2WMCProgram(buyerArgs)
898         if err != nil {
899                 t.Fatal(err)
900         }
901
902         programSeller, err := vmutil.P2WMCProgram(sellerArgs)
903         if err != nil {
904                 t.Fatal(err)
905         }
906
907         cases := []struct {
908                 desc  string
909                 block *bc.Block
910                 err   error
911         }{
912                 {
913                         desc: "contracts all full trade",
914                         block: &bc.Block{
915                                 BlockHeader: &bc.BlockHeader{Version: 0},
916                                 Transactions: []*bc.Tx{
917                                         types.MapTx(&types.TxData{
918                                                 SerializedSize: 1,
919                                                 Inputs: []*types.TxInput{
920                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
921                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
922                                                 },
923                                                 Outputs: []*types.TxOutput{
924                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, sellerArgs.SellerProgram),
925                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, buyerArgs.SellerProgram),
926                                                 },
927                                         }),
928                                 },
929                         },
930                         err: nil,
931                 },
932                 {
933                         desc: "first contract partial trade, second contract full trade",
934                         block: &bc.Block{
935                                 BlockHeader: &bc.BlockHeader{Version: 0},
936                                 Transactions: []*bc.Tx{
937                                         types.MapTx(&types.TxData{
938                                                 SerializedSize: 1,
939                                                 Inputs: []*types.TxInput{
940                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(0), vm.Int64Bytes(0)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 200000000, 1, programSeller),
941                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer),
942                                                 },
943                                                 Outputs: []*types.TxOutput{
944                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
945                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 150000000, programSeller),
946                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, buyerArgs.SellerProgram),
947                                                 },
948                                         }),
949                                 },
950                         },
951                         err: nil,
952                 },
953                 {
954                         desc: "first contract full trade, second contract partial trade",
955                         block: &bc.Block{
956                                 BlockHeader: &bc.BlockHeader{Version: 0},
957                                 Transactions: []*bc.Tx{
958                                         types.MapTx(&types.TxData{
959                                                 SerializedSize: 1,
960                                                 Inputs: []*types.TxInput{
961                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
962                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(1), vm.Int64Bytes(0)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 300000000, 0, programBuyer),
963                                                 },
964                                                 Outputs: []*types.TxOutput{
965                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, sellerArgs.SellerProgram),
966                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, buyerArgs.SellerProgram),
967                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, programBuyer),
968                                                 },
969                                         }),
970                                 },
971                         },
972                         err: nil,
973                 },
974                 {
975                         desc: "cancel magnetic contract",
976                         block: &bc.Block{
977                                 BlockHeader: &bc.BlockHeader{Version: 0},
978                                 Transactions: []*bc.Tx{
979                                         types.MapTx(&types.TxData{
980                                                 SerializedSize: 1,
981                                                 Inputs: []*types.TxInput{
982                                                         types.NewSpendInput([][]byte{testutil.MustDecodeHexString("0a72a2b2944ec9b4bcdef392e6c532effc77ea536809fa290a12e39df7651851a9939e23e492369dc8936e0ebf3ecd1de4e9077d0593bd3fcb5874fb26dfc60a"), vm.Int64Bytes(0), vm.Int64Bytes(2)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 0, programSeller),
983                                                 },
984                                                 Outputs: []*types.TxOutput{
985                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
986                                                 },
987                                         }),
988                                 },
989                         },
990                         err: nil,
991                 },
992                 {
993                         desc: "wrong signature with cancel magnetic contract",
994                         block: &bc.Block{
995                                 BlockHeader: &bc.BlockHeader{Version: 0},
996                                 Transactions: []*bc.Tx{
997                                         types.MapTx(&types.TxData{
998                                                 SerializedSize: 1,
999                                                 Inputs: []*types.TxInput{
1000                                                         types.NewSpendInput([][]byte{testutil.MustDecodeHexString("686b983a8de1893ef723144389fd1f07b12b048f52f389faa863243195931d5732dbfd15470b43ed63d5067900718cf94f137073f4a972d277bbd967b022545d"), vm.Int64Bytes(0), vm.Int64Bytes(2)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 0, programSeller),
1001                                                 },
1002                                                 Outputs: []*types.TxOutput{
1003                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
1004                                                 },
1005                                         }),
1006                                 },
1007                         },
1008                         err: vm.ErrFalseVMResult,
1009                 },
1010                 {
1011                         desc: "wrong output amount with contracts all full trade",
1012                         block: &bc.Block{
1013                                 BlockHeader: &bc.BlockHeader{Version: 0},
1014                                 Transactions: []*bc.Tx{
1015                                         types.MapTx(&types.TxData{
1016                                                 SerializedSize: 1,
1017                                                 Inputs: []*types.TxInput{
1018                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
1019                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
1020                                                 },
1021                                                 Outputs: []*types.TxOutput{
1022                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, sellerArgs.SellerProgram),
1023                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, buyerArgs.SellerProgram),
1024                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, []byte{0x55}),
1025                                                 },
1026                                         }),
1027                                 },
1028                         },
1029                         err: vm.ErrFalseVMResult,
1030                 },
1031                 {
1032                         desc: "wrong output assetID with contracts all full trade",
1033                         block: &bc.Block{
1034                                 BlockHeader: &bc.BlockHeader{Version: 0},
1035                                 Transactions: []*bc.Tx{
1036                                         types.MapTx(&types.TxData{
1037                                                 SerializedSize: 1,
1038                                                 Inputs: []*types.TxInput{
1039                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
1040                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
1041                                                         types.NewSpendInput(nil, bc.Hash{V0: 30}, bc.AssetID{V0: 1}, 200000000, 0, []byte{0x51}),
1042                                                 },
1043                                                 Outputs: []*types.TxOutput{
1044                                                         types.NewIntraChainOutput(bc.AssetID{V0: 1}, 200000000, sellerArgs.SellerProgram),
1045                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, buyerArgs.SellerProgram),
1046                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, []byte{0x55}),
1047                                                 },
1048                                         }),
1049                                 },
1050                         },
1051                         err: vm.ErrFalseVMResult,
1052                 },
1053                 {
1054                         desc: "wrong output change program with first contract partial trade and second contract full trade",
1055                         block: &bc.Block{
1056                                 BlockHeader: &bc.BlockHeader{Version: 0},
1057                                 Transactions: []*bc.Tx{
1058                                         types.MapTx(&types.TxData{
1059                                                 SerializedSize: 1,
1060                                                 Inputs: []*types.TxInput{
1061                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(0), vm.Int64Bytes(0)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 200000000, 1, programSeller),
1062                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer),
1063                                                 },
1064                                                 Outputs: []*types.TxOutput{
1065                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
1066                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 150000000, []byte{0x55}),
1067                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, buyerArgs.SellerProgram),
1068                                                 },
1069                                         }),
1070                                 },
1071                         },
1072                         err: vm.ErrFalseVMResult,
1073                 },
1074         }
1075
1076         for i, c := range cases {
1077                 if _, err := ValidateTx(c.block.Transactions[0], c.block); rootErr(err) != c.err {
1078                         t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, rootErr(err), c.err)
1079                 }
1080         }
1081 }
1082
1083 func TestRingMagneticContractTx(t *testing.T) {
1084         aliceArgs := vmutil.MagneticContractArgs{
1085                 RequestedAsset:   bc.AssetID{V0: 1},
1086                 RatioNumerator:   2,
1087                 RatioDenominator: 1,
1088                 SellerProgram:    []byte{0x51},
1089                 SellerKey:        testutil.MustDecodeHexString("960ecabafb88ba460a40912841afecebf0e84884178611ac97210e327c0d1173"),
1090         }
1091
1092         bobArgs := vmutil.MagneticContractArgs{
1093                 RequestedAsset:   bc.AssetID{V0: 2},
1094                 RatioNumerator:   2,
1095                 RatioDenominator: 1,
1096                 SellerProgram:    []byte{0x52},
1097                 SellerKey:        testutil.MustDecodeHexString("ad79ec6bd3a6d6dbe4d0ee902afc99a12b9702fb63edce5f651db3081d868b75"),
1098         }
1099
1100         jackArgs := vmutil.MagneticContractArgs{
1101                 RequestedAsset:   bc.AssetID{V0: 3},
1102                 RatioNumerator:   1,
1103                 RatioDenominator: 4,
1104                 SellerProgram:    []byte{0x53},
1105                 SellerKey:        testutil.MustDecodeHexString("9c19a91988c62046c2767bd7e9999b0c142891b9ebf467bfa59210b435cb0de7"),
1106         }
1107
1108         aliceProgram, err := vmutil.P2WMCProgram(aliceArgs)
1109         if err != nil {
1110                 t.Fatal(err)
1111         }
1112
1113         bobProgram, err := vmutil.P2WMCProgram(bobArgs)
1114         if err != nil {
1115                 t.Fatal(err)
1116         }
1117
1118         jackProgram, err := vmutil.P2WMCProgram(jackArgs)
1119         if err != nil {
1120                 t.Fatal(err)
1121         }
1122
1123         cases := []struct {
1124                 desc  string
1125                 block *bc.Block
1126                 err   error
1127         }{
1128                 {
1129                         desc: "contracts all full trade",
1130                         block: &bc.Block{
1131                                 BlockHeader: &bc.BlockHeader{Version: 0},
1132                                 Transactions: []*bc.Tx{
1133                                         types.MapTx(&types.TxData{
1134                                                 SerializedSize: 1,
1135                                                 Inputs: []*types.TxInput{
1136                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, jackArgs.RequestedAsset, 100000000, 0, aliceProgram),
1137                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, aliceArgs.RequestedAsset, 200000000, 0, bobProgram),
1138                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 30}, bobArgs.RequestedAsset, 400000000, 0, jackProgram),
1139                                                 },
1140                                                 Outputs: []*types.TxOutput{
1141                                                         types.NewIntraChainOutput(aliceArgs.RequestedAsset, 200000000, aliceArgs.SellerProgram),
1142                                                         types.NewIntraChainOutput(bobArgs.RequestedAsset, 400000000, bobArgs.SellerProgram),
1143                                                         types.NewIntraChainOutput(jackArgs.RequestedAsset, 100000000, jackArgs.SellerProgram),
1144                                                 },
1145                                         }),
1146                                 },
1147                         },
1148                         err: nil,
1149                 },
1150         }
1151
1152         for i, c := range cases {
1153                 if _, err := ValidateTx(c.block.Transactions[0], c.block); rootErr(err) != c.err {
1154                         t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, rootErr(err), c.err)
1155                 }
1156         }
1157 }
1158
1159 // A txFixture is returned by sample (below) to produce a sample
1160 // transaction, which takes a separate, optional _input_ txFixture to
1161 // affect the transaction that's built. The components of the
1162 // transaction are the fields of txFixture.
1163 type txFixture struct {
1164         initialBlockID bc.Hash
1165         issuanceProg   bc.Program
1166         issuanceArgs   [][]byte
1167         assetDef       []byte
1168         assetID        bc.AssetID
1169         txVersion      uint64
1170         txInputs       []*types.TxInput
1171         txOutputs      []*types.TxOutput
1172         tx             *types.TxData
1173 }
1174
1175 // Produces a sample transaction in a txFixture object (see above). A
1176 // separate input txFixture can be used to alter the transaction
1177 // that's created.
1178 //
1179 // The output of this function can be used as the input to a
1180 // subsequent call to make iterative refinements to a test object.
1181 //
1182 // The default transaction produced is valid and has three inputs:
1183 //  - an issuance of 10 units
1184 //  - a spend of 20 units
1185 //  - a spend of 40 units
1186 // and two outputs, one of 25 units and one of 45 units.
1187 // All amounts are denominated in the same asset.
1188 //
1189 // The issuance program for the asset requires two numbers as
1190 // arguments that add up to 5. The prevout control programs require
1191 // two numbers each, adding to 9 and 13, respectively.
1192 //
1193 // The min and max times for the transaction are now +/- one minute.
1194 func sample(tb testing.TB, in *txFixture) *txFixture {
1195         var result txFixture
1196         if in != nil {
1197                 result = *in
1198         }
1199
1200         if result.initialBlockID.IsZero() {
1201                 result.initialBlockID = *newHash(1)
1202         }
1203         if testutil.DeepEqual(result.issuanceProg, bc.Program{}) {
1204                 prog, err := vm.Assemble("ADD 5 NUMEQUAL")
1205                 if err != nil {
1206                         tb.Fatal(err)
1207                 }
1208                 result.issuanceProg = bc.Program{VmVersion: 1, Code: prog}
1209         }
1210         if len(result.issuanceArgs) == 0 {
1211                 result.issuanceArgs = [][]byte{{2}, {3}}
1212         }
1213         if len(result.assetDef) == 0 {
1214                 result.assetDef = []byte{2}
1215         }
1216         if result.assetID.IsZero() {
1217                 result.assetID = bc.AssetID{V0: 9999}
1218         }
1219
1220         if result.txVersion == 0 {
1221                 result.txVersion = 1
1222         }
1223         if len(result.txInputs) == 0 {
1224                 cp1, err := vm.Assemble("ADD 9 NUMEQUAL")
1225                 if err != nil {
1226                         tb.Fatal(err)
1227                 }
1228                 args1 := [][]byte{{4}, {5}}
1229
1230                 cp2, err := vm.Assemble("ADD 13 NUMEQUAL")
1231                 if err != nil {
1232                         tb.Fatal(err)
1233                 }
1234                 args2 := [][]byte{{6}, {7}}
1235
1236                 result.txInputs = []*types.TxInput{
1237                         types.NewSpendInput(nil, *newHash(9), result.assetID, 10, 0, []byte{byte(vm.OP_TRUE)}),
1238                         types.NewSpendInput(args1, *newHash(5), result.assetID, 20, 0, cp1),
1239                         types.NewSpendInput(args2, *newHash(8), result.assetID, 40, 0, cp2),
1240                 }
1241         }
1242
1243         result.txInputs = append(result.txInputs, mockGasTxInput())
1244
1245         if len(result.txOutputs) == 0 {
1246                 cp1, err := vm.Assemble("ADD 17 NUMEQUAL")
1247                 if err != nil {
1248                         tb.Fatal(err)
1249                 }
1250                 cp2, err := vm.Assemble("ADD 21 NUMEQUAL")
1251                 if err != nil {
1252                         tb.Fatal(err)
1253                 }
1254
1255                 result.txOutputs = []*types.TxOutput{
1256                         types.NewIntraChainOutput(result.assetID, 25, cp1),
1257                         types.NewIntraChainOutput(result.assetID, 45, cp2),
1258                 }
1259         }
1260
1261         result.tx = &types.TxData{
1262                 Version: result.txVersion,
1263                 Inputs:  result.txInputs,
1264                 Outputs: result.txOutputs,
1265         }
1266
1267         return &result
1268 }
1269
1270 func mockBlock() *bc.Block {
1271         return &bc.Block{
1272                 BlockHeader: &bc.BlockHeader{
1273                         Height: 666,
1274                 },
1275         }
1276 }
1277
1278 func mockGasTxInput() *types.TxInput {
1279         cp, _ := vmutil.DefaultCoinbaseProgram()
1280         return types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp)
1281 }
1282
1283 // Like errors.Root, but also unwraps vm.Error objects.
1284 func rootErr(e error) error {
1285         return errors.Root(e)
1286 }
1287
1288 func hashData(data []byte) bc.Hash {
1289         var b32 [32]byte
1290         sha3pool.Sum256(b32[:], data)
1291         return bc.NewHash(b32)
1292 }
1293
1294 func newHash(n byte) *bc.Hash {
1295         h := bc.NewHash([32]byte{n})
1296         return &h
1297 }
1298
1299 func newAssetID(n byte) *bc.AssetID {
1300         a := bc.NewAssetID([32]byte{n})
1301         return &a
1302 }
1303
1304 func txSpend(t *testing.T, tx *bc.Tx, index int) *bc.Spend {
1305         id := tx.InputIDs[index]
1306         res, err := tx.Spend(id)
1307         if err != nil {
1308                 t.Fatal(err)
1309         }
1310         return res
1311 }
1312
1313 func getMuxID(tx *bc.Tx) *bc.Hash {
1314         out := tx.Entries[*tx.ResultIds[0]]
1315         switch result := out.(type) {
1316         case *bc.IntraChainOutput:
1317                 return result.Source.Ref
1318         case *bc.Retirement:
1319                 return result.Source.Ref
1320         }
1321         return nil
1322 }