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/math/checked"
- "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
var (
errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
errStatusFailMustFalse = errors.New("status fail of transaction does not allow to be true")
- errInputProgramMustP2WMCScript = errors.New("input program of matched tx must p2wmc script")
+ errInputProgramMustP2WMCScript = errors.New("input program of trade tx must p2wmc script")
errExistCancelOrderInMatchedTx = errors.New("can't exist cancel order in the matched transaction")
errExistTradeInCancelOrderTx = errors.New("can't exist trade in the cancel order transaction")
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,
// verify the match transaction in block is correct, and update the order table according to the transaction.
type MovCore struct {
- movStore database.MovStore
+ movStore database.MovStore
+ startBlockHeight uint64
}
// NewMovCore return a instance of MovCore by path of mov db
-func NewMovCore(dbBackend, dbDir string) *MovCore {
+func NewMovCore(dbBackend, dbDir string, startBlockHeight uint64) *MovCore {
movDB := dbm.NewDB("mov", dbBackend, dbDir)
- return &MovCore{movStore: database.NewLevelDBMovStore(movDB)}
+ return &MovCore{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
}
-// Name return the name of current module
-func (m *MovCore) Name() string {
- return "MOV"
+// 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 block.Height < m.startBlockHeight {
+ return nil
+ }
+
+ if block.Height == m.startBlockHeight {
+ blockHash := block.Hash()
+ if err := m.movStore.InitDBState(block.Height, &blockHash); err != nil {
+ return err
+ }
+ }
+
+ if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
+ return err
+ }
+
+ addOrders, deleteOrders, err := decodeTxsOrders(block.Transactions)
+ if err != nil {
+ return err
+ }
+
+ return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
}
-// InitChainStatus init the start block height and start block hash, this method only needs to be called once in the initialization
-func (m *MovCore) InitChainStatus(blockHeight uint64, blockHash *bc.Hash) error {
- return m.movStore.InitDBState(blockHeight, blockHash)
+// 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
+ }
+
+ orderBook, err := buildOrderBook(m.movStore, txs)
+ if err != nil {
+ return nil, err
+ }
+
+ matchEngine := match.NewEngine(orderBook, maxFeeRate, nodeProgram)
+ tradePairIterator := database.NewTradePairIterator(m.movStore)
+ matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout)
+ return matchCollector.result()
}
// ChainStatus return the current block height and block hash in dex core
return state.Height, state.Hash, nil
}
-// ValidateBlock no need to verify the block header, becaure the first module has been verified.
+// 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 {
+ return nil
+ }
+
+ deleteOrders, addOrders, err := decodeTxsOrders(block.Transactions)
+ if err != nil {
+ return err
+ }
+
+ return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
+}
+
+// IsDust block the transaction that are not generated by the match engine
+func (m *MovCore) IsDust(tx *types.Tx) bool {
+ for _, input := range tx.Inputs {
+ if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
+ return true
+ }
+ }
+ return false
+}
+
+// Name return the name of current module
+func (m *MovCore) Name() string {
+ return "MOV"
+}
+
+// StartHeight return the start block height of current module
+func (m *MovCore) StartHeight() uint64 {
+ return m.startBlockHeight
+}
+
+// ValidateBlock no need to verify the block header, because the first module has been verified.
// just need to verify the transactions in the block.
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 i, tx := range txs {
- if common.IsMatchedTx(tx) {
- if err := validateMatchedTx(tx, verifyResults[i]); err != nil {
- return err
- }
- }
-
- if common.IsCancelOrderTx(tx) {
- if err := validateCancelOrderTx(tx, verifyResults[i]); err != nil {
- return err
- }
- }
-
- for _, output := range tx.Outputs {
- if !segwit.IsP2WMCScript(output.ControlProgram()) {
- continue
- }
- if verifyResults[i].StatusFail {
- return errStatusFailMustFalse
- }
-
- if err := validateMagneticContractArgs(output.AssetAmount().Amount, output.ControlProgram()); err != nil {
- return err
- }
+ if err := m.ValidateTx(tx, verifyResults[i]); err != nil {
+ return err
}
}
return nil
}
-func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
- if verifyResult.StatusFail {
- return errStatusFailMustFalse
+// 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
+ }
+ } else if common.IsCancelOrderTx(tx) {
+ if err := validateCancelOrderTx(tx, verifyResult); err != nil {
+ return err
+ }
}
- fromAssetIDMap := make(map[string]bool)
- toAssetIDMap := make(map[string]bool)
- for i, input := range tx.Inputs {
- if !segwit.IsP2WMCScript(input.ControlProgram()) {
- return errInputProgramMustP2WMCScript
+ for _, output := range tx.Outputs {
+ if !segwit.IsP2WMCScript(output.ControlProgram()) {
+ continue
}
-
- if contract.IsCancelClauseSelector(input) {
- return errExistCancelOrderInMatchedTx
+ if verifyResult.StatusFail {
+ return errStatusFailMustFalse
}
- order, err := common.NewOrderFromInput(tx, i)
- if err != nil {
+ if err := validateMagneticContractArgs(output.AssetAmount(), output.ControlProgram()); 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 errAssetIDMustUniqueInMatchedTx
}
-
- return validateMatchedTxFeeAmount(tx)
+ return nil
}
func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
return nil
}
-func validateMatchedTxFeeAmount(tx *types.Tx) error {
- txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
+func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte) error {
+ contractArgs, err := segwit.DecodeP2WMCProgram(program)
if err != nil {
return err
}
- for _, amount := range txFee {
- if amount.FeeAmount > amount.MaxFeeAmount {
- return errAmountOfFeeGreaterThanMaximum
- }
- }
- return nil
-}
-
-func validateMagneticContractArgs(inputAmount uint64, 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 _, ok := checked.MulInt64(int64(inputAmount), contractArgs.RatioNumerator); !ok {
- return errNumeratorOfRatioIsOverflow
+ if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs) < 1 {
+ return errRequestAmountMath
}
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
+func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
+ if verifyResult.StatusFail {
+ return errStatusFailMustFalse
}
- return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
-}
-
-func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
- matchEngine := match.NewEngine(m.movStore, maxFeeRate, nil)
- for _, matchedTx := range txs {
- if !common.IsMatchedTx(matchedTx) {
- continue
+ fromAssetIDMap := make(map[string]bool)
+ toAssetIDMap := make(map[string]bool)
+ for i, input := range tx.Inputs {
+ if !segwit.IsP2WMCScript(input.ControlProgram()) {
+ return errInputProgramMustP2WMCScript
}
- tradePairs, err := getSortedTradePairsFromMatchedTx(matchedTx)
- if err != nil {
- return err
+ if contract.IsCancelClauseSelector(input) {
+ return errExistCancelOrderInMatchedTx
}
- actualMatchedTx, err := matchEngine.NextMatchedTx(tradePairs...)
+ order, err := common.NewOrderFromInput(tx, i)
if err != nil {
return err
}
- if len(matchedTx.Inputs) != len(actualMatchedTx.Inputs) {
- return errLengthOfInputIsIncorrect
+ fromAssetIDMap[order.FromAssetID.String()] = true
+ toAssetIDMap[order.ToAssetID.String()] = true
+ }
+
+ if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
+ return errAssetIDMustUniqueInMatchedTx
+ }
+
+ return validateMatchedTxFeeAmount(tx)
+}
+
+func validateMatchedTxFeeAmount(tx *types.Tx) error {
+ txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
+ if err != nil {
+ return err
+ }
+
+ for _, amount := range txFee {
+ if amount.FeeAmount > amount.MaxFeeAmount {
+ return errAmountOfFeeGreaterThanMaximum
}
+ }
+ return nil
+}
- spendOutputIDs := make(map[string]bool)
- for _, input := range matchedTx.Inputs {
- spendOutputID, err := input.SpentOutputID()
+func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
+ orderBook := match.NewOrderBook(m.movStore, nil, nil)
+ for _, tx := range txs {
+ if common.IsMatchedTx(tx) {
+ tradePairs, err := getTradePairsFromMatchedTx(tx)
if err != nil {
return err
}
- spendOutputIDs[spendOutputID.String()] = true
- }
+ orders := orderBook.PeekOrders(tradePairs)
+ if err := validateSpendOrders(tx, orders); err != nil {
+ return err
+ }
- for _, input := range actualMatchedTx.Inputs {
- spendOutputID, err := input.SpentOutputID()
+ orderBook.PopOrders(tradePairs)
+ } else if common.IsCancelOrderTx(tx) {
+ orders, err := getDeleteOrdersFromTx(tx)
if err != nil {
return err
}
- if _, ok := spendOutputIDs[spendOutputID.String()]; !ok {
- return errSpendOutputIDIsIncorrect
+ for _, order := range orders {
+ orderBook.DelOrder(order)
}
}
- }
- return nil
-}
-func getSortedTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
- assetMap := make(map[bc.AssetID]bc.AssetID)
- var firstTradePair *common.TradePair
- for _, tx := range tx.Inputs {
- contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
+ addOrders, err := getAddOrdersFromTx(tx)
if err != nil {
- return nil, err
- }
-
- assetMap[tx.AssetID()] = contractArgs.RequestedAsset
- if firstTradePair == nil {
- firstTradePair = &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset}
+ return err
}
- }
- tradePairs := []*common.TradePair{firstTradePair}
- for tradePair := firstTradePair; *tradePair.ToAssetID != *firstTradePair.FromAssetID; {
- nextTradePairToAssetID, ok := assetMap[*tradePair.ToAssetID]
- if !ok {
- return nil, errInvalidTradePairs
+ for _, order := range addOrders {
+ if err := orderBook.AddOrder(order); err != nil {
+ return err
+ }
}
-
- tradePair = &common.TradePair{FromAssetID: tradePair.ToAssetID, ToAssetID: &nextTradePairToAssetID}
- tradePairs = append(tradePairs, tradePair)
}
-
- if len(tradePairs) != len(tx.Inputs) {
- return nil, errInvalidTradePairs
- }
- return tradePairs, nil
+ return 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
+func validateSpendOrders(tx *types.Tx, orders []*common.Order) error {
+ if len(tx.Inputs) != len(orders) {
+ return errNotMatchedOrder
}
- 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, maxFeeRate, nodeProgram)
- tradePairMap := make(map[string]bool)
- tradePairIterator := database.NewTradePairIterator(m.movStore)
-
- var packagedTxs []*types.Tx
- for len(packagedTxs) < capacity && tradePairIterator.HasNext() {
- tradePair := tradePairIterator.Next()
- if tradePairMap[tradePair.Key()] {
- continue
+ spendOutputIDs := make(map[string]bool)
+ for _, input := range tx.Inputs {
+ spendOutputID, err := input.SpentOutputID()
+ if err != nil {
+ return err
}
- tradePairMap[tradePair.Key()] = true
- tradePairMap[tradePair.Reverse().Key()] = true
- for len(packagedTxs) < capacity && matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) {
- matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
- if err != nil {
- return nil, err
- }
-
- packagedTxs = append(packagedTxs, matchedTx)
- }
+ spendOutputIDs[spendOutputID.String()] = true
}
- return packagedTxs, nil
-}
-// IsDust block the transaction that are not generated by the match engine
-func (m *MovCore) IsDust(tx *types.Tx) bool {
- for _, input := range tx.Inputs {
- if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
- return true
+ for _, order := range orders {
+ outputID := order.UTXOHash().String()
+ if _, ok := spendOutputIDs[outputID]; !ok {
+ return errSpendOutputIDIsIncorrect
}
}
- return false
+ 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
}
-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
+func buildOrderBook(store database.MovStore, txs []*types.Tx) (*match.OrderBook, error) {
+ var nonMatchedTxs []*types.Tx
+ for _, tx := range txs {
+ if !common.IsMatchedTx(tx) {
+ nonMatchedTxs = append(nonMatchedTxs, tx)
}
- addOrders = append(addOrders, order)
}
- for _, order := range deleteOrderMap {
- deleteOrders = append(deleteOrders, order)
+ var arrivalAddOrders, arrivalDelOrders []*common.Order
+ for _, tx := range nonMatchedTxs {
+ addOrders, err := getAddOrdersFromTx(tx)
+ if err != nil {
+ return nil, err
+ }
+
+ delOrders, err := getDeleteOrdersFromTx(tx)
+ if err != nil {
+ return nil, err
+ }
+
+ arrivalAddOrders = append(arrivalAddOrders, addOrders...)
+ arrivalDelOrders = append(arrivalDelOrders, delOrders...)
}
- return addOrders, deleteOrders
+
+ return match.NewOrderBook(store, arrivalAddOrders, arrivalDelOrders), nil
}
func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
}
return orders, nil
}
+
+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
+ }
+
+ tradePairs = append(tradePairs, &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset})
+ }
+ return tradePairs, 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
+}