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/protocol/bc"
12 "github.com/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 errLengthOfInputIsIncorrect = errors.New("length of matched tx input is not equals to actual matched tx input")
27 errSpendOutputIDIsIncorrect = errors.New("spend output id of matched tx is not equals to actual matched tx")
28 errRequestAmountMath = errors.New("request amount of order less than one or big than max of int64")
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)
73 @issue: I have two orders A and B, order A's seller program is order B and order B's seller program is order A.
74 Assume consensus node accept 0% fee and This two orders are the only two order of this trading pair, will this
75 become an infinite loop and DDoS attacks the whole network?
77 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
78 func (m *MovCore) BeforeProposalBlock(txs []*types.Tx, nodeProgram []byte, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
79 if blockHeight <= m.startBlockHeight {
83 orderTable, err := buildOrderTable(m.movStore, txs)
88 matchEngine := match.NewEngine(orderTable, maxFeeRate, nodeProgram)
89 tradePairMap := make(map[string]bool)
90 tradePairIterator := database.NewTradePairIterator(m.movStore)
92 var packagedTxs []*types.Tx
93 for gasLeft > 0 && !isTimeout() && tradePairIterator.HasNext() {
94 tradePair := tradePairIterator.Next()
95 if tradePairMap[tradePair.Key()] {
98 tradePairMap[tradePair.Key()] = true
99 tradePairMap[tradePair.Reverse().Key()] = true
101 for gasLeft > 0 && !isTimeout() && matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) {
102 matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
107 gasUsed := calcMatchedTxGasUsed(matchedTx)
108 if gasLeft-gasUsed >= 0 {
109 packagedTxs = append(packagedTxs, matchedTx)
114 return packagedTxs, nil
117 // ChainStatus return the current block height and block hash in dex core
118 func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
119 state, err := m.movStore.GetMovDatabaseState()
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 *MovCore) DetachBlock(block *types.Block) error {
130 if block.Height <= m.startBlockHeight {
134 deleteOrders, addOrders, err := applyTransactions(block.Transactions)
139 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
142 // IsDust block the transaction that are not generated by the match engine
143 func (m *MovCore) IsDust(tx *types.Tx) bool {
144 for _, input := range tx.Inputs {
145 if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
152 // Name return the name of current module
153 func (m *MovCore) Name() string {
157 // StartHeight return the start block height of current module
158 func (m *MovCore) StartHeight() uint64 {
159 return m.startBlockHeight
162 // ValidateBlock no need to verify the block header, because the first module has been verified.
163 // just need to verify the transactions in the block.
164 func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
165 return m.ValidateTxs(block.Transactions, verifyResults)
168 // ValidateTxs validate the trade transaction.
169 func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error {
170 for i, tx := range txs {
171 if err := m.ValidateTx(tx, verifyResults[i]); err != nil {
178 // ValidateTxs validate one transaction.
179 func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
180 if common.IsMatchedTx(tx) {
181 if err := validateMatchedTx(tx, verifyResult); err != nil {
186 if common.IsCancelOrderTx(tx) {
187 if err := validateCancelOrderTx(tx, verifyResult); err != nil {
192 for _, output := range tx.Outputs {
193 if !segwit.IsP2WMCScript(output.ControlProgram()) {
196 if verifyResult.StatusFail {
197 return errStatusFailMustFalse
200 if err := validateMagneticContractArgs(output.AssetAmount().Amount, output.ControlProgram()); err != nil {
207 func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
208 if verifyResult.StatusFail {
209 return errStatusFailMustFalse
212 for _, input := range tx.Inputs {
213 if !segwit.IsP2WMCScript(input.ControlProgram()) {
214 return errInputProgramMustP2WMCScript
217 if contract.IsTradeClauseSelector(input) {
218 return errExistTradeInCancelOrderTx
224 func validateMagneticContractArgs(fromAmount uint64, program []byte) error {
225 contractArgs, err := segwit.DecodeP2WMCProgram(program)
230 if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
231 return errRatioOfTradeLessThanZero
234 if match.CalcRequestAmount(fromAmount, contractArgs) < 1 {
235 return errRequestAmountMath
240 func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
241 if verifyResult.StatusFail {
242 return errStatusFailMustFalse
245 fromAssetIDMap := make(map[string]bool)
246 toAssetIDMap := make(map[string]bool)
247 for i, input := range tx.Inputs {
248 if !segwit.IsP2WMCScript(input.ControlProgram()) {
249 return errInputProgramMustP2WMCScript
252 if contract.IsCancelClauseSelector(input) {
253 return errExistCancelOrderInMatchedTx
256 order, err := common.NewOrderFromInput(tx, i)
261 if *order.FromAssetID == *order.ToAssetID {
262 return errInvalidTradePairs
265 fromAssetIDMap[order.FromAssetID.String()] = true
266 toAssetIDMap[order.ToAssetID.String()] = true
269 if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
270 return errAssetIDMustUniqueInMatchedTx
273 return validateMatchedTxFeeAmount(tx)
276 func validateMatchedTxFeeAmount(tx *types.Tx) error {
277 txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
282 for _, amount := range txFee {
283 if amount.FeeAmount > amount.MaxFeeAmount {
284 return errAmountOfFeeGreaterThanMaximum
291 @issue: the match package didn't support circle yet
293 func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
294 orderTable, err := buildOrderTable(m.movStore, txs)
299 matchEngine := match.NewEngine(orderTable, maxFeeRate, nil)
300 for _, matchedTx := range txs {
301 if !common.IsMatchedTx(matchedTx) {
305 tradePairs, err := getSortedTradePairsFromMatchedTx(matchedTx)
310 actualMatchedTx, err := matchEngine.NextMatchedTx(tradePairs...)
315 if len(matchedTx.Inputs) != len(actualMatchedTx.Inputs) {
316 return errLengthOfInputIsIncorrect
319 spendOutputIDs := make(map[string]bool)
320 for _, input := range matchedTx.Inputs {
321 spendOutputID, err := input.SpentOutputID()
326 spendOutputIDs[spendOutputID.String()] = true
329 for _, input := range actualMatchedTx.Inputs {
330 spendOutputID, err := input.SpentOutputID()
335 if _, ok := spendOutputIDs[spendOutputID.String()]; !ok {
336 return errSpendOutputIDIsIncorrect
343 func applyTransactions(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
344 deleteOrderMap := make(map[string]*common.Order)
345 addOrderMap := make(map[string]*common.Order)
346 for _, tx := range txs {
347 addOrders, err := getAddOrdersFromTx(tx)
352 for _, order := range addOrders {
353 addOrderMap[order.Key()] = order
356 deleteOrders, err := getDeleteOrdersFromTx(tx)
361 for _, order := range deleteOrders {
362 deleteOrderMap[order.Key()] = order
366 addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
367 return addOrders, deleteOrders, nil
371 @issue: if consensus node packed match transaction first then packed regular tx, this function's logic may make a valid block invalid
373 func buildOrderTable(store database.MovStore, txs []*types.Tx) (*match.OrderTable, error) {
374 var nonMatchedTxs []*types.Tx
375 for _, tx := range txs {
376 if !common.IsMatchedTx(tx) {
377 nonMatchedTxs = append(nonMatchedTxs, tx)
381 var arrivalAddOrders, arrivalDelOrders []*common.Order
382 for _, tx := range nonMatchedTxs {
383 addOrders, err := getAddOrdersFromTx(tx)
388 delOrders, err := getDeleteOrdersFromTx(tx)
393 arrivalAddOrders = append(arrivalAddOrders, addOrders...)
394 arrivalDelOrders = append(arrivalDelOrders, delOrders...)
397 return match.NewOrderTable(store, arrivalAddOrders, arrivalDelOrders), nil
400 func calcMatchedTxGasUsed(tx *types.Tx) int64 {
401 return int64(len(tx.Inputs))*150 + int64(tx.SerializedSize)
404 func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
405 var orders []*common.Order
406 for i, output := range tx.Outputs {
407 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
411 order, err := common.NewOrderFromOutput(tx, i)
416 orders = append(orders, order)
421 func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
422 var orders []*common.Order
423 for i, input := range tx.Inputs {
424 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
428 order, err := common.NewOrderFromInput(tx, i)
433 orders = append(orders, order)
438 func getSortedTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
439 assetMap := make(map[bc.AssetID]bc.AssetID)
440 var firstTradePair *common.TradePair
441 for _, tx := range tx.Inputs {
442 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
447 assetMap[tx.AssetID()] = contractArgs.RequestedAsset
448 if firstTradePair == nil {
449 firstTradePair = &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset}
453 tradePairs := []*common.TradePair{firstTradePair}
454 for tradePair := firstTradePair; *tradePair.ToAssetID != *firstTradePair.FromAssetID; {
455 nextTradePairToAssetID, ok := assetMap[*tradePair.ToAssetID]
457 return nil, errInvalidTradePairs
460 tradePair = &common.TradePair{FromAssetID: tradePair.ToAssetID, ToAssetID: &nextTradePairToAssetID}
461 tradePairs = append(tradePairs, tradePair)
464 if len(tradePairs) != len(tx.Inputs) {
465 return nil, errInvalidTradePairs
467 return tradePairs, nil
470 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
471 var deleteOrders, addOrders []*common.Order
472 for orderID, order := range addOrderMap {
473 if _, ok := deleteOrderMap[orderID]; ok {
474 delete(deleteOrderMap, orderID)
477 addOrders = append(addOrders, order)
480 for _, order := range deleteOrderMap {
481 deleteOrders = append(deleteOrders, order)
483 return addOrders, deleteOrders