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"
17 "github.com/davecgh/go-spew/spew"
18 "github.com/golang/protobuf/proto"
22 spew.Config.DisableMethods = true
25 func TestTxValidation(t *testing.T) {
31 // the mux from tx, pulled out for convenience
36 desc string // description of the test case
37 f func() // function to adjust tx, vs, and/or mux
38 err error // expected error
44 desc: "failing mux program",
46 mux.Program.Code = []byte{byte(vm.OP_FALSE)}
48 err: vm.ErrFalseVMResult,
51 desc: "unbalanced mux amounts",
53 mux.Sources[0].Value.Amount++
54 iss := tx.Entries[*mux.Sources[0].Ref].(*bc.Issuance)
55 iss.WitnessDestination.Value.Amount++
60 desc: "overflowing mux source amounts",
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
69 desc: "underflowing mux destination amounts",
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
81 desc: "unbalanced mux assets",
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)
90 desc: "nonempty mux exthash",
92 mux.ExtHash = newHash(1)
94 err: errNonemptyExtHash,
97 desc: "nonempty mux exthash, but that's OK",
100 mux.ExtHash = newHash(1)
104 desc: "failing nonce program",
106 iss := txIssuance(t, tx, 0)
107 nonce := tx.Entries[*iss.AnchorId].(*bc.Nonce)
108 nonce.Program.Code = []byte{byte(vm.OP_FALSE)}
110 err: vm.ErrFalseVMResult,
113 desc: "nonce exthash nonempty",
115 iss := txIssuance(t, tx, 0)
116 nonce := tx.Entries[*iss.AnchorId].(*bc.Nonce)
117 nonce.ExtHash = newHash(1)
119 err: errNonemptyExtHash,
122 desc: "nonce exthash nonempty, but that's OK",
125 iss := txIssuance(t, tx, 0)
126 nonce := tx.Entries[*iss.AnchorId].(*bc.Nonce)
127 nonce.ExtHash = newHash(1)
131 desc: "nonce timerange misordered",
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
138 err: errBadTimeRange,
141 desc: "nonce timerange disagrees with tx timerange",
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
148 err: errBadTimeRange,
151 desc: "nonce timerange exthash nonempty",
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)
158 err: errNonemptyExtHash,
161 desc: "nonce timerange exthash nonempty, but that's OK",
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)
171 desc: "mismatched output source / mux dest position",
173 tx.Entries[*tx.ResultIds[0]].(*bc.Output).Source.Position = 1
175 err: errMismatchedPosition,
178 desc: "mismatched output source and mux dest",
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
192 err: errMismatchedReference,
195 desc: "invalid mux destination position",
197 mux.WitnessDestinations[0].Position = 1
202 desc: "mismatched mux dest value / output source value",
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,
210 mux.Sources[0].Value.Amount++ // the mux must still balance
212 err: errMismatchedValue,
215 desc: "output exthash nonempty",
217 tx.Entries[*tx.ResultIds[0]].(*bc.Output).ExtHash = newHash(1)
219 err: errNonemptyExtHash,
222 desc: "output exthash nonempty, but that's OK",
225 tx.Entries[*tx.ResultIds[0]].(*bc.Output).ExtHash = newHash(1)
229 desc: "misordered tx time range",
231 tx.MinTimeMs = tx.MaxTimeMs + 1
233 err: errBadTimeRange,
236 desc: "empty tx results",
240 err: errEmptyResults,
243 desc: "empty tx results, but that's OK",
250 desc: "tx header exthash nonempty",
252 tx.ExtHash = newHash(1)
254 err: errNonemptyExtHash,
257 desc: "tx header exthash nonempty, but that's OK",
260 tx.ExtHash = newHash(1)
264 desc: "wrong blockchain",
266 vs.blockchainID = *newHash(2)
268 err: errWrongBlockchain,
271 desc: "issuance program failure",
273 iss := txIssuance(t, tx, 0)
274 iss.WitnessArguments[0] = []byte{}
276 err: vm.ErrFalseVMResult,
279 desc: "issuance exthash nonempty",
281 iss := txIssuance(t, tx, 0)
282 iss.ExtHash = newHash(1)
284 err: errNonemptyExtHash,
287 desc: "issuance exthash nonempty, but that's OK",
290 iss := txIssuance(t, tx, 0)
291 iss.ExtHash = newHash(1)
295 desc: "spend control program failure",
297 spend := txSpend(t, tx, 1)
298 spend.WitnessArguments[0] = []byte{}
300 err: vm.ErrFalseVMResult,
303 desc: "mismatched spent source/witness value",
305 spend := txSpend(t, tx, 1)
306 spentOutput := tx.Entries[*spend.SpentOutputId].(*bc.Output)
307 spentOutput.Source.Value = &bc.AssetAmount{
308 AssetId: spend.WitnessDestination.Value.AssetId,
309 Amount: spend.WitnessDestination.Value.Amount + 1,
312 err: errMismatchedValue,
315 desc: "spend exthash nonempty",
317 spend := txSpend(t, tx, 1)
318 spend.ExtHash = newHash(1)
320 err: errNonemptyExtHash,
323 desc: "spend exthash nonempty, but that's OK",
326 spend := txSpend(t, tx, 1)
327 spend.ExtHash = newHash(1)
332 for _, c := range cases {
333 t.Run(c.desc, func(t *testing.T) {
334 fixture = sample(t, nil)
335 tx = legacy.NewTx(*fixture.tx).Tx
336 vs = &validationState{
337 blockchainID: fixture.initialBlockID,
341 gasLeft: uint64(1000),
343 maxGas: uint64(1000),
345 cache: make(map[bc.Hash]error),
347 out := tx.Entries[*tx.ResultIds[0]].(*bc.Output)
348 muxID := out.Source.Ref
349 mux = tx.Entries[*muxID].(*bc.Mux)
354 err := checkValid(vs, tx.TxHeader)
355 if rootErr(err) != c.err {
356 t.Errorf("got error %s, want %s; validationState is:\n%s", err, c.err, spew.Sdump(vs))
362 func TestNoncelessIssuance(t *testing.T) {
363 tx := bctest.NewIssuanceTx(t, bc.EmptyStringHash, func(tx *legacy.Tx) {
364 // Remove the issuance nonce.
365 tx.Inputs[0].TypedInput.(*legacy.IssuanceInput).Nonce = nil
368 _, err := ValidateTx(legacy.MapTx(&tx.TxData), bc.EmptyStringHash)
369 if errors.Root(err) != bc.ErrMissingEntry {
370 t.Fatalf("got %s, want %s", err, bc.ErrMissingEntry)
374 func TestBlockHeaderValid(t *testing.T) {
375 base := bc.NewBlockHeader(1, 1, &bc.Hash{}, 1, &bc.Hash{}, &bc.Hash{}, 0, 0)
376 baseBytes, _ := proto.Marshal(base)
378 var bh bc.BlockHeader
392 for i, c := range cases {
393 t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
394 proto.Unmarshal(baseBytes, &bh)
402 // A txFixture is returned by sample (below) to produce a sample
403 // transaction, which takes a separate, optional _input_ txFixture to
404 // affect the transaction that's built. The components of the
405 // transaction are the fields of txFixture.
406 type txFixture struct {
407 initialBlockID bc.Hash
408 issuanceProg bc.Program
409 issuanceArgs [][]byte
413 txInputs []*legacy.TxInput
414 txOutputs []*legacy.TxOutput
415 txMinTime, txMaxTime uint64
420 // Produces a sample transaction in a txFixture object (see above). A
421 // separate input txFixture can be used to alter the transaction
424 // The output of this function can be used as the input to a
425 // subsequent call to make iterative refinements to a test object.
427 // The default transaction produced is valid and has three inputs:
428 // - an issuance of 10 units
429 // - a spend of 20 units
430 // - a spend of 40 units
431 // and two outputs, one of 25 units and one of 45 units.
432 // All amounts are denominated in the same asset.
434 // The issuance program for the asset requires two numbers as
435 // arguments that add up to 5. The prevout control programs require
436 // two numbers each, adding to 9 and 13, respectively.
438 // The min and max times for the transaction are now +/- one minute.
439 func sample(tb testing.TB, in *txFixture) *txFixture {
445 if result.initialBlockID.IsZero() {
446 result.initialBlockID = *newHash(1)
448 if testutil.DeepEqual(result.issuanceProg, bc.Program{}) {
449 prog, err := vm.Assemble("ADD 5 NUMEQUAL")
453 result.issuanceProg = bc.Program{VmVersion: 1, Code: prog}
455 if len(result.issuanceArgs) == 0 {
456 result.issuanceArgs = [][]byte{[]byte{2}, []byte{3}}
458 if len(result.assetDef) == 0 {
459 result.assetDef = []byte{2}
461 if result.assetID.IsZero() {
462 refdatahash := hashData(result.assetDef)
463 result.assetID = bc.ComputeAssetID(result.issuanceProg.Code, &result.initialBlockID, result.issuanceProg.VmVersion, &refdatahash)
466 if result.txVersion == 0 {
469 if len(result.txInputs) == 0 {
470 cp1, err := vm.Assemble("ADD 9 NUMEQUAL")
474 args1 := [][]byte{[]byte{4}, []byte{5}}
476 cp2, err := vm.Assemble("ADD 13 NUMEQUAL")
480 args2 := [][]byte{[]byte{6}, []byte{7}}
482 result.txInputs = []*legacy.TxInput{
483 legacy.NewIssuanceInput([]byte{3}, 10, []byte{4}, result.initialBlockID, result.issuanceProg.Code, result.issuanceArgs, result.assetDef),
484 legacy.NewSpendInput(args1, *newHash(5), result.assetID, 20, 0, cp1, *newHash(6), []byte{7}),
485 legacy.NewSpendInput(args2, *newHash(8), result.assetID, 40, 0, cp2, *newHash(9), []byte{10}),
488 if len(result.txOutputs) == 0 {
489 cp1, err := vm.Assemble("ADD 17 NUMEQUAL")
493 cp2, err := vm.Assemble("ADD 21 NUMEQUAL")
498 result.txOutputs = []*legacy.TxOutput{
499 legacy.NewTxOutput(result.assetID, 25, cp1, []byte{11}),
500 legacy.NewTxOutput(result.assetID, 45, cp2, []byte{12}),
503 if result.txMinTime == 0 {
504 result.txMinTime = bc.Millis(time.Now().Add(-time.Minute))
506 if result.txMaxTime == 0 {
507 result.txMaxTime = bc.Millis(time.Now().Add(time.Minute))
509 if len(result.txRefData) == 0 {
510 result.txRefData = []byte{13}
513 result.tx = &legacy.TxData{
514 Version: result.txVersion,
515 Inputs: result.txInputs,
516 Outputs: result.txOutputs,
517 MinTime: result.txMinTime,
518 MaxTime: result.txMaxTime,
519 ReferenceData: result.txRefData,
525 // Like errors.Root, but also unwraps vm.Error objects.
526 func rootErr(e error) error {
529 if e2, ok := e.(vm.Error); ok {
537 func hashData(data []byte) bc.Hash {
539 sha3pool.Sum256(b32[:], data)
540 return bc.NewHash(b32)
543 func newHash(n byte) *bc.Hash {
544 h := bc.NewHash([32]byte{n})
548 func newAssetID(n byte) *bc.AssetID {
549 a := bc.NewAssetID([32]byte{n})
553 func txIssuance(t *testing.T, tx *bc.Tx, index int) *bc.Issuance {
554 id := tx.InputIDs[index]
555 res, err := tx.Issuance(id)
562 func txSpend(t *testing.T, tx *bc.Tx, index int) *bc.Spend {
563 id := tx.InputIDs[index]
564 res, err := tx.Spend(id)