OSDN Git Service

Mov late node sync test (#529)
[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 block.Height == m.startBlockHeight {
61                 blockHash := block.Hash()
62                 return m.InitChainStatus(&blockHash)
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 *Core) 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(), 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 *Core) ChainStatus() (uint64, *bc.Hash, error) {
102         state, err := m.movStore.GetMovDatabaseState()
103         if err == database.ErrNotInitDBState {
104                 return 0, nil, protocol.ErrNotInitSubProtocolChainStatus
105         }
106
107         if err != nil {
108                 return 0, nil, err
109         }
110
111         return state.Height, state.Hash, nil
112 }
113
114 // DetachBlock parse pending order and cancel from the the transactions of block
115 // and add cancel order to the dex db, remove pending order from dex db.
116 func (m *Core) DetachBlock(block *types.Block) error {
117         if block.Height < m.startBlockHeight {
118                 return nil
119         }
120
121         if block.Height == m.startBlockHeight {
122                 m.movStore.Clear()
123                 return nil
124         }
125
126         deleteOrders, addOrders, err := decodeTxsOrders(block.Transactions)
127         if err != nil {
128                 return err
129         }
130
131         return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
132 }
133
134 // InitChainStatus used to init the start block height and start block hash to store
135 func (m *Core) InitChainStatus(startHash *bc.Hash) error {
136         if _, err := m.movStore.GetMovDatabaseState(); err == nil {
137                 return errChainStatusHasAlreadyInit
138         }
139
140         return m.movStore.InitDBState(m.startBlockHeight, startHash)
141 }
142
143 // IsDust block the transaction that are not generated by the match engine
144 func (m *Core) 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 *Core) Name() string {
155         return "MOV"
156 }
157
158 // StartHeight return the start block height of current module
159 func (m *Core) 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 *Core) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
166         for i, tx := range block.Transactions {
167                 if err := m.ValidateTx(tx, verifyResults[i], block.Height); err != nil {
168                         return err
169                 }
170         }
171         return nil
172 }
173
174 // ValidateTx validate one transaction.
175 func (m *Core) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
176         if blockHeight <= m.startBlockHeight {
177                 return nil
178         }
179
180         if verifyResult.StatusFail {
181                 return errStatusFailMustFalse
182         }
183
184         if common.IsMatchedTx(tx) {
185                 if err := validateMatchedTx(tx, blockHeight); err != nil {
186                         return err
187                 }
188         } else if common.IsCancelOrderTx(tx) {
189                 if err := validateCancelOrderTx(tx); err != nil {
190                         return err
191                 }
192         }
193
194         for _, output := range tx.Outputs {
195                 if !segwit.IsP2WMCScript(output.ControlProgram()) {
196                         continue
197                 }
198
199                 if err := validateMagneticContractArgs(output.AssetAmount(), output.ControlProgram()); err != nil {
200                         return err
201                 }
202         }
203         return nil
204 }
205
206 // matchedTxFee is object to record the mov tx's fee information
207 type matchedTxFee struct {
208         rewardProgram []byte
209         amount        uint64
210 }
211
212 // calcFeeAmount return the amount of fee in the matching transaction
213 func calcFeeAmount(matchedTx *types.Tx) (map[bc.AssetID]*matchedTxFee, error) {
214         assetFeeMap := make(map[bc.AssetID]*matchedTxFee)
215         dealProgMaps := make(map[string]bool)
216
217         for _, input := range matchedTx.Inputs {
218                 assetFeeMap[input.AssetID()] = &matchedTxFee{amount: input.AssetAmount().Amount}
219                 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
220                 if err != nil {
221                         return nil, err
222                 }
223
224                 dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
225         }
226
227         for _, output := range matchedTx.Outputs {
228                 assetAmount := output.AssetAmount()
229                 if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
230                         assetFeeMap[*assetAmount.AssetId].amount -= assetAmount.Amount
231                         if assetFeeMap[*assetAmount.AssetId].amount <= 0 {
232                                 delete(assetFeeMap, *assetAmount.AssetId)
233                         }
234                 } else if assetFeeMap[*assetAmount.AssetId].rewardProgram == nil {
235                         assetFeeMap[*assetAmount.AssetId].rewardProgram = output.ControlProgram()
236                 } else {
237                         return nil, errors.Wrap(errRewardProgramIsWrong, "double reward program")
238                 }
239         }
240         return assetFeeMap, nil
241 }
242
243 func validateCancelOrderTx(tx *types.Tx) error {
244         for _, input := range tx.Inputs {
245                 if !segwit.IsP2WMCScript(input.ControlProgram()) {
246                         return errInputProgramMustP2WMCScript
247                 }
248
249                 if contract.IsTradeClauseSelector(input) {
250                         return errExistTradeInCancelOrderTx
251                 }
252         }
253         return nil
254 }
255
256 func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte) error {
257         contractArgs, err := segwit.DecodeP2WMCProgram(program)
258         if err != nil {
259                 return err
260         }
261
262         if *fromAssetAmount.AssetId == contractArgs.RequestedAsset {
263                 return errInvalidTradePairs
264         }
265
266         if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
267                 return errRatioOfTradeLessThanZero
268         }
269
270         if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) < 1 {
271                 return errRequestAmountMath
272         }
273         return nil
274 }
275
276 func validateMatchedTx(tx *types.Tx, blockHeight uint64) error {
277         fromAssetIDMap := make(map[string]bool)
278         toAssetIDMap := make(map[string]bool)
279         for i, input := range tx.Inputs {
280                 if !segwit.IsP2WMCScript(input.ControlProgram()) {
281                         return errInputProgramMustP2WMCScript
282                 }
283
284                 if contract.IsCancelClauseSelector(input) {
285                         return errExistCancelOrderInMatchedTx
286                 }
287
288                 order, err := common.NewOrderFromInput(tx, i)
289                 if err != nil {
290                         return err
291                 }
292
293                 fromAssetIDMap[order.FromAssetID.String()] = true
294                 toAssetIDMap[order.ToAssetID.String()] = true
295         }
296
297         if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
298                 return errAssetIDMustUniqueInMatchedTx
299         }
300
301         return validateMatchedTxFee(tx, blockHeight)
302 }
303
304 func validateMatchedTxFee(tx *types.Tx, blockHeight uint64) error {
305         matchedTxFees, err := calcFeeAmount(tx)
306         if err != nil {
307                 return err
308         }
309
310         for _, fee := range matchedTxFees {
311                 if err := validateRewardProgram(blockHeight, hex.EncodeToString(fee.rewardProgram)); err != nil {
312                         return err
313                 }
314         }
315
316         orders, err := getDeleteOrdersFromTx(tx)
317         if err != nil {
318                 return err
319         }
320
321         receivedAmount, _ := match.CalcReceivedAmount(orders)
322         feeAmounts := make(map[bc.AssetID]uint64)
323         for assetID, fee := range matchedTxFees {
324                 feeAmounts[assetID] = fee.amount
325         }
326
327         feeStrategy := match.NewDefaultFeeStrategy()
328         return feeStrategy.Validate(receivedAmount, feeAmounts)
329 }
330
331 func (m *Core) validateMatchedTxSequence(txs []*types.Tx) error {
332         orderBook := match.NewOrderBook(m.movStore, nil, nil)
333         for _, tx := range txs {
334                 if common.IsMatchedTx(tx) {
335                         tradePairs, err := getTradePairsFromMatchedTx(tx)
336                         if err != nil {
337                                 return err
338                         }
339
340                         orders := orderBook.PeekOrders(tradePairs)
341                         if err := validateSpendOrders(tx, orders); err != nil {
342                                 return err
343                         }
344
345                         orderBook.PopOrders(tradePairs)
346                 } else if common.IsCancelOrderTx(tx) {
347                         orders, err := getDeleteOrdersFromTx(tx)
348                         if err != nil {
349                                 return err
350                         }
351
352                         for _, order := range orders {
353                                 orderBook.DelOrder(order)
354                         }
355                 }
356
357                 addOrders, err := getAddOrdersFromTx(tx)
358                 if err != nil {
359                         return err
360                 }
361
362                 for _, order := range addOrders {
363                         orderBook.AddOrder(order)
364                 }
365         }
366         return nil
367 }
368
369 func validateSpendOrders(tx *types.Tx, orders []*common.Order) error {
370         if len(tx.Inputs) != len(orders) {
371                 return errNotMatchedOrder
372         }
373
374         spendOutputIDs := make(map[string]bool)
375         for _, input := range tx.Inputs {
376                 spendOutputID, err := input.SpentOutputID()
377                 if err != nil {
378                         return err
379                 }
380
381                 spendOutputIDs[spendOutputID.String()] = true
382         }
383
384         for _, order := range orders {
385                 outputID := order.UTXOHash().String()
386                 if _, ok := spendOutputIDs[outputID]; !ok {
387                         return errSpendOutputIDIsIncorrect
388                 }
389         }
390         return nil
391 }
392
393 func decodeTxsOrders(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
394         deleteOrderMap := make(map[string]*common.Order)
395         addOrderMap := make(map[string]*common.Order)
396         for _, tx := range txs {
397                 addOrders, err := getAddOrdersFromTx(tx)
398                 if err != nil {
399                         return nil, nil, err
400                 }
401
402                 for _, order := range addOrders {
403                         addOrderMap[order.Key()] = order
404                 }
405
406                 deleteOrders, err := getDeleteOrdersFromTx(tx)
407                 if err != nil {
408                         return nil, nil, err
409                 }
410
411                 for _, order := range deleteOrders {
412                         deleteOrderMap[order.Key()] = order
413                 }
414         }
415
416         addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
417         return addOrders, deleteOrders, nil
418 }
419
420 func buildOrderBook(store database.MovStore, txs []*types.Tx) (*match.OrderBook, error) {
421         var arrivalAddOrders, arrivalDelOrders []*common.Order
422         for _, tx := range txs {
423                 addOrders, err := getAddOrdersFromTx(tx)
424                 if err != nil {
425                         return nil, err
426                 }
427
428                 delOrders, err := getDeleteOrdersFromTx(tx)
429                 if err != nil {
430                         return nil, err
431                 }
432
433                 arrivalAddOrders = append(arrivalAddOrders, addOrders...)
434                 arrivalDelOrders = append(arrivalDelOrders, delOrders...)
435         }
436
437         return match.NewOrderBook(store, arrivalAddOrders, arrivalDelOrders), nil
438 }
439
440 func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
441         var orders []*common.Order
442         for i, output := range tx.Outputs {
443                 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
444                         continue
445                 }
446
447                 if output.AssetAmount().Amount == 0 {
448                         continue
449                 }
450
451                 order, err := common.NewOrderFromOutput(tx, i)
452                 if err != nil {
453                         return nil, err
454                 }
455
456                 orders = append(orders, order)
457         }
458         return orders, nil
459 }
460
461 func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
462         var orders []*common.Order
463         for i, input := range tx.Inputs {
464                 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
465                         continue
466                 }
467
468                 order, err := common.NewOrderFromInput(tx, i)
469                 if err != nil {
470                         return nil, err
471                 }
472
473                 orders = append(orders, order)
474         }
475         return orders, nil
476 }
477
478 func getTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
479         var tradePairs []*common.TradePair
480         for _, tx := range tx.Inputs {
481                 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
482                 if err != nil {
483                         return nil, err
484                 }
485
486                 tradePairs = append(tradePairs, &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset})
487         }
488         return tradePairs, nil
489 }
490
491 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
492         var deleteOrders, addOrders []*common.Order
493         for orderID, order := range addOrderMap {
494                 if _, ok := deleteOrderMap[orderID]; ok {
495                         delete(deleteOrderMap, orderID)
496                         continue
497                 }
498                 addOrders = append(addOrders, order)
499         }
500
501         for _, order := range deleteOrderMap {
502                 deleteOrders = append(deleteOrders, order)
503         }
504         return addOrders, deleteOrders
505 }
506
507 // getRewardProgram return the reward program by specified block height
508 // if no reward program configured, then will return empty string
509 // if reward program of 0-100 height is configured, but the specified height is 200, then will return  0-100's reward program
510 // the second return value represent whether to find exactly
511 func getRewardProgram(height uint64) (string, bool) {
512         rewardPrograms := consensus.ActiveNetParams.MovRewardPrograms
513         if len(rewardPrograms) == 0 {
514                 return "51", false
515         }
516
517         var program string
518         for _, rewardProgram := range rewardPrograms {
519                 program = rewardProgram.Program
520                 if height >= rewardProgram.BeginBlock && height <= rewardProgram.EndBlock {
521                         return program, true
522                 }
523         }
524         return program, false
525 }
526
527 func validateRewardProgram(height uint64, program string) error {
528         rewardProgram, exact := getRewardProgram(height)
529         if exact && rewardProgram != program {
530                 return errRewardProgramIsWrong
531         }
532         return nil
533 }