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"
15 const maxFeeRate = 0.05
18 errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
22 movStore database.MovStore
25 func NewMovCore(store database.MovStore) *MovCore {
26 return &MovCore{movStore: store}
29 // ChainStatus return the current block height and block hash in dex core
30 func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
31 state, err := m.movStore.GetMovDatabaseState()
36 return state.Height, state.Hash, nil
39 func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
40 return m.ValidateTxs(block.Transactions, verifyResults)
43 // ValidateTxs validate the trade transaction.
44 func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error {
45 for _, tx := range txs {
46 if common.IsMatchedTx(tx) {
47 if err := validateMatchedTx(tx); err != nil {
52 if common.IsCancelOrderTx(tx) {
53 if err := validateCancelOrderTx(tx); err != nil {
58 for _, output := range tx.Outputs {
59 if !segwit.IsP2WMCScript(output.ControlProgram()) {
63 if err := validateMagneticContractArgs(output.AssetAmount().Amount, output.ControlProgram()); err != nil {
71 func validateMatchedTx(tx *types.Tx) error {
72 fromAssetIDMap := make(map[string]bool)
73 toAssetIDMap := make(map[string]bool)
74 for i, input := range tx.Inputs {
75 if !segwit.IsP2WMCScript(input.ControlProgram()) {
76 return errors.New("input program of matched tx must p2wmc script")
79 if contract.IsCancelClauseSelector(input) {
80 return errors.New("can't exist cancel order in the matched transaction")
83 order, err := common.NewOrderFromInput(tx, i)
88 fromAssetIDMap[order.FromAssetID.String()] = true
89 toAssetIDMap[order.ToAssetID.String()] = true
92 if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
93 return errors.New("asset id must unique in matched transaction")
96 return validateMatchedTxFeeAmount(tx)
99 func validateCancelOrderTx(tx *types.Tx) error {
100 for _, input := range tx.Inputs {
101 if !segwit.IsP2WMCScript(input.ControlProgram()) {
102 return errors.New("input program of cancel order tx must p2wmc script")
105 if contract.IsTradeClauseSelector(input) {
106 return errors.New("can't exist trade order in the cancel order transaction")
112 func validateMatchedTxFeeAmount(tx *types.Tx) error {
113 txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
118 for _, amount := range txFee {
119 if amount.FeeAmount > amount.MaxFeeAmount {
120 return errors.New("amount of fee greater than max fee amount")
126 func validateMagneticContractArgs(inputAmount uint64, program []byte) error {
127 contractArgs, err := segwit.DecodeP2WMCProgram(program)
132 if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
133 return errors.New("ratio arguments must greater than zero")
136 if _, ok := checked.MulInt64(int64(inputAmount), contractArgs.RatioNumerator); !ok {
137 return errors.New("ratio numerator of contract args product input amount is overflow")
142 // ApplyBlock parse pending order and cancel from the the transactions of block
143 // and add pending order to the dex db, remove cancel order from dex db.
144 func (m *MovCore) ApplyBlock(block *types.Block) error {
145 if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
149 addOrders, deleteOrders, err := applyTransactions(block.Transactions)
154 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
157 func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx, ) error {
158 matchEngine := match.NewEngine(m.movStore, maxFeeRate, nil)
159 for _, matchedTx := range txs {
160 if !common.IsMatchedTx(matchedTx) {
164 tradePairs, err := getSortedTradePairsFromMatchedTx(matchedTx)
169 actualMatchedTx, err := matchEngine.NextMatchedTx(tradePairs...)
174 if len(matchedTx.Inputs) != len(actualMatchedTx.Inputs) {
175 return errors.New("length of matched tx input is not equals to actual matched tx input")
178 spendOutputIDs := make(map[string]bool)
179 for _, input := range matchedTx.Inputs {
180 spendOutputID, err := input.SpentOutputID()
185 spendOutputIDs[spendOutputID.String()] = true
188 for _, input := range actualMatchedTx.Inputs {
189 spendOutputID, err := input.SpentOutputID()
194 if _, ok := spendOutputIDs[spendOutputID.String()]; !ok {
195 return errors.New("spend output id of matched tx is not equals to actual matched tx")
202 func getSortedTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
203 assetMap := make(map[bc.AssetID]bc.AssetID)
204 var firstTradePair *common.TradePair
205 for _, tx := range tx.Inputs {
206 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
211 assetMap[tx.AssetID()] = contractArgs.RequestedAsset
212 if firstTradePair == nil {
213 firstTradePair = &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset}
217 tradePairs := []*common.TradePair{firstTradePair}
218 for tradePair := firstTradePair; *tradePair.ToAssetID != *firstTradePair.FromAssetID; {
219 nextTradePairToAssetID, ok := assetMap[*tradePair.ToAssetID]
221 return nil, errInvalidTradePairs
224 tradePair = &common.TradePair{FromAssetID: tradePair.ToAssetID, ToAssetID: &nextTradePairToAssetID}
225 tradePairs = append(tradePairs, tradePair)
228 if len(tradePairs) != len(tx.Inputs) {
229 return nil, errInvalidTradePairs
231 return tradePairs, nil
234 // DetachBlock parse pending order and cancel from the the transactions of block
235 // and add cancel order to the dex db, remove pending order from dex db.
236 func (m *MovCore) DetachBlock(block *types.Block) error {
237 deleteOrders, addOrders, err := applyTransactions(block.Transactions)
242 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
245 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
246 func (m *MovCore) BeforeProposalBlock(capacity int, nodeProgram []byte) ([]*types.Tx, error) {
247 matchEngine := match.NewEngine(m.movStore, maxFeeRate, nodeProgram)
248 tradePairMap := make(map[string]bool)
249 tradePairIterator := database.NewTradePairIterator(m.movStore)
251 var packagedTxs []*types.Tx
252 for len(packagedTxs) < capacity && tradePairIterator.HasNext() {
253 tradePair := tradePairIterator.Next()
254 if tradePairMap[tradePair.Key()] {
257 tradePairMap[tradePair.Key()] = true
258 tradePairMap[tradePair.Reverse().Key()] = true
260 for len(packagedTxs) < capacity && matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) {
261 matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
266 packagedTxs = append(packagedTxs, matchedTx)
269 return packagedTxs, nil
272 func (m *MovCore) IsDust(tx *types.Tx) bool {
273 for _, input := range tx.Inputs {
274 if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
281 func applyTransactions(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
282 deleteOrderMap := make(map[string]*common.Order)
283 addOrderMap := make(map[string]*common.Order)
284 for _, tx := range txs {
285 addOrders, err := getAddOrdersFromTx(tx)
290 for _, order := range addOrders {
291 addOrderMap[order.Key()] = order
294 deleteOrders, err := getDeleteOrdersFromTx(tx)
299 for _, order := range deleteOrders {
300 deleteOrderMap[order.Key()] = order
304 addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
305 return addOrders, deleteOrders, nil
308 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
309 var deleteOrders, addOrders []*common.Order
310 for orderID, order := range addOrderMap {
311 if _, ok := deleteOrderMap[orderID]; ok {
312 delete(deleteOrderMap, orderID)
315 addOrders = append(addOrders, order)
318 for _, order := range deleteOrderMap {
319 deleteOrders = append(deleteOrders, order)
321 return addOrders, deleteOrders
324 func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
325 var orders []*common.Order
326 for i, output := range tx.Outputs {
327 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
331 order, err := common.NewOrderFromOutput(tx, i)
336 orders = append(orders, order)
341 func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
342 var orders []*common.Order
343 for i, input := range tx.Inputs {
344 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
348 order, err := common.NewOrderFromInput(tx, i)
353 orders = append(orders, order)