OSDN Git Service

add mov core test
authorshenao78 <shenao.78@163.com>
Mon, 4 Nov 2019 08:09:09 +0000 (16:09 +0800)
committershenao78 <shenao.78@163.com>
Mon, 4 Nov 2019 08:09:09 +0000 (16:09 +0800)
application/mov/common/type.go
application/mov/database/mock_mov_store.go [deleted file]
application/mov/database/mov_iterator_test.go
application/mov/database/mov_store.go
application/mov/database/mov_store_test.go
application/mov/match/match_test.go
application/mov/match/order_table_test.go [new file with mode: 0644]
application/mov/mock/mock.go [new file with mode: 0644]
application/mov/mock/mock_mov_store.go [new file with mode: 0644]
application/mov/mov_core.go
application/mov/mov_core_test.go [new file with mode: 0644]

index 95a3fff..bc4757c 100644 (file)
@@ -1,6 +1,7 @@
 package common
 
 import (
+       "encoding/hex"
        "fmt"
 
        "github.com/vapor/consensus/segwit"
@@ -32,6 +33,9 @@ func (o OrderSlice) Swap(i, j int) {
        o[i], o[j] = o[j], o[i]
 }
 func (o OrderSlice) Less(i, j int) bool {
+       if o[i].Rate == o[j].Rate {
+               return hex.EncodeToString(o[i].UTXOHash().Bytes()) < hex.EncodeToString(o[j].UTXOHash().Bytes())
+       }
        return o[i].Rate < o[j].Rate
 }
 
@@ -85,6 +89,17 @@ func NewOrderFromInput(tx *types.Tx, inputIndex int) (*Order, error) {
        }, nil
 }
 
+func (o *Order) UTXOHash() *bc.Hash {
+       prog := &bc.Program{VmVersion: 1, Code: o.Utxo.ControlProgram}
+       src := &bc.ValueSource{
+               Ref:      o.Utxo.SourceID,
+               Value:    &bc.AssetAmount{AssetId: o.FromAssetID, Amount: o.Utxo.Amount},
+               Position: o.Utxo.SourcePos,
+       }
+       hash := bc.EntryID(bc.NewIntraChainOutput(src, prog, 0))
+       return &hash
+}
+
 func (o *Order) TradePair() *TradePair {
        return &TradePair{FromAssetID: o.FromAssetID, ToAssetID: o.ToAssetID}
 }
