OSDN Git Service

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