OSDN Git Service

a9d2c9494a41f00300fbc8f209fb9d82d874ae93
[bytom/bytom.git] / protocol / validation / validation_test.go
1 package validation
2
3 import (
4         "fmt"
5         "math"
6         "testing"
7         "time"
8
9         "github.com/blockchain/crypto/sha3pool"
10         "github.com/blockchain/errors"
11         "github.com/blockchain/protocol/bc"
12         "github.com/blockchain/protocol/bc/bctest"
13         "github.com/blockchain/protocol/bc/legacy"
14         "github.com/blockchain/protocol/vm"
15         "github.com/blockchain/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 TestTxValidation(t *testing.T) {
26         var (
27                 tx      *bc.Tx
28                 vs      *validationState
29                 fixture *txFixture
30
31                 // the mux from tx, pulled out for convenience
32                 mux *bc.Mux
33         )
34
35         cases := []struct {
36                 desc string // description of the test case
37                 f    func() // function to adjust tx, vs, and/or mux
38                 err  error  // expected error
39         }{
40                 {
41                         desc: "base case",
42                 },
43                 {
44                         desc: "failing mux program",
45                         f: func() {
46                                 mux.Program.Code = []byte{byte(vm.OP_FALSE)}
47                         },
48                         err: vm.ErrFalseVMResult,
49                 },
50                 {
51                         desc: "unbalanced mux amounts",
52                         f: func() {
53                                 mux.Sources[0].Value.Amount++
54                                 iss := tx.Entries[*mux.Sources[0].Ref].(*bc.Issuance)
55                                 iss.WitnessDestination.Value.Amount++
56                         },
57                         err: errUnbalanced,
58                 },
59                 {
60                         desc: "overflowing mux source amounts",
61                         f: func() {
62                                 mux.Sources[0].Value.Amount = math.MaxInt64
63                                 iss := tx.Entries[*mux.Sources[0].Ref].(*bc.Issuance)
64                                 iss.WitnessDestination.Value.Amount = math.MaxInt64
65                         },
66                         err: errOverflow,
67                 },
68                 {
69                         desc: "underflowing mux destination amounts",
70                         f: func() {
71                                 mux.WitnessDestinations[0].Value.Amount = math.MaxInt64
72                                 out := tx.Entries[*mux.WitnessDestinations[0].Ref].(*bc.Output)
73                                 out.Source.Value.Amount = math.MaxInt64
74                                 mux.WitnessDestinations[1].Value.Amount = math.MaxInt64
75                                 out = tx.Entries[*mux.WitnessDestinations[1].Ref].(*bc.Output)
76                                 out.Source.Value.Amount = math.MaxInt64
77                         },
78                         err: errOverflow,
79                 },
80                 {
81                         desc: "unbalanced mux assets",
82                         f: func() {
83                                 mux.Sources[1].Value.AssetId = newAssetID(255)
84                                 sp := tx.Entries[*mux.Sources[1].Ref].(*bc.Spend)
85                                 sp.WitnessDestination.Value.AssetId = newAssetID(255)
86                         },
87                         err: errUnbalanced,
88                 },
89                 {
90                         desc: "nonempty mux exthash",
91                         f: func() {
92                                 mux.ExtHash = newHash(1)
93                         },
94                         err: errNonemptyExtHash,
95                 },
96                 {
97                         desc: "nonempty mux exthash, but that's OK",
98                         f: func() {
99                                 tx.Version = 2
100                                 mux.ExtHash = newHash(1)
101                         },
102                 },
103                 {
104                         desc: "failing nonce program",
105                         f: func() {
106                                 iss := txIssuance(t, tx, 0)
107                                 nonce := tx.Entries[*iss.AnchorId].(*bc.Nonce)
108                                 nonce.Program.Code = []byte{byte(vm.OP_FALSE)}
109                         },
110                         err: vm.ErrFalseVMResult,
111                 },
112                 {
113                         desc: "nonce exthash nonempty",
114                         f: func() {
115                                 iss := txIssuance(t, tx, 0)
116                                 nonce := tx.Entries[*iss.AnchorId].(*bc.Nonce)
117                                 nonce.ExtHash = newHash(1)
118                         },
119                         err: errNonemptyExtHash,
120                 },
121                 {
122                         desc: "nonce exthash nonempty, but that's OK",
123                         f: func() {
124                                 tx.Version = 2
125                                 iss := txIssuance(t, tx, 0)
126                                 nonce := tx.Entries[*iss.AnchorId].(*bc.Nonce)
127                                 nonce.ExtHash = newHash(1)
128                         },
129                 },
130                 {
131                         desc: "nonce timerange misordered",
132                         f: func() {
133                                 iss := txIssuance(t, tx, 0)
134                                 nonce := tx.Entries[*iss.AnchorId].(*bc.Nonce)
135                                 tr := tx.Entries[*nonce.TimeRangeId].(*bc.TimeRange)
136                                 tr.MinTimeMs = tr.MaxTimeMs + 1
137                         },
138                         err: errBadTimeRange,
139                 },
140                 {
141                         desc: "nonce timerange disagrees with tx timerange",
142                         f: func() {
143                                 iss := txIssuance(t, tx, 0)
144                                 nonce := tx.Entries[*iss.AnchorId].(*bc.Nonce)
145                                 tr := tx.Entries[*nonce.TimeRangeId].(*bc.TimeRange)
146                                 tr.MaxTimeMs = tx.MaxTimeMs - 1
147                         },
148                         err: errBadTimeRange,
149                 },
150                 {
151                         desc: "nonce timerange exthash nonempty",
152                         f: func() {
153                                 iss := txIssuance(t, tx, 0)
154                                 nonce := tx.Entries[*iss.AnchorId].(*bc.Nonce)
155                                 tr := tx.Entries[*nonce.TimeRangeId].(*bc.TimeRange)
156                                 tr.ExtHash = newHash(1)
157                         },
158                         err: errNonemptyExtHash,
159                 },
160                 {
161                         desc: "nonce timerange exthash nonempty, but that's OK",
162                         f: func() {
163                                 tx.Version = 2
164                                 iss := txIssuance(t, tx, 0)
165                                 nonce := tx.Entries[*iss.AnchorId].(*bc.Nonce)
166                                 tr := tx.Entries[*nonce.TimeRangeId].(*bc.TimeRange)
167                                 tr.ExtHash = newHash(1)
168                         },
169                 },
170                 {
171                         desc: "mismatched output source / mux dest position",
172                         f: func() {
173                                 tx.Entries[*tx.ResultIds[0]].(*bc.Output).Source.Position = 1
174                         },
175                         err: errMismatchedPosition,
176                 },
177                 {
178                         desc: "mismatched output source and mux dest",
179                         f: func() {
180                                 // For this test, it's necessary to construct a mostly
181                                 // identical second transaction in order to get a similar but
182                                 // not equal output entry for the mux to falsely point
183                                 // to. That entry must be added to the first tx's Entries map.
184                                 fixture.txOutputs[0].ReferenceData = []byte{1}
185                                 fixture2 := sample(t, fixture)
186                                 tx2 := legacy.NewTx(*fixture2.tx).Tx
187                                 out2ID := tx2.ResultIds[0]
188                                 out2 := tx2.Entries[*out2ID].(*bc.Output)
189                                 tx.Entries[*out2ID] = out2
190                                 mux.WitnessDestinations[0].Ref = out2ID
191                         },
192                         err: errMismatchedReference,
193                 },
194                 {
195                         desc: "invalid mux destination position",
196                         f: func() {
197                                 mux.WitnessDestinations[0].Position = 1
198                         },
199                         err: errPosition,
200                 },
201                 {
202                         desc: "mismatched mux dest value / output source value",
203                         f: func() {
204                                 outID := tx.ResultIds[0]
205                                 out := tx.Entries[*outID].(*bc.Output)
206                                 mux.WitnessDestinations[0].Value = &bc.AssetAmount{
207                                         AssetId: out.Source.Value.AssetId,
208                                         Amount:  out.Source.Value.Amount + 1,
209                                 }
210                                 mux.Sources[0].Value.Amount++ // the mux must still balance
211                         },
212                         err: errMismatchedValue,
213                 },
214                 {
215                         desc: "output exthash nonempty",
216                         f: func() {
217                                 tx.Entries[*tx.ResultIds[0]].(*bc.Output).ExtHash = newHash(1)
218                         },
219                         err: errNonemptyExtHash,
220                 },
221                 {
222                         desc: "output exthash nonempty, but that's OK",
223                         f: func() {
224                                 tx.Version = 2
225                                 tx.Entries[*tx.ResultIds[0]].(*bc.Output).ExtHash = newHash(1)
226                         },
227                 },
228                 {
229                         desc: "misordered tx time range",
230                         f: func() {
231                                 tx.MinTimeMs = tx.MaxTimeMs + 1
232                         },
233                         err: errBadTimeRange,
234                 },
235                 {
236                         desc: "empty tx results",
237                         f: func() {
238                                 tx.ResultIds = nil
239                         },
240                         err: errEmptyResults,
241                 },
242                 {
243                         desc: "empty tx results, but that's OK",
244                         f: func() {
245                                 tx.Version = 2
246                                 tx.ResultIds = nil
247                         },
248                 },
249                 {
250                         desc: "tx header exthash nonempty",
251                         f: func() {
252                                 tx.ExtHash = newHash(1)
253                         },
254                         err: errNonemptyExtHash,
255                 },
256                 {
257                         desc: "tx header exthash nonempty, but that's OK",
258                         f: func() {
259                                 tx.Version = 2
260                                 tx.ExtHash = newHash(1)
261                         },
262                 },
263                 {
264                         desc: "wrong blockchain",
265                         f: func() {
266                                 vs.blockchainID = *newHash(2)
267                         },
268                         err: errWrongBlockchain,
269                 },
270                 {
271                         desc: "issuance asset ID mismatch",
272                         f: func() {
273                                 iss := txIssuance(t, tx, 0)
274                                 iss.Value.AssetId = newAssetID(1)
275                         },
276                         err: errMismatchedAssetID,
277                 },
278                 {
279                         desc: "issuance program failure",
280                         f: func() {
281                                 iss := txIssuance(t, tx, 0)
282                                 iss.WitnessArguments[0] = []byte{}
283                         },
284                         err: vm.ErrFalseVMResult,
285                 },
286                 {
287                         desc: "issuance exthash nonempty",
288                         f: func() {
289                                 iss := txIssuance(t, tx, 0)
290                                 iss.ExtHash = newHash(1)
291                         },
292                         err: errNonemptyExtHash,
293                 },
294                 {
295                         desc: "issuance exthash nonempty, but that's OK",
296                         f: func() {
297                                 tx.Version = 2
298                                 iss := txIssuance(t, tx, 0)
299                                 iss.ExtHash = newHash(1)
300                         },
301                 },
302                 {
303                         desc: "spend control program failure",
304                         f: func() {
305                                 spend := txSpend(t, tx, 1)
306                                 spend.WitnessArguments[0] = []byte{}
307                         },
308                         err: vm.ErrFalseVMResult,
309                 },
310                 {
311                         desc: "mismatched spent source/witness value",
312                         f: func() {
313                                 spend := txSpend(t, tx, 1)
314                                 spentOutput := tx.Entries[*spend.SpentOutputId].(*bc.Output)
315                                 spentOutput.Source.Value = &bc.AssetAmount{
316                                         AssetId: spend.WitnessDestination.Value.AssetId,
317                                         Amount:  spend.WitnessDestination.Value.Amount + 1,
318                                 }
319                         },
320                         err: errMismatchedValue,
321                 },
322                 {
323                         desc: "spend exthash nonempty",
324                         f: func() {
325                                 spend := txSpend(t, tx, 1)
326                                 spend.ExtHash = newHash(1)
327                         },
328                         err: errNonemptyExtHash,
329                 },
330                 {
331                         desc: "spend exthash nonempty, but that's OK",
332                         f: func() {
333                                 tx.Version = 2
334                                 spend := txSpend(t, tx, 1)
335                                 spend.ExtHash = newHash(1)
336                         },
337                 },
338         }
339
340         for _, c := range cases {
341                 t.Run(c.desc, func(t *testing.T) {
342                         fixture = sample(t, nil)
343                         tx = legacy.NewTx(*fixture.tx).Tx
344                         vs = &validationState{
345                                 blockchainID: fixture.initialBlockID,
346                                 tx:           tx,
347                                 entryID:      tx.ID,
348                                 cache:        make(map[bc.Hash]error),
349                         }
350                         out := tx.Entries[*tx.ResultIds[0]].(*bc.Output)
351                         muxID := out.Source.Ref
352                         mux = tx.Entries[*muxID].(*bc.Mux)
353
354                         if c.f != nil {
355                                 c.f()
356                         }
357                         err := checkValid(vs, tx.TxHeader)
358                         if rootErr(err) != c.err {
359                                 t.Errorf("got error %s, want %s; validationState is:\n%s", err, c.err, spew.Sdump(vs))
360                         }
361                 })
362         }
363 }
364
365 func TestNoncelessIssuance(t *testing.T) {
366         tx := bctest.NewIssuanceTx(t, bc.EmptyStringHash, func(tx *legacy.Tx) {
367                 // Remove the issuance nonce.
368                 tx.Inputs[0].TypedInput.(*legacy.IssuanceInput).Nonce = nil
369         })
370
371         err := ValidateTx(legacy.MapTx(&tx.TxData), bc.EmptyStringHash)
372         if errors.Root(err) != bc.ErrMissingEntry {
373                 t.Fatalf("got %s, want %s", err, bc.ErrMissingEntry)
374         }
375 }
376
377 func TestBlockHeaderValid(t *testing.T) {
378         base := bc.NewBlockHeader(1, 1, &bc.Hash{}, 1, &bc.Hash{}, &bc.Hash{}, nil)
379         baseBytes, _ := proto.Marshal(base)
380
381         var bh bc.BlockHeader
382
383         cases := []struct {
384                 f   func()
385                 err error
386         }{
387                 {},
388                 {
389                         f: func() {
390                                 bh.Version = 2
391                         },
392                 },
393                 {
394                         f: func() {
395                                 bh.ExtHash = newHash(1)
396                         },
397                         err: errNonemptyExtHash,
398                 },
399         }
400
401         for i, c := range cases {
402                 t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
403                         proto.Unmarshal(baseBytes, &bh)
404                         if c.f != nil {
405                                 c.f()
406                         }
407                         err := checkValidBlockHeader(&bh)
408                         if err != c.err {
409                                 t.Errorf("got error %s, want %s; bh is:\n%s", err, c.err, spew.Sdump(bh))
410                         }
411                 })
412         }
413 }
414
415 // A txFixture is returned by sample (below) to produce a sample
416 // transaction, which takes a separate, optional _input_ txFixture to
417 // affect the transaction that's built. The components of the
418 // transaction are the fields of txFixture.
419 type txFixture struct {
420         initialBlockID       bc.Hash
421         issuanceProg         bc.Program
422         issuanceArgs         [][]byte
423         assetDef             []byte
424         assetID              bc.AssetID
425         txVersion            uint64
426         txInputs             []*legacy.TxInput
427         txOutputs            []*legacy.TxOutput
428         txMinTime, txMaxTime uint64
429         txRefData            []byte
430         tx                   *legacy.TxData
431 }
432
433 // Produces a sample transaction in a txFixture object (see above). A
434 // separate input txFixture can be used to alter the transaction
435 // that's created.
436 //
437 // The output of this function can be used as the input to a
438 // subsequent call to make iterative refinements to a test object.
439 //
440 // The default transaction produced is valid and has three inputs:
441 //  - an issuance of 10 units
442 //  - a spend of 20 units
443 //  - a spend of 40 units
444 // and two outputs, one of 25 units and one of 45 units.
445 // All amounts are denominated in the same asset.
446 //
447 // The issuance program for the asset requires two numbers as
448 // arguments that add up to 5. The prevout control programs require
449 // two numbers each, adding to 9 and 13, respectively.
450 //
451 // The min and max times for the transaction are now +/- one minute.
452 func sample(tb testing.TB, in *txFixture) *txFixture {
453         var result txFixture
454         if in != nil {
455                 result = *in
456         }
457
458         if result.initialBlockID.IsZero() {
459                 result.initialBlockID = *newHash(1)
460         }
461         if testutil.DeepEqual(result.issuanceProg, bc.Program{}) {
462                 prog, err := vm.Assemble("ADD 5 NUMEQUAL")
463                 if err != nil {
464                         tb.Fatal(err)
465                 }
466                 result.issuanceProg = bc.Program{VmVersion: 1, Code: prog}
467         }
468         if len(result.issuanceArgs) == 0 {
469                 result.issuanceArgs = [][]byte{[]byte{2}, []byte{3}}
470         }
471         if len(result.assetDef) == 0 {
472                 result.assetDef = []byte{2}
473         }
474         if result.assetID.IsZero() {
475                 refdatahash := hashData(result.assetDef)
476                 result.assetID = bc.ComputeAssetID(result.issuanceProg.Code, &result.initialBlockID, result.issuanceProg.VmVersion, &refdatahash)
477         }
478
479         if result.txVersion == 0 {
480                 result.txVersion = 1
481         }
482         if len(result.txInputs) == 0 {
483                 cp1, err := vm.Assemble("ADD 9 NUMEQUAL")
484                 if err != nil {
485                         tb.Fatal(err)
486                 }
487                 args1 := [][]byte{[]byte{4}, []byte{5}}
488
489                 cp2, err := vm.Assemble("ADD 13 NUMEQUAL")
490                 if err != nil {
491                         tb.Fatal(err)
492                 }
493                 args2 := [][]byte{[]byte{6}, []byte{7}}
494
495                 result.txInputs = []*legacy.TxInput{
496                         legacy.NewIssuanceInput([]byte{3}, 10, []byte{4}, result.initialBlockID, result.issuanceProg.Code, result.issuanceArgs, result.assetDef),
497                         legacy.NewSpendInput(args1, *newHash(5), result.assetID, 20, 0, cp1, *newHash(6), []byte{7}),
498                         legacy.NewSpendInput(args2, *newHash(8), result.assetID, 40, 0, cp2, *newHash(9), []byte{10}),
499                 }
500         }
501         if len(result.txOutputs) == 0 {
502                 cp1, err := vm.Assemble("ADD 17 NUMEQUAL")
503                 if err != nil {
504                         tb.Fatal(err)
505                 }
506                 cp2, err := vm.Assemble("ADD 21 NUMEQUAL")
507                 if err != nil {
508                         tb.Fatal(err)
509                 }
510
511                 result.txOutputs = []*legacy.TxOutput{
512                         legacy.NewTxOutput(result.assetID, 25, cp1, []byte{11}),
513                         legacy.NewTxOutput(result.assetID, 45, cp2, []byte{12}),
514                 }
515         }
516         if result.txMinTime == 0 {
517                 result.txMinTime = bc.Millis(time.Now().Add(-time.Minute))
518         }
519         if result.txMaxTime == 0 {
520                 result.txMaxTime = bc.Millis(time.Now().Add(time.Minute))
521         }
522         if len(result.txRefData) == 0 {
523                 result.txRefData = []byte{13}
524         }
525
526         result.tx = &legacy.TxData{
527                 Version:       result.txVersion,
528                 Inputs:        result.txInputs,
529                 Outputs:       result.txOutputs,
530                 MinTime:       result.txMinTime,
531                 MaxTime:       result.txMaxTime,
532                 ReferenceData: result.txRefData,
533         }
534
535         return &result
536 }
537
538 // Like errors.Root, but also unwraps vm.Error objects.
539 func rootErr(e error) error {
540         for {
541                 e = errors.Root(e)
542                 if e2, ok := e.(vm.Error); ok {
543                         e = e2.Err
544                         continue
545                 }
546                 return e
547         }
548 }
549
550 func hashData(data []byte) bc.Hash {
551         var b32 [32]byte
552         sha3pool.Sum256(b32[:], data)
553         return bc.NewHash(b32)
554 }
555
556 func newHash(n byte) *bc.Hash {
557         h := bc.NewHash([32]byte{n})
558         return &h
559 }
560
561 func newAssetID(n byte) *bc.AssetID {
562         a := bc.NewAssetID([32]byte{n})
563         return &a
564 }
565
566 func txIssuance(t *testing.T, tx *bc.Tx, index int) *bc.Issuance {
567         id := tx.InputIDs[index]
568         res, err := tx.Issuance(id)
569         if err != nil {
570                 t.Fatal(err)
571         }
572         return res
573 }
574
575 func txSpend(t *testing.T, tx *bc.Tx, index int) *bc.Spend {
576         id := tx.InputIDs[index]
577         res, err := tx.Spend(id)
578         if err != nil {
579                 t.Fatal(err)
580         }
581         return res
582 }