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")
35 // Core 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 // NewCore return a instance of Core by path of mov db
43 func NewCore(dbBackend, dbDir string, startBlockHeight uint64) *Core {
44 movDB := dbm.NewDB("mov", dbBackend, dbDir)
45 return &Core{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
48 // NewCoreWithDB return a instance of Core by movStore
49 func NewCoreWithDB(store *database.LevelDBMovStore, startBlockHeight uint64) *Core {
50 return &Core{movStore: store, startBlockHeight: startBlockHeight}
53 // ApplyBlock parse pending order and cancel from the the transactions of block
54 // and add pending order to the dex db, remove cancel order from dex db.
55 func (m *Core) ApplyBlock(block *types.Block) error {
56 if block.Height < m.startBlockHeight {
60 if block.Height == m.startBlockHeight {
61 blockHash := block.Hash()
62 return m.InitChainStatus(&blockHash)
65 if err := m.validateMatchedTxSequence(movTxs(block)); err != nil {
69 addOrders, deleteOrders, err := decodeTxsOrders(movTxs(block))
74 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
77 // Tx contains raw transaction and the sequence of tx in block
84 // NewTx create a new Tx instance
85 func NewTx(tx *types.Tx, blockHeight uint64, sequence int) *Tx {
86 return &Tx{rawTx: tx, blockHeight: blockHeight, txIndex: sequence}
89 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
90 func (m *Core) BeforeProposalBlock(block *types.Block, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
91 if block.Height <= m.startBlockHeight {
95 orderBook, err := buildOrderBook(m.movStore, movTxs(block))
100 program, _ := getRewardProgram(block.Height)
101 rewardProgram, err := hex.DecodeString(program)
103 return nil, errNotConfiguredRewardProgram
106 matchEngine := match.NewEngine(orderBook, rewardProgram)
107 tradePairIterator := database.NewTradePairIterator(m.movStore)
108 matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout)
109 return matchCollector.result()
112 // ChainStatus return the current block height and block hash in dex core
113 func (m *Core) ChainStatus() (uint64, *bc.Hash, error) {
114 state, err := m.movStore.GetMovDatabaseState()
115 if err == database.ErrNotInitDBState {
116 return 0, nil, protocol.ErrNotInitSubProtocolChainStatus
123 return state.Height, state.Hash, nil
126 // DetachBlock parse pending order and cancel from the the transactions of block
127 // and add cancel order to the dex db, remove pending order from dex db.
128 func (m *Core) DetachBlock(block *types.Block) error {
129 if block.Height < m.startBlockHeight {
133 if block.Height == m.startBlockHeight {
138 deleteOrders, addOrders, err := decodeTxsOrders(movTxs(block))
143 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
146 // InitChainStatus used to init the start block height and start block hash to store
147 func (m *Core) InitChainStatus(startHash *bc.Hash) error {
148 if _, err := m.movStore.GetMovDatabaseState(); err == nil {
149 return errChainStatusHasAlreadyInit
152 return m.movStore.InitDBState(m.startBlockHeight, startHash)
155 // IsDust block the transaction that are not generated by the match engine
156 func (m *Core) IsDust(tx *types.Tx) bool {
157 for _, input := range tx.Inputs {
158 if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
165 // Name return the name of current module
166 func (m *Core) Name() string {
170 // StartHeight return the start block height of current module
171 func (m *Core) StartHeight() uint64 {
172 return m.startBlockHeight
175 // ValidateBlock no need to verify the block header, because the first module has been verified.
176 // just need to verify the transactions in the block.
177 func (m *Core) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
178 for i, tx := range block.Transactions {
179 if err := m.ValidateTx(tx, verifyResults[i], block.Height); err != nil {
186 // ValidateTx validate one transaction.
187 func (m *Core) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
188 if blockHeight <= m.startBlockHeight {
192 if verifyResult.StatusFail {
193 return errStatusFailMustFalse
196 if common.IsMatchedTx(tx) {
197 if err := validateMatchedTx(tx, blockHeight); err != nil {
200 } else if common.IsCancelOrderTx(tx) {
201 if err := validateCancelOrderTx(tx); err != nil {
206 for _, output := range tx.Outputs {
207 if !segwit.IsP2WMCScript(output.ControlProgram()) {
211 if err := validateMagneticContractArgs(output.AssetAmount(), output.ControlProgram()); err != nil {
218 // matchedTxFee is object to record the mov tx's fee information
219 type matchedTxFee struct {
224 // calcFeeAmount return the amount of fee in the matching transaction
225 func calcFeeAmount(matchedTx *types.Tx) (map[bc.AssetID]*matchedTxFee, error) {
226 assetFeeMap := make(map[bc.AssetID]*matchedTxFee)
227 dealProgMaps := make(map[string]bool)
229 for _, input := range matchedTx.Inputs {
230 assetFeeMap[input.AssetID()] = &matchedTxFee{amount: input.AssetAmount().Amount}
231 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
236 dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
239 for _, output := range matchedTx.Outputs {
240 assetAmount := output.AssetAmount()
241 if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
242 assetFeeMap[*assetAmount.AssetId].amount -= assetAmount.Amount
243 if assetFeeMap[*assetAmount.AssetId].amount <= 0 {
244 delete(assetFeeMap, *assetAmount.AssetId)
246 } else if assetFeeMap[*assetAmount.AssetId].rewardProgram == nil {
247 assetFeeMap[*assetAmount.AssetId].rewardProgram = output.ControlProgram()
249 return nil, errors.Wrap(errRewardProgramIsWrong, "double reward program")
252 return assetFeeMap, nil
255 func validateCancelOrderTx(tx *types.Tx) error {
256 for _, input := range tx.Inputs {
257 if !segwit.IsP2WMCScript(input.ControlProgram()) {
258 return errInputProgramMustP2WMCScript
261 if contract.IsTradeClauseSelector(input) {
262 return errExistTradeInCancelOrderTx
268 func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte) error {
269 contractArgs, err := segwit.DecodeP2WMCProgram(program)
274 if *fromAssetAmount.AssetId == contractArgs.RequestedAsset {
275 return errInvalidTradePairs
278 if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
279 return errRatioOfTradeLessThanZero
282 if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) < 1 {
283 return errRequestAmountMath
288 func validateMatchedTx(tx *types.Tx, blockHeight uint64) error {
289 fromAssetIDMap := make(map[string]bool)
290 toAssetIDMap := make(map[string]bool)
291 for i, input := range tx.Inputs {
292 if !segwit.IsP2WMCScript(input.ControlProgram()) {
293 return errInputProgramMustP2WMCScript
296 if contract.IsCancelClauseSelector(input) {
297 return errExistCancelOrderInMatchedTx
300 order, err := common.NewOrderFromInput(tx, i)
305 fromAssetIDMap[order.FromAssetID.String()] = true
306 toAssetIDMap[order.ToAssetID.String()] = true
309 inputSize := len(tx.Inputs)
310 if len(fromAssetIDMap) != inputSize || len(toAssetIDMap) != inputSize {
311 return errAssetIDMustUniqueInMatchedTx
314 return validateMatchedTxFee(tx, blockHeight)
317 func validateMatchedTxFee(tx *types.Tx, blockHeight uint64) error {
318 matchedTxFees, err := calcFeeAmount(tx)
323 for _, fee := range matchedTxFees {
324 if err := validateRewardProgram(blockHeight, hex.EncodeToString(fee.rewardProgram)); err != nil {
329 orders, err := parseDeleteOrdersFromTx(tx)
334 receivedAmount, _ := match.CalcReceivedAmount(orders)
335 feeAmounts := make(map[bc.AssetID]uint64)
336 for assetID, fee := range matchedTxFees {
337 feeAmounts[assetID] = fee.amount
340 feeStrategy := match.NewDefaultFeeStrategy()
341 return feeStrategy.Validate(receivedAmount, feeAmounts)
344 func (m *Core) validateMatchedTxSequence(txs []*Tx) error {
345 orderBook := match.NewOrderBook(m.movStore, nil, nil)
346 for _, tx := range txs {
347 if common.IsMatchedTx(tx.rawTx) {
348 tradePairs, err := parseTradePairsFromMatchedTx(tx.rawTx)
353 orders := orderBook.PeekOrders(tradePairs)
354 if err := validateSpendOrders(tx.rawTx, orders); err != nil {
358 orderBook.PopOrders(tradePairs)
359 } else if common.IsCancelOrderTx(tx.rawTx) {
360 orders, err := parseDeleteOrdersFromTx(tx.rawTx)
365 for _, order := range orders {
366 orderBook.DelOrder(order)
370 addOrders, err := parseAddOrdersFromTx(tx)
375 for _, order := range addOrders {
376 orderBook.AddOrder(order)
382 func validateSpendOrders(tx *types.Tx, orders []*common.Order) error {
383 if len(tx.Inputs) != len(orders) {
384 return errNotMatchedOrder
387 spendOutputIDs := make(map[string]bool)
388 for _, input := range tx.Inputs {
389 spendOutputID, err := input.SpentOutputID()
394 spendOutputIDs[spendOutputID.String()] = true
397 for _, order := range orders {
398 outputID := order.UTXOHash().String()
399 if _, ok := spendOutputIDs[outputID]; !ok {
400 return errSpendOutputIDIsIncorrect
406 func decodeTxsOrders(txs []*Tx) ([]*common.Order, []*common.Order, error) {
407 deleteOrderMap := make(map[string]*common.Order)
408 addOrderMap := make(map[string]*common.Order)
409 for _, tx := range txs {
410 addOrders, err := parseAddOrdersFromTx(tx)
415 for _, order := range addOrders {
416 addOrderMap[order.Key()] = order
419 deleteOrders, err := parseDeleteOrdersFromTx(tx.rawTx)
424 for _, order := range deleteOrders {
425 deleteOrderMap[order.Key()] = order
429 addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
430 return addOrders, deleteOrders, nil
433 func buildOrderBook(store database.MovStore, txs []*Tx) (*match.OrderBook, error) {
434 var arrivalAddOrders, arrivalDelOrders []*common.Order
435 for _, tx := range txs {
436 addOrders, err := parseAddOrdersFromTx(tx)
441 delOrders, err := parseDeleteOrdersFromTx(tx.rawTx)
446 arrivalAddOrders = append(arrivalAddOrders, addOrders...)
447 arrivalDelOrders = append(arrivalDelOrders, delOrders...)
450 return match.NewOrderBook(store, arrivalAddOrders, arrivalDelOrders), nil
453 func parseAddOrdersFromTx(tx *Tx) ([]*common.Order, error) {
454 var orders []*common.Order
455 for i, output := range tx.rawTx.Outputs {
456 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
460 if output.AssetAmount().Amount == 0 {
464 order, err := common.NewOrderFromOutput(tx.rawTx, i, tx.blockHeight, tx.txIndex)
469 orders = append(orders, order)
474 func parseDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
475 var orders []*common.Order
476 for i, input := range tx.Inputs {
477 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
481 order, err := common.NewOrderFromInput(tx, i)
486 orders = append(orders, order)
491 func parseTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
492 var tradePairs []*common.TradePair
493 for _, tx := range tx.Inputs {
494 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
499 tradePairs = append(tradePairs, &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset})
501 return tradePairs, nil
504 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
505 var deleteOrders, addOrders []*common.Order
506 for orderID, order := range addOrderMap {
507 if _, ok := deleteOrderMap[orderID]; ok {
508 delete(deleteOrderMap, orderID)
511 addOrders = append(addOrders, order)
514 for _, order := range deleteOrderMap {
515 deleteOrders = append(deleteOrders, order)
517 return addOrders, deleteOrders
520 func movTxs(block *types.Block) []*Tx {
522 for i, tx := range block.Transactions {
523 movTxs = append(movTxs, NewTx(tx, block.Height, i))
528 // getRewardProgram return the reward program by specified block height
529 // if no reward program configured, then will return empty string
530 // if reward program of 0-100 height is configured, but the specified height is 200, then will return 0-100's reward program
531 // the second return value represent whether to find exactly
532 func getRewardProgram(height uint64) (string, bool) {
533 rewardPrograms := consensus.ActiveNetParams.MovRewardPrograms
534 if len(rewardPrograms) == 0 {
539 for _, rewardProgram := range rewardPrograms {
540 program = rewardProgram.Program
541 if height >= rewardProgram.BeginBlock && height <= rewardProgram.EndBlock {
545 return program, false
548 func validateRewardProgram(height uint64, program string) error {
549 rewardProgram, exact := getRewardProgram(height)
550 if exact && rewardProgram != program {
551 return errRewardProgramIsWrong