diff --git a/application/mov/database/mock_mov_store.go b/application/mov/database/mock_mov_store.go
deleted file mode 100644 (file)
index 96bbcdd..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-package database
-
-import (
-       "sort"
-
-       "github.com/vapor/application/mov/common"
-       "github.com/vapor/errors"
-       "github.com/vapor/protocol/bc"
-       "github.com/vapor/protocol/bc/types"
-)
-
-type MockMovStore struct {
-       TradePairs []*common.TradePair
-       OrderMap   map[string][]*common.Order
-       DBState    *common.MovDatabaseState
-}
-
-func (m *MockMovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
-       return m.DBState, nil
-}
-
-func (m *MockMovStore) InitDBState(height uint64, hash *bc.Hash) error {
-       return nil
-}
-
-func (m *MockMovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
-       tradePair := &common.TradePair{FromAssetID: orderAfter.FromAssetID, ToAssetID: orderAfter.ToAssetID}
-       orders := m.OrderMap[tradePair.Key()]
-       begin := len(orders)
-       if orderAfter.Rate == 0 {
-               begin = 0
-       } else {
-               for i, order := range orders {
-                       if order.Rate == orderAfter.Rate {
-                               begin = i + 1
-                               break
-                       }
-               }
-       }
-       var result []*common.Order
-       for i := begin; i < len(orders) && len(result) < 3; i++ {
-               result = append(result, orders[i])
-       }
-       return result, nil
-}
-
-func (m *MockMovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
-       begin := len(m.TradePairs)
-       if fromAssetIDAfter == nil || toAssetIDAfter == nil {
-               begin = 0
-       } else {
-               for i, tradePair := range m.TradePairs {
-                       if *tradePair.FromAssetID == *fromAssetIDAfter && *tradePair.ToAssetID == *toAssetIDAfter {
-                               begin = i + 1
-                               break
-                       }
-               }
-       }
-       var result []*common.TradePair
-       for i := begin; i < len(m.TradePairs) && len(result) < 3; i++ {
-               result = append(result, m.TradePairs[i])
-       }
-       return result, nil
-}
-
-func (m *MockMovStore) ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error {
-       for _, order := range addOrders {
-               tradePair := &common.TradePair{FromAssetID: order.FromAssetID, ToAssetID: order.ToAssetID}
-               m.OrderMap[tradePair.Key()] = append(m.OrderMap[tradePair.Key()], order)
-       }
-       for _, delOrder := range delOrders {
-               tradePair := &common.TradePair{FromAssetID: delOrder.FromAssetID, ToAssetID: delOrder.ToAssetID}
-               orders := m.OrderMap[tradePair.Key()]
-               for i, order := range orders {
-                       if delOrder.Key() == order.Key() {
-                               m.OrderMap[tradePair.Key()] = append(orders[0:i], orders[i+1:]...)
-                       }
-               }
-       }
-       for _, orders := range m.OrderMap {
-               sort.Sort(common.OrderSlice(orders))
-       }
-
-       if blockHeader.Height == m.DBState.Height {
-               m.DBState = &common.MovDatabaseState{Height: blockHeader.Height - 1, Hash: &blockHeader.PreviousBlockHash}
-       } else if blockHeader.Height == m.DBState.Height+1 {
-               blockHash := blockHeader.Hash()
-               m.DBState = &common.MovDatabaseState{Height: blockHeader.Height, Hash: &blockHash}
-       } else {
-               return errors.New("error block header")
-       }
-       return nil
-}
index 3591432..0bc8f61 100644 (file)
@@ -1,6 +1,7 @@
 package database
 
 import (
+       "github.com/vapor/application/mov/mock"
        "testing"
 
        "github.com/vapor/application/mov/common"
@@ -106,7 +107,7 @@ func TestTradePairIterator(t *testing.T) {
        }
 
        for i, c := range cases {
-               store := &MockMovStore{TradePairs: c.storeTradePairs}
+               store := mock.NewMovStore(c.storeTradePairs, nil)
                var gotTradePairs []*common.TradePair
                iterator := NewTradePairIterator(store)
                for iterator.HasNext() {
@@ -152,8 +153,7 @@ func TestOrderIterator(t *testing.T) {
        }
 
        for i, c := range cases {
-               orderMap := map[string][]*common.Order{c.tradePair.Key(): c.storeOrders}
-               store := &MockMovStore{OrderMap: orderMap}
+               store := mock.NewMovStore(nil, c.storeOrders)
 
                var gotOrders []*common.Order
                iterator := NewOrderIterator(store, c.tradePair)
index 9be669a..ac40417 100644 (file)
@@ -52,17 +52,6 @@ func calcTradePairKey(fromAssetID, toAssetID *bc.AssetID) []byte {
        return append(key, toAssetID.Bytes()...)
 }
 
-func calcUTXOHash(order *common.Order) *bc.Hash {
-       prog := &bc.Program{VmVersion: 1, Code: order.Utxo.ControlProgram}
-       src := &bc.ValueSource{
-               Ref:      order.Utxo.SourceID,
-               Value:    &bc.AssetAmount{AssetId: order.FromAssetID, Amount: order.Utxo.Amount},
-               Position: order.Utxo.SourcePos,
-       }
-       hash := bc.EntryID(bc.NewIntraChainOutput(src, prog, 0))
-       return &hash
-}
-
 func getAssetIDFromTradePairKey(key []byte, prefix []byte, posIndex int) *bc.AssetID {
        b := [32]byte{}
        pos := len(prefix) + assetIDLen*posIndex
@@ -84,7 +73,7 @@ type LevelDBMovStore struct {
        db dbm.DB
 }
 
-func NewLevelDBMovStore(db dbm.DB) *LevelDBMovStore {
+func NewLevelDBMovStore(db dbm.DB) *LevelDBMovStore {
        return &LevelDBMovStore{db: db}
 }
 
@@ -109,7 +98,7 @@ func (m *LevelDBMovStore) ListOrders(orderAfter *common.Order) ([]*common.Order,
 
        var startKey []byte
        if orderAfter.Rate > 0 {
-               startKey = calcOrderKey(orderAfter.FromAssetID, orderAfter.ToAssetID, calcUTXOHash(orderAfter), orderAfter.Rate)
+               startKey = calcOrderKey(orderAfter.FromAssetID, orderAfter.ToAssetID, orderAfter.UTXOHash(), orderAfter.Rate)
        }
 
        itr := m.db.IteratorPrefixWithStart(orderPrefix, startKey, false)
@@ -170,7 +159,7 @@ func (m *LevelDBMovStore) addOrders(batch dbm.Batch, orders []*common.Order, tra
                        return err
                }
 
-               key := calcOrderKey(order.FromAssetID, order.ToAssetID, calcUTXOHash(order), order.Rate)
+               key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate)
                batch.Set(key, data)
 
                tradePair := &common.TradePair{
@@ -187,7 +176,7 @@ func (m *LevelDBMovStore) addOrders(batch dbm.Batch, orders []*common.Order, tra
 
 func (m *LevelDBMovStore) deleteOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[string]*common.TradePair) {
        for _, order := range orders {
-               key := calcOrderKey(order.FromAssetID, order.ToAssetID, calcUTXOHash(order), order.Rate)
+               key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate)
                batch.Delete(key)
 
                tradePair := &common.TradePair{
index c35afca..2ebb319 100644 (file)
@@ -48,7 +48,7 @@ func TestCalcUTXOHash(t *testing.T) {
                },
        }
 
-       hash := calcUTXOHash(order)
+       hash := order.UTXOHash()
        if hash.String() != wantHash {
                t.Fatal("The function is incorrect")
        }
@@ -328,7 +328,7 @@ func TestSortOrderKey(t *testing.T) {
 
        for i, c := range cases {
                for _, order := range c.orders {
-                       key := calcOrderKey(order.FromAssetID, order.ToAssetID, calcUTXOHash(&order), order.Rate)
+                       key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate)
                        data, err := json.Marshal(order.Utxo)
                        if err != nil {
                                t.Fatal(err)
index ee38a0a..4e16cb2 100644 (file)
@@ -3,178 +3,68 @@ package match
 import (
        "testing"
 
-       "github.com/vapor/protocol/vm"
-       "github.com/vapor/testutil"
-
        "github.com/vapor/application/mov/common"
-       "github.com/vapor/application/mov/database"
-       "github.com/vapor/protocol/bc"
+       "github.com/vapor/application/mov/mock"
        "github.com/vapor/protocol/bc/types"
-       "github.com/vapor/protocol/vm/vmutil"
-)
-
-var (
-       btc = bc.NewAssetID([32]byte{1})
-       eth = bc.NewAssetID([32]byte{2})
-
-       orders = []*common.Order{
-               // btc -> eth
-               {
-                       FromAssetID: &btc,
-                       ToAssetID:   &eth,
-                       Rate:        50,
-                       Utxo: &common.MovUtxo{
-                               SourceID:       hashPtr(testutil.MustDecodeHash("37b8edf656e45a7addf47f5626e114a8c394d918a36f61b5a2905675a09b40ae")),
-                               SourcePos:      0,
-                               Amount:         10,
-                               ControlProgram: mustCreateP2WMCProgram(eth, testutil.MustDecodeHexString("51"), 50, 1),
-                       },
-               },
-               {
-                       FromAssetID: &btc,
-                       ToAssetID:   &eth,
-                       Rate:        53,
-                       Utxo: &common.MovUtxo{
-                               SourceID:       hashPtr(testutil.MustDecodeHash("3ec2bbfb499a8736d377b547eee5392bcddf7ec2b287e9ed20b5938c3d84e7cd")),
-                               SourcePos:      0,
-                               Amount:         20,
-                               ControlProgram: mustCreateP2WMCProgram(eth, testutil.MustDecodeHexString("52"), 53, 1),
-                       },
-               },
-
-               // eth -> btc
-               {
-                       FromAssetID: &eth,
-                       ToAssetID:   &btc,
-                       Rate:        1 / 51.0,
-                       Utxo: &common.MovUtxo{
-                               SourceID:       hashPtr(testutil.MustDecodeHash("fba43ff5155209cb1769e2ec0e1d4a33accf899c740865edfc6d1de39b873b29")),
-                               SourcePos:      0,
-                               Amount:         510,
-                               ControlProgram: mustCreateP2WMCProgram(btc, testutil.MustDecodeHexString("53"), 1, 51.0),
-                       },
-               },
-               {
-                       FromAssetID: &eth,
-                       ToAssetID:   &btc,
-                       Rate:        1 / 52.0,
-                       Utxo: &common.MovUtxo{
-                               SourceID:       hashPtr(testutil.MustDecodeHash("05f24bb847db823075d81786aa270748e02602199cd009c0284f928503846a5a")),
-                               SourcePos:      0,
-                               Amount:         416,
-                               ControlProgram: mustCreateP2WMCProgram(btc, testutil.MustDecodeHexString("54"), 1, 52.0),
-                       },
-               },
-               {
-                       FromAssetID: &eth,
-                       ToAssetID:   &btc,
-                       Rate:        1 / 54.0,
-                       Utxo: &common.MovUtxo{
-                               SourceID:       hashPtr(testutil.MustDecodeHash("119a02980796dc352cf6475457463aef5666f66622088de3551fa73a65f0d201")),
-                               SourcePos:      0,
-                               Amount:         810,
-                               ControlProgram: mustCreateP2WMCProgram(btc, testutil.MustDecodeHexString("55"), 1, 54.0),
-                       },
-               },
-       }
 )
 
 func TestGenerateMatchedTxs(t *testing.T) {
-       btc2eth := &common.TradePair{FromAssetID: &btc, ToAssetID: &eth}
-       eth2btc := &common.TradePair{FromAssetID: &eth, ToAssetID: &btc}
+       btc2eth := &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH}
+       eth2btc := &common.TradePair{FromAssetID: &mock.ETH, ToAssetID: &mock.BTC}
 
        cases := []struct {
-               desc           string
-               tradePair      *common.TradePair
-               storeOrderMap  map[string][]*common.Order
-               wantMatchedTxs []*types.TxData
+               desc            string
+               tradePair       *common.TradePair
+               initStoreOrders []*common.Order
+               wantMatchedTxs  []*types.Tx
        }{
                {
                        desc:      "full matched",
-                       tradePair: &common.TradePair{FromAssetID: &btc, ToAssetID: &eth},
-                       storeOrderMap: map[string][]*common.Order{
-                               btc2eth.Key(): {orders[0], orders[1]},
-                               eth2btc.Key(): {orders[2], orders[3]},
+                       tradePair: &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH},
+                       initStoreOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+                               mock.Eth2BtcOrders[0],
                        },
-                       wantMatchedTxs: []*types.TxData{
-                               {
-                                       Inputs: []*types.TxInput{
-                                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *orders[0].Utxo.SourceID, *orders[0].FromAssetID, orders[0].Utxo.Amount, orders[0].Utxo.SourcePos, orders[0].Utxo.ControlProgram),
-                                               types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *orders[2].Utxo.SourceID, *orders[2].FromAssetID, orders[2].Utxo.Amount, orders[2].Utxo.SourcePos, orders[2].Utxo.ControlProgram),
-                                       },
-                                       Outputs: []*types.TxOutput{
-                                               types.NewIntraChainOutput(*orders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
-                                               types.NewIntraChainOutput(*orders[2].ToAssetID, 10, testutil.MustDecodeHexString("53")),
-                                               types.NewIntraChainOutput(*orders[0].ToAssetID, 10, []byte{0x51}),
-                                       },
-                               },
+                       wantMatchedTxs: []*types.Tx{
+                               mock.MatchedTxs[1],
                        },
                },
                {
                        desc:      "partial matched",
-                       tradePair: &common.TradePair{FromAssetID: &btc, ToAssetID: &eth},
-                       storeOrderMap: map[string][]*common.Order{
-                               btc2eth.Key(): {orders[0], orders[1]},
-                               eth2btc.Key(): {orders[3]},
+                       tradePair: &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH},
+                       initStoreOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+                               mock.Eth2BtcOrders[1],
                        },
-                       wantMatchedTxs: []*types.TxData{
-                               {
-                                       Inputs: []*types.TxInput{
-                                               types.NewSpendInput([][]byte{vm.Int64Bytes(416), vm.Int64Bytes(0), vm.Int64Bytes(0)}, *orders[0].Utxo.SourceID, *orders[0].FromAssetID, orders[0].Utxo.Amount, orders[0].Utxo.SourcePos, orders[0].Utxo.ControlProgram),
-                                               types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, *orders[3].Utxo.SourceID, *orders[3].FromAssetID, orders[3].Utxo.Amount, orders[3].Utxo.SourcePos, orders[3].Utxo.ControlProgram),
-                                       },
-                                       Outputs: []*types.TxOutput{
-                                               types.NewIntraChainOutput(*orders[0].ToAssetID, 416, testutil.MustDecodeHexString("51")),
-                                               // re-order
-                                               types.NewIntraChainOutput(*orders[0].FromAssetID, 2, orders[0].Utxo.ControlProgram),
-                                               types.NewIntraChainOutput(*orders[3].ToAssetID, 8, testutil.MustDecodeHexString("54")),
-                                       },
-                               },
+                       wantMatchedTxs: []*types.Tx{
+                               mock.MatchedTxs[0],
                        },
                },
                {
                        desc:      "partial matched and continue to match",
-                       tradePair: &common.TradePair{FromAssetID: &btc, ToAssetID: &eth},
-                       storeOrderMap: map[string][]*common.Order{
-                               btc2eth.Key(): {orders[0], orders[1]},
-                               eth2btc.Key(): {orders[4]},
+                       tradePair: &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH},
+                       initStoreOrders: []*common.Order{
+                               mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+                               mock.Eth2BtcOrders[2],
                        },
-                       wantMatchedTxs: []*types.TxData{
-                               {
-                                       Inputs: []*types.TxInput{
-                                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *orders[0].Utxo.SourceID, *orders[0].FromAssetID, orders[0].Utxo.Amount, orders[0].Utxo.SourcePos, orders[0].Utxo.ControlProgram),
-                                               types.NewSpendInput([][]byte{vm.Int64Bytes(10), vm.Int64Bytes(1), vm.Int64Bytes(0)}, *orders[4].Utxo.SourceID, *orders[4].FromAssetID, orders[4].Utxo.Amount, orders[4].Utxo.SourcePos, orders[4].Utxo.ControlProgram),
-                                       },
-                                       Outputs: []*types.TxOutput{
-                                               types.NewIntraChainOutput(*orders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
-                                               types.NewIntraChainOutput(*orders[4].ToAssetID, 10, testutil.MustDecodeHexString("55")),
-                                               // re-order
-                                               types.NewIntraChainOutput(*orders[4].FromAssetID, 270, orders[4].Utxo.ControlProgram),
-                                               // fee
-                                               types.NewIntraChainOutput(*orders[4].FromAssetID, 27, []byte{0x51}),
-                                               // refund
-                                               types.NewIntraChainOutput(*orders[4].FromAssetID, 6, testutil.MustDecodeHexString("51")),
-                                               types.NewIntraChainOutput(*orders[4].FromAssetID, 7, testutil.MustDecodeHexString("55")),
-                                       },
-                               },
-                               {
-                                       Inputs: []*types.TxInput{
-                                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(0)}, *orders[1].Utxo.SourceID, *orders[1].FromAssetID, orders[1].Utxo.Amount, orders[1].Utxo.SourcePos, orders[1].Utxo.ControlProgram),
-                                               types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, testutil.MustDecodeHash("f47177c12d25f5316eb377ea006e77bf07e4f9646860e4641e313e004f9aa989"), *orders[4].FromAssetID, 270, 2, orders[4].Utxo.ControlProgram),
-                                       },
-                                       Outputs: []*types.TxOutput{
-                                               types.NewIntraChainOutput(*orders[1].ToAssetID, 270, testutil.MustDecodeHexString("52")),
-                                               // re-order
-                                               types.NewIntraChainOutput(*orders[1].FromAssetID, 15, orders[1].Utxo.ControlProgram),
-                                               types.NewIntraChainOutput(*orders[4].ToAssetID, 5, testutil.MustDecodeHexString("55")),
-                                       },
-                               },
+                       wantMatchedTxs: []*types.Tx{
+                               mock.MatchedTxs[2],
+                               mock.MatchedTxs[3],
                        },
                },
+               {
+                       desc:      "unable to match",
+                       tradePair: &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH},
+                       initStoreOrders: []*common.Order{
+                               mock.Btc2EthOrders[1],
+                               mock.Eth2BtcOrders[0],
+                       },
+                       wantMatchedTxs: []*types.Tx{},
+               },
        }
 
        for i, c := range cases {
-               movStore := &database.MockMovStore{OrderMap: c.storeOrderMap}
+               movStore := mock.NewMovStore([]*common.TradePair{btc2eth, eth2btc}, c.initStoreOrders)
                matchEngine := NewEngine(NewOrderTable(movStore, nil, nil), 0.05, []byte{0x51})
                var gotMatchedTxs []*types.Tx
                for matchEngine.HasMatchedTx(c.tradePair, c.tradePair.Reverse()) {
@@ -199,29 +89,10 @@ func TestGenerateMatchedTxs(t *testing.T) {
                        }
 
                        c.wantMatchedTxs[i].SerializedSize = uint64(len(byteData))
-                       wantMatchedTx := types.NewTx(*c.wantMatchedTxs[i])
+                       wantMatchedTx := types.NewTx(c.wantMatchedTxs[i].TxData)
                        if gotMatchedTx.ID != wantMatchedTx.ID {
                                t.Errorf("#%d(%s) the tx hash of got matched tx: %s is not equals want matched tx: %s", i, c.desc, gotMatchedTx.ID.String(), wantMatchedTx.ID.String())
                        }
                }
        }
 }
-
-func mustCreateP2WMCProgram(requestAsset bc.AssetID, sellerProgram []byte, ratioMolecule, ratioDenominator int64) []byte {
-       contractArgs := vmutil.MagneticContractArgs{
-               RequestedAsset:   requestAsset,
-               RatioNumerator:   ratioMolecule,
-               RatioDenominator: ratioDenominator,
-               SellerProgram:    sellerProgram,
-               SellerKey:        testutil.MustDecodeHexString("ad79ec6bd3a6d6dbe4d0ee902afc99a12b9702fb63edce5f651db3081d868b75"),
-       }
-       program, err := vmutil.P2WMCProgram(contractArgs)
-       if err != nil {
-               panic(err)
-       }
-       return program
-}
-
-func hashPtr(hash bc.Hash) *bc.Hash {
-       return &hash
-}
diff --git a/application/mov/match/order_table_test.go b/application/mov/match/order_table_test.go
new file mode 100644 (file)
index 0000000..898f09e
--- /dev/null
@@ -0,0 +1,182 @@
+package match
+
+import (
+       "testing"
+
+       "github.com/vapor/application/mov/mock"
+       "github.com/vapor/application/mov/common"
+       "github.com/vapor/application/mov/database"
+)
+
+var (
+       btc2eth = &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH}
+)
+
+func TestOrderTable(t *testing.T) {
+       cases := []struct {
+               desc                 string
+               initMovStore         database.MovStore
+               initArrivalAddOrders []*common.Order
+               initArrivalDelOrders []*common.Order
+               addOrders            []*common.Order
+               popOrders            []*common.TradePair
+               wantPeekedOrders     map[common.TradePair]*common.Order
+       }{
+               {
+                       desc: "no arrival orders, no add order, no pop order",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+                               },
+                       ),
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[0],
+                       },
+               },
+               {
+                       desc: "no arrival orders, add lower price order, no pop order",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+                               }),
+                       addOrders: []*common.Order{mock.Btc2EthOrders[3]},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[3],
+                       },
+               },
+               {
+                       desc: "no arrival orders, no add order, pop one order",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+                               }),
+                       popOrders: []*common.TradePair{btc2eth},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[2],
+                       },
+               },
+               {
+                       desc: "has arrival add orders, no add order, no pop order, the arrival add order is lower price",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+                               }),
+                       initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[3]},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[3],
+                       },
+               },
+               {
+                       desc: "has arrival add orders, no add order, no pop order, the db add order is lower price",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+                               }),
+                       initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[2]},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[0],
+                       },
+               },
+               {
+                       desc: "has arrival add orders, no add order, pop one order, after pop the arrival order is lower price",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+                               }),
+                       initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+                       popOrders: []*common.TradePair{btc2eth},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[0],
+                       },
+               },
+               {
+                       desc: "has arrival delete orders, no add order, no pop order",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+                               }),
+                       initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3]},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[2],
+                       },
+               },
+               {
+                       desc: "has arrival delete orders and arrival add orders, no add order, no pop order, the arrival order is lower price",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[3], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+                               }),
+                       initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+                       initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3]},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[0],
+                       },
+               },
+               {
+                       desc: "has arrival delete orders and arrival add orders, no add order, pop one order",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{
+                                       mock.Btc2EthOrders[3], mock.Btc2EthOrders[1],
+                               }),
+                       initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[2]},
+                       initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3]},
+                       popOrders: []*common.TradePair{btc2eth},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[2],
+                       },
+               },
+               {
+                       desc: "has arrival add orders, but db order is empty",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{}),
+                       initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[2]},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: mock.Btc2EthOrders[0],
+                       },
+               },
+               {
+                       desc: "no add orders, and db order is empty",
+                       initMovStore: mock.NewMovStore(
+                               []*common.TradePair{btc2eth},
+                               []*common.Order{}),
+                       initArrivalAddOrders: []*common.Order{},
+                       wantPeekedOrders: map[common.TradePair]*common.Order{
+                               *btc2eth: nil,
+                       },
+               },
+       }
+
+       for i, c := range cases {
+               orderTable := NewOrderTable(c.initMovStore, c.initArrivalAddOrders, c.initArrivalDelOrders)
+               for _, order := range c.addOrders {
+                       if err := orderTable.AddOrder(order); err != nil {
+                               t.Fatal(err)
+                       }
+               }
+
+               for _, tradePair := range c.popOrders {
+                       orderTable.PopOrder(tradePair)
+               }
+
+               for tradePair, wantOrder := range c.wantPeekedOrders {
+                       gotOrder := orderTable.PeekOrder(&tradePair)
+                       if wantOrder == gotOrder && wantOrder == nil {
+                               continue
+                       }
+
+                       if wantOrder != nil && gotOrder != nil && gotOrder.Key() != wantOrder.Key() {
+                               t.Errorf("#%d(%s):the key of got order(%v) is not equals key of want order(%v)", i, c.desc, gotOrder, wantOrder)
+                       }
+               }
+       }
+}
diff --git a/application/mov/mock/mock.go b/application/mov/mock/mock.go
new file mode 100644 (file)
index 0000000..a23c004
--- /dev/null
@@ -0,0 +1,241 @@
+package mock
+
+import (
+       "github.com/vapor/application/mov/common"
+       "github.com/vapor/protocol/bc"
+       "github.com/vapor/protocol/bc/types"
+       "github.com/vapor/protocol/vm"
+       "github.com/vapor/protocol/vm/vmutil"
+       "github.com/vapor/testutil"
+)
+
+var (
+       BTC = bc.NewAssetID([32]byte{1})
+       ETH = bc.NewAssetID([32]byte{2})
+
+       Btc2EthOrders = []*common.Order{
+               {
+                       FromAssetID: &BTC,
+                       ToAssetID:   &ETH,
+                       Rate:        50,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("37b8edf656e45a7addf47f5626e114a8c394d918a36f61b5a2905675a09b40ae")),
+                               SourcePos:      0,
+                               Amount:         10,
+                               ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("51"), 50, 1),
+                       },
+               },
+               {
+                       FromAssetID: &BTC,
+                       ToAssetID:   &ETH,
+                       Rate:        53,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("3ec2bbfb499a8736d377b547eee5392bcddf7ec2b287e9ed20b5938c3d84e7cd")),
+                               SourcePos:      0,
+                               Amount:         20,
+                               ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("52"), 53, 1),
+                       },
+               },
+               {
+                       FromAssetID: &BTC,
+                       ToAssetID:   &ETH,
+                       Rate:        52,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("1232bbfb499a8736d377b547eee5392bcddf7ec2b287e9ed20b5938c3d84e7cd")),
+                               SourcePos:      0,
+                               Amount:         15,
+                               ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("52"), 53, 1),
+                       },
+               },
+               {
+                       FromAssetID: &BTC,
+                       ToAssetID:   &ETH,
+                       Rate:        49,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("7872bbfb499a8736d377b547eee5392bcddf7ec2b287e9ed20b5938c3d84e7cd")),
+                               SourcePos:      0,
+                               Amount:         17,
+                               ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("52"), 53, 1),
+                       },
+               },
+       }
+
+       Eth2BtcOrders = []*common.Order{
+               {
+                       FromAssetID: &ETH,
+                       ToAssetID:   &BTC,
+                       Rate:        1 / 51.0,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("fba43ff5155209cb1769e2ec0e1d4a33accf899c740865edfc6d1de39b873b29")),
+                               SourcePos:      0,
+                               Amount:         510,
+                               ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("53"), 1, 51.0),
+                       },
+               },
+               {
+                       FromAssetID: &ETH,
+                       ToAssetID:   &BTC,
+                       Rate:        1 / 52.0,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("05f24bb847db823075d81786aa270748e02602199cd009c0284f928503846a5a")),
+                               SourcePos:      0,
+                               Amount:         416,
+                               ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("54"), 1, 52.0),
+                       },
+               },
+               {
+                       FromAssetID: &ETH,
+                       ToAssetID:   &BTC,
+                       Rate:        1 / 54.0,
+                       Utxo: &common.MovUtxo{
+                               SourceID:       hashPtr(testutil.MustDecodeHash("119a02980796dc352cf6475457463aef5666f66622088de3551fa73a65f0d201")),
+                               SourcePos:      0,
+                               Amount:         810,
+                               ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("55"), 1, 54.0),
+                       },
+               },
+       }
+
+       Btc2EthMakerTxs = []*types.Tx {
+               // Btc2EthOrders[0]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.ControlProgram)},
+               }),
+               // Btc2EthOrders[1]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Btc2EthOrders[1].Utxo.SourceID, *Btc2EthOrders[1].FromAssetID, Btc2EthOrders[1].Utxo.Amount, Btc2EthOrders[1].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Btc2EthOrders[1].FromAssetID, Btc2EthOrders[1].Utxo.Amount, Btc2EthOrders[1].Utxo.ControlProgram)},
+               }),
+               // Btc2EthOrders[2]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Btc2EthOrders[2].Utxo.SourceID, *Btc2EthOrders[2].FromAssetID, Btc2EthOrders[2].Utxo.Amount, Btc2EthOrders[2].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Btc2EthOrders[2].FromAssetID, Btc2EthOrders[2].Utxo.Amount, Btc2EthOrders[2].Utxo.ControlProgram)},
+               }),
+               // Btc2EthOrders[3]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Btc2EthOrders[3].Utxo.SourceID, *Btc2EthOrders[3].FromAssetID, Btc2EthOrders[3].Utxo.Amount, Btc2EthOrders[3].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Btc2EthOrders[3].FromAssetID, Btc2EthOrders[3].Utxo.Amount, Btc2EthOrders[3].Utxo.ControlProgram)},
+               }),
+       }
+
+       Eth2BtcMakerTxs = []*types.Tx {
+               // Eth2Btc[0]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Eth2BtcOrders[0].Utxo.SourceID, *Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, Eth2BtcOrders[0].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, Eth2BtcOrders[0].Utxo.ControlProgram)},
+               }),
+               // Eth2Btc[1]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Eth2BtcOrders[1].Utxo.SourceID, *Eth2BtcOrders[1].FromAssetID, Eth2BtcOrders[1].Utxo.Amount, Eth2BtcOrders[1].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Eth2BtcOrders[1].FromAssetID, Eth2BtcOrders[1].Utxo.Amount, Eth2BtcOrders[1].Utxo.ControlProgram)},
+               }),
+               // Eth2Btc[2]
+               types.NewTx(types.TxData{
+                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, *Eth2BtcOrders[2].Utxo.SourceID, *Eth2BtcOrders[2].FromAssetID, Eth2BtcOrders[2].Utxo.Amount, Eth2BtcOrders[2].Utxo.SourcePos, []byte{0x51})},
+                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, Eth2BtcOrders[2].Utxo.Amount, Eth2BtcOrders[2].Utxo.ControlProgram)},
+               }),
+       }
+       
+       MatchedTxs = []*types.Tx{
+               // partial matched transaction from Btc2EthOrders[0], Eth2BtcOrders[1]
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(416), vm.Int64Bytes(0), vm.Int64Bytes(0)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, *Eth2BtcOrders[1].Utxo.SourceID, *Eth2BtcOrders[1].FromAssetID, Eth2BtcOrders[1].Utxo.Amount, Eth2BtcOrders[1].Utxo.SourcePos, Eth2BtcOrders[1].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 416, testutil.MustDecodeHexString("51")),
+                               // re-order
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 2, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 8, testutil.MustDecodeHexString("54")),
+                       },
+               }),
+               
+               // full matched transaction from Btc2EthOrders[0], Eth2BtcOrders[0]
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *Eth2BtcOrders[0].Utxo.SourceID, *Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, Eth2BtcOrders[0].Utxo.SourcePos, Eth2BtcOrders[0].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
+                       },
+               }),
+
+               // partial matched transaction from Btc2EthOrders[0], Eth2BtcOrders[2]
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(10), vm.Int64Bytes(1), vm.Int64Bytes(0)}, *Eth2BtcOrders[2].Utxo.SourceID, *Eth2BtcOrders[2].FromAssetID, Eth2BtcOrders[2].Utxo.Amount, Eth2BtcOrders[2].Utxo.SourcePos, Eth2BtcOrders[2].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 10, testutil.MustDecodeHexString("55")),
+                               // re-order
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 270, Eth2BtcOrders[2].Utxo.ControlProgram),
+                               // fee
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 27, []byte{0x51}),
+                               // refund
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 6, testutil.MustDecodeHexString("51")),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 7, testutil.MustDecodeHexString("55")),
+                       },
+               }),
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(0)}, *Btc2EthOrders[1].Utxo.SourceID, *Btc2EthOrders[1].FromAssetID, Btc2EthOrders[1].Utxo.Amount, Btc2EthOrders[1].Utxo.SourcePos, Btc2EthOrders[1].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, testutil.MustDecodeHash("f47177c12d25f5316eb377ea006e77bf07e4f9646860e4641e313e004f9aa989"), *Eth2BtcOrders[2].FromAssetID, 270, 2, Eth2BtcOrders[2].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Btc2EthOrders[1].ToAssetID, 270, testutil.MustDecodeHexString("52")),
+                               // re-order
+                               types.NewIntraChainOutput(*Btc2EthOrders[1].FromAssetID, 15, Btc2EthOrders[1].Utxo.ControlProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 5, testutil.MustDecodeHexString("55")),
+                       },
+               }),
+
+               // partial matched transaction from Btc2EthMakerTxs[0], Eth2BtcMakerTxs[1]
+               types.NewTx(types.TxData{
+                       Inputs: []*types.TxInput{
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Btc2EthMakerTxs[0], 0).Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, 0, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Eth2BtcMakerTxs[1], 0).Utxo.SourceID, *Eth2BtcOrders[1].FromAssetID, Eth2BtcOrders[1].Utxo.Amount, 0, Eth2BtcOrders[1].Utxo.ControlProgram),
+                       },
+                       Outputs: []*types.TxOutput{
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 416, testutil.MustDecodeHexString("51")),
+                               // re-order
+                               types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 2, Btc2EthOrders[0].Utxo.ControlProgram),
+                               types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 8, testutil.MustDecodeHexString("54")),
+                       },
+               }),
+       }
+)
+
+func MustCreateP2WMCProgram(requestAsset bc.AssetID, sellerProgram []byte, ratioNumerator, ratioDenominator int64) []byte {
+       contractArgs := vmutil.MagneticContractArgs{
+               RequestedAsset:   requestAsset,
+               RatioNumerator:   ratioNumerator,
+               RatioDenominator: ratioDenominator,
+               SellerProgram:    sellerProgram,
+               SellerKey:        testutil.MustDecodeHexString("ad79ec6bd3a6d6dbe4d0ee902afc99a12b9702fb63edce5f651db3081d868b75"),
+       }
+       program, err := vmutil.P2WMCProgram(contractArgs)
+       if err != nil {
+               panic(err)
+       }
+       return program
+}
+
+func MustNewOrderFromOutput(tx *types.Tx, outputIndex int) *common.Order {
+       order, err := common.NewOrderFromOutput(tx, outputIndex)
+       if err != nil {
+               panic(err)
+       }
+
+       return order
+}
+
+func hashPtr(hash bc.Hash) *bc.Hash {
+       return &hash
+}
diff --git a/application/mov/mock/mock_mov_store.go b/application/mov/mock/mock_mov_store.go
new file mode 100644 (file)
index 0000000..fc5c6db
--- /dev/null
@@ -0,0 +1,108 @@
+package mock
+
+import (
+       "sort"
+
+       "github.com/vapor/application/mov/common"
+       "github.com/vapor/errors"
+       "github.com/vapor/protocol/bc"
+       "github.com/vapor/protocol/bc/types"
+)
+
+type MovStore struct {
+       tradePairs []*common.TradePair
+       orderMap   map[string][]*common.Order
+       dbState    *common.MovDatabaseState
+}
+
+func NewMovStore(tradePairs []*common.TradePair, orders []*common.Order) *MovStore {
+       orderMap := make(map[string][]*common.Order)
+       for _, order := range orders {
+               orderMap[order.TradePair().Key()] = append(orderMap[order.TradePair().Key()], order)
+       }
+
+       for _, orders := range orderMap {
+               sort.Sort(common.OrderSlice(orders))
+       }
+       return &MovStore{
+               tradePairs: tradePairs,
+               orderMap:   orderMap,
+       }
+}
+
+func (m *MovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
+       return m.dbState, nil
+}
+
+func (m *MovStore) InitDBState(height uint64, hash *bc.Hash) error {
+       return nil
+}
+
+func (m *MovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
+       tradePair := &common.TradePair{FromAssetID: orderAfter.FromAssetID, ToAssetID: orderAfter.ToAssetID}
+       orders := m.orderMap[tradePair.Key()]
+       begin := len(orders)
+       if orderAfter.Rate == 0 {
+               begin = 0
+       } else {
+               for i, order := range orders {
+                       if order.Rate == orderAfter.Rate {
+                               begin = i + 1
+                               break
+                       }
+               }
+       }
+       var result []*common.Order
+       for i := begin; i < len(orders) && len(result) < 3; i++ {
+               result = append(result, orders[i])
+       }
+       return result, nil
+}
+
+func (m *MovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
+       begin := len(m.tradePairs)
+       if fromAssetIDAfter == nil || toAssetIDAfter == nil {
+               begin = 0
+       } else {
+               for i, tradePair := range m.tradePairs {
+                       if *tradePair.FromAssetID == *fromAssetIDAfter && *tradePair.ToAssetID == *toAssetIDAfter {
+                               begin = i + 1
+                               break
+                       }
+               }
+       }
+       var result []*common.TradePair
+       for i := begin; i < len(m.tradePairs) && len(result) < 3; i++ {
+               result = append(result, m.tradePairs[i])
+       }
+       return result, nil
+}
+
+func (m *MovStore) ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error {
+       for _, order := range addOrders {
+               tradePair := &common.TradePair{FromAssetID: order.FromAssetID, ToAssetID: order.ToAssetID}
+               m.orderMap[tradePair.Key()] = append(m.orderMap[tradePair.Key()], order)
+       }
+       for _, delOrder := range delOrders {
+               tradePair := &common.TradePair{FromAssetID: delOrder.FromAssetID, ToAssetID: delOrder.ToAssetID}
+               orders := m.orderMap[tradePair.Key()]
+               for i, order := range orders {
+                       if delOrder.Key() == order.Key() {
+                               m.orderMap[tradePair.Key()] = append(orders[0:i], orders[i+1:]...)
+                       }
+               }
+       }
+       for _, orders := range m.orderMap {
+               sort.Sort(common.OrderSlice(orders))
+       }
+
+       if blockHeader.Height == m.dbState.Height {
+               m.dbState = &common.MovDatabaseState{Height: blockHeader.Height - 1, Hash: &blockHeader.PreviousBlockHash}
+       } else if blockHeader.Height == m.dbState.Height+1 {
+               blockHash := blockHeader.Hash()
+               m.dbState = &common.MovDatabaseState{Height: blockHeader.Height, Hash: &blockHash}
+       } else {
+               return errors.New("error block header")
+       }
+       return nil
+}
index 5663f30..929d163 100644 (file)
@@ -18,7 +18,7 @@ const maxFeeRate = 0.05
 var (
        errInvalidTradePairs             = errors.New("The trade pairs in the tx input is invalid")
        errStatusFailMustFalse           = errors.New("status fail of transaction does not allow to be true")
-       errInputProgramMustP2WMCScript   = errors.New("input program of matched tx must p2wmc script")
+       errInputProgramMustP2WMCScript   = errors.New("input program of trade tx must p2wmc script")
        errExistCancelOrderInMatchedTx   = errors.New("can't exist cancel order in the matched transaction")
        errExistTradeInCancelOrderTx     = errors.New("can't exist trade in the cancel order transaction")
        errAmountOfFeeGreaterThanMaximum = errors.New("amount of fee greater than max fee amount")
@@ -57,7 +57,7 @@ func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
        return state.Height, state.Hash, nil
 }
 
