OSDN Git Service

Mov (#518)
[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/bytom/vapor/consensus"
10         "github.com/bytom/vapor/crypto/ed25519/chainkd"
11         "github.com/bytom/vapor/crypto/sha3pool"
12         "github.com/bytom/vapor/errors"
13         "github.com/bytom/vapor/protocol/bc"
14         "github.com/bytom/vapor/protocol/bc/types"
15         "github.com/bytom/vapor/protocol/vm"
16         "github.com/bytom/vapor/protocol/vm/vmutil"
17         "github.com/bytom/vapor/testutil"
18 )
19
20 func init() {
21         spew.Config.DisableMethods = true
22 }
23
24 func TestGasStatus(t *testing.T) {
25         cases := []struct {
26                 input  *GasState
27                 output *GasState
28                 f      func(*GasState) error
29                 err    error
30         }{
31                 {
32                         input: &GasState{
33                                 GasLeft:  10000,
34                                 GasUsed:  0,
35                                 BTMValue: 0,
36                         },
37                         output: &GasState{
38                                 GasLeft:  10000/consensus.ActiveNetParams.VMGasRate + consensus.ActiveNetParams.DefaultGasCredit,
39                                 GasUsed:  0,
40                                 BTMValue: 10000,
41                         },
42                         f: func(input *GasState) error {
43                                 return input.setGas(10000, 0)
44                         },
45                         err: nil,
46                 },
47                 {
48                         input: &GasState{
49                                 GasLeft:  10000,
50                                 GasUsed:  0,
51                                 BTMValue: 0,
52                         },
53                         output: &GasState{
54                                 GasLeft:  10000,
55                                 GasUsed:  0,
56                                 BTMValue: 0,
57                         },
58                         f: func(input *GasState) error {
59                                 return input.setGas(-10000, 0)
60                         },
61                         err: ErrGasCalculate,
62                 },
63                 {
64                         input: &GasState{
65                                 GasLeft:  consensus.ActiveNetParams.DefaultGasCredit,
66                                 GasUsed:  0,
67                                 BTMValue: 0,
68                         },
69                         output: &GasState{
70                                 GasLeft:  640000,
71                                 GasUsed:  0,
72                                 BTMValue: 80000000000,
73                         },
74                         f: func(input *GasState) error {
75                                 return input.setGas(80000000000, 0)
76                         },
77                         err: nil,
78                 },
79                 {
80                         input: &GasState{
81                                 GasLeft:  consensus.ActiveNetParams.DefaultGasCredit,
82                                 GasUsed:  0,
83                                 BTMValue: 0,
84                         },
85                         output: &GasState{
86                                 GasLeft:  640000,
87                                 GasUsed:  0,
88                                 BTMValue: math.MaxInt64,
89                         },
90                         f: func(input *GasState) error {
91                                 return input.setGas(math.MaxInt64, 0)
92                         },
93                         err: nil,
94                 },
95                 {
96                         input: &GasState{
97                                 GasLeft:  10000,
98                                 GasUsed:  0,
99                                 BTMValue: 0,
100                         },
101                         output: &GasState{
102                                 GasLeft:  10000,
103                                 GasUsed:  0,
104                                 BTMValue: 0,
105                         },
106                         f: func(input *GasState) error {
107                                 return input.updateUsage(-1)
108                         },
109                         err: ErrGasCalculate,
110                 },
111                 {
112                         input: &GasState{
113                                 GasLeft:  10000,
114                                 GasUsed:  0,
115                                 BTMValue: 0,
116                         },
117                         output: &GasState{
118                                 GasLeft:  9999,
119                                 GasUsed:  1,
120                                 BTMValue: 0,
121                         },
122                         f: func(input *GasState) error {
123                                 return input.updateUsage(9999)
124                         },
125                         err: nil,
126                 },
127                 {
128                         input: &GasState{
129                                 GasLeft:  -10000,
130                                 GasUsed:  0,
131                                 BTMValue: 0,
132                         },
133                         output: &GasState{
134                                 GasLeft:  -10000,
135                                 GasUsed:  0,
136                                 BTMValue: 0,
137                         },
138                         f: func(input *GasState) error {
139                                 return input.updateUsage(math.MaxInt64)
140                         },
141                         err: ErrGasCalculate,
142                 },
143                 {
144                         input: &GasState{
145                                 GasLeft:    1000,
146                                 GasUsed:    10,
147                                 StorageGas: 1000,
148                                 GasValid:   false,
149                         },
150                         output: &GasState{
151                                 GasLeft:    0,
152                                 GasUsed:    1010,
153                                 StorageGas: 1000,
154                                 GasValid:   true,
155                         },
156                         f: func(input *GasState) error {
157                                 return input.setGasValid()
158                         },
159                         err: nil,
160                 },
161                 {
162                         input: &GasState{
163                                 GasLeft:    900,
164                                 GasUsed:    10,
165                                 StorageGas: 1000,
166                                 GasValid:   false,
167                         },
168                         output: &GasState{
169                                 GasLeft:    -100,
170                                 GasUsed:    10,
171                                 StorageGas: 1000,
172                                 GasValid:   false,
173                         },
174                         f: func(input *GasState) error {
175                                 return input.setGasValid()
176                         },
177                         err: ErrGasCalculate,
178                 },
179                 {
180                         input: &GasState{
181                                 GasLeft:    1000,
182                                 GasUsed:    math.MaxInt64,
183                                 StorageGas: 1000,
184                                 GasValid:   false,
185                         },
186                         output: &GasState{
187                                 GasLeft:    0,
188                                 GasUsed:    0,
189                                 StorageGas: 1000,
190                                 GasValid:   false,
191                         },
192                         f: func(input *GasState) error {
193                                 return input.setGasValid()
194                         },
195                         err: ErrGasCalculate,
196                 },
197                 {
198                         input: &GasState{
199                                 GasLeft:    math.MinInt64,
200                                 GasUsed:    0,
201                                 StorageGas: 1000,
202                                 GasValid:   false,
203                         },
204                         output: &GasState{
205                                 GasLeft:    0,
206                                 GasUsed:    0,
207                                 StorageGas: 1000,
208                                 GasValid:   false,
209                         },
210                         f: func(input *GasState) error {
211                                 return input.setGasValid()
212                         },
213                         err: ErrGasCalculate,
214                 },
215         }
216
217         for i, c := range cases {
218                 err := c.f(c.input)
219
220                 if rootErr(err) != c.err {
221                         t.Errorf("case %d: got error %s, want %s", i, err, c.err)
222                 } else if *c.input != *c.output {
223                         t.Errorf("case %d: gasStatus %v, want %v;", i, c.input, c.output)
224                 }
225         }
226 }
227
228 func TestOverflow(t *testing.T) {
229         sourceID := &bc.Hash{V0: 9999}
230         ctrlProgram := []byte{byte(vm.OP_TRUE)}
231         newTx := func(inputs []uint64, outputs []uint64) *bc.Tx {
232                 txInputs := make([]*types.TxInput, 0, len(inputs))
233                 txOutputs := make([]*types.TxOutput, 0, len(outputs))
234
235                 for i, amount := range inputs {
236                         txInput := types.NewSpendInput(nil, *sourceID, *consensus.BTMAssetID, amount, uint64(i), ctrlProgram)
237                         txInputs = append(txInputs, txInput)
238                 }
239
240                 for _, amount := range outputs {
241                         txOutput := types.NewIntraChainOutput(*consensus.BTMAssetID, amount, ctrlProgram)
242                         txOutputs = append(txOutputs, txOutput)
243                 }
244
245                 txData := &types.TxData{
246                         Version:        1,
247                         SerializedSize: 100,
248                         TimeRange:      0,
249                         Inputs:         txInputs,
250                         Outputs:        txOutputs,
251                 }
252                 return types.MapTx(txData)
253         }
254
255         cases := []struct {
256                 inputs  []uint64
257                 outputs []uint64
258                 err     error
259         }{
260                 {
261                         inputs:  []uint64{math.MaxUint64, 1},
262                         outputs: []uint64{0},
263                         err:     ErrOverflow,
264                 },
265                 {
266                         inputs:  []uint64{math.MaxUint64, math.MaxUint64},
267                         outputs: []uint64{0},
268                         err:     ErrOverflow,
269                 },
270                 {
271                         inputs:  []uint64{math.MaxUint64, math.MaxUint64 - 1},
272                         outputs: []uint64{0},
273                         err:     ErrOverflow,
274                 },
275                 {
276                         inputs:  []uint64{math.MaxInt64, 1},
277                         outputs: []uint64{0},
278                         err:     ErrOverflow,
279                 },
280                 {
281                         inputs:  []uint64{math.MaxInt64, math.MaxInt64},
282                         outputs: []uint64{0},
283                         err:     ErrOverflow,
284                 },
285                 {
286                         inputs:  []uint64{math.MaxInt64, math.MaxInt64 - 1},
287                         outputs: []uint64{0},
288                         err:     ErrOverflow,
289                 },
290                 {
291                         inputs:  []uint64{0},
292                         outputs: []uint64{math.MaxUint64},
293                         err:     ErrOverflow,
294                 },
295                 {
296                         inputs:  []uint64{0},
297                         outputs: []uint64{math.MaxInt64},
298                         err:     ErrGasCalculate,
299                 },
300                 {
301                         inputs:  []uint64{math.MaxInt64 - 1},
302                         outputs: []uint64{math.MaxInt64},
303                         err:     ErrGasCalculate,
304                 },
305         }
306
307         for i, c := range cases {
308                 tx := newTx(c.inputs, c.outputs)
309                 if _, err := ValidateTx(tx, mockBlock()); rootErr(err) != c.err {
310                         t.Fatalf("case %d test failed, want %s, have %s", i, c.err, rootErr(err))
311                 }
312         }
313 }
314
315 func TestTxValidation(t *testing.T) {
316         var (
317                 tx      *bc.Tx
318                 vs      *validationState
319                 fixture *txFixture
320
321                 // the mux from tx, pulled out for convenience
322                 mux *bc.Mux
323         )
324
325         addCoinbase := func(assetID *bc.AssetID, amount uint64, arbitrary []byte) {
326                 coinbase := bc.NewCoinbase(arbitrary)
327                 txOutput := types.NewIntraChainOutput(*assetID, amount, []byte{byte(vm.OP_TRUE)})
328                 assetAmount := txOutput.AssetAmount()
329                 muxID := getMuxID(tx)
330                 coinbase.SetDestination(muxID, &assetAmount, uint64(len(mux.Sources)))
331                 coinbaseID := bc.EntryID(coinbase)
332                 tx.Entries[coinbaseID] = coinbase
333
334                 mux.Sources = append(mux.Sources, &bc.ValueSource{
335                         Ref:   &coinbaseID,
336                         Value: &assetAmount,
337                 })
338
339                 src := &bc.ValueSource{
340                         Ref:      muxID,
341                         Value:    &assetAmount,
342                         Position: uint64(len(tx.ResultIds)),
343                 }
344                 prog := &bc.Program{txOutput.VMVersion(), txOutput.ControlProgram()}
345                 output := bc.NewIntraChainOutput(src, prog, uint64(len(tx.ResultIds)))
346                 outputID := bc.EntryID(output)
347                 tx.Entries[outputID] = output
348
349                 dest := &bc.ValueDestination{
350                         Value:    src.Value,
351                         Ref:      &outputID,
352                         Position: 0,
353                 }
354                 mux.WitnessDestinations = append(mux.WitnessDestinations, dest)
355                 tx.ResultIds = append(tx.ResultIds, &outputID)
356                 vs.block.Transactions = append(vs.block.Transactions, vs.tx)
357         }
358
359         cases := []struct {
360                 desc string // description of the test case
361                 f    func() // function to adjust tx, vs, and/or mux
362                 err  error  // expected error
363         }{
364                 {
365                         desc: "base case",
366                 },
367                 {
368                         desc: "unbalanced mux amounts",
369                         f: func() {
370                                 mux.WitnessDestinations[0].Value.Amount++
371                         },
372                         err: ErrUnbalanced,
373                 },
374                 {
375                         desc: "balanced mux amounts",
376                         f: func() {
377                                 mux.Sources[1].Value.Amount++
378                                 mux.WitnessDestinations[0].Value.Amount++
379                         },
380                         err: nil,
381                 },
382                 {
383                         desc: "underflowing mux destination amounts",
384                         f: func() {
385                                 mux.WitnessDestinations[0].Value.Amount = math.MaxInt64
386                                 out := tx.Entries[*mux.WitnessDestinations[0].Ref].(*bc.IntraChainOutput)
387                                 out.Source.Value.Amount = math.MaxInt64
388                                 mux.WitnessDestinations[1].Value.Amount = math.MaxInt64
389                                 out = tx.Entries[*mux.WitnessDestinations[1].Ref].(*bc.IntraChainOutput)
390                                 out.Source.Value.Amount = math.MaxInt64
391                         },
392                         err: ErrOverflow,
393                 },
394                 {
395                         desc: "unbalanced mux assets",
396                         f: func() {
397                                 mux.Sources[1].Value.AssetId = newAssetID(255)
398                                 sp := tx.Entries[*mux.Sources[1].Ref].(*bc.Spend)
399                                 sp.WitnessDestination.Value.AssetId = newAssetID(255)
400                         },
401                         err: ErrUnbalanced,
402                 },
403                 {
404                         desc: "mismatched output source / mux dest position",
405                         f: func() {
406                                 tx.Entries[*tx.ResultIds[0]].(*bc.IntraChainOutput).Source.Position = 1
407                         },
408                         err: ErrMismatchedPosition,
409                 },
410                 {
411                         desc: "mismatched input dest / mux source position",
412                         f: func() {
413                                 mux.Sources[0].Position = 1
414                         },
415                         err: ErrMismatchedPosition,
416                 },
417                 {
418                         desc: "mismatched output source and mux dest",
419                         f: func() {
420                                 // For this test, it's necessary to construct a mostly
421                                 // identical second transaction in order to get a similar but
422                                 // not equal output entry for the mux to falsely point
423                                 // to. That entry must be added to the first tx's Entries map.
424                                 fixture2 := sample(t, fixture)
425                                 tx2 := types.NewTx(*fixture2.tx).Tx
426                                 out2ID := tx2.ResultIds[0]
427                                 out2 := tx2.Entries[*out2ID].(*bc.IntraChainOutput)
428                                 tx.Entries[*out2ID] = out2
429                                 mux.WitnessDestinations[0].Ref = out2ID
430                         },
431                         err: ErrMismatchedReference,
432                 },
433                 {
434                         desc: "invalid mux destination position",
435                         f: func() {
436                                 mux.WitnessDestinations[0].Position = 1
437                         },
438                         err: ErrPosition,
439                 },
440                 {
441                         desc: "mismatched mux dest value / output source value",
442                         f: func() {
443                                 outID := tx.ResultIds[0]
444                                 out := tx.Entries[*outID].(*bc.IntraChainOutput)
445                                 mux.WitnessDestinations[0].Value = &bc.AssetAmount{
446                                         AssetId: out.Source.Value.AssetId,
447                                         Amount:  out.Source.Value.Amount + 1,
448                                 }
449                                 mux.Sources[0].Value.Amount++ // the mux must still balance
450                         },
451                         err: ErrMismatchedValue,
452                 },
453                 {
454                         desc: "empty tx results",
455                         f: func() {
456                                 tx.ResultIds = nil
457                         },
458                         err: ErrEmptyResults,
459                 },
460                 {
461                         desc: "empty tx results, but that's OK",
462                         f: func() {
463                                 tx.Version = 2
464                                 tx.ResultIds = nil
465                         },
466                 },
467                 {
468                         desc: "spend control program failure",
469                         f: func() {
470                                 spend := txSpend(t, tx, 1)
471                                 spend.WitnessArguments[0] = []byte{}
472                         },
473                         err: vm.ErrFalseVMResult,
474                 },
475                 {
476                         desc: "mismatched spent source/witness value",
477                         f: func() {
478                                 spend := txSpend(t, tx, 1)
479                                 spentOutput := tx.Entries[*spend.SpentOutputId].(*bc.IntraChainOutput)
480                                 spentOutput.Source.Value = &bc.AssetAmount{
481                                         AssetId: spend.WitnessDestination.Value.AssetId,
482                                         Amount:  spend.WitnessDestination.Value.Amount + 1,
483                                 }
484                         },
485                         err: ErrMismatchedValue,
486                 },
487                 {
488                         desc: "gas out of limit",
489                         f: func() {
490                                 vs.tx.SerializedSize = 10000000
491                         },
492                         err: ErrOverGasCredit,
493                 },
494                 {
495                         desc: "can't find gas spend input in entries",
496                         f: func() {
497                                 spendID := mux.Sources[len(mux.Sources)-1].Ref
498                                 delete(tx.Entries, *spendID)
499                                 mux.Sources = mux.Sources[:len(mux.Sources)-1]
500                         },
501                         err: bc.ErrMissingEntry,
502                 },
503                 {
504                         desc: "normal check with no gas spend input",
505                         f: func() {
506                                 spendID := mux.Sources[len(mux.Sources)-1].Ref
507                                 delete(tx.Entries, *spendID)
508                                 mux.Sources = mux.Sources[:len(mux.Sources)-1]
509                                 tx.GasInputIDs = nil
510                                 vs.gasStatus.GasLeft = 0
511                         },
512                         err: nil,
513                 },
514                 {
515                         desc: "no gas spend input, but set gas left, so it's ok",
516                         f: func() {
517                                 spendID := mux.Sources[len(mux.Sources)-1].Ref
518                                 delete(tx.Entries, *spendID)
519                                 mux.Sources = mux.Sources[:len(mux.Sources)-1]
520                                 tx.GasInputIDs = nil
521                         },
522                         err: nil,
523                 },
524                 {
525                         desc: "mismatched gas spend input destination amount/prevout source amount",
526                         f: func() {
527                                 spendID := mux.Sources[len(mux.Sources)-1].Ref
528                                 spend := tx.Entries[*spendID].(*bc.Spend)
529                                 spend.WitnessDestination.Value = &bc.AssetAmount{
530                                         AssetId: spend.WitnessDestination.Value.AssetId,
531                                         Amount:  spend.WitnessDestination.Value.Amount + 1,
532                                 }
533                         },
534                         err: ErrMismatchedValue,
535                 },
536                 {
537                         desc: "normal coinbase tx",
538                         f: func() {
539                                 addCoinbase(consensus.BTMAssetID, 100000, nil)
540                         },
541                         err: nil,
542                 },
543                 {
544                         desc: "invalid coinbase tx asset id",
545                         f: func() {
546                                 addCoinbase(&bc.AssetID{V1: 100}, 100000, nil)
547                         },
548                         err: ErrWrongCoinbaseAsset,
549                 },
550                 {
551                         desc: "coinbase tx is not first tx in block",
552                         f: func() {
553                                 addCoinbase(consensus.BTMAssetID, 100000, nil)
554                                 vs.block.Transactions[0] = nil
555                         },
556                         err: ErrWrongCoinbaseTransaction,
557                 },
558                 {
559                         desc: "coinbase arbitrary size out of limit",
560                         f: func() {
561                                 arbitrary := make([]byte, consensus.ActiveNetParams.CoinbaseArbitrarySizeLimit+1)
562                                 addCoinbase(consensus.BTMAssetID, 100000, arbitrary)
563                         },
564                         err: ErrCoinbaseArbitraryOversize,
565                 },
566                 {
567                         desc: "normal retirement output",
568                         f: func() {
569                                 outputID := tx.ResultIds[0]
570                                 output := tx.Entries[*outputID].(*bc.IntraChainOutput)
571                                 retirement := bc.NewRetirement(output.Source, output.Ordinal)
572                                 retirementID := bc.EntryID(retirement)
573                                 tx.Entries[retirementID] = retirement
574                                 delete(tx.Entries, *outputID)
575                                 tx.ResultIds[0] = &retirementID
576                                 mux.WitnessDestinations[0].Ref = &retirementID
577                         },
578                         err: nil,
579                 },
580                 {
581                         desc: "ordinal doesn't matter for prevouts",
582                         f: func() {
583                                 spend := txSpend(t, tx, 1)
584                                 prevout := tx.Entries[*spend.SpentOutputId].(*bc.IntraChainOutput)
585                                 newPrevout := bc.NewIntraChainOutput(prevout.Source, prevout.ControlProgram, 10)
586                                 hash := bc.EntryID(newPrevout)
587                                 spend.SpentOutputId = &hash
588                         },
589                         err: nil,
590                 },
591                 {
592                         desc: "mux witness destination have no source",
593                         f: func() {
594                                 dest := &bc.ValueDestination{
595                                         Value: &bc.AssetAmount{
596                                                 AssetId: &bc.AssetID{V2: 1000},
597                                                 Amount:  100,
598                                         },
599                                         Ref:      mux.WitnessDestinations[0].Ref,
600                                         Position: 0,
601                                 }
602                                 mux.WitnessDestinations = append(mux.WitnessDestinations, dest)
603                         },
604                         err: ErrNoSource,
605                 },
606         }
607
608         for i, c := range cases {
609                 t.Run(c.desc, func(t *testing.T) {
610                         fixture = sample(t, nil)
611                         tx = types.NewTx(*fixture.tx).Tx
612                         vs = &validationState{
613                                 block:   mockBlock(),
614                                 tx:      tx,
615                                 entryID: tx.ID,
616                                 gasStatus: &GasState{
617                                         GasLeft: int64(80000),
618                                         GasUsed: 0,
619                                 },
620                                 cache: make(map[bc.Hash]error),
621                         }
622                         muxID := getMuxID(tx)
623                         mux = tx.Entries[*muxID].(*bc.Mux)
624
625                         if c.f != nil {
626                                 c.f()
627                         }
628                         err := checkValid(vs, tx.TxHeader)
629
630                         if rootErr(err) != c.err {
631                                 t.Errorf("case #%d (%s) got error %s, want %s; validationState is:\n%s", i, c.desc, err, c.err, spew.Sdump(vs))
632                         }
633                 })
634         }
635 }
636
637 // TestCoinbase test the coinbase transaction is valid (txtest#1016)
638 func TestCoinbase(t *testing.T) {
639         cp, _ := vmutil.DefaultCoinbaseProgram()
640         retire, _ := vmutil.RetireProgram([]byte{})
641         CbTx := types.MapTx(&types.TxData{
642                 SerializedSize: 1,
643                 Inputs: []*types.TxInput{
644                         types.NewCoinbaseInput(nil),
645                 },
646                 Outputs: []*types.TxOutput{
647                         types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
648                 },
649         })
650
651         cases := []struct {
652                 block    *bc.Block
653                 txIndex  int
654                 GasValid bool
655                 err      error
656         }{
657                 {
658                         block: &bc.Block{
659                                 BlockHeader:  &bc.BlockHeader{Height: 666},
660                                 Transactions: []*bc.Tx{CbTx},
661                         },
662                         txIndex:  0,
663                         GasValid: true,
664                         err:      nil,
665                 },
666                 {
667                         block: &bc.Block{
668                                 BlockHeader: &bc.BlockHeader{Height: 666},
669                                 Transactions: []*bc.Tx{
670                                         CbTx,
671                                         types.MapTx(&types.TxData{
672                                                 SerializedSize: 1,
673                                                 Inputs: []*types.TxInput{
674                                                         types.NewCoinbaseInput(nil),
675                                                 },
676                                                 Outputs: []*types.TxOutput{
677                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
678                                                 },
679                                         }),
680                                 },
681                         },
682                         txIndex:  1,
683                         GasValid: false,
684                         err:      ErrWrongCoinbaseTransaction,
685                 },
686                 {
687                         block: &bc.Block{
688                                 BlockHeader: &bc.BlockHeader{Height: 666},
689                                 Transactions: []*bc.Tx{
690                                         CbTx,
691                                         types.MapTx(&types.TxData{
692                                                 SerializedSize: 1,
693                                                 Inputs: []*types.TxInput{
694                                                         types.NewCoinbaseInput(nil),
695                                                         types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp),
696                                                 },
697                                                 Outputs: []*types.TxOutput{
698                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
699                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 90000000, cp),
700                                                 },
701                                         }),
702                                 },
703                         },
704                         txIndex:  1,
705                         GasValid: false,
706                         err:      ErrWrongCoinbaseTransaction,
707                 },
708                 {
709                         block: &bc.Block{
710                                 BlockHeader: &bc.BlockHeader{Height: 666},
711                                 Transactions: []*bc.Tx{
712                                         CbTx,
713                                         types.MapTx(&types.TxData{
714                                                 SerializedSize: 1,
715                                                 Inputs: []*types.TxInput{
716                                                         types.NewCoinbaseInput(nil),
717                                                         types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp),
718                                                 },
719                                                 Outputs: []*types.TxOutput{
720                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
721                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 90000000, cp),
722                                                 },
723                                         }),
724                                 },
725                         },
726                         txIndex:  1,
727                         GasValid: false,
728                         err:      ErrWrongCoinbaseTransaction,
729                 },
730                 {
731                         block: &bc.Block{
732                                 BlockHeader: &bc.BlockHeader{Height: 666},
733                                 Transactions: []*bc.Tx{
734                                         types.MapTx(&types.TxData{
735                                                 SerializedSize: 1,
736                                                 Inputs: []*types.TxInput{
737                                                         types.NewCoinbaseInput(nil),
738                                                         types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp),
739                                                 },
740                                                 Outputs: []*types.TxOutput{
741                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
742                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 90000000, cp),
743                                                 },
744                                         }),
745                                 },
746                         },
747                         txIndex:  0,
748                         GasValid: true,
749                         err:      nil,
750                 },
751                 {
752                         block: &bc.Block{
753                                 BlockHeader: &bc.BlockHeader{Height: 666},
754                                 Transactions: []*bc.Tx{
755                                         types.MapTx(&types.TxData{
756                                                 SerializedSize: 1,
757                                                 Inputs: []*types.TxInput{
758                                                         types.NewCoinbaseInput(nil),
759                                                         types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, retire),
760                                                 },
761                                                 Outputs: []*types.TxOutput{
762                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
763                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 90000000, cp),
764                                                 },
765                                         }),
766                                 },
767                         },
768                         txIndex:  0,
769                         GasValid: false,
770                         err:      vm.ErrReturn,
771                 },
772         }
773
774         for i, c := range cases {
775                 gasStatus, err := ValidateTx(c.block.Transactions[c.txIndex], c.block)
776
777                 if rootErr(err) != c.err {
778                         t.Errorf("#%d got error %s, want %s", i, err, c.err)
779                 }
780                 if c.GasValid != gasStatus.GasValid {
781                         t.Errorf("#%d got GasValid %t, want %t", i, gasStatus.GasValid, c.GasValid)
782                 }
783         }
784 }
785
786 // TestTimeRange test the checkTimeRange function (txtest#1004)
787 func TestTimeRange(t *testing.T) {
788         cases := []struct {
789                 timeRange uint64
790                 err       bool
791         }{
792                 {
793                         timeRange: 0,
794                         err:       false,
795                 },
796                 {
797                         timeRange: 334,
798                         err:       false,
799                 },
800                 {
801                         timeRange: 332,
802                         err:       true,
803                 },
804                 {
805                         timeRange: 1521625824,
806                         err:       false,
807                 },
808         }
809
810         block := &bc.Block{
811                 BlockHeader: &bc.BlockHeader{
812                         Height:    333,
813                         Timestamp: 1521625823000,
814                 },
815         }
816
817         tx := types.MapTx(&types.TxData{
818                 SerializedSize: 1,
819                 TimeRange:      0,
820                 Inputs: []*types.TxInput{
821                         mockGasTxInput(),
822                 },
823                 Outputs: []*types.TxOutput{
824                         types.NewIntraChainOutput(*consensus.BTMAssetID, 1, []byte{0x6a}),
825                 },
826         })
827
828         for i, c := range cases {
829                 tx.TimeRange = c.timeRange
830                 if _, err := ValidateTx(tx, block); (err != nil) != c.err {
831                         t.Errorf("#%d got error %t, want %t", i, !c.err, c.err)
832                 }
833         }
834 }
835
836 func TestValidateTxVersion(t *testing.T) {
837         cases := []struct {
838                 desc  string
839                 block *bc.Block
840                 err   error
841         }{
842                 {
843                         desc: "tx version greater than 1 (txtest#1001)",
844                         block: &bc.Block{
845                                 BlockHeader: &bc.BlockHeader{Version: 1},
846                                 Transactions: []*bc.Tx{
847                                         {TxHeader: &bc.TxHeader{Version: 2}},
848                                 },
849                         },
850                         err: ErrTxVersion,
851                 },
852                 {
853                         desc: "tx version equals 0 (txtest#1002)",
854                         block: &bc.Block{
855                                 BlockHeader: &bc.BlockHeader{Version: 1},
856                                 Transactions: []*bc.Tx{
857                                         {TxHeader: &bc.TxHeader{Version: 0}},
858                                 },
859                         },
860                         err: ErrTxVersion,
861                 },
862                 {
863                         desc: "tx version equals max uint64 (txtest#1003)",
864                         block: &bc.Block{
865                                 BlockHeader: &bc.BlockHeader{Version: 1},
866                                 Transactions: []*bc.Tx{
867                                         {TxHeader: &bc.TxHeader{Version: math.MaxUint64}},
868                                 },
869                         },
870                         err: ErrTxVersion,
871                 },
872         }
873
874         for i, c := range cases {
875                 if _, err := ValidateTx(c.block.Transactions[0], c.block); rootErr(err) != c.err {
876                         t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, err, c.err)
877                 }
878         }
879 }
880
881 func TestMagneticContractTx(t *testing.T) {
882         buyerArgs := vmutil.MagneticContractArgs{
883                 RequestedAsset:   bc.AssetID{V0: 1},
884                 RatioNumerator:   1,
885                 RatioDenominator: 2,
886                 SellerProgram:    testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19204"),
887                 SellerKey:        testutil.MustDecodeHexString("af1927316233365dd525d3b48f2869f125a656958ee3946286f42904c35b9c91"),
888         }
889
890         sellerArgs := vmutil.MagneticContractArgs{
891                 RequestedAsset:   bc.AssetID{V0: 2},
892                 RatioNumerator:   2,
893                 RatioDenominator: 1,
894                 SellerProgram:    testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19204"),
895                 SellerKey:        testutil.MustDecodeHexString("af1927316233365dd525d3b48f2869f125a656958ee3946286f42904c35b9c91"),
896         }
897
898         programBuyer, err := vmutil.P2WMCProgram(buyerArgs)
899         if err != nil {
900                 t.Fatal(err)
901         }
902
903         programSeller, err := vmutil.P2WMCProgram(sellerArgs)
904         if err != nil {
905                 t.Fatal(err)
906         }
907
908         cases := []struct {
909                 desc  string
910                 block *bc.Block
911                 err   error
912         }{
913                 {
914                         desc: "contracts all full trade",
915                         block: &bc.Block{
916                                 BlockHeader: &bc.BlockHeader{Version: 0},
917                                 Transactions: []*bc.Tx{
918                                         types.MapTx(&types.TxData{
919                                                 SerializedSize: 1,
920                                                 Inputs: []*types.TxInput{
921                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
922                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
923                                                 },
924                                                 Outputs: []*types.TxOutput{
925                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 199800000, sellerArgs.SellerProgram),
926                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 99900000, buyerArgs.SellerProgram),
927                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000, []byte{0x51}),
928                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000, []byte{0x51}),
929                                                 },
930                                         }),
931                                 },
932                         },
933                         err: nil,
934                 },
935                 {
936                         desc: "first contract partial trade, second contract full trade",
937                         block: &bc.Block{
938                                 BlockHeader: &bc.BlockHeader{Version: 0},
939                                 Transactions: []*bc.Tx{
940                                         types.MapTx(&types.TxData{
941                                                 SerializedSize: 1,
942                                                 Inputs: []*types.TxInput{
943                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(0), vm.Int64Bytes(0)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 200000000, 1, programSeller),
944                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer),
945                                                 },
946                                                 Outputs: []*types.TxOutput{
947                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 99900000, sellerArgs.SellerProgram),
948                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 150000000, programSeller),
949                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 49950000, buyerArgs.SellerProgram),
950                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000, []byte{0x51}),
951                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000, []byte{0x51}),
952                                                 },
953                                         }),
954                                 },
955                         },
956                         err: nil,
957                 },
958                 {
959                         desc: "first contract full trade, second contract partial trade",
960                         block: &bc.Block{
961                                 BlockHeader: &bc.BlockHeader{Version: 0},
962                                 Transactions: []*bc.Tx{
963                                         types.MapTx(&types.TxData{
964                                                 SerializedSize: 1,
965                                                 Inputs: []*types.TxInput{
966                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
967                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(1), vm.Int64Bytes(0)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 300000000, 0, programBuyer),
968                                                 },
969                                                 Outputs: []*types.TxOutput{
970                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 199800000, sellerArgs.SellerProgram),
971                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 99900000, buyerArgs.SellerProgram),
972                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, programBuyer),
973                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000, []byte{0x51}),
974                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000, []byte{0x51}),
975                                                 },
976                                         }),
977                                 },
978                         },
979                         err: nil,
980                 },
981                 {
982                         desc: "cancel magnetic contract",
983                         block: &bc.Block{
984                                 BlockHeader: &bc.BlockHeader{Version: 0},
985                                 Transactions: []*bc.Tx{
986                                         types.MapTx(&types.TxData{
987                                                 SerializedSize: 1,
988                                                 Inputs: []*types.TxInput{
989                                                         types.NewSpendInput([][]byte{testutil.MustDecodeHexString("851a14d69076507e202a94a884cdfb3b9f1ecbc1fb0634d2f0d1f9c1a275fdbdf921af0c5309d2d0a0deb85973cba23a4076d2c169c7f08ade2af4048d91d209"), vm.Int64Bytes(0), vm.Int64Bytes(2)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 0, programSeller),
990                                                 },
991                                                 Outputs: []*types.TxOutput{
992                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
993                                                 },
994                                         }),
995                                 },
996                         },
997                         err: nil,
998                 },
999                 {
1000                         desc: "wrong signature with cancel magnetic contract",
1001                         block: &bc.Block{
1002                                 BlockHeader: &bc.BlockHeader{Version: 0},
1003                                 Transactions: []*bc.Tx{
1004                                         types.MapTx(&types.TxData{
1005                                                 SerializedSize: 1,
1006                                                 Inputs: []*types.TxInput{
1007                                                         types.NewSpendInput([][]byte{testutil.MustDecodeHexString("686b983a8de1893ef723144389fd1f07b12b048f52f389faa863243195931d5732dbfd15470b43ed63d5067900718cf94f137073f4a972d277bbd967b022545d"), vm.Int64Bytes(0), vm.Int64Bytes(2)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 0, programSeller),
1008                                                 },
1009                                                 Outputs: []*types.TxOutput{
1010                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
1011                                                 },
1012                                         }),
1013                                 },
1014                         },
1015                         err: vm.ErrFalseVMResult,
1016                 },
1017                 {
1018                         desc: "wrong output amount with contracts all full trade",
1019                         block: &bc.Block{
1020                                 BlockHeader: &bc.BlockHeader{Version: 0},
1021                                 Transactions: []*bc.Tx{
1022                                         types.MapTx(&types.TxData{
1023                                                 SerializedSize: 1,
1024                                                 Inputs: []*types.TxInput{
1025                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
1026                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
1027                                                 },
1028                                                 Outputs: []*types.TxOutput{
1029                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, sellerArgs.SellerProgram),
1030                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, buyerArgs.SellerProgram),
1031                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, []byte{0x55}),
1032                                                 },
1033                                         }),
1034                                 },
1035                         },
1036                         err: vm.ErrFalseVMResult,
1037                 },
1038                 {
1039                         desc: "wrong output assetID with contracts all full trade",
1040                         block: &bc.Block{
1041                                 BlockHeader: &bc.BlockHeader{Version: 0},
1042                                 Transactions: []*bc.Tx{
1043                                         types.MapTx(&types.TxData{
1044                                                 SerializedSize: 1,
1045                                                 Inputs: []*types.TxInput{
1046                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
1047                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
1048                                                         types.NewSpendInput(nil, bc.Hash{V0: 30}, bc.AssetID{V0: 1}, 200000000, 0, []byte{0x51}),
1049                                                 },
1050                                                 Outputs: []*types.TxOutput{
1051                                                         types.NewIntraChainOutput(bc.AssetID{V0: 1}, 200000000, sellerArgs.SellerProgram),
1052                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, buyerArgs.SellerProgram),
1053                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, []byte{0x55}),
1054                                                 },
1055                                         }),
1056                                 },
1057                         },
1058                         err: vm.ErrFalseVMResult,
1059                 },
1060                 {
1061                         desc: "wrong output change program with first contract partial trade and second contract full trade",
1062                         block: &bc.Block{
1063                                 BlockHeader: &bc.BlockHeader{Version: 0},
1064                                 Transactions: []*bc.Tx{
1065                                         types.MapTx(&types.TxData{
1066                                                 SerializedSize: 1,
1067                                                 Inputs: []*types.TxInput{
1068                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(0), vm.Int64Bytes(0)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 200000000, 1, programSeller),
1069                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer),
1070                                                 },
1071                                                 Outputs: []*types.TxOutput{
1072                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 99900000, sellerArgs.SellerProgram),
1073                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 150000000, []byte{0x55}),
1074                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 49950000, buyerArgs.SellerProgram),
1075                                                         types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000, []byte{0x51}),
1076                                                         types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000, []byte{0x51}),
1077                                                 },
1078                                         }),
1079                                 },
1080                         },
1081                         err: vm.ErrFalseVMResult,
1082                 },
1083         }
1084
1085         for i, c := range cases {
1086                 if _, err := ValidateTx(c.block.Transactions[0], c.block); rootErr(err) != c.err {
1087                         t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, rootErr(err), c.err)
1088                 }
1089         }
1090 }
1091
1092 func TestRingMagneticContractTx(t *testing.T) {
1093         aliceArgs := vmutil.MagneticContractArgs{
1094                 RequestedAsset:   bc.AssetID{V0: 1},
1095                 RatioNumerator:   2,
1096                 RatioDenominator: 1,
1097                 SellerProgram:    testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19204"),
1098                 SellerKey:        testutil.MustDecodeHexString("960ecabafb88ba460a40912841afecebf0e84884178611ac97210e327c0d1173"),
1099         }
1100
1101         bobArgs := vmutil.MagneticContractArgs{
1102                 RequestedAsset:   bc.AssetID{V0: 2},
1103                 RatioNumerator:   2,
1104                 RatioDenominator: 1,
1105                 SellerProgram:    testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19204"),
1106                 SellerKey:        testutil.MustDecodeHexString("ad79ec6bd3a6d6dbe4d0ee902afc99a12b9702fb63edce5f651db3081d868b75"),
1107         }
1108
1109         jackArgs := vmutil.MagneticContractArgs{
1110                 RequestedAsset:   bc.AssetID{V0: 3},
1111                 RatioNumerator:   1,
1112                 RatioDenominator: 4,
1113                 SellerProgram:    testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19204"),
1114                 SellerKey:        testutil.MustDecodeHexString("9c19a91988c62046c2767bd7e9999b0c142891b9ebf467bfa59210b435cb0de7"),
1115         }
1116
1117         aliceProgram, err := vmutil.P2WMCProgram(aliceArgs)
1118         if err != nil {
1119                 t.Fatal(err)
1120         }
1121
1122         bobProgram, err := vmutil.P2WMCProgram(bobArgs)
1123         if err != nil {
1124                 t.Fatal(err)
1125         }
1126
1127         jackProgram, err := vmutil.P2WMCProgram(jackArgs)
1128         if err != nil {
1129                 t.Fatal(err)
1130         }
1131
1132         cases := []struct {
1133                 desc  string
1134                 block *bc.Block
1135                 err   error
1136         }{
1137                 {
1138                         desc: "contracts all full trade",
1139                         block: &bc.Block{
1140                                 BlockHeader: &bc.BlockHeader{Version: 0},
1141                                 Transactions: []*bc.Tx{
1142                                         types.MapTx(&types.TxData{
1143                                                 SerializedSize: 1,
1144                                                 Inputs: []*types.TxInput{
1145                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, jackArgs.RequestedAsset, 100000000, 0, aliceProgram),
1146                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, aliceArgs.RequestedAsset, 200000000, 0, bobProgram),
1147                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 30}, bobArgs.RequestedAsset, 400000000, 0, jackProgram),
1148                                                 },
1149                                                 Outputs: []*types.TxOutput{
1150                                                         types.NewIntraChainOutput(aliceArgs.RequestedAsset, 199800000, aliceArgs.SellerProgram),
1151                                                         types.NewIntraChainOutput(bobArgs.RequestedAsset, 399600000, bobArgs.SellerProgram),
1152                                                         types.NewIntraChainOutput(jackArgs.RequestedAsset, 99900000, jackArgs.SellerProgram),
1153                                                         types.NewIntraChainOutput(aliceArgs.RequestedAsset, 200000, []byte{0x51}),
1154                                                         types.NewIntraChainOutput(bobArgs.RequestedAsset, 400000, []byte{0x51}),
1155                                                         types.NewIntraChainOutput(jackArgs.RequestedAsset, 100000, []byte{0x51}),
1156                                                 },
1157                                         }),
1158                                 },
1159                         },
1160                         err: nil,
1161                 },
1162         }
1163
1164         for i, c := range cases {
1165                 if _, err := ValidateTx(c.block.Transactions[0], c.block); rootErr(err) != c.err {
1166                         t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, rootErr(err), c.err)
1167                 }
1168         }
1169 }
1170
1171 func TestValidateOpenFederationIssueAsset(t *testing.T) {
1172         tx := &types.Tx{TxData: types.TxData{Version: 1}}
1173         tx.Inputs = append(tx.Inputs, types.NewCrossChainInput(nil,
1174                 testutil.MustDecodeHash("449143cb95389d19a1939879681168f78cc62614f4e0fb41f17b3232528a709d"),
1175                 testutil.MustDecodeAsset("60b550a772ddde42717ef3ab1178cf4f712a02fc9faf3678aa5468facff128f5"),
1176                 100000000,
1177                 0,
1178                 1,
1179                 testutil.MustDecodeHexString("7b0a202022646563696d616c73223a20382c0a2020226465736372697074696f6e223a207b0a202020202269737375655f61737365745f616374696f6e223a20226f70656e5f66656465726174696f6e5f63726f73735f636861696e220a20207d2c0a2020226e616d65223a2022454f53222c0a20202271756f72756d223a20312c0a20202272656973737565223a202274727565222c0a20202273796d626f6c223a2022454f53220a7d"),
1180                 testutil.MustDecodeHexString("ae20d827c281d47f5de93f98544b20468feaac046bf8b89bd51102f6e971f09d215920be43bb856fe337b37f5f09040c2b6cdbe23eaf5aa4770b16ea51fdfc45514c295152ad"),
1181         ))
1182
1183         tx.Outputs = append(tx.Outputs, types.NewIntraChainOutput(
1184                 testutil.MustDecodeAsset("60b550a772ddde42717ef3ab1178cf4f712a02fc9faf3678aa5468facff128f5"),
1185                 100000000,
1186                 testutil.MustDecodeHexString("0014d8dd58f374f58cffb1b1a7cc1e18a712b4fe67b5"),
1187         ))
1188
1189         byteData, err := tx.MarshalText()
1190         if err != nil {
1191                 t.Fatal(err)
1192         }
1193
1194         tx.SerializedSize = uint64(len(byteData))
1195         tx = types.NewTx(tx.TxData)
1196
1197         xPrv := chainkd.XPrv(toByte64("f0293101b509a0e919b4775d849372f97c688af8bd85a9d369fc1a4528baa94c0d74dd09aa6eaeed582df47d391c816b916a0537302291b09743903b730333f9"))
1198         signature := xPrv.Sign(tx.SigHash(0).Bytes())
1199         tx.Inputs[0].SetArguments([][]byte{signature})
1200         tx = types.NewTx(tx.TxData)
1201
1202         if _, err := ValidateTx(tx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Version: 1}}); err != nil {
1203                 t.Fatal(err)
1204         }
1205 }
1206
1207 func toByte64(str string) [64]byte {
1208         var result [64]byte
1209         bytes := testutil.MustDecodeHexString(str)
1210         for i := range bytes {
1211                 result[i] = bytes[i]
1212         }
1213         return result
1214 }
1215
1216 // A txFixture is returned by sample (below) to produce a sample
1217 // transaction, which takes a separate, optional _input_ txFixture to
1218 // affect the transaction that's built. The components of the
1219 // transaction are the fields of txFixture.
1220 type txFixture struct {
1221         initialBlockID bc.Hash
1222         issuanceProg   bc.Program
1223         issuanceArgs   [][]byte
1224         assetDef       []byte
1225         assetID        bc.AssetID
1226         txVersion      uint64
1227         txInputs       []*types.TxInput
1228         txOutputs      []*types.TxOutput
1229         tx             *types.TxData
1230 }
1231
1232 // Produces a sample transaction in a txFixture object (see above). A
1233 // separate input txFixture can be used to alter the transaction
1234 // that's created.
1235 //
1236 // The output of this function can be used as the input to a
1237 // subsequent call to make iterative refinements to a test object.
1238 //
1239 // The default transaction produced is valid and has three inputs:
1240 //  - an issuance of 10 units
1241 //  - a spend of 20 units
1242 //  - a spend of 40 units
1243 // and two outputs, one of 25 units and one of 45 units.
1244 // All amounts are denominated in the same asset.
1245 //
1246 // The issuance program for the asset requires two numbers as
1247 // arguments that add up to 5. The prevout control programs require
1248 // two numbers each, adding to 9 and 13, respectively.
1249 //
1250 // The min and max times for the transaction are now +/- one minute.
1251 func sample(tb testing.TB, in *txFixture) *txFixture {
1252         var result txFixture
1253         if in != nil {
1254                 result = *in
1255         }
1256
1257         if result.initialBlockID.IsZero() {
1258                 result.initialBlockID = *newHash(1)
1259         }
1260         if testutil.DeepEqual(result.issuanceProg, bc.Program{}) {
1261                 prog, err := vm.Assemble("ADD 5 NUMEQUAL")
1262                 if err != nil {
1263                         tb.Fatal(err)
1264                 }
1265                 result.issuanceProg = bc.Program{VmVersion: 1, Code: prog}
1266         }
1267         if len(result.issuanceArgs) == 0 {
1268                 result.issuanceArgs = [][]byte{{2}, {3}}
1269         }
1270         if len(result.assetDef) == 0 {
1271                 result.assetDef = []byte{2}
1272         }
1273         if result.assetID.IsZero() {
1274                 result.assetID = bc.AssetID{V0: 9999}
1275         }
1276
1277         if result.txVersion == 0 {
1278                 result.txVersion = 1
1279         }
1280         if len(result.txInputs) == 0 {
1281                 cp1, err := vm.Assemble("ADD 9 NUMEQUAL")
1282                 if err != nil {
1283                         tb.Fatal(err)
1284                 }
1285                 args1 := [][]byte{{4}, {5}}
1286
1287                 cp2, err := vm.Assemble("ADD 13 NUMEQUAL")
1288                 if err != nil {
1289                         tb.Fatal(err)
1290                 }
1291                 args2 := [][]byte{{6}, {7}}
1292
1293                 result.txInputs = []*types.TxInput{
1294                         types.NewSpendInput(nil, *newHash(9), result.assetID, 10, 0, []byte{byte(vm.OP_TRUE)}),
1295                         types.NewSpendInput(args1, *newHash(5), result.assetID, 20, 0, cp1),
1296                         types.NewSpendInput(args2, *newHash(8), result.assetID, 40, 0, cp2),
1297                 }
1298         }
1299
1300         result.txInputs = append(result.txInputs, mockGasTxInput())
1301
1302         if len(result.txOutputs) == 0 {
1303                 cp1, err := vm.Assemble("ADD 17 NUMEQUAL")
1304                 if err != nil {
1305                         tb.Fatal(err)
1306                 }
1307                 cp2, err := vm.Assemble("ADD 21 NUMEQUAL")
1308                 if err != nil {
1309                         tb.Fatal(err)
1310                 }
1311
1312                 result.txOutputs = []*types.TxOutput{
1313                         types.NewIntraChainOutput(result.assetID, 25, cp1),
1314                         types.NewIntraChainOutput(result.assetID, 45, cp2),
1315                 }
1316         }
1317
1318         result.tx = &types.TxData{
1319                 Version: result.txVersion,
1320                 Inputs:  result.txInputs,
1321                 Outputs: result.txOutputs,
1322         }
1323
1324         return &result
1325 }
1326
1327 func mockBlock() *bc.Block {
1328         return &bc.Block{
1329                 BlockHeader: &bc.BlockHeader{
1330                         Height: 666,
1331                 },
1332         }
1333 }
1334
1335 func mockGasTxInput() *types.TxInput {
1336         cp, _ := vmutil.DefaultCoinbaseProgram()
1337         return types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp)
1338 }
1339
1340 // Like errors.Root, but also unwraps vm.Error objects.
1341 func rootErr(e error) error {
1342         return errors.Root(e)
1343 }
1344
1345 func hashData(data []byte) bc.Hash {
1346         var b32 [32]byte
1347         sha3pool.Sum256(b32[:], data)
1348         return bc.NewHash(b32)
1349 }
1350
1351 func newHash(n byte) *bc.Hash {
1352         h := bc.NewHash([32]byte{n})
1353         return &h
1354 }
1355
1356 func newAssetID(n byte) *bc.AssetID {
1357         a := bc.NewAssetID([32]byte{n})
1358         return &a
1359 }
1360
1361 func txSpend(t *testing.T, tx *bc.Tx, index int) *bc.Spend {
1362         id := tx.InputIDs[index]
1363         res, err := tx.Spend(id)
1364         if err != nil {
1365                 t.Fatal(err)
1366         }
1367         return res
1368 }
1369
1370 func getMuxID(tx *bc.Tx) *bc.Hash {
1371         out := tx.Entries[*tx.ResultIds[0]]
1372         switch result := out.(type) {
1373         case *bc.IntraChainOutput:
1374                 return result.Source.Ref
1375         case *bc.Retirement:
1376                 return result.Source.Ref
1377         }
1378         return nil
1379 }