8 "github.com/vapor/application/mov/common"
9 "github.com/vapor/application/mov/database"
10 "github.com/vapor/application/mov/mock"
11 "github.com/vapor/consensus"
12 dbm "github.com/vapor/database/leveldb"
13 "github.com/vapor/protocol/bc"
14 "github.com/vapor/protocol/bc/types"
15 "github.com/vapor/protocol/vm"
16 "github.com/vapor/testutil"
20 @addTest:BeforeProposalBlock: will gas affect generate tx? will be packed tx affect generate tx?
21 @addTest:TestApplyBlock: one block has two different trade pairs & different trade pair won't affect each order(attach & detach)
22 @addTest:TestApplyBlock: node packed maker tx and match transaction in random order(attach & detach)
23 @addTest:TestValidateBlock: one tx has trade input and cancel input mixed
24 @addTest:TestValidateBlock: regular match transaction's seller program is also a P2WMCProgram
26 func TestApplyBlock(t *testing.T) {
27 initBlockHeader := &types.BlockHeader{Height: 1, PreviousBlockHash: bc.Hash{}}
32 initOrders []*common.Order
33 wantOrders []*common.Order
34 wantDBState *common.MovDatabaseState
38 desc: "apply block has pending order transaction",
40 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
41 Transactions: []*types.Tx{
42 mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[0],
45 blockFunc: applyBlock,
46 wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0)},
47 wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
50 desc: "apply block has full matched transaction",
52 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
53 Transactions: []*types.Tx{
57 blockFunc: applyBlock,
58 initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
59 wantOrders: []*common.Order{mock.Btc2EthOrders[1]},
60 wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
63 desc: "apply block has partial matched transaction",
65 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
66 Transactions: []*types.Tx{
70 blockFunc: applyBlock,
71 initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
72 wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
73 wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
76 desc: "apply block has two partial matched transaction",
78 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
79 Transactions: []*types.Tx{
80 mock.MatchedTxs[2], mock.MatchedTxs[3],
83 blockFunc: applyBlock,
84 initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
85 wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
86 wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
89 desc: "apply block has partial matched transaction by pending orders from tx pool",
91 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
92 Transactions: []*types.Tx{
93 mock.Btc2EthMakerTxs[0],
94 mock.Eth2BtcMakerTxs[1],
98 blockFunc: applyBlock,
99 initOrders: []*common.Order{},
100 wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1)},
101 wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
104 desc: "detach block has pending order transaction",
106 BlockHeader: *initBlockHeader,
107 Transactions: []*types.Tx{
108 mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1],
111 blockFunc: detachBlock,
112 initOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[1], 0)},
113 wantOrders: []*common.Order{},
114 wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
117 desc: "detach block has full matched transaction",
119 BlockHeader: *initBlockHeader,
120 Transactions: []*types.Tx{
124 blockFunc: detachBlock,
125 initOrders: []*common.Order{mock.Btc2EthOrders[1]},
126 wantOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
127 wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
130 desc: "detach block has partial matched transaction",
132 BlockHeader: *initBlockHeader,
133 Transactions: []*types.Tx{
137 blockFunc: detachBlock,
138 initOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
139 wantOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
140 wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
143 desc: "detach block has two partial matched transaction",
145 BlockHeader: *initBlockHeader,
146 Transactions: []*types.Tx{
147 mock.MatchedTxs[2], mock.MatchedTxs[3],
150 blockFunc: detachBlock,
151 initOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
152 wantOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
153 wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
157 defer os.RemoveAll("temp")
158 for i, c := range cases {
159 testDB := dbm.NewDB("testdb", "leveldb", "temp")
160 store := database.NewLevelDBMovStore(testDB)
161 if err := store.InitDBState(0, &bc.Hash{}); err != nil {
165 if err := store.ProcessOrders(c.initOrders, nil, initBlockHeader); err != nil {
169 movCore := &MovCore{movStore: store}
170 if err := c.blockFunc(movCore, c.block); err != c.wantError {
171 t.Errorf("#%d(%s):apply block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
174 gotOrders := queryAllOrders(store)
175 if !ordersEquals(c.wantOrders, gotOrders) {
176 t.Errorf("#%d(%s):apply block want orders(%v), got orders(%v)", i, c.desc, c.wantOrders, gotOrders)
179 dbState, err := store.GetMovDatabaseState()
184 if !testutil.DeepEqual(c.wantDBState, dbState) {
185 t.Errorf("#%d(%s):apply block want db state(%v), got db state(%v)", i, c.desc, c.wantDBState, dbState)
193 func TestValidateBlock(t *testing.T) {
197 verifyResults []*bc.TxVerifyResult
201 desc: "block only has maker tx",
203 Transactions: []*types.Tx{
204 mock.Eth2BtcMakerTxs[0],
205 mock.Btc2EthMakerTxs[0],
208 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}},
212 desc: "block only has matched tx",
214 Transactions: []*types.Tx{
220 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
224 desc: "block has maker tx and matched tx",
226 Transactions: []*types.Tx{
227 mock.Eth2BtcMakerTxs[0],
228 mock.Btc2EthMakerTxs[0],
234 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
238 desc: "status fail of maker tx is true",
240 Transactions: []*types.Tx{
241 mock.Eth2BtcMakerTxs[0],
242 mock.Btc2EthMakerTxs[0],
245 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
246 wantError: errStatusFailMustFalse,
249 desc: "status fail of matched tx is true",
251 Transactions: []*types.Tx{
256 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
257 wantError: errStatusFailMustFalse,
260 desc: "asset id in matched tx is not unique",
262 Transactions: []*types.Tx{
263 types.NewTx(types.TxData{
264 Inputs: []*types.TxInput{
265 types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
266 types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *mock.Eth2BtcOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Eth2BtcOrders[0].Utxo.Amount, mock.Eth2BtcOrders[0].Utxo.SourcePos, mock.Eth2BtcOrders[0].Utxo.ControlProgram),
268 Outputs: []*types.TxOutput{
269 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
270 types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
271 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
276 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
277 wantError: errAssetIDMustUniqueInMatchedTx,
280 desc: "common input in the matched tx",
282 Transactions: []*types.Tx{
283 types.NewTx(types.TxData{
284 Inputs: []*types.TxInput{
285 types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
286 types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *mock.Eth2BtcOrders[0].Utxo.SourceID, *mock.Eth2BtcOrders[0].FromAssetID, mock.Eth2BtcOrders[0].Utxo.Amount, mock.Eth2BtcOrders[0].Utxo.SourcePos, mock.Eth2BtcOrders[0].Utxo.ControlProgram),
287 types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
289 Outputs: []*types.TxOutput{
290 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
291 types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
292 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
293 types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
298 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
299 wantError: errInputProgramMustP2WMCScript,
302 desc: "cancel order in the matched tx",
304 Transactions: []*types.Tx{
305 types.NewTx(types.TxData{
306 Inputs: []*types.TxInput{
307 types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
308 types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *mock.Eth2BtcOrders[0].Utxo.SourceID, *mock.Eth2BtcOrders[0].FromAssetID, mock.Eth2BtcOrders[0].Utxo.Amount, mock.Eth2BtcOrders[0].Utxo.SourcePos, mock.Eth2BtcOrders[0].Utxo.ControlProgram),
309 types.NewSpendInput([][]byte{{}, {}, vm.Int64Bytes(2)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
311 Outputs: []*types.TxOutput{
312 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
313 types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
314 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
315 types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
320 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
321 wantError: errExistCancelOrderInMatchedTx,
324 desc: "common input in the cancel order tx",
326 Transactions: []*types.Tx{
327 types.NewTx(types.TxData{
328 Inputs: []*types.TxInput{
329 types.NewSpendInput([][]byte{{}, {}, vm.Int64Bytes(2)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
330 types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
332 Outputs: []*types.TxOutput{
333 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
334 types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
339 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
340 wantError: errInputProgramMustP2WMCScript,
343 desc: "amount of fee greater than max fee amount",
345 Transactions: []*types.Tx{
346 types.NewTx(types.TxData{
347 Inputs: []*types.TxInput{
348 types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
349 types.NewSpendInput([][]byte{vm.Int64Bytes(10), vm.Int64Bytes(1), vm.Int64Bytes(0)}, *mock.Eth2BtcOrders[2].Utxo.SourceID, *mock.Eth2BtcOrders[2].FromAssetID, mock.Eth2BtcOrders[2].Utxo.Amount, mock.Eth2BtcOrders[2].Utxo.SourcePos, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
351 Outputs: []*types.TxOutput{
352 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
353 types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 10, testutil.MustDecodeHexString("55")),
355 types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 270, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
357 types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 40, []byte{0x59}),
362 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
363 wantError: errAmountOfFeeGreaterThanMaximum,
366 desc: "ratio numerator is zero",
368 Transactions: []*types.Tx{
369 types.NewTx(types.TxData{
370 Inputs: []*types.TxInput{types.NewSpendInput(nil, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, []byte{0x51})},
371 Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 0, 1))},
375 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
376 wantError: errRatioOfTradeLessThanZero,
379 desc: "ratio denominator is zero",
381 Transactions: []*types.Tx{
382 types.NewTx(types.TxData{
383 Inputs: []*types.TxInput{types.NewSpendInput(nil, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, []byte{0x51})},
384 Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 1, 0))},
388 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
389 wantError: errRatioOfTradeLessThanZero,
392 desc: "want amount is overflow",
394 Transactions: []*types.Tx{
395 types.NewTx(types.TxData{
396 Inputs: []*types.TxInput{types.NewSpendInput(nil, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, []byte{0x51})},
397 Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), math.MaxInt64, 1))},
401 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
402 wantError: errRequestAmountMath,
406 for i, c := range cases {
407 movCore := &MovCore{}
408 if err := movCore.ValidateBlock(c.block, c.verifyResults); err != c.wantError {
409 t.Errorf("#%d(%s):validate block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
414 type testFun func(movCore *MovCore, block *types.Block) error
416 func applyBlock(movCore *MovCore, block *types.Block) error {
417 return movCore.ApplyBlock(block)
420 func detachBlock(movCore *MovCore, block *types.Block) error {
421 return movCore.DetachBlock(block)
424 func queryAllOrders(store *database.LevelDBMovStore) []*common.Order {
425 var orders []*common.Order
426 tradePairIterator := database.NewTradePairIterator(store)
427 for tradePairIterator.HasNext() {
428 orderIterator := database.NewOrderIterator(store, tradePairIterator.Next())
429 for orderIterator.HasNext() {
430 orders = append(orders, orderIterator.NextBatch()...)
436 func ordersEquals(orders1 []*common.Order, orders2 []*common.Order) bool {
437 orderMap1 := make(map[string]*common.Order)
438 for _, order := range orders1 {
439 orderMap1[order.Key()] = order
442 orderMap2 := make(map[string]*common.Order)
443 for _, order := range orders2 {
444 orderMap2[order.Key()] = order
446 return testutil.DeepEqual(orderMap1, orderMap2)
449 func hashPtr(hash bc.Hash) *bc.Hash {