• R/O
  • HTTP
  • SSH
  • HTTPS

vapor: Commit

Golang implemented sidechain for Bytom


Commit MetaInfo

Revision9caa2ff0ad8d1c1021a390f87f1a9fc28ebada29 (tree)
Time2019-10-23 22:17:46
Authorwz <mars@byto...>
CommiterGitHub

Log Message

Merge branch 'mov' into modify_federation_sync

Change Summary

Incremental Difference

--- a/application/mov/common/type.go
+++ b/application/mov/common/type.go
@@ -1,6 +1,13 @@
11 package common
22
3-import "github.com/vapor/protocol/bc"
3+import (
4+ "fmt"
5+
6+ "github.com/vapor/consensus/segwit"
7+ "github.com/vapor/errors"
8+ "github.com/vapor/protocol/bc"
9+ "github.com/vapor/protocol/bc/types"
10+)
411
512 type MovUtxo struct {
613 SourceID *bc.Hash
@@ -16,12 +23,81 @@ type Order struct {
1623 Rate float64
1724 }
1825
26+func NewOrderFromOutput(tx *types.Tx, outputIndex int) (*Order, error) {
27+ outputID := tx.OutputID(outputIndex)
28+ output, err := tx.IntraChainOutput(*outputID)
29+ if err != nil {
30+ return nil, err
31+ }
32+
33+ contractArgs, err := segwit.DecodeP2WMCProgram(output.ControlProgram.Code)
34+ if err != nil {
35+ return nil, err
36+ }
37+
38+ assetAmount := output.Source.Value
39+ return &Order{
40+ FromAssetID: assetAmount.AssetId,
41+ ToAssetID: &contractArgs.RequestedAsset,
42+ Rate: float64(contractArgs.RatioNumerator) / float64(contractArgs.RatioDenominator),
43+ Utxo: &MovUtxo{
44+ SourceID: output.Source.Ref,
45+ Amount: assetAmount.Amount,
46+ SourcePos: uint64(outputIndex),
47+ ControlProgram: output.ControlProgram.Code,
48+ },
49+ }, nil
50+}
51+
52+func NewOrderFromInput(tx *types.Tx, inputIndex int) (*Order, error) {
53+ input, ok := tx.Inputs[inputIndex].TypedInput.(*types.SpendInput)
54+ if !ok {
55+ return nil, errors.New("input is not type of spend input")
56+ }
57+
58+ contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram)
59+ if err != nil {
60+ return nil, err
61+ }
62+
63+ return &Order{
64+ FromAssetID: input.AssetId,
65+ ToAssetID: &contractArgs.RequestedAsset,
66+ Rate: float64(contractArgs.RatioNumerator) / float64(contractArgs.RatioDenominator),
67+ Utxo: &MovUtxo{
68+ SourceID: &input.SourceID,
69+ Amount: input.Amount,
70+ SourcePos: input.SourcePosition,
71+ ControlProgram: input.ControlProgram,
72+ },
73+ }, nil
74+}
75+
76+func (o *Order) GetTradePair() *TradePair {
77+ return &TradePair{FromAssetID: o.FromAssetID, ToAssetID: o.ToAssetID}
78+}
79+
80+func (o *Order) Key() string {
81+ return fmt.Sprintf("%s:%d", o.Utxo.SourceID, o.Utxo.SourcePos)
82+}
83+
1984 type TradePair struct {
2085 FromAssetID *bc.AssetID
2186 ToAssetID *bc.AssetID
2287 Count int
2388 }
2489
90+func (t *TradePair) Reverse() *TradePair {
91+ return &TradePair{
92+ FromAssetID: t.ToAssetID,
93+ ToAssetID: t.FromAssetID,
94+ }
95+}
96+
97+func (t *TradePair) Key() string {
98+ return fmt.Sprintf("%s:%s", t.FromAssetID, t.ToAssetID)
99+}
100+
25101 type MovDatabaseState struct {
26102 Height uint64
27103 Hash *bc.Hash
--- /dev/null
+++ b/application/mov/contract/contract.go
@@ -0,0 +1,34 @@
1+package contract
2+
3+import (
4+ "encoding/hex"
5+
6+ "github.com/vapor/protocol/bc/types"
7+ "github.com/vapor/protocol/vm"
8+)
9+
10+const (
11+ sizeOfCancelClauseArgs = 3
12+ sizeOfPartialTradeClauseArgs = 3
13+ sizeOfFullTradeClauseArgs = 2
14+
15+ PartialTradeClauseSelector int64 = iota
16+ FullTradeClauseSelector
17+ CancelClauseSelector
18+)
19+
20+func IsCancelClauseSelector(input *types.TxInput) bool {
21+ return len(input.Arguments()) == sizeOfCancelClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments()) - 1]) == hex.EncodeToString(vm.Int64Bytes(CancelClauseSelector))
22+}
23+
24+func IsTradeClauseSelector(input *types.TxInput) bool {
25+ return IsPartialTradeClauseSelector(input) || IsFullTradeClauseSelector(input)
26+}
27+
28+func IsPartialTradeClauseSelector(input *types.TxInput) bool {
29+ return len(input.Arguments()) == sizeOfPartialTradeClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments()) - 1]) == hex.EncodeToString(vm.Int64Bytes(PartialTradeClauseSelector))
30+}
31+
32+func IsFullTradeClauseSelector(input *types.TxInput) bool {
33+ return len(input.Arguments()) == sizeOfFullTradeClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments()) - 1]) == hex.EncodeToString(vm.Int64Bytes(FullTradeClauseSelector))
34+}
--- /dev/null
+++ b/application/mov/database/mock_mov_store.go
@@ -0,0 +1,101 @@
1+package database
2+
3+import (
4+ "sort"
5+
6+ "github.com/vapor/application/mov/common"
7+ "github.com/vapor/errors"
8+ "github.com/vapor/protocol/bc"
9+ "github.com/vapor/protocol/bc/types"
10+)
11+
12+type MockMovStore struct {
13+ TradePairs []*common.TradePair
14+ OrderMap map[string][]*common.Order
15+ DBState *common.MovDatabaseState
16+}
17+
18+func (m *MockMovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
19+ return m.DBState, nil
20+}
21+
22+func (m *MockMovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
23+ tradePair := &common.TradePair{FromAssetID: orderAfter.FromAssetID, ToAssetID: orderAfter.ToAssetID}
24+ orders := m.OrderMap[tradePair.Key()]
25+ begin := len(orders)
26+ if orderAfter.Rate == 0 {
27+ begin = 0
28+ } else {
29+ for i, order := range orders {
30+ if order.Rate == orderAfter.Rate {
31+ begin = i + 1
32+ break
33+ }
34+ }
35+ }
36+ var result []*common.Order
37+ for i := begin; i < len(orders) && len(result) < 3; i++ {
38+ result = append(result, orders[i])
39+ }
40+ return result, nil
41+}
42+
43+func (m *MockMovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
44+ begin := len(m.TradePairs)
45+ if fromAssetIDAfter == nil || toAssetIDAfter == nil {
46+ begin = 0
47+ } else {
48+ for i, tradePair := range m.TradePairs {
49+ if *tradePair.FromAssetID == *fromAssetIDAfter && *tradePair.ToAssetID == *toAssetIDAfter {
50+ begin = i + 1
51+ break
52+ }
53+ }
54+ }
55+ var result []*common.TradePair
56+ for i := begin; i < len(m.TradePairs) && len(result) < 3; i++ {
57+ result = append(result, m.TradePairs[i])
58+ }
59+ return result, nil
60+}
61+
62+type OrderSlice []*common.Order
63+
64+func (o OrderSlice) Len() int {
65+ return len(o)
66+}
67+func (o OrderSlice) Swap(i, j int) {
68+ o[i], o[j] = o[j], o[i]
69+}
70+func (o OrderSlice) Less(i, j int) bool {
71+ return o[i].Rate < o[j].Rate
72+}
73+
74+func (m *MockMovStore) ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error {
75+ for _, order := range addOrders {
76+ tradePair := &common.TradePair{FromAssetID: order.FromAssetID, ToAssetID: order.ToAssetID}
77+ m.OrderMap[tradePair.Key()] = append(m.OrderMap[tradePair.Key()], order)
78+ }
79+ for _, delOrder := range delOrders {
80+ tradePair := &common.TradePair{FromAssetID: delOrder.FromAssetID, ToAssetID: delOrder.ToAssetID}
81+ orders := m.OrderMap[tradePair.Key()]
82+ for i, order := range orders {
83+ if delOrder.Key() == order.Key() {
84+ m.OrderMap[tradePair.Key()] = append(orders[0:i], orders[i+1:]...)
85+ }
86+ }
87+ }
88+ for _, orders := range m.OrderMap {
89+ sort.Sort(OrderSlice(orders))
90+ }
91+
92+ if blockHeader.Height == m.DBState.Height {
93+ m.DBState = &common.MovDatabaseState{Height: blockHeader.Height - 1, Hash: &blockHeader.PreviousBlockHash}
94+ } else if blockHeader.Height == m.DBState.Height+1 {
95+ blockHash := blockHeader.Hash()
96+ m.DBState = &common.MovDatabaseState{Height: blockHeader.Height, Hash: &blockHash}
97+ } else {
98+ return errors.New("error block header")
99+ }
100+ return nil
101+}
--- /dev/null
+++ b/application/mov/database/mov_iterator.go
@@ -0,0 +1,95 @@
1+package database
2+
3+import (
4+ log "github.com/sirupsen/logrus"
5+
6+ "github.com/vapor/application/mov/common"
7+ "github.com/vapor/protocol/bc"
8+)
9+
10+type TradePairIterator struct {
11+ movStore MovStore
12+ tradePairs []*common.TradePair
13+ tradePairIndex int
14+}
15+
16+func NewTradePairIterator(movStore MovStore) *TradePairIterator {
17+ return &TradePairIterator{movStore: movStore}
18+}
19+
20+func (t *TradePairIterator) HasNext() bool {
21+ tradePairSize := len(t.tradePairs)
22+ if t.tradePairIndex < tradePairSize {
23+ return true
24+ }
25+ var fromAssetID, toAssetID *bc.AssetID
26+ if len(t.tradePairs) > 0 {
27+ lastTradePair := t.tradePairs[tradePairSize-1]
28+ fromAssetID, toAssetID = lastTradePair.FromAssetID, lastTradePair.ToAssetID
29+ }
30+
31+ tradePairs, err := t.movStore.ListTradePairsWithStart(fromAssetID, toAssetID)
32+ if err != nil {
33+ // If the error is returned, it's an error of program itself,
34+ // and cannot be recovered, so panic directly.
35+ log.WithField("err", err).Fatal("fail to list trade pairs")
36+ }
37+
38+ if len(tradePairs) == 0 {
39+ return false
40+ }
41+
42+ t.tradePairs = tradePairs
43+ t.tradePairIndex = 0
44+ return true
45+}
46+
47+func (t *TradePairIterator) Next() *common.TradePair {
48+ if !t.HasNext() {
49+ return nil
50+ }
51+
52+ tradePair := t.tradePairs[t.tradePairIndex]
53+ t.tradePairIndex++
54+ return tradePair
55+}
56+
57+type OrderIterator struct {
58+ movStore MovStore
59+ lastOrder *common.Order
60+ orders []*common.Order
61+}
62+
63+func NewOrderIterator(movStore MovStore, tradePair *common.TradePair) *OrderIterator {
64+ return &OrderIterator{
65+ movStore: movStore,
66+ lastOrder: &common.Order{FromAssetID: tradePair.FromAssetID, ToAssetID: tradePair.ToAssetID},
67+ }
68+}
69+
70+func (o *OrderIterator) HasNext() bool {
71+ if len(o.orders) == 0 {
72+ orders, err := o.movStore.ListOrders(o.lastOrder)
73+ if err != nil {
74+ log.WithField("err", err).Fatal("fail to list orders")
75+ }
76+
77+ if len(orders) == 0 {
78+ return false
79+ }
80+
81+ o.orders = orders
82+ o.lastOrder = o.orders[len(o.orders)-1]
83+ }
84+ return true
85+}
86+
87+func (o *OrderIterator) NextBatch() []*common.Order {
88+ if !o.HasNext() {
89+ return nil
90+ }
91+
92+ orders := o.orders
93+ o.orders = nil
94+ return orders
95+}
--- /dev/null
+++ b/application/mov/database/mov_iterator_test.go
@@ -0,0 +1,167 @@
1+package database
2+
3+import (
4+ "testing"
5+
6+ "github.com/vapor/application/mov/common"
7+ "github.com/vapor/protocol/bc"
8+ "github.com/vapor/testutil"
9+
10+)
11+
12+var (
13+ asset1 = bc.NewAssetID([32]byte{1})
14+ asset2 = bc.NewAssetID([32]byte{2})
15+ asset3 = bc.NewAssetID([32]byte{3})
16+ asset4 = bc.NewAssetID([32]byte{4})
17+
18+ order1 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, Rate: 0.1}
19+ order2 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, Rate: 0.2}
20+ order3 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, Rate: 0.3}
21+ order4 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, Rate: 0.4}
22+ order5 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, Rate: 0.5}
23+)
24+
25+func TestTradePairIterator(t *testing.T) {
26+ cases := []struct {
27+ desc string
28+ storeTradePairs []*common.TradePair
29+ wantTradePairs []*common.TradePair
30+ }{
31+ {
32+ desc: "normal case",
33+ storeTradePairs: []*common.TradePair{
34+ {
35+ FromAssetID: &asset1,
36+ ToAssetID: &asset2,
37+ },
38+ {
39+ FromAssetID: &asset1,
40+ ToAssetID: &asset3,
41+ },
42+ {
43+ FromAssetID: &asset1,
44+ ToAssetID: &asset4,
45+ },
46+ },
47+ wantTradePairs: []*common.TradePair{
48+ {
49+ FromAssetID: &asset1,
50+ ToAssetID: &asset2,
51+ },
52+ {
53+ FromAssetID: &asset1,
54+ ToAssetID: &asset3,
55+ },
56+ {
57+ FromAssetID: &asset1,
58+ ToAssetID: &asset4,
59+ },
60+ },
61+ },
62+ {
63+ desc: "num of trade pairs more than one return",
64+ storeTradePairs: []*common.TradePair{
65+ {
66+ FromAssetID: &asset1,
67+ ToAssetID: &asset2,
68+ },
69+ {
70+ FromAssetID: &asset1,
71+ ToAssetID: &asset3,
72+ },
73+ {
74+ FromAssetID: &asset1,
75+ ToAssetID: &asset4,
76+ },
77+ {
78+ FromAssetID: &asset2,
79+ ToAssetID: &asset1,
80+ },
81+ },
82+ wantTradePairs: []*common.TradePair{
83+ {
84+ FromAssetID: &asset1,
85+ ToAssetID: &asset2,
86+ },
87+ {
88+ FromAssetID: &asset1,
89+ ToAssetID: &asset3,
90+ },
91+ {
92+ FromAssetID: &asset1,
93+ ToAssetID: &asset4,
94+ },
95+ {
96+ FromAssetID: &asset2,
97+ ToAssetID: &asset1,
98+ },
99+ },
100+ },
101+ {
102+ desc: "store is empty",
103+ storeTradePairs: []*common.TradePair{},
104+ wantTradePairs: []*common.TradePair{},
105+ },
106+ }
107+
108+ for i, c := range cases {
109+ store := &MockMovStore{TradePairs: c.storeTradePairs}
110+ var gotTradePairs []*common.TradePair
111+ iterator := NewTradePairIterator(store)
112+ for iterator.HasNext() {
113+ gotTradePairs = append(gotTradePairs, iterator.Next())
114+ }
115+ if !testutil.DeepEqual(c.wantTradePairs, gotTradePairs) {
116+ t.Errorf("#%d(%s):got trade pairs is not equals want trade pairs", i, c.desc)
117+ }
118+ }
119+}
120+
121+func TestOrderIterator(t *testing.T) {
122+ cases := []struct {
123+ desc string
124+ tradePair *common.TradePair
125+ storeOrders []*common.Order
126+ wantOrders []*common.Order
127+ }{
128+ {
129+ desc: "normal case",
130+ tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2},
131+ storeOrders: []*common.Order{order1, order2, order3},
132+ wantOrders: []*common.Order{order1, order2, order3},
133+ },
134+ {
135+ desc: "num of orders more than one return",
136+ tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2},
137+ storeOrders: []*common.Order{order1, order2, order3, order4, order5},
138+ wantOrders: []*common.Order{order1, order2, order3, order4, order5},
139+ },
140+ {
141+ desc: "only one order",
142+ tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2},
143+ storeOrders: []*common.Order{order1},
144+ wantOrders: []*common.Order{order1},
145+ },
146+ {
147+ desc: "store is empty",
148+ tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2},
149+ storeOrders: []*common.Order{},
150+ wantOrders: []*common.Order{},
151+ },
152+ }
153+
154+ for i, c := range cases {
155+ orderMap := map[string][]*common.Order{c.tradePair.Key(): c.storeOrders}
156+ store := &MockMovStore{OrderMap: orderMap}
157+
158+ var gotOrders []*common.Order
159+ iterator := NewOrderIterator(store, c.tradePair)
160+ for iterator.HasNext() {
161+ gotOrders = append(gotOrders, iterator.NextBatch()...)
162+ }
163+ if !testutil.DeepEqual(c.wantOrders, gotOrders) {
164+ t.Errorf("#%d(%s):got orders it not equals want orders", i, c.desc)
165+ }
166+ }
167+}
--- a/application/mov/database/mov_store.go
+++ b/application/mov/database/mov_store.go
@@ -12,6 +12,13 @@ import (
1212 "github.com/vapor/protocol/bc/types"
1313 )
1414
15+type MovStore interface {
16+ GetMovDatabaseState() (*common.MovDatabaseState, error)
17+ ListOrders(orderAfter *common.Order) ([]*common.Order, error)
18+ ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error)
19+ ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error
20+}
21+
1522 const (
1623 order byte = iota
1724 tradePair
@@ -72,11 +79,11 @@ type tradePairData struct {
7279 Count int
7380 }
7481
75-type MovStore struct {
82+type LevelDBMovStore struct {
7683 db dbm.DB
7784 }
7885
79-func NewMovStore(db dbm.DB, height uint64, hash *bc.Hash) (*MovStore, error) {
86+func NewLevelDBMovStore(db dbm.DB, height uint64, hash *bc.Hash) (*LevelDBMovStore, error) {
8087 if value := db.Get(bestMatchStore); value == nil {
8188 state := &common.MovDatabaseState{Height: height, Hash: hash}
8289 value, err := json.Marshal(state)
@@ -86,10 +93,10 @@ func NewMovStore(db dbm.DB, height uint64, hash *bc.Hash) (*MovStore, error) {
8693
8794 db.Set(bestMatchStore, value)
8895 }
89- return &MovStore{db: db}, nil
96+ return &LevelDBMovStore{db: db}, nil
9097 }
9198
92-func (m *MovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
99+func (m *LevelDBMovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
93100 if orderAfter.FromAssetID == nil || orderAfter.ToAssetID == nil {
94101 return nil, errors.New("assetID is nil")
95102 }
@@ -123,7 +130,7 @@ func (m *MovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error)
123130 return orders, nil
124131 }
125132
126-func (m *MovStore) ProcessOrders(addOrders []*common.Order, delOreders []*common.Order, blockHeader *types.BlockHeader) error {
133+func (m *LevelDBMovStore) ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error {
127134 if err := m.checkMovDatabaseState(blockHeader); err != nil {
128135 return err
129136 }
@@ -134,7 +141,7 @@ func (m *MovStore) ProcessOrders(addOrders []*common.Order, delOreders []*common
134141 return err
135142 }
136143
137- m.deleteOrders(batch, delOreders, tradePairsCnt)
144+ m.deleteOrders(batch, delOrders, tradePairsCnt)
138145
139146 if err := m.updateTradePairs(batch, tradePairsCnt); err != nil {
140147 return err
@@ -153,7 +160,7 @@ func (m *MovStore) ProcessOrders(addOrders []*common.Order, delOreders []*common
153160 return nil
154161 }
155162
156-func (m *MovStore) addOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[common.TradePair]int) error {
163+func (m *LevelDBMovStore) addOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[common.TradePair]int) error {
157164 for _, order := range orders {
158165 data, err := json.Marshal(order.Utxo)
159166 if err != nil {
@@ -172,7 +179,7 @@ func (m *MovStore) addOrders(batch dbm.Batch, orders []*common.Order, tradePairs
172179 return nil
173180 }
174181
175-func (m *MovStore) deleteOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[common.TradePair]int) {
182+func (m *LevelDBMovStore) deleteOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[common.TradePair]int) {
176183 for _, order := range orders {
177184 key := calcOrderKey(order.FromAssetID, order.ToAssetID, calcUTXOHash(order), order.Rate)
178185 batch.Delete(key)
@@ -185,7 +192,7 @@ func (m *MovStore) deleteOrders(batch dbm.Batch, orders []*common.Order, tradePa
185192 }
186193 }
187194
188-func (m *MovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
195+func (m *LevelDBMovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
189196 if value := m.db.Get(bestMatchStore); value != nil {
190197 state := &common.MovDatabaseState{}
191198 return state, json.Unmarshal(value, state)
@@ -194,7 +201,7 @@ func (m *MovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
194201 return nil, errors.New("don't find state of mov-database")
195202 }
196203
197-func (m *MovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
204+func (m *LevelDBMovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
198205 var startKey []byte
199206 if fromAssetIDAfter != nil && toAssetIDAfter != nil {
200207 startKey = calcTradePairKey(fromAssetIDAfter, toAssetIDAfter)
@@ -219,7 +226,7 @@ func (m *MovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.
219226 return tradePairs, nil
220227 }
221228
222-func (m *MovStore) updateTradePairs(batch dbm.Batch, tradePairs map[common.TradePair]int) error {
229+func (m *LevelDBMovStore) updateTradePairs(batch dbm.Batch, tradePairs map[common.TradePair]int) error {
223230 for k, v := range tradePairs {
224231 key := calcTradePairKey(k.FromAssetID, k.ToAssetID)
225232 tradePairData := &tradePairData{}
@@ -246,7 +253,7 @@ func (m *MovStore) updateTradePairs(batch dbm.Batch, tradePairs map[common.Trade
246253 return nil
247254 }
248255
249-func (m *MovStore) checkMovDatabaseState(header *types.BlockHeader) error {
256+func (m *LevelDBMovStore) checkMovDatabaseState(header *types.BlockHeader) error {
250257 state, err := m.GetMovDatabaseState()
251258 if err != nil {
252259 return err
@@ -259,7 +266,7 @@ func (m *MovStore) checkMovDatabaseState(header *types.BlockHeader) error {
259266 return errors.New("the status of the block is inconsistent with that of mov-database")
260267 }
261268
262-func (m *MovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDatabaseState) error {
269+func (m *LevelDBMovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDatabaseState) error {
263270 value, err := json.Marshal(state)
264271 if err != nil {
265272 return err
@@ -269,7 +276,7 @@ func (m *MovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDataba
269276 return nil
270277 }
271278
272-func (m *MovStore) calcNextDatabaseState(blockHeader *types.BlockHeader) (*common.MovDatabaseState, error) {
279+func (m *LevelDBMovStore) calcNextDatabaseState(blockHeader *types.BlockHeader) (*common.MovDatabaseState, error) {
273280 hash := blockHeader.Hash()
274281 height := blockHeader.Height
275282
--- a/application/mov/database/mov_store_test.go
+++ b/application/mov/database/mov_store_test.go
@@ -1535,7 +1535,7 @@ func TestMovStore(t *testing.T) {
15351535 defer os.RemoveAll("temp")
15361536 for i, c := range cases {
15371537 testDB := dbm.NewDB("testdb", "leveldb", "temp")
1538- movStore, err := NewMovStore(testDB, height, &hash)
1538+ movStore, err := NewLevelDBMovStore(testDB, height, &hash)
15391539 if err != nil {
15401540 t.Fatalf("case %d: NewMovStore error %v.", i, err)
15411541 }
@@ -1969,7 +1969,7 @@ func TestListOrders(t *testing.T) {
19691969 defer os.RemoveAll("temp")
19701970 for i, c := range cases {
19711971 testDB := dbm.NewDB("testdb", "leveldb", "temp")
1972- movStore, err := NewMovStore(testDB, height, &hash)
1972+ movStore, err := NewLevelDBMovStore(testDB, height, &hash)
19731973 if err != nil {
19741974 t.Fatalf("case %d: NewMovStore error %v.", i, err)
19751975 }
@@ -2382,7 +2382,7 @@ func TestAddOrders(t *testing.T) {
23822382 defer os.RemoveAll("temp")
23832383 for i, c := range cases {
23842384 testDB := dbm.NewDB("testdb", "leveldb", "temp")
2385- movStore, err := NewMovStore(testDB, height, &hash)
2385+ movStore, err := NewLevelDBMovStore(testDB, height, &hash)
23862386 if err != nil {
23872387 t.Fatalf("case %d: NewMovStore error %v.", i, err)
23882388 }
@@ -2722,7 +2722,7 @@ func TestDelOrders(t *testing.T) {
27222722 defer os.RemoveAll("temp")
27232723 for i, c := range cases {
27242724 testDB := dbm.NewDB("testdb", "leveldb", "temp")
2725- movStore, err := NewMovStore(testDB, height, &hash)
2725+ movStore, err := NewLevelDBMovStore(testDB, height, &hash)
27262726 if err != nil {
27272727 t.Fatalf("case %d: NewMovStore error %v.", i, err)
27282728 }
@@ -2816,7 +2816,7 @@ func TestListTradePairsWithStart(t *testing.T) {
28162816 defer os.RemoveAll("temp")
28172817 for i, c := range cases {
28182818 testDB := dbm.NewDB("testdb", "leveldb", "temp")
2819- movStore, err := NewMovStore(testDB, height, &hash)
2819+ movStore, err := NewLevelDBMovStore(testDB, height, &hash)
28202820 if err != nil {
28212821 t.Fatalf("case %d: NewMovStore error %v.", i, err)
28222822 }
@@ -2923,7 +2923,7 @@ func TestUpdateTradePairs(t *testing.T) {
29232923 defer os.RemoveAll("temp")
29242924 for i, c := range cases {
29252925 testDB := dbm.NewDB("testdb", "leveldb", "temp")
2926- movStore, err := NewMovStore(testDB, height, &hash)
2926+ movStore, err := NewLevelDBMovStore(testDB, height, &hash)
29272927 if err != nil {
29282928 t.Fatalf("case %d: NewMovStore error %v.", i, err)
29292929 }
@@ -2995,7 +2995,7 @@ func TestCheckMovDatabaseState(t *testing.T) {
29952995 defer os.RemoveAll("temp")
29962996 for i, c := range cases {
29972997 testDB := dbm.NewDB("testdb", "leveldb", "temp")
2998- movStore, err := NewMovStore(testDB, height, &hash)
2998+ movStore, err := NewLevelDBMovStore(testDB, height, &hash)
29992999 if err != nil {
30003000 t.Fatalf("case %d: NewMovStore error %v.", i, err)
30013001 }
--- /dev/null
+++ b/application/mov/match/match.go
@@ -0,0 +1,280 @@
1+package match
2+
3+import (
4+ "encoding/hex"
5+ "math"
6+
7+ "github.com/vapor/application/mov/common"
8+ "github.com/vapor/application/mov/contract"
9+ "github.com/vapor/application/mov/database"
10+ "github.com/vapor/consensus/segwit"
11+ "github.com/vapor/errors"
12+ vprMath "github.com/vapor/math"
13+ "github.com/vapor/protocol/bc"
14+ "github.com/vapor/protocol/bc/types"
15+ "github.com/vapor/protocol/vm"
16+ "github.com/vapor/protocol/vm/vmutil"
17+)
18+
19+const maxFeeRate = 0.05
20+
21+type Engine struct {
22+ orderTable *OrderTable
23+ nodeProgram []byte
24+}
25+
26+func NewEngine(movStore database.MovStore, nodeProgram []byte) *Engine {
27+ return &Engine{orderTable: NewOrderTable(movStore), nodeProgram: nodeProgram}
28+}
29+
30+func (e *Engine) HasMatchedTx(tradePairs ...*common.TradePair) bool {
31+ if err := validateTradePairs(tradePairs); err != nil {
32+ return false
33+ }
34+
35+ orders := e.peekOrders(tradePairs)
36+ if len(orders) == 0 {
37+ return false
38+ }
39+
40+ for i, order := range orders {
41+ if canNotBeMatched(order, orders[getOppositeIndex(len(orders), i)]) {
42+ return false
43+ }
44+ }
45+ return true
46+}
47+
48+// NextMatchedTx return the next matchable transaction by the specified trade pairs
49+// the size of trade pairs at least 2, and the sequence of trade pairs can form a loop
50+// for example, [assetA -> assetB, assetB -> assetC, assetC -> assetA]
51+func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, error) {
52+ if err := validateTradePairs(tradePairs); err != nil {
53+ return nil, err
54+ }
55+
56+ orders := e.peekOrders(tradePairs)
57+ if len(orders) == 0 {
58+ return nil, errors.New("no order for the specified trade pair in the order table")
59+ }
60+
61+ tx, err := e.buildMatchTx(orders)
62+ if err != nil {
63+ return nil, err
64+ }
65+
66+ for _, tradePair := range tradePairs {
67+ e.orderTable.PopOrder(tradePair)
68+ }
69+
70+ if err := addPartialTradeOrder(tx, e.orderTable); err != nil {
71+ return nil, err
72+ }
73+ return tx, nil
74+}
75+
76+func (e *Engine) peekOrders(tradePairs []*common.TradePair) []*common.Order {
77+ var orders []*common.Order
78+ for _, tradePair := range tradePairs {
79+ order := e.orderTable.PeekOrder(tradePair)
80+ if order == nil {
81+ return nil
82+ }
83+
84+ orders = append(orders, order)
85+ }
86+ return orders
87+}
88+
89+func validateTradePairs(tradePairs []*common.TradePair) error {
90+ if len(tradePairs) < 2 {
91+ return errors.New("size of trade pairs at least 2")
92+ }
93+
94+ for i, tradePair := range tradePairs {
95+ oppositeTradePair := tradePairs[getOppositeIndex(len(tradePairs), i)]
96+ if *tradePair.ToAssetID != *oppositeTradePair.FromAssetID {
97+ return errors.New("specified trade pairs is invalid")
98+ }
99+ }
100+ return nil
101+}
102+
103+func canNotBeMatched(order, oppositeOrder *common.Order) bool {
104+ rate := 1 / order.Rate
105+ return rate < oppositeOrder.Rate
106+}
107+
108+func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
109+ txData := &types.TxData{Version: 1}
110+ for i, order := range orders {
111+ input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram)
112+ txData.Inputs = append(txData.Inputs, input)
113+
114+ oppositeOrder := orders[getOppositeIndex(len(orders), i)]
115+ if err := addMatchTxOutput(txData, input, order, oppositeOrder.Utxo.Amount); err != nil {
116+ return nil, err
117+ }
118+ }
119+
120+ if err := e.addMatchTxFeeOutput(txData); err != nil {
121+ return nil, err
122+ }
123+
124+ byteData, err := txData.MarshalText()
125+ if err != nil {
126+ return nil, err
127+ }
128+
129+ txData.SerializedSize = uint64(len(byteData))
130+ return types.NewTx(*txData), nil
131+}
132+
133+func addMatchTxOutput(txData *types.TxData, txInput *types.TxInput, order *common.Order, oppositeAmount uint64) error {
134+ contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
135+ if err != nil {
136+ return err
137+ }
138+
139+ requestAmount := calcRequestAmount(order.Utxo.Amount, contractArgs)
140+ receiveAmount := vprMath.MinUint64(requestAmount, oppositeAmount)
141+ shouldPayAmount := CalcShouldPayAmount(receiveAmount, contractArgs)
142+ isPartialTrade := order.Utxo.Amount > shouldPayAmount
143+
144+ setMatchTxArguments(txInput, isPartialTrade, len(txData.Outputs), receiveAmount)
145+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, receiveAmount, contractArgs.SellerProgram))
146+ if isPartialTrade {
147+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram))
148+ }
149+ return nil
150+}
151+
152+func (e *Engine) addMatchTxFeeOutput(txData *types.TxData) error {
153+ feeAssetAmountMap, err := CalcFeeFromMatchedTx(txData)
154+ if err != nil {
155+ return err
156+ }
157+
158+ for feeAssetID, amount := range feeAssetAmountMap {
159+ var reminder int64 = 0
160+ feeAmount := amount.payableFeeAmount
161+ if amount.payableFeeAmount > amount.maxFeeAmount {
162+ feeAmount = amount.maxFeeAmount
163+ reminder = amount.payableFeeAmount - amount.maxFeeAmount
164+ }
165+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, uint64(feeAmount), e.nodeProgram))
166+
167+ // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
168+ averageAmount := reminder / int64(len(txData.Inputs))
169+ if averageAmount == 0 {
170+ averageAmount = 1
171+ }
172+ for i := 0; i < len(txData.Inputs) && reminder > 0; i++ {
173+ contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram())
174+ if err != nil {
175+ return err
176+ }
177+
178+ if i == len(txData.Inputs)-1 {
179+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, uint64(reminder), contractArgs.SellerProgram))
180+ } else {
181+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, uint64(averageAmount), contractArgs.SellerProgram))
182+ }
183+ reminder -= averageAmount
184+ }
185+ }
186+ return nil
187+}
188+
189+func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) {
190+ var arguments [][]byte
191+ if isPartialTrade {
192+ arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts)), vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.PartialTradeClauseSelector)}
193+ } else {
194+ arguments = [][]byte{vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.FullTradeClauseSelector)}
195+ }
196+ txInput.SetArguments(arguments)
197+}
198+
199+func addPartialTradeOrder(tx *types.Tx, orderTable *OrderTable) error {
200+ for i, output := range tx.Outputs {
201+ if !segwit.IsP2WMCScript(output.ControlProgram()) {
202+ continue
203+ }
204+
205+ order, err := common.NewOrderFromOutput(tx, i)
206+ if err != nil {
207+ return err
208+ }
209+
210+ if err := orderTable.AddOrder(order); err != nil {
211+ return err
212+ }
213+ }
214+ return nil
215+}
216+
217+func getOppositeIndex(size int, selfIdx int) int {
218+ oppositeIdx := selfIdx + 1
219+ if selfIdx >= size-1 {
220+ oppositeIdx = 0
221+ }
222+ return oppositeIdx
223+}
224+
225+type feeAmount struct {
226+ maxFeeAmount int64
227+ payableFeeAmount int64
228+}
229+
230+func CalcFeeFromMatchedTx(txData *types.TxData) (map[bc.AssetID]*feeAmount, error) {
231+ assetAmountMap := make(map[bc.AssetID]*feeAmount)
232+ for _, input := range txData.Inputs {
233+ assetAmountMap[input.AssetID()] = &feeAmount{}
234+ }
235+
236+ receiveOutputMap := make(map[string]*types.TxOutput)
237+ for _, output := range txData.Outputs {
238+ // minus the amount of the re-order
239+ if segwit.IsP2WMCScript(output.ControlProgram()) {
240+ assetAmountMap[*output.AssetAmount().AssetId].payableFeeAmount -= int64(output.AssetAmount().Amount)
241+ } else {
242+ receiveOutputMap[hex.EncodeToString(output.ControlProgram())] = output
243+ }
244+ }
245+
246+ for _, input := range txData.Inputs {
247+ contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
248+ if err != nil {
249+ return nil, err
250+ }
251+
252+ assetAmountMap[input.AssetID()].payableFeeAmount += int64(input.AssetAmount().Amount)
253+ receiveOutput, ok := receiveOutputMap[hex.EncodeToString(contractArgs.SellerProgram)]
254+ if !ok {
255+ return nil, errors.New("the input of matched tx has no receive output")
256+ }
257+
258+ assetAmountMap[*receiveOutput.AssetAmount().AssetId].payableFeeAmount -= int64(receiveOutput.AssetAmount().Amount)
259+ assetAmountMap[input.AssetID()].maxFeeAmount = CalcMaxFeeAmount(CalcShouldPayAmount(receiveOutput.AssetAmount().Amount, contractArgs))
260+ }
261+
262+ for assetID, amount := range assetAmountMap {
263+ if amount.payableFeeAmount == 0 {
264+ delete(assetAmountMap, assetID)
265+ }
266+ }
267+ return assetAmountMap, nil
268+}
269+
270+func calcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
271+ return uint64(int64(fromAmount) * contractArg.RatioNumerator / contractArg.RatioDenominator)
272+}
273+
274+func CalcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
275+ return uint64(math.Ceil(float64(receiveAmount) * float64(contractArg.RatioDenominator) / float64(contractArg.RatioNumerator)))
276+}
277+
278+func CalcMaxFeeAmount(shouldPayAmount uint64) int64 {
279+ return int64(math.Ceil(float64(shouldPayAmount) * maxFeeRate))
280+}
--- /dev/null
+++ b/application/mov/match/match_test.go
@@ -0,0 +1,230 @@
1+package match
2+
3+import (
4+ "testing"
5+
6+ "github.com/vapor/protocol/vm"
7+ "github.com/vapor/testutil"
8+
9+ "github.com/vapor/application/mov/common"
10+ "github.com/vapor/application/mov/database"
11+ "github.com/vapor/protocol/bc"
12+ "github.com/vapor/protocol/bc/types"
13+ "github.com/vapor/protocol/vm/vmutil"
14+)
15+
16+var (
17+ btc = bc.NewAssetID([32]byte{1})
18+ eth = bc.NewAssetID([32]byte{2})
19+
20+ orders = []*common.Order{
21+ // btc -> eth
22+ {
23+ FromAssetID: &btc,
24+ ToAssetID: &eth,
25+ Rate: 50,
26+ Utxo: &common.MovUtxo{
27+ SourceID: hashPtr(testutil.MustDecodeHash("37b8edf656e45a7addf47f5626e114a8c394d918a36f61b5a2905675a09b40ae")),
28+ SourcePos: 0,
29+ Amount: 10,
30+ ControlProgram: mustCreateP2WMCProgram(eth, testutil.MustDecodeHexString("51"), 50, 1),
31+ },
32+ },
33+ {
34+ FromAssetID: &btc,
35+ ToAssetID: &eth,
36+ Rate: 53,
37+ Utxo: &common.MovUtxo{
38+ SourceID: hashPtr(testutil.MustDecodeHash("3ec2bbfb499a8736d377b547eee5392bcddf7ec2b287e9ed20b5938c3d84e7cd")),
39+ SourcePos: 0,
40+ Amount: 20,
41+ ControlProgram: mustCreateP2WMCProgram(eth, testutil.MustDecodeHexString("52"), 53, 1),
42+ },
43+ },
44+
45+ // eth -> btc
46+ {
47+ FromAssetID: &eth,
48+ ToAssetID: &btc,
49+ Rate: 1 / 51.0,
50+ Utxo: &common.MovUtxo{
51+ SourceID: hashPtr(testutil.MustDecodeHash("fba43ff5155209cb1769e2ec0e1d4a33accf899c740865edfc6d1de39b873b29")),
52+ SourcePos: 0,
53+ Amount: 510,
54+ ControlProgram: mustCreateP2WMCProgram(btc, testutil.MustDecodeHexString("53"), 1, 51.0),
55+ },
56+ },
57+ {
58+ FromAssetID: &eth,
59+ ToAssetID: &btc,
60+ Rate: 1 / 52.0,
61+ Utxo: &common.MovUtxo{
62+ SourceID: hashPtr(testutil.MustDecodeHash("05f24bb847db823075d81786aa270748e02602199cd009c0284f928503846a5a")),
63+ SourcePos: 0,
64+ Amount: 416,
65+ ControlProgram: mustCreateP2WMCProgram(btc, testutil.MustDecodeHexString("54"), 1, 52.0),
66+ },
67+ },
68+ {
69+ FromAssetID: &eth,
70+ ToAssetID: &btc,
71+ Rate: 1 / 54.0,
72+ Utxo: &common.MovUtxo{
73+ SourceID: hashPtr(testutil.MustDecodeHash("119a02980796dc352cf6475457463aef5666f66622088de3551fa73a65f0d201")),
74+ SourcePos: 0,
75+ Amount: 810,
76+ ControlProgram: mustCreateP2WMCProgram(btc, testutil.MustDecodeHexString("55"), 1, 54.0),
77+ },
78+ },
79+ }
80+)
81+
82+func TestGenerateMatchedTxs(t *testing.T) {
83+ btc2eth := &common.TradePair{FromAssetID: &btc, ToAssetID: &eth}
84+ eth2btc := &common.TradePair{FromAssetID: &eth, ToAssetID: &btc}
85+
86+ cases := []struct {
87+ desc string
88+ tradePair *common.TradePair
89+ storeOrderMap map[string][]*common.Order
90+ wantMatchedTxs []*types.TxData
91+ }{
92+ {
93+ desc: "full matched",
94+ tradePair: &common.TradePair{FromAssetID: &btc, ToAssetID: &eth},
95+ storeOrderMap: map[string][]*common.Order{
96+ btc2eth.Key(): {orders[0], orders[1]},
97+ eth2btc.Key(): {orders[2], orders[3]},
98+ },
99+ wantMatchedTxs: []*types.TxData{
100+ {
101+ Inputs: []*types.TxInput{
102+ 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),
103+ 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),
104+ },
105+ Outputs: []*types.TxOutput{
106+ types.NewIntraChainOutput(*orders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
107+ types.NewIntraChainOutput(*orders[2].ToAssetID, 10, testutil.MustDecodeHexString("53")),
108+ types.NewIntraChainOutput(*orders[0].ToAssetID, 10, []byte{0x51}),
109+ },
110+ },
111+ },
112+ },
113+ {
114+ desc: "partial matched",
115+ tradePair: &common.TradePair{FromAssetID: &btc, ToAssetID: &eth},
116+ storeOrderMap: map[string][]*common.Order{
117+ btc2eth.Key(): {orders[0], orders[1]},
118+ eth2btc.Key(): {orders[3]},
119+ },
120+ wantMatchedTxs: []*types.TxData{
121+ {
122+ Inputs: []*types.TxInput{
123+ 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),
124+ 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),
125+ },
126+ Outputs: []*types.TxOutput{
127+ types.NewIntraChainOutput(*orders[0].ToAssetID, 416, testutil.MustDecodeHexString("51")),
128+ // re-order
129+ types.NewIntraChainOutput(*orders[0].FromAssetID, 1, orders[0].Utxo.ControlProgram),
130+ types.NewIntraChainOutput(*orders[3].ToAssetID, 8, testutil.MustDecodeHexString("54")),
131+ // fee
132+ types.NewIntraChainOutput(*orders[3].ToAssetID, 1, []byte{0x51}),
133+ },
134+ },
135+ },
136+ },
137+ {
138+ desc: "partial matched and continue to match",
139+ tradePair: &common.TradePair{FromAssetID: &btc, ToAssetID: &eth},
140+ storeOrderMap: map[string][]*common.Order{
141+ btc2eth.Key(): {orders[0], orders[1]},
142+ eth2btc.Key(): {orders[4]},
143+ },
144+ wantMatchedTxs: []*types.TxData{
145+ {
146+ Inputs: []*types.TxInput{
147+ 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),
148+ 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),
149+ },
150+ Outputs: []*types.TxOutput{
151+ types.NewIntraChainOutput(*orders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
152+ types.NewIntraChainOutput(*orders[4].ToAssetID, 10, testutil.MustDecodeHexString("55")),
153+ // re-order
154+ types.NewIntraChainOutput(*orders[4].FromAssetID, 270, orders[4].Utxo.ControlProgram),
155+ // fee
156+ types.NewIntraChainOutput(*orders[4].FromAssetID, 27, []byte{0x51}),
157+ // refund
158+ types.NewIntraChainOutput(*orders[4].FromAssetID, 6, testutil.MustDecodeHexString("51")),
159+ types.NewIntraChainOutput(*orders[4].FromAssetID, 7, testutil.MustDecodeHexString("55")),
160+ },
161+ },
162+ {
163+ Inputs: []*types.TxInput{
164+ 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),
165+ types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, testutil.MustDecodeHash("f47177c12d25f5316eb377ea006e77bf07e4f9646860e4641e313e004f9aa989"), *orders[4].FromAssetID, 270, 2, orders[4].Utxo.ControlProgram),
166+ },
167+ Outputs: []*types.TxOutput{
168+ types.NewIntraChainOutput(*orders[1].ToAssetID, 270, testutil.MustDecodeHexString("52")),
169+ // re-order
170+ types.NewIntraChainOutput(*orders[1].FromAssetID, 14, orders[1].Utxo.ControlProgram),
171+ types.NewIntraChainOutput(*orders[4].ToAssetID, 5, testutil.MustDecodeHexString("55")),
172+ types.NewIntraChainOutput(*orders[1].FromAssetID, 1, []byte{0x51}),
173+ },
174+ },
175+ },
176+ },
177+ }
178+
179+ for i, c := range cases {
180+ movStore := &database.MockMovStore{OrderMap: c.storeOrderMap}
181+ matchEngine := NewEngine(movStore, []byte{0x51})
182+ var gotMatchedTxs []*types.Tx
183+ for matchEngine.HasMatchedTx(c.tradePair, c.tradePair.Reverse()) {
184+ matchedTx, err := matchEngine.NextMatchedTx(c.tradePair, c.tradePair.Reverse())
185+ if err != nil {
186+ t.Fatal(err)
187+ }
188+
189+ gotMatchedTxs = append(gotMatchedTxs, matchedTx)
190+ }
191+
192+ if len(c.wantMatchedTxs) != len(gotMatchedTxs) {
193+ t.Errorf("#%d(%s) the length of got matched tx is not equals want matched tx", i, c.desc)
194+ continue
195+ }
196+
197+ for i, gotMatchedTx := range gotMatchedTxs {
198+ c.wantMatchedTxs[i].Version = 1
199+ byteData, err := c.wantMatchedTxs[i].MarshalText()
200+ if err != nil {
201+ t.Fatal(err)
202+ }
203+
204+ c.wantMatchedTxs[i].SerializedSize = uint64(len(byteData))
205+ wantMatchedTx := types.NewTx(*c.wantMatchedTxs[i])
206+ if gotMatchedTx.ID != wantMatchedTx.ID {
207+ 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())
208+ }
209+ }
210+ }
211+}
212+
213+func mustCreateP2WMCProgram(requestAsset bc.AssetID, sellerProgram []byte, ratioMolecule, ratioDenominator int64) []byte {
214+ contractArgs := vmutil.MagneticContractArgs{
215+ RequestedAsset: requestAsset,
216+ RatioNumerator: ratioMolecule,
217+ RatioDenominator: ratioDenominator,
218+ SellerProgram: sellerProgram,
219+ SellerKey: testutil.MustDecodeHexString("ad79ec6bd3a6d6dbe4d0ee902afc99a12b9702fb63edce5f651db3081d868b75"),
220+ }
221+ program, err := vmutil.P2WMCProgram(contractArgs)
222+ if err != nil {
223+ panic(err)
224+ }
225+ return program
226+}
227+
228+func hashPtr(hash bc.Hash) *bc.Hash {
229+ return &hash
230+}
--- /dev/null
+++ b/application/mov/match/order_table.go
@@ -0,0 +1,61 @@
1+package match
2+
3+import (
4+ "github.com/vapor/application/mov/common"
5+ "github.com/vapor/application/mov/database"
6+ "github.com/vapor/errors"
7+)
8+
9+type OrderTable struct {
10+ movStore database.MovStore
11+ orderMap map[string][]*common.Order
12+ iteratorMap map[string]*database.OrderIterator
13+}
14+
15+func NewOrderTable(movStore database.MovStore) *OrderTable {
16+ return &OrderTable{
17+ movStore: movStore,
18+ orderMap: make(map[string][]*common.Order),
19+ iteratorMap: make(map[string]*database.OrderIterator),
20+ }
21+}
22+
23+func (o *OrderTable) PeekOrder(tradePair *common.TradePair) *common.Order {
24+ orders := o.orderMap[tradePair.Key()]
25+ if len(orders) != 0 {
26+ return orders[len(orders)-1]
27+ }
28+
29+ iterator, ok := o.iteratorMap[tradePair.Key()]
30+ if !ok {
31+ iterator = database.NewOrderIterator(o.movStore, tradePair)
32+ o.iteratorMap[tradePair.Key()] = iterator
33+ }
34+
35+ nextOrders := iterator.NextBatch()
36+ if len(nextOrders) == 0 {
37+ return nil
38+ }
39+
40+ for i := len(nextOrders) - 1; i >= 0; i-- {
41+ o.orderMap[tradePair.Key()] = append(o.orderMap[tradePair.Key()], nextOrders[i])
42+ }
43+ return nextOrders[0]
44+}
45+
46+func (o *OrderTable) PopOrder(tradePair *common.TradePair) {
47+ if orders := o.orderMap[tradePair.Key()]; len(orders) > 0 {
48+ o.orderMap[tradePair.Key()] = orders[0 : len(orders)-1]
49+ }
50+}
51+
52+func (o *OrderTable) AddOrder(order *common.Order) error {
53+ tradePair := order.GetTradePair()
54+ orders := o.orderMap[tradePair.Key()]
55+ if len(orders) > 0 && order.Rate > orders[len(orders)-1].Rate {
56+ return errors.New("rate of order must less than the min order in order table")
57+ }
58+
59+ o.orderMap[tradePair.Key()] = append(orders, order)
60+ return nil
61+}
--- /dev/null
+++ b/math/algorithm.go
@@ -0,0 +1,8 @@
1+package math
2+
3+func MinUint64(x, y uint64) uint64 {
4+ if x < y {
5+ return x
6+ }
7+ return y
8+}
Show on old repository browser