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/bc"
15 "github.com/bytom/vapor/protocol/bc/types"
18 const maxFeeRate = 0.05
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")
35 // MovCore 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.
38 movStore database.MovStore
39 startBlockHeight uint64
42 // NewMovCore return a instance of MovCore by path of mov db
43 func NewMovCore(dbBackend, dbDir string, startBlockHeight uint64) *MovCore {
44 movDB := dbm.NewDB("mov", dbBackend, dbDir)
45 return &MovCore{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
48 // ApplyBlock parse pending order and cancel from the the transactions of block
49 // and add pending order to the dex db, remove cancel order from dex db.
50 func (m *MovCore) ApplyBlock(block *types.Block) error {
51 if block.Height < m.startBlockHeight {
55 if block.Height == m.startBlockHeight {
56 blockHash := block.Hash()
57 if err := m.movStore.InitDBState(block.Height, &blockHash); err != nil {
61 // the next block can send orders
65 if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
69 addOrders, deleteOrders, err := decodeTxsOrders(block.Transactions)
74 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
77 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
78 func (m *MovCore) BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
79 if blockHeight <= m.startBlockHeight {
83 orderBook, err := buildOrderBook(m.movStore, txs)
88 program, _ := getRewardProgram(blockHeight)
89 rewardProgram, err := hex.DecodeString(program)
91 return nil, errNotConfiguredRewardProgram
94 matchEngine := match.NewEngine(orderBook, match.NewDefaultFeeStrategy(maxFeeRate), rewardProgram)
95 tradePairIterator := database.NewTradePairIterator(m.movStore)
96 matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout)
97 return matchCollector.result()
100 // ChainStatus return the current block height and block hash in dex core
101 func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
102 state, err := m.movStore.GetMovDatabaseState()
107 return state.Height, state.Hash, nil
110 // DetachBlock parse pending order and cancel from the the transactions of block
111 // and add cancel order to the dex db, remove pending order from dex db.
112 func (m *MovCore) DetachBlock(block *types.Block) error {
113 if block.Height < m.startBlockHeight {
117 deleteOrders, addOrders, err := decodeTxsOrders(block.Transactions)
122 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
125 // IsDust block the transaction that are not generated by the match engine
126 func (m *MovCore) IsDust(tx *types.Tx) bool {
127 for _, input := range tx.Inputs {
128 if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
135 // Name return the name of current module
136 func (m *MovCore) Name() string {
140 // StartHeight return the start block height of current module
141 func (m *MovCore) StartHeight() uint64 {
142 return m.startBlockHeight
145 // ValidateBlock no need to verify the block header, because the first module has been verified.
146 // just need to verify the transactions in the block.
147 func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
148 for i, tx := range block.Transactions {
149 if err := m.ValidateTx(tx, verifyResults[i], block.Height); err != nil {
156 // ValidateTx validate one transaction.
157 func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
158 if common.IsMatchedTx(tx) {
159 if err := validateMatchedTx(tx, verifyResult, blockHeight); err != nil {
162 } else if common.IsCancelOrderTx(tx) {
163 if err := validateCancelOrderTx(tx, verifyResult); err != nil {
168 for _, output := range tx.Outputs {
169 if !segwit.IsP2WMCScript(output.ControlProgram()) {
172 if verifyResult.StatusFail {
173 return errStatusFailMustFalse
176 if err := validateMagneticContractArgs(output.AssetAmount(), output.ControlProgram()); err != nil {
183 // matchedTxFee is object to record the mov tx's fee information
184 type matchedTxFee struct {
189 // calcFeeAmount return the amount of fee in the matching transaction
190 func calcFeeAmount(matchedTx *types.Tx) (map[bc.AssetID]*matchedTxFee, error) {
191 assetFeeMap := make(map[bc.AssetID]*matchedTxFee)
192 dealProgMaps := make(map[string]bool)
194 for _, input := range matchedTx.Inputs {
195 assetFeeMap[input.AssetID()] = &matchedTxFee{amount: int64(input.AssetAmount().Amount)}
196 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
201 dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
204 for _, output := range matchedTx.Outputs {
205 assetAmount := output.AssetAmount()
206 if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
207 assetFeeMap[*assetAmount.AssetId].amount -= int64(assetAmount.Amount)
208 if assetFeeMap[*assetAmount.AssetId].amount <= 0 {
209 delete(assetFeeMap, *assetAmount.AssetId)
212 assetFeeMap[*assetAmount.AssetId].rewardProgram = output.ControlProgram()
215 return assetFeeMap, nil
218 func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
219 if verifyResult.StatusFail {
220 return errStatusFailMustFalse
223 for _, input := range tx.Inputs {
224 if !segwit.IsP2WMCScript(input.ControlProgram()) {
225 return errInputProgramMustP2WMCScript
228 if contract.IsTradeClauseSelector(input) {
229 return errExistTradeInCancelOrderTx
235 func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte) error {
236 contractArgs, err := segwit.DecodeP2WMCProgram(program)
241 if *fromAssetAmount.AssetId == contractArgs.RequestedAsset {
242 return errInvalidTradePairs
245 if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
246 return errRatioOfTradeLessThanZero
249 if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) < 1 {
250 return errRequestAmountMath
255 func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
256 if verifyResult.StatusFail {
257 return errStatusFailMustFalse
260 fromAssetIDMap := make(map[string]bool)
261 toAssetIDMap := make(map[string]bool)
262 for i, input := range tx.Inputs {
263 if !segwit.IsP2WMCScript(input.ControlProgram()) {
264 return errInputProgramMustP2WMCScript
267 if contract.IsCancelClauseSelector(input) {
268 return errExistCancelOrderInMatchedTx
271 order, err := common.NewOrderFromInput(tx, i)
276 fromAssetIDMap[order.FromAssetID.String()] = true
277 toAssetIDMap[order.ToAssetID.String()] = true
280 if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
281 return errAssetIDMustUniqueInMatchedTx
284 return validateMatchedTxFee(tx, blockHeight)
287 func validateMatchedTxFee(tx *types.Tx, blockHeight uint64) error {
288 matchedTxFees, err := calcFeeAmount(tx)
293 for _, fee := range matchedTxFees {
294 if err := validateRewardProgram(blockHeight, hex.EncodeToString(fee.rewardProgram)); err != nil {
299 orders, err := getDeleteOrdersFromTx(tx)
304 feeAmounts := make(map[bc.AssetID]int64)
305 for assetID, fee := range matchedTxFees {
306 feeAmounts[assetID] = fee.amount
309 receivedAmount, priceDiff := match.CalcReceivedAmount(orders)
310 feeStrategy := match.NewDefaultFeeStrategy(maxFeeRate)
311 return feeStrategy.Validate(receivedAmount, priceDiff, feeAmounts)
314 func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
315 orderBook := match.NewOrderBook(m.movStore, nil, nil)
316 for _, tx := range txs {
317 if common.IsMatchedTx(tx) {
318 tradePairs, err := getTradePairsFromMatchedTx(tx)
323 orders := orderBook.PeekOrders(tradePairs)
324 if err := validateSpendOrders(tx, orders); err != nil {
328 orderBook.PopOrders(tradePairs)
329 } else if common.IsCancelOrderTx(tx) {
330 orders, err := getDeleteOrdersFromTx(tx)
335 for _, order := range orders {
336 orderBook.DelOrder(order)
340 addOrders, err := getAddOrdersFromTx(tx)
345 for _, order := range addOrders {
346 orderBook.AddOrder(order)
352 func validateSpendOrders(tx *types.Tx, orders []*common.Order) error {
353 if len(tx.Inputs) != len(orders) {
354 return errNotMatchedOrder
357 spendOutputIDs := make(map[string]bool)
358 for _, input := range tx.Inputs {
359 spendOutputID, err := input.SpentOutputID()
364 spendOutputIDs[spendOutputID.String()] = true
367 for _, order := range orders {
368 outputID := order.UTXOHash().String()
369 if _, ok := spendOutputIDs[outputID]; !ok {
370 return errSpendOutputIDIsIncorrect
376 func decodeTxsOrders(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
377 deleteOrderMap := make(map[string]*common.Order)
378 addOrderMap := make(map[string]*common.Order)
379 for _, tx := range txs {
380 addOrders, err := getAddOrdersFromTx(tx)
385 for _, order := range addOrders {
386 addOrderMap[order.Key()] = order
389 deleteOrders, err := getDeleteOrdersFromTx(tx)
394 for _, order := range deleteOrders {
395 deleteOrderMap[order.Key()] = order
399 addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
400 return addOrders, deleteOrders, nil
403 func buildOrderBook(store database.MovStore, txs []*types.Tx) (*match.OrderBook, error) {
404 var nonMatchedTxs []*types.Tx
405 for _, tx := range txs {
406 if !common.IsMatchedTx(tx) {
407 nonMatchedTxs = append(nonMatchedTxs, tx)
411 var arrivalAddOrders, arrivalDelOrders []*common.Order
412 for _, tx := range nonMatchedTxs {
413 addOrders, err := getAddOrdersFromTx(tx)
418 delOrders, err := getDeleteOrdersFromTx(tx)
423 arrivalAddOrders = append(arrivalAddOrders, addOrders...)
424 arrivalDelOrders = append(arrivalDelOrders, delOrders...)
427 return match.NewOrderBook(store, arrivalAddOrders, arrivalDelOrders), nil
430 func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
431 var orders []*common.Order
432 for i, output := range tx.Outputs {
433 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
437 order, err := common.NewOrderFromOutput(tx, i)
442 orders = append(orders, order)
447 func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
448 var orders []*common.Order
449 for i, input := range tx.Inputs {
450 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
454 order, err := common.NewOrderFromInput(tx, i)
459 orders = append(orders, order)
464 func getTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
465 var tradePairs []*common.TradePair
466 for _, tx := range tx.Inputs {
467 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
472 tradePairs = append(tradePairs, &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset})
474 return tradePairs, nil
477 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
478 var deleteOrders, addOrders []*common.Order
479 for orderID, order := range addOrderMap {
480 if _, ok := deleteOrderMap[orderID]; ok {
481 delete(deleteOrderMap, orderID)
484 addOrders = append(addOrders, order)
487 for _, order := range deleteOrderMap {
488 deleteOrders = append(deleteOrders, order)
490 return addOrders, deleteOrders
493 // getRewardProgram return the reward program by specified block height
494 // if no reward program configured, then will return empty string
495 // if reward program of 0-100 height is configured, but the specified height is 200, then will return 0-100's reward program
496 // the second return value represent whether to find exactly
497 func getRewardProgram(height uint64) (string, bool) {
498 rewardPrograms := consensus.ActiveNetParams.MovRewardPrograms
499 if len(rewardPrograms) == 0 {
504 for _, rewardProgram := range rewardPrograms {
505 program = rewardProgram.Program
506 if height >= rewardProgram.BeginBlock && height <= rewardProgram.EndBlock {
510 return program, false
513 func validateRewardProgram(height uint64, program string) error {
514 rewardProgram, exact := getRewardProgram(height)
515 if exact && rewardProgram != program {
516 return errRewardProgramIsWrong