OSDN Git Service

843a1831a57ca04cf0fa75485c486820fe365894
[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         dbm "github.com/vapor/database/leveldb"
10         "github.com/vapor/errors"
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         errStatusFailMustFalse           = errors.New("status fail of transaction does not allow to be true")
20         errInputProgramMustP2WMCScript   = errors.New("input program of trade tx must p2wmc script")
21         errExistCancelOrderInMatchedTx   = errors.New("can't exist cancel order in the matched transaction")
22         errExistTradeInCancelOrderTx     = errors.New("can't exist trade in the cancel order transaction")
23         errAmountOfFeeGreaterThanMaximum = errors.New("amount of fee greater than max fee amount")
24         errAssetIDMustUniqueInMatchedTx  = errors.New("asset id must unique in matched transaction")
25         errRatioOfTradeLessThanZero      = errors.New("ratio arguments must greater than zero")
26         errLengthOfInputIsIncorrect      = errors.New("length of matched tx input is not equals to actual matched tx input")
27         errSpendOutputIDIsIncorrect      = errors.New("spend output id of matched tx is not equals to actual matched tx")
28         errRequestAmountMath             = errors.New("request amount of order less than one or big than max of int64")
29 )
30
31 // MovCore represent the core logic of the match module, which include generate match transactions before packing the block,
32 // verify the match transaction in block is correct, and update the order table according to the transaction.
33 type MovCore struct {
34         movStore         database.MovStore
35         startBlockHeight uint64
36 }
37
38 // NewMovCore return a instance of MovCore by path of mov db
39 func NewMovCore(dbBackend, dbDir string, startBlockHeight uint64) *MovCore {
40         movDB := dbm.NewDB("mov", dbBackend, dbDir)
41         return &MovCore{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
42 }
43
44 // ApplyBlock parse pending order and cancel from the the transactions of block
45 // and add pending order to the dex db, remove cancel order from dex db.
46 func (m *MovCore) ApplyBlock(block *types.Block) error {
47         if block.Height < m.startBlockHeight {
48                 return nil
49         }
50
51         if block.Height == m.startBlockHeight {
52                 blockHash := block.Hash()
53                 if err := m.movStore.InitDBState(block.Height, &blockHash); err != nil {
54                         return err
55                 }
56
57                 return nil
58         }
59
60         if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
61                 return err
62         }
63
64         addOrders, deleteOrders, err := applyTransactions(block.Transactions)
65         if err != nil {
66                 return err
67         }
68
69         return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
70 }
71
72 /*
73         @issue: I have two orders A and B, order A's seller program is order B and order B's seller program is order A.
74     Assume consensus node accept 0% fee and This two orders are the only two order of this trading pair, will this
75     become an infinite loop and DDoS attacks the whole network?
76 */
77 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
78 func (m *MovCore) BeforeProposalBlock(txs []*types.Tx, nodeProgram []byte, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
79         if blockHeight <= m.startBlockHeight {
80                 return nil, nil
81         }
82
83         orderTable, err := buildOrderTable(m.movStore, txs)
84         if err != nil {
85                 return nil, err
86         }
87
88         matchEngine := match.NewEngine(orderTable, maxFeeRate, nodeProgram)
89         tradePairMap := make(map[string]bool)
90         tradePairIterator := database.NewTradePairIterator(m.movStore)
91
92         var packagedTxs []*types.Tx
93         for gasLeft > 0 && !isTimeout() && tradePairIterator.HasNext() {
94                 tradePair := tradePairIterator.Next()
95                 if tradePairMap[tradePair.Key()] {
96                         continue
97                 }
98                 tradePairMap[tradePair.Key()] = true
99                 tradePairMap[tradePair.Reverse().Key()] = true
100
101                 for gasLeft > 0 && !isTimeout() && matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) {
102                         matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
103                         if err != nil {
104                                 return nil, err
105                         }
106
107                         gasUsed := calcMatchedTxGasUsed(matchedTx)
108                         if gasLeft-gasUsed >= 0 {
109                                 packagedTxs = append(packagedTxs, matchedTx)
110                         }
111                         gasLeft -= gasUsed
112                 }
113         }
114         return packagedTxs, nil
115 }
116
117 // ChainStatus return the current block height and block hash in dex core
118 func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
119         state, err := m.movStore.GetMovDatabaseState()
120         if err != nil {
121                 return 0, nil, err
122         }
123
124         return state.Height, state.Hash, nil
125 }
126
127 // DetachBlock parse pending order and cancel from the the transactions of block
128 // and add cancel order to the dex db, remove pending order from dex db.
129 func (m *MovCore) DetachBlock(block *types.Block) error {
130         if block.Height <= m.startBlockHeight {
131                 return nil
132         }
133
134         deleteOrders, addOrders, err := applyTransactions(block.Transactions)
135         if err != nil {
136                 return err
137         }
138
139         return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
140 }
141
142 // IsDust block the transaction that are not generated by the match engine
143 func (m *MovCore) IsDust(tx *types.Tx) bool {
144         for _, input := range tx.Inputs {
145                 if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
146                         return true
147                 }
148         }
149         return false
150 }
151
152 // Name return the name of current module
153 func (m *MovCore) Name() string {
154         return "MOV"
155 }
156
157 // StartHeight return the start block height of current module
158 func (m *MovCore) StartHeight() uint64 {
159         return m.startBlockHeight
160 }
161
162 // ValidateBlock no need to verify the block header, because the first module has been verified.
163 // just need to verify the transactions in the block.
164 func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
165         return m.ValidateTxs(block.Transactions, verifyResults)
166 }
167
168 // ValidateTxs validate the trade transaction.
169 func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error {
170         for i, tx := range txs {
171                 if err := m.ValidateTx(tx, verifyResults[i]); err != nil {
172                         return err
173                 }
174         }
175         return nil
176 }
177
178 // ValidateTxs validate one transaction.
179 func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
180         if common.IsMatchedTx(tx) {
181                 if err := validateMatchedTx(tx, verifyResult); err != nil {
182                         return err
183                 }
184         }
185
186         if common.IsCancelOrderTx(tx) {
187                 if err := validateCancelOrderTx(tx, verifyResult); err != nil {
188                         return err
189                 }
190         }
191
192         for _, output := range tx.Outputs {
193                 if !segwit.IsP2WMCScript(output.ControlProgram()) {
194                         continue
195                 }
196                 if verifyResult.StatusFail {
197                         return errStatusFailMustFalse
198                 }
199
200                 if err := validateMagneticContractArgs(output.AssetAmount().Amount, output.ControlProgram()); err != nil {
201                         return err
202                 }
203         }
204         return nil
205 }
206
207 func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
208         if verifyResult.StatusFail {
209                 return errStatusFailMustFalse
210         }
211
212         for _, input := range tx.Inputs {
213                 if !segwit.IsP2WMCScript(input.ControlProgram()) {
214                         return errInputProgramMustP2WMCScript
215                 }
216
217                 if contract.IsTradeClauseSelector(input) {
218                         return errExistTradeInCancelOrderTx
219                 }
220         }
221         return nil
222 }
223
224 func validateMagneticContractArgs(fromAmount uint64, program []byte) error {
225         contractArgs, err := segwit.DecodeP2WMCProgram(program)
226         if err != nil {
227                 return err
228         }
229
230         if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
231                 return errRatioOfTradeLessThanZero
232         }
233
234         if match.CalcRequestAmount(fromAmount, contractArgs) < 1 {
235                 return errRequestAmountMath
236         }
237         return nil
238 }
239
240 func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
241         if verifyResult.StatusFail {
242                 return errStatusFailMustFalse
243         }
244
245         fromAssetIDMap := make(map[string]bool)
246         toAssetIDMap := make(map[string]bool)
247         for i, input := range tx.Inputs {
248                 if !segwit.IsP2WMCScript(input.ControlProgram()) {
249                         return errInputProgramMustP2WMCScript
250                 }
251
252                 if contract.IsCancelClauseSelector(input) {
253                         return errExistCancelOrderInMatchedTx
254                 }
255
256                 order, err := common.NewOrderFromInput(tx, i)
257                 if err != nil {
258                         return err
259                 }
260
261                 if *order.FromAssetID == *order.ToAssetID {
262                         return errInvalidTradePairs
263                 }
264
265                 fromAssetIDMap[order.FromAssetID.String()] = true
266                 toAssetIDMap[order.ToAssetID.String()] = true
267         }
268
269         if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
270                 return errAssetIDMustUniqueInMatchedTx
271         }
272
273         return validateMatchedTxFeeAmount(tx)
274 }
275
276 func validateMatchedTxFeeAmount(tx *types.Tx) error {
277         txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
278         if err != nil {
279                 return err
280         }
281
282         for _, amount := range txFee {
283                 if amount.FeeAmount > amount.MaxFeeAmount {
284                         return errAmountOfFeeGreaterThanMaximum
285                 }
286         }
287         return nil
288 }
289
290 /*
291         @issue: the match package didn't support circle yet
292 */
293 func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
294         orderTable, err := buildOrderTable(m.movStore, txs)
295         if err != nil {
296                 return err
297         }
298
299         matchEngine := match.NewEngine(orderTable, maxFeeRate, nil)
300         for _, matchedTx := range txs {
301                 if !common.IsMatchedTx(matchedTx) {
302                         continue
303                 }
304
305                 tradePairs, err := getSortedTradePairsFromMatchedTx(matchedTx)
306                 if err != nil {
307                         return err
308                 }
309
310                 actualMatchedTx, err := matchEngine.NextMatchedTx(tradePairs...)
311                 if err != nil {
312                         return err
313                 }
314
315                 if len(matchedTx.Inputs) != len(actualMatchedTx.Inputs) {
316                         return errLengthOfInputIsIncorrect
317                 }
318
319                 spendOutputIDs := make(map[string]bool)
320                 for _, input := range matchedTx.Inputs {
321                         spendOutputID, err := input.SpentOutputID()
322                         if err != nil {
323                                 return err
324                         }
325
326                         spendOutputIDs[spendOutputID.String()] = true
327                 }
328
329                 for _, input := range actualMatchedTx.Inputs {
330                         spendOutputID, err := input.SpentOutputID()
331                         if err != nil {
332                                 return err
333                         }
334
335                         if _, ok := spendOutputIDs[spendOutputID.String()]; !ok {
336                                 return errSpendOutputIDIsIncorrect
337                         }
338                 }
339         }
340         return nil
341 }
342
343 func applyTransactions(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
344         deleteOrderMap := make(map[string]*common.Order)
345         addOrderMap := make(map[string]*common.Order)
346         for _, tx := range txs {
347                 addOrders, err := getAddOrdersFromTx(tx)
348                 if err != nil {
349                         return nil, nil, err
350                 }
351
352                 for _, order := range addOrders {
353                         addOrderMap[order.Key()] = order
354                 }
355
356                 deleteOrders, err := getDeleteOrdersFromTx(tx)
357                 if err != nil {
358                         return nil, nil, err
359                 }
360
361                 for _, order := range deleteOrders {
362                         deleteOrderMap[order.Key()] = order
363                 }
364         }
365
366         addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
367         return addOrders, deleteOrders, nil
368 }
369
370 /*
371         @issue: if consensus node packed match transaction first then packed regular tx, this function's logic may make a valid block invalid
372 */
373 func buildOrderTable(store database.MovStore, txs []*types.Tx) (*match.OrderTable, error) {
374         var nonMatchedTxs []*types.Tx
375         for _, tx := range txs {
376                 if !common.IsMatchedTx(tx) {
377                         nonMatchedTxs = append(nonMatchedTxs, tx)
378                 }
379         }
380
381         var arrivalAddOrders, arrivalDelOrders []*common.Order
382         for _, tx := range nonMatchedTxs {
383                 addOrders, err := getAddOrdersFromTx(tx)
384                 if err != nil {
385                         return nil, err
386                 }
387
388                 delOrders, err := getDeleteOrdersFromTx(tx)
389                 if err != nil {
390                         return nil, err
391                 }
392
393                 arrivalAddOrders = append(arrivalAddOrders, addOrders...)
394                 arrivalDelOrders = append(arrivalDelOrders, delOrders...)
395         }
396
397         return match.NewOrderTable(store, arrivalAddOrders, arrivalDelOrders), nil
398 }
399
400 func calcMatchedTxGasUsed(tx *types.Tx) int64 {
401         return int64(len(tx.Inputs))*150 + int64(tx.SerializedSize)
402 }
403
404 func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
405         var orders []*common.Order
406         for i, output := range tx.Outputs {
407                 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
408                         continue
409                 }
410
411                 order, err := common.NewOrderFromOutput(tx, i)
412                 if err != nil {
413                         return nil, err
414                 }
415
416                 orders = append(orders, order)
417         }
418         return orders, nil
419 }
420
421 func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
422         var orders []*common.Order
423         for i, input := range tx.Inputs {
424                 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
425                         continue
426                 }
427
428                 order, err := common.NewOrderFromInput(tx, i)
429                 if err != nil {
430                         return nil, err
431                 }
432
433                 orders = append(orders, order)
434         }
435         return orders, nil
436 }
437
438 func getSortedTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
439         assetMap := make(map[bc.AssetID]bc.AssetID)
440         var firstTradePair *common.TradePair
441         for _, tx := range tx.Inputs {
442                 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
443                 if err != nil {
444                         return nil, err
445                 }
446
447                 assetMap[tx.AssetID()] = contractArgs.RequestedAsset
448                 if firstTradePair == nil {
449                         firstTradePair = &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset}
450                 }
451         }
452
453         tradePairs := []*common.TradePair{firstTradePair}
454         for tradePair := firstTradePair; *tradePair.ToAssetID != *firstTradePair.FromAssetID; {
455                 nextTradePairToAssetID, ok := assetMap[*tradePair.ToAssetID]
456                 if !ok {
457                         return nil, errInvalidTradePairs
458                 }
459
460                 tradePair = &common.TradePair{FromAssetID: tradePair.ToAssetID, ToAssetID: &nextTradePairToAssetID}
461                 tradePairs = append(tradePairs, tradePair)
462         }
463
464         if len(tradePairs) != len(tx.Inputs) {
465                 return nil, errInvalidTradePairs
466         }
467         return tradePairs, nil
468 }
469
470 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
471         var deleteOrders, addOrders []*common.Order
472         for orderID, order := range addOrderMap {
473                 if _, ok := deleteOrderMap[orderID]; ok {
474                         delete(deleteOrderMap, orderID)
475                         continue
476                 }
477                 addOrders = append(addOrders, order)
478         }
479
480         for _, order := range deleteOrderMap {
481                 deleteOrders = append(deleteOrders, order)
482         }
483         return addOrders, deleteOrders
484 }