Golang implemented sidechain for Bytom
Revision | 9caa2ff0ad8d1c1021a390f87f1a9fc28ebada29 (tree) |
---|---|
Time | 2019-10-23 22:17:46 |
Author | wz <mars@byto...> |
Commiter | GitHub |
Merge branch 'mov' into modify_federation_sync
@@ -1,6 +1,13 @@ | ||
1 | 1 | package common |
2 | 2 | |
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 | +) | |
4 | 11 | |
5 | 12 | type MovUtxo struct { |
6 | 13 | SourceID *bc.Hash |
@@ -16,12 +23,81 @@ type Order struct { | ||
16 | 23 | Rate float64 |
17 | 24 | } |
18 | 25 | |
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 | + | |
19 | 84 | type TradePair struct { |
20 | 85 | FromAssetID *bc.AssetID |
21 | 86 | ToAssetID *bc.AssetID |
22 | 87 | Count int |
23 | 88 | } |
24 | 89 | |
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 | + | |
25 | 101 | type MovDatabaseState struct { |
26 | 102 | Height uint64 |
27 | 103 | Hash *bc.Hash |
@@ -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 | +} |
@@ -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 | +} |
@@ -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 | +} |
@@ -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 | +} |
@@ -12,6 +12,13 @@ import ( | ||
12 | 12 | "github.com/vapor/protocol/bc/types" |
13 | 13 | ) |
14 | 14 | |
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 | + | |
15 | 22 | const ( |
16 | 23 | order byte = iota |
17 | 24 | tradePair |
@@ -72,11 +79,11 @@ type tradePairData struct { | ||
72 | 79 | Count int |
73 | 80 | } |
74 | 81 | |
75 | -type MovStore struct { | |
82 | +type LevelDBMovStore struct { | |
76 | 83 | db dbm.DB |
77 | 84 | } |
78 | 85 | |
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) { | |
80 | 87 | if value := db.Get(bestMatchStore); value == nil { |
81 | 88 | state := &common.MovDatabaseState{Height: height, Hash: hash} |
82 | 89 | value, err := json.Marshal(state) |
@@ -86,10 +93,10 @@ func NewMovStore(db dbm.DB, height uint64, hash *bc.Hash) (*MovStore, error) { | ||
86 | 93 | |
87 | 94 | db.Set(bestMatchStore, value) |
88 | 95 | } |
89 | - return &MovStore{db: db}, nil | |
96 | + return &LevelDBMovStore{db: db}, nil | |
90 | 97 | } |
91 | 98 | |
92 | -func (m *MovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) { | |
99 | +func (m *LevelDBMovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) { | |
93 | 100 | if orderAfter.FromAssetID == nil || orderAfter.ToAssetID == nil { |
94 | 101 | return nil, errors.New("assetID is nil") |
95 | 102 | } |
@@ -123,7 +130,7 @@ func (m *MovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) | ||
123 | 130 | return orders, nil |
124 | 131 | } |
125 | 132 | |
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 { | |
127 | 134 | if err := m.checkMovDatabaseState(blockHeader); err != nil { |
128 | 135 | return err |
129 | 136 | } |
@@ -134,7 +141,7 @@ func (m *MovStore) ProcessOrders(addOrders []*common.Order, delOreders []*common | ||
134 | 141 | return err |
135 | 142 | } |
136 | 143 | |
137 | - m.deleteOrders(batch, delOreders, tradePairsCnt) | |
144 | + m.deleteOrders(batch, delOrders, tradePairsCnt) | |
138 | 145 | |
139 | 146 | if err := m.updateTradePairs(batch, tradePairsCnt); err != nil { |
140 | 147 | return err |
@@ -153,7 +160,7 @@ func (m *MovStore) ProcessOrders(addOrders []*common.Order, delOreders []*common | ||
153 | 160 | return nil |
154 | 161 | } |
155 | 162 | |
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 { | |
157 | 164 | for _, order := range orders { |
158 | 165 | data, err := json.Marshal(order.Utxo) |
159 | 166 | if err != nil { |
@@ -172,7 +179,7 @@ func (m *MovStore) addOrders(batch dbm.Batch, orders []*common.Order, tradePairs | ||
172 | 179 | return nil |
173 | 180 | } |
174 | 181 | |
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) { | |
176 | 183 | for _, order := range orders { |
177 | 184 | key := calcOrderKey(order.FromAssetID, order.ToAssetID, calcUTXOHash(order), order.Rate) |
178 | 185 | batch.Delete(key) |
@@ -185,7 +192,7 @@ func (m *MovStore) deleteOrders(batch dbm.Batch, orders []*common.Order, tradePa | ||
185 | 192 | } |
186 | 193 | } |
187 | 194 | |
188 | -func (m *MovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) { | |
195 | +func (m *LevelDBMovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) { | |
189 | 196 | if value := m.db.Get(bestMatchStore); value != nil { |
190 | 197 | state := &common.MovDatabaseState{} |
191 | 198 | return state, json.Unmarshal(value, state) |
@@ -194,7 +201,7 @@ func (m *MovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) { | ||
194 | 201 | return nil, errors.New("don't find state of mov-database") |
195 | 202 | } |
196 | 203 | |
197 | -func (m *MovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) { | |
204 | +func (m *LevelDBMovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) { | |
198 | 205 | var startKey []byte |
199 | 206 | if fromAssetIDAfter != nil && toAssetIDAfter != nil { |
200 | 207 | startKey = calcTradePairKey(fromAssetIDAfter, toAssetIDAfter) |
@@ -219,7 +226,7 @@ func (m *MovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc. | ||
219 | 226 | return tradePairs, nil |
220 | 227 | } |
221 | 228 | |
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 { | |
223 | 230 | for k, v := range tradePairs { |
224 | 231 | key := calcTradePairKey(k.FromAssetID, k.ToAssetID) |
225 | 232 | tradePairData := &tradePairData{} |
@@ -246,7 +253,7 @@ func (m *MovStore) updateTradePairs(batch dbm.Batch, tradePairs map[common.Trade | ||
246 | 253 | return nil |
247 | 254 | } |
248 | 255 | |
249 | -func (m *MovStore) checkMovDatabaseState(header *types.BlockHeader) error { | |
256 | +func (m *LevelDBMovStore) checkMovDatabaseState(header *types.BlockHeader) error { | |
250 | 257 | state, err := m.GetMovDatabaseState() |
251 | 258 | if err != nil { |
252 | 259 | return err |
@@ -259,7 +266,7 @@ func (m *MovStore) checkMovDatabaseState(header *types.BlockHeader) error { | ||
259 | 266 | return errors.New("the status of the block is inconsistent with that of mov-database") |
260 | 267 | } |
261 | 268 | |
262 | -func (m *MovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDatabaseState) error { | |
269 | +func (m *LevelDBMovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDatabaseState) error { | |
263 | 270 | value, err := json.Marshal(state) |
264 | 271 | if err != nil { |
265 | 272 | return err |
@@ -269,7 +276,7 @@ func (m *MovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDataba | ||
269 | 276 | return nil |
270 | 277 | } |
271 | 278 | |
272 | -func (m *MovStore) calcNextDatabaseState(blockHeader *types.BlockHeader) (*common.MovDatabaseState, error) { | |
279 | +func (m *LevelDBMovStore) calcNextDatabaseState(blockHeader *types.BlockHeader) (*common.MovDatabaseState, error) { | |
273 | 280 | hash := blockHeader.Hash() |
274 | 281 | height := blockHeader.Height |
275 | 282 |
@@ -1535,7 +1535,7 @@ func TestMovStore(t *testing.T) { | ||
1535 | 1535 | defer os.RemoveAll("temp") |
1536 | 1536 | for i, c := range cases { |
1537 | 1537 | testDB := dbm.NewDB("testdb", "leveldb", "temp") |
1538 | - movStore, err := NewMovStore(testDB, height, &hash) | |
1538 | + movStore, err := NewLevelDBMovStore(testDB, height, &hash) | |
1539 | 1539 | if err != nil { |
1540 | 1540 | t.Fatalf("case %d: NewMovStore error %v.", i, err) |
1541 | 1541 | } |
@@ -1969,7 +1969,7 @@ func TestListOrders(t *testing.T) { | ||
1969 | 1969 | defer os.RemoveAll("temp") |
1970 | 1970 | for i, c := range cases { |
1971 | 1971 | testDB := dbm.NewDB("testdb", "leveldb", "temp") |
1972 | - movStore, err := NewMovStore(testDB, height, &hash) | |
1972 | + movStore, err := NewLevelDBMovStore(testDB, height, &hash) | |
1973 | 1973 | if err != nil { |
1974 | 1974 | t.Fatalf("case %d: NewMovStore error %v.", i, err) |
1975 | 1975 | } |
@@ -2382,7 +2382,7 @@ func TestAddOrders(t *testing.T) { | ||
2382 | 2382 | defer os.RemoveAll("temp") |
2383 | 2383 | for i, c := range cases { |
2384 | 2384 | testDB := dbm.NewDB("testdb", "leveldb", "temp") |
2385 | - movStore, err := NewMovStore(testDB, height, &hash) | |
2385 | + movStore, err := NewLevelDBMovStore(testDB, height, &hash) | |
2386 | 2386 | if err != nil { |
2387 | 2387 | t.Fatalf("case %d: NewMovStore error %v.", i, err) |
2388 | 2388 | } |
@@ -2722,7 +2722,7 @@ func TestDelOrders(t *testing.T) { | ||
2722 | 2722 | defer os.RemoveAll("temp") |
2723 | 2723 | for i, c := range cases { |
2724 | 2724 | testDB := dbm.NewDB("testdb", "leveldb", "temp") |
2725 | - movStore, err := NewMovStore(testDB, height, &hash) | |
2725 | + movStore, err := NewLevelDBMovStore(testDB, height, &hash) | |
2726 | 2726 | if err != nil { |
2727 | 2727 | t.Fatalf("case %d: NewMovStore error %v.", i, err) |
2728 | 2728 | } |
@@ -2816,7 +2816,7 @@ func TestListTradePairsWithStart(t *testing.T) { | ||
2816 | 2816 | defer os.RemoveAll("temp") |
2817 | 2817 | for i, c := range cases { |
2818 | 2818 | testDB := dbm.NewDB("testdb", "leveldb", "temp") |
2819 | - movStore, err := NewMovStore(testDB, height, &hash) | |
2819 | + movStore, err := NewLevelDBMovStore(testDB, height, &hash) | |
2820 | 2820 | if err != nil { |
2821 | 2821 | t.Fatalf("case %d: NewMovStore error %v.", i, err) |
2822 | 2822 | } |
@@ -2923,7 +2923,7 @@ func TestUpdateTradePairs(t *testing.T) { | ||
2923 | 2923 | defer os.RemoveAll("temp") |
2924 | 2924 | for i, c := range cases { |
2925 | 2925 | testDB := dbm.NewDB("testdb", "leveldb", "temp") |
2926 | - movStore, err := NewMovStore(testDB, height, &hash) | |
2926 | + movStore, err := NewLevelDBMovStore(testDB, height, &hash) | |
2927 | 2927 | if err != nil { |
2928 | 2928 | t.Fatalf("case %d: NewMovStore error %v.", i, err) |
2929 | 2929 | } |
@@ -2995,7 +2995,7 @@ func TestCheckMovDatabaseState(t *testing.T) { | ||
2995 | 2995 | defer os.RemoveAll("temp") |
2996 | 2996 | for i, c := range cases { |
2997 | 2997 | testDB := dbm.NewDB("testdb", "leveldb", "temp") |
2998 | - movStore, err := NewMovStore(testDB, height, &hash) | |
2998 | + movStore, err := NewLevelDBMovStore(testDB, height, &hash) | |
2999 | 2999 | if err != nil { |
3000 | 3000 | t.Fatalf("case %d: NewMovStore error %v.", i, err) |
3001 | 3001 | } |
@@ -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 | +} |
@@ -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: ð, | |
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: ð, | |
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: ð, | |
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: ð, | |
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: ð, | |
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: ð} | |
84 | + eth2btc := &common.TradePair{FromAssetID: ð, 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: ð}, | |
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: ð}, | |
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: ð}, | |
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 | +} |
@@ -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 | +} |
@@ -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 | +} |