OSDN Git Service

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