OSDN Git Service

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