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
+ }
+
+ return nil
+ }
+
+ 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)
+}
+
+/*
+ @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) ([]*types.Tx, int64, error) {
+ if blockHeight <= m.startBlockHeight {
+ return nil, 0, nil
+ }
+
+ orderTable, err := buildOrderTable(m.movStore, txs)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ matchEngine := match.NewEngine(orderTable, maxFeeRate, nodeProgram)
+ tradePairMap := make(map[string]bool)
+ tradePairIterator := database.NewTradePairIterator(m.movStore)
+
+ var packagedTxs []*types.Tx
+ for gasLeft > 0 && tradePairIterator.HasNext() {
+ tradePair := tradePairIterator.Next()
+ if tradePairMap[tradePair.Key()] {
+ continue
+ }
+ tradePairMap[tradePair.Key()] = true
+ tradePairMap[tradePair.Reverse().Key()] = true
+
+ for gasLeft > 0 && matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) {
+ matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
+ if err != nil {
+ return nil, 0, err
+ }
+
+ gasUsed := calcMatchedTxGasUsed(matchedTx)
+ if gasLeft-gasUsed >= 0 {
+ packagedTxs = append(packagedTxs, matchedTx)
+ }
+ gasLeft -= gasUsed
+ }
+ }
+ return packagedTxs, gasLeft, nil
}
// ChainStatus return the current block height and block hash in dex core
return state.Height, state.Hash, 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 {
+ if block.Height <= m.startBlockHeight {
+ return nil
+ }
+
+ deleteOrders, addOrders, err := applyTransactions(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"
+}
+
// 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 nil
}
+// ValidateTxs 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 nil
}
-func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
- if verifyResult.StatusFail {
- return errStatusFailMustFalse
- }
-
- fromAssetIDMap := make(map[string]bool)
- toAssetIDMap := make(map[string]bool)
- for i, input := range tx.Inputs {
- if !segwit.IsP2WMCScript(input.ControlProgram()) {
- return errInputProgramMustP2WMCScript
- }
-
- if contract.IsCancelClauseSelector(input) {
- return errExistCancelOrderInMatchedTx
- }
-
- 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 errAssetIDMustUniqueInMatchedTx
- }
-
- return validateMatchedTxFeeAmount(tx)
-}
-
func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
if verifyResult.StatusFail {
return errStatusFailMustFalse
return nil
}
-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
-}
-
func validateMagneticContractArgs(inputAmount uint64, program []byte) error {
contractArgs, err := segwit.DecodeP2WMCProgram(program)
if err != nil {
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 block.Height < m.startBlockHeight {
- return nil
+func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
+ if verifyResult.StatusFail {
+ return errStatusFailMustFalse
}
- if block.Height == m.startBlockHeight {
- blockHash := block.Hash()
- if err := m.movStore.InitDBState(block.Height, &blockHash); err != nil {
+ fromAssetIDMap := make(map[string]bool)
+ toAssetIDMap := make(map[string]bool)
+ for i, input := range tx.Inputs {
+ if !segwit.IsP2WMCScript(input.ControlProgram()) {
+ return errInputProgramMustP2WMCScript
+ }
+
+ if contract.IsCancelClauseSelector(input) {
+ return errExistCancelOrderInMatchedTx
+ }
+
+ order, err := common.NewOrderFromInput(tx, i)
+ if err != nil {
return err
}
- return nil
+ fromAssetIDMap[order.FromAssetID.String()] = true
+ toAssetIDMap[order.ToAssetID.String()] = true
}
- if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
- return err
+ if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
+ return errAssetIDMustUniqueInMatchedTx
}
- addOrders, deleteOrders, err := applyTransactions(block.Transactions)
+ return validateMatchedTxFeeAmount(tx)
+}
+
+func validateMatchedTxFeeAmount(tx *types.Tx) error {
+ txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
if err != nil {
return err
}
- return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
+ for _, amount := range txFee {
+ if amount.FeeAmount > amount.MaxFeeAmount {
+ return errAmountOfFeeGreaterThanMaximum
+ }
+ }
+ 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 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())
+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, err
- }
-
- assetMap[tx.AssetID()] = contractArgs.RequestedAsset
- if firstTradePair == nil {
- firstTradePair = &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset}
+ return nil, nil, 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 {
+ addOrderMap[order.Key()] = order
}
- 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 {
- if block.Height <= m.startBlockHeight {
- return nil
- }
-
- 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(txs []*types.Tx, nodeProgram []byte, blockHeight uint64, gasLeft int64) ([]*types.Tx, int64, error) {
- if blockHeight <= m.startBlockHeight {
- return nil, 0, nil
- }
-
- orderTable, err := buildOrderTable(m.movStore, txs)
- if err != nil {
- return nil, 0, err
- }
-
- matchEngine := match.NewEngine(orderTable, maxFeeRate, nodeProgram)
- tradePairMap := make(map[string]bool)
- tradePairIterator := database.NewTradePairIterator(m.movStore)
-
- var packagedTxs []*types.Tx
- for gasLeft > 0 && tradePairIterator.HasNext() {
- tradePair := tradePairIterator.Next()
- if tradePairMap[tradePair.Key()] {
- continue
+ deleteOrders, err := getDeleteOrdersFromTx(tx)
+ if err != nil {
+ return nil, nil, err
}
- tradePairMap[tradePair.Key()] = true
- tradePairMap[tradePair.Reverse().Key()] = true
-
- for gasLeft > 0 && matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) {
- matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
- if err != nil {
- return nil, 0, err
- }
- gasUsed := calcMatchedTxGasUsed(matchedTx)
- if gasLeft - gasUsed >= 0 {
- packagedTxs = append(packagedTxs, matchedTx)
- }
- gasLeft -= gasUsed
+ for _, order := range deleteOrders {
+ deleteOrderMap[order.Key()] = order
}
}
- return packagedTxs, gasLeft, nil
-}
-func calcMatchedTxGasUsed(tx *types.Tx) int64 {
- return int64(len(tx.Inputs)) * 150 + int64(tx.SerializedSize)
+ addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
+ 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) {
var nonMatchedTxs []*types.Tx
for _, tx := range txs {
return match.NewOrderTable(store, arrivalAddOrders, arrivalDelOrders), 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
- }
- }
- 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 calcMatchedTxGasUsed(tx *types.Tx) int64 {
+ return int64(len(tx.Inputs))*150 + int64(tx.SerializedSize)
}
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
+ 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
+ }
+ 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
+}
"github.com/vapor/testutil"
)
+/*
+ @addTest:BeforeProposalBlock: will gas affect generate tx? will be packed tx affect generate tx?
+ @addTest:TestApplyBlock: one block has two different trade pairs & different trade pair won't affect each order(attach & detach)
+ @addTest:TestApplyBlock: node packed maker tx and match transaction in random order(attach & detach)
+ @addTest:TestValidateBlock: one tx has trade input and cancel input mixed
+ @addTest:TestValidateBlock: regular match transaction's seller program is also a P2WMCProgram
+*/
func TestApplyBlock(t *testing.T) {
initBlockHeader := &types.BlockHeader{Height: 1, PreviousBlockHash: bc.Hash{}}
cases := []struct {
},
},
verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}},
- wantError: nil,
+ wantError: nil,
},
{
desc: "block only has matched tx",
},
},
verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
- wantError: nil,
+ wantError: nil,
},
{
desc: "block has maker tx and matched tx",
},
},
verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
- wantError: nil,
+ wantError: nil,
},
{
desc: "status fail of maker tx is true",
},
},
verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
- wantError: errStatusFailMustFalse,
+ wantError: errStatusFailMustFalse,
},
{
desc: "status fail of matched tx is true",
},
},
verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
- wantError: errStatusFailMustFalse,
+ wantError: errStatusFailMustFalse,
},
{
desc: "asset id in matched tx is not unique",
},
},
verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
- wantError: errAssetIDMustUniqueInMatchedTx,
+ wantError: errAssetIDMustUniqueInMatchedTx,
},
{
desc: "common input in the matched tx",
},
},
verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
- wantError: errInputProgramMustP2WMCScript,
+ wantError: errInputProgramMustP2WMCScript,
},
{
desc: "cancel order in the matched tx",
},
},
verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
- wantError: errExistCancelOrderInMatchedTx,
+ wantError: errExistCancelOrderInMatchedTx,
},
{
desc: "common input in the cancel order tx",
},
},
verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
- wantError: errInputProgramMustP2WMCScript,
+ wantError: errInputProgramMustP2WMCScript,
},
{
desc: "amount of fee greater than max fee amount",
},
},
verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
- wantError: errAmountOfFeeGreaterThanMaximum,
+ wantError: errAmountOfFeeGreaterThanMaximum,
},
{
desc: "ratio numerator is zero",
},
},
verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
- wantError: errRatioOfTradeLessThanZero,
+ wantError: errRatioOfTradeLessThanZero,
},
{
desc: "ratio denominator is zero",
},
},
verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
- wantError: errRatioOfTradeLessThanZero,
+ wantError: errRatioOfTradeLessThanZero,
},
{
desc: "ratio numerator product input amount is overflow",
},
},
verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
- wantError: errNumeratorOfRatioIsOverflow,
+ wantError: errNumeratorOfRatioIsOverflow,
},
}