7 "github.com/davecgh/go-spew/spew"
9 "github.com/bytom/consensus"
10 "github.com/bytom/crypto/sha3pool"
11 "github.com/bytom/errors"
12 "github.com/bytom/protocol/bc"
13 "github.com/bytom/protocol/bc/types"
14 "github.com/bytom/protocol/vm"
15 "github.com/bytom/protocol/vm/vmutil"
16 "github.com/bytom/testutil"
20 spew.Config.DisableMethods = true
23 func TestGasStatus(t *testing.T) {
27 f func(*GasState) error
37 GasLeft: 10000 / consensus.VMGasRate,
41 f: func(input *GasState) error {
42 return input.setGas(10000, 0)
57 f: func(input *GasState) error {
58 return input.setGas(-10000, 0)
64 GasLeft: consensus.DefaultGasCredit,
71 BTMValue: 80000000000,
73 f: func(input *GasState) error {
74 return input.setGas(80000000000, 0)
89 f: func(input *GasState) error {
90 return input.updateUsage(-1)
105 f: func(input *GasState) error {
106 return input.updateUsage(9999)
112 for i, c := range cases {
115 if rootErr(err) != c.err {
116 t.Errorf("case %d: got error %s, want %s", i, err, c.err)
117 } else if *c.input != *c.output {
118 t.Errorf("case %d: gasStatus %v, want %v;", i, c.input, c.output)
123 func TestOverflow(t *testing.T) {
124 sourceID := &bc.Hash{V0: 9999}
125 ctrlProgram := []byte{byte(vm.OP_TRUE)}
126 newTx := func(inputs []uint64, outputs []uint64) *bc.Tx {
127 txInputs := make([]*types.TxInput, 0, len(inputs))
128 txOutputs := make([]*types.TxOutput, 0, len(outputs))
130 for _, amount := range inputs {
131 txInput := types.NewSpendInput(nil, *sourceID, *consensus.BTMAssetID, amount, 0, ctrlProgram)
132 txInputs = append(txInputs, txInput)
135 for _, amount := range outputs {
136 txOutput := types.NewTxOutput(*consensus.BTMAssetID, amount, ctrlProgram)
137 txOutputs = append(txOutputs, txOutput)
140 txData := &types.TxData{
147 return types.MapTx(txData)
156 inputs: []uint64{math.MaxUint64, 1},
157 outputs: []uint64{0},
161 inputs: []uint64{math.MaxUint64, math.MaxUint64},
162 outputs: []uint64{0},
166 inputs: []uint64{math.MaxUint64, math.MaxUint64 - 1},
167 outputs: []uint64{0},
171 inputs: []uint64{math.MaxInt64, 1},
172 outputs: []uint64{0},
176 inputs: []uint64{math.MaxInt64, math.MaxInt64},
177 outputs: []uint64{0},
181 inputs: []uint64{math.MaxInt64, math.MaxInt64 - 1},
182 outputs: []uint64{0},
187 outputs: []uint64{math.MaxUint64},
192 outputs: []uint64{math.MaxInt64},
193 err: errGasCalculate,
196 inputs: []uint64{math.MaxInt64 - 1},
197 outputs: []uint64{math.MaxInt64},
198 err: errGasCalculate,
202 for i, c := range cases {
203 tx := newTx(c.inputs, c.outputs)
204 if _, err := ValidateTx(tx, mockBlock()); rootErr(err) != c.err {
205 t.Fatalf("case %d test failed, want %s, have %s", i, c.err, rootErr(err))
210 func TestTxValidation(t *testing.T) {
216 // the mux from tx, pulled out for convenience
220 addCoinbase := func(assetID *bc.AssetID, amount uint64, arbitrary []byte) {
221 coinbase := bc.NewCoinbase(arbitrary)
222 txOutput := types.NewTxOutput(*assetID, amount, []byte{byte(vm.OP_TRUE)})
223 muxID := getMuxID(tx)
224 coinbase.SetDestination(muxID, &txOutput.AssetAmount, uint64(len(mux.Sources)))
225 coinbaseID := bc.EntryID(coinbase)
226 tx.Entries[coinbaseID] = coinbase
228 mux.Sources = append(mux.Sources, &bc.ValueSource{
230 Value: &txOutput.AssetAmount,
233 src := &bc.ValueSource{
235 Value: &txOutput.AssetAmount,
236 Position: uint64(len(tx.ResultIds)),
238 prog := &bc.Program{txOutput.VMVersion, txOutput.ControlProgram}
239 output := bc.NewOutput(src, prog, uint64(len(tx.ResultIds)))
240 outputID := bc.EntryID(output)
241 tx.Entries[outputID] = output
243 dest := &bc.ValueDestination{
248 mux.WitnessDestinations = append(mux.WitnessDestinations, dest)
249 tx.ResultIds = append(tx.ResultIds, &outputID)
250 vs.block.Transactions = append(vs.block.Transactions, vs.tx)
254 desc string // description of the test case
255 f func() // function to adjust tx, vs, and/or mux
256 err error // expected error
262 desc: "unbalanced mux amounts",
264 mux.Sources[0].Value.Amount++
265 iss := tx.Entries[*mux.Sources[0].Ref].(*bc.Issuance)
266 iss.WitnessDestination.Value.Amount++
271 desc: "unbalanced mux amounts",
273 mux.WitnessDestinations[0].Value.Amount++
278 desc: "balanced mux amounts",
280 mux.Sources[1].Value.Amount++
281 mux.WitnessDestinations[0].Value.Amount++
286 desc: "overflowing mux source amounts",
288 mux.Sources[0].Value.Amount = math.MaxInt64
289 iss := tx.Entries[*mux.Sources[0].Ref].(*bc.Issuance)
290 iss.WitnessDestination.Value.Amount = math.MaxInt64
295 desc: "underflowing mux destination amounts",
297 mux.WitnessDestinations[0].Value.Amount = math.MaxInt64
298 out := tx.Entries[*mux.WitnessDestinations[0].Ref].(*bc.Output)
299 out.Source.Value.Amount = math.MaxInt64
300 mux.WitnessDestinations[1].Value.Amount = math.MaxInt64
301 out = tx.Entries[*mux.WitnessDestinations[1].Ref].(*bc.Output)
302 out.Source.Value.Amount = math.MaxInt64
307 desc: "unbalanced mux assets",
309 mux.Sources[1].Value.AssetId = newAssetID(255)
310 sp := tx.Entries[*mux.Sources[1].Ref].(*bc.Spend)
311 sp.WitnessDestination.Value.AssetId = newAssetID(255)
316 desc: "mismatched output source / mux dest position",
318 tx.Entries[*tx.ResultIds[0]].(*bc.Output).Source.Position = 1
320 err: errMismatchedPosition,
323 desc: "mismatched output source and mux dest",
325 // For this test, it's necessary to construct a mostly
326 // identical second transaction in order to get a similar but
327 // not equal output entry for the mux to falsely point
328 // to. That entry must be added to the first tx's Entries map.
329 fixture2 := sample(t, fixture)
330 tx2 := types.NewTx(*fixture2.tx).Tx
331 out2ID := tx2.ResultIds[0]
332 out2 := tx2.Entries[*out2ID].(*bc.Output)
333 tx.Entries[*out2ID] = out2
334 mux.WitnessDestinations[0].Ref = out2ID
336 err: errMismatchedReference,
339 desc: "invalid mux destination position",
341 mux.WitnessDestinations[0].Position = 1
346 desc: "mismatched mux dest value / output source value",
348 outID := tx.ResultIds[0]
349 out := tx.Entries[*outID].(*bc.Output)
350 mux.WitnessDestinations[0].Value = &bc.AssetAmount{
351 AssetId: out.Source.Value.AssetId,
352 Amount: out.Source.Value.Amount + 1,
354 mux.Sources[0].Value.Amount++ // the mux must still balance
356 err: errMismatchedValue,
359 desc: "empty tx results",
363 err: errEmptyResults,
366 desc: "empty tx results, but that's OK",
373 desc: "issuance program failure",
375 iss := txIssuance(t, tx, 0)
376 iss.WitnessArguments[0] = []byte{}
378 err: vm.ErrFalseVMResult,
381 desc: "spend control program failure",
383 spend := txSpend(t, tx, 1)
384 spend.WitnessArguments[0] = []byte{}
386 err: vm.ErrFalseVMResult,
389 desc: "mismatched spent source/witness value",
391 spend := txSpend(t, tx, 1)
392 spentOutput := tx.Entries[*spend.SpentOutputId].(*bc.Output)
393 spentOutput.Source.Value = &bc.AssetAmount{
394 AssetId: spend.WitnessDestination.Value.AssetId,
395 Amount: spend.WitnessDestination.Value.Amount + 1,
398 err: errMismatchedValue,
401 desc: "gas out of limit",
403 vs.tx.SerializedSize = 10000000
405 err: errOverGasCredit,
408 desc: "can't find gas spend input in entries",
410 spendID := mux.Sources[len(mux.Sources)-1].Ref
411 delete(tx.Entries, *spendID)
412 mux.Sources = mux.Sources[:len(mux.Sources)-1]
414 err: bc.ErrMissingEntry,
417 desc: "no gas spend input",
419 spendID := mux.Sources[len(mux.Sources)-1].Ref
420 delete(tx.Entries, *spendID)
421 mux.Sources = mux.Sources[:len(mux.Sources)-1]
423 vs.gasStatus.GasLeft = 0
425 err: vm.ErrRunLimitExceeded,
428 desc: "no gas spend input, but set gas left, so it's ok",
430 spendID := mux.Sources[len(mux.Sources)-1].Ref
431 delete(tx.Entries, *spendID)
432 mux.Sources = mux.Sources[:len(mux.Sources)-1]
438 desc: "mismatched gas spend input destination amount/prevout source amount",
440 spendID := mux.Sources[len(mux.Sources)-1].Ref
441 spend := tx.Entries[*spendID].(*bc.Spend)
442 spend.WitnessDestination.Value = &bc.AssetAmount{
443 AssetId: spend.WitnessDestination.Value.AssetId,
444 Amount: spend.WitnessDestination.Value.Amount + 1,
447 err: errMismatchedValue,
450 desc: "mismatched witness asset destination",
452 issuanceID := mux.Sources[0].Ref
453 issuance := tx.Entries[*issuanceID].(*bc.Issuance)
454 issuance.WitnessAssetDefinition.Data = &bc.Hash{V0: 9999}
456 err: errMismatchedAssetID,
459 desc: "issuance witness position greater than length of mux sources",
461 issuanceID := mux.Sources[0].Ref
462 issuance := tx.Entries[*issuanceID].(*bc.Issuance)
463 issuance.WitnessDestination.Position = uint64(len(mux.Sources) + 1)
468 desc: "normal coinbase tx",
470 addCoinbase(consensus.BTMAssetID, 100000, nil)
475 desc: "invalid coinbase tx asset id",
477 addCoinbase(&bc.AssetID{V1: 100}, 100000, nil)
479 err: errWrongCoinbaseAsset,
482 desc: "coinbase tx is not first tx in block",
484 addCoinbase(consensus.BTMAssetID, 100000, nil)
485 vs.block.Transactions[0] = nil
487 err: errWrongCoinbaseTransaction,
490 desc: "coinbase arbitrary size out of limit",
492 arbitrary := make([]byte, consensus.CoinbaseArbitrarySizeLimit+1)
493 addCoinbase(consensus.BTMAssetID, 100000, arbitrary)
495 err: errCoinbaseArbitraryOversize,
498 desc: "normal retirement output",
500 outputID := tx.ResultIds[0]
501 output := tx.Entries[*outputID].(*bc.Output)
502 retirement := bc.NewRetirement(output.Source, output.Ordinal)
503 retirementID := bc.EntryID(retirement)
504 tx.Entries[retirementID] = retirement
505 delete(tx.Entries, *outputID)
506 tx.ResultIds[0] = &retirementID
507 mux.WitnessDestinations[0].Ref = &retirementID
512 desc: "ordinal doesn't matter for prevouts",
514 spend := txSpend(t, tx, 1)
515 prevout := tx.Entries[*spend.SpentOutputId].(*bc.Output)
516 newPrevout := bc.NewOutput(prevout.Source, prevout.ControlProgram, 10)
517 hash := bc.EntryID(newPrevout)
518 spend.SpentOutputId = &hash
523 desc: "mux witness destination have no source",
525 dest := &bc.ValueDestination{
526 Value: &bc.AssetAmount{
527 AssetId: &bc.AssetID{V2: 1000},
530 Ref: mux.WitnessDestinations[0].Ref,
533 mux.WitnessDestinations = append(mux.WitnessDestinations, dest)
539 for i, c := range cases {
540 t.Run(c.desc, func(t *testing.T) {
541 fixture = sample(t, nil)
542 tx = types.NewTx(*fixture.tx).Tx
543 vs = &validationState{
547 gasStatus: &GasState{
548 GasLeft: int64(80000),
551 cache: make(map[bc.Hash]error),
553 muxID := getMuxID(tx)
554 mux = tx.Entries[*muxID].(*bc.Mux)
559 err := checkValid(vs, tx.TxHeader)
561 if rootErr(err) != c.err {
562 t.Errorf("case #%d (%s) got error %s, want %s; validationState is:\n%s", i, c.desc, err, c.err, spew.Sdump(vs))
568 func TestCoinbase(t *testing.T) {
569 CbTx := mockCoinbaseTx(5000000000)
578 BlockHeader: &bc.BlockHeader{
581 Transactions: []*bc.Tx{CbTx},
589 for i, c := range cases {
590 gasStatus, err := ValidateTx(c.tx, c.block)
592 if rootErr(err) != c.err {
593 t.Errorf("#%d got error %s, want %s", i, err, c.err)
595 if c.GasValid != gasStatus.GasValid {
596 t.Errorf("#%d got GasValid %t, want %t", i, gasStatus.GasValid, c.GasValid)
601 func TestTimeRange(t *testing.T) {
619 timeRange: 1521625824,
625 BlockHeader: &bc.BlockHeader{
627 Timestamp: 1521625823,
631 tx := types.MapTx(&types.TxData{
634 Inputs: []*types.TxInput{
637 Outputs: []*types.TxOutput{
638 types.NewTxOutput(*consensus.BTMAssetID, 1, []byte{0x6a}),
642 for i, c := range cases {
643 tx.TimeRange = c.timeRange
644 if _, err := ValidateTx(tx, block); (err != nil) != c.err {
645 t.Errorf("#%d got error %t, want %t", i, !c.err, c.err)
650 // A txFixture is returned by sample (below) to produce a sample
651 // transaction, which takes a separate, optional _input_ txFixture to
652 // affect the transaction that's built. The components of the
653 // transaction are the fields of txFixture.
654 type txFixture struct {
655 initialBlockID bc.Hash
656 issuanceProg bc.Program
657 issuanceArgs [][]byte
661 txInputs []*types.TxInput
662 txOutputs []*types.TxOutput
666 // Produces a sample transaction in a txFixture object (see above). A
667 // separate input txFixture can be used to alter the transaction
670 // The output of this function can be used as the input to a
671 // subsequent call to make iterative refinements to a test object.
673 // The default transaction produced is valid and has three inputs:
674 // - an issuance of 10 units
675 // - a spend of 20 units
676 // - a spend of 40 units
677 // and two outputs, one of 25 units and one of 45 units.
678 // All amounts are denominated in the same asset.
680 // The issuance program for the asset requires two numbers as
681 // arguments that add up to 5. The prevout control programs require
682 // two numbers each, adding to 9 and 13, respectively.
684 // The min and max times for the transaction are now +/- one minute.
685 func sample(tb testing.TB, in *txFixture) *txFixture {
691 if result.initialBlockID.IsZero() {
692 result.initialBlockID = *newHash(1)
694 if testutil.DeepEqual(result.issuanceProg, bc.Program{}) {
695 prog, err := vm.Assemble("ADD 5 NUMEQUAL")
699 result.issuanceProg = bc.Program{VmVersion: 1, Code: prog}
701 if len(result.issuanceArgs) == 0 {
702 result.issuanceArgs = [][]byte{[]byte{2}, []byte{3}}
704 if len(result.assetDef) == 0 {
705 result.assetDef = []byte{2}
707 if result.assetID.IsZero() {
708 refdatahash := hashData(result.assetDef)
709 result.assetID = bc.ComputeAssetID(result.issuanceProg.Code, result.issuanceProg.VmVersion, &refdatahash)
712 if result.txVersion == 0 {
715 if len(result.txInputs) == 0 {
716 cp1, err := vm.Assemble("ADD 9 NUMEQUAL")
720 args1 := [][]byte{[]byte{4}, []byte{5}}
722 cp2, err := vm.Assemble("ADD 13 NUMEQUAL")
726 args2 := [][]byte{[]byte{6}, []byte{7}}
728 result.txInputs = []*types.TxInput{
729 types.NewIssuanceInput([]byte{3}, 10, result.issuanceProg.Code, result.issuanceArgs, result.assetDef),
730 types.NewSpendInput(args1, *newHash(5), result.assetID, 20, 0, cp1),
731 types.NewSpendInput(args2, *newHash(8), result.assetID, 40, 0, cp2),
735 result.txInputs = append(result.txInputs, mockGasTxInput())
737 if len(result.txOutputs) == 0 {
738 cp1, err := vm.Assemble("ADD 17 NUMEQUAL")
742 cp2, err := vm.Assemble("ADD 21 NUMEQUAL")
747 result.txOutputs = []*types.TxOutput{
748 types.NewTxOutput(result.assetID, 25, cp1),
749 types.NewTxOutput(result.assetID, 45, cp2),
753 result.tx = &types.TxData{
754 Version: result.txVersion,
755 Inputs: result.txInputs,
756 Outputs: result.txOutputs,
762 func mockBlock() *bc.Block {
764 BlockHeader: &bc.BlockHeader{
770 func mockCoinbaseTx(amount uint64) *bc.Tx {
771 cp, _ := vmutil.DefaultCoinbaseProgram()
772 return types.MapTx(&types.TxData{
774 Inputs: []*types.TxInput{
775 types.NewCoinbaseInput(nil),
777 Outputs: []*types.TxOutput{
778 types.NewTxOutput(*consensus.BTMAssetID, amount, cp),
783 func mockGasTxInput() *types.TxInput {
784 cp, _ := vmutil.DefaultCoinbaseProgram()
785 return types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp)
788 // Like errors.Root, but also unwraps vm.Error objects.
789 func rootErr(e error) error {
792 if e2, ok := e.(vm.Error); ok {
800 func hashData(data []byte) bc.Hash {
802 sha3pool.Sum256(b32[:], data)
803 return bc.NewHash(b32)
806 func newHash(n byte) *bc.Hash {
807 h := bc.NewHash([32]byte{n})
811 func newAssetID(n byte) *bc.AssetID {
812 a := bc.NewAssetID([32]byte{n})
816 func txIssuance(t *testing.T, tx *bc.Tx, index int) *bc.Issuance {
817 id := tx.InputIDs[index]
818 res, err := tx.Issuance(id)
825 func txSpend(t *testing.T, tx *bc.Tx, index int) *bc.Spend {
826 id := tx.InputIDs[index]
827 res, err := tx.Spend(id)
834 func getMuxID(tx *bc.Tx) *bc.Hash {
835 out := tx.Entries[*tx.ResultIds[0]]
836 switch result := out.(type) {
838 return result.Source.Ref
840 return result.Source.Ref