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"
15 "github.com/bytom/vapor/protocol/bc"
16 "github.com/bytom/vapor/protocol/bc/types"
20 errChainStatusHasAlreadyInit = errors.New("mov chain status has already initialized")
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")
33 errInvalidFeeRate = errors.New("fee rate from input witness is invalid")
36 // Core represent the core logic of the match module, which include generate match transactions before packing the block,
37 // verify the match transaction in block is correct, and update the order table according to the transaction.
39 movStore database.MovStore
40 startBlockHeight uint64
43 // NewCore return a instance of Core by path of mov db
44 func NewCore(dbBackend, dbDir string, startBlockHeight uint64) *Core {
45 movDB := dbm.NewDB("mov", dbBackend, dbDir)
46 return &Core{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
49 // NewCoreWithDB return a instance of Core by movStore
50 func NewCoreWithDB(store *database.LevelDBMovStore, startBlockHeight uint64) *Core {
51 return &Core{movStore: store, startBlockHeight: startBlockHeight}
54 // ApplyBlock parse pending order and cancel from the the transactions of block
55 // and add pending order to the dex db, remove cancel order from dex db.
56 func (m *Core) ApplyBlock(block *types.Block) error {
57 if block.Height < m.startBlockHeight {
61 if block.Height == m.startBlockHeight {
62 blockHash := block.Hash()
63 return m.InitChainStatus(&blockHash)
66 if err := m.validateMatchedTxSequence(movTxs(block)); err != nil {
70 addOrders, deleteOrders, err := decodeTxsOrders(movTxs(block))
75 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
78 // Tx contains raw transaction and the sequence of tx in block
85 // NewTx create a new Tx instance
86 func NewTx(tx *types.Tx, blockHeight uint64, sequence int) *Tx {
87 return &Tx{rawTx: tx, blockHeight: blockHeight, txIndex: sequence}
90 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
91 func (m *Core) BeforeProposalBlock(block *types.Block, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
92 if block.Height <= m.startBlockHeight {
96 orderBook, err := buildOrderBook(m.movStore, movTxs(block))
101 program, _ := getRewardProgram(block.Height)
102 rewardProgram, err := hex.DecodeString(program)
104 return nil, errNotConfiguredRewardProgram
107 matchEngine := match.NewEngine(orderBook, rewardProgram)
108 tradePairIterator := database.NewTradePairIterator(m.movStore)
109 matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout)
110 return matchCollector.result()
113 // ChainStatus return the current block height and block hash in dex core
114 func (m *Core) ChainStatus() (uint64, *bc.Hash, error) {
115 state, err := m.movStore.GetMovDatabaseState()
116 if err == database.ErrNotInitDBState {
117 return 0, nil, protocol.ErrNotInitSubProtocolChainStatus
124 return state.Height, state.Hash, nil
127 // DetachBlock parse pending order and cancel from the the transactions of block
128 // and add cancel order to the dex db, remove pending order from dex db.
129 func (m *Core) DetachBlock(block *types.Block) error {
130 if block.Height < m.startBlockHeight {
134 if block.Height == m.startBlockHeight {
139 deleteOrders, addOrders, err := decodeTxsOrders(movTxs(block))
144 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
147 // InitChainStatus used to init the start block height and start block hash to store
148 func (m *Core) InitChainStatus(startHash *bc.Hash) error {
149 if _, err := m.movStore.GetMovDatabaseState(); err == nil {
150 return errChainStatusHasAlreadyInit
153 return m.movStore.InitDBState(m.startBlockHeight, startHash)
156 // IsDust block the transaction that are not generated by the match engine
157 func (m *Core) IsDust(tx *types.Tx) bool {
158 for _, input := range tx.Inputs {
159 if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
166 // Name return the name of current module
167 func (m *Core) Name() string {
171 // StartHeight return the start block height of current module
172 func (m *Core) StartHeight() uint64 {
173 return m.startBlockHeight
176 // ValidateBlock no need to verify the block header, because the first module has been verified.
177 // just need to verify the transactions in the block.
178 func (m *Core) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
179 for i, tx := range block.Transactions {
180 if err := m.ValidateTx(tx, verifyResults[i], block.Height); err != nil {
187 // ValidateTx validate one transaction.
188 func (m *Core) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
189 if blockHeight <= m.startBlockHeight {
193 if verifyResult.StatusFail {
194 return errStatusFailMustFalse
197 if common.IsMatchedTx(tx) {
198 if err := validateMatchedTx(tx, blockHeight); err != nil {
201 } else if common.IsCancelOrderTx(tx) {
202 if err := validateCancelOrderTx(tx); err != nil {
207 for _, output := range tx.Outputs {
208 if !segwit.IsP2WMCScript(output.ControlProgram()) {
212 if err := validateMagneticContractArgs(output.AssetAmount(), output.ControlProgram()); err != nil {
219 // matchedTxFee is object to record the mov tx's fee information
220 type matchedTxFee struct {
225 // calcFeeAmount return the amount of fee in the matching transaction
226 func calcFeeAmount(matchedTx *types.Tx) (map[bc.AssetID]*matchedTxFee, error) {
227 assetFeeMap := make(map[bc.AssetID]*matchedTxFee)
228 dealProgMaps := make(map[string]bool)
230 for _, input := range matchedTx.Inputs {
231 assetFeeMap[input.AssetID()] = &matchedTxFee{amount: input.AssetAmount().Amount}
232 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
237 dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
240 for _, output := range matchedTx.Outputs {
241 assetAmount := output.AssetAmount()
242 if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
243 assetFeeMap[*assetAmount.AssetId].amount -= assetAmount.Amount
244 if assetFeeMap[*assetAmount.AssetId].amount <= 0 {
245 delete(assetFeeMap, *assetAmount.AssetId)
247 } else if assetFeeMap[*assetAmount.AssetId].rewardProgram == nil {
248 assetFeeMap[*assetAmount.AssetId].rewardProgram = output.ControlProgram()
250 return nil, errors.Wrap(errRewardProgramIsWrong, "double reward program")
253 return assetFeeMap, nil
256 func validateCancelOrderTx(tx *types.Tx) error {
257 for _, input := range tx.Inputs {
258 if !segwit.IsP2WMCScript(input.ControlProgram()) {
259 return errInputProgramMustP2WMCScript
262 if contract.IsTradeClauseSelector(input) {
263 return errExistTradeInCancelOrderTx
269 func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte) error {
270 contractArgs, err := segwit.DecodeP2WMCProgram(program)
275 if *fromAssetAmount.AssetId == contractArgs.RequestedAsset {
276 return errInvalidTradePairs
279 if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
280 return errRatioOfTradeLessThanZero
283 if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) < 1 {
284 return errRequestAmountMath
289 func validateMatchedTx(tx *types.Tx, blockHeight uint64) error {
290 fromAssetIDMap := make(map[string]bool)
291 toAssetIDMap := make(map[string]bool)
292 for i, input := range tx.Inputs {
293 if !segwit.IsP2WMCScript(input.ControlProgram()) {
294 return errInputProgramMustP2WMCScript
297 if contract.IsCancelClauseSelector(input) {
298 return errExistCancelOrderInMatchedTx
301 order, err := common.NewOrderFromInput(tx, i)
306 fromAssetIDMap[order.FromAssetID.String()] = true
307 toAssetIDMap[order.ToAssetID.String()] = true
310 inputSize := len(tx.Inputs)
311 if len(fromAssetIDMap) != inputSize || len(toAssetIDMap) != inputSize {
312 return errAssetIDMustUniqueInMatchedTx
315 return validateMatchedTxFee(tx, blockHeight)
318 func validateMatchedTxFee(tx *types.Tx, blockHeight uint64) error {
319 matchedTxFees, err := calcFeeAmount(tx)
324 for _, fee := range matchedTxFees {
325 if err := validateRewardProgram(blockHeight, hex.EncodeToString(fee.rewardProgram)); err != nil {
330 orders, err := parseDeleteOrdersFromTx(tx)
335 receivedAmount, priceDiffs := match.CalcReceivedAmount(orders)
336 feeAmounts := make(map[bc.AssetID]uint64)
337 for assetID, fee := range matchedTxFees {
338 feeAmounts[assetID] = fee.amount
341 makerFlags, err := makerFlagsByWitness(tx.Inputs, orders)
346 feeStrategy := match.NewDefaultFeeStrategy()
347 return feeStrategy.Validate(receivedAmount, priceDiffs, feeAmounts, makerFlags)
350 func makerFlagsByWitness(inputs []*types.TxInput, orders []*common.Order) ([]match.MakerFlag, error) {
351 makerFlags := make([]match.MakerFlag, len(orders))
352 for i, order := range orders {
353 makerFlags[i].ContractVersion = order.ContractArgs.Version
354 if order.ContractArgs.Version == segwit.MagneticV1 {
355 // no need to know if the order is maker
359 feeRate, err := contract.FeeRate(inputs[i])
364 if feeRate != match.MakerFeeRate && feeRate != match.TakerFeeRate {
365 return nil, errInvalidFeeRate
368 makerFlags[i].IsMaker = feeRate == match.MakerFeeRate
370 return makerFlags, nil
373 func (m *Core) validateMatchedTxSequence(txs []*Tx) error {
374 orderBook := match.NewOrderBook(m.movStore, nil, nil)
375 for _, tx := range txs {
376 if common.IsMatchedTx(tx.rawTx) {
377 tradePairs, err := parseTradePairsFromMatchedTx(tx.rawTx)
382 orders := orderBook.PeekOrders(tradePairs)
383 if err := validateSpendOrders(tx.rawTx, orders); err != nil {
387 orderBook.PopOrders(tradePairs)
388 } else if common.IsCancelOrderTx(tx.rawTx) {
389 orders, err := parseDeleteOrdersFromTx(tx.rawTx)
394 for _, order := range orders {
395 orderBook.DelOrder(order)
399 addOrders, err := parseAddOrdersFromTx(tx)
404 for _, order := range addOrders {
405 orderBook.AddOrder(order)
411 func validateSpendOrders(tx *types.Tx, orders []*common.Order) error {
412 if len(tx.Inputs) != len(orders) {
413 return errNotMatchedOrder
416 spendOutputIDs := make(map[string]bool)
417 for _, input := range tx.Inputs {
418 spendOutputID, err := input.SpentOutputID()
423 spendOutputIDs[spendOutputID.String()] = true
426 for _, order := range orders {
427 outputID := order.UTXOHash().String()
428 if _, ok := spendOutputIDs[outputID]; !ok {
429 return errSpendOutputIDIsIncorrect
435 func decodeTxsOrders(txs []*Tx) ([]*common.Order, []*common.Order, error) {
436 deleteOrderMap := make(map[string]*common.Order)
437 addOrderMap := make(map[string]*common.Order)
438 for _, tx := range txs {
439 addOrders, err := parseAddOrdersFromTx(tx)
444 for _, order := range addOrders {
445 addOrderMap[order.Key()] = order
448 deleteOrders, err := parseDeleteOrdersFromTx(tx.rawTx)
453 for _, order := range deleteOrders {
454 deleteOrderMap[order.Key()] = order
458 addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
459 return addOrders, deleteOrders, nil
462 func buildOrderBook(store database.MovStore, txs []*Tx) (*match.OrderBook, error) {
463 var arrivalAddOrders, arrivalDelOrders []*common.Order
464 for _, tx := range txs {
465 addOrders, err := parseAddOrdersFromTx(tx)
470 delOrders, err := parseDeleteOrdersFromTx(tx.rawTx)
475 arrivalAddOrders = append(arrivalAddOrders, addOrders...)
476 arrivalDelOrders = append(arrivalDelOrders, delOrders...)
479 return match.NewOrderBook(store, arrivalAddOrders, arrivalDelOrders), nil
482 func parseAddOrdersFromTx(tx *Tx) ([]*common.Order, error) {
483 var orders []*common.Order
484 for i, output := range tx.rawTx.Outputs {
485 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
489 if output.AssetAmount().Amount == 0 {
493 order, err := common.NewOrderFromOutput(tx.rawTx, i, tx.blockHeight, tx.txIndex)
498 orders = append(orders, order)
503 func parseDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
504 var orders []*common.Order
505 for i, input := range tx.Inputs {
506 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
510 order, err := common.NewOrderFromInput(tx, i)
515 orders = append(orders, order)
520 func parseTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
521 var tradePairs []*common.TradePair
522 for _, tx := range tx.Inputs {
523 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
528 tradePairs = append(tradePairs, &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset})
530 return tradePairs, nil
533 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
534 var deleteOrders, addOrders []*common.Order
535 for orderID, order := range addOrderMap {
536 if _, ok := deleteOrderMap[orderID]; ok {
537 delete(deleteOrderMap, orderID)
540 addOrders = append(addOrders, order)
543 for _, order := range deleteOrderMap {
544 deleteOrders = append(deleteOrders, order)
546 return addOrders, deleteOrders
549 func movTxs(block *types.Block) []*Tx {
551 for i, tx := range block.Transactions {
552 movTxs = append(movTxs, NewTx(tx, block.Height, i))
557 // getRewardProgram return the reward program by specified block height
558 // if no reward program configured, then will return empty string
559 // if reward program of 0-100 height is configured, but the specified height is 200, then will return 0-100's reward program
560 // the second return value represent whether to find exactly
561 func getRewardProgram(height uint64) (string, bool) {
562 rewardPrograms := consensus.ActiveNetParams.MovRewardPrograms
563 if len(rewardPrograms) == 0 {
568 for _, rewardProgram := range rewardPrograms {
569 program = rewardProgram.Program
570 if height >= rewardProgram.BeginBlock && height <= rewardProgram.EndBlock {
574 return program, false
577 func validateRewardProgram(height uint64, program string) error {
578 rewardProgram, exact := getRewardProgram(height)
579 if exact && rewardProgram != program {
580 return errRewardProgramIsWrong