OSDN Git Service

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