RatioDenominator int64
}
+// Rate return the exchange represented by float64
func (o *Order) Rate() float64 {
if o.RatioDenominator == 0 {
return 0
}
- rate := big.NewFloat(0).SetInt64(o.RatioNumerator)
- rate.Quo(rate, big.NewFloat(0).SetInt64(o.RatioDenominator))
+ rate := big.NewRat(o.RatioNumerator, o.RatioDenominator)
result, _ := rate.Float64()
return result
}
+// Cmp compares x and y and returns -1 if x < y, 0 if x == y, +1 if x > y
func (o *Order) Cmp(other *Order) int {
rate := big.NewRat(o.RatioNumerator, o.RatioDenominator)
otherRate := big.NewRat(other.RatioNumerator, other.RatioDenominator)
return false
}
for _, input := range tx.Inputs {
- if input.InputType() == types.SpendInputType && contract.IsTradeClauseSelector(input) && segwit.IsP2WMCScript(input.ControlProgram()) {
+ if input.InputType() == types.SpendInputType && segwit.IsP2WMCScript(input.ControlProgram()) && contract.IsTradeClauseSelector(input) {
return true
}
}
// IsCancelOrderTx check if this transaction has cancel mov order input
func IsCancelOrderTx(tx *types.Tx) bool {
for _, input := range tx.Inputs {
- if input.InputType() == types.SpendInputType && contract.IsCancelClauseSelector(input) && segwit.IsP2WMCScript(input.ControlProgram()) {
+ if input.InputType() == types.SpendInputType && segwit.IsP2WMCScript(input.ControlProgram()) && contract.IsCancelClauseSelector(input) {
return true
}
}
orderPrefix = append(orderPrefix, orderAfter.ToAssetID.Bytes()...)
var startKey []byte
-
if orderAfter.Rate() > 0 {
startKey = calcOrderKey(orderAfter.FromAssetID, orderAfter.ToAssetID, orderAfter.UTXOHash(), orderAfter.Rate())
}
return nil
}
+// CalcRequestAmount is from amount * numerator / ratioDenominator
func CalcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
res := big.NewInt(0).SetUint64(fromAmount)
res.Mul(res, big.NewInt(contractArg.RatioNumerator)).Quo(res, big.NewInt(contractArg.RatioDenominator))
return (selfIdx + 1) % size
}
+// IsMatched check does the orders can be exchange
func IsMatched(orders []*common.Order) bool {
sortedOrders := sortOrders(orders)
if len(sortedOrders) == 0 {
}
func sortOrders(orders []*common.Order) []*common.Order {
+ if len(orders) == 0 {
+ return nil
+ }
+
orderMap := make(map[bc.AssetID]*common.Order)
firstOrder := orders[0]
for i := 1; i < len(orders); i++ {
arrivalDelOrders *sync.Map
}
+func arrangeArrivalAddOrders(orders []*common.Order) *sync.Map {
+ orderMap := make(map[string][]*common.Order)
+ for _, order := range orders {
+ orderMap[order.TradePair().Key()] = append(orderMap[order.TradePair().Key()], order)
+ }
+
+ arrivalOrderMap := &sync.Map{}
+ for key, orders := range orderMap {
+ sort.Sort(sort.Reverse(common.OrderSlice(orders)))
+ arrivalOrderMap.Store(key, orders)
+ }
+ return arrivalOrderMap
+}
+
+func arrangeArrivalDelOrders(orders []*common.Order) *sync.Map {
+ arrivalDelOrderMap := &sync.Map{}
+ for _, order := range orders {
+ arrivalDelOrderMap.Store(order.Key(), order)
+ }
+ return arrivalDelOrderMap
+}
+
// NewOrderBook create a new OrderBook object
func NewOrderBook(movStore database.MovStore, arrivalAddOrders, arrivalDelOrders []*common.Order) *OrderBook {
return &OrderBook{
}
}
-// AddOrder add the in memory temp order to order table
+// AddOrder add the in memory temp order to order table, because temp order is what left for the
+// partial trade order, so the price should be lowest.
func (o *OrderBook) AddOrder(order *common.Order) error {
tradePairKey := order.TradePair().Key()
orders := o.getArrivalAddOrders(tradePairKey)
return nil
}
+// DelOrder mark the order has been deleted in order book
+func (o *OrderBook) DelOrder(order *common.Order) {
+ o.arrivalDelOrders.Store(order.Key(), order)
+}
+
// PeekOrder return the next lowest order of given trade pair
func (o *OrderBook) PeekOrder(tradePair *common.TradePair) *common.Order {
if len(o.getDBOrders(tradePair.Key())) == 0 {
orders := o.getDBOrders(tradePair.Key())
if len(orders) != 0 && orders[len(orders)-1].Key() == order.Key() {
- o.dbOrders.Store(tradePair.Key(), orders[0 : len(orders)-1])
+ o.dbOrders.Store(tradePair.Key(), orders[0:len(orders)-1])
+ return
}
arrivalOrders := o.getArrivalAddOrders(tradePair.Key())
if len(arrivalOrders) != 0 && arrivalOrders[len(arrivalOrders)-1].Key() == order.Key() {
- o.arrivalAddOrders.Store(tradePair.Key(), arrivalOrders[0 : len(arrivalOrders)-1])
+ o.arrivalAddOrders.Store(tradePair.Key(), arrivalOrders[0:len(arrivalOrders)-1])
}
}
return nil
}
-func arrangeArrivalAddOrders(orders []*common.Order) *sync.Map {
- orderMap := make(map[string][]*common.Order)
- for _, order := range orders {
- orderMap[order.TradePair().Key()] = append(orderMap[order.TradePair().Key()], order)
- }
-
- arrivalOrderMap := &sync.Map{}
- for key, orders := range orderMap {
- sort.Sort(sort.Reverse(common.OrderSlice(orders)))
- arrivalOrderMap.Store(key, orders)
-
- }
- return arrivalOrderMap
-}
-
-func arrangeArrivalDelOrders(orders []*common.Order) *sync.Map {
- arrivalDelOrderMap := &sync.Map{}
- for _, order := range orders {
- arrivalDelOrderMap.Store(order.Key(), order)
- }
- return arrivalDelOrderMap
-}
-
func (o *OrderBook) extendDBOrders(tradePair *common.TradePair) {
iterator, ok := o.orderIterators.Load(tradePair.Key())
if !ok {
}
func (o *OrderBook) peekArrivalOrder(tradePair *common.TradePair) *common.Order {
- if arrivalAddOrders := o.getArrivalAddOrders(tradePair.Key()); len(arrivalAddOrders) > 0 {
- return arrivalAddOrders[len(arrivalAddOrders)-1]
+ orders := o.getArrivalAddOrders(tradePair.Key())
+ delPos := len(orders)
+ for i := delPos - 1; i >= 0; i-- {
+ if o.getArrivalDelOrders(orders[i].Key()) != nil {
+ delPos = i
+ } else {
+ break
+ }
+ }
+
+ if delPos < len(orders) {
+ orders = orders[:delPos]
+ o.arrivalAddOrders.Store(tradePair.Key(), orders)
+ }
+
+ if size := len(orders); size > 0 {
+ return orders[size-1]
}
return nil
}
"github.com/bytom/vapor/application/mov/common"
"github.com/bytom/vapor/application/mov/database"
"github.com/bytom/vapor/application/mov/mock"
+ "github.com/bytom/vapor/testutil"
)
var (
}
if gotOrder.Key() != wantOrder.Key() {
- t.Errorf("#%d(%s):the key of got order(%v) is not equals key of want order(%v)", i, c.desc, gotOrder, wantOrder)
+ t.Fatalf("#%d(%s):the key of got order(%v) is not equals key of want order(%v)", i, c.desc, gotOrder, wantOrder)
}
}
}
}
+
+func TestPeekArrivalOrder(t *testing.T) {
+ cases := []struct {
+ desc string
+ initArrivalAddOrders []*common.Order
+ initArrivalDelOrders []*common.Order
+ peekTradePair *common.TradePair
+ wantArrivalAddOrders []*common.Order
+ wantOrder *common.Order
+ }{
+ {
+ desc: "empty peek",
+ initArrivalAddOrders: []*common.Order{},
+ initArrivalDelOrders: []*common.Order{},
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{},
+ wantOrder: nil,
+ },
+ {
+ desc: "1 element regular peek",
+ initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ initArrivalDelOrders: []*common.Order{},
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ wantOrder: mock.Btc2EthOrders[0],
+ },
+ {
+ desc: "4 element regular peek with",
+ initArrivalAddOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ },
+ initArrivalDelOrders: []*common.Order{},
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ },
+ wantOrder: mock.Btc2EthOrders[3],
+ },
+ {
+ desc: "1 element peek with 1 unrelated deleted order",
+ initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[1]},
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ wantOrder: mock.Btc2EthOrders[0],
+ },
+ {
+ desc: "1 element peek with 1 related deleted order",
+ initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{},
+ wantOrder: nil,
+ },
+ {
+ desc: "4 element peek with first 3 deleted order",
+ initArrivalAddOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ },
+ initArrivalDelOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ },
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[1]},
+ wantOrder: mock.Btc2EthOrders[1],
+ },
+ {
+ desc: "4 element peek with first 1 deleted order",
+ initArrivalAddOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ },
+ initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3]},
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+ },
+ wantOrder: mock.Btc2EthOrders[0],
+ },
+ {
+ desc: "4 element peek with first 2th deleted order",
+ initArrivalAddOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ },
+ initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ },
+ wantOrder: mock.Btc2EthOrders[3],
+ },
+ }
+
+ for i, c := range cases {
+ orderBook := NewOrderBook(mock.NewMovStore(nil, nil), c.initArrivalAddOrders, c.initArrivalDelOrders)
+ gotOrder := orderBook.PeekOrder(c.peekTradePair)
+ if !testutil.DeepEqual(gotOrder, c.wantOrder) {
+ t.Fatalf("#%d(%s):the key of got order(%v) is not equals key of want order(%v)", i, c.desc, gotOrder, c.wantOrder)
+ }
+
+ wantAddOrders, _ := arrangeArrivalAddOrders(c.wantArrivalAddOrders).Load(c.peekTradePair.Key())
+ gotAddOrders := orderBook.getArrivalAddOrders(c.peekTradePair.Key())
+ if !testutil.DeepEqual(gotAddOrders, wantAddOrders) {
+ t.Fatalf("#%d(%s): the got arrivalAddOrders(%v) is differnt than want arrivalAddOrders(%v)", i, c.desc, gotAddOrders, wantAddOrders)
+ }
+ }
+}
gasLeft int64
isTimeout func() bool
- workerNum int
- workerNumCh chan int
- processCh chan *matchTxResult
- tradePairCh chan *common.TradePair
- closeCh chan struct{}
+ workerNum int
+ endWorkCh chan int
+ tradePairCh chan *common.TradePair
+ matchResultCh chan *matchTxResult
+ closeCh chan struct{}
}
type matchTxResult struct {
return &matchCollector{
engine: engine,
tradePairIterator: iterator,
+ gasLeft: gasLeft,
+ isTimeout: isTimeout,
workerNum: workerNum,
- workerNumCh: make(chan int, workerNum),
- processCh: make(chan *matchTxResult),
+ endWorkCh: make(chan int, workerNum),
tradePairCh: make(chan *common.TradePair, workerNum),
+ matchResultCh: make(chan *matchTxResult),
closeCh: make(chan struct{}),
- gasLeft: gasLeft,
- isTimeout: isTimeout,
}
}
defer close(m.closeCh)
var matchedTxs []*types.Tx
- completed := 0
- for !m.isTimeout() {
+ for completed := 0; !m.isTimeout(); {
select {
- case data := <-m.processCh:
+ case data := <-m.matchResultCh:
if data.err != nil {
return nil, data.err
}
} else {
return matchedTxs, nil
}
- case <-m.workerNumCh:
+ case <-m.endWorkCh:
if completed++; completed == m.workerNum {
return matchedTxs, nil
}
func (m *matchCollector) matchTxWorker(wg *sync.WaitGroup) {
defer func() {
- m.workerNumCh <- 1
+ m.endWorkCh <- 1
wg.Done()
}()
case <-m.closeCh:
return
case tradePair := <-m.tradePairCh:
+ // end worker due to all trade pair has been matched
if tradePair == nil {
return
}
+
for m.engine.HasMatchedTx(tradePair, tradePair.Reverse()) {
matchedTx, err := m.engine.NextMatchedTx(tradePair, tradePair.Reverse())
- data := &matchTxResult{matchedTx: matchedTx, err: err}
select {
case <-m.closeCh:
return
- case m.processCh <- data:
- if data.err != nil {
+ case m.matchResultCh <- &matchTxResult{matchedTx: matchedTx, err: err}:
+ if err != nil {
return
}
}
}
}
-
}
}
+
+func calcMatchedTxGasUsed(tx *types.Tx) int64 {
+ return int64(len(tx.Inputs))*150 + int64(tx.SerializedSize)
+}
types.NewIntraChainOutput(BTC, 10, testutil.MustDecodeHexString("00144d0dfc8a0c5ce41d31d4f61d99aff70588bff8bc")),
},
}),
+
+ // partial matched transaction from MatchedTxs[4], Eth2BtcMakerTxs[0]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, testutil.MustDecodeHash("ed810e1672c3b9de27a1db23e017e6b9cc23334b6e3dbd25dfe8857e289b7f06"), *Btc2EthOrders[0].FromAssetID, 2, 1, Btc2EthOrders[0].Utxo.ControlProgram),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Eth2BtcMakerTxs[0], 0).Utxo.SourceID, *Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, 0, Eth2BtcOrders[0].Utxo.ControlProgram),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 100, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 2, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
+ // re-order
+ types.NewIntraChainOutput(*Eth2BtcOrders[0].FromAssetID, 404, Eth2BtcOrders[0].Utxo.ControlProgram),
+ },
+ }),
}
)
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)
+ addOrders, deleteOrders, err := decodeTxsOrders(block.Transactions)
if err != nil {
return err
}
// 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 {
+ if block.Height < m.startBlockHeight {
return nil
}
- deleteOrders, addOrders, err := applyTransactions(block.Transactions)
+ deleteOrders, addOrders, err := decodeTxsOrders(block.Transactions)
if err != nil {
return err
}
return nil
}
-// ValidateTxs validate one transaction.
+// 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
}
- }
-
- if common.IsCancelOrderTx(tx) {
+ } else if common.IsCancelOrderTx(tx) {
if err := validateCancelOrderTx(tx, verifyResult); err != nil {
return err
}
}
func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
- var matchedTxs []*types.Tx
+ orderBook := match.NewOrderBook(m.movStore, nil, nil)
for _, tx := range txs {
if common.IsMatchedTx(tx) {
- matchedTxs = append(matchedTxs, tx)
- }
- }
-
- if len(matchedTxs) == 0 {
- return nil
- }
+ tradePairs, err := getTradePairsFromMatchedTx(tx)
+ if err != nil {
+ return err
+ }
- orderBook, err := buildOrderBook(m.movStore, txs)
- if err != nil {
- return err
- }
+ orders := orderBook.PeekOrders(tradePairs)
+ if err := validateSpendOrders(tx, orders); err != nil {
+ return err
+ }
- for _, matchedTx := range matchedTxs {
- tradePairs, err := getTradePairsFromMatchedTx(matchedTx)
- if err != nil {
- return err
- }
+ orderBook.PopOrders(tradePairs)
+ } else if common.IsCancelOrderTx(tx) {
+ orders, err := getDeleteOrdersFromTx(tx)
+ if err != nil {
+ return err
+ }
- orders := orderBook.PeekOrders(tradePairs)
- if !match.IsMatched(orders) {
- return errNotMatchedOrder
+ for _, order := range orders {
+ orderBook.DelOrder(order)
+ }
}
- if err := validateSpendOrders(matchedTx, orders); err != nil {
+ addOrders, err := getAddOrdersFromTx(tx)
+ if err != nil {
return err
}
- orderBook.PopOrders(tradePairs)
-
- for i, output := range matchedTx.Outputs {
- if !segwit.IsP2WMCScript(output.ControlProgram()) {
- continue
- }
-
- order, err := common.NewOrderFromOutput(matchedTx, i)
- if err != nil {
- return err
- }
-
+ for _, order := range addOrders {
if err := orderBook.AddOrder(order); err != nil {
return err
}
return nil
}
-func validateSpendOrders(matchedTx *types.Tx, orders []*common.Order) error {
+func validateSpendOrders(tx *types.Tx, orders []*common.Order) error {
+ if len(tx.Inputs) != len(orders) {
+ return errNotMatchedOrder
+ }
+
spendOutputIDs := make(map[string]bool)
- for _, input := range matchedTx.Inputs {
+ for _, input := range tx.Inputs {
spendOutputID, err := input.SpentOutputID()
if err != nil {
return err
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 match.NewOrderBook(store, arrivalAddOrders, arrivalDelOrders), nil
}
-func calcMatchedTxGasUsed(tx *types.Tx) int64 {
- return int64(len(tx.Inputs))*150 + int64(tx.SerializedSize)
-}
-
func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
var orders []*common.Order
for i, output := range tx.Outputs {
Transactions: []*types.Tx{
mock.Eos2EtcMakerTxs[0],
mock.Btc2EthMakerTxs[0],
+ mock.Eth2BtcMakerTxs[1],
mock.MatchedTxs[4],
mock.Eth2EosMakerTxs[0],
- mock.Eth2BtcMakerTxs[1],
- mock.MatchedTxs[5],
mock.Etc2EosMakerTxs[0],
+ mock.MatchedTxs[5],
},
},
blockFunc: applyBlock,
wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
},
{
+ desc: "apply block has partial matched transaction chain",
+ block: &types.Block{
+ BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+ Transactions: []*types.Tx{
+ mock.Btc2EthMakerTxs[0],
+ mock.Eth2BtcMakerTxs[1],
+ mock.MatchedTxs[4],
+ mock.Eth2BtcMakerTxs[0],
+ mock.MatchedTxs[7],
+ },
+ },
+ blockFunc: applyBlock,
+ initOrders: []*common.Order{},
+ wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[7], 2)},
+ wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+ },
+ {
desc: "detach block has pending order transaction",
block: &types.Block{
BlockHeader: *initBlockHeader,
b.gasLeft -= gasState.GasUsed
return nil
}
+
func (b *blockBuilder) applyTransactions(txs []*types.Tx, timeoutStatus uint8) error {
tempTxs := []*types.Tx{}
for i := 0; i < len(txs); i++ {
if err = builder.AddInput(types.NewCoinbaseInput(arbitrary), &txbuilder.SigningInstruction{}); err != nil {
return nil, err
}
+
if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, 0, script)); err != nil {
return nil, err
}
func preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoViewpoint, gasLeft int64) ([]*validateTxResult, int64) {
var results []*validateTxResult
-
bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: chain.BestBlockHeight() + 1}}
bcTxs := make([]*bc.Tx, len(txs))
for i, tx := range txs {