OSDN Git Service

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