-// ValidateBlock no need to verify the block header, becaure the first module has been verified.
+// ValidateBlock no need to verify the block header, because the first module has been verified.
 // just need to verify the transactions in the block.
 func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
        return m.ValidateTxs(block.Transactions, verifyResults)
diff --git a/application/mov/mov_core_test.go b/application/mov/mov_core_test.go
new file mode 100644 (file)
index 0000000..64d10ce
--- /dev/null
@@ -0,0 +1,444 @@
+package mov
+
+import (
+       "math"
+       "os"
+       "testing"
+
+       "github.com/vapor/application/mov/common"
+       "github.com/vapor/application/mov/database"
+       "github.com/vapor/application/mov/mock"
+       "github.com/vapor/consensus"
+       dbm "github.com/vapor/database/leveldb"
+       "github.com/vapor/protocol/bc"
+       "github.com/vapor/protocol/bc/types"
+       "github.com/vapor/protocol/vm"
+       "github.com/vapor/testutil"
+)
+
+func TestApplyBlock(t *testing.T) {
+       initBlockHeader := &types.BlockHeader{Height: 1, PreviousBlockHash: bc.Hash{}}
+       cases := []struct {
+               desc        string
+               block       *types.Block
+               blockFunc   testFun
+               initOrders  []*common.Order
+               wantOrders  []*common.Order
+               wantDBState *common.MovDatabaseState
+               wantError   error
+       }{
+               {
+                       desc: "apply block has pending order transaction",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+                               Transactions: []*types.Tx{
+                                       mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[0],
+                               },
+                       },
+                       blockFunc:   applyBlock,
+                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0)},
+                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+               },
+               {
+                       desc: "apply block has full matched transaction",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[1],
+                               },
+                       },
+                       blockFunc:   applyBlock,
+                       initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
+                       wantOrders:  []*common.Order{mock.Btc2EthOrders[1]},
+                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+               },
+               {
+                       desc: "apply block has partial matched transaction",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[0],
+                               },
+                       },
+                       blockFunc:   applyBlock,
+                       initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
+                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
+                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+               },
+               {
+                       desc: "apply block has two partial matched transaction",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[2], mock.MatchedTxs[3],
+                               },
+                       },
+                       blockFunc:   applyBlock,
+                       initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
+                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
+                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+               },
+               {
+                       desc: "apply block has partial matched transaction by pending orders from tx pool",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+                               Transactions: []*types.Tx{
+                                       mock.Btc2EthMakerTxs[0],
+                                       mock.Eth2BtcMakerTxs[1],
+                                       mock.MatchedTxs[4],
+                               },
+                       },
+                       blockFunc:   applyBlock,
+                       initOrders:  []*common.Order{},
+                       wantOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1)},
+                       wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+               },
+               {
+                       desc: "detach block has pending order transaction",
+                       block: &types.Block{
+                               BlockHeader: *initBlockHeader,
+                               Transactions: []*types.Tx{
+                                       mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1],
+                               },
+                       },
+                       blockFunc:   detachBlock,
+                       initOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[1], 0)},
+                       wantOrders:  []*common.Order{},
+                       wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
+               },
+               {
+                       desc: "detach block has full matched transaction",
+                       block: &types.Block{
+                               BlockHeader: *initBlockHeader,
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[1],
+                               },
+                       },
+                       blockFunc:   detachBlock,
+                       initOrders:  []*common.Order{mock.Btc2EthOrders[1]},
+                       wantOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
+                       wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
+               },
+               {
+                       desc: "detach block has partial matched transaction",
+                       block: &types.Block{
+                               BlockHeader: *initBlockHeader,
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[0],
+                               },
+                       },
+                       blockFunc:   detachBlock,
+                       initOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
+                       wantOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
+                       wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
+               },
+               {
+                       desc: "detach block has two partial matched transaction",
+                       block: &types.Block{
+                               BlockHeader: *initBlockHeader,
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[2], mock.MatchedTxs[3],
+                               },
+                       },
+                       blockFunc:   detachBlock,
+                       initOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
+                       wantOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
+                       wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
+               },
+       }
+
+       defer os.RemoveAll("temp")
+       for i, c := range cases {
+               testDB := dbm.NewDB("testdb", "leveldb", "temp")
+               store := database.NewLevelDBMovStore(testDB)
+               if err := store.InitDBState(0, &bc.Hash{}); err != nil {
+                       t.Fatal(err)
+               }
+
+               if err := store.ProcessOrders(c.initOrders, nil, initBlockHeader); err != nil {
+                       t.Fatal(err)
+               }
+
+               movCore := &MovCore{movStore: store}
+               if err := c.blockFunc(movCore, c.block); err != c.wantError {
+                       t.Errorf("#%d(%s):apply block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
+               }
+
+               gotOrders := queryAllOrders(store)
+               if !ordersEquals(c.wantOrders, gotOrders) {
+                       t.Errorf("#%d(%s):apply block want orders(%v), got orders(%v)", i, c.desc, c.wantOrders, gotOrders)
+               }
+
+               dbState, err := store.GetMovDatabaseState()
+               if err != nil {
+                       t.Fatal(err)
+               }
+
+               if !testutil.DeepEqual(c.wantDBState, dbState) {
+                       t.Errorf("#%d(%s):apply block want db state(%v), got db state(%v)", i, c.desc, c.wantDBState, dbState)
+               }
+
+               testDB.Close()
+               os.RemoveAll("temp")
+       }
+}
+
+func TestValidateBlock(t *testing.T) {
+       cases := []struct {
+               desc          string
+               block         *types.Block
+               verifyResults []*bc.TxVerifyResult
+               wantError     error
+       }{
+               {
+                       desc: "block only has maker tx",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       mock.Eth2BtcMakerTxs[0],
+                                       mock.Btc2EthMakerTxs[0],
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}},
+                       wantError: nil,
+               },
+               {
+                       desc: "block only has matched tx",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[0],
+                                       mock.MatchedTxs[1],
+                                       mock.MatchedTxs[2],
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
+                       wantError: nil,
+               },
+               {
+                       desc: "block has maker tx and matched tx",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       mock.Eth2BtcMakerTxs[0],
+                                       mock.Btc2EthMakerTxs[0],
+                                       mock.MatchedTxs[0],
+                                       mock.MatchedTxs[1],
+                                       mock.MatchedTxs[2],
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
+                       wantError: nil,
+               },
+               {
+                       desc: "status fail of maker tx is true",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       mock.Eth2BtcMakerTxs[0],
+                                       mock.Btc2EthMakerTxs[0],
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
+                       wantError: errStatusFailMustFalse,
+               },
+               {
+                       desc: "status fail of matched tx is true",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       mock.MatchedTxs[1],
+                                       mock.MatchedTxs[2],
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
+                       wantError: errStatusFailMustFalse,
+               },
+               {
+                       desc: "asset id in matched tx is not unique",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               Inputs: []*types.TxInput{
+                                                       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),
+                                                       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),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
+                                                       types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
+                                               },
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
+                       wantError: errAssetIDMustUniqueInMatchedTx,
+               },
+               {
+                       desc: "common input in the matched tx",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               Inputs: []*types.TxInput{
+                                                       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),
+                                                       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),
+                                                       types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
+                                                       types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
+                                                       types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
+                                               },
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+                       wantError: errInputProgramMustP2WMCScript,
+               },
+               {
+                       desc: "cancel order in the matched tx",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               Inputs: []*types.TxInput{
+                                                       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),
+                                                       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),
+                                                       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),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
+                                                       types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
+                                                       types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
+                                               },
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+                       wantError: errExistCancelOrderInMatchedTx,
+               },
+               {
+                       desc: "common input in the cancel order tx",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               Inputs: []*types.TxInput{
+                                                       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),
+                                                       types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
+                                                       types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
+                                               },
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+                       wantError: errInputProgramMustP2WMCScript,
+               },
+               {
+                       desc: "amount of fee greater than max fee amount",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               Inputs: []*types.TxInput{
+                                                       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),
+                                                       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),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
+                                                       types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 10, testutil.MustDecodeHexString("55")),
+                                                       // re-order
+                                                       types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 270, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
+                                                       // fee
+                                                       types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 40, []byte{0x59}),
+                                               },
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+                       wantError: errAmountOfFeeGreaterThanMaximum,
+               },
+               {
+                       desc: "ratio numerator is zero",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               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})},
+                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("51"), 0, 1))},
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+                       wantError: errRatioOfTradeLessThanZero,
+               },
+               {
+                       desc: "ratio denominator is zero",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               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})},
+                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("51"), 1, 0))},
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+                       wantError: errRatioOfTradeLessThanZero,
+               },
+               {
+                       desc: "ratio numerator product input amount is overflow",
+                       block: &types.Block{
+                               Transactions: []*types.Tx{
+                                       types.NewTx(types.TxData{
+                                               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})},
+                                               Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("51"), math.MaxInt64, 10))},
+                                       }),
+                               },
+                       },
+                       verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+                       wantError: errNumeratorOfRatioIsOverflow,
+               },
+       }
+
+       for i, c := range cases {
+               movCore := &MovCore{}
+               if err := movCore.ValidateBlock(c.block, c.verifyResults); err != c.wantError {
+                       t.Errorf("#%d(%s):validate block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
+               }
+       }
+}
+
+type testFun func(movCore *MovCore, block *types.Block) error
+
+func applyBlock(movCore *MovCore, block *types.Block) error {
+       return movCore.ApplyBlock(block)
+}
+
+func detachBlock(movCore *MovCore, block *types.Block) error {
+       return movCore.DetachBlock(block)
+}
+
+func queryAllOrders(store *database.LevelDBMovStore) []*common.Order {
+       var orders []*common.Order
+       tradePairIterator := database.NewTradePairIterator(store)
+       for tradePairIterator.HasNext() {
+               orderIterator := database.NewOrderIterator(store, tradePairIterator.Next())
+               for orderIterator.HasNext() {
+                       orders = append(orders, orderIterator.NextBatch()...)
+               }
+       }
+       return orders
+}
+
+func ordersEquals(orders1 []*common.Order, orders2 []*common.Order) bool {
+       orderMap1 := make(map[string]*common.Order)
+       for _, order := range orders1 {
+               orderMap1[order.Key()] = order
+       }
+
+       orderMap2 := make(map[string]*common.Order)
+       for _, order := range orders2 {
+               orderMap2[order.Key()] = order
+       }
+       return testutil.DeepEqual(orderMap1, orderMap2)
+}
+
+func hashPtr(hash bc.Hash) *bc.Hash {
+       return &hash
+}