8 "github.com/vapor/application/mov/common"
9 "github.com/vapor/application/mov/database"
10 "github.com/vapor/application/mov/mock"
11 "github.com/vapor/consensus"
12 dbm "github.com/vapor/database/leveldb"
13 "github.com/vapor/protocol/bc"
14 "github.com/vapor/protocol/bc/types"
15 "github.com/vapor/protocol/vm"
16 "github.com/vapor/testutil"
19 func TestApplyBlock(t *testing.T) {
20 initBlockHeader := &types.BlockHeader{Height: 1, PreviousBlockHash: bc.Hash{}}
25 initOrders []*common.Order
26 wantOrders []*common.Order
27 wantDBState *common.MovDatabaseState
31 desc: "apply block has pending order transaction",
33 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
34 Transactions: []*types.Tx{
35 mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[0],
38 blockFunc: applyBlock,
39 wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0)},
40 wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
43 desc: "apply block has full matched transaction",
45 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
46 Transactions: []*types.Tx{
50 blockFunc: applyBlock,
51 initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
52 wantOrders: []*common.Order{mock.Btc2EthOrders[1]},
53 wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
56 desc: "apply block has partial matched transaction",
58 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
59 Transactions: []*types.Tx{
63 blockFunc: applyBlock,
64 initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
65 wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
66 wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
69 desc: "apply block has two partial matched transaction",
71 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
72 Transactions: []*types.Tx{
73 mock.MatchedTxs[2], mock.MatchedTxs[3],
76 blockFunc: applyBlock,
77 initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
78 wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
79 wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
82 desc: "apply block has partial matched transaction by pending orders from tx pool",
84 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
85 Transactions: []*types.Tx{
86 mock.Btc2EthMakerTxs[0],
87 mock.Eth2BtcMakerTxs[1],
91 blockFunc: applyBlock,
92 initOrders: []*common.Order{},
93 wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1)},
94 wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
97 desc: "detach block has pending order transaction",
99 BlockHeader: *initBlockHeader,
100 Transactions: []*types.Tx{
101 mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1],
104 blockFunc: detachBlock,
105 initOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[1], 0)},
106 wantOrders: []*common.Order{},
107 wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
110 desc: "detach block has full matched transaction",
112 BlockHeader: *initBlockHeader,
113 Transactions: []*types.Tx{
117 blockFunc: detachBlock,
118 initOrders: []*common.Order{mock.Btc2EthOrders[1]},
119 wantOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
120 wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
123 desc: "detach block has partial matched transaction",
125 BlockHeader: *initBlockHeader,
126 Transactions: []*types.Tx{
130 blockFunc: detachBlock,
131 initOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
132 wantOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
133 wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
136 desc: "detach block has two partial matched transaction",
138 BlockHeader: *initBlockHeader,
139 Transactions: []*types.Tx{
140 mock.MatchedTxs[2], mock.MatchedTxs[3],
143 blockFunc: detachBlock,
144 initOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
145 wantOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
146 wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
150 defer os.RemoveAll("temp")
151 for i, c := range cases {
152 testDB := dbm.NewDB("testdb", "leveldb", "temp")
153 store := database.NewLevelDBMovStore(testDB)
154 if err := store.InitDBState(0, &bc.Hash{}); err != nil {
158 if err := store.ProcessOrders(c.initOrders, nil, initBlockHeader); err != nil {
162 movCore := &MovCore{movStore: store}
163 if err := c.blockFunc(movCore, c.block); err != c.wantError {
164 t.Errorf("#%d(%s):apply block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
167 gotOrders := queryAllOrders(store)
168 if !ordersEquals(c.wantOrders, gotOrders) {
169 t.Errorf("#%d(%s):apply block want orders(%v), got orders(%v)", i, c.desc, c.wantOrders, gotOrders)
172 dbState, err := store.GetMovDatabaseState()
177 if !testutil.DeepEqual(c.wantDBState, dbState) {
178 t.Errorf("#%d(%s):apply block want db state(%v), got db state(%v)", i, c.desc, c.wantDBState, dbState)
186 func TestValidateBlock(t *testing.T) {
190 verifyResults []*bc.TxVerifyResult
194 desc: "block only has maker tx",
196 Transactions: []*types.Tx{
197 mock.Eth2BtcMakerTxs[0],
198 mock.Btc2EthMakerTxs[0],
201 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}},
205 desc: "block only has matched tx",
207 Transactions: []*types.Tx{
213 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
217 desc: "block has maker tx and matched tx",
219 Transactions: []*types.Tx{
220 mock.Eth2BtcMakerTxs[0],
221 mock.Btc2EthMakerTxs[0],
227 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
231 desc: "status fail of maker tx is true",
233 Transactions: []*types.Tx{
234 mock.Eth2BtcMakerTxs[0],
235 mock.Btc2EthMakerTxs[0],
238 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
239 wantError: errStatusFailMustFalse,
242 desc: "status fail of matched tx is true",
244 Transactions: []*types.Tx{
249 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
250 wantError: errStatusFailMustFalse,
253 desc: "asset id in matched tx is not unique",
255 Transactions: []*types.Tx{
256 types.NewTx(types.TxData{
257 Inputs: []*types.TxInput{
258 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),
259 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),
261 Outputs: []*types.TxOutput{
262 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
263 types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
264 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
269 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
270 wantError: errAssetIDMustUniqueInMatchedTx,
273 desc: "common input in the matched tx",
275 Transactions: []*types.Tx{
276 types.NewTx(types.TxData{
277 Inputs: []*types.TxInput{
278 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),
279 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),
280 types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
282 Outputs: []*types.TxOutput{
283 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
284 types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
285 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
286 types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
291 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
292 wantError: errInputProgramMustP2WMCScript,
295 desc: "cancel order in the matched tx",
297 Transactions: []*types.Tx{
298 types.NewTx(types.TxData{
299 Inputs: []*types.TxInput{
300 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),
301 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),
302 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),
304 Outputs: []*types.TxOutput{
305 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
306 types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
307 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
308 types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
313 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
314 wantError: errExistCancelOrderInMatchedTx,
317 desc: "common input in the cancel order tx",
319 Transactions: []*types.Tx{
320 types.NewTx(types.TxData{
321 Inputs: []*types.TxInput{
322 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),
323 types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
325 Outputs: []*types.TxOutput{
326 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
327 types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
332 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
333 wantError: errInputProgramMustP2WMCScript,
336 desc: "amount of fee greater than max fee amount",
338 Transactions: []*types.Tx{
339 types.NewTx(types.TxData{
340 Inputs: []*types.TxInput{
341 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),
342 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),
344 Outputs: []*types.TxOutput{
345 types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
346 types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 10, testutil.MustDecodeHexString("55")),
348 types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 270, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
350 types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 40, []byte{0x59}),
355 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
356 wantError: errAmountOfFeeGreaterThanMaximum,
359 desc: "ratio numerator is zero",
361 Transactions: []*types.Tx{
362 types.NewTx(types.TxData{
363 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})},
364 Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("51"), 0, 1))},
368 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
369 wantError: errRatioOfTradeLessThanZero,
372 desc: "ratio denominator is zero",
374 Transactions: []*types.Tx{
375 types.NewTx(types.TxData{
376 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})},
377 Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("51"), 1, 0))},
381 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
382 wantError: errRatioOfTradeLessThanZero,
385 desc: "ratio numerator product input amount is overflow",
387 Transactions: []*types.Tx{
388 types.NewTx(types.TxData{
389 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})},
390 Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("51"), math.MaxInt64, 10))},
394 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
395 wantError: errNumeratorOfRatioIsOverflow,
399 for i, c := range cases {
400 movCore := &MovCore{}
401 if err := movCore.ValidateBlock(c.block, c.verifyResults); err != c.wantError {
402 t.Errorf("#%d(%s):validate block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
407 type testFun func(movCore *MovCore, block *types.Block) error
409 func applyBlock(movCore *MovCore, block *types.Block) error {
410 return movCore.ApplyBlock(block)
413 func detachBlock(movCore *MovCore, block *types.Block) error {
414 return movCore.DetachBlock(block)
417 func queryAllOrders(store *database.LevelDBMovStore) []*common.Order {
418 var orders []*common.Order
419 tradePairIterator := database.NewTradePairIterator(store)
420 for tradePairIterator.HasNext() {
421 orderIterator := database.NewOrderIterator(store, tradePairIterator.Next())
422 for orderIterator.HasNext() {
423 orders = append(orders, orderIterator.NextBatch()...)
429 func ordersEquals(orders1 []*common.Order, orders2 []*common.Order) bool {
430 orderMap1 := make(map[string]*common.Order)
431 for _, order := range orders1 {
432 orderMap1[order.Key()] = order
435 orderMap2 := make(map[string]*common.Order)
436 for _, order := range orders2 {
437 orderMap2[order.Key()] = order
439 return testutil.DeepEqual(orderMap1, orderMap2)
442 func hashPtr(hash bc.Hash) *bc.Hash {