OSDN Git Service

Merge branch 'master' into demo
[bytom/bytom.git] / protocol / validation / validation_test.go
1 package validation
2
3 import (
4         "blockchain/consensus"
5         "fmt"
6         "math"
7         "testing"
8         "time"
9
10         "github.com/bytom/crypto/sha3pool"
11         "github.com/bytom/errors"
12         "github.com/bytom/protocol/bc"
13         "github.com/bytom/protocol/bc/legacy"
14         "github.com/bytom/protocol/vm"
15         "github.com/bytom/testutil"
16
17         "github.com/davecgh/go-spew/spew"
18         "github.com/golang/protobuf/proto"
19 )
20
21 func init() {
22         spew.Config.DisableMethods = true
23 }
24
25 func TestGasStatus(t *testing.T) {
26         cases := []struct {
27                 input  *gasState
28                 output *gasState
29                 f      func(*gasState) error
30                 err    error
31         }{
32                 {
33                         input: &gasState{
34                                 gasLeft:  10000,
35                                 gasUsed:  0,
36                                 BTMValue: 0,
37                         },
38                         output: &gasState{
39                                 gasLeft:  10000 / gasRate,
40                                 gasUsed:  0,
41                                 BTMValue: 10000,
42                         },
43                         f: func(input *gasState) error {
44                                 return input.setGas(10000)
45                         },
46                         err: nil,
47                 },
48                 {
49                         input: &gasState{
50                                 gasLeft:  10000,
51                                 gasUsed:  0,
52                                 BTMValue: 0,
53                         },
54                         output: &gasState{
55                                 gasLeft:  10000,
56                                 gasUsed:  0,
57                                 BTMValue: 0,
58                         },
59                         f: func(input *gasState) error {
60                                 return input.setGas(-10000)
61                         },
62                         err: errGasCalculate,
63                 },
64                 {
65                         input: &gasState{
66                                 gasLeft:  defaultGasLimit,
67                                 gasUsed:  0,
68                                 BTMValue: 0,
69                         },
70                         output: &gasState{
71                                 gasLeft:  defaultGasLimit,
72                                 gasUsed:  0,
73                                 BTMValue: 80000000000,
74                         },
75                         f: func(input *gasState) error {
76                                 return input.setGas(80000000000)
77                         },
78                         err: nil,
79                 },
80                 {
81                         input: &gasState{
82                                 gasLeft:  10000,
83                                 gasUsed:  0,
84                                 BTMValue: 0,
85                         },
86                         output: &gasState{
87                                 gasLeft:  10000,
88                                 gasUsed:  0,
89                                 BTMValue: 0,
90                         },
91                         f: func(input *gasState) error {
92                                 return input.updateUsage(-1)
93                         },
94                         err: errGasCalculate,
95                 },
96                 {
97                         input: &gasState{
98                                 gasLeft:  10000,
99                                 gasUsed:  0,
100                                 BTMValue: 0,
101                         },
102                         output: &gasState{
103                                 gasLeft:  9999,
104                                 gasUsed:  1,
105                                 BTMValue: 0,
106                         },
107                         f: func(input *gasState) error {
108                                 return input.updateUsage(9999)
109                         },
110                         err: nil,
111                 },
112         }
113
114         for _, c := range cases {
115                 err := c.f(c.input)
116
117                 if err != c.err {
118                         t.Errorf("got error %s, want %s", err, c.err)
119                 } else if *c.input != *c.output {
120                         t.Errorf("got gasStatus %s, want %s;", c.input, c.output)
121                 }
122         }
123 }
124
125 func TestTxValidation(t *testing.T) {
126         var (
127                 tx      *bc.Tx
128                 vs      *validationState
129                 fixture *txFixture
130
131                 // the mux from tx, pulled out for convenience
132                 mux *bc.Mux
133         )
134
135         cases := []struct {
136                 desc string // description of the test case
137                 f    func() // function to adjust tx, vs, and/or mux
138                 err  error  // expected error
139         }{
140                 {
141                         desc: "base case",
142                 },
143                 {
144                         desc: "failing mux program",
145                         f: func() {
146                                 mux.Program.Code = []byte{byte(vm.OP_FALSE)}
147                         },
148                         err: vm.ErrFalseVMResult,
149                 },
150                 {
151                         desc: "unbalanced mux amounts",
152                         f: func() {
153                                 mux.Sources[0].Value.Amount++
154                                 iss := tx.Entries[*mux.Sources[0].Ref].(*bc.Issuance)
155                                 iss.WitnessDestination.Value.Amount++
156                         },
157                         err: errUnbalanced,
158                 },
159                 {
160                         desc: "overflowing mux source amounts",
161                         f: func() {
162                                 mux.Sources[0].Value.Amount = math.MaxInt64
163                                 iss := tx.Entries[*mux.Sources[0].Ref].(*bc.Issuance)
164                                 iss.WitnessDestination.Value.Amount = math.MaxInt64
165                         },
166                         err: errOverflow,
167                 },
168                 {
169                         desc: "underflowing mux destination amounts",
170                         f: func() {
171                                 mux.WitnessDestinations[0].Value.Amount = math.MaxInt64
172                                 out := tx.Entries[*mux.WitnessDestinations[0].Ref].(*bc.Output)
173                                 out.Source.Value.Amount = math.MaxInt64
174                                 mux.WitnessDestinations[1].Value.Amount = math.MaxInt64
175                                 out = tx.Entries[*mux.WitnessDestinations[1].Ref].(*bc.Output)
176                                 out.Source.Value.Amount = math.MaxInt64
177                         },
178                         err: errOverflow,
179                 },
180                 {
181                         desc: "unbalanced mux assets",
182                         f: func() {
183                                 mux.Sources[1].Value.AssetId = newAssetID(255)
184                                 sp := tx.Entries[*mux.Sources[1].Ref].(*bc.Spend)
185                                 sp.WitnessDestination.Value.AssetId = newAssetID(255)
186                         },
187                         err: errUnbalanced,
188                 },
189                 {
190                         desc: "nonempty mux exthash",
191                         f: func() {
192                                 mux.ExtHash = newHash(1)
193                         },
194                         err: errNonemptyExtHash,
195                 },
196                 {
197                         desc: "nonempty mux exthash, but that's OK",
198                         f: func() {
199                                 tx.Version = 2
200                                 mux.ExtHash = newHash(1)
201                         },
202                 },
203                 {
204                         desc: "failing nonce program",
205                         f: func() {
206                                 iss := txIssuance(t, tx, 0)
207                                 nonce := tx.Entries[*iss.AnchorId].(*bc.Nonce)
208                                 nonce.Program.Code = []byte{byte(vm.OP_FALSE)}
209                         },
210                         err: vm.ErrFalseVMResult,
211                 },
212                 {
213                         desc: "nonce exthash nonempty",
214                         f: func() {
215                                 iss := txIssuance(t, tx, 0)
216                                 nonce := tx.Entries[*iss.AnchorId].(*bc.Nonce)
217                                 nonce.ExtHash = newHash(1)
218                         },
219                         err: errNonemptyExtHash,
220                 },
221                 {
222                         desc: "nonce exthash nonempty, but that's OK",
223                         f: func() {
224                                 tx.Version = 2
225                                 iss := txIssuance(t, tx, 0)
226                                 nonce := tx.Entries[*iss.AnchorId].(*bc.Nonce)
227                                 nonce.ExtHash = newHash(1)
228                         },
229                 },
230                 {
231                         desc: "mismatched output source / mux dest position",
232                         f: func() {
233                                 tx.Entries[*tx.ResultIds[0]].(*bc.Output).Source.Position = 1
234                         },
235                         err: errMismatchedPosition,
236                 },
237                 {
238                         desc: "mismatched output source and mux dest",
239                         f: func() {
240                                 // For this test, it's necessary to construct a mostly
241                                 // identical second transaction in order to get a similar but
242                                 // not equal output entry for the mux to falsely point
243                                 // to. That entry must be added to the first tx's Entries map.
244                                 fixture.txOutputs[0].ReferenceData = []byte{1}
245                                 fixture2 := sample(t, fixture)
246                                 tx2 := legacy.NewTx(*fixture2.tx).Tx
247                                 out2ID := tx2.ResultIds[0]
248                                 out2 := tx2.Entries[*out2ID].(*bc.Output)
249                                 tx.Entries[*out2ID] = out2
250                                 mux.WitnessDestinations[0].Ref = out2ID
251                         },
252                         err: errMismatchedReference,
253                 },
254                 {
255                         desc: "invalid mux destination position",
256                         f: func() {
257                                 mux.WitnessDestinations[0].Position = 1
258                         },
259                         err: errPosition,
260                 },
261                 {
262                         desc: "mismatched mux dest value / output source value",
263                         f: func() {
264                                 outID := tx.ResultIds[0]
265                                 out := tx.Entries[*outID].(*bc.Output)
266                                 mux.WitnessDestinations[0].Value = &bc.AssetAmount{
267                                         AssetId: out.Source.Value.AssetId,
268                                         Amount:  out.Source.Value.Amount + 1,
269                                 }
270                                 mux.Sources[0].Value.Amount++ // the mux must still balance
271                         },
272                         err: errMismatchedValue,
273                 },
274                 {
275                         desc: "output exthash nonempty",
276                         f: func() {
277                                 tx.Entries[*tx.ResultIds[0]].(*bc.Output).ExtHash = newHash(1)
278                         },
279                         err: errNonemptyExtHash,
280                 },
281                 {
282                         desc: "output exthash nonempty, but that's OK",
283                         f: func() {
284                                 tx.Version = 2
285                                 tx.Entries[*tx.ResultIds[0]].(*bc.Output).ExtHash = newHash(1)
286                         },
287                 },
288                 {
289                         desc: "empty tx results",
290                         f: func() {
291                                 tx.ResultIds = nil
292                         },
293                         err: errEmptyResults,
294                 },
295                 {
296                         desc: "empty tx results, but that's OK",
297                         f: func() {
298                                 tx.Version = 2
299                                 tx.ResultIds = nil
300                         },
301                 },
302                 {
303                         desc: "tx header exthash nonempty",
304                         f: func() {
305                                 tx.ExtHash = newHash(1)
306                         },
307                         err: errNonemptyExtHash,
308                 },
309                 {
310                         desc: "tx header exthash nonempty, but that's OK",
311                         f: func() {
312                                 tx.Version = 2
313                                 tx.ExtHash = newHash(1)
314                         },
315                 },
316                 {
317                         desc: "issuance program failure",
318                         f: func() {
319                                 iss := txIssuance(t, tx, 0)
320                                 iss.WitnessArguments[0] = []byte{}
321                         },
322                         err: vm.ErrFalseVMResult,
323                 },
324                 {
325                         desc: "issuance exthash nonempty",
326                         f: func() {
327                                 iss := txIssuance(t, tx, 0)
328                                 iss.ExtHash = newHash(1)
329                         },
330                         err: errNonemptyExtHash,
331                 },
332                 {
333                         desc: "issuance exthash nonempty, but that's OK",
334                         f: func() {
335                                 tx.Version = 2
336                                 iss := txIssuance(t, tx, 0)
337                                 iss.ExtHash = newHash(1)
338                         },
339                 },
340                 {
341                         desc: "spend control program failure",
342                         f: func() {
343                                 spend := txSpend(t, tx, 1)
344                                 spend.WitnessArguments[0] = []byte{}
345                         },
346                         err: vm.ErrFalseVMResult,
347                 },
348                 {
349                         desc: "mismatched spent source/witness value",
350                         f: func() {
351                                 spend := txSpend(t, tx, 1)
352                                 spentOutput := tx.Entries[*spend.SpentOutputId].(*bc.Output)
353                                 spentOutput.Source.Value = &bc.AssetAmount{
354                                         AssetId: spend.WitnessDestination.Value.AssetId,
355                                         Amount:  spend.WitnessDestination.Value.Amount + 1,
356                                 }
357                         },
358                         err: errMismatchedValue,
359                 },
360                 {
361                         desc: "spend exthash nonempty",
362                         f: func() {
363                                 spend := txSpend(t, tx, 1)
364                                 spend.ExtHash = newHash(1)
365                         },
366                         err: errNonemptyExtHash,
367                 },
368                 {
369                         desc: "spend exthash nonempty, but that's OK",
370                         f: func() {
371                                 tx.Version = 2
372                                 spend := txSpend(t, tx, 1)
373                                 spend.ExtHash = newHash(1)
374                         },
375                 },
376         }
377
378         for _, c := range cases {
379                 t.Run(c.desc, func(t *testing.T) {
380                         fixture = sample(t, nil)
381                         tx = legacy.NewTx(*fixture.tx).Tx
382                         vs = &validationState{
383                                 block:   mockBlock(),
384                                 tx:      tx,
385                                 entryID: tx.ID,
386                                 gas: &gasState{
387                                         gasLeft: int64(80000),
388                                         gasUsed: 0,
389                                 },
390                                 cache: make(map[bc.Hash]error),
391                         }
392                         out := tx.Entries[*tx.ResultIds[0]].(*bc.Output)
393                         muxID := out.Source.Ref
394                         mux = tx.Entries[*muxID].(*bc.Mux)
395
396                         if c.f != nil {
397                                 c.f()
398                         }
399                         err := checkValid(vs, tx.TxHeader)
400
401                         if rootErr(err) != c.err {
402                                 t.Errorf("got error %s, want %s; validationState is:\n%s", err, c.err, spew.Sdump(vs))
403                         }
404                 })
405         }
406 }
407
408 func TestValidateBlock(t *testing.T) {
409         cases := []struct {
410                 block *bc.Block
411                 err   error
412         }{
413                 {
414                         block: &bc.Block{
415                                 BlockHeader: &bc.BlockHeader{
416                                         Height: 1,
417                                 },
418                                 Transactions: []*bc.Tx{mockCoinbaseTx(624000000000)},
419                         },
420                         err: nil,
421                 },
422                 {
423                         block: &bc.Block{
424                                 BlockHeader: &bc.BlockHeader{
425                                         Height: 1,
426                                 },
427                                 Transactions: []*bc.Tx{mockCoinbaseTx(1)},
428                         },
429                         err: errWrongCoinbaseTransaction,
430                 },
431                 {
432                         block: &bc.Block{
433                                 BlockHeader: &bc.BlockHeader{
434                                         Height:         1,
435                                         SerializedSize: 88888888,
436                                 },
437                                 Transactions: []*bc.Tx{mockCoinbaseTx(1)},
438                         },
439                         err: errWrongBlockSize,
440                 },
441         }
442
443         for _, c := range cases {
444                 txRoot, err := bc.MerkleRoot(c.block.Transactions)
445                 if err != nil {
446                         t.Errorf("computing transaction merkle root", err)
447                         continue
448                 }
449                 c.block.TransactionsRoot = &txRoot
450                 err = ValidateBlock(c.block, nil)
451
452                 if rootErr(err) != c.err {
453                         t.Errorf("got error %s, want %s", err, c.err)
454                 }
455         }
456 }
457
458 func TestCoinbase(t *testing.T) {
459         CbTx := mockCoinbaseTx(5000000000)
460         errCbTx := legacy.MapTx(&legacy.TxData{
461                 Outputs: []*legacy.TxOutput{
462                         legacy.NewTxOutput(bc.AssetID{
463                                 V0: uint64(18446744073709551611),
464                                 V1: uint64(18446744073709551615),
465                                 V2: uint64(18446744073709551615),
466                                 V3: uint64(18446744073709551615),
467                         }, 800000000000, []byte{1}, nil),
468                 },
469         })
470         cases := []struct {
471                 block *bc.Block
472                 tx    *bc.Tx
473                 err   error
474         }{
475                 {
476                         block: &bc.Block{
477                                 BlockHeader: &bc.BlockHeader{
478                                         Height: 666,
479                                 },
480                                 Transactions: []*bc.Tx{errCbTx},
481                         },
482                         tx:  CbTx,
483                         err: errWrongCoinbaseTransaction,
484                 },
485                 {
486                         block: &bc.Block{
487                                 BlockHeader: &bc.BlockHeader{
488                                         Height: 666,
489                                 },
490                                 Transactions: []*bc.Tx{CbTx},
491                         },
492                         tx:  CbTx,
493                         err: nil,
494                 },
495                 {
496                         block: &bc.Block{
497                                 BlockHeader: &bc.BlockHeader{
498                                         Height: 666,
499                                 },
500                                 Transactions: []*bc.Tx{errCbTx},
501                         },
502                         tx:  errCbTx,
503                         err: errWrongCoinbaseAsset,
504                 },
505         }
506
507         for _, c := range cases {
508                 _, err := ValidateTx(c.tx, c.block)
509
510                 if rootErr(err) != c.err {
511                         t.Errorf("got error %s, want %s", err, c.err)
512                 }
513         }
514 }
515
516 func TestBlockHeaderValid(t *testing.T) {
517         base := bc.NewBlockHeader(1, 1, &bc.Hash{}, 1, &bc.Hash{}, &bc.Hash{}, 0, 0)
518         baseBytes, _ := proto.Marshal(base)
519
520         var bh bc.BlockHeader
521
522         cases := []struct {
523                 f   func()
524                 err error
525         }{
526                 {},
527                 {
528                         f: func() {
529                                 bh.Version = 2
530                         },
531                 },
532         }
533
534         for i, c := range cases {
535                 t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
536                         proto.Unmarshal(baseBytes, &bh)
537                         if c.f != nil {
538                                 c.f()
539                         }
540                 })
541         }
542 }
543
544 // A txFixture is returned by sample (below) to produce a sample
545 // transaction, which takes a separate, optional _input_ txFixture to
546 // affect the transaction that's built. The components of the
547 // transaction are the fields of txFixture.
548 type txFixture struct {
549         initialBlockID       bc.Hash
550         issuanceProg         bc.Program
551         issuanceArgs         [][]byte
552         assetDef             []byte
553         assetID              bc.AssetID
554         txVersion            uint64
555         txInputs             []*legacy.TxInput
556         txOutputs            []*legacy.TxOutput
557         txMinTime, txMaxTime uint64
558         txRefData            []byte
559         tx                   *legacy.TxData
560 }
561
562 // Produces a sample transaction in a txFixture object (see above). A
563 // separate input txFixture can be used to alter the transaction
564 // that's created.
565 //
566 // The output of this function can be used as the input to a
567 // subsequent call to make iterative refinements to a test object.
568 //
569 // The default transaction produced is valid and has three inputs:
570 //  - an issuance of 10 units
571 //  - a spend of 20 units
572 //  - a spend of 40 units
573 // and two outputs, one of 25 units and one of 45 units.
574 // All amounts are denominated in the same asset.
575 //
576 // The issuance program for the asset requires two numbers as
577 // arguments that add up to 5. The prevout control programs require
578 // two numbers each, adding to 9 and 13, respectively.
579 //
580 // The min and max times for the transaction are now +/- one minute.
581 func sample(tb testing.TB, in *txFixture) *txFixture {
582         var result txFixture
583         if in != nil {
584                 result = *in
585         }
586
587         if result.initialBlockID.IsZero() {
588                 result.initialBlockID = *newHash(1)
589         }
590         if testutil.DeepEqual(result.issuanceProg, bc.Program{}) {
591                 prog, err := vm.Assemble("ADD 5 NUMEQUAL")
592                 if err != nil {
593                         tb.Fatal(err)
594                 }
595                 result.issuanceProg = bc.Program{VmVersion: 1, Code: prog}
596         }
597         if len(result.issuanceArgs) == 0 {
598                 result.issuanceArgs = [][]byte{[]byte{2}, []byte{3}}
599         }
600         if len(result.assetDef) == 0 {
601                 result.assetDef = []byte{2}
602         }
603         if result.assetID.IsZero() {
604                 refdatahash := hashData(result.assetDef)
605                 result.assetID = bc.ComputeAssetID(result.issuanceProg.Code, &result.initialBlockID, result.issuanceProg.VmVersion, &refdatahash)
606         }
607
608         if result.txVersion == 0 {
609                 result.txVersion = 1
610         }
611         if len(result.txInputs) == 0 {
612                 cp1, err := vm.Assemble("ADD 9 NUMEQUAL")
613                 if err != nil {
614                         tb.Fatal(err)
615                 }
616                 args1 := [][]byte{[]byte{4}, []byte{5}}
617
618                 cp2, err := vm.Assemble("ADD 13 NUMEQUAL")
619                 if err != nil {
620                         tb.Fatal(err)
621                 }
622                 args2 := [][]byte{[]byte{6}, []byte{7}}
623
624                 result.txInputs = []*legacy.TxInput{
625                         legacy.NewIssuanceInput([]byte{3}, 10, []byte{4}, result.initialBlockID, result.issuanceProg.Code, result.issuanceArgs, result.assetDef),
626                         legacy.NewSpendInput(args1, *newHash(5), result.assetID, 20, 0, cp1, *newHash(6), []byte{7}),
627                         legacy.NewSpendInput(args2, *newHash(8), result.assetID, 40, 0, cp2, *newHash(9), []byte{10}),
628                 }
629         }
630
631         result.txInputs = append(result.txInputs, mockGasTxInput())
632
633         if len(result.txOutputs) == 0 {
634                 cp1, err := vm.Assemble("ADD 17 NUMEQUAL")
635                 if err != nil {
636                         tb.Fatal(err)
637                 }
638                 cp2, err := vm.Assemble("ADD 21 NUMEQUAL")
639                 if err != nil {
640                         tb.Fatal(err)
641                 }
642
643                 result.txOutputs = []*legacy.TxOutput{
644                         legacy.NewTxOutput(result.assetID, 25, cp1, []byte{11}),
645                         legacy.NewTxOutput(result.assetID, 45, cp2, []byte{12}),
646                 }
647         }
648         if result.txMinTime == 0 {
649                 result.txMinTime = bc.Millis(time.Now().Add(-time.Minute))
650         }
651         if result.txMaxTime == 0 {
652                 result.txMaxTime = bc.Millis(time.Now().Add(time.Minute))
653         }
654         if len(result.txRefData) == 0 {
655                 result.txRefData = []byte{13}
656         }
657
658         result.tx = &legacy.TxData{
659                 Version:       result.txVersion,
660                 Inputs:        result.txInputs,
661                 Outputs:       result.txOutputs,
662                 MinTime:       result.txMinTime,
663                 MaxTime:       result.txMaxTime,
664                 ReferenceData: result.txRefData,
665         }
666
667         return &result
668 }
669
670 func mockBlock() *bc.Block {
671         return &bc.Block{
672                 BlockHeader: &bc.BlockHeader{
673                         Height: 666,
674                 },
675         }
676 }
677
678 func mockCoinbaseTx(amount uint64) *bc.Tx {
679         return legacy.MapTx(&legacy.TxData{
680                 Outputs: []*legacy.TxOutput{
681                         legacy.NewTxOutput(*consensus.BTMAssetID, amount, []byte{1}, nil),
682                 },
683         })
684 }
685
686 func mockGasTxInput() *legacy.TxInput {
687         return legacy.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, []byte{byte(vm.OP_TRUE)}, *newHash(9), []byte{})
688 }
689
690 // Like errors.Root, but also unwraps vm.Error objects.
691 func rootErr(e error) error {
692         for {
693                 e = errors.Root(e)
694                 if e2, ok := e.(vm.Error); ok {
695                         e = e2.Err
696                         continue
697                 }
698                 return e
699         }
700 }
701
702 func hashData(data []byte) bc.Hash {
703         var b32 [32]byte
704         sha3pool.Sum256(b32[:], data)
705         return bc.NewHash(b32)
706 }
707
708 func newHash(n byte) *bc.Hash {
709         h := bc.NewHash([32]byte{n})
710         return &h
711 }
712
713 func newAssetID(n byte) *bc.AssetID {
714         a := bc.NewAssetID([32]byte{n})
715         return &a
716 }
717
718 func txIssuance(t *testing.T, tx *bc.Tx, index int) *bc.Issuance {
719         id := tx.InputIDs[index]
720         res, err := tx.Issuance(id)
721         if err != nil {
722                 t.Fatal(err)
723         }
724         return res
725 }
726
727 func txSpend(t *testing.T, tx *bc.Tx, index int) *bc.Spend {
728         id := tx.InputIDs[index]
729         res, err := tx.Spend(id)
730         if err != nil {
731                 t.Fatal(err)
732         }
733         return res
734 }