package validation import ( "math" "testing" "time" "github.com/vapor/consensus" "github.com/vapor/protocol/bc" "github.com/vapor/protocol/bc/types" "github.com/vapor/protocol/state" "github.com/vapor/protocol/vm" "github.com/vapor/protocol/vm/vmutil" "github.com/vapor/testutil" ) func TestCheckBlockTime(t *testing.T) { cases := []struct { desc string blockTime uint64 parentTime []uint64 err error }{ { blockTime: 1520000500, parentTime: []uint64{1520000000}, err: nil, }, { desc: "timestamp less than past median time", blockTime: 1520005500, parentTime: []uint64{1520000000, 1520000500, 1520001000, 1520001500, 1520002000, 1520002500, 1520003000, 1520003500, 1520004000, 1520004500, 1520005000}, err: nil, }, { desc: "timestamp greater than max limit", blockTime: 99999999990000, parentTime: []uint64{15200000000000}, err: errBadTimestamp, }, { desc: "timestamp of the block and the parent block are both greater than max limit", blockTime: uint64(time.Now().UnixNano()/int64(time.Millisecond)) + consensus.ActiveNetParams.MaxTimeOffsetMs + 2000, parentTime: []uint64{uint64(time.Now().UnixNano()/int64(time.Millisecond)) + consensus.ActiveNetParams.MaxTimeOffsetMs + 1000}, err: errBadTimestamp, }, } parent := &types.BlockHeader{Version: 1} block := &bc.Block{ BlockHeader: &bc.BlockHeader{Version: 1}, } for i, c := range cases { parent.Timestamp = c.parentTime[0] parentSuccessor := parent for i := 1; i < len(c.parentTime); i++ { Previous := &types.BlockHeader{Version: 1, Timestamp: c.parentTime[i]} parentSuccessor.PreviousBlockHash = Previous.Hash() parentSuccessor = Previous } block.Timestamp = c.blockTime if err := checkBlockTime(block, parent); rootErr(err) != c.err { t.Errorf("case %d got error %s, want %s", i, err, c.err) } } } func TestCheckCoinbaseTx(t *testing.T) { cases := []struct { desc string txs []*types.Tx rewards []state.CoinbaseReward err error }{ { desc: "zero coinbase amount", txs: []*types.Tx{ types.NewTx(types.TxData{ Inputs: []*types.TxInput{types.NewCoinbaseInput(nil)}, Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 0, []byte{0x51})}, }), }, rewards: []state.CoinbaseReward{}, err: nil, }, { desc: "zero coinbase amount and aggregate rewards", txs: []*types.Tx{ types.NewTx(types.TxData{ Inputs: []*types.TxInput{types.NewCoinbaseInput(nil)}, Outputs: []*types.TxOutput{ types.NewIntraChainOutput(*consensus.BTMAssetID, 0, []byte{0x51}), types.NewIntraChainOutput(*consensus.BTMAssetID, 5000, []byte{0x51}), }, }), }, rewards: []state.CoinbaseReward{ state.CoinbaseReward{ Amount: 5000, ControlProgram: []byte{0x51}, }, }, err: nil, }, { desc: "wrong coinbase transaction", txs: []*types.Tx{}, rewards: []state.CoinbaseReward{}, err: ErrWrongCoinbaseTransaction, }, } block := new(types.Block) for i, c := range cases { block.Transactions = c.txs if err := checkCoinbaseTx(types.MapBlock(block), c.rewards); rootErr(err) != c.err { t.Errorf("case %d got error %s, want %T", i, err, c.err) } } } func TestValidateBlockHeader(t *testing.T) { parent := &types.BlockHeader{ Version: 1, Height: 0, Timestamp: 1523352600000, } parentHash := parent.Hash() cases := []struct { desc string block *bc.Block parent *types.BlockHeader err error }{ { desc: "dismatch version", block: &bc.Block{BlockHeader: &bc.BlockHeader{ Version: 2, }}, parent: &types.BlockHeader{ Version: 1, }, err: errVersionRegression, }, { desc: "misordered block height", block: &bc.Block{BlockHeader: &bc.BlockHeader{ Version: 1, Height: 20, }}, parent: &types.BlockHeader{ Version: 1, Height: 18, }, err: errMisorderedBlockHeight, }, { desc: "the prev block hash not equals to the hash of parent", block: &bc.Block{BlockHeader: &bc.BlockHeader{ Version: 1, Height: 20, PreviousBlockId: &bc.Hash{V0: 20}, }}, parent: &types.BlockHeader{ Version: 1, Height: 19, PreviousBlockHash: bc.Hash{V0: 19}, }, err: errMismatchedBlock, }, { desc: "normal block", block: &bc.Block{ ID: bc.Hash{V0: 1}, BlockHeader: &bc.BlockHeader{ Version: 1, Height: 1, Timestamp: 1523352601000, PreviousBlockId: &parentHash, }, }, parent: parent, err: nil, }, { desc: "version greater than 1", block: &bc.Block{ ID: bc.Hash{V0: 1}, BlockHeader: &bc.BlockHeader{ Version: 2, }, }, parent: &types.BlockHeader{ Version: 1, }, err: errVersionRegression, }, { desc: "version equals 0", block: &bc.Block{ ID: bc.Hash{V0: 1}, BlockHeader: &bc.BlockHeader{ Version: 0, }, }, parent: &types.BlockHeader{ Version: 1, }, err: errVersionRegression, }, { desc: "version equals max uint64", block: &bc.Block{ ID: bc.Hash{V0: 1}, BlockHeader: &bc.BlockHeader{ Version: math.MaxUint64, }, }, parent: &types.BlockHeader{ Version: 1, }, err: errVersionRegression, }, } for i, c := range cases { if err := ValidateBlockHeader(c.block, c.parent); rootErr(err) != c.err { t.Errorf("case %d (%s) got error %s, want %s", i, c.desc, err, c.err) } } } func TestValidateBlock(t *testing.T) { cp, _ := vmutil.DefaultCoinbaseProgram() parent := &types.BlockHeader{ Version: 1, Height: 0, Timestamp: 1523352600000, PreviousBlockHash: bc.Hash{V0: 0}, } parentHash := parent.Hash() txsRoot := testutil.MustDecodeHash("001e21b9618c503d909c1e0b32bab9ccf80c538b35d49ac7fffcef98eb373b23") txStatusHash := testutil.MustDecodeHash("6978a65b4ee5b6f4914fe5c05000459a803ecf59132604e5d334d64249c5e50a") cases := []struct { desc string block *bc.Block parent *types.BlockHeader rewards []state.CoinbaseReward err error }{ { desc: "The calculated transaction merkel root hash is not equals to the hash of the block header", block: &bc.Block{ ID: bc.Hash{V0: 1}, BlockHeader: &bc.BlockHeader{ Version: 1, Height: 1, Timestamp: 1523352601000, PreviousBlockId: &parentHash, TransactionsRoot: &bc.Hash{V0: 1}, }, Transactions: []*bc.Tx{ types.MapTx(&types.TxData{ Version: 1, SerializedSize: 1, Inputs: []*types.TxInput{types.NewCoinbaseInput(nil)}, Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 0, cp)}, }), }, }, parent: parent, err: errMismatchedMerkleRoot, }, { desc: "The calculated transaction status merkel root hash is not equals to the hash of the block header", block: &bc.Block{ ID: bc.Hash{V0: 1}, BlockHeader: &bc.BlockHeader{ Version: 1, Height: 1, Timestamp: 1523352601000, PreviousBlockId: &parentHash, TransactionsRoot: &bc.Hash{V0: 6294987741126419124, V1: 12520373106916389157, V2: 5040806596198303681, V3: 1151748423853876189}, TransactionStatusHash: &bc.Hash{V0: 1}, }, Transactions: []*bc.Tx{ types.MapTx(&types.TxData{ Version: 1, SerializedSize: 1, Inputs: []*types.TxInput{types.NewCoinbaseInput(nil)}, Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 0, cp)}, }), }, }, parent: parent, err: errMismatchedMerkleRoot, }, { desc: "the coinbase amount is not equal to the real coinbase amount", block: &bc.Block{ ID: bc.Hash{V0: 1}, BlockHeader: &bc.BlockHeader{ Version: 1, Height: 1, Timestamp: 1523352601000, PreviousBlockId: &parentHash, TransactionsRoot: &txsRoot, TransactionStatusHash: &txStatusHash, }, Transactions: []*bc.Tx{ types.MapTx(&types.TxData{ Version: 1, SerializedSize: 1, Inputs: []*types.TxInput{types.NewCoinbaseInput(nil)}, Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 41250000000, cp)}, }), types.MapTx(&types.TxData{ Version: 1, SerializedSize: 1, Inputs: []*types.TxInput{types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp)}, Outputs: []*types.TxOutput{ types.NewIntraChainOutput(*consensus.BTMAssetID, 0, cp), types.NewIntraChainOutput(*consensus.BTMAssetID, 90000000, cp), }, }), }, }, parent: parent, rewards: []state.CoinbaseReward{}, err: ErrWrongCoinbaseTransaction, }, { desc: "the coinbase amount is less than the real coinbase amount", block: &bc.Block{ ID: bc.Hash{V0: 1}, BlockHeader: &bc.BlockHeader{ Version: 1, Height: 1, Timestamp: 1523352601000, PreviousBlockId: &parentHash, TransactionsRoot: &bc.Hash{V0: 16229071813194843118, V1: 7413717724217377663, V2: 10255217553502780716, V3: 17975900656333257644}, TransactionStatusHash: &txStatusHash, }, Transactions: []*bc.Tx{ types.MapTx(&types.TxData{ Version: 1, SerializedSize: 1, Inputs: []*types.TxInput{types.NewCoinbaseInput(nil)}, Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 0, cp)}, }), types.MapTx(&types.TxData{ Version: 1, SerializedSize: 1, Inputs: []*types.TxInput{types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp)}, Outputs: []*types.TxOutput{ types.NewIntraChainOutput(*consensus.BTMAssetID, 0, cp), types.NewIntraChainOutput(*consensus.BTMAssetID, 100000000, cp), }, }), }, }, parent: parent, err: nil, }, } for i, c := range cases { err := ValidateBlock(c.block, c.parent, c.rewards) if rootErr(err) != c.err { t.Errorf("case #%d (%s) got error %s, want %s", i, c.desc, err, c.err) } } } func TestGasOverBlockLimit(t *testing.T) { cp, _ := vmutil.DefaultCoinbaseProgram() parent := &types.BlockHeader{ Version: 1, Height: 0, Timestamp: 1523352600000, PreviousBlockHash: bc.Hash{V0: 0}, } parentHash := parent.Hash() block := &bc.Block{ ID: bc.Hash{V0: 1}, BlockHeader: &bc.BlockHeader{ Version: 1, Height: 1, Timestamp: 1523352601000, PreviousBlockId: &parentHash, }, Transactions: []*bc.Tx{ types.MapTx(&types.TxData{ Version: 1, SerializedSize: 1, Inputs: []*types.TxInput{types.NewCoinbaseInput(nil)}, Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 0, cp)}, }), }, } for i := 0; i < 100; i++ { block.Transactions = append(block.Transactions, types.MapTx(&types.TxData{ Version: 1, SerializedSize: 100000, Inputs: []*types.TxInput{ types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 10000000000, 0, cp), }, Outputs: []*types.TxOutput{ types.NewIntraChainOutput(*consensus.BTMAssetID, 9000000000, cp), }, })) } if err := ValidateBlock(block, parent, []state.CoinbaseReward{}); err != errOverBlockLimit { t.Errorf("got error %s, want %s", err, errOverBlockLimit) } } // TestSetTransactionStatus verify the transaction status is set correctly (blocktest#1010) func TestSetTransactionStatus(t *testing.T) { cp, _ := vmutil.DefaultCoinbaseProgram() parent := &types.BlockHeader{ Version: 1, Height: 0, Timestamp: 1523352600000, PreviousBlockHash: bc.Hash{V0: 0}, } parentHash := parent.Hash() block := &bc.Block{ ID: bc.Hash{V0: 1}, BlockHeader: &bc.BlockHeader{ Version: 1, Height: 1, Timestamp: 1523352601000, PreviousBlockId: &parentHash, TransactionsRoot: &bc.Hash{V0: 8176741810667217458, V1: 14830712230021600370, V2: 8921661778795432162, V3: 3391855546006364086}, TransactionStatusHash: &bc.Hash{V0: 8682965660674182538, V1: 8424137560837623409, V2: 6979974817894224946, V3: 4673809519342015041}, }, Transactions: []*bc.Tx{ types.MapTx(&types.TxData{ Version: 1, SerializedSize: 1, Inputs: []*types.TxInput{types.NewCoinbaseInput(nil)}, Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 0, cp)}, }), types.MapTx(&types.TxData{ Version: 1, SerializedSize: 1, Inputs: []*types.TxInput{ types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp), types.NewSpendInput([][]byte{}, *newHash(8), bc.AssetID{V0: 1}, 1000, 0, []byte{byte(vm.OP_FALSE)}), }, Outputs: []*types.TxOutput{ types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp), types.NewIntraChainOutput(bc.AssetID{V0: 1}, 1000, cp), }, }), types.MapTx(&types.TxData{ Version: 1, SerializedSize: 1, Inputs: []*types.TxInput{ types.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp), }, Outputs: []*types.TxOutput{ types.NewIntraChainOutput(*consensus.BTMAssetID, 888, cp), }, }), }, } if err := ValidateBlock(block, parent, []state.CoinbaseReward{}); err != nil { t.Fatal(err) } expectTxStatuses := []bool{false, true, false} txStatuses := block.GetTransactionStatus().VerifyStatus if len(expectTxStatuses) != len(txStatuses) { t.Error("the size of expect tx status is not equals to size of got tx status") } for i, status := range txStatuses { if expectTxStatuses[i] != status.StatusFail { t.Errorf("got tx status: %v, expect tx status: %v\n", status.StatusFail, expectTxStatuses[i]) } } }