OSDN Git Service

mov core
[bytom/vapor.git] / application / mov / mov_core.go
1 package mov
2
3 import (
4         "github.com/vapor/application/mov/common"
5         "github.com/vapor/application/mov/contract"
6         "github.com/vapor/application/mov/database"
7         "github.com/vapor/application/mov/match"
8         "github.com/vapor/consensus/segwit"
9         "github.com/vapor/errors"
10         "github.com/vapor/math/checked"
11         "github.com/vapor/protocol/bc"
12         "github.com/vapor/protocol/bc/types"
13 )
14
15 var (
16         errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
17 )
18
19 type MovCore struct {
20         movStore database.MovStore
21 }
22
23 func NewMovCore(store database.MovStore) *MovCore {
24         return &MovCore{movStore: store}
25 }
26
27 // ChainStatus return the current block height and block hash in dex core
28 func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
29         state, err := m.movStore.GetMovDatabaseState()
30         if err != nil {
31                 return 0, nil, err
32         }
33
34         return state.Height, state.Hash, nil
35 }
36
37 func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
38         return m.ValidateTxs(block.Transactions, verifyResults)
39 }
40
41 // ValidateTxs validate the trade transaction.
42 func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error {
43         for _, tx := range txs {
44                 if isMatchedTx(tx) {
45                         if err := validateMatchedTx(tx); err != nil {
46                                 return err
47                         }
48                 }
49
50                 if isCancelOrderTx(tx) {
51                         if err := validateCancelOrderTx(tx); err != nil {
52                                 return err
53                         }
54                 }
55
56                 for _, output := range tx.Outputs {
57                         if !segwit.IsP2WMCScript(output.ControlProgram()) {
58                                 continue
59                         }
60
61                         if err := validateMagneticContractArgs(output.AssetAmount().Amount, output.ControlProgram()); err != nil {
62                                 return err
63                         }
64                 }
65         }
66         return nil
67 }
68
69 func validateMatchedTx(tx *types.Tx) error {
70         fromAssetIDMap := make(map[string]bool)
71         toAssetIDMap := make(map[string]bool)
72         for i, input := range tx.Inputs {
73                 if !segwit.IsP2WMCScript(input.ControlProgram()) {
74                         return errors.New("input program of matched tx must p2wmc script")
75                 }
76
77                 if contract.IsCancelClauseSelector(input) {
78                         return errors.New("can't exist cancel order in the matched transaction")
79                 }
80
81                 if err := validateMagneticContractArgs(input.AssetAmount().Amount, input.ControlProgram()); err != nil {
82                         return err
83                 }
84
85                 order, err := common.NewOrderFromInput(tx, i)
86                 if err != nil {
87                         return err
88                 }
89
90                 fromAssetIDMap[order.FromAssetID.String()] = true
91                 toAssetIDMap[order.ToAssetID.String()] = true
92         }
93
94         if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
95                 return errors.New("asset id must unique in matched transaction")
96         }
97
98         return validateMatchedTxFeeAmount(tx)
99 }
100
101 func validateCancelOrderTx(tx *types.Tx) error {
102         for _, input := range tx.Inputs {
103                 if !segwit.IsP2WMCScript(input.ControlProgram()) {
104                         return errors.New("input program of cancel order tx must p2wmc script")
105                 }
106
107                 if contract.IsTradeClauseSelector(input) {
108                         return errors.New("can't exist trade order in the cancel order transaction")
109                 }
110
111                 if err := validateMagneticContractArgs(input.AssetAmount().Amount, input.ControlProgram()); err != nil {
112                         return err
113                 }
114         }
115         return nil
116 }
117
118 func validateMatchedTxFeeAmount(tx *types.Tx) error {
119         txFee, err := match.CalcMatchedTxFee(&tx.TxData)
120         if err != nil {
121                 return err
122         }
123
124         for _, amount := range txFee {
125                 if amount.FeeAmount > amount.MaxFeeAmount {
126                         return errors.New("amount of fee greater than max fee amount")
127                 }
128         }
129         return nil
130 }
131
132 func validateMagneticContractArgs(inputAmount uint64, program []byte) error {
133         contractArgs, err := segwit.DecodeP2WMCProgram(program)
134         if err != nil {
135                 return err
136         }
137
138         if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
139                 return errors.New("ratio arguments must greater than zero")
140         }
141
142         if _, ok := checked.MulInt64(int64(inputAmount), contractArgs.RatioNumerator); !ok {
143                 return errors.New("ratio numerator of contract args product input amount is overflow")
144         }
145         return nil
146 }
147
148 // ApplyBlock parse pending order and cancel from the the transactions of block
149 // and add pending order to the dex db, remove cancel order from dex db.
150 func (m *MovCore) ApplyBlock(block *types.Block) error {
151         if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
152                 return err
153         }
154
155         addOrders, deleteOrders, err := applyTransactions(block.Transactions)
156         if err != nil {
157                 return err
158         }
159
160         return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
161 }
162
163 func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx, ) error {
164         matchEngine := match.NewEngine(m.movStore, nil)
165         for _, matchedTx := range txs {
166                 if !isMatchedTx(matchedTx) {
167                         continue
168                 }
169
170                 tradePairs, err := getSortedTradePairsFromMatchedTx(matchedTx)
171                 if err != nil {
172                         return err
173                 }
174
175                 actualMatchedTx, err := matchEngine.NextMatchedTx(tradePairs...)
176                 if err != nil {
177                         return err
178                 }
179
180                 if len(matchedTx.Inputs) != len(actualMatchedTx.Inputs) {
181                         return errors.New("length of matched tx input is not equals to actual matched tx input")
182                 }
183
184                 spendOutputIDs := make(map[string]bool)
185                 for _, input := range matchedTx.Inputs {
186                         spendOutputID, err := input.SpentOutputID()
187                         if err != nil {
188                                 return err
189                         }
190
191                         spendOutputIDs[spendOutputID.String()] = true
192                 }
193
194                 for _, input := range actualMatchedTx.Inputs {
195                         spendOutputID, err := input.SpentOutputID()
196                         if err != nil {
197                                 return err
198                         }
199
200                         if _, ok := spendOutputIDs[spendOutputID.String()]; !ok {
201                                 return errors.New("spend output id of matched tx is not equals to actual matched tx")
202                         }
203                 }
204         }
205         return nil
206 }
207
208 func getSortedTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
209         assetMap := make(map[bc.AssetID]bc.AssetID)
210         for _, tx := range tx.Inputs {
211                 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
212                 if err != nil {
213                         return nil, err
214                 }
215
216                 assetMap[tx.AssetID()] = contractArgs.RequestedAsset
217         }
218
219         firstTradePair := &common.TradePair{}
220         tradePairs := []*common.TradePair{firstTradePair}
221         tradePair := firstTradePair
222         for *tradePair.ToAssetID != *firstTradePair.FromAssetID {
223                 nextTradePairToAssetID, ok := assetMap[*tradePair.ToAssetID]
224                 if !ok {
225                         return nil, errInvalidTradePairs
226                 }
227
228                 tradePair = &common.TradePair{FromAssetID: tradePair.ToAssetID, ToAssetID: &nextTradePairToAssetID}
229                 tradePairs = append(tradePairs, tradePair)
230         }
231
232         if len(tradePairs) != len(tx.Inputs) {
233                 return nil, errInvalidTradePairs
234         }
235         return tradePairs, nil
236 }
237
238 // DetachBlock parse pending order and cancel from the the transactions of block
239 // and add cancel order to the dex db, remove pending order from dex db.
240 func (m *MovCore) DetachBlock(block *types.Block) error {
241         deleteOrders, addOrders, err := applyTransactions(block.Transactions)
242         if err != nil {
243                 return err
244         }
245
246         return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
247 }
248
249 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
250 func (m *MovCore) BeforeProposalBlock(capacity int, nodeProgram []byte) ([]*types.Tx, error) {
251         matchEngine := match.NewEngine(m.movStore, nodeProgram)
252         tradePairMap := make(map[string]bool)
253         tradePairIterator := database.NewTradePairIterator(m.movStore)
254         remainder := capacity
255
256         var packagedTxs []*types.Tx
257         for remainder > 0 && tradePairIterator.HasNext() {
258                 tradePair := tradePairIterator.Next()
259                 if tradePairMap[tradePair.Key()] {
260                         continue
261                 }
262                 tradePairMap[tradePair.Key()] = true
263                 tradePairMap[tradePair.Reverse().Key()] = true
264
265                 for matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) && remainder > 0 {
266                         matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
267                         if err != nil {
268                                 return nil, err
269                         }
270
271                         if matchedTx == nil {
272                                 break
273                         }
274                         packagedTxs = append(packagedTxs, matchedTx)
275                         remainder--
276                 }
277         }
278         return packagedTxs, nil
279 }
280
281 func (m *MovCore) IsDust(tx *types.Tx) bool {
282         return false
283 }
284
285 func applyTransactions(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
286         deleteOrderMap := make(map[string]*common.Order)
287         addOrderMap := make(map[string]*common.Order)
288         for _, tx := range txs {
289                 addOrders, err := getAddOrdersFromTx(tx)
290                 if err != nil {
291                         return nil, nil, err
292                 }
293
294                 for _, order := range addOrders {
295                         addOrderMap[order.Key()] = order
296                 }
297
298                 deleteOrders, err := getDeleteOrdersFromTx(tx)
299                 if err != nil {
300                         return nil, nil, err
301                 }
302
303                 for _, order := range deleteOrders {
304                         deleteOrderMap[order.Key()] = order
305                 }
306         }
307
308         addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
309         return addOrders, deleteOrders, nil
310 }
311
312 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
313         var deleteOrders, addOrders []*common.Order
314         for orderID, order := range addOrderMap {
315                 if _, ok := deleteOrderMap[orderID]; ok {
316                         delete(deleteOrderMap, orderID)
317                         continue
318                 }
319                 addOrders = append(addOrders, order)
320         }
321
322         for _, order := range deleteOrderMap {
323                 deleteOrders = append(deleteOrders, order)
324         }
325         return addOrders, deleteOrders
326 }
327
328 func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
329         var orders []*common.Order
330         for i, output := range tx.Outputs {
331                 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
332                         continue
333                 }
334
335                 order, err := common.NewOrderFromOutput(tx, i)
336                 if err != nil {
337                         return nil, err
338                 }
339
340                 orders = append(orders, order)
341         }
342         return orders, nil
343 }
344
345 func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
346         var orders []*common.Order
347         for i, input := range tx.Inputs {
348                 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
349                         continue
350                 }
351
352                 order, err := common.NewOrderFromInput(tx, i)
353                 if err != nil {
354                         return nil, err
355                 }
356
357                 orders = append(orders, order)
358         }
359         return orders, nil
360 }
361
362 func isMatchedTx(tx *types.Tx) bool {
363         p2wmCount := 0
364         for _, input := range tx.Inputs {
365                 if input.InputType() == types.SpendInputType && contract.IsTradeClauseSelector(input) && segwit.IsP2WMCScript(input.ControlProgram()) {
366                         p2wmCount++
367                 }
368         }
369         return p2wmCount >= 2
370 }
371
372 func isCancelOrderTx(tx *types.Tx) bool {
373         for _, input := range tx.Inputs {
374                 if input.InputType() == types.SpendInputType && contract.IsCancelClauseSelector(input) && segwit.IsP2WMCScript(input.ControlProgram()) {
375                         return true
376                 }
377         }
378         return false
379 }