OSDN Git Service

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