OSDN Git Service

mov core
authorshenao78 <shenao.78@163.com>
Wed, 23 Oct 2019 13:02:25 +0000 (21:02 +0800)
committershenao78 <shenao.78@163.com>
Wed, 23 Oct 2019 13:02:25 +0000 (21:02 +0800)
application/mov/match/match.go
application/mov/mov_core.go [new file with mode: 0644]

index c09ec88..a5340c3 100644 (file)
@@ -150,17 +150,17 @@ func addMatchTxOutput(txData *types.TxData, txInput *types.TxInput, order *commo
 }
 
 func (e *Engine) addMatchTxFeeOutput(txData *types.TxData) error {
-       feeAssetAmountMap, err := CalcFeeFromMatchedTx(txData)
+       txFee, err := CalcMatchedTxFee(txData)
        if err != nil {
                return err
        }
 
-       for feeAssetID, amount := range feeAssetAmountMap {
+       for feeAssetID, amount := range txFee {
                var reminder int64 = 0
-               feeAmount := amount.payableFeeAmount
-               if amount.payableFeeAmount > amount.maxFeeAmount {
-                       feeAmount = amount.maxFeeAmount
-                       reminder = amount.payableFeeAmount - amount.maxFeeAmount
+               feeAmount := amount.FeeAmount
+               if amount.FeeAmount > amount.MaxFeeAmount {
+                       feeAmount = amount.MaxFeeAmount
+                       reminder = amount.FeeAmount - amount.MaxFeeAmount
                }
                txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, uint64(feeAmount), e.nodeProgram))
 
@@ -222,25 +222,26 @@ func getOppositeIndex(size int, selfIdx int) int {
        return oppositeIdx
 }
 
-type feeAmount struct {
-       maxFeeAmount     int64
-       payableFeeAmount int64
+type MatchedTxFee struct {
+       MaxFeeAmount int64
+       FeeAmount    int64
 }
 
-func CalcFeeFromMatchedTx(txData *types.TxData) (map[bc.AssetID]*feeAmount, error) {
-       assetAmountMap := make(map[bc.AssetID]*feeAmount)
-       for _, input := range txData.Inputs {
-               assetAmountMap[input.AssetID()] = &feeAmount{}
-       }
+func CalcMatchedTxFee(txData *types.TxData) (map[bc.AssetID]*MatchedTxFee, error) {
+       assetFeeMap := make(map[bc.AssetID]*MatchedTxFee)
+       sellerProgramMap := make(map[string]bool)
+       assetInputMap := make(map[bc.AssetID]uint64)
 
-       receiveOutputMap := make(map[string]*types.TxOutput)
-       for _, output := range txData.Outputs {
-               // minus the amount of the re-order
-               if segwit.IsP2WMCScript(output.ControlProgram()) {
-                       assetAmountMap[*output.AssetAmount().AssetId].payableFeeAmount -= int64(output.AssetAmount().Amount)
-               } else {
-                       receiveOutputMap[hex.EncodeToString(output.ControlProgram())] = output
+       for _, input := range txData.Inputs {
+               assetFeeMap[input.AssetID()] = &MatchedTxFee{}
+               assetFeeMap[input.AssetID()].FeeAmount += int64(input.AssetAmount().Amount)
+               contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
+               if err != nil {
+                       return nil, err
                }
+
+               sellerProgramMap[hex.EncodeToString(contractArgs.SellerProgram)] = true
+               assetInputMap[input.AssetID()] = input.Amount()
        }
 
        for _, input := range txData.Inputs {
@@ -249,22 +250,28 @@ func CalcFeeFromMatchedTx(txData *types.TxData) (map[bc.AssetID]*feeAmount, erro
                        return nil, err
                }
 
-               assetAmountMap[input.AssetID()].payableFeeAmount += int64(input.AssetAmount().Amount)
-               receiveOutput, ok := receiveOutputMap[hex.EncodeToString(contractArgs.SellerProgram)]
-               if !ok {
-                       return nil, errors.New("the input of matched tx has no receive output")
-               }
+               oppositeAmount := assetInputMap[contractArgs.RequestedAsset]
+               receiveAmount := vprMath.MinUint64(calcRequestAmount(input.Amount(), contractArgs), oppositeAmount)
+               assetFeeMap[input.AssetID()].MaxFeeAmount = CalcMaxFeeAmount(CalcShouldPayAmount(receiveAmount, contractArgs))
+       }
 
-               assetAmountMap[*receiveOutput.AssetAmount().AssetId].payableFeeAmount -= int64(receiveOutput.AssetAmount().Amount)
-               assetAmountMap[input.AssetID()].maxFeeAmount = CalcMaxFeeAmount(CalcShouldPayAmount(receiveOutput.AssetAmount().Amount, contractArgs))
+       for _, output := range txData.Outputs {
+               // minus the amount of the re-order
+               if segwit.IsP2WMCScript(output.ControlProgram()) {
+                       assetFeeMap[*output.AssetAmount().AssetId].FeeAmount -= int64(output.AssetAmount().Amount)
+               }
+               // minus the amount of seller's receiving output
+               if _, ok := sellerProgramMap[hex.EncodeToString(output.ControlProgram())]; ok {
+                       assetFeeMap[*output.AssetAmount().AssetId].FeeAmount -= int64(output.AssetAmount().Amount)
+               }
        }
 
-       for assetID, amount := range assetAmountMap {
-               if amount.payableFeeAmount == 0 {
-                       delete(assetAmountMap, assetID)
+       for assetID, amount := range assetFeeMap {
+               if amount.FeeAmount == 0 {
+                       delete(assetFeeMap, assetID)
                }
        }
-       return assetAmountMap, nil
+       return assetFeeMap, nil
 }
 
 func calcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
diff --git a/application/mov/mov_core.go b/application/mov/mov_core.go
new file mode 100644 (file)
index 0000000..c73c2e7
--- /dev/null
@@ -0,0 +1,379 @@
+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"
+       "github.com/vapor/errors"
+       "github.com/vapor/math/checked"
+       "github.com/vapor/protocol/bc"
+       "github.com/vapor/protocol/bc/types"
+)
+
+var (
+       errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
+)
+
+type MovCore struct {
+       movStore database.MovStore
+}
+
+func NewMovCore(store database.MovStore) *MovCore {
+       return &MovCore{movStore: store}
+}
+
+// ChainStatus return the current block height and block hash in dex core
+func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
+       state, err := m.movStore.GetMovDatabaseState()
+       if err != nil {
+               return 0, nil, err
+       }
+
+       return state.Height, state.Hash, nil
+}
+
+func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
+       return m.ValidateTxs(block.Transactions, verifyResults)
+}
+
+// ValidateTxs validate the trade transaction.
+func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error {
+       for _, tx := range txs {
+               if isMatchedTx(tx) {
+                       if err := validateMatchedTx(tx); err != nil {
+                               return err
+                       }
+               }
+
+               if isCancelOrderTx(tx) {
+                       if err := validateCancelOrderTx(tx); err != nil {
+                               return err
+                       }
+               }
+
+               for _, output := range tx.Outputs {
+                       if !segwit.IsP2WMCScript(output.ControlProgram()) {
+                               continue
+                       }
+
+                       if err := validateMagneticContractArgs(output.AssetAmount().Amount, output.ControlProgram()); err != nil {
+                               return err
+                       }
+               }
+       }
+       return nil
+}
+
+func validateMatchedTx(tx *types.Tx) error {
+       fromAssetIDMap := make(map[string]bool)
+       toAssetIDMap := make(map[string]bool)
+       for i, input := range tx.Inputs {
+               if !segwit.IsP2WMCScript(input.ControlProgram()) {
+                       return errors.New("input program of matched tx must p2wmc script")
+               }
+
+               if contract.IsCancelClauseSelector(input) {
+                       return errors.New("can't exist cancel order in the matched transaction")
+               }
+
+               if err := validateMagneticContractArgs(input.AssetAmount().Amount, input.ControlProgram()); err != nil {
+                       return err
+               }
+
+               order, err := common.NewOrderFromInput(tx, i)
+               if err != nil {
+                       return err
+               }
+
+               fromAssetIDMap[order.FromAssetID.String()] = true
+               toAssetIDMap[order.ToAssetID.String()] = true
+       }
+
+       if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
+               return errors.New("asset id must unique in matched transaction")
+       }
+
+       return validateMatchedTxFeeAmount(tx)
+}
+
+func validateCancelOrderTx(tx *types.Tx) error {
+       for _, input := range tx.Inputs {
+               if !segwit.IsP2WMCScript(input.ControlProgram()) {
+                       return errors.New("input program of cancel order tx must p2wmc script")
+               }
+
+               if contract.IsTradeClauseSelector(input) {
+                       return errors.New("can't exist trade order in the cancel order transaction")
+               }
+
+               if err := validateMagneticContractArgs(input.AssetAmount().Amount, input.ControlProgram()); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+func validateMatchedTxFeeAmount(tx *types.Tx) error {
+       txFee, err := match.CalcMatchedTxFee(&tx.TxData)
+       if err != nil {
+               return err
+       }
+
+       for _, amount := range txFee {
+               if amount.FeeAmount > amount.MaxFeeAmount {
+                       return errors.New("amount of fee greater than max fee amount")
+               }
+       }
+       return nil
+}
+
+func validateMagneticContractArgs(inputAmount uint64, program []byte) error {
+       contractArgs, err := segwit.DecodeP2WMCProgram(program)
+       if err != nil {
+               return err
+       }
+
+       if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
+               return errors.New("ratio arguments must greater than zero")
+       }
+
+       if _, ok := checked.MulInt64(int64(inputAmount), contractArgs.RatioNumerator); !ok {
+               return errors.New("ratio numerator of contract args product input amount is overflow")
+       }
+       return nil
+}
+
+// ApplyBlock parse pending order and cancel from the the transactions of block
+// and add pending order to the dex db, remove cancel order from dex db.
+func (m *MovCore) ApplyBlock(block *types.Block) error {
+       if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
+               return err
+       }
+
+       addOrders, deleteOrders, err := applyTransactions(block.Transactions)
+       if err != nil {
+               return err
+       }
+
+       return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
+}
+
+func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx, ) error {
+       matchEngine := match.NewEngine(m.movStore, nil)
+       for _, matchedTx := range txs {
+               if !isMatchedTx(matchedTx) {
+                       continue
+               }
+
+               tradePairs, err := getSortedTradePairsFromMatchedTx(matchedTx)
+               if err != nil {
+                       return err
+               }
+
+               actualMatchedTx, err := matchEngine.NextMatchedTx(tradePairs...)
+               if err != nil {
+                       return err
+               }
+
+               if len(matchedTx.Inputs) != len(actualMatchedTx.Inputs) {
+                       return errors.New("length of matched tx input is not equals to actual matched tx input")
+               }
+
+               spendOutputIDs := make(map[string]bool)
+               for _, input := range matchedTx.Inputs {
+                       spendOutputID, err := input.SpentOutputID()
+                       if err != nil {
+                               return err
+                       }
+
+                       spendOutputIDs[spendOutputID.String()] = true
+               }
+
+               for _, input := range actualMatchedTx.Inputs {
+                       spendOutputID, err := input.SpentOutputID()
+                       if err != nil {
+                               return err
+                       }
+
+                       if _, ok := spendOutputIDs[spendOutputID.String()]; !ok {
+                               return errors.New("spend output id of matched tx is not equals to actual matched tx")
+                       }
+               }
+       }
+       return nil
+}
+
+func getSortedTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
+       assetMap := make(map[bc.AssetID]bc.AssetID)
+       for _, tx := range tx.Inputs {
+               contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
+               if err != nil {
+                       return nil, err
+               }
+
+               assetMap[tx.AssetID()] = contractArgs.RequestedAsset
+       }
+
+       firstTradePair := &common.TradePair{}
+       tradePairs := []*common.TradePair{firstTradePair}
+       tradePair := firstTradePair
+       for *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
+       }
+       return tradePairs, nil
+}
+
+// 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 {
+       deleteOrders, addOrders, err := applyTransactions(block.Transactions)
+       if err != nil {
+               return err
+       }
+
+       return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
+}
+
+// BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
+func (m *MovCore) BeforeProposalBlock(capacity int, nodeProgram []byte) ([]*types.Tx, error) {
+       matchEngine := match.NewEngine(m.movStore, nodeProgram)
+       tradePairMap := make(map[string]bool)
+       tradePairIterator := database.NewTradePairIterator(m.movStore)
+       remainder := capacity
+
+       var packagedTxs []*types.Tx
+       for remainder > 0 && tradePairIterator.HasNext() {
+               tradePair := tradePairIterator.Next()
+               if tradePairMap[tradePair.Key()] {
+                       continue
+               }
+               tradePairMap[tradePair.Key()] = true
+               tradePairMap[tradePair.Reverse().Key()] = true
+
+               for matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) && remainder > 0 {
+                       matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       if matchedTx == nil {
+                               break
+                       }
+                       packagedTxs = append(packagedTxs, matchedTx)
+                       remainder--
+               }
+       }
+       return packagedTxs, nil
+}
+
+func (m *MovCore) IsDust(tx *types.Tx) bool {
+       return false
+}
+
+func applyTransactions(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
+       deleteOrderMap := make(map[string]*common.Order)
+       addOrderMap := make(map[string]*common.Order)
+       for _, tx := range txs {
+               addOrders, err := getAddOrdersFromTx(tx)
+               if err != nil {
+                       return nil, nil, err
+               }
+
+               for _, order := range addOrders {
+                       addOrderMap[order.Key()] = order
+               }
+
+               deleteOrders, err := getDeleteOrdersFromTx(tx)
+               if err != nil {
+                       return nil, nil, err
+               }
+
+               for _, order := range deleteOrders {
+                       deleteOrderMap[order.Key()] = order
+               }
+       }
+
+       addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
+       return addOrders, deleteOrders, nil
+}
+
+func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
+       var deleteOrders, addOrders []*common.Order
+       for orderID, order := range addOrderMap {
+               if _, ok := deleteOrderMap[orderID]; ok {
+                       delete(deleteOrderMap, orderID)
+                       continue
+               }
+               addOrders = append(addOrders, order)
+       }
+
+       for _, order := range deleteOrderMap {
+               deleteOrders = append(deleteOrders, order)
+       }
+       return addOrders, deleteOrders
+}
+
+func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
+       var orders []*common.Order
+       for i, output := range tx.Outputs {
+               if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
+                       continue
+               }
+
+               order, err := common.NewOrderFromOutput(tx, i)
+               if err != nil {
+                       return nil, err
+               }
+
+               orders = append(orders, order)
+       }
+       return orders, nil
+}
+
+func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
+       var orders []*common.Order
+       for i, input := range tx.Inputs {
+               if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
+                       continue
+               }
+
+               order, err := common.NewOrderFromInput(tx, i)
+               if err != nil {
+                       return nil, err
+               }
+
+               orders = append(orders, order)
+       }
+       return orders, nil
+}
+
+func isMatchedTx(tx *types.Tx) bool {
+       p2wmCount := 0
+       for _, input := range tx.Inputs {
+               if input.InputType() == types.SpendInputType && contract.IsTradeClauseSelector(input) && segwit.IsP2WMCScript(input.ControlProgram()) {
+                       p2wmCount++
+               }
+       }
+       return p2wmCount >= 2
+}
+
+func isCancelOrderTx(tx *types.Tx) bool {
+       for _, input := range tx.Inputs {
+               if input.InputType() == types.SpendInputType && contract.IsCancelClauseSelector(input) && segwit.IsP2WMCScript(input.ControlProgram()) {
+                       return true
+               }
+       }
+       return false
+}