OSDN Git Service

711dad8f9515ee1b529a9cd0f71edf512dd8b888
[bytom/vapor.git] / application / mov / mov_core_test.go
1 package mov
2
3 import (
4         "encoding/hex"
5         "math"
6         "os"
7         "testing"
8
9         "github.com/bytom/vapor/application/mov/common"
10         "github.com/bytom/vapor/application/mov/database"
11         "github.com/bytom/vapor/application/mov/match"
12         "github.com/bytom/vapor/application/mov/mock"
13         "github.com/bytom/vapor/consensus"
14         dbm "github.com/bytom/vapor/database/leveldb"
15         "github.com/bytom/vapor/protocol/bc"
16         "github.com/bytom/vapor/protocol/bc/types"
17         "github.com/bytom/vapor/protocol/vm"
18         "github.com/bytom/vapor/testutil"
19 )
20
21 var initBlockHeader = &types.BlockHeader{Height: 1, PreviousBlockHash: bc.Hash{}}
22
23 func TestApplyBlock(t *testing.T) {
24         cases := []struct {
25                 desc        string
26                 block       *types.Block
27                 blockFunc   testFun
28                 initOrders  []*common.Order
29                 wantOrders  []*common.Order
30                 wantDBState *common.MovDatabaseState
31                 wantError   error
32         }{
33                 {
34                         desc: "apply block has pending order transaction",
35                         block: &types.Block{
36                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
37                                 Transactions: []*types.Tx{
38                                         mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[0],
39                                 },
40                         },
41                         blockFunc:   applyBlock,
42                         wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0)},
43                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
44                 },
45                 {
46                         desc: "apply block has two different trade pairs & different trade pair won't affect each order",
47                         block: &types.Block{
48                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
49                                 Transactions: []*types.Tx{
50                                         mock.Btc2EthMakerTxs[0],
51                                         mock.Eth2BtcMakerTxs[0],
52                                         mock.Eos2EtcMakerTxs[0],
53                                         mock.Eth2EosMakerTxs[0],
54                                 },
55                         },
56                         blockFunc: applyBlock,
57                         wantOrders: []*common.Order{
58                                 mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0),
59                                 mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0),
60                                 mock.MustNewOrderFromOutput(mock.Eos2EtcMakerTxs[0], 0),
61                                 mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
62                         },
63                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
64                 },
65                 {
66                         desc: "apply block has full matched transaction",
67                         block: &types.Block{
68                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
69                                 Transactions: []*types.Tx{
70                                         mock.MatchedTxs[1],
71                                 },
72                         },
73                         blockFunc:   applyBlock,
74                         initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
75                         wantOrders:  []*common.Order{mock.Btc2EthOrders[1]},
76                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
77                 },
78                 {
79                         desc: "apply block has partial matched transaction",
80                         block: &types.Block{
81                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
82                                 Transactions: []*types.Tx{
83                                         mock.MatchedTxs[0],
84                                 },
85                         },
86                         blockFunc:   applyBlock,
87                         initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
88                         wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
89                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
90                 },
91                 {
92                         desc: "apply block has two partial matched transaction",
93                         block: &types.Block{
94                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
95                                 Transactions: []*types.Tx{
96                                         mock.MatchedTxs[2], mock.MatchedTxs[3],
97                                 },
98                         },
99                         blockFunc:   applyBlock,
100                         initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
101                         wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
102                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
103                 },
104                 {
105                         desc: "apply block has partial matched transaction by pending orders from tx pool",
106                         block: &types.Block{
107                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
108                                 Transactions: []*types.Tx{
109                                         mock.Btc2EthMakerTxs[0],
110                                         mock.Eth2BtcMakerTxs[1],
111                                         mock.MatchedTxs[4],
112                                 },
113                         },
114                         blockFunc:   applyBlock,
115                         initOrders:  []*common.Order{},
116                         wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1)},
117                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
118                 },
119                 {
120                         desc: "apply block which node packed maker tx and match transaction in random orde",
121                         block: &types.Block{
122                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
123                                 Transactions: []*types.Tx{
124                                         mock.Eos2EtcMakerTxs[0],
125                                         mock.Btc2EthMakerTxs[0],
126                                         mock.Eth2BtcMakerTxs[1],
127                                         mock.MatchedTxs[4],
128                                         mock.Eth2EosMakerTxs[0],
129                                         mock.Etc2EosMakerTxs[0],
130                                         mock.MatchedTxs[5],
131                                 },
132                         },
133                         blockFunc:  applyBlock,
134                         initOrders: []*common.Order{},
135                         wantOrders: []*common.Order{
136                                 mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1),
137                                 mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
138                         },
139                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
140                 },
141                 {
142                         desc: "apply block has partial matched transaction chain",
143                         block: &types.Block{
144                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
145                                 Transactions: []*types.Tx{
146                                         mock.Btc2EthMakerTxs[0],
147                                         mock.Eth2BtcMakerTxs[1],
148                                         mock.MatchedTxs[4],
149                                         mock.Eth2BtcMakerTxs[0],
150                                         mock.MatchedTxs[7],
151                                 },
152                         },
153                         blockFunc:   applyBlock,
154                         initOrders:  []*common.Order{},
155                         wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[7], 2)},
156                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
157                 },
158                 {
159                         desc: "detach block has pending order transaction",
160                         block: &types.Block{
161                                 BlockHeader: *initBlockHeader,
162                                 Transactions: []*types.Tx{
163                                         mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1],
164                                 },
165                         },
166                         blockFunc:   detachBlock,
167                         initOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[1], 0)},
168                         wantOrders:  []*common.Order{},
169                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
170                 },
171                 {
172                         desc: "detach block has two different trade pairs & different trade pair won't affect each order",
173                         block: &types.Block{
174                                 BlockHeader: *initBlockHeader,
175                                 Transactions: []*types.Tx{
176                                         mock.Btc2EthMakerTxs[0],
177                                         mock.Eth2BtcMakerTxs[0],
178                                         mock.Eos2EtcMakerTxs[0],
179                                         mock.Eth2EosMakerTxs[0],
180                                 },
181                         },
182                         blockFunc: detachBlock,
183                         initOrders: []*common.Order{
184                                 mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0),
185                                 mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0),
186                                 mock.MustNewOrderFromOutput(mock.Eos2EtcMakerTxs[0], 0),
187                                 mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
188                         },
189                         wantOrders:  []*common.Order{},
190                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
191                 },
192                 {
193                         desc: "detach block has full matched transaction",
194                         block: &types.Block{
195                                 BlockHeader: *initBlockHeader,
196                                 Transactions: []*types.Tx{
197                                         mock.MatchedTxs[1],
198                                 },
199                         },
200                         blockFunc:   detachBlock,
201                         initOrders:  []*common.Order{mock.Btc2EthOrders[1]},
202                         wantOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
203                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
204                 },
205                 {
206                         desc: "detach block has partial matched transaction",
207                         block: &types.Block{
208                                 BlockHeader: *initBlockHeader,
209                                 Transactions: []*types.Tx{
210                                         mock.MatchedTxs[0],
211                                 },
212                         },
213                         blockFunc:   detachBlock,
214                         initOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
215                         wantOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
216                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
217                 },
218                 {
219                         desc: "detach block has two partial matched transaction",
220                         block: &types.Block{
221                                 BlockHeader: *initBlockHeader,
222                                 Transactions: []*types.Tx{
223                                         mock.MatchedTxs[2], mock.MatchedTxs[3],
224                                 },
225                         },
226                         blockFunc:   detachBlock,
227                         initOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
228                         wantOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
229                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
230                 },
231                 {
232                         desc: "detach block which node packed maker tx and match transaction in random orde",
233                         block: &types.Block{
234                                 BlockHeader: *initBlockHeader,
235                                 Transactions: []*types.Tx{
236                                         mock.Eos2EtcMakerTxs[0],
237                                         mock.Btc2EthMakerTxs[0],
238                                         mock.MatchedTxs[4],
239                                         mock.Eth2EosMakerTxs[0],
240                                         mock.Eth2BtcMakerTxs[1],
241                                         mock.MatchedTxs[5],
242                                         mock.Etc2EosMakerTxs[0],
243                                 },
244                         },
245                         blockFunc: detachBlock,
246                         initOrders: []*common.Order{
247                                 mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1),
248                                 mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
249                         },
250                         wantOrders:  []*common.Order{},
251                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
252                 },
253         }
254
255         defer os.RemoveAll("temp")
256         for i, c := range cases {
257                 testDB := dbm.NewDB("testdb", "leveldb", "temp")
258                 store := database.NewLevelDBMovStore(testDB)
259                 if err := store.InitDBState(0, &bc.Hash{}); err != nil {
260                         t.Fatal(err)
261                 }
262
263                 if err := store.ProcessOrders(c.initOrders, nil, initBlockHeader); err != nil {
264                         t.Fatal(err)
265                 }
266
267                 movCore := &MovCore{movStore: store}
268                 if err := c.blockFunc(movCore, c.block); err != c.wantError {
269                         t.Errorf("#%d(%s):apply block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
270                 }
271
272                 gotOrders := queryAllOrders(store)
273                 if !ordersEquals(c.wantOrders, gotOrders) {
274                         t.Errorf("#%d(%s):apply block want orders(%v), got orders(%v)", i, c.desc, c.wantOrders, gotOrders)
275                 }
276
277                 dbState, err := store.GetMovDatabaseState()
278                 if err != nil {
279                         t.Fatal(err)
280                 }
281
282                 if !testutil.DeepEqual(c.wantDBState, dbState) {
283                         t.Errorf("#%d(%s):apply block want db state(%v), got db state(%v)", i, c.desc, c.wantDBState, dbState)
284                 }
285
286                 testDB.Close()
287                 os.RemoveAll("temp")
288         }
289 }
290
291 func TestValidateBlock(t *testing.T) {
292         consensus.ActiveNetParams.MovRewardPrograms = []consensus.MovRewardProgram{
293                 {
294                         BeginBlock: 0,
295                         EndBlock:   100,
296                         Program:    hex.EncodeToString(mock.RewardProgram),
297                 },
298         }
299
300         cases := []struct {
301                 desc          string
302                 block         *types.Block
303                 verifyResults []*bc.TxVerifyResult
304                 wantError     error
305         }{
306                 {
307                         desc: "block only has maker tx",
308                         block: &types.Block{
309                                 Transactions: []*types.Tx{
310                                         mock.Eth2BtcMakerTxs[0],
311                                         mock.Btc2EthMakerTxs[0],
312                                 },
313                         },
314                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}},
315                         wantError:     nil,
316                 },
317                 {
318                         desc: "block only has matched tx",
319                         block: &types.Block{
320                                 Transactions: []*types.Tx{
321                                         mock.MatchedTxs[0],
322                                         mock.MatchedTxs[1],
323                                         mock.MatchedTxs[2],
324                                 },
325                         },
326                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
327                         wantError:     nil,
328                 },
329                 {
330                         desc: "block has maker tx and matched tx",
331                         block: &types.Block{
332                                 Transactions: []*types.Tx{
333                                         mock.Eth2BtcMakerTxs[0],
334                                         mock.Btc2EthMakerTxs[0],
335                                         mock.MatchedTxs[0],
336                                         mock.MatchedTxs[1],
337                                         mock.MatchedTxs[2],
338                                 },
339                         },
340                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
341                         wantError:     nil,
342                 },
343                 {
344                         desc: "status fail of maker tx is true",
345                         block: &types.Block{
346                                 Transactions: []*types.Tx{
347                                         mock.Eth2BtcMakerTxs[0],
348                                         mock.Btc2EthMakerTxs[0],
349                                 },
350                         },
351                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
352                         wantError:     errStatusFailMustFalse,
353                 },
354                 {
355                         desc: "status fail of matched tx is true",
356                         block: &types.Block{
357                                 Transactions: []*types.Tx{
358                                         mock.MatchedTxs[1],
359                                         mock.MatchedTxs[2],
360                                 },
361                         },
362                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
363                         wantError:     errStatusFailMustFalse,
364                 },
365                 {
366                         desc: "asset id in matched tx is not unique",
367                         block: &types.Block{
368                                 Transactions: []*types.Tx{
369                                         types.NewTx(types.TxData{
370                                                 Inputs: []*types.TxInput{
371                                                         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),
372                                                         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),
373                                                 },
374                                                 Outputs: []*types.TxOutput{
375                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
376                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
377                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
378                                                 },
379                                         }),
380                                 },
381                         },
382                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
383                         wantError:     errAssetIDMustUniqueInMatchedTx,
384                 },
385                 {
386                         desc: "common input in the matched tx",
387                         block: &types.Block{
388                                 Transactions: []*types.Tx{
389                                         types.NewTx(types.TxData{
390                                                 Inputs: []*types.TxInput{
391                                                         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),
392                                                         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),
393                                                         types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
394                                                 },
395                                                 Outputs: []*types.TxOutput{
396                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
397                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
398                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
399                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
400                                                 },
401                                         }),
402                                 },
403                         },
404                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
405                         wantError:     errInputProgramMustP2WMCScript,
406                 },
407                 {
408                         desc: "cancel order in the matched tx",
409                         block: &types.Block{
410                                 Transactions: []*types.Tx{
411                                         types.NewTx(types.TxData{
412                                                 Inputs: []*types.TxInput{
413                                                         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),
414                                                         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),
415                                                         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),
416                                                 },
417                                                 Outputs: []*types.TxOutput{
418                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
419                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
420                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
421                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 100, mock.RewardProgram),
422                                                 },
423                                         }),
424                                 },
425                         },
426                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
427                         wantError:     errExistCancelOrderInMatchedTx,
428                 },
429                 {
430                         desc: "common input in the cancel order tx",
431                         block: &types.Block{
432                                 Transactions: []*types.Tx{
433                                         types.NewTx(types.TxData{
434                                                 Inputs: []*types.TxInput{
435                                                         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),
436                                                         types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
437                                                 },
438                                                 Outputs: []*types.TxOutput{
439                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
440                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 100, mock.RewardProgram),
441                                                 },
442                                         }),
443                                 },
444                         },
445                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
446                         wantError:     errInputProgramMustP2WMCScript,
447                 },
448                 {
449                         desc: "amount of fee greater than max fee amount",
450                         block: &types.Block{
451                                 Transactions: []*types.Tx{
452                                         types.NewTx(types.TxData{
453                                                 Inputs: []*types.TxInput{
454                                                         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),
455                                                         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),
456                                                 },
457                                                 Outputs: []*types.TxOutput{
458                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
459                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 10, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
460                                                         // re-order
461                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 270, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
462                                                         // fee
463                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 40, mock.RewardProgram),
464                                                 },
465                                         }),
466                                 },
467                         },
468                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
469                         wantError:     match.ErrAmountOfFeeExceedMaximum,
470                 },
471                 {
472                         desc: "ratio numerator is zero",
473                         block: &types.Block{
474                                 Transactions: []*types.Tx{
475                                         types.NewTx(types.TxData{
476                                                 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})},
477                                                 Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 0, 1))},
478                                         }),
479                                 },
480                         },
481                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
482                         wantError:     errRatioOfTradeLessThanZero,
483                 },
484                 {
485                         desc: "ratio denominator is zero",
486                         block: &types.Block{
487                                 Transactions: []*types.Tx{
488                                         types.NewTx(types.TxData{
489                                                 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})},
490                                                 Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 1, 0))},
491                                         }),
492                                 },
493                         },
494                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
495                         wantError:     errRatioOfTradeLessThanZero,
496                 },
497                 {
498                         desc: "want amount is overflow",
499                         block: &types.Block{
500                                 Transactions: []*types.Tx{
501                                         types.NewTx(types.TxData{
502                                                 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})},
503                                                 Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), math.MaxInt64, 1))},
504                                         }),
505                                 },
506                         },
507                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
508                         wantError:     errRequestAmountMath,
509                 },
510         }
511
512         for i, c := range cases {
513                 movCore := &MovCore{}
514                 if err := movCore.ValidateBlock(c.block, c.verifyResults); err != c.wantError {
515                         t.Errorf("#%d(%s):validate block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
516                 }
517         }
518 }
519
520 func TestCalcMatchedTxFee(t *testing.T) {
521         cases := []struct {
522                 desc             string
523                 tx               types.TxData
524                 maxFeeRate       float64
525                 wantMatchedTxFee map[bc.AssetID]*matchedTxFee
526         }{
527                 {
528                         desc:             "fee less than max fee",
529                         maxFeeRate:       0.05,
530                         wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{mock.ETH: {amount: 10, rewardProgram: mock.RewardProgram}},
531                         tx:               mock.MatchedTxs[1].TxData,
532                 },
533                 {
534                         desc:             "fee refund in tx",
535                         maxFeeRate:       0.05,
536                         wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{mock.ETH: {amount: 25, rewardProgram: mock.RewardProgram}},
537                         tx:               mock.MatchedTxs[2].TxData,
538                 },
539                 {
540                         desc:             "fee is zero",
541                         maxFeeRate:       0.05,
542                         wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{},
543                         tx:               mock.MatchedTxs[0].TxData,
544                 },
545         }
546
547         for i, c := range cases {
548                 gotMatchedTxFee, err := calcFeeAmount(types.NewTx(c.tx))
549                 if err != nil {
550                         t.Fatal(err)
551                 }
552
553                 if !testutil.DeepEqual(gotMatchedTxFee, c.wantMatchedTxFee) {
554                         t.Errorf("#%d(%s):fail to caculate matched tx fee, got (%v), want (%v)", i, c.desc, gotMatchedTxFee, c.wantMatchedTxFee)
555                 }
556         }
557 }
558
559 func TestBeforeProposalBlock(t *testing.T) {
560         consensus.ActiveNetParams.MovRewardPrograms = []consensus.MovRewardProgram{
561                 {
562                         BeginBlock: 0,
563                         EndBlock:   100,
564                         Program:    hex.EncodeToString(mock.RewardProgram),
565                 },
566         }
567
568         cases := []struct {
569                 desc           string
570                 initOrders     []*common.Order
571                 gasLeft        int64
572                 wantMatchedTxs []*types.Tx
573         }{
574                 {
575                         desc:           "has matched tx, but gas left is zero",
576                         initOrders:     []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
577                         gasLeft:        0,
578                         wantMatchedTxs: []*types.Tx{},
579                 },
580                 {
581                         desc:           "has one matched tx, and gas is sufficient",
582                         initOrders:     []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
583                         gasLeft:        2000,
584                         wantMatchedTxs: []*types.Tx{mock.MatchedTxs[1]},
585                 },
586                 {
587                         desc: "has two matched tx, but gas is only enough to pack a matched tx",
588                         initOrders: []*common.Order{
589                                 mock.Btc2EthOrders[0],
590                                 mock.Btc2EthOrders[1],
591                                 mock.Eth2BtcOrders[2],
592                         },
593                         gasLeft:        2000,
594                         wantMatchedTxs: []*types.Tx{mock.MatchedTxs[2]},
595                 },
596                 {
597                         desc: "has two matched tx, and gas left is sufficient",
598                         initOrders: []*common.Order{
599                                 mock.Btc2EthOrders[0],
600                                 mock.Btc2EthOrders[1],
601                                 mock.Eth2BtcOrders[2],
602                         },
603                         gasLeft:        4000,
604                         wantMatchedTxs: []*types.Tx{mock.MatchedTxs[2], mock.MatchedTxs[3]},
605                 },
606                 {
607                         desc: "has multiple trade pairs, and gas left is sufficient",
608                         initOrders: []*common.Order{
609                                 mock.Btc2EthOrders[0],
610                                 mock.Btc2EthOrders[1],
611                                 mock.Eth2BtcOrders[2],
612                                 mock.Eos2EtcOrders[0],
613                                 mock.Etc2EosOrders[0],
614                         },
615                         gasLeft:        6000,
616                         wantMatchedTxs: []*types.Tx{mock.MatchedTxs[2], mock.MatchedTxs[3], mock.MatchedTxs[5]},
617                 },
618         }
619
620         for i, c := range cases {
621                 testDB := dbm.NewDB("testdb", "leveldb", "temp")
622                 store := database.NewLevelDBMovStore(testDB)
623                 if err := store.InitDBState(0, &bc.Hash{}); err != nil {
624                         t.Fatal(err)
625                 }
626
627                 if err := store.ProcessOrders(c.initOrders, nil, initBlockHeader); err != nil {
628                         t.Fatal(err)
629                 }
630
631                 movCore := &MovCore{movStore: store}
632                 gotMatchedTxs, err := movCore.BeforeProposalBlock(nil, 2, c.gasLeft, func() bool { return false })
633                 if err != nil {
634                         t.Fatal(err)
635                 }
636
637                 gotMatchedTxMap := make(map[string]interface{})
638                 for _, matchedTx := range gotMatchedTxs {
639                         gotMatchedTxMap[matchedTx.ID.String()] = nil
640                 }
641
642                 wantMatchedTxMap := make(map[string]interface{})
643                 for _, matchedTx := range c.wantMatchedTxs {
644                         wantMatchedTxMap[matchedTx.ID.String()] = nil
645                 }
646
647                 if !testutil.DeepEqual(gotMatchedTxMap, wantMatchedTxMap) {
648                         t.Errorf("#%d(%s):want matched tx(%v) is not equals got matched tx(%v)", i, c.desc, c.wantMatchedTxs, gotMatchedTxs)
649                 }
650
651                 testDB.Close()
652                 os.RemoveAll("temp")
653         }
654 }
655
656 func TestValidateMatchedTxSequence(t *testing.T) {
657         cases := []struct {
658                 desc         string
659                 initOrders   []*common.Order
660                 transactions []*types.Tx
661                 wantError    error
662         }{
663                 {
664                         desc:         "both db orders and transactions is empty",
665                         initOrders:   []*common.Order{},
666                         transactions: []*types.Tx{},
667                         wantError:    nil,
668                 },
669                 {
670                         desc:         "existing matched orders in db, and transactions is empty",
671                         initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
672                         transactions: []*types.Tx{},
673                         wantError:    nil,
674                 },
675                 {
676                         desc:         "db orders is empty, but transactions has matched tx",
677                         initOrders:   []*common.Order{},
678                         transactions: []*types.Tx{mock.MatchedTxs[1]},
679                         wantError:    errNotMatchedOrder,
680                 },
681                 {
682                         desc:         "existing matched orders in db, and corresponding matched tx in transactions",
683                         initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
684                         transactions: []*types.Tx{mock.MatchedTxs[1]},
685                         wantError:    nil,
686                 },
687                 {
688                         desc:         "package matched tx, one order from db, and the another order from transactions",
689                         initOrders:   []*common.Order{mock.Btc2EthOrders[0]},
690                         transactions: []*types.Tx{mock.Eth2BtcMakerTxs[0], mock.MatchedTxs[10]},
691                         wantError:    nil,
692                 },
693                 {
694                         desc:         "two matched txs use the same orders",
695                         initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
696                         transactions: []*types.Tx{mock.MatchedTxs[1], mock.MatchedTxs[1]},
697                         wantError:    errNotMatchedOrder,
698                 },
699                 {
700                         desc: "existing two matched orders in db, and only one corresponding matched tx in transactions",
701                         initOrders: []*common.Order{
702                                 mock.Btc2EthOrders[3], mock.Eth2BtcOrders[2],
703                                 mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
704                         },
705                         transactions: []*types.Tx{mock.MatchedTxs[8]},
706                         wantError:    nil,
707                 },
708                 {
709                         desc: "existing two matched orders in db, and the sequence of match txs in incorrect",
710                         initOrders: []*common.Order{
711                                 mock.Btc2EthOrders[3], mock.Eth2BtcOrders[2],
712                                 mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
713                         },
714                         transactions: []*types.Tx{mock.MatchedTxs[1], mock.MatchedTxs[8]},
715                         wantError:    errSpendOutputIDIsIncorrect,
716                 },
717                 {
718                         desc:         "matched tx and orders from packaged transactions",
719                         initOrders:   []*common.Order{},
720                         transactions: []*types.Tx{mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1], mock.MatchedTxs[4]},
721                         wantError:    nil,
722                 },
723                 {
724                         desc:         "package the matched tx first, then package match orders",
725                         initOrders:   []*common.Order{},
726                         transactions: []*types.Tx{mock.MatchedTxs[4], mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1]},
727                         wantError:    errNotMatchedOrder,
728                 },
729                 {
730                         desc:         "cancel order in transactions",
731                         initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
732                         transactions: []*types.Tx{mock.Btc2EthCancelTxs[0], mock.MatchedTxs[1]},
733                         wantError:    errNotMatchedOrder,
734                 },
735                 {
736                         desc:         "package cancel order after match tx",
737                         initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
738                         transactions: []*types.Tx{mock.MatchedTxs[1], mock.Btc2EthCancelTxs[0]},
739                         wantError:    nil,
740                 },
741                 {
742                         desc: "package matched txs of different trade pairs",
743                         initOrders: []*common.Order{
744                                 mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
745                                 mock.Eos2EtcOrders[0], mock.Etc2EosOrders[0],
746                         },
747                         transactions: []*types.Tx{mock.MatchedTxs[1], mock.MatchedTxs[9]},
748                         wantError:    nil,
749                 },
750                 {
751                         desc: "package matched txs of different trade pairs in different sequence",
752                         initOrders: []*common.Order{
753                                 mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
754                                 mock.Eos2EtcOrders[0], mock.Etc2EosOrders[0],
755                         },
756                         transactions: []*types.Tx{mock.MatchedTxs[9], mock.MatchedTxs[1]},
757                         wantError:    nil,
758                 },
759                 {
760                         desc:         "package partial matched tx from db orders, and the re-pending order continue to match",
761                         initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
762                         transactions: []*types.Tx{mock.MatchedTxs[2], mock.MatchedTxs[3]},
763                         wantError:    nil,
764                 },
765                 {
766                         desc:         "cancel the re-pending order",
767                         initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
768                         transactions: []*types.Tx{mock.MatchedTxs[2], mock.Btc2EthCancelTxs[1], mock.MatchedTxs[3]},
769                         wantError:    errNotMatchedOrder,
770                 },
771         }
772
773         for i, c := range cases {
774                 testDB := dbm.NewDB("testdb", "leveldb", "temp")
775                 store := database.NewLevelDBMovStore(testDB)
776                 if err := store.InitDBState(0, &bc.Hash{}); err != nil {
777                         t.Fatal(err)
778                 }
779
780                 if err := store.ProcessOrders(c.initOrders, nil, initBlockHeader); err != nil {
781                         t.Fatal(err)
782                 }
783
784                 movCore := &MovCore{movStore: store}
785                 if err := movCore.validateMatchedTxSequence(c.transactions); err != c.wantError {
786                         t.Errorf("#%d(%s):wanet error(%v), got error(%v)", i, c.desc, c.wantError, err)
787                 }
788
789                 testDB.Close()
790                 os.RemoveAll("temp")
791         }
792 }
793
794 type testFun func(movCore *MovCore, block *types.Block) error
795
796 func applyBlock(movCore *MovCore, block *types.Block) error {
797         return movCore.ApplyBlock(block)
798 }
799
800 func detachBlock(movCore *MovCore, block *types.Block) error {
801         return movCore.DetachBlock(block)
802 }
803
804 func queryAllOrders(store *database.LevelDBMovStore) []*common.Order {
805         var orders []*common.Order
806         tradePairIterator := database.NewTradePairIterator(store)
807         for tradePairIterator.HasNext() {
808                 orderIterator := database.NewOrderIterator(store, tradePairIterator.Next())
809                 for orderIterator.HasNext() {
810                         orders = append(orders, orderIterator.NextBatch()...)
811                 }
812         }
813         return orders
814 }
815
816 func ordersEquals(orders1 []*common.Order, orders2 []*common.Order) bool {
817         orderMap1 := make(map[string]*common.Order)
818         for _, order := range orders1 {
819                 orderMap1[order.Key()] = order
820         }
821
822         orderMap2 := make(map[string]*common.Order)
823         for _, order := range orders2 {
824                 orderMap2[order.Key()] = order
825         }
826         return testutil.DeepEqual(orderMap1, orderMap2)
827 }
828
829 func hashPtr(hash bc.Hash) *bc.Hash {
830         return &hash
831 }