OSDN Git Service

add validate tx overflow tests
[bytom/bytom.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/bytom/consensus"
10         "github.com/bytom/crypto/sha3pool"
11         "github.com/bytom/errors"
12         "github.com/bytom/protocol/bc"
13         "github.com/bytom/protocol/bc/types"
14         "github.com/bytom/protocol/vm"
15         "github.com/bytom/protocol/vm/vmutil"
16         "github.com/bytom/testutil"
17 )
18
19 func init() {
20         spew.Config.DisableMethods = true
21 }
22
23 func TestGasStatus(t *testing.T) {
24         cases := []struct {
25                 input  *GasState
26                 output *GasState
27                 f      func(*GasState) error
28                 err    error
29         }{
30                 {
31                         input: &GasState{
32                                 GasLeft:  10000,
33                                 GasUsed:  0,
34                                 BTMValue: 0,
35                         },
36                         output: &GasState{
37                                 GasLeft:  10000 / consensus.VMGasRate,
38                                 GasUsed:  0,
39                                 BTMValue: 10000,
40                         },
41                         f: func(input *GasState) error {
42                                 return input.setGas(10000, 0)
43                         },
44                         err: nil,
45                 },
46                 {
47                         input: &GasState{
48                                 GasLeft:  10000,
49                                 GasUsed:  0,
50                                 BTMValue: 0,
51                         },
52                         output: &GasState{
53                                 GasLeft:  10000,
54                                 GasUsed:  0,
55                                 BTMValue: 0,
56                         },
57                         f: func(input *GasState) error {
58                                 return input.setGas(-10000, 0)
59                         },
60                         err: errGasCalculate,
61                 },
62                 {
63                         input: &GasState{
64                                 GasLeft:  consensus.DefaultGasCredit,
65                                 GasUsed:  0,
66                                 BTMValue: 0,
67                         },
68                         output: &GasState{
69                                 GasLeft:  200000,
70                                 GasUsed:  0,
71                                 BTMValue: 80000000000,
72                         },
73                         f: func(input *GasState) error {
74                                 return input.setGas(80000000000, 0)
75                         },
76                         err: nil,
77                 },
78                 {
79                         input: &GasState{
80                                 GasLeft:  10000,
81                                 GasUsed:  0,
82                                 BTMValue: 0,
83                         },
84                         output: &GasState{
85                                 GasLeft:  10000,
86                                 GasUsed:  0,
87                                 BTMValue: 0,
88                         },
89                         f: func(input *GasState) error {
90                                 return input.updateUsage(-1)
91                         },
92                         err: errGasCalculate,
93                 },
94                 {
95                         input: &GasState{
96                                 GasLeft:  10000,
97                                 GasUsed:  0,
98                                 BTMValue: 0,
99                         },
100                         output: &GasState{
101                                 GasLeft:  9999,
102                                 GasUsed:  1,
103                                 BTMValue: 0,
104                         },
105                         f: func(input *GasState) error {
106                                 return input.updateUsage(9999)
107                         },
108                         err: nil,
109                 },
110         }
111
112         for i, c := range cases {
113                 err := c.f(c.input)
114
115                 if rootErr(err) != c.err {
116                         t.Errorf("case %d: got error %s, want %s", i, err, c.err)
117                 } else if *c.input != *c.output {
118                         t.Errorf("case %d: gasStatus %v, want %v;", i, c.input, c.output)
119                 }
120         }
121 }
122
123 func TestOverflow(t *testing.T) {
124         sourceID := &bc.Hash{V0: 9999}
125         ctrlProgram := []byte{byte(vm.OP_TRUE)}
126         newTx := func(inputs []uint64, outputs []uint64) *bc.Tx {
127                 txInputs := make([]*types.TxInput, 0, len(inputs))
128                 txOutputs := make([]*types.TxOutput, 0, len(outputs))
129
130                 for _, amount := range inputs {
131                         txInput := types.NewSpendInput(nil, *sourceID, *consensus.BTMAssetID, amount, 0, ctrlProgram)
132                         txInputs = append(txInputs, txInput)
133                 }
134
135                 for _, amount := range outputs {
136                         txOutput := types.NewTxOutput(*consensus.BTMAssetID, amount, ctrlProgram)
137                         txOutputs = append(txOutputs, txOutput)
138                 }
139
140                 txData := &types.TxData{
141                         Version:        1,
142                         SerializedSize: 100,
143                         TimeRange:      0,
144                         Inputs:         txInputs,
145                         Outputs:        txOutputs,
146                 }
147                 return types.MapTx(txData)
148         }
149
150         cases := []struct {
151                 inputs  []uint64
152                 outputs []uint64
153                 err     error
154         }{
155                 {
156                         inputs:  []uint64{math.MaxUint64, 1},
157                         outputs: []uint64{0},
158                         err:     errOverflow,
159                 },
160                 {
161                         inputs:  []uint64{math.MaxUint64, math.MaxUint64},
162                         outputs: []uint64{0},
163                         err:     errOverflow,
164                 },
165                 {
166                         inputs:  []uint64{math.MaxUint64, math.MaxUint64 - 1},
167                         outputs: []uint64{0},
168                         err:     errOverflow,
169                 },
170                 {
171                         inputs:  []uint64{math.MaxInt64, 1},
172                         outputs: []uint64{0},
173                         err:     errOverflow,
174                 },
175                 {
176                         inputs:  []uint64{math.MaxInt64, math.MaxInt64},
177                         outputs: []uint64{0},
178                         err:     errOverflow,
179                 },
180                 {
181                         inputs:  []uint64{math.MaxInt64, math.MaxInt64 - 1},
182                         outputs: []uint64{0},
183                         err:     errOverflow,
184                 },
185                 {
186                         inputs:  []uint64{0},
187                         outputs: []uint64{math.MaxUint64},
188                         err:     errOverflow,
189                 },
190                 {
191                         inputs:  []uint64{0},
192                         outputs: []uint64{math.MaxInt64},
193                         err:     errGasCalculate,
194                 },
195                 {
196                         inputs:  []uint64{math.MaxInt64 - 1},
197                         outputs: []uint64{math.MaxInt64},
198                         err:     errGasCalculate,
199                 },
200         }
201
202         for i, c := range cases {
203                 tx := newTx(c.inputs, c.outputs)
204                 if _, err := ValidateTx(tx, mockBlock()); rootErr(err) != c.err {
205                         t.Fatalf("case %d test failed, want %s, have %s", i, c.err, rootErr(err))
206                 }
207         }
208 }
209
210 func TestTxValidation(t *testing.T) {
211         var (
212                 tx      *bc.Tx
213                 vs      *validationState
214                 fixture *txFixture
215
216                 // the mux from tx, pulled out for convenience
217                 mux *bc.Mux
218         )
219
220         addCoinbase := func(assetID *bc.AssetID, amount uint64, arbitrary []byte) {
221                 coinbase := bc.NewCoinbase(arbitrary)
222                 txOutput := types.NewTxOutput(*assetID, amount, []byte{byte(vm.OP_TRUE)})
223                 muxID := getMuxID(tx)
224                 coinbase.SetDestination(muxID, &txOutput.AssetAmount, uint64(len(mux.Sources)))
225                 coinbaseID := bc.EntryID(coinbase)
226                 tx.Entries[coinbaseID] = coinbase
227
228                 mux.Sources = append(mux.Sources, &bc.ValueSource{
229                         Ref:   &coinbaseID,
230                         Value: &txOutput.AssetAmount,
231                 })
232
233                 src := &bc.ValueSource{
234                         Ref:      muxID,
235                         Value:    &txOutput.AssetAmount,
236                         Position: uint64(len(tx.ResultIds)),
237                 }
238                 prog := &bc.Program{txOutput.VMVersion, txOutput.ControlProgram}
239                 output := bc.NewOutput(src, prog, uint64(len(tx.ResultIds)))
240                 outputID := bc.EntryID(output)
241                 tx.Entries[outputID] = output
242
243                 dest := &bc.ValueDestination{
244                         Value:    src.Value,
245                         Ref:      &outputID,
246                         Position: 0,
247                 }
248                 mux.WitnessDestinations = append(mux.WitnessDestinations, dest)
249                 tx.ResultIds = append(tx.ResultIds, &outputID)
250                 vs.block.Transactions = append(vs.block.Transactions, vs.tx)
251         }
252
253         cases := []struct {
254                 desc string // description of the test case
255                 f    func() // function to adjust tx, vs, and/or mux
256                 err  error  // expected error
257         }{
258                 {
259                         desc: "base case",
260                 },
261                 {
262                         desc: "unbalanced mux amounts",
263                         f: func() {
264                                 mux.Sources[0].Value.Amount++
265                                 iss := tx.Entries[*mux.Sources[0].Ref].(*bc.Issuance)
266                                 iss.WitnessDestination.Value.Amount++
267                         },
268                         err: errUnbalanced,
269                 },
270                 {
271                         desc: "unbalanced mux amounts",
272                         f: func() {
273                                 mux.WitnessDestinations[0].Value.Amount++
274                         },
275                         err: errUnbalanced,
276                 },
277                 {
278                         desc: "balanced mux amounts",
279                         f: func() {
280                                 mux.Sources[1].Value.Amount++
281                                 mux.WitnessDestinations[0].Value.Amount++
282                         },
283                         err: nil,
284                 },
285                 {
286                         desc: "overflowing mux source amounts",
287                         f: func() {
288                                 mux.Sources[0].Value.Amount = math.MaxInt64
289                                 iss := tx.Entries[*mux.Sources[0].Ref].(*bc.Issuance)
290                                 iss.WitnessDestination.Value.Amount = math.MaxInt64
291                         },
292                         err: errOverflow,
293                 },
294                 {
295                         desc: "underflowing mux destination amounts",
296                         f: func() {
297                                 mux.WitnessDestinations[0].Value.Amount = math.MaxInt64
298                                 out := tx.Entries[*mux.WitnessDestinations[0].Ref].(*bc.Output)
299                                 out.Source.Value.Amount = math.MaxInt64
300                                 mux.WitnessDestinations[1].Value.Amount = math.MaxInt64
301                                 out = tx.Entries[*mux.WitnessDestinations[1].Ref].(*bc.Output)
302                                 out.Source.Value.Amount = math.MaxInt64
303                         },
304                         err: errOverflow,
305                 },
306                 {
307                         desc: "unbalanced mux assets",
308                         f: func() {
309                                 mux.Sources[1].Value.AssetId = newAssetID(255)
310                                 sp := tx.Entries[*mux.Sources[1].Ref].(*bc.Spend)
311                                 sp.WitnessDestination.Value.AssetId = newAssetID(255)
312                         },
313                         err: errUnbalanced,
314                 },
315                 {
316                         desc: "mismatched output source / mux dest position",
317                         f: func() {
318                                 tx.Entries[*tx.ResultIds[0]].(*bc.Output).Source.Position = 1
319                         },
320                         err: errMismatchedPosition,
321                 },
322                 {
323                         desc: "mismatched output source and mux dest",
324                         f: func() {
325                                 // For this test, it's necessary to construct a mostly
326                                 // identical second transaction in order to get a similar but
327                                 // not equal output entry for the mux to falsely point
328                                 // to. That entry must be added to the first tx's Entries map.
329                                 fixture2 := sample(t, fixture)
330                                 tx2 := types.NewTx(*fixture2.tx).Tx
331                                 out2ID := tx2.ResultIds[0]
332                                 out2 := tx2.Entries[*out2ID].(*bc.Output)
333                                 tx.Entries[*out2ID] = out2
334                                 mux.WitnessDestinations[0].Ref = out2ID
335                         },
336                         err: errMismatchedReference,
337                 },
338                 {
339                         desc: "invalid mux destination position",
340                         f: func() {
341                                 mux.WitnessDestinations[0].Position = 1
342                         },
343                         err: errPosition,
344                 },
345                 {
346                         desc: "mismatched mux dest value / output source value",
347                         f: func() {
348                                 outID := tx.ResultIds[0]
349                                 out := tx.Entries[*outID].(*bc.Output)
350                                 mux.WitnessDestinations[0].Value = &bc.AssetAmount{
351                                         AssetId: out.Source.Value.AssetId,
352                                         Amount:  out.Source.Value.Amount + 1,
353                                 }
354                                 mux.Sources[0].Value.Amount++ // the mux must still balance
355                         },
356                         err: errMismatchedValue,
357                 },
358                 {
359                         desc: "empty tx results",
360                         f: func() {
361                                 tx.ResultIds = nil
362                         },
363                         err: errEmptyResults,
364                 },
365                 {
366                         desc: "empty tx results, but that's OK",
367                         f: func() {
368                                 tx.Version = 2
369                                 tx.ResultIds = nil
370                         },
371                 },
372                 {
373                         desc: "issuance program failure",
374                         f: func() {
375                                 iss := txIssuance(t, tx, 0)
376                                 iss.WitnessArguments[0] = []byte{}
377                         },
378                         err: vm.ErrFalseVMResult,
379                 },
380                 {
381                         desc: "spend control program failure",
382                         f: func() {
383                                 spend := txSpend(t, tx, 1)
384                                 spend.WitnessArguments[0] = []byte{}
385                         },
386                         err: vm.ErrFalseVMResult,
387                 },
388                 {
389                         desc: "mismatched spent source/witness value",
390                         f: func() {
391                                 spend := txSpend(t, tx, 1)
392                                 spentOutput := tx.Entries[*spend.SpentOutputId].(*bc.Output)
393                                 spentOutput.Source.Value = &bc.AssetAmount{
394                                         AssetId: spend.WitnessDestination.Value.AssetId,
395                                         Amount:  spend.WitnessDestination.Value.Amount + 1,
396                                 }
397                         },
398                         err: errMismatchedValue,
399                 },
400                 {
401                         desc: "gas out of limit",
402                         f: func() {
403                                 vs.tx.SerializedSize = 10000000
404                         },
405                         err: errOverGasCredit,
406                 },
407                 {
408                         desc: "can't find gas spend input in entries",
409                         f: func() {
410                                 spendID := mux.Sources[len(mux.Sources)-1].Ref
411                                 delete(tx.Entries, *spendID)
412                                 mux.Sources = mux.Sources[:len(mux.Sources)-1]
413                         },
414                         err: bc.ErrMissingEntry,
415                 },
416                 {
417                         desc: "no gas spend input",
418                         f: func() {
419                                 spendID := mux.Sources[len(mux.Sources)-1].Ref
420                                 delete(tx.Entries, *spendID)
421                                 mux.Sources = mux.Sources[:len(mux.Sources)-1]
422                                 tx.GasInputIDs = nil
423                                 vs.gasStatus.GasLeft = 0
424                         },
425                         err: vm.ErrRunLimitExceeded,
426                 },
427                 {
428                         desc: "no gas spend input, but set gas left, so it's ok",
429                         f: func() {
430                                 spendID := mux.Sources[len(mux.Sources)-1].Ref
431                                 delete(tx.Entries, *spendID)
432                                 mux.Sources = mux.Sources[:len(mux.Sources)-1]
433                                 tx.GasInputIDs = nil
434                         },
435                         err: nil,
436                 },
437                 {
438                         desc: "mismatched gas spend input destination amount/prevout source amount",
439                         f: func() {
440                                 spendID := mux.Sources[len(mux.Sources)-1].Ref
441                                 spend := tx.Entries[*spendID].(*bc.Spend)
442                                 spend.WitnessDestination.Value = &bc.AssetAmount{
443                                         AssetId: spend.WitnessDestination.Value.AssetId,
444                                         Amount:  spend.WitnessDestination.Value.Amount + 1,
445                                 }
446                         },
447                         err: errMismatchedValue,
448                 },
449                 {
450                         desc: "mismatched witness asset destination",
451                         f: func() {
452                                 issuanceID := mux.Sources[0].Ref
453                                 issuance := tx.Entries[*issuanceID].(*bc.Issuance)
454                                 issuance.WitnessAssetDefinition.Data = &bc.Hash{V0: 9999}
455                         },
456                         err: errMismatchedAssetID,
457                 },
458                 {
459                         desc: "issuance witness position greater than length of mux sources",
460                         f: func() {
461                                 issuanceID := mux.Sources[0].Ref
462                                 issuance := tx.Entries[*issuanceID].(*bc.Issuance)
463                                 issuance.WitnessDestination.Position = uint64(len(mux.Sources) + 1)
464                         },
465                         err: errPosition,
466                 },
467                 {
468                         desc: "normal coinbase tx",
469                         f: func() {
470                                 addCoinbase(consensus.BTMAssetID, 100000, nil)
471                         },
472                         err: nil,
473                 },
474                 {
475                         desc: "invalid coinbase tx asset id",
476                         f: func() {
477                                 addCoinbase(&bc.AssetID{V1: 100}, 100000, nil)
478                         },
479                         err: errWrongCoinbaseAsset,
480                 },
481                 {
482                         desc: "coinbase tx is not first tx in block",
483                         f: func() {
484                                 addCoinbase(consensus.BTMAssetID, 100000, nil)
485                                 vs.block.Transactions[0] = nil
486                         },
487                         err: errWrongCoinbaseTransaction,
488                 },
489                 {
490                         desc: "coinbase arbitrary size out of limit",
491                         f: func() {
492                                 arbitrary := make([]byte, consensus.CoinbaseArbitrarySizeLimit+1)
493                                 addCoinbase(consensus.BTMAssetID, 100000, arbitrary)
494                         },
495                         err: errCoinbaseArbitraryOversize,
496                 },
497                 {
498                         desc: "normal retirement output",
499                         f: func() {
500                                 outputID := tx.ResultIds[0]
501                                 output := tx.Entries[*outputID].(*bc.Output)
502                                 retirement := bc.NewRetirement(output.Source, output.Ordinal)
503                                 retirementID := bc.EntryID(retirement)
504                                 tx.Entries[retirementID] = retirement
505                                 delete(tx.Entries, *outputID)
506                                 tx.ResultIds[0] = &retirementID
507                                 mux.WitnessDestinations[0].Ref = &retirementID
508                         },
509                         err: nil,
510                 },
511                 {
512                         desc: "ordinal doesn't matter for prevouts",
513                         f: func() {
514                                 spend := txSpend(t, tx, 1)
515                                 prevout := tx.Entries[*spend.SpentOutputId].(*bc.Output)
516                                 newPrevout := bc.NewOutput(prevout.Source, prevout.ControlProgram, 10)
517                                 hash := bc.EntryID(newPrevout)
518                                 spend.SpentOutputId = &hash
519                         },
520                         err: nil,
521                 },
522                 {
523                         desc: "mux witness destination have no source",
524                         f: func() {
525                                 dest := &bc.ValueDestination{
526                                         Value: &bc.AssetAmount{
527                                                 AssetId: &bc.AssetID{V2: 1000},
528                                                 Amount:  100,
529                                         },
530                                         Ref:      mux.WitnessDestinations[0].Ref,
531                                         Position: 0,
532                                 }
533                                 mux.WitnessDestinations = append(mux.WitnessDestinations, dest)
534                         },
535                         err: errNoSource,
536                 },
537         }
538
539         for i, c := range cases {
540                 t.Run(c.desc, func(t *testing.T) {
541                         fixture = sample(t, nil)
542                         tx = types.NewTx(*fixture.tx).Tx
543                         vs = &validationState{
544                                 block:   mockBlock(),
545                                 tx:      tx,
546                                 entryID: tx.ID,
547                                 gasStatus: &GasState{
548                                         GasLeft: int64(80000),
549                                         GasUsed: 0,
550                                 },
551                                 cache: make(map[bc.Hash]error),
552                         }
553                         muxID := getMuxID(tx)
554                         mux = tx.Entries[*muxID].(*bc.Mux)
555
556                         if c.f != nil {
557                                 c.f()
558                         }
559                         err := checkValid(vs, tx.TxHeader)
560
561                         if rootErr(err) != c.err {
562                                 t.Errorf("case #%d (%s) got error %s, want %s; validationState is:\n%s", i, c.desc, err, c.err, spew.Sdump(vs))
563                         }
564                 })
565         }
566 }
567
568 func TestCoinbase(t *testing.T) {
569         CbTx := mockCoinbaseTx(5000000000)
570         cases := []struct {
571                 block    *bc.Block
572                 tx       *bc.Tx
573                 GasValid bool
574                 err      error
575         }{
576                 {
577                         block: &bc.Block{
578                                 BlockHeader: &bc.BlockHeader{
579                                         Height: 666,
580                                 },
581                                 Transactions: []*bc.Tx{CbTx},
582                         },
583                         tx:       CbTx,
584                         GasValid: true,
585                         err:      nil,
586                 },
587         }
588
589         for i, c := range cases {
590                 gasStatus, err := ValidateTx(c.tx, c.block)
591
592                 if rootErr(err) != c.err {
593                         t.Errorf("#%d got error %s, want %s", i, err, c.err)
594                 }
595                 if c.GasValid != gasStatus.GasValid {
596                         t.Errorf("#%d got GasValid %t, want %t", i, gasStatus.GasValid, c.GasValid)
597                 }
598         }
599 }
600
601 func TestTimeRange(t *testing.T) {
602         cases := []struct {
603                 timeRange uint64
604                 err       bool
605         }{
606                 {
607                         timeRange: 0,
608                         err:       false,
609                 },
610                 {
611                         timeRange: 334,
612                         err:       false,
613                 },
614                 {
615                         timeRange: 332,
616                         err:       true,
617                 },
618                 {
619                         timeRange: 1521625824,
620                         err:       false,
621                 },
622         }
623
624         block := &bc.Block{
625                 BlockHeader: &bc.BlockHeader{
626                         Height:    333,
627                         Timestamp: 1521625823,
628                 },
629         }
630
631         tx := types.MapTx(&types.TxData{
632                 SerializedSize: 1,
633                 TimeRange:      0,
634                 Inputs: []*types.TxInput{
635                         mockGasTxInput(),
636                 },
637                 Outputs: []*types.TxOutput{
638                         types.NewTxOutput(*consensus.BTMAssetID, 1, []byte{0x6a}),
639                 },
640         })
641
642         for i, c := range cases {
643                 tx.TimeRange = c.timeRange
644                 if _, err := ValidateTx(tx, block); (err != nil) != c.err {
645                         t.Errorf("#%d got error %t, want %t", i, !c.err, c.err)
646                 }
647         }
648 }
649
650 // A txFixture is returned by sample (below) to produce a sample
651 // transaction, which takes a separate, optional _input_ txFixture to
652 // affect the transaction that's built. The components of the
653 // transaction are the fields of txFixture.
654 type txFixture struct {
655         initialBlockID bc.Hash
656         issuanceProg   bc.Program
657         issuanceArgs   [][]byte
658         assetDef       []byte
659         assetID        bc.AssetID
660         txVersion      uint64
661         txInputs       []*types.TxInput
662         txOutputs      []*types.TxOutput
663         tx             *types.TxData
664 }
665
666 // Produces a sample transaction in a txFixture object (see above). A
667 // separate input txFixture can be used to alter the transaction
668 // that's created.
669 //
670 // The output of this function can be used as the input to a
671 // subsequent call to make iterative refinements to a test object.
672 //
673 // The default transaction produced is valid and has three inputs:
674 //  - an issuance of 10 units
675 //  - a spend of 20 units
676 //  - a spend of 40 units
677 // and two outputs, one of 25 units and one of 45 units.
678 // All amounts are denominated in the same asset.
679 //
680 // The issuance program for the asset requires two numbers as
681 // arguments that add up to 5. The prevout control programs require
682 // two numbers each, adding to 9 and 13, respectively.
683 //
684 // The min and max times for the transaction are now +/- one minute.
685 func sample(tb testing.TB, in *txFixture) *txFixture {
686         var result txFixture
687         if in != nil {
688                 result = *in
689         }
690
691         if result.initialBlockID.IsZero() {
692                 result.initialBlockID = *newHash(1)
693         }
694         if testutil.DeepEqual(result.issuanceProg, bc.Program{}) {
695                 prog, err := vm.Assemble("ADD 5 NUMEQUAL")
696                 if err != nil {
697                         tb.Fatal(err)
698                 }
699                 result.issuanceProg = bc.Program{VmVersion: 1, Code: prog}
700         }
701         if len(result.issuanceArgs) == 0 {
702                 result.issuanceArgs = [][]byte{[]byte{2}, []byte{3}}
703         }
704         if len(result.assetDef) == 0 {
705                 result.assetDef = []byte{2}
706         }
707         if result.assetID.IsZero() {
708                 refdatahash := hashData(result.assetDef)
709                 result.assetID = bc.ComputeAssetID(result.issuanceProg.Code, result.issuanceProg.VmVersion, &refdatahash)
710         }
711
712         if result.txVersion == 0 {
713                 result.txVersion = 1
714         }
715         if len(result.txInputs) == 0 {
716                 cp1, err := vm.Assemble("ADD 9 NUMEQUAL")
717                 if err != nil {
718                         tb.Fatal(err)
719                 }
720                 args1 := [][]byte{[]byte{4}, []byte{5}}
721
722                 cp2, err := vm.Assemble("ADD 13 NUMEQUAL")
723                 if err != nil {
724                         tb.Fatal(err)
725                 }
726                 args2 := [][]byte{[]byte{6}, []byte{7}}
727
728                 result.txInputs = []*types.TxInput{
729                         types.NewIssuanceInput([]byte{3}, 10, result.issuanceProg.Code, result.issuanceArgs, result.assetDef),
730                         types.NewSpendInput(args1, *newHash(5), result.assetID, 20, 0, cp1),
731                         types.NewSpendInput(args2, *newHash(8), result.assetID, 40, 0, cp2),
732                 }
733         }
734
735         result.txInputs = append(result.txInputs, mockGasTxInput())
736
737         if len(result.txOutputs) == 0 {
738                 cp1, err := vm.Assemble("ADD 17 NUMEQUAL")
739                 if err != nil {
740                         tb.Fatal(err)
741                 }
742                 cp2, err := vm.Assemble("ADD 21 NUMEQUAL")
743                 if err != nil {
744                         tb.Fatal(err)
745                 }
746
747                 result.txOutputs = []*types.TxOutput{
748                         types.NewTxOutput(result.assetID, 25, cp1),
749                         types.NewTxOutput(result.assetID, 45, cp2),
750                 }
751         }
752
753         result.tx = &types.TxData{
754                 Version: result.txVersion,
755                 Inputs:  result.txInputs,
756                 Outputs: result.txOutputs,
757         }
758
759         return &result
760 }
761
762 func mockBlock() *bc.Block {
763         return &bc.Block{
764                 BlockHeader: &bc.BlockHeader{
765                         Height: 666,
766                 },
767         }
768 }
769
770 func mockCoinbaseTx(amount uint64) *bc.Tx {
771         cp, _ := vmutil.DefaultCoinbaseProgram()
772         return types.MapTx(&types.TxData{
773                 SerializedSize: 1,
774                 Inputs: []*types.TxInput{
775                         types.NewCoinbaseInput(nil),
776                 },
777                 Outputs: []*types.TxOutput{
778                         types.NewTxOutput(*consensus.BTMAssetID, amount, cp),
779                 },
780         })
781 }
782
783 func mockGasTxInput() *types.TxInput {
784         cp, _ := vmutil.DefaultCoinbaseProgram()
785         return types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp)
786 }
787
788 // Like errors.Root, but also unwraps vm.Error objects.
789 func rootErr(e error) error {
790         for {
791                 e = errors.Root(e)
792                 if e2, ok := e.(vm.Error); ok {
793                         e = e2.Err
794                         continue
795                 }
796                 return e
797         }
798 }
799
800 func hashData(data []byte) bc.Hash {
801         var b32 [32]byte
802         sha3pool.Sum256(b32[:], data)
803         return bc.NewHash(b32)
804 }
805
806 func newHash(n byte) *bc.Hash {
807         h := bc.NewHash([32]byte{n})
808         return &h
809 }
810
811 func newAssetID(n byte) *bc.AssetID {
812         a := bc.NewAssetID([32]byte{n})
813         return &a
814 }
815
816 func txIssuance(t *testing.T, tx *bc.Tx, index int) *bc.Issuance {
817         id := tx.InputIDs[index]
818         res, err := tx.Issuance(id)
819         if err != nil {
820                 t.Fatal(err)
821         }
822         return res
823 }
824
825 func txSpend(t *testing.T, tx *bc.Tx, index int) *bc.Spend {
826         id := tx.InputIDs[index]
827         res, err := tx.Spend(id)
828         if err != nil {
829                 t.Fatal(err)
830         }
831         return res
832 }
833
834 func getMuxID(tx *bc.Tx) *bc.Hash {
835         out := tx.Entries[*tx.ResultIds[0]]
836         switch result := out.(type) {
837         case *bc.Output:
838                 return result.Source.Ref
839         case *bc.Retirement:
840                 return result.Source.Ref
841         }
842         return nil
843 }