Utxo *MovUtxo
RatioNumerator int64
RatioDenominator int64
+ SellerProgram []byte
+ BlockHeight uint64
+ TxIndex uint64
}
// Rate return the exchange represented by float64
}
// NewOrderFromOutput convert txinput to order
-func NewOrderFromOutput(tx *types.Tx, outputIndex int) (*Order, error) {
+func NewOrderFromOutput(tx *types.Tx, outputIndex int, blockHeight, txIndex uint64) (*Order, error) {
outputID := tx.OutputID(outputIndex)
output, err := tx.IntraChainOutput(*outputID)
if err != nil {
ToAssetID: &contractArgs.RequestedAsset,
RatioNumerator: contractArgs.RatioNumerator,
RatioDenominator: contractArgs.RatioDenominator,
+ SellerProgram: contractArgs.SellerProgram,
+ BlockHeight: blockHeight,
+ TxIndex: txIndex,
Utxo: &MovUtxo{
SourceID: output.Source.Ref,
Amount: assetAmount.Amount,
ToAssetID: &contractArgs.RequestedAsset,
RatioNumerator: contractArgs.RatioNumerator,
RatioDenominator: contractArgs.RatioDenominator,
+ SellerProgram: contractArgs.SellerProgram,
Utxo: &MovUtxo{
SourceID: &input.SourceID,
Amount: input.Amount,
"math"
"github.com/bytom/vapor/application/mov/common"
+ "github.com/bytom/vapor/consensus/segwit"
dbm "github.com/bytom/vapor/database/leveldb"
"github.com/bytom/vapor/protocol/bc"
"github.com/bytom/vapor/protocol/bc/types"
Utxo *common.MovUtxo
RatioNumerator int64
RatioDenominator int64
+ BlockHeight uint64
+ TxIndex uint64
}
func calcOrderKey(fromAssetID, toAssetID *bc.AssetID, utxoHash *bc.Hash, rate float64) []byte {
return nil, err
}
+ contractArgs, err := segwit.DecodeP2WMCProgram(orderData.Utxo.ControlProgram)
+ if err != nil {
+ return nil, err
+ }
+
orders = append(orders, &common.Order{
FromAssetID: orderAfter.FromAssetID,
ToAssetID: orderAfter.ToAssetID,
Utxo: orderData.Utxo,
RatioNumerator: orderData.RatioNumerator,
RatioDenominator: orderData.RatioDenominator,
+ BlockHeight: orderData.BlockHeight,
+ TxIndex: orderData.TxIndex,
+ SellerProgram: contractArgs.SellerProgram,
})
}
return orders, nil
Utxo: order.Utxo,
RatioNumerator: order.RatioNumerator,
RatioDenominator: order.RatioDenominator,
+ BlockHeight: order.BlockHeight,
+ TxIndex: order.TxIndex,
}
data, err := json.Marshal(orderData)
if err != nil {
assetID6 = &bc.AssetID{V0: 6}
assetID7 = &bc.AssetID{V0: 7}
assetID8 = &bc.AssetID{V0: 8}
+
+ orderProgram = testutil.MustDecodeHexString("0020184e1cc4ee4845023888810a79eed7a42c02c544cf2c61ceac05e176d575bd4603ed4e0e0210272200204775b9e167e2c1ffe57ae3e5088af69e518be010529b5fdadf4be97656084eec20a3e21b55f44403884457166ad5847fdb5489512ba9611eee466efb9f94319143")
+ sellerProgram = testutil.MustDecodeHexString("00204775b9e167e2c1ffe57ae3e5088af69e518be010529b5fdadf4be97656084eec")
mockOrders = []*common.Order{
&common.Order{
SourceID: &bc.Hash{V0: 21},
Amount: 1,
SourcePos: 0,
- ControlProgram: []byte("aa"),
+ ControlProgram: orderProgram,
},
+ SellerProgram: sellerProgram,
},
&common.Order{
FromAssetID: assetID1,
SourceID: &bc.Hash{V0: 22},
Amount: 1,
SourcePos: 0,
- ControlProgram: []byte("aa"),
+ ControlProgram: orderProgram,
},
+ SellerProgram: sellerProgram,
},
&common.Order{
FromAssetID: assetID1,
SourceID: &bc.Hash{V0: 23},
Amount: 1,
SourcePos: 0,
- ControlProgram: []byte("aa"),
+ ControlProgram: orderProgram,
},
+ SellerProgram: sellerProgram,
},
&common.Order{
FromAssetID: assetID1,
SourceID: &bc.Hash{V0: 13},
Amount: 1,
SourcePos: 0,
- ControlProgram: []byte("aa"),
+ ControlProgram: orderProgram,
},
+ SellerProgram: sellerProgram,
},
&common.Order{
FromAssetID: assetID1,
SourceID: &bc.Hash{V0: 24},
Amount: 10,
SourcePos: 1,
- ControlProgram: []byte("aa"),
+ ControlProgram: orderProgram,
},
+ SellerProgram: sellerProgram,
},
&common.Order{
FromAssetID: assetID1,
SourceID: &bc.Hash{V0: 24},
Amount: 1,
SourcePos: 0,
- ControlProgram: []byte("aa"),
+ ControlProgram: orderProgram,
},
+ SellerProgram: sellerProgram,
},
&common.Order{
FromAssetID: assetID1,
SourceID: &bc.Hash{V0: 25},
Amount: 1,
SourcePos: 0,
- ControlProgram: []byte("aa"),
+ ControlProgram: orderProgram,
},
+ SellerProgram: sellerProgram,
},
&common.Order{
FromAssetID: assetID1,
SourceID: &bc.Hash{V0: 26},
Amount: 1,
SourcePos: 0,
- ControlProgram: []byte("aa"),
+ ControlProgram: orderProgram,
},
+ SellerProgram: sellerProgram,
},
&common.Order{
FromAssetID: assetID1,
SourceID: &bc.Hash{V0: 1},
Amount: 1,
SourcePos: 0,
- ControlProgram: []byte("aa"),
+ ControlProgram: orderProgram,
},
+ SellerProgram: sellerProgram,
},
&common.Order{
FromAssetID: assetID1,
SourceID: &bc.Hash{V0: 2},
Amount: 1,
SourcePos: 0,
- ControlProgram: []byte("aa"),
+ ControlProgram: orderProgram,
},
+ SellerProgram: sellerProgram,
},
&common.Order{
FromAssetID: assetID3,
SourceID: &bc.Hash{V0: 33},
Amount: 1,
SourcePos: 0,
- ControlProgram: []byte("aa"),
+ ControlProgram: orderProgram,
},
+ SellerProgram: sellerProgram,
},
&common.Order{
FromAssetID: assetID4,
SourceID: &bc.Hash{V0: 34},
Amount: 1,
SourcePos: 0,
- ControlProgram: []byte("aa"),
+ ControlProgram: orderProgram,
},
+ SellerProgram: sellerProgram,
},
&common.Order{
FromAssetID: assetID4,
SourceID: &bc.Hash{V0: 36},
Amount: 1,
SourcePos: 0,
- ControlProgram: []byte("aa"),
+ ControlProgram: orderProgram,
},
+ SellerProgram: sellerProgram,
},
&common.Order{
FromAssetID: assetID5,
SourceID: &bc.Hash{V0: 37},
Amount: 1,
SourcePos: 0,
- ControlProgram: []byte("aa"),
+ ControlProgram: orderProgram,
},
+ SellerProgram: sellerProgram,
},
&common.Order{
FromAssetID: assetID6,
SourceID: &bc.Hash{V0: 38},
Amount: 1,
SourcePos: 0,
- ControlProgram: []byte("aa"),
+ ControlProgram: orderProgram,
},
+ SellerProgram: sellerProgram,
},
}
)
mockOrders[7],
mockOrders[6],
mockOrders[2],
- mockOrders[3],
mockOrders[4],
+ mockOrders[3],
mockOrders[5],
mockOrders[0],
},
wantOrders: []*common.Order{
mockOrders[7],
mockOrders[6],
- mockOrders[3],
mockOrders[4],
+ mockOrders[3],
mockOrders[5],
},
wantTradePairs: []*common.TradePair{
mockOrders[7],
mockOrders[6],
mockOrders[2],
- mockOrders[3],
mockOrders[4],
+ mockOrders[3],
mockOrders[5],
mockOrders[0],
},
},
query: mockOrders[3],
wantOrders: []*common.Order{
- mockOrders[4],
mockOrders[5],
mockOrders[0],
},
mockOrders[7],
mockOrders[6],
mockOrders[2],
- mockOrders[3],
mockOrders[4],
+ mockOrders[3],
mockOrders[5],
mockOrders[0],
},
mockOrders[7],
mockOrders[6],
mockOrders[2],
- mockOrders[3],
mockOrders[4],
+ mockOrders[3],
mockOrders[5],
mockOrders[0],
},
rewardProgram []byte
}
+type orderPosition struct {
+ blockHeight uint64
+ txIndex uint64
+}
+
// NewEngine return a new Engine
-func NewEngine(orderBook *OrderBook, feeStrategy FeeStrategy, rewardProgram []byte) *Engine {
- return &Engine{orderBook: orderBook, feeStrategy: feeStrategy, rewardProgram: rewardProgram}
+func NewEngine(orderBook *OrderBook, rewardProgram []byte) *Engine {
+ return &Engine{orderBook: orderBook, feeStrategy: NewDefaultFeeStrategy(), rewardProgram: rewardProgram}
}
// HasMatchedTx check does the input trade pair can generate a match deal
return nil, errors.New("the specified trade pairs can not be matched")
}
- tx, err := e.buildMatchTx(sortOrders(e.orderBook.PeekOrders(tradePairs)))
+ tx, partialOrderPositions, err := e.buildMatchTx(sortOrders(e.orderBook.PeekOrders(tradePairs)))
if err != nil {
return nil, err
}
e.orderBook.PopOrder(tradePair)
}
- if err := e.addPartialTradeOrder(tx); err != nil {
+ if err := e.addReOrder(tx, partialOrderPositions); err != nil {
return nil, err
}
return tx, nil
}
-func (e *Engine) addMatchTxFeeOutput(txData *types.TxData, fees []*bc.AssetAmount) error {
+func addMatchTxFeeOutput(txData *types.TxData, fees []*bc.AssetAmount, rewardProgram []byte) {
for _, feeAmount := range fees {
- txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*feeAmount.AssetId, feeAmount.Amount, e.rewardProgram))
- }
-
- refoundAmount := map[bc.AssetID]uint64{}
- assetIDs := []bc.AssetID{}
- refoundScript := [][]byte{}
- for _, input := range txData.Inputs {
- refoundAmount[input.AssetID()] += input.Amount()
- contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
- if err != nil {
- return err
- }
-
- assetIDs = append(assetIDs, input.AssetID())
- refoundScript = append(refoundScript, contractArgs.SellerProgram)
+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*feeAmount.AssetId, feeAmount.Amount, rewardProgram))
}
-
- for _, output := range txData.Outputs {
- assetAmount := output.AssetAmount()
- refoundAmount[*assetAmount.AssetId] -= assetAmount.Amount
- }
-
- refoundCount := len(refoundScript)
- for _, assetID := range assetIDs {
- amount := refoundAmount[assetID]
- averageAmount := amount / uint64(refoundCount)
- if averageAmount == 0 {
- averageAmount = 1
- }
-
- for i := 0; i < refoundCount && amount > 0; i++ {
- if i == refoundCount-1 {
- averageAmount = amount
- }
- txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, averageAmount, refoundScript[i]))
- amount -= averageAmount
- }
- }
- return nil
}
-func (e *Engine) addPartialTradeOrder(tx *types.Tx) error {
+func (e *Engine) addReOrder(tx *types.Tx, partialOrderPositions []*orderPosition) error {
+ index := 0
for i, output := range tx.Outputs {
if !segwit.IsP2WMCScript(output.ControlProgram()) || output.AssetAmount().Amount == 0 {
continue
}
- order, err := common.NewOrderFromOutput(tx, i)
+ partialOrderPos := partialOrderPositions[index]
+ order, err := common.NewOrderFromOutput(tx, i, partialOrderPos.blockHeight, partialOrderPos.txIndex)
if err != nil {
return err
}
+ index++
e.orderBook.AddOrder(order)
}
return nil
}
-func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
+func addRefundOutput(txData *types.TxData, takerProgram []byte) {
+ refundAmount := map[bc.AssetID]uint64{}
+ for _, input := range txData.Inputs {
+ refundAmount[input.AssetID()] += input.Amount()
+ }
+
+ for _, output := range txData.Outputs {
+ assetAmount := output.AssetAmount()
+ refundAmount[*assetAmount.AssetId] -= assetAmount.Amount
+ }
+
+ for assetID, amount := range refundAmount {
+ if amount != 0 {
+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, amount, takerProgram))
+ }
+ }
+}
+
+func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, []*orderPosition, error) {
txData := &types.TxData{Version: 1}
for _, order := range orders {
input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram)
txData.Inputs = append(txData.Inputs, input)
}
+ takerPos := takerPos(orders)
receivedAmounts, priceDiffs := CalcReceivedAmount(orders)
- allocatedAssets := e.feeStrategy.Allocate(receivedAmounts, priceDiffs)
- if err := addMatchTxOutput(txData, orders, receivedAmounts, allocatedAssets); err != nil {
- return nil, err
- }
+ allocatedAssets := e.feeStrategy.Allocate(receivedAmounts, priceDiffs, takerPos)
- if err := e.addMatchTxFeeOutput(txData, allocatedAssets.Fees); err != nil {
- return nil, err
+ partialOrderPositions, err := addMatchTxOutput(txData, orders, receivedAmounts, allocatedAssets)
+ if err != nil {
+ return nil, nil, err
}
+ addMatchTxFeeOutput(txData, allocatedAssets.Fees, e.rewardProgram)
+ addRefundOutput(txData, orders[takerPos].SellerProgram)
+
byteData, err := txData.MarshalText()
if err != nil {
- return nil, err
+ return nil, nil, err
}
txData.SerializedSize = uint64(len(byteData))
- return types.NewTx(*txData), nil
+ return types.NewTx(*txData), partialOrderPositions, nil
}
-func addMatchTxOutput(txData *types.TxData, orders []*common.Order, receivedAmounts []*bc.AssetAmount, allocatedAssets *AllocatedAssets) error {
+func addMatchTxOutput(txData *types.TxData, orders []*common.Order, receivedAmounts []*bc.AssetAmount, allocatedAssets *AllocatedAssets) ([]*orderPosition, error) {
+ var partialOrderPositions []*orderPosition
for i, order := range orders {
- contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
- if err != nil {
- return err
- }
-
receivedAmount := receivedAmounts[i].Amount
- shouldPayAmount := calcShouldPayAmount(receivedAmount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
+ shouldPayAmount := calcShouldPayAmount(receivedAmount, order.RatioNumerator, order.RatioDenominator)
requestAmount := CalcRequestAmount(order.Utxo.Amount, order.RatioNumerator, order.RatioDenominator)
exchangeAmount := order.Utxo.Amount - shouldPayAmount
- isPartialTrade := requestAmount > receivedAmount && CalcRequestAmount(exchangeAmount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) >= 1
+ isPartialTrade := requestAmount > receivedAmount && CalcRequestAmount(exchangeAmount, order.RatioNumerator, order.RatioDenominator) >= 1
setMatchTxArguments(txData.Inputs[i], isPartialTrade, len(txData.Outputs), receivedAmount)
- txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, allocatedAssets.Receives[i].Amount, contractArgs.SellerProgram))
+
+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, allocatedAssets.Receives[i].Amount, order.SellerProgram))
if isPartialTrade {
txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, exchangeAmount, order.Utxo.ControlProgram))
+ partialOrderPositions = append(partialOrderPositions, &orderPosition{blockHeight: order.BlockHeight, txIndex: order.TxIndex})
}
}
- return nil
+ return partialOrderPositions, nil
}
func calcOppositeIndex(size int, selfIdx int) int {
return product.Cmp(one) <= 0
}
+func takerPos(orders []*common.Order) int {
+ for i, order := range orders {
+ if !isMaker(order, orders[calcOppositeIndex(len(orders), i)]) {
+ return i
+ }
+ }
+ return 0
+}
+
+func isMaker(order, oppositeOrder *common.Order) bool {
+ if order.BlockHeight != oppositeOrder.BlockHeight {
+ return order.BlockHeight < oppositeOrder.BlockHeight
+ }
+
+ if order.TxIndex != oppositeOrder.TxIndex {
+ return order.TxIndex < oppositeOrder.TxIndex
+ }
+
+ return order.UTXOHash().String() < oppositeOrder.UTXOHash().String()
+}
+
func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) {
var arguments [][]byte
if isPartialTrade {
for i, c := range cases {
movStore := mock.NewMovStore([]*common.TradePair{btc2eth, eth2btc}, c.initStoreOrders)
- matchEngine := NewEngine(NewOrderBook(movStore, nil, nil), NewDefaultFeeStrategy(), mock.RewardProgram)
+ matchEngine := NewEngine(NewOrderBook(movStore, nil, nil), mock.RewardProgram)
var gotMatchedTxs []*types.Tx
for matchEngine.HasMatchedTx(c.tradePairs...) {
matchedTx, err := matchEngine.NextMatchedTx(c.tradePairs...)
)
var (
- // ErrAmountOfFeeOutOfRange represent The fee charged is out of range
- ErrAmountOfFeeOutOfRange = errors.New("amount of fee is out of range")
+ // ErrInvalidAmountOfFee represent The fee charged is invalid
+ ErrInvalidAmountOfFee = errors.New("amount of fee is invalid")
)
+const forkBlockHeightAt20201028 = 78968400
+
// AllocatedAssets represent reallocated assets after calculating fees
type AllocatedAssets struct {
Receives []*bc.AssetAmount
type FeeStrategy interface {
// Allocate will allocate the price differential in matching transaction to the participants and the fee
// @param receiveAmounts the amount of assets that the participants in the matching transaction can received when no fee is considered
- // @param priceDiffs price differential of matching transaction
// @return reallocated assets after calculating fees
- Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets
+ Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount, takerPos int) *AllocatedAssets
// Validate verify that the fee charged for a matching transaction is correct
- Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error
+ Validate(receiveAmounts, priceDiffs []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64, blockHeight uint64) error
}
// DefaultFeeStrategy represent the default fee charge strategy
}
// Allocate will allocate the price differential in matching transaction to the participants and the fee
-func (d *DefaultFeeStrategy) Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets {
+func (d *DefaultFeeStrategy) Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount, takerPos int) *AllocatedAssets {
receives := make([]*bc.AssetAmount, len(receiveAmounts))
fees := make([]*bc.AssetAmount, len(receiveAmounts))
for i, receiveAmount := range receiveAmounts {
- standFee := d.calcMinFeeAmount(receiveAmount.Amount)
- fee := standFee + priceDiffs[i].Amount
- if maxFeeAmount := d.calcMaxFeeAmount(receiveAmount.Amount); fee > maxFeeAmount {
- fee = maxFeeAmount
- }
-
- receives[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: receiveAmount.Amount - standFee}
+ fee := calcMinFeeAmount(receiveAmount.Amount)
+ receives[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: receiveAmount.Amount - fee}
fees[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: fee}
+
+ if i == takerPos {
+ for _, priceDiff := range priceDiffs {
+ if *priceDiff.AssetId == *receiveAmount.AssetId {
+ fee = calcMinFeeAmount(priceDiff.Amount)
+ priceDiff.Amount -= fee
+ fees[i].Amount += fee
+ }
+ }
+ }
}
return &AllocatedAssets{Receives: receives, Fees: fees}
}
// Validate verify that the fee charged for a matching transaction is correct
-func (d *DefaultFeeStrategy) Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error {
+func (d *DefaultFeeStrategy) Validate(receiveAmounts, priceDiffs []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64, blockHeight uint64) error {
+ if blockHeight < forkBlockHeightAt20201028 {
+ return legendValidateFee(receiveAmounts, feeAmounts)
+ }
+ return validateFee(receiveAmounts, priceDiffs, feeAmounts)
+}
+
+func validateFee(receiveAmounts, priceDiffs []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error {
+ existTaker := false
+ for _, receiveAmount := range receiveAmounts {
+ feeAmount := calcMinFeeAmount(receiveAmount.Amount)
+ realFeeAmount := feeAmounts[*receiveAmount.AssetId]
+ if equalsFeeAmount(realFeeAmount, feeAmount) {
+ continue
+ }
+
+ if existTaker {
+ return ErrInvalidAmountOfFee
+ }
+
+ for _, priceDiff := range priceDiffs {
+ if *priceDiff.AssetId == *receiveAmount.AssetId {
+ feeAmount += calcMinFeeAmount(priceDiff.Amount)
+ }
+ }
+
+ if !equalsFeeAmount(realFeeAmount, feeAmount) {
+ return ErrInvalidAmountOfFee
+ }
+ existTaker = true
+ }
+ return nil
+}
+
+func equalsFeeAmount(realFeeAmount, feeAmount uint64) bool {
+ var tolerance float64 = 5
+ return math.Abs(float64(realFeeAmount)-float64(feeAmount)) < tolerance
+}
+
+func legendValidateFee(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error {
for _, receiveAmount := range receiveAmounts {
- feeAmount := feeAmounts[*receiveAmount.AssetId]
- maxFeeAmount := d.calcMaxFeeAmount(receiveAmount.Amount)
- minFeeAmount := d.calcMinFeeAmount(receiveAmount.Amount)
- if feeAmount < minFeeAmount || feeAmount > maxFeeAmount {
- return ErrAmountOfFeeOutOfRange
+ realFeeAmount := feeAmounts[*receiveAmount.AssetId]
+ minFeeAmount := calcMinFeeAmount(receiveAmount.Amount)
+ maxFeeAmount := calcMaxFeeAmount(receiveAmount.Amount)
+ if realFeeAmount < minFeeAmount || realFeeAmount > maxFeeAmount {
+ return ErrInvalidAmountOfFee
}
}
return nil
}
-func (d *DefaultFeeStrategy) calcMinFeeAmount(amount uint64) uint64 {
+func calcMinFeeAmount(amount uint64) uint64 {
return uint64(math.Ceil(float64(amount) / 1000))
}
-func (d *DefaultFeeStrategy) calcMaxFeeAmount(amount uint64) uint64 {
+func calcMaxFeeAmount(amount uint64) uint64 {
return uint64(math.Ceil(float64(amount) * 0.05))
}
Amount: 10,
ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 50, 1),
},
+ BlockHeight: 100,
+ TxIndex: 1,
+ SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"),
},
{
FromAssetID: &BTC,
Amount: 20,
ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"), 53, 1),
},
+ BlockHeight: 100,
+ TxIndex: 2,
+ SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"),
},
{
FromAssetID: &BTC,
Amount: 15,
ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"), 53, 1),
},
+ SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"),
},
{
FromAssetID: &BTC,
Amount: 17,
ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"), 53, 1),
},
+ SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"),
},
}
Amount: 510,
ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253"), 1, 51.0),
},
+ SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253"),
},
{
FromAssetID: Ð,
Amount: 416,
ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254"), 1, 52.0),
},
+ BlockHeight: 100,
+ TxIndex: 2,
+ SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254"),
},
{
FromAssetID: Ð,
Amount: 810,
ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"), 1, 54.0),
},
+ BlockHeight: 101,
+ TxIndex: 1,
+ SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"),
},
{
FromAssetID: Ð,
Amount: 600,
ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256"), 1, 150.0),
},
+ BlockHeight: 101,
+ TxIndex: 3,
+ SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256"),
},
}
Amount: 100,
ControlProgram: MustCreateP2WMCProgram(ETC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"), 1, 2.0),
},
+ SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"),
},
}
Amount: 50,
ControlProgram: MustCreateP2WMCProgram(EOS, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3"), 2, 1.0),
},
+ SellerProgram: testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3"),
},
}
Amount: 500,
ControlProgram: MustCreateP2WMCProgram(EOS, testutil.MustDecodeHexString("0014e3178c0f294a9a8f4b304236406507913091df86"), 2, 1.0),
},
+ SellerProgram: testutil.MustDecodeHexString("0014e3178c0f294a9a8f4b304236406507913091df86"),
},
}
Amount: 1000,
ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("00144d0dfc8a0c5ce41d31d4f61d99aff70588bff8bc"), 1, 100.0),
},
+ SellerProgram: testutil.MustDecodeHexString("00144d0dfc8a0c5ce41d31d4f61d99aff70588bff8bc"),
},
}
// partial matched transaction from Btc2EthOrders[0], Eth2BtcOrders[1]
types.NewTx(types.TxData{
Inputs: []*types.TxInput{
+ // maker
types.NewSpendInput([][]byte{vm.Int64Bytes(416), vm.Int64Bytes(0), vm.Int64Bytes(0)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
+ // taker
types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, *Eth2BtcOrders[1].Utxo.SourceID, *Eth2BtcOrders[1].FromAssetID, Eth2BtcOrders[1].Utxo.Amount, Eth2BtcOrders[1].Utxo.SourcePos, Eth2BtcOrders[1].Utxo.ControlProgram),
},
Outputs: []*types.TxOutput{
types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
// fee
- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 11, RewardProgram),
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 2, RewardProgram),
types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, RewardProgram),
+ // refund to taker
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
},
}),
// partial matched transaction from Btc2EthOrders[0], Eth2BtcOrders[2]
types.NewTx(types.TxData{
Inputs: []*types.TxInput{
+ // maker
types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
+ // taker
types.NewSpendInput([][]byte{vm.Int64Bytes(10), vm.Int64Bytes(1), vm.Int64Bytes(0)}, *Eth2BtcOrders[2].Utxo.SourceID, *Eth2BtcOrders[2].FromAssetID, Eth2BtcOrders[2].Utxo.Amount, Eth2BtcOrders[2].Utxo.SourcePos, Eth2BtcOrders[2].Utxo.ControlProgram),
},
Outputs: []*types.TxOutput{
// re-order
types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 270, Eth2BtcOrders[2].Utxo.ControlProgram),
// fee
- types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 25, RewardProgram),
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 1, RewardProgram),
- // refund
- types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
- types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+ // re-order
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 40, Eth2BtcOrders[2].SellerProgram),
},
}),
types.NewTx(types.TxData{
Inputs: []*types.TxInput{
+ // maker
types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(0)}, *Btc2EthOrders[1].Utxo.SourceID, *Btc2EthOrders[1].FromAssetID, Btc2EthOrders[1].Utxo.Amount, Btc2EthOrders[1].Utxo.SourcePos, Btc2EthOrders[1].Utxo.ControlProgram),
+ // taker
types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, testutil.MustDecodeHash("39bdb7058a0c31fb740af8e3c382bf608efff1b041cd4dd461332722ad24552a"), *Eth2BtcOrders[2].FromAssetID, 270, 2, Eth2BtcOrders[2].Utxo.ControlProgram),
},
Outputs: []*types.TxOutput{
types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, *Eos2BtcOrders[0].Utxo.SourceID, *Eos2BtcOrders[0].FromAssetID, Eos2BtcOrders[0].Utxo.Amount, Eos2BtcOrders[0].Utxo.SourcePos, Eos2BtcOrders[0].Utxo.ControlProgram),
},
Outputs: []*types.TxOutput{
- types.NewIntraChainOutput(ETH, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
- types.NewIntraChainOutput(EOS, 999, testutil.MustDecodeHexString("0014e3178c0f294a9a8f4b304236406507913091df86")),
- types.NewIntraChainOutput(BTC, 9, testutil.MustDecodeHexString("00144d0dfc8a0c5ce41d31d4f61d99aff70588bff8bc")),
+ types.NewIntraChainOutput(ETH, 499, Btc2EthOrders[0].SellerProgram),
+ types.NewIntraChainOutput(EOS, 999, Eth2EosOrders[0].SellerProgram),
+ types.NewIntraChainOutput(BTC, 9, Eos2BtcOrders[0].SellerProgram),
// fee
types.NewIntraChainOutput(ETH, 1, RewardProgram),
types.NewIntraChainOutput(EOS, 1, RewardProgram),
// full matched transaction from Btc2EthOrders[0] Eth2BtcOrders[3]
types.NewTx(types.TxData{
Inputs: []*types.TxInput{
+ // maker
types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram),
+ // taker
types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *Eth2BtcOrders[3].Utxo.SourceID, *Eth2BtcOrders[3].FromAssetID, Eth2BtcOrders[3].Utxo.Amount, Eth2BtcOrders[3].Utxo.SourcePos, Eth2BtcOrders[3].Utxo.ControlProgram),
},
Outputs: []*types.TxOutput{
types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
// fee
- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 25, RewardProgram),
- types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 1, RewardProgram),
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 2, RewardProgram),
// refund
- types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
- types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 38, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
- types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 38, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 5, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 100, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
},
}),
}
)
func MustCreateP2WMCProgram(requestAsset bc.AssetID, sellerProgram []byte, ratioNumerator, ratioDenominator int64) []byte {
- contractArgs := vmutil.MagneticContractArgs{
+ contractArgs := createContractArgs(requestAsset, sellerProgram, ratioNumerator, ratioDenominator)
+ program, err := vmutil.P2WMCProgram(*contractArgs)
+ if err != nil {
+ panic(err)
+ }
+ return program
+}
+
+func createContractArgs(requestAsset bc.AssetID, sellerProgram []byte, ratioNumerator, ratioDenominator int64) *vmutil.MagneticContractArgs {
+ return &vmutil.MagneticContractArgs{
RequestedAsset: requestAsset,
RatioNumerator: ratioNumerator,
RatioDenominator: ratioDenominator,
SellerProgram: sellerProgram,
SellerKey: testutil.MustDecodeHexString("ad79ec6bd3a6d6dbe4d0ee902afc99a12b9702fb63edce5f651db3081d868b75"),
}
- program, err := vmutil.P2WMCProgram(contractArgs)
- if err != nil {
- panic(err)
- }
- return program
}
-func MustNewOrderFromOutput(tx *types.Tx, outputIndex int) *common.Order {
- order, err := common.NewOrderFromOutput(tx, outputIndex)
+func MustNewOrderFromOutputV2(tx *types.Tx, outputIndex int, blockHeight, txIndex uint64) *common.Order {
+ order, err := common.NewOrderFromOutput(tx, outputIndex, blockHeight, txIndex)
if err != nil {
panic(err)
}
return order
}
+func MustNewOrderFromOutput(tx *types.Tx, outputIndex int) *common.Order {
+ return MustNewOrderFromOutputV2(tx, outputIndex, 0, 0)
+}
+
func hashPtr(hash bc.Hash) *bc.Hash {
return &hash
}
return m.InitChainStatus(&blockHash)
}
- if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
+ if err := m.validateMatchedTxSequence(movTxs(block)); err != nil {
return err
}
- addOrders, deleteOrders, err := decodeTxsOrders(block.Transactions)
+ addOrders, deleteOrders, err := decodeTxsOrders(movTxs(block))
if err != nil {
return err
}
return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
}
+// Tx contains raw transaction and the sequence of tx in block
+type Tx struct {
+ rawTx *types.Tx
+ blockHeight uint64
+ txIndex uint64
+}
+
+// NewTx create a new Tx instance
+func NewTx(tx *types.Tx, blockHeight, txIndex uint64) *Tx {
+ return &Tx{rawTx: tx, blockHeight: blockHeight, txIndex: txIndex}
+}
+
// BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
-func (m *Core) BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
- if blockHeight <= m.startBlockHeight {
+func (m *Core) BeforeProposalBlock(block *types.Block, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
+ if block.Height <= m.startBlockHeight {
return nil, nil
}
- orderBook, err := buildOrderBook(m.movStore, txs)
+ orderBook, err := buildOrderBook(m.movStore, movTxs(block))
if err != nil {
return nil, err
}
- program, _ := getRewardProgram(blockHeight)
+ program, _ := getRewardProgram(block.Height)
rewardProgram, err := hex.DecodeString(program)
if err != nil {
return nil, errNotConfiguredRewardProgram
}
- matchEngine := match.NewEngine(orderBook, match.NewDefaultFeeStrategy(), rewardProgram)
+ matchEngine := match.NewEngine(orderBook, rewardProgram)
tradePairIterator := database.NewTradePairIterator(m.movStore)
matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout)
return matchCollector.result()
return nil
}
- deleteOrders, addOrders, err := decodeTxsOrders(block.Transactions)
+ deleteOrders, addOrders, err := decodeTxsOrders(movTxs(block))
if err != nil {
return err
}
toAssetIDMap[order.ToAssetID.String()] = true
}
- if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
+ if inputSize := len(tx.Inputs); len(fromAssetIDMap) != inputSize || len(toAssetIDMap) != inputSize {
return errAssetIDMustUniqueInMatchedTx
}
}
}
- orders, err := getDeleteOrdersFromTx(tx)
+ orders, err := parseDeleteOrdersFromTx(tx)
if err != nil {
return err
}
- receivedAmount, _ := match.CalcReceivedAmount(orders)
+ receivedAmount, priceDiffs := match.CalcReceivedAmount(orders)
feeAmounts := make(map[bc.AssetID]uint64)
for assetID, fee := range matchedTxFees {
feeAmounts[assetID] = fee.amount
}
feeStrategy := match.NewDefaultFeeStrategy()
- return feeStrategy.Validate(receivedAmount, feeAmounts)
+ return feeStrategy.Validate(receivedAmount, priceDiffs, feeAmounts, blockHeight)
}
-func (m *Core) validateMatchedTxSequence(txs []*types.Tx) error {
+func (m *Core) validateMatchedTxSequence(txs []*Tx) error {
orderBook := match.NewOrderBook(m.movStore, nil, nil)
for _, tx := range txs {
- if common.IsMatchedTx(tx) {
- tradePairs, err := getTradePairsFromMatchedTx(tx)
+ if common.IsMatchedTx(tx.rawTx) {
+ tradePairs, err := parseTradePairsFromMatchedTx(tx.rawTx)
if err != nil {
return err
}
orders := orderBook.PeekOrders(tradePairs)
- if err := validateSpendOrders(tx, orders); err != nil {
+ if err := validateSpendOrders(tx.rawTx, orders); err != nil {
return err
}
orderBook.PopOrders(tradePairs)
- } else if common.IsCancelOrderTx(tx) {
- orders, err := getDeleteOrdersFromTx(tx)
+ } else if common.IsCancelOrderTx(tx.rawTx) {
+ orders, err := parseDeleteOrdersFromTx(tx.rawTx)
if err != nil {
return err
}
}
}
- addOrders, err := getAddOrdersFromTx(tx)
+ addOrders, err := parseAddOrdersFromTx(tx)
if err != nil {
return err
}
return nil
}
-func decodeTxsOrders(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
+func decodeTxsOrders(txs []*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)
+ addOrders, err := parseAddOrdersFromTx(tx)
if err != nil {
return nil, nil, err
}
addOrderMap[order.Key()] = order
}
- deleteOrders, err := getDeleteOrdersFromTx(tx)
+ deleteOrders, err := parseDeleteOrdersFromTx(tx.rawTx)
if err != nil {
return nil, nil, err
}
return addOrders, deleteOrders, nil
}
-func buildOrderBook(store database.MovStore, txs []*types.Tx) (*match.OrderBook, error) {
+func buildOrderBook(store database.MovStore, txs []*Tx) (*match.OrderBook, error) {
var arrivalAddOrders, arrivalDelOrders []*common.Order
for _, tx := range txs {
- addOrders, err := getAddOrdersFromTx(tx)
+ addOrders, err := parseAddOrdersFromTx(tx)
if err != nil {
return nil, err
}
- delOrders, err := getDeleteOrdersFromTx(tx)
+ delOrders, err := parseDeleteOrdersFromTx(tx.rawTx)
if err != nil {
return nil, err
}
return match.NewOrderBook(store, arrivalAddOrders, arrivalDelOrders), nil
}
-func getAddOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
+func parseAddOrdersFromTx(tx *Tx) ([]*common.Order, error) {
var orders []*common.Order
- for i, output := range tx.Outputs {
+ for i, output := range tx.rawTx.Outputs {
if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
continue
}
continue
}
- order, err := common.NewOrderFromOutput(tx, i)
+ order, err := common.NewOrderFromOutput(tx.rawTx, i, tx.blockHeight, tx.txIndex)
if err != nil {
return nil, err
}
return orders, nil
}
-func getDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
+func parseDeleteOrdersFromTx(tx *types.Tx) ([]*common.Order, error) {
var orders []*common.Order
for i, input := range tx.Inputs {
if input.InputType() != types.SpendInputType || !segwit.IsP2WMCScript(input.ControlProgram()) {
return orders, nil
}
-func getTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
+func parseTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
var tradePairs []*common.TradePair
for _, tx := range tx.Inputs {
contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
return addOrders, deleteOrders
}
+func movTxs(block *types.Block) []*Tx {
+ var movTxs []*Tx
+ for i, tx := range block.Transactions {
+ movTxs = append(movTxs, NewTx(tx, block.Height, uint64(i)))
+ }
+ return movTxs
+}
+
// getRewardProgram return the reward program by specified block height
// if no reward program configured, then will return empty string
// if reward program of 0-100 height is configured, but the specified height is 200, then will return 0-100's reward program
},
},
blockFunc: applyBlock,
- wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0)},
+ wantOrders: []*common.Order{mock.MustNewOrderFromOutputV2(mock.Btc2EthMakerTxs[0], 0, 2, 0), mock.MustNewOrderFromOutputV2(mock.Eth2BtcMakerTxs[0], 0, 2, 1)},
wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
},
{
},
blockFunc: applyBlock,
wantOrders: []*common.Order{
- mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0),
- mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0),
- mock.MustNewOrderFromOutput(mock.Eos2EtcMakerTxs[0], 0),
- mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
+ mock.MustNewOrderFromOutputV2(mock.Btc2EthMakerTxs[0], 0, 2, 0),
+ mock.MustNewOrderFromOutputV2(mock.Eth2BtcMakerTxs[0], 0, 2, 1),
+ mock.MustNewOrderFromOutputV2(mock.Eos2EtcMakerTxs[0], 0, 2, 2),
+ mock.MustNewOrderFromOutputV2(mock.Eth2EosMakerTxs[0], 0, 2, 3),
},
wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
},
},
blockFunc: applyBlock,
initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
- wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
+ wantOrders: []*common.Order{mock.MustNewOrderFromOutputV2(mock.MatchedTxs[0], 1, 2, 0)},
wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
},
{
},
blockFunc: applyBlock,
initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
- wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
+ wantOrders: []*common.Order{mock.MustNewOrderFromOutputV2(mock.MatchedTxs[3], 1, 2, 1)},
wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
},
{
},
blockFunc: applyBlock,
initOrders: []*common.Order{},
- wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1)},
+ wantOrders: []*common.Order{mock.MustNewOrderFromOutputV2(mock.MatchedTxs[4], 1, 2, 2)},
wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
},
{
blockFunc: applyBlock,
initOrders: []*common.Order{},
wantOrders: []*common.Order{
- mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1),
- mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
+ mock.MustNewOrderFromOutputV2(mock.MatchedTxs[4], 1, 2, 3),
+ mock.MustNewOrderFromOutputV2(mock.Eth2EosMakerTxs[0], 0, 2, 4),
},
wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
},
},
blockFunc: applyBlock,
initOrders: []*common.Order{},
- wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[7], 2)},
+ wantOrders: []*common.Order{mock.MustNewOrderFromOutputV2(mock.MatchedTxs[7], 2, 2, 4)},
wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
},
{
mock.MatchedTxs[1],
},
},
- blockFunc: detachBlock,
- initOrders: []*common.Order{mock.Btc2EthOrders[1]},
- wantOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
+ blockFunc: detachBlock,
+ initOrders: []*common.Order{mock.Btc2EthOrders[1]},
+ wantOrders: []*common.Order{
+ orderWithHeightAndTxIndex(mock.Btc2EthOrders[0], 0, 0),
+ mock.Btc2EthOrders[1],
+ orderWithHeightAndTxIndex(mock.Eth2BtcOrders[0], 0, 0),
+ },
wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
},
{
mock.MatchedTxs[0],
},
},
- blockFunc: detachBlock,
- initOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
- wantOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
+ blockFunc: detachBlock,
+ initOrders: []*common.Order{mock.MustNewOrderFromOutputV2(mock.MatchedTxs[0], 1, 1, 0)},
+ wantOrders: []*common.Order{
+ orderWithHeightAndTxIndex(mock.Btc2EthOrders[0], 0, 0),
+ orderWithHeightAndTxIndex(mock.Eth2BtcOrders[1], 0, 0),
+ },
wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
},
{
mock.MatchedTxs[2], mock.MatchedTxs[3],
},
},
- blockFunc: detachBlock,
- initOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
- wantOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
+ blockFunc: detachBlock,
+ initOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
+ wantOrders: []*common.Order{
+ orderWithHeightAndTxIndex(mock.Btc2EthOrders[0], 0, 0),
+ orderWithHeightAndTxIndex(mock.Btc2EthOrders[1], 0, 0),
+ orderWithHeightAndTxIndex(mock.Eth2BtcOrders[2], 0, 0),
+ },
wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
},
{
movCore := &Core{movStore: store}
if err := c.blockFunc(movCore, c.block); err != c.wantError {
- t.Errorf("#%d(%s):apply block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
+ t.Errorf("#%d(%s):want error(%v), got error(%v)", i, c.desc, c.wantError, err)
}
gotOrders := queryAllOrders(store)
if !ordersEquals(c.wantOrders, gotOrders) {
- t.Errorf("#%d(%s):apply block want orders(%v), got orders(%v)", i, c.desc, c.wantOrders, gotOrders)
+ t.Errorf("#%d(%s):want orders(%v), got orders(%v)", i, c.desc, c.wantOrders, gotOrders)
}
dbState, err := store.GetMovDatabaseState()
}
if !testutil.DeepEqual(c.wantDBState, dbState) {
- t.Errorf("#%d(%s):apply block want db state(%v), got db state(%v)", i, c.desc, c.wantDBState, dbState)
+ t.Errorf("#%d(%s):want db state(%v), got db state(%v)", i, c.desc, c.wantDBState, dbState)
}
testDB.Close()
},
},
verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
- wantError: match.ErrAmountOfFeeOutOfRange,
+ wantError: match.ErrInvalidAmountOfFee,
},
{
desc: "ratio numerator is zero",
for i, c := range cases {
movCore := &Core{}
- c.block.Height = 3456786543
+ c.block.Height = 84000000
if err := movCore.ValidateBlock(c.block, c.verifyResults); err != c.wantError {
t.Errorf("#%d(%s):validate block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
}
cases := []struct {
desc string
tx types.TxData
- maxFeeRate float64
wantMatchedTxFee map[bc.AssetID]*matchedTxFee
}{
{
- desc: "fee less than max fee",
- maxFeeRate: 0.05,
+ desc: "fee less than max fee",
wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
- mock.ETH: {amount: 11, rewardProgram: mock.RewardProgram},
mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
+ mock.ETH: {amount: 2, rewardProgram: mock.RewardProgram},
},
tx: mock.MatchedTxs[1].TxData,
},
{
- desc: "fee refund in tx",
- maxFeeRate: 0.05,
+ desc: "fee refund in tx",
wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
- mock.ETH: {amount: 25, rewardProgram: mock.RewardProgram},
mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
+ mock.ETH: {amount: 1, rewardProgram: mock.RewardProgram},
},
tx: mock.MatchedTxs[2].TxData,
},
{
- desc: "no price diff",
- maxFeeRate: 0.05,
+ desc: "no price diff",
wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
- mock.ETH: {amount: 1, rewardProgram: mock.RewardProgram},
mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
+ mock.ETH: {amount: 1, rewardProgram: mock.RewardProgram},
},
tx: mock.MatchedTxs[0].TxData,
},
}
movCore := &Core{movStore: store}
- gotMatchedTxs, err := movCore.BeforeProposalBlock(nil, 2, c.gasLeft, func() bool { return false })
+ gotMatchedTxs, err := movCore.BeforeProposalBlock(&types.Block{BlockHeader: types.BlockHeader{Height: 2}}, c.gasLeft, func() bool { return false })
if err != nil {
t.Fatal(err)
}
cases := []struct {
desc string
initOrders []*common.Order
- transactions []*types.Tx
+ transactions []*Tx
wantError error
}{
{
desc: "both db orders and transactions is empty",
initOrders: []*common.Order{},
- transactions: []*types.Tx{},
+ transactions: []*Tx{},
wantError: nil,
},
{
desc: "existing matched orders in db, and transactions is empty",
initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
- transactions: []*types.Tx{},
+ transactions: []*Tx{},
wantError: nil,
},
{
desc: "db orders is empty, but transactions has matched tx",
initOrders: []*common.Order{},
- transactions: []*types.Tx{mock.MatchedTxs[1]},
+ transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}},
wantError: errNotMatchedOrder,
},
{
desc: "existing matched orders in db, and corresponding matched tx in transactions",
initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
- transactions: []*types.Tx{mock.MatchedTxs[1]},
+ transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}},
wantError: nil,
},
{
desc: "package matched tx, one order from db, and the another order from transactions",
initOrders: []*common.Order{mock.Btc2EthOrders[0]},
- transactions: []*types.Tx{mock.Eth2BtcMakerTxs[0], mock.MatchedTxs[10]},
+ transactions: []*Tx{{rawTx: mock.Eth2BtcMakerTxs[0]}, {rawTx: mock.MatchedTxs[10]}},
wantError: nil,
},
{
desc: "two matched txs use the same orders",
initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
- transactions: []*types.Tx{mock.MatchedTxs[1], mock.MatchedTxs[1]},
+ transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}, {rawTx: mock.MatchedTxs[1]}},
wantError: errNotMatchedOrder,
},
{
mock.Btc2EthOrders[3], mock.Eth2BtcOrders[2],
mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
},
- transactions: []*types.Tx{mock.MatchedTxs[8]},
+ transactions: []*Tx{{rawTx: mock.MatchedTxs[8]}},
wantError: nil,
},
{
mock.Btc2EthOrders[3], mock.Eth2BtcOrders[2],
mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
},
- transactions: []*types.Tx{mock.MatchedTxs[1], mock.MatchedTxs[8]},
+ transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}, {rawTx: mock.MatchedTxs[8]}},
wantError: errSpendOutputIDIsIncorrect,
},
{
desc: "matched tx and orders from packaged transactions",
initOrders: []*common.Order{},
- transactions: []*types.Tx{mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1], mock.MatchedTxs[4]},
+ transactions: []*Tx{{rawTx: mock.Btc2EthMakerTxs[0]}, {rawTx: mock.Eth2BtcMakerTxs[1]}, {rawTx: mock.MatchedTxs[4]}},
wantError: nil,
},
{
desc: "package the matched tx first, then package match orders",
initOrders: []*common.Order{},
- transactions: []*types.Tx{mock.MatchedTxs[4], mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1]},
+ transactions: []*Tx{{rawTx: mock.MatchedTxs[4]}, {rawTx: mock.Btc2EthMakerTxs[0]}, {rawTx: mock.Eth2BtcMakerTxs[1]}},
wantError: errNotMatchedOrder,
},
{
desc: "cancel order in transactions",
initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
- transactions: []*types.Tx{mock.Btc2EthCancelTxs[0], mock.MatchedTxs[1]},
+ transactions: []*Tx{{rawTx: mock.Btc2EthCancelTxs[0]}, {rawTx: mock.MatchedTxs[1]}},
wantError: errNotMatchedOrder,
},
{
desc: "package cancel order after match tx",
initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
- transactions: []*types.Tx{mock.MatchedTxs[1], mock.Btc2EthCancelTxs[0]},
+ transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}, {rawTx: mock.Btc2EthCancelTxs[0]}},
wantError: nil,
},
{
mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
mock.Eos2EtcOrders[0], mock.Etc2EosOrders[0],
},
- transactions: []*types.Tx{mock.MatchedTxs[1], mock.MatchedTxs[9]},
+ transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}, {rawTx: mock.MatchedTxs[9]}},
wantError: nil,
},
{
mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
mock.Eos2EtcOrders[0], mock.Etc2EosOrders[0],
},
- transactions: []*types.Tx{mock.MatchedTxs[9], mock.MatchedTxs[1]},
+ transactions: []*Tx{{rawTx: mock.MatchedTxs[9]}, {rawTx: mock.MatchedTxs[1]}},
wantError: nil,
},
{
desc: "package partial matched tx from db orders, and the re-pending order continue to match",
initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
- transactions: []*types.Tx{mock.MatchedTxs[2], mock.MatchedTxs[3]},
+ transactions: []*Tx{{rawTx: mock.MatchedTxs[2]}, {rawTx: mock.MatchedTxs[3]}},
wantError: nil,
},
{
desc: "cancel the re-pending order",
initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
- transactions: []*types.Tx{mock.MatchedTxs[2], mock.Btc2EthCancelTxs[1], mock.MatchedTxs[3]},
+ transactions: []*Tx{{rawTx: mock.MatchedTxs[2]}, {rawTx: mock.Btc2EthCancelTxs[1]}, {rawTx: mock.MatchedTxs[3]}},
wantError: errNotMatchedOrder,
},
}
func hashPtr(hash bc.Hash) *bc.Hash {
return &hash
}
+
+func orderWithHeightAndTxIndex(order *common.Order, blockHeight, txIndex uint64) *common.Order {
+ order.BlockHeight = blockHeight
+ order.TxIndex = txIndex
+ return order
+}
break
}
- subTxs, err := p.BeforeProposalBlock(b.block.Transactions, b.block.Height, b.gasLeft, isTimeout)
+ subTxs, err := p.BeforeProposalBlock(b.block, b.gasLeft, isTimeout)
if err != nil {
log.WithFields(log.Fields{"module": logModule, "index": i, "error": err}).Error("failed on sub protocol txs package")
continue
type SubProtocol interface {
Name() string
StartHeight() uint64
- BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error)
+ BeforeProposalBlock(block *types.Block, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error)
// ChainStatus return the the current block height and block hash of sub protocol.
// it will return ErrNotInitSubProtocolChainStatus if not initialized.