4 "github.com/vapor/application/mov/common"
5 "github.com/vapor/application/mov/contract"
6 "github.com/vapor/application/mov/database"
7 "github.com/vapor/application/mov/match"
8 "github.com/vapor/consensus/segwit"
9 dbm "github.com/vapor/database/leveldb"
10 "github.com/vapor/errors"
11 "github.com/vapor/math/checked"
12 "github.com/vapor/protocol/bc"
13 "github.com/vapor/protocol/bc/types"
16 const maxFeeRate = 0.05
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 matched 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 errAmountOfFeeGreaterThanMaximum = errors.New("amount of fee greater than max fee amount")
25 errAssetIDMustUniqueInMatchedTx = errors.New("asset id must unique in matched transaction")
26 errRatioOfTradeLessThanZero = errors.New("ratio arguments must greater than zero")
27 errNumeratorOfRatioIsOverflow = errors.New("ratio numerator of contract args product input amount is overflow")
28 errLengthOfInputIsIncorrect = errors.New("length of matched tx input is not equals to actual matched tx input")
29 errSpendOutputIDIsIncorrect = errors.New("spend output id of matched tx is not equals to actual matched tx")
32 // MovCore represent the core logic of the match module, which include generate match transactions before packing the block,
33 // verify the match transaction in block is correct, and update the order table according to the transaction.
35 movStore database.MovStore
36 startBlockHeight uint64
39 // NewMovCore return a instance of MovCore by path of mov db
40 func NewMovCore(dbBackend, dbDir string, startBlockHeight uint64) *MovCore {
41 movDB := dbm.NewDB("mov", dbBackend, dbDir)
42 return &MovCore{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
45 // Name return the name of current module
46 func (m *MovCore) Name() string {
50 // ChainStatus return the current block height and block hash in dex core
51 func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
52 state, err := m.movStore.GetMovDatabaseState()
57 return state.Height, state.Hash, nil
60 // ValidateBlock no need to verify the block header, becaure the first module has been verified.
61 // just need to verify the transactions in the block.
62 func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
63 return m.ValidateTxs(block.Transactions, verifyResults)
66 // ValidateTxs validate the trade transaction.
67 func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error {
68 for i, tx := range txs {
69 if err := m.ValidateTx(tx, verifyResults[i]); err != nil {
76 func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
77 if common.IsMatchedTx(tx) {
78 if err := validateMatchedTx(tx, verifyResult); err != nil {
83 if common.IsCancelOrderTx(tx) {
84 if err := validateCancelOrderTx(tx, verifyResult); err != nil {
89 for _, output := range tx.Outputs {
90 if !segwit.IsP2WMCScript(output.ControlProgram()) {
93 if verifyResult.StatusFail {
94 return errStatusFailMustFalse
97 if err := validateMagneticContractArgs(output.AssetAmount().Amount, output.ControlProgram()); err != nil {
104 func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
105 if verifyResult.StatusFail {
106 return errStatusFailMustFalse
109 fromAssetIDMap := make(map[string]bool)
110 toAssetIDMap := make(map[string]bool)
111 for i, input := range tx.Inputs {
112 if !segwit.IsP2WMCScript(input.ControlProgram()) {
113 return errInputProgramMustP2WMCScript
116 if contract.IsCancelClauseSelector(input) {
117 return errExistCancelOrderInMatchedTx
120 order, err := common.NewOrderFromInput(tx, i)
125 fromAssetIDMap[order.FromAssetID.String()] = true
126 toAssetIDMap[order.ToAssetID.String()] = true
129 if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
130 return errAssetIDMustUniqueInMatchedTx
133 return validateMatchedTxFeeAmount(tx)
136 func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
137 if verifyResult.StatusFail {
138 return errStatusFailMustFalse
141 for _, input := range tx.Inputs {
142 if !segwit.IsP2WMCScript(input.ControlProgram()) {
143 return errInputProgramMustP2WMCScript
146 if contract.IsTradeClauseSelector(input) {
147 return errExistTradeInCancelOrderTx
153 func validateMatchedTxFeeAmount(tx *types.Tx) error {
154 txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
159 for _, amount := range txFee {
160 if amount.FeeAmount > amount.MaxFeeAmount {
161 return errAmountOfFeeGreaterThanMaximum
167 func validateMagneticContractArgs(inputAmount uint64, program []byte) error {
168 contractArgs, err := segwit.DecodeP2WMCProgram(program)
173 if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
174 return errRatioOfTradeLessThanZero
177 if _, ok := checked.MulInt64(int64(inputAmount), contractArgs.RatioNumerator); !ok {
178 return errNumeratorOfRatioIsOverflow
183 // ApplyBlock parse pending order and cancel from the the transactions of block
184 // and add pending order to the dex db, remove cancel order from dex db.
185 func (m *MovCore) ApplyBlock(block *types.Block) error {
186 if block.Height < m.startBlockHeight {
190 if block.Height == m.startBlockHeight {
191 blockHash := block.Hash()
192 if err := m.movStore.InitDBState(block.Height, &blockHash); err != nil {
197 if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
201 addOrders, deleteOrders, err := applyTransactions(block.Transactions)
206 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
209 func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
210 matchEngine := match.NewEngine(m.movStore, maxFeeRate, nil)
211 for _, matchedTx := range txs {
212 if !common.IsMatchedTx(matchedTx) {
216 tradePairs, err := getSortedTradePairsFromMatchedTx(matchedTx)
221 actualMatchedTx, err := matchEngine.NextMatchedTx(tradePairs...)
226 if len(matchedTx.Inputs) != len(actualMatchedTx.Inputs) {
227 return errLengthOfInputIsIncorrect
230 spendOutputIDs := make(map[string]bool)
231 for _, input := range matchedTx.Inputs {
232 spendOutputID, err := input.SpentOutputID()
237 spendOutputIDs[spendOutputID.String()] = true
240 for _, input := range actualMatchedTx.Inputs {
241 spendOutputID, err := input.SpentOutputID()
246 if _, ok := spendOutputIDs[spendOutputID.String()]; !ok {
247 return errSpendOutputIDIsIncorrect
254 func getSortedTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
255 assetMap := make(map[bc.AssetID]bc.AssetID)
256 var firstTradePair *common.TradePair
257 for _, tx := range tx.Inputs {
258 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
263 assetMap[tx.AssetID()] = contractArgs.RequestedAsset
264 if firstTradePair == nil {
265 firstTradePair = &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset}
269 tradePairs := []*common.TradePair{firstTradePair}
270 for tradePair := firstTradePair; *tradePair.ToAssetID != *firstTradePair.FromAssetID; {
271 nextTradePairToAssetID, ok := assetMap[*tradePair.ToAssetID]
273 return nil, errInvalidTradePairs
276 tradePair = &common.TradePair{FromAssetID: tradePair.ToAssetID, ToAssetID: &nextTradePairToAssetID}
277 tradePairs = append(tradePairs, tradePair)
280 if len(tradePairs) != len(tx.Inputs) {
281 return nil, errInvalidTradePairs
283 return tradePairs, nil
286 // DetachBlock parse pending order and cancel from the the transactions of block
287 // and add cancel order to the dex db, remove pending order from dex db.
288 func (m *MovCore) DetachBlock(block *types.Block) error {
289 if block.Height <= m.startBlockHeight {
293 deleteOrders, addOrders, err := applyTransactions(block.Transactions)
298 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
301 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
302 func (m *MovCore) BeforeProposalBlock(nodeProgram []byte, gasLeft int64, calcGasUsed func(*types.Tx) (int64, error)) ([]*types.Tx, int64, error) {
303 matchEngine := match.NewEngine(m.movStore, maxFeeRate, nodeProgram)
304 tradePairMap := make(map[string]bool)
305 tradePairIterator := database.NewTradePairIterator(m.movStore)
307 var packagedTxs []*types.Tx
308 for gasLeft > 0 && tradePairIterator.HasNext() {
309 tradePair := tradePairIterator.Next()
310 if tradePairMap[tradePair.Key()] {
313 tradePairMap[tradePair.Key()] = true
314 tradePairMap[tradePair.Reverse().Key()] = true
316 for gasLeft > 0 && matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) {
317 matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
322 gasUsed, err := calcGasUsed(matchedTx)
327 packagedTxs = append(packagedTxs, matchedTx)
331 return packagedTxs, gasLeft, nil
334 // IsDust block the transaction that are not generated by the match engine
335 func (m *MovCore) IsDust(tx *types.Tx) bool {
336 for _, input := range tx.Inputs {
337 if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
344 func applyTransactions(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
345 deleteOrderMap := make(map[string]*common.Order)
346 addOrderMap := make(map[string]*common.Order)
347 for _, tx := range txs {
348 addOrders, err := getAddOrdersFromTx(tx)
353 for _, order := range addOrders {
354 addOrderMap[order.Key()] = order
357 deleteOrders, err := getDeleteOrdersFromTx(tx)
362 for _, order := range deleteOrders {
363 deleteOrderMap[order.Key()] = order
367 addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
368 return addOrders, deleteOrders, nil
371 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
372 var deleteOrders, addOrders []*common.Order
373 for orderID, order := range addOrderMap {
374 if _, ok := deleteOrderMap[orderID]; ok {
375 delete(deleteOrderMap, orderID)
378 addOrders = append(addOrders, order)
381 for _, order := range deleteOrderMap {
382 deleteOrders = append(deleteOrders, order)
384 return addOrders, deleteOrders
387 func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
388 var orders []*common.Order
389 for i, output := range tx.Outputs {
390 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
394 order, err := common.NewOrderFromOutput(tx, i)
399 orders = append(orders, order)
404 func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
405 var orders []*common.Order
406 for i, input := range tx.Inputs {
407 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
411 order, err := common.NewOrderFromInput(tx, i)
416 orders = append(orders, order)