OSDN Git Service

955a493b849ec4dce69ea1d513bfcafd1e362c08
[bytom/vapor.git] / application / mov / mov_core.go
1 package mov
2
3 import (
4         "encoding/hex"
5
6         "github.com/bytom/vapor/application/mov/common"
7         "github.com/bytom/vapor/application/mov/contract"
8         "github.com/bytom/vapor/application/mov/database"
9         "github.com/bytom/vapor/application/mov/match"
10         "github.com/bytom/vapor/consensus"
11         "github.com/bytom/vapor/consensus/segwit"
12         dbm "github.com/bytom/vapor/database/leveldb"
13         "github.com/bytom/vapor/errors"
14         "github.com/bytom/vapor/protocol/bc"
15         "github.com/bytom/vapor/protocol/bc/types"
16 )
17
18 const maxFeeRate = 0.05
19
20 var (
21         errInvalidTradePairs            = errors.New("The trade pairs in the tx input is invalid")
22         errStatusFailMustFalse          = errors.New("status fail of transaction does not allow to be true")
23         errInputProgramMustP2WMCScript  = errors.New("input program of trade tx must p2wmc script")
24         errExistCancelOrderInMatchedTx  = errors.New("can't exist cancel order in the matched transaction")
25         errExistTradeInCancelOrderTx    = errors.New("can't exist trade in the cancel order transaction")
26         errAssetIDMustUniqueInMatchedTx = errors.New("asset id must unique in matched transaction")
27         errRatioOfTradeLessThanZero     = errors.New("ratio arguments must greater than zero")
28         errSpendOutputIDIsIncorrect     = errors.New("spend output id of matched tx is not equals to actual matched tx")
29         errRequestAmountMath            = errors.New("request amount of order less than one or big than max of int64")
30         errNotMatchedOrder              = errors.New("order in matched tx is not matched")
31         errNotConfiguredRewardProgram   = errors.New("reward program is not configured properly")
32         errRewardProgramIsWrong         = errors.New("the reward program is not correct")
33 )
34
35 // MovCore represent the core logic of the match module, which include generate match transactions before packing the block,
36 // verify the match transaction in block is correct, and update the order table according to the transaction.
37 type MovCore struct {
38         movStore         database.MovStore
39         startBlockHeight uint64
40 }
41
42 // NewMovCore return a instance of MovCore by path of mov db
43 func NewMovCore(dbBackend, dbDir string, startBlockHeight uint64) *MovCore {
44         movDB := dbm.NewDB("mov", dbBackend, dbDir)
45         return &MovCore{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
46 }
47
48 // ApplyBlock parse pending order and cancel from the the transactions of block
49 // and add pending order to the dex db, remove cancel order from dex db.
50 func (m *MovCore) ApplyBlock(block *types.Block) error {
51         if block.Height < m.startBlockHeight {
52                 return nil
53         }
54
55         if block.Height == m.startBlockHeight {
56                 blockHash := block.Hash()
57                 if err := m.movStore.InitDBState(block.Height, &blockHash); err != nil {
58                         return err
59                 }
60
61                 // the next block can send orders
62                 return nil
63         }
64
65         if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
66                 return err
67         }
68
69         addOrders, deleteOrders, err := decodeTxsOrders(block.Transactions)
70         if err != nil {
71                 return err
72         }
73
74         return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
75 }
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, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
79         if blockHeight <= m.startBlockHeight {
80                 return nil, nil
81         }
82
83         orderBook, err := buildOrderBook(m.movStore, txs)
84         if err != nil {
85                 return nil, err
86         }
87
88         program, _ := getRewardProgram(blockHeight)
89         rewardProgram, err := hex.DecodeString(program)
90         if err != nil {
91                 return nil, errNotConfiguredRewardProgram
92         }
93
94         matchEngine := match.NewEngine(orderBook, match.NewDefaultFeeStrategy(maxFeeRate), rewardProgram)
95         tradePairIterator := database.NewTradePairIterator(m.movStore)
96         matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout)
97         return matchCollector.result()
98 }
99
100 // ChainStatus return the current block height and block hash in dex core
101 func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
102         state, err := m.movStore.GetMovDatabaseState()
103         if err != nil {
104                 return 0, nil, err
105         }
106
107         return state.Height, state.Hash, nil
108 }
109
110 // DetachBlock parse pending order and cancel from the the transactions of block
111 // and add cancel order to the dex db, remove pending order from dex db.
112 func (m *MovCore) DetachBlock(block *types.Block) error {
113         if block.Height < m.startBlockHeight {
114                 return nil
115         }
116
117         deleteOrders, addOrders, err := decodeTxsOrders(block.Transactions)
118         if err != nil {
119                 return err
120         }
121
122         return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
123 }
124
125 // IsDust block the transaction that are not generated by the match engine
126 func (m *MovCore) IsDust(tx *types.Tx) bool {
127         for _, input := range tx.Inputs {
128                 if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
129                         return true
130                 }
131         }
132         return false
133 }
134
135 // Name return the name of current module
136 func (m *MovCore) Name() string {
137         return "MOV"
138 }
139
140 // StartHeight return the start block height of current module
141 func (m *MovCore) StartHeight() uint64 {
142         return m.startBlockHeight
143 }
144
145 // ValidateBlock no need to verify the block header, because the first module has been verified.
146 // just need to verify the transactions in the block.
147 func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
148         for i, tx := range block.Transactions {
149                 if err := m.ValidateTx(tx, verifyResults[i], block.Height); err != nil {
150                         return err
151                 }
152         }
153         return nil
154 }
155
156 // ValidateTx validate one transaction.
157 func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
158         if common.IsMatchedTx(tx) {
159                 if err := validateMatchedTx(tx, verifyResult, blockHeight); err != nil {
160                         return err
161                 }
162         } else if common.IsCancelOrderTx(tx) {
163                 if err := validateCancelOrderTx(tx, verifyResult); err != nil {
164                         return err
165                 }
166         }
167
168         for _, output := range tx.Outputs {
169                 if !segwit.IsP2WMCScript(output.ControlProgram()) {
170                         continue
171                 }
172                 if verifyResult.StatusFail {
173                         return errStatusFailMustFalse
174                 }
175
176                 if err := validateMagneticContractArgs(output.AssetAmount(), output.ControlProgram()); err != nil {
177                         return err
178                 }
179         }
180         return nil
181 }
182
183 // matchedTxFee is object to record the mov tx's fee information
184 type matchedTxFee struct {
185         rewardProgram []byte
186         amount        int64
187 }
188
189 // calcFeeAmount return the amount of fee in the matching transaction
190 func calcFeeAmount(matchedTx *types.Tx) (map[bc.AssetID]*matchedTxFee, error) {
191         assetFeeMap := make(map[bc.AssetID]*matchedTxFee)
192         dealProgMaps := make(map[string]bool)
193
194         for _, input := range matchedTx.Inputs {
195                 assetFeeMap[input.AssetID()] = &matchedTxFee{amount: int64(input.AssetAmount().Amount)}
196                 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
197                 if err != nil {
198                         return nil, err
199                 }
200
201                 dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
202         }
203
204         for _, output := range matchedTx.Outputs {
205                 assetAmount := output.AssetAmount()
206                 if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
207                         assetFeeMap[*assetAmount.AssetId].amount -= int64(assetAmount.Amount)
208                         if assetFeeMap[*assetAmount.AssetId].amount <= 0 {
209                                 delete(assetFeeMap, *assetAmount.AssetId)
210                         }
211                 } else {
212                         assetFeeMap[*assetAmount.AssetId].rewardProgram = output.ControlProgram()
213                 }
214         }
215         return assetFeeMap, nil
216 }
217
218 func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
219         if verifyResult.StatusFail {
220                 return errStatusFailMustFalse
221         }
222
223         for _, input := range tx.Inputs {
224                 if !segwit.IsP2WMCScript(input.ControlProgram()) {
225                         return errInputProgramMustP2WMCScript
226                 }
227
228                 if contract.IsTradeClauseSelector(input) {
229                         return errExistTradeInCancelOrderTx
230                 }
231         }
232         return nil
233 }
234
235 func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte) error {
236         contractArgs, err := segwit.DecodeP2WMCProgram(program)
237         if err != nil {
238                 return err
239         }
240
241         if *fromAssetAmount.AssetId == contractArgs.RequestedAsset {
242                 return errInvalidTradePairs
243         }
244
245         if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
246                 return errRatioOfTradeLessThanZero
247         }
248
249         if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) < 1 {
250                 return errRequestAmountMath
251         }
252         return nil
253 }
254
255 func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
256         if verifyResult.StatusFail {
257                 return errStatusFailMustFalse
258         }
259
260         fromAssetIDMap := make(map[string]bool)
261         toAssetIDMap := make(map[string]bool)
262         for i, input := range tx.Inputs {
263                 if !segwit.IsP2WMCScript(input.ControlProgram()) {
264                         return errInputProgramMustP2WMCScript
265                 }
266
267                 if contract.IsCancelClauseSelector(input) {
268                         return errExistCancelOrderInMatchedTx
269                 }
270
271                 order, err := common.NewOrderFromInput(tx, i)
272                 if err != nil {
273                         return err
274                 }
275
276                 fromAssetIDMap[order.FromAssetID.String()] = true
277                 toAssetIDMap[order.ToAssetID.String()] = true
278         }
279
280         if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
281                 return errAssetIDMustUniqueInMatchedTx
282         }
283
284         return validateMatchedTxFee(tx, blockHeight)
285 }
286
287 func validateMatchedTxFee(tx *types.Tx, blockHeight uint64) error {
288         matchedTxFees, err := calcFeeAmount(tx)
289         if err != nil {
290                 return err
291         }
292
293         for _, fee := range matchedTxFees {
294                 if err := validateRewardProgram(blockHeight, hex.EncodeToString(fee.rewardProgram)); err != nil {
295                         return err
296                 }
297         }
298
299         orders, err := getDeleteOrdersFromTx(tx)
300         if err != nil {
301                 return err
302         }
303
304         feeAmounts := make(map[bc.AssetID]int64)
305         for assetID, fee := range matchedTxFees {
306                 feeAmounts[assetID] = fee.amount
307         }
308
309         receivedAmount, priceDiff := match.CalcReceivedAmount(orders)
310         feeStrategy := match.NewDefaultFeeStrategy(maxFeeRate)
311         return feeStrategy.Validate(receivedAmount, priceDiff, feeAmounts)
312 }
313
314 func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
315         orderBook := match.NewOrderBook(m.movStore, nil, nil)
316         for _, tx := range txs {
317                 if common.IsMatchedTx(tx) {
318                         tradePairs, err := getTradePairsFromMatchedTx(tx)
319                         if err != nil {
320                                 return err
321                         }
322
323                         orders := orderBook.PeekOrders(tradePairs)
324                         if err := validateSpendOrders(tx, orders); err != nil {
325                                 return err
326                         }
327
328                         orderBook.PopOrders(tradePairs)
329                 } else if common.IsCancelOrderTx(tx) {
330                         orders, err := getDeleteOrdersFromTx(tx)
331                         if err != nil {
332                                 return err
333                         }
334
335                         for _, order := range orders {
336                                 orderBook.DelOrder(order)
337                         }
338                 }
339
340                 addOrders, err := getAddOrdersFromTx(tx)
341                 if err != nil {
342                         return err
343                 }
344
345                 for _, order := range addOrders {
346                         orderBook.AddOrder(order)
347                 }
348         }
349         return nil
350 }
351
352 func validateSpendOrders(tx *types.Tx, orders []*common.Order) error {
353         if len(tx.Inputs) != len(orders) {
354                 return errNotMatchedOrder
355         }
356
357         spendOutputIDs := make(map[string]bool)
358         for _, input := range tx.Inputs {
359                 spendOutputID, err := input.SpentOutputID()
360                 if err != nil {
361                         return err
362                 }
363
364                 spendOutputIDs[spendOutputID.String()] = true
365         }
366
367         for _, order := range orders {
368                 outputID := order.UTXOHash().String()
369                 if _, ok := spendOutputIDs[outputID]; !ok {
370                         return errSpendOutputIDIsIncorrect
371                 }
372         }
373         return nil
374 }
375
376 func decodeTxsOrders(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
377         deleteOrderMap := make(map[string]*common.Order)
378         addOrderMap := make(map[string]*common.Order)
379         for _, tx := range txs {
380                 addOrders, err := getAddOrdersFromTx(tx)
381                 if err != nil {
382                         return nil, nil, err
383                 }
384
385                 for _, order := range addOrders {
386                         addOrderMap[order.Key()] = order
387                 }
388
389                 deleteOrders, err := getDeleteOrdersFromTx(tx)
390                 if err != nil {
391                         return nil, nil, err
392                 }
393
394                 for _, order := range deleteOrders {
395                         deleteOrderMap[order.Key()] = order
396                 }
397         }
398
399         addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
400         return addOrders, deleteOrders, nil
401 }
402
403 func buildOrderBook(store database.MovStore, txs []*types.Tx) (*match.OrderBook, error) {
404         var nonMatchedTxs []*types.Tx
405         for _, tx := range txs {
406                 if !common.IsMatchedTx(tx) {
407                         nonMatchedTxs = append(nonMatchedTxs, tx)
408                 }
409         }
410
411         var arrivalAddOrders, arrivalDelOrders []*common.Order
412         for _, tx := range nonMatchedTxs {
413                 addOrders, err := getAddOrdersFromTx(tx)
414                 if err != nil {
415                         return nil, err
416                 }
417
418                 delOrders, err := getDeleteOrdersFromTx(tx)
419                 if err != nil {
420                         return nil, err
421                 }
422
423                 arrivalAddOrders = append(arrivalAddOrders, addOrders...)
424                 arrivalDelOrders = append(arrivalDelOrders, delOrders...)
425         }
426
427         return match.NewOrderBook(store, arrivalAddOrders, arrivalDelOrders), nil
428 }
429
430 func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
431         var orders []*common.Order
432         for i, output := range tx.Outputs {
433                 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
434                         continue
435                 }
436
437                 order, err := common.NewOrderFromOutput(tx, i)
438                 if err != nil {
439                         return nil, err
440                 }
441
442                 orders = append(orders, order)
443         }
444         return orders, nil
445 }
446
447 func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
448         var orders []*common.Order
449         for i, input := range tx.Inputs {
450                 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
451                         continue
452                 }
453
454                 order, err := common.NewOrderFromInput(tx, i)
455                 if err != nil {
456                         return nil, err
457                 }
458
459                 orders = append(orders, order)
460         }
461         return orders, nil
462 }
463
464 func getTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
465         var tradePairs []*common.TradePair
466         for _, tx := range tx.Inputs {
467                 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
468                 if err != nil {
469                         return nil, err
470                 }
471
472                 tradePairs = append(tradePairs, &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset})
473         }
474         return tradePairs, nil
475 }
476
477 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
478         var deleteOrders, addOrders []*common.Order
479         for orderID, order := range addOrderMap {
480                 if _, ok := deleteOrderMap[orderID]; ok {
481                         delete(deleteOrderMap, orderID)
482                         continue
483                 }
484                 addOrders = append(addOrders, order)
485         }
486
487         for _, order := range deleteOrderMap {
488                 deleteOrders = append(deleteOrders, order)
489         }
490         return addOrders, deleteOrders
491 }
492
493 // getRewardProgram return the reward program by specified block height
494 // if no reward program configured, then will return empty string
495 // if reward program of 0-100 height is configured, but the specified height is 200, then will return  0-100's reward program
496 // the second return value represent whether to find exactly
497 func getRewardProgram(height uint64) (string, bool) {
498         rewardPrograms := consensus.ActiveNetParams.MovRewardPrograms
499         if len(rewardPrograms) == 0 {
500                 return "", false
501         }
502
503         var program string
504         for _, rewardProgram := range rewardPrograms {
505                 program = rewardProgram.Program
506                 if height >= rewardProgram.BeginBlock && height <= rewardProgram.EndBlock {
507                         return program, true
508                 }
509         }
510         return program, false
511 }
512
513 func validateRewardProgram(height uint64, program string) error {
514         rewardProgram, exact := getRewardProgram(height)
515         if exact && rewardProgram != program {
516                 return errRewardProgramIsWrong
517         }
518         return nil
519 }