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 dbm "github.com/vapor/database/leveldb"
10 "github.com/vapor/errors"
11 "github.com/vapor/math/checked"
12 "github.com/vapor/protocol/bc"
13 "github.com/vapor/protocol/bc/types"
16 const maxFeeRate = 0.05
19 errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
23 movStore database.MovStore
26 func NewMovCore(dbBackend, dbDir string) *MovCore {
27 movDB := dbm.NewDB("mov", dbBackend, dbDir)
28 return &MovCore{movStore: database.NewLevelDBMovStore(movDB)}
31 func (m *MovCore) Name() string {
35 func (m *MovCore) InitChainStatus(blockHeight uint64, blockHash *bc.Hash) error {
36 return m.movStore.InitDBState(blockHeight, blockHash)
39 // ChainStatus return the current block height and block hash in dex core
40 func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
41 state, err := m.movStore.GetMovDatabaseState()
46 return state.Height, state.Hash, nil
49 func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
50 return m.ValidateTxs(block.Transactions, verifyResults)
53 // ValidateTxs validate the trade transaction.
54 func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error {
55 for _, tx := range txs {
56 if common.IsMatchedTx(tx) {
57 if err := validateMatchedTx(tx); err != nil {
62 if common.IsCancelOrderTx(tx) {
63 if err := validateCancelOrderTx(tx); err != nil {
68 for _, output := range tx.Outputs {
69 if !segwit.IsP2WMCScript(output.ControlProgram()) {
73 if err := validateMagneticContractArgs(output.AssetAmount().Amount, output.ControlProgram()); err != nil {
81 func validateMatchedTx(tx *types.Tx) error {
82 fromAssetIDMap := make(map[string]bool)
83 toAssetIDMap := make(map[string]bool)
84 for i, input := range tx.Inputs {
85 if !segwit.IsP2WMCScript(input.ControlProgram()) {
86 return errors.New("input program of matched tx must p2wmc script")
89 if contract.IsCancelClauseSelector(input) {
90 return errors.New("can't exist cancel order in the matched transaction")
93 order, err := common.NewOrderFromInput(tx, i)
98 fromAssetIDMap[order.FromAssetID.String()] = true
99 toAssetIDMap[order.ToAssetID.String()] = true
102 if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
103 return errors.New("asset id must unique in matched transaction")
106 return validateMatchedTxFeeAmount(tx)
109 func validateCancelOrderTx(tx *types.Tx) error {
110 for _, input := range tx.Inputs {
111 if !segwit.IsP2WMCScript(input.ControlProgram()) {
112 return errors.New("input program of cancel order tx must p2wmc script")
115 if contract.IsTradeClauseSelector(input) {
116 return errors.New("can't exist trade order in the cancel order transaction")
122 func validateMatchedTxFeeAmount(tx *types.Tx) error {
123 txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
128 for _, amount := range txFee {
129 if amount.FeeAmount > amount.MaxFeeAmount {
130 return errors.New("amount of fee greater than max fee amount")
136 func validateMagneticContractArgs(inputAmount uint64, program []byte) error {
137 contractArgs, err := segwit.DecodeP2WMCProgram(program)
142 if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
143 return errors.New("ratio arguments must greater than zero")
146 if _, ok := checked.MulInt64(int64(inputAmount), contractArgs.RatioNumerator); !ok {
147 return errors.New("ratio numerator of contract args product input amount is overflow")
152 // ApplyBlock parse pending order and cancel from the the transactions of block
153 // and add pending order to the dex db, remove cancel order from dex db.
154 func (m *MovCore) ApplyBlock(block *types.Block) error {
155 if block.Height <= m.startHeight {
159 if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
163 addOrders, deleteOrders, err := applyTransactions(block.Transactions)
168 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
171 func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx, ) error {
172 matchEngine := match.NewEngine(m.movStore, maxFeeRate, nil)
173 for _, matchedTx := range txs {
174 if !common.IsMatchedTx(matchedTx) {
178 tradePairs, err := getSortedTradePairsFromMatchedTx(matchedTx)
183 actualMatchedTx, err := matchEngine.NextMatchedTx(tradePairs...)
188 if len(matchedTx.Inputs) != len(actualMatchedTx.Inputs) {
189 return errors.New("length of matched tx input is not equals to actual matched tx input")
192 spendOutputIDs := make(map[string]bool)
193 for _, input := range matchedTx.Inputs {
194 spendOutputID, err := input.SpentOutputID()
199 spendOutputIDs[spendOutputID.String()] = true
202 for _, input := range actualMatchedTx.Inputs {
203 spendOutputID, err := input.SpentOutputID()
208 if _, ok := spendOutputIDs[spendOutputID.String()]; !ok {
209 return errors.New("spend output id of matched tx is not equals to actual matched tx")
216 func getSortedTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
217 assetMap := make(map[bc.AssetID]bc.AssetID)
218 var firstTradePair *common.TradePair
219 for _, tx := range tx.Inputs {
220 contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
225 assetMap[tx.AssetID()] = contractArgs.RequestedAsset
226 if firstTradePair == nil {
227 firstTradePair = &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset}
231 tradePairs := []*common.TradePair{firstTradePair}
232 for tradePair := firstTradePair; *tradePair.ToAssetID != *firstTradePair.FromAssetID; {
233 nextTradePairToAssetID, ok := assetMap[*tradePair.ToAssetID]
235 return nil, errInvalidTradePairs
238 tradePair = &common.TradePair{FromAssetID: tradePair.ToAssetID, ToAssetID: &nextTradePairToAssetID}
239 tradePairs = append(tradePairs, tradePair)
242 if len(tradePairs) != len(tx.Inputs) {
243 return nil, errInvalidTradePairs
245 return tradePairs, nil
248 // DetachBlock parse pending order and cancel from the the transactions of block
249 // and add cancel order to the dex db, remove pending order from dex db.
250 func (m *MovCore) DetachBlock(block *types.Block) error {
251 if block.Height <= m.startHeight {
255 deleteOrders, addOrders, err := applyTransactions(block.Transactions)
260 return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
263 // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
264 func (m *MovCore) BeforeProposalBlock(capacity int, nodeProgram []byte) ([]*types.Tx, error) {
265 matchEngine := match.NewEngine(m.movStore, maxFeeRate, nodeProgram)
266 tradePairMap := make(map[string]bool)
267 tradePairIterator := database.NewTradePairIterator(m.movStore)
269 var packagedTxs []*types.Tx
270 for len(packagedTxs) < capacity && tradePairIterator.HasNext() {
271 tradePair := tradePairIterator.Next()
272 if tradePairMap[tradePair.Key()] {
275 tradePairMap[tradePair.Key()] = true
276 tradePairMap[tradePair.Reverse().Key()] = true
278 for len(packagedTxs) < capacity && matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) {
279 matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
284 packagedTxs = append(packagedTxs, matchedTx)
287 return packagedTxs, nil
290 func (m *MovCore) IsDust(tx *types.Tx) bool {
291 for _, input := range tx.Inputs {
292 if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
299 func applyTransactions(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
300 deleteOrderMap := make(map[string]*common.Order)
301 addOrderMap := make(map[string]*common.Order)
302 for _, tx := range txs {
303 addOrders, err := getAddOrdersFromTx(tx)
308 for _, order := range addOrders {
309 addOrderMap[order.Key()] = order
312 deleteOrders, err := getDeleteOrdersFromTx(tx)
317 for _, order := range deleteOrders {
318 deleteOrderMap[order.Key()] = order
322 addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
323 return addOrders, deleteOrders, nil
326 func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
327 var deleteOrders, addOrders []*common.Order
328 for orderID, order := range addOrderMap {
329 if _, ok := deleteOrderMap[orderID]; ok {
330 delete(deleteOrderMap, orderID)
333 addOrders = append(addOrders, order)
336 for _, order := range deleteOrderMap {
337 deleteOrders = append(deleteOrders, order)
339 return addOrders, deleteOrders
342 func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
343 var orders []*common.Order
344 for i, output := range tx.Outputs {
345 if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
349 order, err := common.NewOrderFromOutput(tx, i)
354 orders = append(orders, order)
359 func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
360 var orders []*common.Order
361 for i, input := range tx.Inputs {
362 if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
366 order, err := common.NewOrderFromInput(tx, i)
371 orders = append(orders, order)