4 "github.com/bytom/vapor/application/mov/common"
5 "github.com/bytom/vapor/application/mov/contract"
6 "github.com/bytom/vapor/application/mov/database"
7 "github.com/bytom/vapor/application/mov/match"
8 "github.com/bytom/vapor/consensus/segwit"
9 dbm "github.com/bytom/vapor/database/leveldb"
10 "github.com/bytom/vapor/errors"
11 "github.com/bytom/vapor/protocol/bc"
12 "github.com/bytom/vapor/protocol/bc/types"
15 const maxFeeRate = 0.05
18 errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
19 errStatusFailMustFalse = errors.New("status fail of transaction does not allow to be true")
20 errInputProgramMustP2WMCScript = errors.New("input program of trade tx must p2wmc script")
21 errExistCancelOrderInMatchedTx = errors.New("can't exist cancel order in the matched transaction")
22 errExistTradeInCancelOrderTx = errors.New("can't exist trade in the cancel order transaction")
23 errAmountOfFeeGreaterThanMaximum = errors.New("amount of fee greater than max fee amount")
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")
31 // MovCore represent the core logic of the match module, which include generate match transactions before packing the block,
32 // verify the match transaction in block is correct, and update the order table according to the transaction.
34 movStore database.MovStore
35 startBlockHeight uint64
38 // NewMovCore return a instance of MovCore by path of mov db
39 func NewMovCore(dbBackend, dbDir string, startBlockHeight uint64) *MovCore {
40 movDB := dbm.NewDB("mov", dbBackend, dbDir)
41 return &MovCore{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
44 // ApplyBlock parse pending order and cancel from the the transactions of block
45 // and add pending order to the dex db, remove cancel order from dex db.
46 func (m *MovCore) ApplyBlock(block *types.Block) error {
47 if block.Height < m.startBlockHeight {
51 if block.Height == m.startBlockHeight {
52 blockHash := block.Hash()
53 if err := m.movStore.InitDBState(block.Height, &blockHash); err != nil {
60 if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
64 addOrders, deleteOrders, err := applyTransactions(block.Transactions)
69 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
72 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
73 func (m *MovCore) BeforeProposalBlock(txs []*types.Tx, nodeProgram []byte, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
74 if blockHeight <= m.startBlockHeight {
78 orderBook, err := buildOrderBook(m.movStore, txs)
83 matchEngine := match.NewEngine(orderBook, maxFeeRate, nodeProgram)
84 tradePairMap := make(map[string]bool)
85 tradePairIterator := database.NewTradePairIterator(m.movStore)
87 var packagedTxs []*types.Tx
88 for gasLeft > 0 && !isTimeout() && tradePairIterator.HasNext() {
89 tradePair := tradePairIterator.Next()
90 if tradePairMap[tradePair.Key()] {
93 tradePairMap[tradePair.Key()] = true
94 tradePairMap[tradePair.Reverse().Key()] = true
96 for gasLeft > 0 && !isTimeout() && matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) {
97 matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
102 gasUsed := calcMatchedTxGasUsed(matchedTx)
103 if gasLeft-gasUsed >= 0 {
104 packagedTxs = append(packagedTxs, matchedTx)
109 return packagedTxs, nil
112 // ChainStatus return the current block height and block hash in dex core
113 func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
114 state, err := m.movStore.GetMovDatabaseState()
119 return state.Height, state.Hash, nil
122 // DetachBlock parse pending order and cancel from the the transactions of block
123 // and add cancel order to the dex db, remove pending order from dex db.
124 func (m *MovCore) DetachBlock(block *types.Block) error {
125 if block.Height <= m.startBlockHeight {
129 deleteOrders, addOrders, err := applyTransactions(block.Transactions)
134 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
137 // IsDust block the transaction that are not generated by the match engine
138 func (m *MovCore) IsDust(tx *types.Tx) bool {
139 for _, input := range tx.Inputs {
140 if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
147 // Name return the name of current module
148 func (m *MovCore) Name() string {
152 // StartHeight return the start block height of current module
153 func (m *MovCore) StartHeight() uint64 {
154 return m.startBlockHeight
157 // ValidateBlock no need to verify the block header, because the first module has been verified.
158 // just need to verify the transactions in the block.
159 func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
160 return m.ValidateTxs(block.Transactions, verifyResults)
163 // ValidateTxs validate the trade transaction.
164 func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error {
165 for i, tx := range txs {
166 if err := m.ValidateTx(tx, verifyResults[i]); err != nil {
173 // ValidateTxs validate one transaction.
174 func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
175 if common.IsMatchedTx(tx) {
176 if err := validateMatchedTx(tx, verifyResult); err != nil {
181 if common.IsCancelOrderTx(tx) {
182 if err := validateCancelOrderTx(tx, verifyResult); err != nil {
187 for _, output := range tx.Outputs {
188 if !segwit.IsP2WMCScript(output.ControlProgram()) {
191 if verifyResult.StatusFail {
192 return errStatusFailMustFalse
195 if err := validateMagneticContractArgs(output.AssetAmount(), output.ControlProgram()); err != nil {
202 func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
203 if verifyResult.StatusFail {
204 return errStatusFailMustFalse
207 for _, input := range tx.Inputs {
208 if !segwit.IsP2WMCScript(input.ControlProgram()) {
209 return errInputProgramMustP2WMCScript
212 if contract.IsTradeClauseSelector(input) {
213 return errExistTradeInCancelOrderTx
219 func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte) error {
220 contractArgs, err := segwit.DecodeP2WMCProgram(program)
225 if *fromAssetAmount.AssetId == contractArgs.RequestedAsset {
226 return errInvalidTradePairs
229 if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
230 return errRatioOfTradeLessThanZero
233 if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs) < 1 {
234 return errRequestAmountMath
239 func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
240 if verifyResult.StatusFail {
241 return errStatusFailMustFalse
244 fromAssetIDMap := make(map[string]bool)
245 toAssetIDMap := make(map[string]bool)
246 for i, input := range tx.Inputs {
247 if !segwit.IsP2WMCScript(input.ControlProgram()) {
248 return errInputProgramMustP2WMCScript
251 if contract.IsCancelClauseSelector(input) {
252 return errExistCancelOrderInMatchedTx
255 order, err := common.NewOrderFromInput(tx, i)
260 fromAssetIDMap[order.FromAssetID.String()] = true
261 toAssetIDMap[order.ToAssetID.String()] = true
264 if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
265 return errAssetIDMustUniqueInMatchedTx
268 return validateMatchedTxFeeAmount(tx)
271 func validateMatchedTxFeeAmount(tx *types.Tx) error {
272 txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
277 for _, amount := range txFee {
278 if amount.FeeAmount > amount.MaxFeeAmount {
279 return errAmountOfFeeGreaterThanMaximum
285 func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
286 orderBook, err := buildOrderBook(m.movStore, txs)
291 for _, matchedTx := range txs {
292 if !common.IsMatchedTx(matchedTx) {
296 tradePairs, err := getTradePairsFromMatchedTx(matchedTx)
301 orders := orderBook.PeekOrders(tradePairs)
302 if !match.IsMatched(orders) {
303 return errNotMatchedOrder
306 if err := validateSpendOrders(matchedTx, orders); err != nil {
310 orderBook.PopOrders(tradePairs)
312 for i, output := range matchedTx.Outputs {
313 if !segwit.IsP2WMCScript(output.ControlProgram()) {
317 order, err := common.NewOrderFromOutput(matchedTx, i)
322 if err := orderBook.AddOrder(order); err != nil {
331 func validateSpendOrders(matchedTx *types.Tx, orders []*common.Order) error {
332 spendOutputIDs := make(map[string]bool)
333 for _, input := range matchedTx.Inputs {
334 spendOutputID, err := input.SpentOutputID()
339 spendOutputIDs[spendOutputID.String()] = true
342 for _, order := range orders {
343 outputID := order.UTXOHash().String()
344 if _, ok := spendOutputIDs[outputID]; !ok {
345 return errSpendOutputIDIsIncorrect
351 func applyTransactions(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
352 deleteOrderMap := make(map[string]*common.Order)
353 addOrderMap := make(map[string]*common.Order)
354 for _, tx := range txs {
355 addOrders, err := getAddOrdersFromTx(tx)
360 for _, order := range addOrders {
361 addOrderMap[order.Key()] = order
364 deleteOrders, err := getDeleteOrdersFromTx(tx)
369 for _, order := range deleteOrders {
370 deleteOrderMap[order.Key()] = order
374 addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
375 return addOrders, deleteOrders, nil
378 func buildOrderBook(store database.MovStore, txs []*types.Tx) (*match.OrderBook, error) {
379 var nonMatchedTxs []*types.Tx
380 for _, tx := range txs {
381 if !common.IsMatchedTx(tx) {
382 nonMatchedTxs = append(nonMatchedTxs, tx)
386 var arrivalAddOrders, arrivalDelOrders []*common.Order
387 for _, tx := range nonMatchedTxs {
388 addOrders, err := getAddOrdersFromTx(tx)
393 delOrders, err := getDeleteOrdersFromTx(tx)
398 arrivalAddOrders = append(arrivalAddOrders, addOrders...)
399 arrivalDelOrders = append(arrivalDelOrders, delOrders...)
402 return match.NewOrderBook(store, arrivalAddOrders, arrivalDelOrders), nil
405 func calcMatchedTxGasUsed(tx *types.Tx) int64 {
406 return int64(len(tx.Inputs))*150 + int64(tx.SerializedSize)
409 func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
410 var orders []*common.Order
411 for i, output := range tx.Outputs {
412 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
416 order, err := common.NewOrderFromOutput(tx, i)
421 orders = append(orders, order)
426 func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
427 var orders []*common.Order
428 for i, input := range tx.Inputs {
429 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
433 order, err := common.NewOrderFromInput(tx, i)
438 orders = append(orders, order)
443 func getTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
444 var tradePairs []*common.TradePair
445 for _, tx := range tx.Inputs {
446 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
451 tradePairs = append(tradePairs, &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset})
453 return tradePairs, nil
456 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
457 var deleteOrders, addOrders []*common.Order
458 for orderID, order := range addOrderMap {
459 if _, ok := deleteOrderMap[orderID]; ok {
460 delete(deleteOrderMap, orderID)
463 addOrders = append(addOrders, order)
466 for _, order := range deleteOrderMap {
467 deleteOrders = append(deleteOrders, order)
469 return addOrders, deleteOrders