OSDN Git Service

mov core (#421)
[bytom/vapor.git] / application / mov / mov_core.go
1 package mov
2
3 import (
4         "github.com/vapor/application/mov/common"
5         "github.com/vapor/application/mov/contract"
6         "github.com/vapor/application/mov/database"
7         "github.com/vapor/application/mov/match"
8         "github.com/vapor/consensus/segwit"
9         "github.com/vapor/errors"
10         "github.com/vapor/math/checked"
11         "github.com/vapor/protocol/bc"
12         "github.com/vapor/protocol/bc/types"
13 )
14
15 const maxFeeRate = 0.05
16
17 var (
18         errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
19 )
20
21 type MovCore struct {
22         movStore database.MovStore
23 }
24
25 func NewMovCore(store database.MovStore) *MovCore {
26         return &MovCore{movStore: store}
27 }
28
29 // ChainStatus return the current block height and block hash in dex core
30 func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
31         state, err := m.movStore.GetMovDatabaseState()
32         if err != nil {
33                 return 0, nil, err
34         }
35
36         return state.Height, state.Hash, nil
37 }
38
39 func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
40         return m.ValidateTxs(block.Transactions, verifyResults)
41 }
42
43 // ValidateTxs validate the trade transaction.
44 func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error {
45         for _, tx := range txs {
46                 if common.IsMatchedTx(tx) {
47                         if err := validateMatchedTx(tx); err != nil {
48                                 return err
49                         }
50                 }
51
52                 if common.IsCancelOrderTx(tx) {
53                         if err := validateCancelOrderTx(tx); err != nil {
54                                 return err
55                         }
56                 }
57
58                 for _, output := range tx.Outputs {
59                         if !segwit.IsP2WMCScript(output.ControlProgram()) {
60                                 continue
61                         }
62
63                         if err := validateMagneticContractArgs(output.AssetAmount().Amount, output.ControlProgram()); err != nil {
64                                 return err
65                         }
66                 }
67         }
68         return nil
69 }
70
71 func validateMatchedTx(tx *types.Tx) error {
72         fromAssetIDMap := make(map[string]bool)
73         toAssetIDMap := make(map[string]bool)
74         for i, input := range tx.Inputs {
75                 if !segwit.IsP2WMCScript(input.ControlProgram()) {
76                         return errors.New("input program of matched tx must p2wmc script")
77                 }
78
79                 if contract.IsCancelClauseSelector(input) {
80                         return errors.New("can't exist cancel order in the matched transaction")
81                 }
82
83                 order, err := common.NewOrderFromInput(tx, i)
84                 if err != nil {
85                         return err
86                 }
87
88                 fromAssetIDMap[order.FromAssetID.String()] = true
89                 toAssetIDMap[order.ToAssetID.String()] = true
90         }
91
92         if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
93                 return errors.New("asset id must unique in matched transaction")
94         }
95
96         return validateMatchedTxFeeAmount(tx)
97 }
98
99 func validateCancelOrderTx(tx *types.Tx) error {
100         for _, input := range tx.Inputs {
101                 if !segwit.IsP2WMCScript(input.ControlProgram()) {
102                         return errors.New("input program of cancel order tx must p2wmc script")
103                 }
104
105                 if contract.IsTradeClauseSelector(input) {
106                         return errors.New("can't exist trade order in the cancel order transaction")
107                 }
108         }
109         return nil
110 }
111
112 func validateMatchedTxFeeAmount(tx *types.Tx) error {
113         txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
114         if err != nil {
115                 return err
116         }
117
118         for _, amount := range txFee {
119                 if amount.FeeAmount > amount.MaxFeeAmount {
120                         return errors.New("amount of fee greater than max fee amount")
121                 }
122         }
123         return nil
124 }
125
126 func validateMagneticContractArgs(inputAmount uint64, program []byte) error {
127         contractArgs, err := segwit.DecodeP2WMCProgram(program)
128         if err != nil {
129                 return err
130         }
131
132         if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
133                 return errors.New("ratio arguments must greater than zero")
134         }
135
136         if _, ok := checked.MulInt64(int64(inputAmount), contractArgs.RatioNumerator); !ok {
137                 return errors.New("ratio numerator of contract args product input amount is overflow")
138         }
139         return nil
140 }
141
142 // ApplyBlock parse pending order and cancel from the the transactions of block
143 // and add pending order to the dex db, remove cancel order from dex db.
144 func (m *MovCore) ApplyBlock(block *types.Block) error {
145         if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
146                 return err
147         }
148
149         addOrders, deleteOrders, err := applyTransactions(block.Transactions)
150         if err != nil {
151                 return err
152         }
153
154         return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
155 }
156
157 func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx, ) error {
158         matchEngine := match.NewEngine(m.movStore, maxFeeRate, nil)
159         for _, matchedTx := range txs {
160                 if !common.IsMatchedTx(matchedTx) {
161                         continue
162                 }
163
164                 tradePairs, err := getSortedTradePairsFromMatchedTx(matchedTx)
165                 if err != nil {
166                         return err
167                 }
168
169                 actualMatchedTx, err := matchEngine.NextMatchedTx(tradePairs...)
170                 if err != nil {
171                         return err
172                 }
173
174                 if len(matchedTx.Inputs) != len(actualMatchedTx.Inputs) {
175                         return errors.New("length of matched tx input is not equals to actual matched tx input")
176                 }
177
178                 spendOutputIDs := make(map[string]bool)
179                 for _, input := range matchedTx.Inputs {
180                         spendOutputID, err := input.SpentOutputID()
181                         if err != nil {
182                                 return err
183                         }
184
185                         spendOutputIDs[spendOutputID.String()] = true
186                 }
187
188                 for _, input := range actualMatchedTx.Inputs {
189                         spendOutputID, err := input.SpentOutputID()
190                         if err != nil {
191                                 return err
192                         }
193
194                         if _, ok := spendOutputIDs[spendOutputID.String()]; !ok {
195                                 return errors.New("spend output id of matched tx is not equals to actual matched tx")
196                         }
197                 }
198         }
199         return nil
200 }
201
202 func getSortedTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
203         assetMap := make(map[bc.AssetID]bc.AssetID)
204         var firstTradePair *common.TradePair
205         for _, tx := range tx.Inputs {
206                 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
207                 if err != nil {
208                         return nil, err
209                 }
210
211                 assetMap[tx.AssetID()] = contractArgs.RequestedAsset
212                 if firstTradePair == nil {
213                         firstTradePair = &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset}
214                 }
215         }
216
217         tradePairs := []*common.TradePair{firstTradePair}
218         for tradePair := firstTradePair; *tradePair.ToAssetID != *firstTradePair.FromAssetID; {
219                 nextTradePairToAssetID, ok := assetMap[*tradePair.ToAssetID]
220                 if !ok {
221                         return nil, errInvalidTradePairs
222                 }
223
224                 tradePair = &common.TradePair{FromAssetID: tradePair.ToAssetID, ToAssetID: &nextTradePairToAssetID}
225                 tradePairs = append(tradePairs, tradePair)
226         }
227
228         if len(tradePairs) != len(tx.Inputs) {
229                 return nil, errInvalidTradePairs
230         }
231         return tradePairs, nil
232 }
233
234 // DetachBlock parse pending order and cancel from the the transactions of block
235 // and add cancel order to the dex db, remove pending order from dex db.
236 func (m *MovCore) DetachBlock(block *types.Block) error {
237         deleteOrders, addOrders, err := applyTransactions(block.Transactions)
238         if err != nil {
239                 return err
240         }
241
242         return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
243 }
244
245 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
246 func (m *MovCore) BeforeProposalBlock(capacity int, nodeProgram []byte) ([]*types.Tx, error) {
247         matchEngine := match.NewEngine(m.movStore, maxFeeRate, nodeProgram)
248         tradePairMap := make(map[string]bool)
249         tradePairIterator := database.NewTradePairIterator(m.movStore)
250
251         var packagedTxs []*types.Tx
252         for len(packagedTxs) < capacity && tradePairIterator.HasNext() {
253                 tradePair := tradePairIterator.Next()
254                 if tradePairMap[tradePair.Key()] {
255                         continue
256                 }
257                 tradePairMap[tradePair.Key()] = true
258                 tradePairMap[tradePair.Reverse().Key()] = true
259
260                 for len(packagedTxs) < capacity && matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) {
261                         matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
262                         if err != nil {
263                                 return nil, err
264                         }
265
266                         packagedTxs = append(packagedTxs, matchedTx)
267                 }
268         }
269         return packagedTxs, nil
270 }
271
272 func (m *MovCore) IsDust(tx *types.Tx) bool {
273         for _, input := range tx.Inputs {
274                 if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
275                         return true
276                 }
277         }
278         return false
279 }
280
281 func applyTransactions(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
282         deleteOrderMap := make(map[string]*common.Order)
283         addOrderMap := make(map[string]*common.Order)
284         for _, tx := range txs {
285                 addOrders, err := getAddOrdersFromTx(tx)
286                 if err != nil {
287                         return nil, nil, err
288                 }
289
290                 for _, order := range addOrders {
291                         addOrderMap[order.Key()] = order
292                 }
293
294                 deleteOrders, err := getDeleteOrdersFromTx(tx)
295                 if err != nil {
296                         return nil, nil, err
297                 }
298
299                 for _, order := range deleteOrders {
300                         deleteOrderMap[order.Key()] = order
301                 }
302         }
303
304         addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
305         return addOrders, deleteOrders, nil
306 }
307
308 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
309         var deleteOrders, addOrders []*common.Order
310         for orderID, order := range addOrderMap {
311                 if _, ok := deleteOrderMap[orderID]; ok {
312                         delete(deleteOrderMap, orderID)
313                         continue
314                 }
315                 addOrders = append(addOrders, order)
316         }
317
318         for _, order := range deleteOrderMap {
319                 deleteOrders = append(deleteOrders, order)
320         }
321         return addOrders, deleteOrders
322 }
323
324 func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
325         var orders []*common.Order
326         for i, output := range tx.Outputs {
327                 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
328                         continue
329                 }
330
331                 order, err := common.NewOrderFromOutput(tx, i)
332                 if err != nil {
333                         return nil, err
334                 }
335
336                 orders = append(orders, order)
337         }
338         return orders, nil
339 }
340
341 func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
342         var orders []*common.Order
343         for i, input := range tx.Inputs {
344                 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
345                         continue
346                 }
347
348                 order, err := common.NewOrderFromInput(tx, i)
349                 if err != nil {
350                         return nil, err
351                 }
352
353                 orders = append(orders, order)
354         }
355         return orders, nil
356 }