OSDN Git Service

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