OSDN Git Service

final code review (#484)
[bytom/vapor.git] / application / mov / mov_core.go
index 9e9923d..f9b1292 100644 (file)
@@ -1,15 +1,15 @@
 package mov
 
 import (
-       "github.com/vapor/application/mov/common"
-       "github.com/vapor/application/mov/contract"
-       "github.com/vapor/application/mov/database"
-       "github.com/vapor/application/mov/match"
-       "github.com/vapor/consensus/segwit"
-       dbm "github.com/vapor/database/leveldb"
-       "github.com/vapor/errors"
-       "github.com/vapor/protocol/bc"
-       "github.com/vapor/protocol/bc/types"
+       "github.com/bytom/vapor/application/mov/common"
+       "github.com/bytom/vapor/application/mov/contract"
+       "github.com/bytom/vapor/application/mov/database"
+       "github.com/bytom/vapor/application/mov/match"
+       "github.com/bytom/vapor/consensus/segwit"
+       dbm "github.com/bytom/vapor/database/leveldb"
+       "github.com/bytom/vapor/errors"
+       "github.com/bytom/vapor/protocol/bc"
+       "github.com/bytom/vapor/protocol/bc/types"
 )
 
 const maxFeeRate = 0.05
@@ -23,10 +23,9 @@ var (
        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")
-       errNumeratorOfRatioIsOverflow    = errors.New("ratio numerator of contract args product input amount is overflow")
-       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,
@@ -54,15 +53,13 @@ func (m *MovCore) ApplyBlock(block *types.Block) error {
                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
        }
@@ -70,49 +67,21 @@ func (m *MovCore) ApplyBlock(block *types.Block) error {
        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
@@ -128,11 +97,11 @@ func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
 // 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
        }
@@ -176,15 +145,13 @@ func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResul
        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
                }
@@ -198,7 +165,7 @@ func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) erro
                        return errStatusFailMustFalse
                }
 
-               if err := validateMagneticContractArgs(output.AssetAmount().Amount, output.ControlProgram()); err != nil {
+               if err := validateMagneticContractArgs(output.AssetAmount(), output.ControlProgram()); err != nil {
                        return err
                }
        }
@@ -222,17 +189,21 @@ func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error
        return nil
 }
 
-func validateMagneticContractArgs(fromAmount uint64, program []byte) error {
+func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte) error {
        contractArgs, err := segwit.DecodeP2WMCProgram(program)
        if err != nil {
                return err
        }
 
+       if *fromAssetAmount.AssetId == contractArgs.RequestedAsset {
+               return errInvalidTradePairs
+       }
+
        if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
                return errRatioOfTradeLessThanZero
        }
 
-       if match.CalcRequestAmount(fromAmount, contractArgs) < 1 {
+       if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs) < 1 {
                return errRequestAmountMath
        }
        return nil
@@ -284,60 +255,71 @@ func validateMatchedTxFeeAmount(tx *types.Tx) error {
        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 {
@@ -364,10 +346,7 @@ func applyTransactions(txs []*types.Tx) ([]*common.Order, []*common.Order, error
        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) {
@@ -391,11 +370,7 @@ func buildOrderTable(store database.MovStore, txs []*types.Tx) (*match.OrderTabl
                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) {
@@ -432,34 +407,15 @@ func getDeleteOrdersFromTx(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
 }