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"
19 errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
20 errStatusFailMustFalse = errors.New("status fail of transaction does not allow to be true")
21 errInputProgramMustP2WMCScript = errors.New("input program of trade tx must p2wmc script")
22 errExistCancelOrderInMatchedTx = errors.New("can't exist cancel order in the matched transaction")
23 errExistTradeInCancelOrderTx = errors.New("can't exist trade in the cancel order transaction")
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 errNotConfiguredRewardProgram = errors.New("reward program is not configured properly")
30 errRewardProgramIsWrong = errors.New("the reward program is not correct")
33 // Core represent the core logic of the match module, which include generate match transactions before packing the block,
34 // verify the match transaction in block is correct, and update the order table according to the transaction.
36 movStore database.MovStore
37 startBlockHeight uint64
40 // NewCore return a instance of Core by path of mov db
41 func NewCore(dbBackend, dbDir string, startBlockHeight uint64) *Core {
42 movDB := dbm.NewDB("mov", dbBackend, dbDir)
43 return &Core{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
46 // NewCoreWithDB return a instance of Core by movStore
47 func NewCoreWithDB(store *database.LevelDBMovStore, startBlockHeight uint64) *Core {
48 return &Core{movStore: store, startBlockHeight: startBlockHeight}
51 // ApplyBlock parse pending order and cancel from the the transactions of block
52 // and add pending order to the dex db, remove cancel order from dex db.
53 func (m *Core) ApplyBlock(block *types.Block) error {
54 if block.Height < m.startBlockHeight {
58 if block.Height == m.startBlockHeight {
59 blockHash := block.Hash()
60 return m.movStore.InitDBState(block.Height, &blockHash)
63 if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
67 addOrders, deleteOrders, err := decodeTxsOrders(block.Transactions)
72 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
75 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
76 func (m *Core) BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
77 if blockHeight <= m.startBlockHeight {
81 orderBook, err := buildOrderBook(m.movStore, txs)
86 program, _ := getRewardProgram(blockHeight)
87 rewardProgram, err := hex.DecodeString(program)
89 return nil, errNotConfiguredRewardProgram
92 matchEngine := match.NewEngine(orderBook, match.NewDefaultFeeStrategy(), rewardProgram)
93 tradePairIterator := database.NewTradePairIterator(m.movStore)
94 matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout)
95 return matchCollector.result()
98 // ChainStatus return the current block height and block hash in dex core
99 func (m *Core) ChainStatus() (uint64, *bc.Hash, error) {
100 state, err := m.movStore.GetMovDatabaseState()
105 return state.Height, state.Hash, nil
108 // DetachBlock parse pending order and cancel from the the transactions of block
109 // and add cancel order to the dex db, remove pending order from dex db.
110 func (m *Core) DetachBlock(block *types.Block) error {
111 if block.Height < m.startBlockHeight {
115 deleteOrders, addOrders, err := decodeTxsOrders(block.Transactions)
120 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
123 // IsDust block the transaction that are not generated by the match engine
124 func (m *Core) IsDust(tx *types.Tx) bool {
125 for _, input := range tx.Inputs {
126 if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
133 // Name return the name of current module
134 func (m *Core) Name() string {
138 // StartHeight return the start block height of current module
139 func (m *Core) StartHeight() uint64 {
140 return m.startBlockHeight
143 // ValidateBlock no need to verify the block header, because the first module has been verified.
144 // just need to verify the transactions in the block.
145 func (m *Core) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
146 for i, tx := range block.Transactions {
147 if err := m.ValidateTx(tx, verifyResults[i], block.Height); err != nil {
154 // ValidateTx validate one transaction.
155 func (m *Core) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
156 if blockHeight <= m.startBlockHeight {
160 if verifyResult.StatusFail {
161 return errStatusFailMustFalse
164 if common.IsMatchedTx(tx) {
165 if err := validateMatchedTx(tx, blockHeight); err != nil {
168 } else if common.IsCancelOrderTx(tx) {
169 if err := validateCancelOrderTx(tx); err != nil {
174 for _, output := range tx.Outputs {
175 if !segwit.IsP2WMCScript(output.ControlProgram()) {
179 if err := validateMagneticContractArgs(output.AssetAmount(), output.ControlProgram()); err != nil {
186 // matchedTxFee is object to record the mov tx's fee information
187 type matchedTxFee struct {
192 // calcFeeAmount return the amount of fee in the matching transaction
193 func calcFeeAmount(matchedTx *types.Tx) (map[bc.AssetID]*matchedTxFee, error) {
194 assetFeeMap := make(map[bc.AssetID]*matchedTxFee)
195 dealProgMaps := make(map[string]bool)
197 for _, input := range matchedTx.Inputs {
198 assetFeeMap[input.AssetID()] = &matchedTxFee{amount: input.AssetAmount().Amount}
199 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
204 dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
207 for _, output := range matchedTx.Outputs {
208 assetAmount := output.AssetAmount()
209 if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
210 assetFeeMap[*assetAmount.AssetId].amount -= assetAmount.Amount
211 if assetFeeMap[*assetAmount.AssetId].amount <= 0 {
212 delete(assetFeeMap, *assetAmount.AssetId)
214 } else if assetFeeMap[*assetAmount.AssetId].rewardProgram == nil {
215 assetFeeMap[*assetAmount.AssetId].rewardProgram = output.ControlProgram()
217 return nil, errors.Wrap(errRewardProgramIsWrong, "double reward program")
220 return assetFeeMap, nil
223 func validateCancelOrderTx(tx *types.Tx) error {
224 for _, input := range tx.Inputs {
225 if !segwit.IsP2WMCScript(input.ControlProgram()) {
226 return errInputProgramMustP2WMCScript
229 if contract.IsTradeClauseSelector(input) {
230 return errExistTradeInCancelOrderTx
236 func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte) error {
237 contractArgs, err := segwit.DecodeP2WMCProgram(program)
242 if *fromAssetAmount.AssetId == contractArgs.RequestedAsset {
243 return errInvalidTradePairs
246 if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
247 return errRatioOfTradeLessThanZero
250 if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) < 1 {
251 return errRequestAmountMath
256 func validateMatchedTx(tx *types.Tx, blockHeight uint64) error {
257 fromAssetIDMap := make(map[string]bool)
258 toAssetIDMap := make(map[string]bool)
259 for i, input := range tx.Inputs {
260 if !segwit.IsP2WMCScript(input.ControlProgram()) {
261 return errInputProgramMustP2WMCScript
264 if contract.IsCancelClauseSelector(input) {
265 return errExistCancelOrderInMatchedTx
268 order, err := common.NewOrderFromInput(tx, i)
273 fromAssetIDMap[order.FromAssetID.String()] = true
274 toAssetIDMap[order.ToAssetID.String()] = true
277 if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
278 return errAssetIDMustUniqueInMatchedTx
281 return validateMatchedTxFee(tx, blockHeight)
284 func validateMatchedTxFee(tx *types.Tx, blockHeight uint64) error {
285 matchedTxFees, err := calcFeeAmount(tx)
290 for _, fee := range matchedTxFees {
291 if err := validateRewardProgram(blockHeight, hex.EncodeToString(fee.rewardProgram)); err != nil {
296 orders, err := getDeleteOrdersFromTx(tx)
301 receivedAmount, _ := match.CalcReceivedAmount(orders)
302 feeAmounts := make(map[bc.AssetID]uint64)
303 for assetID, fee := range matchedTxFees {
304 feeAmounts[assetID] = fee.amount
307 feeStrategy := match.NewDefaultFeeStrategy()
308 return feeStrategy.Validate(receivedAmount, feeAmounts)
311 func (m *Core) validateMatchedTxSequence(txs []*types.Tx) error {
312 orderBook := match.NewOrderBook(m.movStore, nil, nil)
313 for _, tx := range txs {
314 if common.IsMatchedTx(tx) {
315 tradePairs, err := getTradePairsFromMatchedTx(tx)
320 orders := orderBook.PeekOrders(tradePairs)
321 if err := validateSpendOrders(tx, orders); err != nil {
325 orderBook.PopOrders(tradePairs)
326 } else if common.IsCancelOrderTx(tx) {
327 orders, err := getDeleteOrdersFromTx(tx)
332 for _, order := range orders {
333 orderBook.DelOrder(order)
337 addOrders, err := getAddOrdersFromTx(tx)
342 for _, order := range addOrders {
343 orderBook.AddOrder(order)
349 func validateSpendOrders(tx *types.Tx, orders []*common.Order) error {
350 if len(tx.Inputs) != len(orders) {
351 return errNotMatchedOrder
354 spendOutputIDs := make(map[string]bool)
355 for _, input := range tx.Inputs {
356 spendOutputID, err := input.SpentOutputID()
361 spendOutputIDs[spendOutputID.String()] = true
364 for _, order := range orders {
365 outputID := order.UTXOHash().String()
366 if _, ok := spendOutputIDs[outputID]; !ok {
367 return errSpendOutputIDIsIncorrect
373 func decodeTxsOrders(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
374 deleteOrderMap := make(map[string]*common.Order)
375 addOrderMap := make(map[string]*common.Order)
376 for _, tx := range txs {
377 addOrders, err := getAddOrdersFromTx(tx)
382 for _, order := range addOrders {
383 addOrderMap[order.Key()] = order
386 deleteOrders, err := getDeleteOrdersFromTx(tx)
391 for _, order := range deleteOrders {
392 deleteOrderMap[order.Key()] = order
396 addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
397 return addOrders, deleteOrders, nil
400 func buildOrderBook(store database.MovStore, txs []*types.Tx) (*match.OrderBook, error) {
401 var arrivalAddOrders, arrivalDelOrders []*common.Order
402 for _, tx := range txs {
403 addOrders, err := getAddOrdersFromTx(tx)
408 delOrders, err := getDeleteOrdersFromTx(tx)
413 arrivalAddOrders = append(arrivalAddOrders, addOrders...)
414 arrivalDelOrders = append(arrivalDelOrders, delOrders...)
417 return match.NewOrderBook(store, arrivalAddOrders, arrivalDelOrders), nil
420 func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
421 var orders []*common.Order
422 for i, output := range tx.Outputs {
423 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
427 if output.AssetAmount().Amount == 0 {
431 order, err := common.NewOrderFromOutput(tx, i)
436 orders = append(orders, order)
441 func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
442 var orders []*common.Order
443 for i, input := range tx.Inputs {
444 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
448 order, err := common.NewOrderFromInput(tx, i)
453 orders = append(orders, order)
458 func getTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
459 var tradePairs []*common.TradePair
460 for _, tx := range tx.Inputs {
461 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
466 tradePairs = append(tradePairs, &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset})
468 return tradePairs, nil
471 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
472 var deleteOrders, addOrders []*common.Order
473 for orderID, order := range addOrderMap {
474 if _, ok := deleteOrderMap[orderID]; ok {
475 delete(deleteOrderMap, orderID)
478 addOrders = append(addOrders, order)
481 for _, order := range deleteOrderMap {
482 deleteOrders = append(deleteOrders, order)
484 return addOrders, deleteOrders
487 // getRewardProgram return the reward program by specified block height
488 // if no reward program configured, then will return empty string
489 // if reward program of 0-100 height is configured, but the specified height is 200, then will return 0-100's reward program
490 // the second return value represent whether to find exactly
491 func getRewardProgram(height uint64) (string, bool) {
492 rewardPrograms := consensus.ActiveNetParams.MovRewardPrograms
493 if len(rewardPrograms) == 0 {
498 for _, rewardProgram := range rewardPrograms {
499 program = rewardProgram.Program
500 if height >= rewardProgram.BeginBlock && height <= rewardProgram.EndBlock {
504 return program, false
507 func validateRewardProgram(height uint64, program string) error {
508 rewardProgram, exact := getRewardProgram(height)
509 if exact && rewardProgram != program {
510 return errRewardProgramIsWrong