errAmountOfFeeGreaterThanMaximum = errors.New("amount of fee greater than max fee amount")
errAssetIDMustUniqueInMatchedTx = errors.New("asset id must unique in matched transaction")
errRatioOfTradeLessThanZero = errors.New("ratio arguments must greater than zero")
- errLengthOfInputIsIncorrect = errors.New("length of matched tx input is not equals to actual matched tx input")
errSpendOutputIDIsIncorrect = errors.New("spend output id of matched tx is not equals to actual matched tx")
errRequestAmountMath = errors.New("request amount of order less than one or big than max of int64")
+ errNotMatchedOrder = errors.New("order in matched tx is not matched")
)
// MovCore represent the core logic of the match module, which include generate match transactions before packing the block,
if err := m.movStore.InitDBState(block.Height, &blockHash); err != nil {
return err
}
-
- return nil
}
if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
return err
}
- addOrders, deleteOrders, err := applyTransactions(block.Transactions)
+ addOrders, deleteOrders, err := decodeTxsOrders(block.Transactions)
if err != nil {
return err
}
return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
}
-/*
- @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.
- Assume consensus node accept 0% fee and This two orders are the only two order of this trading pair, will this
- become an infinite loop and DDoS attacks the whole network?
-*/
// BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
func (m *MovCore) BeforeProposalBlock(txs []*types.Tx, nodeProgram []byte, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
if blockHeight <= m.startBlockHeight {
return nil, nil
}
- orderTable, err := buildOrderTable(m.movStore, txs)
+ orderBook, err := buildOrderBook(m.movStore, txs)
if err != nil {
return nil, err
}
- matchEngine := match.NewEngine(orderTable, maxFeeRate, nodeProgram)
- tradePairMap := make(map[string]bool)
+ matchEngine := match.NewEngine(orderBook, maxFeeRate, nodeProgram)
tradePairIterator := database.NewTradePairIterator(m.movStore)
-
- var packagedTxs []*types.Tx
- for gasLeft > 0 && !isTimeout() && tradePairIterator.HasNext() {
- tradePair := tradePairIterator.Next()
- if tradePairMap[tradePair.Key()] {
- continue
- }
- tradePairMap[tradePair.Key()] = true
- tradePairMap[tradePair.Reverse().Key()] = true
-
- for gasLeft > 0 && !isTimeout() && matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) {
- matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
- if err != nil {
- return nil, err
- }
-
- gasUsed := calcMatchedTxGasUsed(matchedTx)
- if gasLeft-gasUsed >= 0 {
- packagedTxs = append(packagedTxs, matchedTx)
- }
- gasLeft -= gasUsed
- }
- }
- return packagedTxs, nil
+ matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout)
+ return matchCollector.result()
}
// ChainStatus return the current block height and block hash in dex core
// DetachBlock parse pending order and cancel from the the transactions of block
// and add cancel order to the dex db, remove pending order from dex db.
func (m *MovCore) DetachBlock(block *types.Block) error {
- if block.Height <= m.startBlockHeight {
+ if block.Height < m.startBlockHeight {
return nil
}
- deleteOrders, addOrders, err := applyTransactions(block.Transactions)
+ deleteOrders, addOrders, err := decodeTxsOrders(block.Transactions)
if err != nil {
return err
}
return nil
}
-// ValidateTxs validate one transaction.
+// ValidateTx validate one transaction.
func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
if common.IsMatchedTx(tx) {
if err := validateMatchedTx(tx, verifyResult); err != nil {
return err
}
- }
-
- if common.IsCancelOrderTx(tx) {
+ } else if common.IsCancelOrderTx(tx) {
if err := validateCancelOrderTx(tx, verifyResult); err != nil {
return err
}
return nil
}
-/*
- @issue: the match package didn't support circle yet
-*/
func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
- orderTable, err := buildOrderTable(m.movStore, txs)
- if err != nil {
- return err
- }
+ orderBook := match.NewOrderBook(m.movStore, nil, nil)
+ for _, tx := range txs {
+ if common.IsMatchedTx(tx) {
+ tradePairs, err := getTradePairsFromMatchedTx(tx)
+ if err != nil {
+ return err
+ }
- matchEngine := match.NewEngine(orderTable, maxFeeRate, nil)
- for _, matchedTx := range txs {
- if !common.IsMatchedTx(matchedTx) {
- continue
- }
+ orders := orderBook.PeekOrders(tradePairs)
+ if err := validateSpendOrders(tx, orders); err != nil {
+ return err
+ }
- tradePairs, err := getSortedTradePairsFromMatchedTx(matchedTx)
- if err != nil {
- return err
+ orderBook.PopOrders(tradePairs)
+ } else if common.IsCancelOrderTx(tx) {
+ orders, err := getDeleteOrdersFromTx(tx)
+ if err != nil {
+ return err
+ }
+
+ for _, order := range orders {
+ orderBook.DelOrder(order)
+ }
}
- actualMatchedTx, err := matchEngine.NextMatchedTx(tradePairs...)
+ addOrders, err := getAddOrdersFromTx(tx)
if err != nil {
return err
}
- if len(matchedTx.Inputs) != len(actualMatchedTx.Inputs) {
- return errLengthOfInputIsIncorrect
- }
-
- spendOutputIDs := make(map[string]bool)
- for _, input := range matchedTx.Inputs {
- spendOutputID, err := input.SpentOutputID()
- if err != nil {
+ for _, order := range addOrders {
+ if err := orderBook.AddOrder(order); err != nil {
return err
}
+ }
+ }
+ return nil
+}
- spendOutputIDs[spendOutputID.String()] = true
+func validateSpendOrders(tx *types.Tx, orders []*common.Order) error {
+ if len(tx.Inputs) != len(orders) {
+ return errNotMatchedOrder
+ }
+
+ spendOutputIDs := make(map[string]bool)
+ for _, input := range tx.Inputs {
+ spendOutputID, err := input.SpentOutputID()
+ if err != nil {
+ return err
}
- for _, input := range actualMatchedTx.Inputs {
- spendOutputID, err := input.SpentOutputID()
- if err != nil {
- return err
- }
+ spendOutputIDs[spendOutputID.String()] = true
+ }
- if _, ok := spendOutputIDs[spendOutputID.String()]; !ok {
- return errSpendOutputIDIsIncorrect
- }
+ for _, order := range orders {
+ outputID := order.UTXOHash().String()
+ if _, ok := spendOutputIDs[outputID]; !ok {
+ return errSpendOutputIDIsIncorrect
}
}
return nil
}
-func applyTransactions(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
+func decodeTxsOrders(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
deleteOrderMap := make(map[string]*common.Order)
addOrderMap := make(map[string]*common.Order)
for _, tx := range txs {
return addOrders, deleteOrders, nil
}
-/*
- @issue: if consensus node packed match transaction first then packed regular tx, this function's logic may make a valid block invalid
-*/
-func buildOrderTable(store database.MovStore, txs []*types.Tx) (*match.OrderTable, error) {
+func buildOrderBook(store database.MovStore, txs []*types.Tx) (*match.OrderBook, error) {
var nonMatchedTxs []*types.Tx
for _, tx := range txs {
if !common.IsMatchedTx(tx) {
arrivalDelOrders = append(arrivalDelOrders, delOrders...)
}
- return match.NewOrderTable(store, arrivalAddOrders, arrivalDelOrders), nil
-}
-
-func calcMatchedTxGasUsed(tx *types.Tx) int64 {
- return int64(len(tx.Inputs))*150 + int64(tx.SerializedSize)
+ return match.NewOrderBook(store, arrivalAddOrders, arrivalDelOrders), nil
}
func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
return orders, nil
}
-func getSortedTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
- assetMap := make(map[bc.AssetID]bc.AssetID)
- var firstTradePair *common.TradePair
+func getTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
+ var tradePairs []*common.TradePair
for _, tx := range tx.Inputs {
contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
if err != nil {
return nil, err
}
- assetMap[tx.AssetID()] = contractArgs.RequestedAsset
- if firstTradePair == nil {
- firstTradePair = &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset}
- }
- }
-
- tradePairs := []*common.TradePair{firstTradePair}
- for tradePair := firstTradePair; *tradePair.ToAssetID != *firstTradePair.FromAssetID; {
- nextTradePairToAssetID, ok := assetMap[*tradePair.ToAssetID]
- if !ok {
- return nil, errInvalidTradePairs
- }
-
- tradePair = &common.TradePair{FromAssetID: tradePair.ToAssetID, ToAssetID: &nextTradePairToAssetID}
- tradePairs = append(tradePairs, tradePair)
- }
-
- if len(tradePairs) != len(tx.Inputs) {
- return nil, errInvalidTradePairs
+ tradePairs = append(tradePairs, &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset})
}
return tradePairs, nil
}