OSDN Git Service

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