4 "github.com/vapor/application/mov/common"
5 "github.com/vapor/application/mov/contract"
6 "github.com/vapor/application/mov/database"
7 "github.com/vapor/application/mov/match"
8 "github.com/vapor/consensus/segwit"
9 "github.com/vapor/errors"
10 "github.com/vapor/math/checked"
11 "github.com/vapor/protocol/bc"
12 "github.com/vapor/protocol/bc/types"
16 errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
20 movStore database.MovStore
23 func NewMovCore(store database.MovStore) *MovCore {
24 return &MovCore{movStore: store}
27 // ChainStatus return the current block height and block hash in dex core
28 func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
29 state, err := m.movStore.GetMovDatabaseState()
34 return state.Height, state.Hash, nil
37 func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
38 return m.ValidateTxs(block.Transactions, verifyResults)
41 // ValidateTxs validate the trade transaction.
42 func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error {
43 for _, tx := range txs {
45 if err := validateMatchedTx(tx); err != nil {
50 if isCancelOrderTx(tx) {
51 if err := validateCancelOrderTx(tx); err != nil {
56 for _, output := range tx.Outputs {
57 if !segwit.IsP2WMCScript(output.ControlProgram()) {
61 if err := validateMagneticContractArgs(output.AssetAmount().Amount, output.ControlProgram()); err != nil {
69 func validateMatchedTx(tx *types.Tx) error {
70 fromAssetIDMap := make(map[string]bool)
71 toAssetIDMap := make(map[string]bool)
72 for i, input := range tx.Inputs {
73 if !segwit.IsP2WMCScript(input.ControlProgram()) {
74 return errors.New("input program of matched tx must p2wmc script")
77 if contract.IsCancelClauseSelector(input) {
78 return errors.New("can't exist cancel order in the matched transaction")
81 if err := validateMagneticContractArgs(input.AssetAmount().Amount, input.ControlProgram()); err != nil {
85 order, err := common.NewOrderFromInput(tx, i)
90 fromAssetIDMap[order.FromAssetID.String()] = true
91 toAssetIDMap[order.ToAssetID.String()] = true
94 if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
95 return errors.New("asset id must unique in matched transaction")
98 return validateMatchedTxFeeAmount(tx)
101 func validateCancelOrderTx(tx *types.Tx) error {
102 for _, input := range tx.Inputs {
103 if !segwit.IsP2WMCScript(input.ControlProgram()) {
104 return errors.New("input program of cancel order tx must p2wmc script")
107 if contract.IsTradeClauseSelector(input) {
108 return errors.New("can't exist trade order in the cancel order transaction")
111 if err := validateMagneticContractArgs(input.AssetAmount().Amount, input.ControlProgram()); err != nil {
118 func validateMatchedTxFeeAmount(tx *types.Tx) error {
119 txFee, err := match.CalcMatchedTxFee(&tx.TxData)
124 for _, amount := range txFee {
125 if amount.FeeAmount > amount.MaxFeeAmount {
126 return errors.New("amount of fee greater than max fee amount")
132 func validateMagneticContractArgs(inputAmount uint64, program []byte) error {
133 contractArgs, err := segwit.DecodeP2WMCProgram(program)
138 if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
139 return errors.New("ratio arguments must greater than zero")
142 if _, ok := checked.MulInt64(int64(inputAmount), contractArgs.RatioNumerator); !ok {
143 return errors.New("ratio numerator of contract args product input amount is overflow")
148 // ApplyBlock parse pending order and cancel from the the transactions of block
149 // and add pending order to the dex db, remove cancel order from dex db.
150 func (m *MovCore) ApplyBlock(block *types.Block) error {
151 if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
155 addOrders, deleteOrders, err := applyTransactions(block.Transactions)
160 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
163 func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx, ) error {
164 matchEngine := match.NewEngine(m.movStore, nil)
165 for _, matchedTx := range txs {
166 if !isMatchedTx(matchedTx) {
170 tradePairs, err := getSortedTradePairsFromMatchedTx(matchedTx)
175 actualMatchedTx, err := matchEngine.NextMatchedTx(tradePairs...)
180 if len(matchedTx.Inputs) != len(actualMatchedTx.Inputs) {
181 return errors.New("length of matched tx input is not equals to actual matched tx input")
184 spendOutputIDs := make(map[string]bool)
185 for _, input := range matchedTx.Inputs {
186 spendOutputID, err := input.SpentOutputID()
191 spendOutputIDs[spendOutputID.String()] = true
194 for _, input := range actualMatchedTx.Inputs {
195 spendOutputID, err := input.SpentOutputID()
200 if _, ok := spendOutputIDs[spendOutputID.String()]; !ok {
201 return errors.New("spend output id of matched tx is not equals to actual matched tx")
208 func getSortedTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
209 assetMap := make(map[bc.AssetID]bc.AssetID)
210 for _, tx := range tx.Inputs {
211 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
216 assetMap[tx.AssetID()] = contractArgs.RequestedAsset
219 firstTradePair := &common.TradePair{}
220 tradePairs := []*common.TradePair{firstTradePair}
221 tradePair := firstTradePair
222 for *tradePair.ToAssetID != *firstTradePair.FromAssetID {
223 nextTradePairToAssetID, ok := assetMap[*tradePair.ToAssetID]
225 return nil, errInvalidTradePairs
228 tradePair = &common.TradePair{FromAssetID: tradePair.ToAssetID, ToAssetID: &nextTradePairToAssetID}
229 tradePairs = append(tradePairs, tradePair)
232 if len(tradePairs) != len(tx.Inputs) {
233 return nil, errInvalidTradePairs
235 return tradePairs, nil
238 // DetachBlock parse pending order and cancel from the the transactions of block
239 // and add cancel order to the dex db, remove pending order from dex db.
240 func (m *MovCore) DetachBlock(block *types.Block) error {
241 deleteOrders, addOrders, err := applyTransactions(block.Transactions)
246 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
249 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
250 func (m *MovCore) BeforeProposalBlock(capacity int, nodeProgram []byte) ([]*types.Tx, error) {
251 matchEngine := match.NewEngine(m.movStore, nodeProgram)
252 tradePairMap := make(map[string]bool)
253 tradePairIterator := database.NewTradePairIterator(m.movStore)
254 remainder := capacity
256 var packagedTxs []*types.Tx
257 for remainder > 0 && tradePairIterator.HasNext() {
258 tradePair := tradePairIterator.Next()
259 if tradePairMap[tradePair.Key()] {
262 tradePairMap[tradePair.Key()] = true
263 tradePairMap[tradePair.Reverse().Key()] = true
265 for matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) && remainder > 0 {
266 matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
271 if matchedTx == nil {
274 packagedTxs = append(packagedTxs, matchedTx)
278 return packagedTxs, nil
281 func (m *MovCore) IsDust(tx *types.Tx) bool {
285 func applyTransactions(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
286 deleteOrderMap := make(map[string]*common.Order)
287 addOrderMap := make(map[string]*common.Order)
288 for _, tx := range txs {
289 addOrders, err := getAddOrdersFromTx(tx)
294 for _, order := range addOrders {
295 addOrderMap[order.Key()] = order
298 deleteOrders, err := getDeleteOrdersFromTx(tx)
303 for _, order := range deleteOrders {
304 deleteOrderMap[order.Key()] = order
308 addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
309 return addOrders, deleteOrders, nil
312 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
313 var deleteOrders, addOrders []*common.Order
314 for orderID, order := range addOrderMap {
315 if _, ok := deleteOrderMap[orderID]; ok {
316 delete(deleteOrderMap, orderID)
319 addOrders = append(addOrders, order)
322 for _, order := range deleteOrderMap {
323 deleteOrders = append(deleteOrders, order)
325 return addOrders, deleteOrders
328 func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
329 var orders []*common.Order
330 for i, output := range tx.Outputs {
331 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
335 order, err := common.NewOrderFromOutput(tx, i)
340 orders = append(orders, order)
345 func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
346 var orders []*common.Order
347 for i, input := range tx.Inputs {
348 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
352 order, err := common.NewOrderFromInput(tx, i)
357 orders = append(orders, order)
362 func isMatchedTx(tx *types.Tx) bool {
364 for _, input := range tx.Inputs {
365 if input.InputType() == types.SpendInputType && contract.IsTradeClauseSelector(input) && segwit.IsP2WMCScript(input.ControlProgram()) {
369 return p2wmCount >= 2
372 func isCancelOrderTx(tx *types.Tx) bool {
373 for _, input := range tx.Inputs {
374 if input.InputType() == types.SpendInputType && contract.IsCancelClauseSelector(input) && segwit.IsP2WMCScript(input.ControlProgram()) {