OSDN Git Service

opt mov validate match tx (#470)
[bytom/vapor.git] / application / mov / mov_core.go
1 package mov
2
3 import (
4         "github.com/bytom/vapor/application/mov/common"
5         "github.com/bytom/vapor/application/mov/contract"
6         "github.com/bytom/vapor/application/mov/database"
7         "github.com/bytom/vapor/application/mov/match"
8         "github.com/bytom/vapor/consensus/segwit"
9         dbm "github.com/bytom/vapor/database/leveldb"
10         "github.com/bytom/vapor/errors"
11         "github.com/bytom/vapor/protocol/bc"
12         "github.com/bytom/vapor/protocol/bc/types"
13 )
14
15 const maxFeeRate = 0.05
16
17 var (
18         errInvalidTradePairs             = errors.New("The trade pairs in the tx input is invalid")
19         errStatusFailMustFalse           = errors.New("status fail of transaction does not allow to be true")
20         errInputProgramMustP2WMCScript   = errors.New("input program of trade tx must p2wmc script")
21         errExistCancelOrderInMatchedTx   = errors.New("can't exist cancel order in the matched transaction")
22         errExistTradeInCancelOrderTx     = errors.New("can't exist trade in the cancel order transaction")
23         errAmountOfFeeGreaterThanMaximum = errors.New("amount of fee greater than max fee amount")
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 )
30
31 // MovCore represent the core logic of the match module, which include generate match transactions before packing the block,
32 // verify the match transaction in block is correct, and update the order table according to the transaction.
33 type MovCore struct {
34         movStore         database.MovStore
35         startBlockHeight uint64
36 }
37
38 // NewMovCore return a instance of MovCore by path of mov db
39 func NewMovCore(dbBackend, dbDir string, startBlockHeight uint64) *MovCore {
40         movDB := dbm.NewDB("mov", dbBackend, dbDir)
41         return &MovCore{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
42 }
43
44 // ApplyBlock parse pending order and cancel from the the transactions of block
45 // and add pending order to the dex db, remove cancel order from dex db.
46 func (m *MovCore) ApplyBlock(block *types.Block) error {
47         if block.Height < m.startBlockHeight {
48                 return nil
49         }
50
51         if block.Height == m.startBlockHeight {
52                 blockHash := block.Hash()
53                 if err := m.movStore.InitDBState(block.Height, &blockHash); err != nil {
54                         return err
55                 }
56
57                 return nil
58         }
59
60         if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
61                 return err
62         }
63
64         addOrders, deleteOrders, err := applyTransactions(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 *MovCore) BeforeProposalBlock(txs []*types.Tx, nodeProgram []byte, 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         matchEngine := match.NewEngine(orderBook, maxFeeRate, nodeProgram)
84         tradePairMap := make(map[string]bool)
85         tradePairIterator := database.NewTradePairIterator(m.movStore)
86
87         var packagedTxs []*types.Tx
88         for gasLeft > 0 && !isTimeout() && tradePairIterator.HasNext() {
89                 tradePair := tradePairIterator.Next()
90                 if tradePairMap[tradePair.Key()] {
91                         continue
92                 }
93                 tradePairMap[tradePair.Key()] = true
94                 tradePairMap[tradePair.Reverse().Key()] = true
95
96                 for gasLeft > 0 && !isTimeout() && matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) {
97                         matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
98                         if err != nil {
99                                 return nil, err
100                         }
101
102                         gasUsed := calcMatchedTxGasUsed(matchedTx)
103                         if gasLeft-gasUsed >= 0 {
104                                 packagedTxs = append(packagedTxs, matchedTx)
105                         }
106                         gasLeft -= gasUsed
107                 }
108         }
109         return packagedTxs, nil
110 }
111
112 // ChainStatus return the current block height and block hash in dex core
113 func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
114         state, err := m.movStore.GetMovDatabaseState()
115         if err != nil {
116                 return 0, nil, err
117         }
118
119         return state.Height, state.Hash, nil
120 }
121
122 // DetachBlock parse pending order and cancel from the the transactions of block
123 // and add cancel order to the dex db, remove pending order from dex db.
124 func (m *MovCore) DetachBlock(block *types.Block) error {
125         if block.Height <= m.startBlockHeight {
126                 return nil
127         }
128
129         deleteOrders, addOrders, err := applyTransactions(block.Transactions)
130         if err != nil {
131                 return err
132         }
133
134         return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
135 }
136
137 // IsDust block the transaction that are not generated by the match engine
138 func (m *MovCore) IsDust(tx *types.Tx) bool {
139         for _, input := range tx.Inputs {
140                 if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
141                         return true
142                 }
143         }
144         return false
145 }
146
147 // Name return the name of current module
148 func (m *MovCore) Name() string {
149         return "MOV"
150 }
151
152 // StartHeight return the start block height of current module
153 func (m *MovCore) StartHeight() uint64 {
154         return m.startBlockHeight
155 }
156
157 // ValidateBlock no need to verify the block header, because the first module has been verified.
158 // just need to verify the transactions in the block.
159 func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
160         return m.ValidateTxs(block.Transactions, verifyResults)
161 }
162
163 // ValidateTxs validate the trade transaction.
164 func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error {
165         for i, tx := range txs {
166                 if err := m.ValidateTx(tx, verifyResults[i]); err != nil {
167                         return err
168                 }
169         }
170         return nil
171 }
172
173 // ValidateTxs validate one transaction.
174 func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
175         if common.IsMatchedTx(tx) {
176                 if err := validateMatchedTx(tx, verifyResult); err != nil {
177                         return err
178                 }
179         }
180
181         if common.IsCancelOrderTx(tx) {
182                 if err := validateCancelOrderTx(tx, verifyResult); err != nil {
183                         return err
184                 }
185         }
186
187         for _, output := range tx.Outputs {
188                 if !segwit.IsP2WMCScript(output.ControlProgram()) {
189                         continue
190                 }
191                 if verifyResult.StatusFail {
192                         return errStatusFailMustFalse
193                 }
194
195                 if err := validateMagneticContractArgs(output.AssetAmount(), output.ControlProgram()); err != nil {
196                         return err
197                 }
198         }
199         return nil
200 }
201
202 func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
203         if verifyResult.StatusFail {
204                 return errStatusFailMustFalse
205         }
206
207         for _, input := range tx.Inputs {
208                 if !segwit.IsP2WMCScript(input.ControlProgram()) {
209                         return errInputProgramMustP2WMCScript
210                 }
211
212                 if contract.IsTradeClauseSelector(input) {
213                         return errExistTradeInCancelOrderTx
214                 }
215         }
216         return nil
217 }
218
219 func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte) error {
220         contractArgs, err := segwit.DecodeP2WMCProgram(program)
221         if err != nil {
222                 return err
223         }
224
225         if *fromAssetAmount.AssetId == contractArgs.RequestedAsset {
226                 return errInvalidTradePairs
227         }
228
229         if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
230                 return errRatioOfTradeLessThanZero
231         }
232
233         if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs) < 1 {
234                 return errRequestAmountMath
235         }
236         return nil
237 }
238
239 func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
240         if verifyResult.StatusFail {
241                 return errStatusFailMustFalse
242         }
243
244         fromAssetIDMap := make(map[string]bool)
245         toAssetIDMap := make(map[string]bool)
246         for i, input := range tx.Inputs {
247                 if !segwit.IsP2WMCScript(input.ControlProgram()) {
248                         return errInputProgramMustP2WMCScript
249                 }
250
251                 if contract.IsCancelClauseSelector(input) {
252                         return errExistCancelOrderInMatchedTx
253                 }
254
255                 order, err := common.NewOrderFromInput(tx, i)
256                 if err != nil {
257                         return err
258                 }
259
260                 fromAssetIDMap[order.FromAssetID.String()] = true
261                 toAssetIDMap[order.ToAssetID.String()] = true
262         }
263
264         if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
265                 return errAssetIDMustUniqueInMatchedTx
266         }
267
268         return validateMatchedTxFeeAmount(tx)
269 }
270
271 func validateMatchedTxFeeAmount(tx *types.Tx) error {
272         txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
273         if err != nil {
274                 return err
275         }
276
277         for _, amount := range txFee {
278                 if amount.FeeAmount > amount.MaxFeeAmount {
279                         return errAmountOfFeeGreaterThanMaximum
280                 }
281         }
282         return nil
283 }
284
285 func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
286         orderBook, err := buildOrderBook(m.movStore, txs)
287         if err != nil {
288                 return err
289         }
290
291         for _, matchedTx := range txs {
292                 if !common.IsMatchedTx(matchedTx) {
293                         continue
294                 }
295
296                 tradePairs, err := getTradePairsFromMatchedTx(matchedTx)
297                 if err != nil {
298                         return err
299                 }
300
301                 orders := orderBook.PeekOrders(tradePairs)
302                 if !match.IsMatched(orders) {
303                         return errNotMatchedOrder
304                 }
305
306                 if err := validateSpendOrders(matchedTx, orders); err != nil {
307                         return err
308                 }
309
310                 orderBook.PopOrders(tradePairs)
311
312                 for i, output := range matchedTx.Outputs {
313                         if !segwit.IsP2WMCScript(output.ControlProgram()) {
314                                 continue
315                         }
316
317                         order, err := common.NewOrderFromOutput(matchedTx, i)
318                         if err != nil {
319                                 return err
320                         }
321
322                         if err := orderBook.AddOrder(order); err != nil {
323                                 return err
324                         }
325                 }
326         }
327         return nil
328 }
329
330
331 func validateSpendOrders(matchedTx *types.Tx, orders []*common.Order) error {
332         spendOutputIDs := make(map[string]bool)
333         for _, input := range matchedTx.Inputs {
334                 spendOutputID, err := input.SpentOutputID()
335                 if err != nil {
336                         return err
337                 }
338
339                 spendOutputIDs[spendOutputID.String()] = true
340         }
341
342         for _, order := range orders {
343                 outputID := order.UTXOHash().String()
344                 if _, ok := spendOutputIDs[outputID]; !ok {
345                         return errSpendOutputIDIsIncorrect
346                 }
347         }
348         return nil
349 }
350
351 func applyTransactions(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
352         deleteOrderMap := make(map[string]*common.Order)
353         addOrderMap := make(map[string]*common.Order)
354         for _, tx := range txs {
355                 addOrders, err := getAddOrdersFromTx(tx)
356                 if err != nil {
357                         return nil, nil, err
358                 }
359
360                 for _, order := range addOrders {
361                         addOrderMap[order.Key()] = order
362                 }
363
364                 deleteOrders, err := getDeleteOrdersFromTx(tx)
365                 if err != nil {
366                         return nil, nil, err
367                 }
368
369                 for _, order := range deleteOrders {
370                         deleteOrderMap[order.Key()] = order
371                 }
372         }
373
374         addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
375         return addOrders, deleteOrders, nil
376 }
377
378 func buildOrderBook(store database.MovStore, txs []*types.Tx) (*match.OrderBook, error) {
379         var nonMatchedTxs []*types.Tx
380         for _, tx := range txs {
381                 if !common.IsMatchedTx(tx) {
382                         nonMatchedTxs = append(nonMatchedTxs, tx)
383                 }
384         }
385
386         var arrivalAddOrders, arrivalDelOrders []*common.Order
387         for _, tx := range nonMatchedTxs {
388                 addOrders, err := getAddOrdersFromTx(tx)
389                 if err != nil {
390                         return nil, err
391                 }
392
393                 delOrders, err := getDeleteOrdersFromTx(tx)
394                 if err != nil {
395                         return nil, err
396                 }
397
398                 arrivalAddOrders = append(arrivalAddOrders, addOrders...)
399                 arrivalDelOrders = append(arrivalDelOrders, delOrders...)
400         }
401
402         return match.NewOrderBook(store, arrivalAddOrders, arrivalDelOrders), nil
403 }
404
405 func calcMatchedTxGasUsed(tx *types.Tx) int64 {
406         return int64(len(tx.Inputs))*150 + int64(tx.SerializedSize)
407 }
408
409 func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
410         var orders []*common.Order
411         for i, output := range tx.Outputs {
412                 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
413                         continue
414                 }
415
416                 order, err := common.NewOrderFromOutput(tx, i)
417                 if err != nil {
418                         return nil, err
419                 }
420
421                 orders = append(orders, order)
422         }
423         return orders, nil
424 }
425
426 func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
427         var orders []*common.Order
428         for i, input := range tx.Inputs {
429                 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
430                         continue
431                 }
432
433                 order, err := common.NewOrderFromInput(tx, i)
434                 if err != nil {
435                         return nil, err
436                 }
437
438                 orders = append(orders, order)
439         }
440         return orders, nil
441 }
442
443 func getTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
444         var tradePairs []*common.TradePair
445         for _, tx := range tx.Inputs {
446                 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
447                 if err != nil {
448                         return nil, err
449                 }
450
451                 tradePairs = append(tradePairs, &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset})
452         }
453         return tradePairs, nil
454 }
455
456 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
457         var deleteOrders, addOrders []*common.Order
458         for orderID, order := range addOrderMap {
459                 if _, ok := deleteOrderMap[orderID]; ok {
460                         delete(deleteOrderMap, orderID)
461                         continue
462                 }
463                 addOrders = append(addOrders, order)
464         }
465
466         for _, order := range deleteOrderMap {
467                 deleteOrders = append(deleteOrders, order)
468         }
469         return addOrders, deleteOrders
470 }