OSDN Git Service

opt mov performance (#480)
[bytom/vapor.git] / application / mov / mov_core_test.go
1 package mov
2
3 import (
4         "math"
5         "os"
6         "testing"
7
8         "github.com/bytom/vapor/application/mov/common"
9         "github.com/bytom/vapor/application/mov/database"
10         "github.com/bytom/vapor/application/mov/mock"
11         "github.com/bytom/vapor/consensus"
12         dbm "github.com/bytom/vapor/database/leveldb"
13         "github.com/bytom/vapor/protocol/bc"
14         "github.com/bytom/vapor/protocol/bc/types"
15         "github.com/bytom/vapor/protocol/vm"
16         "github.com/bytom/vapor/testutil"
17 )
18
19 var initBlockHeader = &types.BlockHeader{Height: 1, PreviousBlockHash: bc.Hash{}}
20
21 func TestApplyBlock(t *testing.T) {
22         cases := []struct {
23                 desc        string
24                 block       *types.Block
25                 blockFunc   testFun
26                 initOrders  []*common.Order
27                 wantOrders  []*common.Order
28                 wantDBState *common.MovDatabaseState
29                 wantError   error
30         }{
31                 {
32                         desc: "apply block has pending order transaction",
33                         block: &types.Block{
34                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
35                                 Transactions: []*types.Tx{
36                                         mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[0],
37                                 },
38                         },
39                         blockFunc:   applyBlock,
40                         wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0)},
41                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
42                 },
43                 {
44                         desc: "apply block has two different trade pairs & different trade pair won't affect each order",
45                         block: &types.Block{
46                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
47                                 Transactions: []*types.Tx{
48                                         mock.Btc2EthMakerTxs[0],
49                                         mock.Eth2BtcMakerTxs[0],
50                                         mock.Eos2EtcMakerTxs[0],
51                                         mock.Eth2EosMakerTxs[0],
52                                 },
53                         },
54                         blockFunc: applyBlock,
55                         wantOrders: []*common.Order{
56                                 mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0),
57                                 mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0),
58                                 mock.MustNewOrderFromOutput(mock.Eos2EtcMakerTxs[0], 0),
59                                 mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
60                         },
61                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
62                 },
63                 {
64                         desc: "apply block has full matched transaction",
65                         block: &types.Block{
66                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
67                                 Transactions: []*types.Tx{
68                                         mock.MatchedTxs[1],
69                                 },
70                         },
71                         blockFunc:   applyBlock,
72                         initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
73                         wantOrders:  []*common.Order{mock.Btc2EthOrders[1]},
74                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
75                 },
76                 {
77                         desc: "apply block has partial matched transaction",
78                         block: &types.Block{
79                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
80                                 Transactions: []*types.Tx{
81                                         mock.MatchedTxs[0],
82                                 },
83                         },
84                         blockFunc:   applyBlock,
85                         initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
86                         wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
87                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
88                 },
89                 {
90                         desc: "apply block has two partial matched transaction",
91                         block: &types.Block{
92                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
93                                 Transactions: []*types.Tx{
94                                         mock.MatchedTxs[2], mock.MatchedTxs[3],
95                                 },
96                         },
97                         blockFunc:   applyBlock,
98                         initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
99                         wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
100                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
101                 },
102                 {
103                         desc: "apply block has partial matched transaction by pending orders from tx pool",
104                         block: &types.Block{
105                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
106                                 Transactions: []*types.Tx{
107                                         mock.Btc2EthMakerTxs[0],
108                                         mock.Eth2BtcMakerTxs[1],
109                                         mock.MatchedTxs[4],
110                                 },
111                         },
112                         blockFunc:   applyBlock,
113                         initOrders:  []*common.Order{},
114                         wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1)},
115                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
116                 },
117                 {
118                         desc: "apply block which node packed maker tx and match transaction in random orde",
119                         block: &types.Block{
120                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
121                                 Transactions: []*types.Tx{
122                                         mock.Eos2EtcMakerTxs[0],
123                                         mock.Btc2EthMakerTxs[0],
124                                         mock.MatchedTxs[4],
125                                         mock.Eth2EosMakerTxs[0],
126                                         mock.Eth2BtcMakerTxs[1],
127                                         mock.MatchedTxs[5],
128                                         mock.Etc2EosMakerTxs[0],
129                                 },
130                         },
131                         blockFunc:  applyBlock,
132                         initOrders: []*common.Order{},
133                         wantOrders: []*common.Order{
134                                 mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1),
135                                 mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
136                         },
137                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
138                 },
139                 {
140                         desc: "detach block has pending order transaction",
141                         block: &types.Block{
142                                 BlockHeader: *initBlockHeader,
143                                 Transactions: []*types.Tx{
144                                         mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1],
145                                 },
146                         },
147                         blockFunc:   detachBlock,
148                         initOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[1], 0)},
149                         wantOrders:  []*common.Order{},
150                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
151                 },
152                 {
153                         desc: "detach block has two different trade pairs & different trade pair won't affect each order",
154                         block: &types.Block{
155                                 BlockHeader: *initBlockHeader,
156                                 Transactions: []*types.Tx{
157                                         mock.Btc2EthMakerTxs[0],
158                                         mock.Eth2BtcMakerTxs[0],
159                                         mock.Eos2EtcMakerTxs[0],
160                                         mock.Eth2EosMakerTxs[0],
161                                 },
162                         },
163                         blockFunc: detachBlock,
164                         initOrders: []*common.Order{
165                                 mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0),
166                                 mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0),
167                                 mock.MustNewOrderFromOutput(mock.Eos2EtcMakerTxs[0], 0),
168                                 mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
169                         },
170                         wantOrders:  []*common.Order{},
171                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
172                 },
173                 {
174                         desc: "detach block has full matched transaction",
175                         block: &types.Block{
176                                 BlockHeader: *initBlockHeader,
177                                 Transactions: []*types.Tx{
178                                         mock.MatchedTxs[1],
179                                 },
180                         },
181                         blockFunc:   detachBlock,
182                         initOrders:  []*common.Order{mock.Btc2EthOrders[1]},
183                         wantOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
184                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
185                 },
186                 {
187                         desc: "detach block has partial matched transaction",
188                         block: &types.Block{
189                                 BlockHeader: *initBlockHeader,
190                                 Transactions: []*types.Tx{
191                                         mock.MatchedTxs[0],
192                                 },
193                         },
194                         blockFunc:   detachBlock,
195                         initOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
196                         wantOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
197                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
198                 },
199                 {
200                         desc: "detach block has two partial matched transaction",
201                         block: &types.Block{
202                                 BlockHeader: *initBlockHeader,
203                                 Transactions: []*types.Tx{
204                                         mock.MatchedTxs[2], mock.MatchedTxs[3],
205                                 },
206                         },
207                         blockFunc:   detachBlock,
208                         initOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
209                         wantOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
210                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
211                 },
212                 {
213                         desc: "detach block which node packed maker tx and match transaction in random orde",
214                         block: &types.Block{
215                                 BlockHeader: *initBlockHeader,
216                                 Transactions: []*types.Tx{
217                                         mock.Eos2EtcMakerTxs[0],
218                                         mock.Btc2EthMakerTxs[0],
219                                         mock.MatchedTxs[4],
220                                         mock.Eth2EosMakerTxs[0],
221                                         mock.Eth2BtcMakerTxs[1],
222                                         mock.MatchedTxs[5],
223                                         mock.Etc2EosMakerTxs[0],
224                                 },
225                         },
226                         blockFunc: detachBlock,
227                         initOrders: []*common.Order{
228                                 mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1),
229                                 mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
230                         },
231                         wantOrders:  []*common.Order{},
232                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
233                 },
234         }
235
236         defer os.RemoveAll("temp")
237         for i, c := range cases {
238                 testDB := dbm.NewDB("testdb", "leveldb", "temp")
239                 store := database.NewLevelDBMovStore(testDB)
240                 if err := store.InitDBState(0, &bc.Hash{}); err != nil {
241                         t.Fatal(err)
242                 }
243
244                 if err := store.ProcessOrders(c.initOrders, nil, initBlockHeader); err != nil {
245                         t.Fatal(err)
246                 }
247
248                 movCore := &MovCore{movStore: store}
249                 if err := c.blockFunc(movCore, c.block); err != c.wantError {
250                         t.Errorf("#%d(%s):apply block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
251                 }
252
253                 gotOrders := queryAllOrders(store)
254                 if !ordersEquals(c.wantOrders, gotOrders) {
255                         t.Errorf("#%d(%s):apply block want orders(%v), got orders(%v)", i, c.desc, c.wantOrders, gotOrders)
256                 }
257
258                 dbState, err := store.GetMovDatabaseState()
259                 if err != nil {
260                         t.Fatal(err)
261                 }
262
263                 if !testutil.DeepEqual(c.wantDBState, dbState) {
264                         t.Errorf("#%d(%s):apply block want db state(%v), got db state(%v)", i, c.desc, c.wantDBState, dbState)
265                 }
266
267                 testDB.Close()
268                 os.RemoveAll("temp")
269         }
270 }
271
272 func TestValidateBlock(t *testing.T) {
273         cases := []struct {
274                 desc          string
275                 block         *types.Block
276                 verifyResults []*bc.TxVerifyResult
277                 wantError     error
278         }{
279                 {
280                         desc: "block only has maker tx",
281                         block: &types.Block{
282                                 Transactions: []*types.Tx{
283                                         mock.Eth2BtcMakerTxs[0],
284                                         mock.Btc2EthMakerTxs[0],
285                                 },
286                         },
287                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}},
288                         wantError:     nil,
289                 },
290                 {
291                         desc: "block only has matched tx",
292                         block: &types.Block{
293                                 Transactions: []*types.Tx{
294                                         mock.MatchedTxs[0],
295                                         mock.MatchedTxs[1],
296                                         mock.MatchedTxs[2],
297                                 },
298                         },
299                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
300                         wantError:     nil,
301                 },
302                 {
303                         desc: "block has maker tx and matched tx",
304                         block: &types.Block{
305                                 Transactions: []*types.Tx{
306                                         mock.Eth2BtcMakerTxs[0],
307                                         mock.Btc2EthMakerTxs[0],
308                                         mock.MatchedTxs[0],
309                                         mock.MatchedTxs[1],
310                                         mock.MatchedTxs[2],
311                                 },
312                         },
313                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
314                         wantError:     nil,
315                 },
316                 {
317                         desc: "status fail of maker tx is true",
318                         block: &types.Block{
319                                 Transactions: []*types.Tx{
320                                         mock.Eth2BtcMakerTxs[0],
321                                         mock.Btc2EthMakerTxs[0],
322                                 },
323                         },
324                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
325                         wantError:     errStatusFailMustFalse,
326                 },
327                 {
328                         desc: "status fail of matched tx is true",
329                         block: &types.Block{
330                                 Transactions: []*types.Tx{
331                                         mock.MatchedTxs[1],
332                                         mock.MatchedTxs[2],
333                                 },
334                         },
335                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
336                         wantError:     errStatusFailMustFalse,
337                 },
338                 {
339                         desc: "asset id in matched tx is not unique",
340                         block: &types.Block{
341                                 Transactions: []*types.Tx{
342                                         types.NewTx(types.TxData{
343                                                 Inputs: []*types.TxInput{
344                                                         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),
345                                                         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),
346                                                 },
347                                                 Outputs: []*types.TxOutput{
348                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
349                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
350                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
351                                                 },
352                                         }),
353                                 },
354                         },
355                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
356                         wantError:     errAssetIDMustUniqueInMatchedTx,
357                 },
358                 {
359                         desc: "common input in the matched tx",
360                         block: &types.Block{
361                                 Transactions: []*types.Tx{
362                                         types.NewTx(types.TxData{
363                                                 Inputs: []*types.TxInput{
364                                                         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),
365                                                         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),
366                                                         types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
367                                                 },
368                                                 Outputs: []*types.TxOutput{
369                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
370                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
371                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
372                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
373                                                 },
374                                         }),
375                                 },
376                         },
377                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
378                         wantError:     errInputProgramMustP2WMCScript,
379                 },
380                 {
381                         desc: "cancel order in the matched tx",
382                         block: &types.Block{
383                                 Transactions: []*types.Tx{
384                                         types.NewTx(types.TxData{
385                                                 Inputs: []*types.TxInput{
386                                                         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),
387                                                         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),
388                                                         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),
389                                                 },
390                                                 Outputs: []*types.TxOutput{
391                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
392                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
393                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
394                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
395                                                 },
396                                         }),
397                                 },
398                         },
399                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
400                         wantError:     errExistCancelOrderInMatchedTx,
401                 },
402                 {
403                         desc: "common input in the cancel order tx",
404                         block: &types.Block{
405                                 Transactions: []*types.Tx{
406                                         types.NewTx(types.TxData{
407                                                 Inputs: []*types.TxInput{
408                                                         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),
409                                                         types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
410                                                 },
411                                                 Outputs: []*types.TxOutput{
412                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
413                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
414                                                 },
415                                         }),
416                                 },
417                         },
418                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
419                         wantError:     errInputProgramMustP2WMCScript,
420                 },
421                 {
422                         desc: "amount of fee greater than max fee amount",
423                         block: &types.Block{
424                                 Transactions: []*types.Tx{
425                                         types.NewTx(types.TxData{
426                                                 Inputs: []*types.TxInput{
427                                                         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),
428                                                         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),
429                                                 },
430                                                 Outputs: []*types.TxOutput{
431                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
432                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 10, testutil.MustDecodeHexString("55")),
433                                                         // re-order
434                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 270, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
435                                                         // fee
436                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 40, []byte{0x59}),
437                                                 },
438                                         }),
439                                 },
440                         },
441                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
442                         wantError:     errAmountOfFeeGreaterThanMaximum,
443                 },
444                 {
445                         desc: "ratio numerator is zero",
446                         block: &types.Block{
447                                 Transactions: []*types.Tx{
448                                         types.NewTx(types.TxData{
449                                                 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})},
450                                                 Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 0, 1))},
451                                         }),
452                                 },
453                         },
454                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
455                         wantError:     errRatioOfTradeLessThanZero,
456                 },
457                 {
458                         desc: "ratio denominator is zero",
459                         block: &types.Block{
460                                 Transactions: []*types.Tx{
461                                         types.NewTx(types.TxData{
462                                                 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})},
463                                                 Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 1, 0))},
464                                         }),
465                                 },
466                         },
467                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
468                         wantError:     errRatioOfTradeLessThanZero,
469                 },
470                 {
471                         desc: "want amount is overflow",
472                         block: &types.Block{
473                                 Transactions: []*types.Tx{
474                                         types.NewTx(types.TxData{
475                                                 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})},
476                                                 Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), math.MaxInt64, 1))},
477                                         }),
478                                 },
479                         },
480                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
481                         wantError:     errRequestAmountMath,
482                 },
483         }
484
485         for i, c := range cases {
486                 movCore := &MovCore{}
487                 if err := movCore.ValidateBlock(c.block, c.verifyResults); err != c.wantError {
488                         t.Errorf("#%d(%s):validate block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
489                 }
490         }
491 }
492
493 func TestBeforeProposalBlock(t *testing.T) {
494         cases := []struct {
495                 desc           string
496                 initOrders     []*common.Order
497                 gasLeft        int64
498                 wantMatchedTxs []*types.Tx
499         }{
500                 {
501                         desc:           "has matched tx, but gas left is zero",
502                         initOrders:     []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
503                         gasLeft:        0,
504                         wantMatchedTxs: []*types.Tx{},
505                 },
506                 {
507                         desc:           "has one matched tx, and gas is sufficient",
508                         initOrders:     []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
509                         gasLeft:        2000,
510                         wantMatchedTxs: []*types.Tx{mock.MatchedTxs[1]},
511                 },
512                 {
513                         desc: "has two matched tx, but gas is only enough to pack a matched tx",
514                         initOrders: []*common.Order{
515                                 mock.Btc2EthOrders[0],
516                                 mock.Btc2EthOrders[1],
517                                 mock.Eth2BtcOrders[2],
518                         },
519                         gasLeft:        2000,
520                         wantMatchedTxs: []*types.Tx{mock.MatchedTxs[2]},
521                 },
522                 {
523                         desc: "has two matched tx, and gas left is sufficient",
524                         initOrders: []*common.Order{
525                                 mock.Btc2EthOrders[0],
526                                 mock.Btc2EthOrders[1],
527                                 mock.Eth2BtcOrders[2],
528                         },
529                         gasLeft:        4000,
530                         wantMatchedTxs: []*types.Tx{mock.MatchedTxs[2], mock.MatchedTxs[3]},
531                 },
532                 {
533                         desc: "has multiple trade pairs, and gas left is sufficient",
534                         initOrders: []*common.Order{
535                                 mock.Btc2EthOrders[0],
536                                 mock.Btc2EthOrders[1],
537                                 mock.Eth2BtcOrders[2],
538                                 mock.Eos2EtcOrders[0],
539                                 mock.Etc2EosOrders[0],
540                         },
541                         gasLeft:        6000,
542                         wantMatchedTxs: []*types.Tx{mock.MatchedTxs[2], mock.MatchedTxs[3], mock.MatchedTxs[5]},
543                 },
544         }
545
546         for i, c := range cases {
547                 testDB := dbm.NewDB("testdb", "leveldb", "temp")
548                 store := database.NewLevelDBMovStore(testDB)
549                 if err := store.InitDBState(0, &bc.Hash{}); err != nil {
550                         t.Fatal(err)
551                 }
552
553                 if err := store.ProcessOrders(c.initOrders, nil, initBlockHeader); err != nil {
554                         t.Fatal(err)
555                 }
556
557                 movCore := &MovCore{movStore: store}
558                 gotMatchedTxs, err := movCore.BeforeProposalBlock(nil, []byte{0x51}, 2, c.gasLeft, func() bool { return false })
559                 if err != nil {
560                         t.Fatal(err)
561                 }
562
563                 gotMatchedTxMap := make(map[string]interface{})
564                 for _, matchedTx := range gotMatchedTxs {
565                         gotMatchedTxMap[matchedTx.ID.String()] = nil
566                 }
567
568                 wantMatchedTxMap := make(map[string]interface{})
569                 for _, matchedTx := range c.wantMatchedTxs {
570                         wantMatchedTxMap[matchedTx.ID.String()] = nil
571                 }
572
573                 if !testutil.DeepEqual(gotMatchedTxMap, wantMatchedTxMap) {
574                         t.Errorf("#%d(%s):want matched tx(%v) is not equals got matched tx(%v)", i, c.desc, c.wantMatchedTxs, gotMatchedTxs)
575                 }
576
577                 testDB.Close()
578                 os.RemoveAll("temp")
579         }
580 }
581
582 type testFun func(movCore *MovCore, block *types.Block) error
583
584 func applyBlock(movCore *MovCore, block *types.Block) error {
585         return movCore.ApplyBlock(block)
586 }
587
588 func detachBlock(movCore *MovCore, block *types.Block) error {
589         return movCore.DetachBlock(block)
590 }
591
592 func queryAllOrders(store *database.LevelDBMovStore) []*common.Order {
593         var orders []*common.Order
594         tradePairIterator := database.NewTradePairIterator(store)
595         for tradePairIterator.HasNext() {
596                 orderIterator := database.NewOrderIterator(store, tradePairIterator.Next())
597                 for orderIterator.HasNext() {
598                         orders = append(orders, orderIterator.NextBatch()...)
599                 }
600         }
601         return orders
602 }
603
604 func ordersEquals(orders1 []*common.Order, orders2 []*common.Order) bool {
605         orderMap1 := make(map[string]*common.Order)
606         for _, order := range orders1 {
607                 orderMap1[order.Key()] = order
608         }
609
610         orderMap2 := make(map[string]*common.Order)
611         for _, order := range orders2 {
612                 orderMap2[order.Key()] = order
613         }
614         return testutil.DeepEqual(orderMap1, orderMap2)
615 }
616
617 func hashPtr(hash bc.Hash) *bc.Hash {
618         return &hash
619 }