OSDN Git Service

remove the timerange check
[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/bytom/crypto/sha3pool"
10         "github.com/bytom/errors"
11         "github.com/bytom/protocol/bc"
12         "github.com/bytom/protocol/bc/bctest"
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 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: "mismatched output source / mux dest position",
132                         f: func() {
133                                 tx.Entries[*tx.ResultIds[0]].(*bc.Output).Source.Position = 1
134                         },
135                         err: errMismatchedPosition,
136                 },
137                 {
138                         desc: "mismatched output source and mux dest",
139                         f: func() {
140                                 // For this test, it's necessary to construct a mostly
141                                 // identical second transaction in order to get a similar but
142                                 // not equal output entry for the mux to falsely point
143                                 // to. That entry must be added to the first tx's Entries map.
144                                 fixture.txOutputs[0].ReferenceData = []byte{1}
145                                 fixture2 := sample(t, fixture)
146                                 tx2 := legacy.NewTx(*fixture2.tx).Tx
147                                 out2ID := tx2.ResultIds[0]
148                                 out2 := tx2.Entries[*out2ID].(*bc.Output)
149                                 tx.Entries[*out2ID] = out2
150                                 mux.WitnessDestinations[0].Ref = out2ID
151                         },
152                         err: errMismatchedReference,
153                 },
154                 {
155                         desc: "invalid mux destination position",
156                         f: func() {
157                                 mux.WitnessDestinations[0].Position = 1
158                         },
159                         err: errPosition,
160                 },
161                 {
162                         desc: "mismatched mux dest value / output source value",
163                         f: func() {
164                                 outID := tx.ResultIds[0]
165                                 out := tx.Entries[*outID].(*bc.Output)
166                                 mux.WitnessDestinations[0].Value = &bc.AssetAmount{
167                                         AssetId: out.Source.Value.AssetId,
168                                         Amount:  out.Source.Value.Amount + 1,
169                                 }
170                                 mux.Sources[0].Value.Amount++ // the mux must still balance
171                         },
172                         err: errMismatchedValue,
173                 },
174                 {
175                         desc: "output exthash nonempty",
176                         f: func() {
177                                 tx.Entries[*tx.ResultIds[0]].(*bc.Output).ExtHash = newHash(1)
178                         },
179                         err: errNonemptyExtHash,
180                 },
181                 {
182                         desc: "output exthash nonempty, but that's OK",
183                         f: func() {
184                                 tx.Version = 2
185                                 tx.Entries[*tx.ResultIds[0]].(*bc.Output).ExtHash = newHash(1)
186                         },
187                 },
188                 {
189                         desc: "misordered tx time range",
190                         f: func() {
191                                 tx.MinTimeMs = tx.MaxTimeMs + 1
192                         },
193                         err: errBadTimeRange,
194                 },
195                 {
196                         desc: "empty tx results",
197                         f: func() {
198                                 tx.ResultIds = nil
199                         },
200                         err: errEmptyResults,
201                 },
202                 {
203                         desc: "empty tx results, but that's OK",
204                         f: func() {
205                                 tx.Version = 2
206                                 tx.ResultIds = nil
207                         },
208                 },
209                 {
210                         desc: "tx header exthash nonempty",
211                         f: func() {
212                                 tx.ExtHash = newHash(1)
213                         },
214                         err: errNonemptyExtHash,
215                 },
216                 {
217                         desc: "tx header exthash nonempty, but that's OK",
218                         f: func() {
219                                 tx.Version = 2
220                                 tx.ExtHash = newHash(1)
221                         },
222                 },
223                 {
224                         desc: "issuance program failure",
225                         f: func() {
226                                 iss := txIssuance(t, tx, 0)
227                                 iss.WitnessArguments[0] = []byte{}
228                         },
229                         err: vm.ErrFalseVMResult,
230                 },
231                 {
232                         desc: "issuance exthash nonempty",
233                         f: func() {
234                                 iss := txIssuance(t, tx, 0)
235                                 iss.ExtHash = newHash(1)
236                         },
237                         err: errNonemptyExtHash,
238                 },
239                 {
240                         desc: "issuance exthash nonempty, but that's OK",
241                         f: func() {
242                                 tx.Version = 2
243                                 iss := txIssuance(t, tx, 0)
244                                 iss.ExtHash = newHash(1)
245                         },
246                 },
247                 {
248                         desc: "spend control program failure",
249                         f: func() {
250                                 spend := txSpend(t, tx, 1)
251                                 spend.WitnessArguments[0] = []byte{}
252                         },
253                         err: vm.ErrFalseVMResult,
254                 },
255                 {
256                         desc: "mismatched spent source/witness value",
257                         f: func() {
258                                 spend := txSpend(t, tx, 1)
259                                 spentOutput := tx.Entries[*spend.SpentOutputId].(*bc.Output)
260                                 spentOutput.Source.Value = &bc.AssetAmount{
261                                         AssetId: spend.WitnessDestination.Value.AssetId,
262                                         Amount:  spend.WitnessDestination.Value.Amount + 1,
263                                 }
264                         },
265                         err: errMismatchedValue,
266                 },
267                 {
268                         desc: "spend exthash nonempty",
269                         f: func() {
270                                 spend := txSpend(t, tx, 1)
271                                 spend.ExtHash = newHash(1)
272                         },
273                         err: errNonemptyExtHash,
274                 },
275                 {
276                         desc: "spend exthash nonempty, but that's OK",
277                         f: func() {
278                                 tx.Version = 2
279                                 spend := txSpend(t, tx, 1)
280                                 spend.ExtHash = newHash(1)
281                         },
282                 },
283         }
284
285         for _, c := range cases {
286                 t.Run(c.desc, func(t *testing.T) {
287                         fixture = sample(t, nil)
288                         tx = legacy.NewTx(*fixture.tx).Tx
289                         vs = &validationState{
290                                 block:   nil,
291                                 tx:      tx,
292                                 entryID: tx.ID,
293                                 gas: &gasState{
294                                         gasLeft: uint64(1000),
295                                         gasUsed: 0,
296                                 },
297                                 cache: make(map[bc.Hash]error),
298                         }
299                         out := tx.Entries[*tx.ResultIds[0]].(*bc.Output)
300                         muxID := out.Source.Ref
301                         mux = tx.Entries[*muxID].(*bc.Mux)
302
303                         if c.f != nil {
304                                 c.f()
305                         }
306                         err := checkValid(vs, tx.TxHeader)
307                         if rootErr(err) != c.err {
308                                 t.Errorf("got error %s, want %s; validationState is:\n%s", err, c.err, spew.Sdump(vs))
309                         }
310                 })
311         }
312 }
313
314 func TestNoncelessIssuance(t *testing.T) {
315         tx := bctest.NewIssuanceTx(t, bc.EmptyStringHash, func(tx *legacy.Tx) {
316                 // Remove the issuance nonce.
317                 tx.Inputs[0].TypedInput.(*legacy.IssuanceInput).Nonce = nil
318         })
319
320         _, err := ValidateTx(legacy.MapTx(&tx.TxData), nil)
321         if errors.Root(err) != bc.ErrMissingEntry {
322                 t.Fatalf("got %s, want %s", err, bc.ErrMissingEntry)
323         }
324 }
325
326 func TestBlockHeaderValid(t *testing.T) {
327         base := bc.NewBlockHeader(1, 1, &bc.Hash{}, 1, &bc.Hash{}, &bc.Hash{}, 0, 0)
328         baseBytes, _ := proto.Marshal(base)
329
330         var bh bc.BlockHeader
331
332         cases := []struct {
333                 f   func()
334                 err error
335         }{
336                 {},
337                 {
338                         f: func() {
339                                 bh.Version = 2
340                         },
341                 },
342         }
343
344         for i, c := range cases {
345                 t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
346                         proto.Unmarshal(baseBytes, &bh)
347                         if c.f != nil {
348                                 c.f()
349                         }
350                 })
351         }
352 }
353
354 // A txFixture is returned by sample (below) to produce a sample
355 // transaction, which takes a separate, optional _input_ txFixture to
356 // affect the transaction that's built. The components of the
357 // transaction are the fields of txFixture.
358 type txFixture struct {
359         initialBlockID       bc.Hash
360         issuanceProg         bc.Program
361         issuanceArgs         [][]byte
362         assetDef             []byte
363         assetID              bc.AssetID
364         txVersion            uint64
365         txInputs             []*legacy.TxInput
366         txOutputs            []*legacy.TxOutput
367         txMinTime, txMaxTime uint64
368         txRefData            []byte
369         tx                   *legacy.TxData
370 }
371
372 // Produces a sample transaction in a txFixture object (see above). A
373 // separate input txFixture can be used to alter the transaction
374 // that's created.
375 //
376 // The output of this function can be used as the input to a
377 // subsequent call to make iterative refinements to a test object.
378 //
379 // The default transaction produced is valid and has three inputs:
380 //  - an issuance of 10 units
381 //  - a spend of 20 units
382 //  - a spend of 40 units
383 // and two outputs, one of 25 units and one of 45 units.
384 // All amounts are denominated in the same asset.
385 //
386 // The issuance program for the asset requires two numbers as
387 // arguments that add up to 5. The prevout control programs require
388 // two numbers each, adding to 9 and 13, respectively.
389 //
390 // The min and max times for the transaction are now +/- one minute.
391 func sample(tb testing.TB, in *txFixture) *txFixture {
392         var result txFixture
393         if in != nil {
394                 result = *in
395         }
396
397         if result.initialBlockID.IsZero() {
398                 result.initialBlockID = *newHash(1)
399         }
400         if testutil.DeepEqual(result.issuanceProg, bc.Program{}) {
401                 prog, err := vm.Assemble("ADD 5 NUMEQUAL")
402                 if err != nil {
403                         tb.Fatal(err)
404                 }
405                 result.issuanceProg = bc.Program{VmVersion: 1, Code: prog}
406         }
407         if len(result.issuanceArgs) == 0 {
408                 result.issuanceArgs = [][]byte{[]byte{2}, []byte{3}}
409         }
410         if len(result.assetDef) == 0 {
411                 result.assetDef = []byte{2}
412         }
413         if result.assetID.IsZero() {
414                 refdatahash := hashData(result.assetDef)
415                 result.assetID = bc.ComputeAssetID(result.issuanceProg.Code, &result.initialBlockID, result.issuanceProg.VmVersion, &refdatahash)
416         }
417
418         if result.txVersion == 0 {
419                 result.txVersion = 1
420         }
421         if len(result.txInputs) == 0 {
422                 cp1, err := vm.Assemble("ADD 9 NUMEQUAL")
423                 if err != nil {
424                         tb.Fatal(err)
425                 }
426                 args1 := [][]byte{[]byte{4}, []byte{5}}
427
428                 cp2, err := vm.Assemble("ADD 13 NUMEQUAL")
429                 if err != nil {
430                         tb.Fatal(err)
431                 }
432                 args2 := [][]byte{[]byte{6}, []byte{7}}
433
434                 result.txInputs = []*legacy.TxInput{
435                         legacy.NewIssuanceInput([]byte{3}, 10, []byte{4}, result.initialBlockID, result.issuanceProg.Code, result.issuanceArgs, result.assetDef),
436                         legacy.NewSpendInput(args1, *newHash(5), result.assetID, 20, 0, cp1, *newHash(6), []byte{7}),
437                         legacy.NewSpendInput(args2, *newHash(8), result.assetID, 40, 0, cp2, *newHash(9), []byte{10}),
438                 }
439         }
440         if len(result.txOutputs) == 0 {
441                 cp1, err := vm.Assemble("ADD 17 NUMEQUAL")
442                 if err != nil {
443                         tb.Fatal(err)
444                 }
445                 cp2, err := vm.Assemble("ADD 21 NUMEQUAL")
446                 if err != nil {
447                         tb.Fatal(err)
448                 }
449
450                 result.txOutputs = []*legacy.TxOutput{
451                         legacy.NewTxOutput(result.assetID, 25, cp1, []byte{11}),
452                         legacy.NewTxOutput(result.assetID, 45, cp2, []byte{12}),
453                 }
454         }
455         if result.txMinTime == 0 {
456                 result.txMinTime = bc.Millis(time.Now().Add(-time.Minute))
457         }
458         if result.txMaxTime == 0 {
459                 result.txMaxTime = bc.Millis(time.Now().Add(time.Minute))
460         }
461         if len(result.txRefData) == 0 {
462                 result.txRefData = []byte{13}
463         }
464
465         result.tx = &legacy.TxData{
466                 Version:       result.txVersion,
467                 Inputs:        result.txInputs,
468                 Outputs:       result.txOutputs,
469                 MinTime:       result.txMinTime,
470                 MaxTime:       result.txMaxTime,
471                 ReferenceData: result.txRefData,
472         }
473
474         return &result
475 }
476
477 // Like errors.Root, but also unwraps vm.Error objects.
478 func rootErr(e error) error {
479         for {
480                 e = errors.Root(e)
481                 if e2, ok := e.(vm.Error); ok {
482                         e = e2.Err
483                         continue
484                 }
485                 return e
486         }
487 }
488
489 func hashData(data []byte) bc.Hash {
490         var b32 [32]byte
491         sha3pool.Sum256(b32[:], data)
492         return bc.NewHash(b32)
493 }
494
495 func newHash(n byte) *bc.Hash {
496         h := bc.NewHash([32]byte{n})
497         return &h
498 }
499
500 func newAssetID(n byte) *bc.AssetID {
501         a := bc.NewAssetID([32]byte{n})
502         return &a
503 }
504
505 func txIssuance(t *testing.T, tx *bc.Tx, index int) *bc.Issuance {
506         id := tx.InputIDs[index]
507         res, err := tx.Issuance(id)
508         if err != nil {
509                 t.Fatal(err)
510         }
511         return res
512 }
513
514 func txSpend(t *testing.T, tx *bc.Tx, index int) *bc.Spend {
515         id := tx.InputIDs[index]
516         res, err := tx.Spend(id)
517         if err != nil {
518                 t.Fatal(err)
519         }
520         return res
521 }