7 "github.com/davecgh/go-spew/spew"
9 "github.com/vapor/consensus"
10 "github.com/vapor/crypto/sha3pool"
11 "github.com/vapor/errors"
12 "github.com/vapor/protocol/bc"
13 "github.com/vapor/protocol/bc/types"
14 "github.com/vapor/protocol/vm"
15 "github.com/vapor/protocol/vm/vmutil"
16 "github.com/vapor/testutil"
20 spew.Config.DisableMethods = true
23 func TestGasStatus(t *testing.T) {
27 f func(*GasState) error
37 GasLeft: 10000/consensus.ActiveNetParams.VMGasRate + consensus.ActiveNetParams.DefaultGasCredit,
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.ActiveNetParams.DefaultGasCredit,
71 BTMValue: 80000000000,
73 f: func(input *GasState) error {
74 return input.setGas(80000000000, 0)
80 GasLeft: consensus.ActiveNetParams.DefaultGasCredit,
87 BTMValue: math.MaxInt64,
89 f: func(input *GasState) error {
90 return input.setGas(math.MaxInt64, 0)
105 f: func(input *GasState) error {
106 return input.updateUsage(-1)
108 err: ErrGasCalculate,
121 f: func(input *GasState) error {
122 return input.updateUsage(9999)
137 f: func(input *GasState) error {
138 return input.updateUsage(math.MaxInt64)
140 err: ErrGasCalculate,
155 f: func(input *GasState) error {
156 return input.setGasValid()
173 f: func(input *GasState) error {
174 return input.setGasValid()
176 err: ErrGasCalculate,
181 GasUsed: math.MaxInt64,
191 f: func(input *GasState) error {
192 return input.setGasValid()
194 err: ErrGasCalculate,
198 GasLeft: math.MinInt64,
209 f: func(input *GasState) error {
210 return input.setGasValid()
212 err: ErrGasCalculate,
216 for i, c := range cases {
219 if rootErr(err) != c.err {
220 t.Errorf("case %d: got error %s, want %s", i, err, c.err)
221 } else if *c.input != *c.output {
222 t.Errorf("case %d: gasStatus %v, want %v;", i, c.input, c.output)
227 func TestOverflow(t *testing.T) {
228 sourceID := &bc.Hash{V0: 9999}
229 ctrlProgram := []byte{byte(vm.OP_TRUE)}
230 newTx := func(inputs []uint64, outputs []uint64) *bc.Tx {
231 txInputs := make([]*types.TxInput, 0, len(inputs))
232 txOutputs := make([]*types.TxOutput, 0, len(outputs))
234 for i, amount := range inputs {
235 txInput := types.NewSpendInput(nil, *sourceID, *consensus.BTMAssetID, amount, uint64(i), ctrlProgram)
236 txInputs = append(txInputs, txInput)
239 for _, amount := range outputs {
240 txOutput := types.NewIntraChainOutput(*consensus.BTMAssetID, amount, ctrlProgram)
241 txOutputs = append(txOutputs, txOutput)
244 txData := &types.TxData{
251 return types.MapTx(txData)
260 inputs: []uint64{math.MaxUint64, 1},
261 outputs: []uint64{0},
265 inputs: []uint64{math.MaxUint64, math.MaxUint64},
266 outputs: []uint64{0},
270 inputs: []uint64{math.MaxUint64, math.MaxUint64 - 1},
271 outputs: []uint64{0},
275 inputs: []uint64{math.MaxInt64, 1},
276 outputs: []uint64{0},
280 inputs: []uint64{math.MaxInt64, math.MaxInt64},
281 outputs: []uint64{0},
285 inputs: []uint64{math.MaxInt64, math.MaxInt64 - 1},
286 outputs: []uint64{0},
291 outputs: []uint64{math.MaxUint64},
296 outputs: []uint64{math.MaxInt64},
297 err: ErrGasCalculate,
300 inputs: []uint64{math.MaxInt64 - 1},
301 outputs: []uint64{math.MaxInt64},
302 err: ErrGasCalculate,
306 for i, c := range cases {
307 tx := newTx(c.inputs, c.outputs)
308 if _, err := ValidateTx(tx, mockBlock()); rootErr(err) != c.err {
309 t.Fatalf("case %d test failed, want %s, have %s", i, c.err, rootErr(err))
314 func TestTxValidation(t *testing.T) {
320 // the mux from tx, pulled out for convenience
324 addCoinbase := func(assetID *bc.AssetID, amount uint64, arbitrary []byte) {
325 coinbase := bc.NewCoinbase(arbitrary)
326 txOutput := types.NewIntraChainOutput(*assetID, amount, []byte{byte(vm.OP_TRUE)})
327 assetAmount := txOutput.AssetAmount()
328 muxID := getMuxID(tx)
329 coinbase.SetDestination(muxID, &assetAmount, uint64(len(mux.Sources)))
330 coinbaseID := bc.EntryID(coinbase)
331 tx.Entries[coinbaseID] = coinbase
333 mux.Sources = append(mux.Sources, &bc.ValueSource{
338 src := &bc.ValueSource{
341 Position: uint64(len(tx.ResultIds)),
343 prog := &bc.Program{txOutput.VMVersion(), txOutput.ControlProgram()}
344 output := bc.NewIntraChainOutput(src, prog, uint64(len(tx.ResultIds)))
345 outputID := bc.EntryID(output)
346 tx.Entries[outputID] = output
348 dest := &bc.ValueDestination{
353 mux.WitnessDestinations = append(mux.WitnessDestinations, dest)
354 tx.ResultIds = append(tx.ResultIds, &outputID)
355 vs.block.Transactions = append(vs.block.Transactions, vs.tx)
359 desc string // description of the test case
360 f func() // function to adjust tx, vs, and/or mux
361 err error // expected error
367 desc: "unbalanced mux amounts",
369 mux.WitnessDestinations[0].Value.Amount++
374 desc: "balanced mux amounts",
376 mux.Sources[1].Value.Amount++
377 mux.WitnessDestinations[0].Value.Amount++
382 desc: "underflowing mux destination amounts",
384 mux.WitnessDestinations[0].Value.Amount = math.MaxInt64
385 out := tx.Entries[*mux.WitnessDestinations[0].Ref].(*bc.IntraChainOutput)
386 out.Source.Value.Amount = math.MaxInt64
387 mux.WitnessDestinations[1].Value.Amount = math.MaxInt64
388 out = tx.Entries[*mux.WitnessDestinations[1].Ref].(*bc.IntraChainOutput)
389 out.Source.Value.Amount = math.MaxInt64
394 desc: "unbalanced mux assets",
396 mux.Sources[1].Value.AssetId = newAssetID(255)
397 sp := tx.Entries[*mux.Sources[1].Ref].(*bc.Spend)
398 sp.WitnessDestination.Value.AssetId = newAssetID(255)
403 desc: "mismatched output source / mux dest position",
405 tx.Entries[*tx.ResultIds[0]].(*bc.IntraChainOutput).Source.Position = 1
407 err: ErrMismatchedPosition,
410 desc: "mismatched input dest / mux source position",
412 mux.Sources[0].Position = 1
414 err: ErrMismatchedPosition,
417 desc: "mismatched output source and mux dest",
419 // For this test, it's necessary to construct a mostly
420 // identical second transaction in order to get a similar but
421 // not equal output entry for the mux to falsely point
422 // to. That entry must be added to the first tx's Entries map.
423 fixture2 := sample(t, fixture)
424 tx2 := types.NewTx(*fixture2.tx).Tx
425 out2ID := tx2.ResultIds[0]
426 out2 := tx2.Entries[*out2ID].(*bc.IntraChainOutput)
427 tx.Entries[*out2ID] = out2
428 mux.WitnessDestinations[0].Ref = out2ID
430 err: ErrMismatchedReference,
433 desc: "invalid mux destination position",
435 mux.WitnessDestinations[0].Position = 1
440 desc: "mismatched mux dest value / output source value",
442 outID := tx.ResultIds[0]
443 out := tx.Entries[*outID].(*bc.IntraChainOutput)
444 mux.WitnessDestinations[0].Value = &bc.AssetAmount{
445 AssetId: out.Source.Value.AssetId,
446 Amount: out.Source.Value.Amount + 1,
448 mux.Sources[0].Value.Amount++ // the mux must still balance
450 err: ErrMismatchedValue,
453 desc: "empty tx results",
457 err: ErrEmptyResults,
460 desc: "empty tx results, but that's OK",
467 desc: "spend control program failure",
469 spend := txSpend(t, tx, 1)
470 spend.WitnessArguments[0] = []byte{}
472 err: vm.ErrFalseVMResult,
475 desc: "mismatched spent source/witness value",
477 spend := txSpend(t, tx, 1)
478 spentOutput := tx.Entries[*spend.SpentOutputId].(*bc.IntraChainOutput)
479 spentOutput.Source.Value = &bc.AssetAmount{
480 AssetId: spend.WitnessDestination.Value.AssetId,
481 Amount: spend.WitnessDestination.Value.Amount + 1,
484 err: ErrMismatchedValue,
487 desc: "gas out of limit",
489 vs.tx.SerializedSize = 10000000
491 err: ErrOverGasCredit,
494 desc: "can't find gas spend input in entries",
496 spendID := mux.Sources[len(mux.Sources)-1].Ref
497 delete(tx.Entries, *spendID)
498 mux.Sources = mux.Sources[:len(mux.Sources)-1]
500 err: bc.ErrMissingEntry,
503 desc: "normal check with no gas spend input",
505 spendID := mux.Sources[len(mux.Sources)-1].Ref
506 delete(tx.Entries, *spendID)
507 mux.Sources = mux.Sources[:len(mux.Sources)-1]
509 vs.gasStatus.GasLeft = 0
514 desc: "no gas spend input, but set gas left, so it's ok",
516 spendID := mux.Sources[len(mux.Sources)-1].Ref
517 delete(tx.Entries, *spendID)
518 mux.Sources = mux.Sources[:len(mux.Sources)-1]
524 desc: "mismatched gas spend input destination amount/prevout source amount",
526 spendID := mux.Sources[len(mux.Sources)-1].Ref
527 spend := tx.Entries[*spendID].(*bc.Spend)
528 spend.WitnessDestination.Value = &bc.AssetAmount{
529 AssetId: spend.WitnessDestination.Value.AssetId,
530 Amount: spend.WitnessDestination.Value.Amount + 1,
533 err: ErrMismatchedValue,
536 desc: "normal coinbase tx",
538 addCoinbase(consensus.BTMAssetID, 100000, nil)
543 desc: "invalid coinbase tx asset id",
545 addCoinbase(&bc.AssetID{V1: 100}, 100000, nil)
547 err: ErrWrongCoinbaseAsset,
550 desc: "coinbase tx is not first tx in block",
552 addCoinbase(consensus.BTMAssetID, 100000, nil)
553 vs.block.Transactions[0] = nil
555 err: ErrWrongCoinbaseTransaction,
558 desc: "coinbase arbitrary size out of limit",
560 arbitrary := make([]byte, consensus.ActiveNetParams.CoinbaseArbitrarySizeLimit+1)
561 addCoinbase(consensus.BTMAssetID, 100000, arbitrary)
563 err: ErrCoinbaseArbitraryOversize,
566 desc: "normal retirement output",
568 outputID := tx.ResultIds[0]
569 output := tx.Entries[*outputID].(*bc.IntraChainOutput)
570 retirement := bc.NewRetirement(output.Source, output.Ordinal)
571 retirementID := bc.EntryID(retirement)
572 tx.Entries[retirementID] = retirement
573 delete(tx.Entries, *outputID)
574 tx.ResultIds[0] = &retirementID
575 mux.WitnessDestinations[0].Ref = &retirementID
580 desc: "ordinal doesn't matter for prevouts",
582 spend := txSpend(t, tx, 1)
583 prevout := tx.Entries[*spend.SpentOutputId].(*bc.IntraChainOutput)
584 newPrevout := bc.NewIntraChainOutput(prevout.Source, prevout.ControlProgram, 10)
585 hash := bc.EntryID(newPrevout)
586 spend.SpentOutputId = &hash
591 desc: "mux witness destination have no source",
593 dest := &bc.ValueDestination{
594 Value: &bc.AssetAmount{
595 AssetId: &bc.AssetID{V2: 1000},
598 Ref: mux.WitnessDestinations[0].Ref,
601 mux.WitnessDestinations = append(mux.WitnessDestinations, dest)
607 for i, c := range cases {
608 t.Run(c.desc, func(t *testing.T) {
609 fixture = sample(t, nil)
610 tx = types.NewTx(*fixture.tx).Tx
611 vs = &validationState{
615 gasStatus: &GasState{
616 GasLeft: int64(80000),
619 cache: make(map[bc.Hash]error),
621 muxID := getMuxID(tx)
622 mux = tx.Entries[*muxID].(*bc.Mux)
627 err := checkValid(vs, tx.TxHeader)
629 if rootErr(err) != c.err {
630 t.Errorf("case #%d (%s) got error %s, want %s; validationState is:\n%s", i, c.desc, err, c.err, spew.Sdump(vs))
636 // TestCoinbase test the coinbase transaction is valid (txtest#1016)
637 func TestCoinbase(t *testing.T) {
638 cp, _ := vmutil.DefaultCoinbaseProgram()
639 retire, _ := vmutil.RetireProgram([]byte{})
640 CbTx := types.MapTx(&types.TxData{
642 Inputs: []*types.TxInput{
643 types.NewCoinbaseInput(nil),
645 Outputs: []*types.TxOutput{
646 types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
658 BlockHeader: &bc.BlockHeader{Height: 666},
659 Transactions: []*bc.Tx{CbTx},
667 BlockHeader: &bc.BlockHeader{Height: 666},
668 Transactions: []*bc.Tx{
670 types.MapTx(&types.TxData{
672 Inputs: []*types.TxInput{
673 types.NewCoinbaseInput(nil),
675 Outputs: []*types.TxOutput{
676 types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
683 err: ErrWrongCoinbaseTransaction,
687 BlockHeader: &bc.BlockHeader{Height: 666},
688 Transactions: []*bc.Tx{
690 types.MapTx(&types.TxData{
692 Inputs: []*types.TxInput{
693 types.NewCoinbaseInput(nil),
694 types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp),
696 Outputs: []*types.TxOutput{
697 types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
698 types.NewIntraChainOutput(*consensus.BTMAssetID, 90000000, cp),
705 err: ErrWrongCoinbaseTransaction,
709 BlockHeader: &bc.BlockHeader{Height: 666},
710 Transactions: []*bc.Tx{
712 types.MapTx(&types.TxData{
714 Inputs: []*types.TxInput{
715 types.NewCoinbaseInput(nil),
716 types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp),
718 Outputs: []*types.TxOutput{
719 types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
720 types.NewIntraChainOutput(*consensus.BTMAssetID, 90000000, cp),
727 err: ErrWrongCoinbaseTransaction,
731 BlockHeader: &bc.BlockHeader{Height: 666},
732 Transactions: []*bc.Tx{
733 types.MapTx(&types.TxData{
735 Inputs: []*types.TxInput{
736 types.NewCoinbaseInput(nil),
737 types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp),
739 Outputs: []*types.TxOutput{
740 types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
741 types.NewIntraChainOutput(*consensus.BTMAssetID, 90000000, cp),
752 BlockHeader: &bc.BlockHeader{Height: 666},
753 Transactions: []*bc.Tx{
754 types.MapTx(&types.TxData{
756 Inputs: []*types.TxInput{
757 types.NewCoinbaseInput(nil),
758 types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, retire),
760 Outputs: []*types.TxOutput{
761 types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp),
762 types.NewIntraChainOutput(*consensus.BTMAssetID, 90000000, cp),
773 for i, c := range cases {
774 gasStatus, err := ValidateTx(c.block.Transactions[c.txIndex], c.block)
776 if rootErr(err) != c.err {
777 t.Errorf("#%d got error %s, want %s", i, err, c.err)
779 if c.GasValid != gasStatus.GasValid {
780 t.Errorf("#%d got GasValid %t, want %t", i, gasStatus.GasValid, c.GasValid)
785 // TestTimeRange test the checkTimeRange function (txtest#1004)
786 func TestTimeRange(t *testing.T) {
804 timeRange: 1521625824,
810 BlockHeader: &bc.BlockHeader{
812 Timestamp: 1521625823000,
816 tx := types.MapTx(&types.TxData{
819 Inputs: []*types.TxInput{
822 Outputs: []*types.TxOutput{
823 types.NewIntraChainOutput(*consensus.BTMAssetID, 1, []byte{0x6a}),
827 for i, c := range cases {
828 tx.TimeRange = c.timeRange
829 if _, err := ValidateTx(tx, block); (err != nil) != c.err {
830 t.Errorf("#%d got error %t, want %t", i, !c.err, c.err)
835 func TestValidateTxVersion(t *testing.T) {
842 desc: "tx version greater than 1 (txtest#1001)",
844 BlockHeader: &bc.BlockHeader{Version: 1},
845 Transactions: []*bc.Tx{
846 {TxHeader: &bc.TxHeader{Version: 2}},
852 desc: "tx version equals 0 (txtest#1002)",
854 BlockHeader: &bc.BlockHeader{Version: 1},
855 Transactions: []*bc.Tx{
856 {TxHeader: &bc.TxHeader{Version: 0}},
862 desc: "tx version equals max uint64 (txtest#1003)",
864 BlockHeader: &bc.BlockHeader{Version: 1},
865 Transactions: []*bc.Tx{
866 {TxHeader: &bc.TxHeader{Version: math.MaxUint64}},
873 for i, c := range cases {
874 if _, err := ValidateTx(c.block.Transactions[0], c.block); rootErr(err) != c.err {
875 t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, err, c.err)
880 // A txFixture is returned by sample (below) to produce a sample
881 // transaction, which takes a separate, optional _input_ txFixture to
882 // affect the transaction that's built. The components of the
883 // transaction are the fields of txFixture.
884 type txFixture struct {
885 initialBlockID bc.Hash
886 issuanceProg bc.Program
887 issuanceArgs [][]byte
891 txInputs []*types.TxInput
892 txOutputs []*types.TxOutput
896 // Produces a sample transaction in a txFixture object (see above). A
897 // separate input txFixture can be used to alter the transaction
900 // The output of this function can be used as the input to a
901 // subsequent call to make iterative refinements to a test object.
903 // The default transaction produced is valid and has three inputs:
904 // - an issuance of 10 units
905 // - a spend of 20 units
906 // - a spend of 40 units
907 // and two outputs, one of 25 units and one of 45 units.
908 // All amounts are denominated in the same asset.
910 // The issuance program for the asset requires two numbers as
911 // arguments that add up to 5. The prevout control programs require
912 // two numbers each, adding to 9 and 13, respectively.
914 // The min and max times for the transaction are now +/- one minute.
915 func sample(tb testing.TB, in *txFixture) *txFixture {
921 if result.initialBlockID.IsZero() {
922 result.initialBlockID = *newHash(1)
924 if testutil.DeepEqual(result.issuanceProg, bc.Program{}) {
925 prog, err := vm.Assemble("ADD 5 NUMEQUAL")
929 result.issuanceProg = bc.Program{VmVersion: 1, Code: prog}
931 if len(result.issuanceArgs) == 0 {
932 result.issuanceArgs = [][]byte{{2}, {3}}
934 if len(result.assetDef) == 0 {
935 result.assetDef = []byte{2}
937 if result.assetID.IsZero() {
938 result.assetID = bc.AssetID{V0: 9999}
941 if result.txVersion == 0 {
944 if len(result.txInputs) == 0 {
945 cp1, err := vm.Assemble("ADD 9 NUMEQUAL")
949 args1 := [][]byte{{4}, {5}}
951 cp2, err := vm.Assemble("ADD 13 NUMEQUAL")
955 args2 := [][]byte{{6}, {7}}
957 result.txInputs = []*types.TxInput{
958 types.NewSpendInput(nil, *newHash(9), result.assetID, 10, 0, []byte{byte(vm.OP_TRUE)}),
959 types.NewSpendInput(args1, *newHash(5), result.assetID, 20, 0, cp1),
960 types.NewSpendInput(args2, *newHash(8), result.assetID, 40, 0, cp2),
964 result.txInputs = append(result.txInputs, mockGasTxInput())
966 if len(result.txOutputs) == 0 {
967 cp1, err := vm.Assemble("ADD 17 NUMEQUAL")
971 cp2, err := vm.Assemble("ADD 21 NUMEQUAL")
976 result.txOutputs = []*types.TxOutput{
977 types.NewIntraChainOutput(result.assetID, 25, cp1),
978 types.NewIntraChainOutput(result.assetID, 45, cp2),
982 result.tx = &types.TxData{
983 Version: result.txVersion,
984 Inputs: result.txInputs,
985 Outputs: result.txOutputs,
991 func mockBlock() *bc.Block {
993 BlockHeader: &bc.BlockHeader{
999 func mockGasTxInput() *types.TxInput {
1000 cp, _ := vmutil.DefaultCoinbaseProgram()
1001 return types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp)
1004 // Like errors.Root, but also unwraps vm.Error objects.
1005 func rootErr(e error) error {
1006 return errors.Root(e)
1009 func hashData(data []byte) bc.Hash {
1011 sha3pool.Sum256(b32[:], data)
1012 return bc.NewHash(b32)
1015 func newHash(n byte) *bc.Hash {
1016 h := bc.NewHash([32]byte{n})
1020 func newAssetID(n byte) *bc.AssetID {
1021 a := bc.NewAssetID([32]byte{n})
1025 func txSpend(t *testing.T, tx *bc.Tx, index int) *bc.Spend {
1026 id := tx.InputIDs[index]
1027 res, err := tx.Spend(id)
1034 func getMuxID(tx *bc.Tx) *bc.Hash {
1035 out := tx.Entries[*tx.ResultIds[0]]
1036 switch result := out.(type) {
1037 case *bc.IntraChainOutput:
1038 return result.Source.Ref
1039 case *bc.Retirement:
1040 return result.Source.Ref