package common
- import "github.com/bytom/vapor/protocol/bc"
+ import (
+ "encoding/hex"
+ "fmt"
- "github.com/vapor/consensus/segwit"
- "github.com/vapor/errors"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/bc/types"
++ "github.com/bytom/vapor/consensus/segwit"
++ "github.com/bytom/vapor/errors"
++ "github.com/bytom/vapor/protocol/bc"
++ "github.com/bytom/vapor/protocol/bc/types"
+ )
+
+ // MovUtxo store the utxo information for mov order
type MovUtxo struct {
SourceID *bc.Hash
SourcePos uint64
--- /dev/null
- "github.com/vapor/consensus"
- "github.com/vapor/testutil"
+ package common
+
+ import (
+ "testing"
+
++ "github.com/bytom/vapor/consensus"
++ "github.com/bytom/vapor/testutil"
+ )
+
+ func TestCalcUTXOHash(t *testing.T) {
+ wantHash := "d94acbac0304e054569b0a2c2ab546be293552eb83d2d84af7234a013986a906"
+ controlProgram := testutil.MustDecodeHexString("0014d6f0330717170c838e6ac4c643de61e4c035e9b7")
+ sourceID := testutil.MustDecodeHash("3cada915465af2f08c93911bce7a100498fddb5738e5400269c4d5c2b2f5b261")
+ order := Order{
+ FromAssetID: consensus.BTMAssetID,
+ Utxo: &MovUtxo{
+ SourceID: &sourceID,
+ SourcePos: 1,
+ Amount: 399551000,
+ ControlProgram: controlProgram,
+ },
+ }
+
+ if hash := order.UTXOHash(); hash.String() != wantHash {
+ t.Fatal("The function is incorrect")
+ }
+ }
--- /dev/null
- "github.com/vapor/application/mov/contract"
- "github.com/vapor/consensus/segwit"
- "github.com/vapor/protocol/bc/types"
+ package common
+
+ import (
++ "github.com/bytom/vapor/application/mov/contract"
++ "github.com/bytom/vapor/consensus/segwit"
++ "github.com/bytom/vapor/protocol/bc/types"
+ )
+
+ // IsMatchedTx check if this transaction has trade mov order input
+ func IsMatchedTx(tx *types.Tx) bool {
+ if len(tx.Inputs) < 2 {
+ return false
+ }
+ for _, input := range tx.Inputs {
+ if input.InputType() == types.SpendInputType && contract.IsTradeClauseSelector(input) && segwit.IsP2WMCScript(input.ControlProgram()) {
+ return true
+ }
+ }
+ return false
+ }
+
+ // 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()) {
+ return true
+ }
+ }
+ return false
+ }
--- /dev/null
- "github.com/vapor/protocol/bc/types"
- "github.com/vapor/protocol/vm"
+ package contract
+
+ import (
+ "encoding/hex"
+
++ "github.com/bytom/vapor/protocol/bc/types"
++ "github.com/bytom/vapor/protocol/vm"
+ )
+
+ const (
+ sizeOfCancelClauseArgs = 3
+ sizeOfPartialTradeClauseArgs = 3
+ sizeOfFullTradeClauseArgs = 2
+ )
+
+ // smart contract clause select for differnet unlock method
+ const (
+ PartialTradeClauseSelector int64 = iota
+ FullTradeClauseSelector
+ CancelClauseSelector
+ )
+
+ // IsCancelClauseSelector check if input select cancel clause
+ func IsCancelClauseSelector(input *types.TxInput) bool {
+ return len(input.Arguments()) == sizeOfCancelClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments())-1]) == hex.EncodeToString(vm.Int64Bytes(CancelClauseSelector))
+ }
+
+ // IsTradeClauseSelector check if input select is partial trade clause or full trade clause
+ func IsTradeClauseSelector(input *types.TxInput) bool {
+ return IsPartialTradeClauseSelector(input) || IsFullTradeClauseSelector(input)
+ }
+
+ // IsPartialTradeClauseSelector check if input select partial trade clause
+ func IsPartialTradeClauseSelector(input *types.TxInput) bool {
+ return len(input.Arguments()) == sizeOfPartialTradeClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments())-1]) == hex.EncodeToString(vm.Int64Bytes(PartialTradeClauseSelector))
+ }
+
+ // IsFullTradeClauseSelector check if input select full trade clause
+ func IsFullTradeClauseSelector(input *types.TxInput) bool {
+ return len(input.Arguments()) == sizeOfFullTradeClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments())-1]) == hex.EncodeToString(vm.Int64Bytes(FullTradeClauseSelector))
+ }
--- /dev/null
- "github.com/vapor/application/mov/common"
- "github.com/vapor/protocol/bc"
+ package database
+
+ import (
+ log "github.com/sirupsen/logrus"
+
++ "github.com/bytom/vapor/application/mov/common"
++ "github.com/bytom/vapor/protocol/bc"
+ )
+
+ // TradePairIterator wrap read trade pair from DB action
+ type TradePairIterator struct {
+ movStore MovStore
+ tradePairs []*common.TradePair
+ tradePairIndex int
+ }
+
+ // NewTradePairIterator create the new TradePairIterator object
+ func NewTradePairIterator(movStore MovStore) *TradePairIterator {
+ return &TradePairIterator{movStore: movStore}
+ }
+
+ // HasNext check if there are more trade pairs in memory or DB
+ func (t *TradePairIterator) HasNext() bool {
+ tradePairSize := len(t.tradePairs)
+ if t.tradePairIndex < tradePairSize {
+ return true
+ }
+
+ var fromAssetID, toAssetID *bc.AssetID
+ if len(t.tradePairs) > 0 {
+ lastTradePair := t.tradePairs[tradePairSize-1]
+ fromAssetID, toAssetID = lastTradePair.FromAssetID, lastTradePair.ToAssetID
+ }
+
+ tradePairs, err := t.movStore.ListTradePairsWithStart(fromAssetID, toAssetID)
+ if err != nil {
+ // If the error is returned, it's an error of program itself,
+ // and cannot be recovered, so panic directly.
+ log.WithField("err", err).Fatal("fail to list trade pairs")
+ }
+
+ if len(tradePairs) == 0 {
+ return false
+ }
+
+ t.tradePairs = tradePairs
+ t.tradePairIndex = 0
+ return true
+ }
+
+ // Next return the next available trade pair in memory or DB
+ func (t *TradePairIterator) Next() *common.TradePair {
+ if !t.HasNext() {
+ return nil
+ }
+
+ tradePair := t.tradePairs[t.tradePairIndex]
+ t.tradePairIndex++
+ return tradePair
+ }
+
+ // OrderIterator wrap read order from DB action
+ type OrderIterator struct {
+ movStore MovStore
+ lastOrder *common.Order
+ orders []*common.Order
+ }
+
+ // NewOrderIterator create the new OrderIterator object
+ func NewOrderIterator(movStore MovStore, tradePair *common.TradePair) *OrderIterator {
+ return &OrderIterator{
+ movStore: movStore,
+ lastOrder: &common.Order{FromAssetID: tradePair.FromAssetID, ToAssetID: tradePair.ToAssetID},
+ }
+ }
+
+ // HasNext check if there are more orders in memory or DB
+ func (o *OrderIterator) HasNext() bool {
+ if len(o.orders) == 0 {
+ orders, err := o.movStore.ListOrders(o.lastOrder)
+ if err != nil {
+ log.WithField("err", err).Fatal("fail to list orders")
+ }
+
+ if len(orders) == 0 {
+ return false
+ }
+
+ o.orders = orders
+ o.lastOrder = o.orders[len(o.orders)-1]
+ }
+ return true
+ }
+
+ // NextBatch return the next batch of orders in memory or DB
+ func (o *OrderIterator) NextBatch() []*common.Order {
+ if !o.HasNext() {
+ return nil
+ }
+
+ orders := o.orders
+ o.orders = nil
+ return orders
+ }
--- /dev/null
- "github.com/vapor/application/mov/common"
- "github.com/vapor/application/mov/mock"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/testutil"
+ package database
+
+ import (
+ "testing"
+
++ "github.com/bytom/vapor/application/mov/common"
++ "github.com/bytom/vapor/application/mov/mock"
++ "github.com/bytom/vapor/protocol/bc"
++ "github.com/bytom/vapor/testutil"
+ )
+
+ var (
+ asset1 = bc.NewAssetID([32]byte{1})
+ asset2 = bc.NewAssetID([32]byte{2})
+ asset3 = bc.NewAssetID([32]byte{3})
+ asset4 = bc.NewAssetID([32]byte{4})
+
+ order1 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, Rate: 0.1}
+ order2 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, Rate: 0.2}
+ order3 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, Rate: 0.3}
+ order4 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, Rate: 0.4}
+ order5 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, Rate: 0.5}
+ )
+
+ func TestTradePairIterator(t *testing.T) {
+ cases := []struct {
+ desc string
+ storeTradePairs []*common.TradePair
+ wantTradePairs []*common.TradePair
+ }{
+ {
+ desc: "normal case",
+ storeTradePairs: []*common.TradePair{
+ {
+ FromAssetID: &asset1,
+ ToAssetID: &asset2,
+ },
+ {
+ FromAssetID: &asset1,
+ ToAssetID: &asset3,
+ },
+ {
+ FromAssetID: &asset1,
+ ToAssetID: &asset4,
+ },
+ },
+ wantTradePairs: []*common.TradePair{
+ {
+ FromAssetID: &asset1,
+ ToAssetID: &asset2,
+ },
+ {
+ FromAssetID: &asset1,
+ ToAssetID: &asset3,
+ },
+ {
+ FromAssetID: &asset1,
+ ToAssetID: &asset4,
+ },
+ },
+ },
+ {
+ desc: "num of trade pairs more than one return",
+ storeTradePairs: []*common.TradePair{
+ {
+ FromAssetID: &asset1,
+ ToAssetID: &asset2,
+ },
+ {
+ FromAssetID: &asset1,
+ ToAssetID: &asset3,
+ },
+ {
+ FromAssetID: &asset1,
+ ToAssetID: &asset4,
+ },
+ {
+ FromAssetID: &asset2,
+ ToAssetID: &asset1,
+ },
+ },
+ wantTradePairs: []*common.TradePair{
+ {
+ FromAssetID: &asset1,
+ ToAssetID: &asset2,
+ },
+ {
+ FromAssetID: &asset1,
+ ToAssetID: &asset3,
+ },
+ {
+ FromAssetID: &asset1,
+ ToAssetID: &asset4,
+ },
+ {
+ FromAssetID: &asset2,
+ ToAssetID: &asset1,
+ },
+ },
+ },
+ {
+ desc: "store is empty",
+ storeTradePairs: []*common.TradePair{},
+ wantTradePairs: []*common.TradePair{},
+ },
+ }
+
+ for i, c := range cases {
+ store := mock.NewMovStore(c.storeTradePairs, nil)
+ var gotTradePairs []*common.TradePair
+ iterator := NewTradePairIterator(store)
+ for iterator.HasNext() {
+ gotTradePairs = append(gotTradePairs, iterator.Next())
+ }
+ if !testutil.DeepEqual(c.wantTradePairs, gotTradePairs) {
+ t.Errorf("#%d(%s):got trade pairs is not equals want trade pairs", i, c.desc)
+ }
+ }
+ }
+
+ func TestOrderIterator(t *testing.T) {
+ cases := []struct {
+ desc string
+ tradePair *common.TradePair
+ storeOrders []*common.Order
+ wantOrders []*common.Order
+ }{
+ {
+ desc: "normal case",
+ tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2},
+ storeOrders: []*common.Order{order1, order2, order3},
+ wantOrders: []*common.Order{order1, order2, order3},
+ },
+ {
+ desc: "num of orders more than one return",
+ tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2},
+ storeOrders: []*common.Order{order1, order2, order3, order4, order5},
+ wantOrders: []*common.Order{order1, order2, order3, order4, order5},
+ },
+ {
+ desc: "only one order",
+ tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2},
+ storeOrders: []*common.Order{order1},
+ wantOrders: []*common.Order{order1},
+ },
+ {
+ desc: "store is empty",
+ tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2},
+ storeOrders: []*common.Order{},
+ wantOrders: []*common.Order{},
+ },
+ }
+
+ for i, c := range cases {
+ store := mock.NewMovStore(nil, c.storeOrders)
+
+ var gotOrders []*common.Order
+ iterator := NewOrderIterator(store, c.tradePair)
+ for iterator.HasNext() {
+ gotOrders = append(gotOrders, iterator.NextBatch()...)
+ }
+ if !testutil.DeepEqual(c.wantOrders, gotOrders) {
+ t.Errorf("#%d(%s):got orders it not equals want orders", i, c.desc)
+ }
+ }
+ }
"errors"
"math"
- "github.com/vapor/application/mov/common"
- dbm "github.com/vapor/database/leveldb"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/bc/types"
+ "github.com/bytom/vapor/application/mov/common"
+ dbm "github.com/bytom/vapor/database/leveldb"
+ "github.com/bytom/vapor/protocol/bc"
+ "github.com/bytom/vapor/protocol/bc/types"
)
+ // MovStore is the interface for mov's persistent storage
+ type MovStore interface {
+ GetMovDatabaseState() (*common.MovDatabaseState, error)
+ InitDBState(height uint64, hash *bc.Hash) error
+ ListOrders(orderAfter *common.Order) ([]*common.Order, error)
+ ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error)
+ ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error
+ }
+
const (
- order byte = iota
+ order byte = iota + 1
tradePair
matchStatus
"github.com/stretchr/testify/require"
- "github.com/vapor/application/mov/common"
- "github.com/vapor/database/leveldb"
- dbm "github.com/vapor/database/leveldb"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/bc/types"
- "github.com/vapor/testutil"
+ "github.com/bytom/vapor/application/mov/common"
- "github.com/bytom/vapor/consensus"
+ "github.com/bytom/vapor/database/leveldb"
+ dbm "github.com/bytom/vapor/database/leveldb"
- chainjson "github.com/bytom/vapor/encoding/json"
+ "github.com/bytom/vapor/protocol/bc"
+ "github.com/bytom/vapor/protocol/bc/types"
+ "github.com/bytom/vapor/testutil"
)
var (
--- /dev/null
- "github.com/vapor/application/mov/common"
- "github.com/vapor/application/mov/contract"
- "github.com/vapor/consensus/segwit"
- "github.com/vapor/errors"
- vprMath "github.com/vapor/math"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/bc/types"
- "github.com/vapor/protocol/vm"
- "github.com/vapor/protocol/vm/vmutil"
+ package match
+
+ import (
+ "encoding/hex"
+ "math"
+ "math/big"
+
++ "github.com/bytom/vapor/application/mov/common"
++ "github.com/bytom/vapor/application/mov/contract"
++ "github.com/bytom/vapor/consensus/segwit"
++ "github.com/bytom/vapor/errors"
++ vprMath "github.com/bytom/vapor/math"
++ "github.com/bytom/vapor/protocol/bc"
++ "github.com/bytom/vapor/protocol/bc/types"
++ "github.com/bytom/vapor/protocol/vm"
++ "github.com/bytom/vapor/protocol/vm/vmutil"
+ )
+
+ // Engine is used to generate math transactions
+ type Engine struct {
+ orderTable *OrderTable
+ maxFeeRate float64
+ nodeProgram []byte
+ }
+
+ // NewEngine return a new Engine
+ func NewEngine(orderTable *OrderTable, maxFeeRate float64, nodeProgram []byte) *Engine {
+ return &Engine{orderTable: orderTable, maxFeeRate: maxFeeRate, nodeProgram: nodeProgram}
+ }
+
+ // HasMatchedTx check does the input trade pair can generate a match deal
+ func (e *Engine) HasMatchedTx(tradePairs ...*common.TradePair) bool {
+ if err := validateTradePairs(tradePairs); err != nil {
+ return false
+ }
+
+ orders := e.peekOrders(tradePairs)
+ if len(orders) == 0 {
+ return false
+ }
+
+ return isMatched(orders)
+ }
+
+ // NextMatchedTx return the next matchable transaction by the specified trade pairs
+ // the size of trade pairs at least 2, and the sequence of trade pairs can form a loop
+ // for example, [assetA -> assetB, assetB -> assetC, assetC -> assetA]
+ func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, error) {
+ if !e.HasMatchedTx(tradePairs...) {
+ return nil, errors.New("the specified trade pairs can not be matched")
+ }
+
+ tx, err := e.buildMatchTx(e.peekOrders(tradePairs))
+ if err != nil {
+ return nil, err
+ }
+
+ for _, tradePair := range tradePairs {
+ e.orderTable.PopOrder(tradePair)
+ }
+
+ if err := e.addPartialTradeOrder(tx); err != nil {
+ return nil, err
+ }
+ return tx, nil
+ }
+
+ func (e *Engine) addMatchTxFeeOutput(txData *types.TxData) error {
+ txFee, err := CalcMatchedTxFee(txData, e.maxFeeRate)
+ if err != nil {
+ return err
+ }
+
+ for assetID, matchTxFee := range txFee {
+ feeAmount, reminder := matchTxFee.FeeAmount, int64(0)
+ if matchTxFee.FeeAmount > matchTxFee.MaxFeeAmount {
+ feeAmount = matchTxFee.MaxFeeAmount
+ reminder = matchTxFee.FeeAmount - matchTxFee.MaxFeeAmount
+ }
+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, uint64(feeAmount), e.nodeProgram))
+
+ // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
+ averageAmount := reminder / int64(len(txData.Inputs))
+ if averageAmount == 0 {
+ averageAmount = 1
+ }
+
+ for i := 0; i < len(txData.Inputs) && reminder > 0; i++ {
+ contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram())
+ if err != nil {
+ return err
+ }
+
+ if i == len(txData.Inputs)-1 {
+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, uint64(reminder), contractArgs.SellerProgram))
+ } else {
+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, uint64(averageAmount), contractArgs.SellerProgram))
+ }
+ reminder -= averageAmount
+ }
+ }
+ return nil
+ }
+
+ func (e *Engine) addPartialTradeOrder(tx *types.Tx) error {
+ for i, output := range tx.Outputs {
+ if !segwit.IsP2WMCScript(output.ControlProgram()) {
+ continue
+ }
+
+ order, err := common.NewOrderFromOutput(tx, i)
+ if err != nil {
+ return err
+ }
+
+ if err := e.orderTable.AddOrder(order); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
+ func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
+ txData := &types.TxData{Version: 1}
+ for i, 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)
+
+ oppositeOrder := orders[calcOppositeIndex(len(orders), i)]
+ if err := addMatchTxOutput(txData, input, order, oppositeOrder.Utxo.Amount); err != nil {
+ return nil, err
+ }
+ }
+
+ if err := e.addMatchTxFeeOutput(txData); err != nil {
+ return nil, err
+ }
+
+ byteData, err := txData.MarshalText()
+ if err != nil {
+ return nil, err
+ }
+
+ txData.SerializedSize = uint64(len(byteData))
+ return types.NewTx(*txData), nil
+ }
+
+ func (e *Engine) peekOrders(tradePairs []*common.TradePair) []*common.Order {
+ var orders []*common.Order
+ for _, tradePair := range tradePairs {
+ order := e.orderTable.PeekOrder(tradePair)
+ if order == nil {
+ return nil
+ }
+
+ orders = append(orders, order)
+ }
+ return orders
+ }
+
+ // MatchedTxFee is object to record the mov tx's fee information
+ type MatchedTxFee struct {
+ MaxFeeAmount int64
+ FeeAmount int64
+ }
+
+ // CalcMatchedTxFee is used to calculate tx's MatchedTxFees
+ func CalcMatchedTxFee(txData *types.TxData, maxFeeRate float64) (map[bc.AssetID]*MatchedTxFee, error) {
+ assetFeeMap := make(map[bc.AssetID]*MatchedTxFee)
+ dealProgMaps := make(map[string]bool)
+
+ for _, input := range txData.Inputs {
+ assetFeeMap[input.AssetID()] = &MatchedTxFee{FeeAmount: int64(input.AssetAmount().Amount)}
+ contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
+ if err != nil {
+ return nil, err
+ }
+
+ dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
+ }
+
+ for _, input := range txData.Inputs {
+ contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
+ if err != nil {
+ return nil, err
+ }
+
+ oppositeAmount := uint64(assetFeeMap[contractArgs.RequestedAsset].FeeAmount)
+ receiveAmount := vprMath.MinUint64(CalcRequestAmount(input.Amount(), contractArgs), oppositeAmount)
+ assetFeeMap[input.AssetID()].MaxFeeAmount = calcMaxFeeAmount(calcShouldPayAmount(receiveAmount, contractArgs), maxFeeRate)
+ }
+
+ for _, output := range txData.Outputs {
+ assetAmount := output.AssetAmount()
+ if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
+ assetFeeMap[*assetAmount.AssetId].FeeAmount -= int64(assetAmount.Amount)
+ if assetFeeMap[*assetAmount.AssetId].FeeAmount <= 0 {
+ delete(assetFeeMap, *assetAmount.AssetId)
+ }
+ }
+ }
+ return assetFeeMap, nil
+ }
+
+ func addMatchTxOutput(txData *types.TxData, txInput *types.TxInput, order *common.Order, oppositeAmount uint64) error {
+ contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
+ if err != nil {
+ return err
+ }
+
+ requestAmount := CalcRequestAmount(order.Utxo.Amount, contractArgs)
+ receiveAmount := vprMath.MinUint64(requestAmount, oppositeAmount)
+ shouldPayAmount := calcShouldPayAmount(receiveAmount, contractArgs)
+ isPartialTrade := requestAmount > receiveAmount
+
+ setMatchTxArguments(txInput, isPartialTrade, len(txData.Outputs), receiveAmount)
+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, receiveAmount, contractArgs.SellerProgram))
+ if isPartialTrade {
+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram))
+ }
+ return nil
+ }
+
+ 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))
+ if !res.IsUint64() {
+ return 0
+ }
+ return res.Uint64()
+ }
+
+ func calcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
+ res := big.NewInt(0).SetUint64(receiveAmount)
+ res.Mul(res, big.NewInt(contractArg.RatioDenominator)).Quo(res, big.NewInt(contractArg.RatioNumerator))
+ if !res.IsUint64() {
+ return 0
+ }
+ return res.Uint64()
+ }
+
+ func calcMaxFeeAmount(shouldPayAmount uint64, maxFeeRate float64) int64 {
+ return int64(math.Ceil(float64(shouldPayAmount) * maxFeeRate))
+ }
+
+ func calcOppositeIndex(size int, selfIdx int) int {
+ return (selfIdx + 1) % size
+ }
+
+ func isMatched(orders []*common.Order) bool {
+ for i, order := range orders {
+ if oppositeOrder := orders[calcOppositeIndex(len(orders), i)]; 1/order.Rate < oppositeOrder.Rate {
+ return false
+ }
+ }
+ return true
+ }
+
+ func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) {
+ var arguments [][]byte
+ if isPartialTrade {
+ arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts)), vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.PartialTradeClauseSelector)}
+ } else {
+ arguments = [][]byte{vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.FullTradeClauseSelector)}
+ }
+ txInput.SetArguments(arguments)
+ }
+
+ func validateTradePairs(tradePairs []*common.TradePair) error {
+ if len(tradePairs) < 2 {
+ return errors.New("size of trade pairs at least 2")
+ }
+
+ for i, tradePair := range tradePairs {
+ oppositeTradePair := tradePairs[calcOppositeIndex(len(tradePairs), i)]
+ if *tradePair.ToAssetID != *oppositeTradePair.FromAssetID {
+ return errors.New("specified trade pairs is invalid")
+ }
+ }
+ return nil
+ }
--- /dev/null
- "github.com/vapor/protocol/bc"
- "github.com/vapor/testutil"
-
- "github.com/vapor/application/mov/common"
- "github.com/vapor/application/mov/mock"
- "github.com/vapor/protocol/bc/types"
+ package match
+
+ import (
+ "testing"
+
++ "github.com/bytom/vapor/protocol/bc"
++ "github.com/bytom/vapor/testutil"
++ "github.com/bytom/vapor/application/mov/common"
++ "github.com/bytom/vapor/application/mov/mock"
++ "github.com/bytom/vapor/protocol/bc/types"
+ )
+
+ /*
+ Test: validateTradePairs vaild and invaild case for 2, 3 trade pairs
+ */
+ func TestGenerateMatchedTxs(t *testing.T) {
+ btc2eth := &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH}
+ eth2btc := &common.TradePair{FromAssetID: &mock.ETH, ToAssetID: &mock.BTC}
+
+ cases := []struct {
+ desc string
+ tradePair *common.TradePair
+ initStoreOrders []*common.Order
+ wantMatchedTxs []*types.Tx
+ }{
+ {
+ desc: "full matched",
+ tradePair: &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH},
+ initStoreOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+ mock.Eth2BtcOrders[0],
+ },
+ wantMatchedTxs: []*types.Tx{
+ mock.MatchedTxs[1],
+ },
+ },
+ {
+ desc: "partial matched",
+ tradePair: &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH},
+ initStoreOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+ mock.Eth2BtcOrders[1],
+ },
+ wantMatchedTxs: []*types.Tx{
+ mock.MatchedTxs[0],
+ },
+ },
+ {
+ desc: "partial matched and continue to match",
+ tradePair: &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH},
+ initStoreOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+ mock.Eth2BtcOrders[2],
+ },
+ wantMatchedTxs: []*types.Tx{
+ mock.MatchedTxs[2],
+ mock.MatchedTxs[3],
+ },
+ },
+ {
+ desc: "unable to match",
+ tradePair: &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH},
+ initStoreOrders: []*common.Order{
+ mock.Btc2EthOrders[1],
+ mock.Eth2BtcOrders[0],
+ },
+ wantMatchedTxs: []*types.Tx{},
+ },
+ }
+
+ for i, c := range cases {
+ movStore := mock.NewMovStore([]*common.TradePair{btc2eth, eth2btc}, c.initStoreOrders)
+ matchEngine := NewEngine(NewOrderTable(movStore, nil, nil), 0.05, mock.NodeProgram)
+ var gotMatchedTxs []*types.Tx
+ for matchEngine.HasMatchedTx(c.tradePair, c.tradePair.Reverse()) {
+ matchedTx, err := matchEngine.NextMatchedTx(c.tradePair, c.tradePair.Reverse())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ gotMatchedTxs = append(gotMatchedTxs, matchedTx)
+ }
+
+ if len(c.wantMatchedTxs) != len(gotMatchedTxs) {
+ t.Errorf("#%d(%s) the length of got matched tx is not equals want matched tx", i, c.desc)
+ continue
+ }
+
+ for i, gotMatchedTx := range gotMatchedTxs {
+ c.wantMatchedTxs[i].Version = 1
+ byteData, err := c.wantMatchedTxs[i].MarshalText()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ c.wantMatchedTxs[i].SerializedSize = uint64(len(byteData))
+ wantMatchedTx := types.NewTx(c.wantMatchedTxs[i].TxData)
+ if gotMatchedTx.ID != wantMatchedTx.ID {
+ t.Errorf("#%d(%s) the tx hash of got matched tx: %s is not equals want matched tx: %s", i, c.desc, gotMatchedTx.ID.String(), wantMatchedTx.ID.String())
+ }
+ }
+ }
+ }
+
+ func TestCalcMatchedTxFee(t *testing.T) {
+ cases := []struct {
+ desc string
+ tx *types.TxData
+ maxFeeRate float64
+ wantMatchedTxFee map[bc.AssetID]*MatchedTxFee
+ }{
+ {
+ desc: "fee less than max fee",
+ maxFeeRate: 0.05,
+ wantMatchedTxFee: map[bc.AssetID]*MatchedTxFee{mock.ETH: {FeeAmount: 10, MaxFeeAmount: 26}},
+ tx: &mock.MatchedTxs[1].TxData,
+ },
+ {
+ desc: "fee refund in tx",
+ maxFeeRate: 0.05,
+ wantMatchedTxFee: map[bc.AssetID]*MatchedTxFee{mock.ETH: {FeeAmount: 27, MaxFeeAmount: 27}},
+ tx: &mock.MatchedTxs[2].TxData,
+ },
+ {
+ desc: "fee is zero",
+ maxFeeRate: 0.05,
+ wantMatchedTxFee: map[bc.AssetID]*MatchedTxFee{},
+ tx: &mock.MatchedTxs[0].TxData,
+ },
+ }
+
+ for i, c := range cases {
+ gotMatchedTxFee, err := CalcMatchedTxFee(c.tx, c.maxFeeRate)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !testutil.DeepEqual(gotMatchedTxFee, c.wantMatchedTxFee) {
+ t.Errorf("#%d(%s):fail to caculate matched tx fee, got (%v), want (%v)", i, c.desc, gotMatchedTxFee, c.wantMatchedTxFee)
+ }
+ }
+ }
--- /dev/null
- "github.com/vapor/application/mov/common"
- "github.com/vapor/application/mov/database"
- "github.com/vapor/errors"
+ package match
+
+ import (
+ "sort"
+
++ "github.com/bytom/vapor/application/mov/common"
++ "github.com/bytom/vapor/application/mov/database"
++ "github.com/bytom/vapor/errors"
+ )
+
+ // OrderTable is used to handle the mov orders in memory like stack
+ type OrderTable struct {
+ movStore database.MovStore
+ // key of tradePair -> []order
+ dbOrders map[string][]*common.Order
+ // key of tradePair -> iterator
+ orderIterators map[string]*database.OrderIterator
+
+ // key of tradePair -> []order
+ arrivalAddOrders map[string][]*common.Order
+ // key of order -> order
+ arrivalDelOrders map[string]*common.Order
+ }
+
+ // NewOrderTable create a new OrderTable object
+ func NewOrderTable(movStore database.MovStore, arrivalAddOrders, arrivalDelOrders []*common.Order) *OrderTable {
+ return &OrderTable{
+ movStore: movStore,
+ dbOrders: make(map[string][]*common.Order),
+ orderIterators: make(map[string]*database.OrderIterator),
+
+ arrivalAddOrders: arrangeArrivalAddOrders(arrivalAddOrders),
+ arrivalDelOrders: arrangeArrivalDelOrders(arrivalDelOrders),
+ }
+ }
+
+ // AddOrder add the in memory temp order to order table
+ func (o *OrderTable) AddOrder(order *common.Order) error {
+ tradePairKey := order.TradePair().Key()
+ orders := o.arrivalAddOrders[tradePairKey]
+ if len(orders) > 0 && order.Rate > orders[len(orders)-1].Rate {
+ return errors.New("rate of order must less than the min order in order table")
+ }
+
+ o.arrivalAddOrders[tradePairKey] = append(orders, order)
+ return nil
+ }
+
+ // PeekOrder return the next lowest order of given trade pair
+ func (o *OrderTable) PeekOrder(tradePair *common.TradePair) *common.Order {
+ if len(o.dbOrders[tradePair.Key()]) == 0 {
+ o.extendDBOrders(tradePair)
+ }
+
+ var nextOrder *common.Order
+ orders := o.dbOrders[tradePair.Key()]
+ if len(orders) != 0 {
+ nextOrder = orders[len(orders)-1]
+ }
+
+ if nextOrder != nil && o.arrivalDelOrders[nextOrder.Key()] != nil {
+ o.dbOrders[tradePair.Key()] = orders[0 : len(orders)-1]
+ return o.PeekOrder(tradePair)
+ }
+
+ arrivalOrder := o.peekArrivalOrder(tradePair)
+ if nextOrder == nil || (arrivalOrder != nil && arrivalOrder.Rate < nextOrder.Rate) {
+ nextOrder = arrivalOrder
+ }
+ return nextOrder
+ }
+
+ // PopOrder delete the next lowest order of given trade pair
+ func (o *OrderTable) PopOrder(tradePair *common.TradePair) {
+ order := o.PeekOrder(tradePair)
+ if order == nil {
+ return
+ }
+
+ orders := o.dbOrders[tradePair.Key()]
+ if len(orders) != 0 && orders[len(orders)-1].Key() == order.Key() {
+ o.dbOrders[tradePair.Key()] = orders[0 : len(orders)-1]
+ }
+
+ arrivalOrders := o.arrivalAddOrders[tradePair.Key()]
+ if len(arrivalOrders) != 0 && arrivalOrders[len(arrivalOrders)-1].Key() == order.Key() {
+ o.arrivalAddOrders[tradePair.Key()] = arrivalOrders[0 : len(arrivalOrders)-1]
+ }
+ }
+
+ func arrangeArrivalAddOrders(orders []*common.Order) map[string][]*common.Order {
+ arrivalAddOrderMap := make(map[string][]*common.Order)
+ for _, order := range orders {
+ arrivalAddOrderMap[order.TradePair().Key()] = append(arrivalAddOrderMap[order.TradePair().Key()], order)
+ }
+
+ for _, orders := range arrivalAddOrderMap {
+ sort.Sort(sort.Reverse(common.OrderSlice(orders)))
+ }
+ return arrivalAddOrderMap
+ }
+
+ func arrangeArrivalDelOrders(orders []*common.Order) map[string]*common.Order {
+ arrivalDelOrderMap := make(map[string]*common.Order)
+ for _, order := range orders {
+ arrivalDelOrderMap[order.Key()] = order
+ }
+ return arrivalDelOrderMap
+ }
+
+ func (o *OrderTable) extendDBOrders(tradePair *common.TradePair) {
+ iterator, ok := o.orderIterators[tradePair.Key()]
+ if !ok {
+ iterator = database.NewOrderIterator(o.movStore, tradePair)
+ o.orderIterators[tradePair.Key()] = iterator
+ }
+
+ nextOrders := iterator.NextBatch()
+ for i := len(nextOrders) - 1; i >= 0; i-- {
+ o.dbOrders[tradePair.Key()] = append(o.dbOrders[tradePair.Key()], nextOrders[i])
+ }
+ }
+
+ func (o *OrderTable) peekArrivalOrder(tradePair *common.TradePair) *common.Order {
+ if arrivalAddOrders := o.arrivalAddOrders[tradePair.Key()]; len(arrivalAddOrders) > 0 {
+ return arrivalAddOrders[len(arrivalAddOrders)-1]
+ }
+ return nil
+ }
--- /dev/null
- "github.com/vapor/application/mov/common"
- "github.com/vapor/application/mov/database"
- "github.com/vapor/application/mov/mock"
+ package match
+
+ import (
+ "testing"
+
++ "github.com/bytom/vapor/application/mov/common"
++ "github.com/bytom/vapor/application/mov/database"
++ "github.com/bytom/vapor/application/mov/mock"
+ )
+
+ var (
+ btc2eth = &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH}
+ )
+
+ func TestOrderTable(t *testing.T) {
+ cases := []struct {
+ desc string
+ initMovStore database.MovStore
+ initArrivalAddOrders []*common.Order
+ initArrivalDelOrders []*common.Order
+ addOrders []*common.Order
+ popOrders []*common.TradePair
+ wantPeekedOrders map[common.TradePair]*common.Order
+ }{
+ {
+ desc: "no arrival orders, no add order, no pop order",
+ initMovStore: mock.NewMovStore(
+ []*common.TradePair{btc2eth},
+ []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+ },
+ ),
+ wantPeekedOrders: map[common.TradePair]*common.Order{
+ *btc2eth: mock.Btc2EthOrders[0],
+ },
+ },
+ {
+ desc: "no arrival orders, add lower price order, no pop order",
+ initMovStore: mock.NewMovStore(
+ []*common.TradePair{btc2eth},
+ []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+ }),
+ addOrders: []*common.Order{mock.Btc2EthOrders[3]},
+ wantPeekedOrders: map[common.TradePair]*common.Order{
+ *btc2eth: mock.Btc2EthOrders[3],
+ },
+ },
+ {
+ desc: "no arrival orders, no add order, pop one order",
+ initMovStore: mock.NewMovStore(
+ []*common.TradePair{btc2eth},
+ []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+ }),
+ popOrders: []*common.TradePair{btc2eth},
+ wantPeekedOrders: map[common.TradePair]*common.Order{
+ *btc2eth: mock.Btc2EthOrders[2],
+ },
+ },
+ {
+ desc: "has arrival add orders, no add order, no pop order, the arrival add order is lower price",
+ initMovStore: mock.NewMovStore(
+ []*common.TradePair{btc2eth},
+ []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+ }),
+ initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[3]},
+ wantPeekedOrders: map[common.TradePair]*common.Order{
+ *btc2eth: mock.Btc2EthOrders[3],
+ },
+ },
+ {
+ desc: "has arrival add orders, no add order, no pop order, the db add order is lower price",
+ initMovStore: mock.NewMovStore(
+ []*common.TradePair{btc2eth},
+ []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+ }),
+ initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[2]},
+ wantPeekedOrders: map[common.TradePair]*common.Order{
+ *btc2eth: mock.Btc2EthOrders[0],
+ },
+ },
+ {
+ desc: "has arrival add orders, no add order, pop one order, after pop the arrival order is lower price",
+ initMovStore: mock.NewMovStore(
+ []*common.TradePair{btc2eth},
+ []*common.Order{
+ mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ }),
+ initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ popOrders: []*common.TradePair{btc2eth},
+ wantPeekedOrders: map[common.TradePair]*common.Order{
+ *btc2eth: mock.Btc2EthOrders[0],
+ },
+ },
+ {
+ desc: "has arrival delete orders, no add order, no pop order",
+ initMovStore: mock.NewMovStore(
+ []*common.TradePair{btc2eth},
+ []*common.Order{
+ mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ }),
+ initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3]},
+ wantPeekedOrders: map[common.TradePair]*common.Order{
+ *btc2eth: mock.Btc2EthOrders[2],
+ },
+ },
+ {
+ desc: "has arrival delete orders and arrival add orders, no add order, no pop order, the arrival order is lower price",
+ initMovStore: mock.NewMovStore(
+ []*common.TradePair{btc2eth},
+ []*common.Order{
+ mock.Btc2EthOrders[3], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+ }),
+ initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3]},
+ wantPeekedOrders: map[common.TradePair]*common.Order{
+ *btc2eth: mock.Btc2EthOrders[0],
+ },
+ },
+ {
+ desc: "has arrival delete orders and arrival add orders, no add order, pop one order",
+ initMovStore: mock.NewMovStore(
+ []*common.TradePair{btc2eth},
+ []*common.Order{
+ mock.Btc2EthOrders[3], mock.Btc2EthOrders[1],
+ }),
+ initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[2]},
+ initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3]},
+ popOrders: []*common.TradePair{btc2eth},
+ wantPeekedOrders: map[common.TradePair]*common.Order{
+ *btc2eth: mock.Btc2EthOrders[2],
+ },
+ },
+ {
+ desc: "has arrival add orders, but db order is empty",
+ initMovStore: mock.NewMovStore(
+ []*common.TradePair{btc2eth},
+ []*common.Order{}),
+ initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[2]},
+ wantPeekedOrders: map[common.TradePair]*common.Order{
+ *btc2eth: mock.Btc2EthOrders[0],
+ },
+ },
+ {
+ desc: "no add orders, and db order is empty",
+ initMovStore: mock.NewMovStore(
+ []*common.TradePair{btc2eth},
+ []*common.Order{}),
+ initArrivalAddOrders: []*common.Order{},
+ wantPeekedOrders: map[common.TradePair]*common.Order{
+ *btc2eth: nil,
+ },
+ },
+ {
+ desc: "has arrival delete orders, no add order, no pop order, need recursive to peek one order",
+ initMovStore: mock.NewMovStore(
+ []*common.TradePair{btc2eth},
+ []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ }),
+ initArrivalAddOrders: []*common.Order{},
+ initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3], mock.Btc2EthOrders[0], mock.Btc2EthOrders[2]},
+ wantPeekedOrders: map[common.TradePair]*common.Order{
+ *btc2eth: mock.Btc2EthOrders[1],
+ },
+ },
+ }
+
+ for i, c := range cases {
+ orderTable := NewOrderTable(c.initMovStore, c.initArrivalAddOrders, c.initArrivalDelOrders)
+ for _, order := range c.addOrders {
+ if err := orderTable.AddOrder(order); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ for _, tradePair := range c.popOrders {
+ orderTable.PopOrder(tradePair)
+ }
+
+ for tradePair, wantOrder := range c.wantPeekedOrders {
+ gotOrder := orderTable.PeekOrder(&tradePair)
+ if wantOrder == gotOrder && wantOrder == nil {
+ continue
+ }
+
+ 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)
+ }
+ }
+ }
+ }
--- /dev/null
- "github.com/vapor/application/mov/common"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/bc/types"
- "github.com/vapor/protocol/vm"
- "github.com/vapor/protocol/vm/vmutil"
- "github.com/vapor/testutil"
+ package mock
+
+ import (
++ "github.com/bytom/vapor/application/mov/common"
++ "github.com/bytom/vapor/protocol/bc"
++ "github.com/bytom/vapor/protocol/bc/types"
++ "github.com/bytom/vapor/protocol/vm"
++ "github.com/bytom/vapor/protocol/vm/vmutil"
++ "github.com/bytom/vapor/testutil"
+ )
+
+ var (
+ BTC = bc.NewAssetID([32]byte{1})
+ ETH = bc.NewAssetID([32]byte{2})
+ NodeProgram = []byte{0x58}
+
+ Btc2EthOrders = []*common.Order{
+ {
+ FromAssetID: &BTC,
+ ToAssetID: Ð,
+ Rate: 50,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("37b8edf656e45a7addf47f5626e114a8c394d918a36f61b5a2905675a09b40ae")),
+ SourcePos: 0,
+ Amount: 10,
+ ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 50, 1),
+ },
+ },
+ {
+ FromAssetID: &BTC,
+ ToAssetID: Ð,
+ Rate: 53,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("3ec2bbfb499a8736d377b547eee5392bcddf7ec2b287e9ed20b5938c3d84e7cd")),
+ SourcePos: 0,
+ Amount: 20,
+ ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"), 53, 1),
+ },
+ },
+ {
+ FromAssetID: &BTC,
+ ToAssetID: Ð,
+ Rate: 52,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("1232bbfb499a8736d377b547eee5392bcddf7ec2b287e9ed20b5938c3d84e7cd")),
+ SourcePos: 0,
+ Amount: 15,
+ ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"), 53, 1),
+ },
+ },
+ {
+ FromAssetID: &BTC,
+ ToAssetID: Ð,
+ Rate: 49,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("7872bbfb499a8736d377b547eee5392bcddf7ec2b287e9ed20b5938c3d84e7cd")),
+ SourcePos: 0,
+ Amount: 17,
+ ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"), 53, 1),
+ },
+ },
+ }
+
+ Eth2BtcOrders = []*common.Order{
+ {
+ FromAssetID: Ð,
+ ToAssetID: &BTC,
+ Rate: 1 / 51.0,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("fba43ff5155209cb1769e2ec0e1d4a33accf899c740865edfc6d1de39b873b29")),
+ SourcePos: 0,
+ Amount: 510,
+ ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253"), 1, 51.0),
+ },
+ },
+ {
+ FromAssetID: Ð,
+ ToAssetID: &BTC,
+ Rate: 1 / 52.0,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("05f24bb847db823075d81786aa270748e02602199cd009c0284f928503846a5a")),
+ SourcePos: 0,
+ Amount: 416,
+ ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254"), 1, 52.0),
+ },
+ },
+ {
+ FromAssetID: Ð,
+ ToAssetID: &BTC,
+ Rate: 1 / 54.0,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("119a02980796dc352cf6475457463aef5666f66622088de3551fa73a65f0d201")),
+ SourcePos: 0,
+ Amount: 810,
+ ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"), 1, 54.0),
+ },
+ },
+ }
+
+ Btc2EthMakerTxs = []*types.Tx{
+ // Btc2EthOrders[0]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewSpendInput(nil, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, []byte{0x51})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.ControlProgram)},
+ }),
+ // Btc2EthOrders[1]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewSpendInput(nil, *Btc2EthOrders[1].Utxo.SourceID, *Btc2EthOrders[1].FromAssetID, Btc2EthOrders[1].Utxo.Amount, Btc2EthOrders[1].Utxo.SourcePos, []byte{0x51})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Btc2EthOrders[1].FromAssetID, Btc2EthOrders[1].Utxo.Amount, Btc2EthOrders[1].Utxo.ControlProgram)},
+ }),
+ // Btc2EthOrders[2]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewSpendInput(nil, *Btc2EthOrders[2].Utxo.SourceID, *Btc2EthOrders[2].FromAssetID, Btc2EthOrders[2].Utxo.Amount, Btc2EthOrders[2].Utxo.SourcePos, []byte{0x51})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Btc2EthOrders[2].FromAssetID, Btc2EthOrders[2].Utxo.Amount, Btc2EthOrders[2].Utxo.ControlProgram)},
+ }),
+ // Btc2EthOrders[3]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewSpendInput(nil, *Btc2EthOrders[3].Utxo.SourceID, *Btc2EthOrders[3].FromAssetID, Btc2EthOrders[3].Utxo.Amount, Btc2EthOrders[3].Utxo.SourcePos, []byte{0x51})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Btc2EthOrders[3].FromAssetID, Btc2EthOrders[3].Utxo.Amount, Btc2EthOrders[3].Utxo.ControlProgram)},
+ }),
+ }
+
+ Eth2BtcMakerTxs = []*types.Tx{
+ // Eth2Btc[0]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewSpendInput(nil, *Eth2BtcOrders[0].Utxo.SourceID, *Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, Eth2BtcOrders[0].Utxo.SourcePos, []byte{0x51})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, Eth2BtcOrders[0].Utxo.ControlProgram)},
+ }),
+ // Eth2Btc[1]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewSpendInput(nil, *Eth2BtcOrders[1].Utxo.SourceID, *Eth2BtcOrders[1].FromAssetID, Eth2BtcOrders[1].Utxo.Amount, Eth2BtcOrders[1].Utxo.SourcePos, []byte{0x51})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Eth2BtcOrders[1].FromAssetID, Eth2BtcOrders[1].Utxo.Amount, Eth2BtcOrders[1].Utxo.ControlProgram)},
+ }),
+ // Eth2Btc[2]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewSpendInput(nil, *Eth2BtcOrders[2].Utxo.SourceID, *Eth2BtcOrders[2].FromAssetID, Eth2BtcOrders[2].Utxo.Amount, Eth2BtcOrders[2].Utxo.SourcePos, []byte{0x51})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, Eth2BtcOrders[2].Utxo.Amount, Eth2BtcOrders[2].Utxo.ControlProgram)},
+ }),
+ }
+
+ MatchedTxs = []*types.Tx{
+ // partial matched transaction from Btc2EthOrders[0], Eth2BtcOrders[1]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ 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),
+ 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, 416, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ // re-order
+ types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 2, Btc2EthOrders[0].Utxo.ControlProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254")),
+ },
+ }),
+
+ // full matched transaction from Btc2EthOrders[0], Eth2BtcOrders[0]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ 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),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *Eth2BtcOrders[0].Utxo.SourceID, *Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, Eth2BtcOrders[0].Utxo.SourcePos, Eth2BtcOrders[0].Utxo.ControlProgram),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 10, NodeProgram),
+ },
+ }),
+
+ // partial matched transaction from Btc2EthOrders[0], Eth2BtcOrders[2]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ 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),
+ 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{
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 10, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+ // re-order
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 270, Eth2BtcOrders[2].Utxo.ControlProgram),
+ // fee
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 27, NodeProgram),
+ // refund
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 6, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 7, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+ },
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ 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),
+ 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.NewIntraChainOutput(*Btc2EthOrders[1].ToAssetID, 270, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")),
+ // re-order
+ types.NewIntraChainOutput(*Btc2EthOrders[1].FromAssetID, 15, Btc2EthOrders[1].Utxo.ControlProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 5, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+ },
+ }),
+
+ // partial matched transaction from Btc2EthMakerTxs[0], Eth2BtcMakerTxs[1]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Btc2EthMakerTxs[0], 0).Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, 0, Btc2EthOrders[0].Utxo.ControlProgram),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Eth2BtcMakerTxs[1], 0).Utxo.SourceID, *Eth2BtcOrders[1].FromAssetID, Eth2BtcOrders[1].Utxo.Amount, 0, Eth2BtcOrders[1].Utxo.ControlProgram),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 416, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ // re-order
+ types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 2, Btc2EthOrders[0].Utxo.ControlProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254")),
+ },
+ }),
+ }
+ )
+
+ func MustCreateP2WMCProgram(requestAsset bc.AssetID, sellerProgram []byte, ratioNumerator, ratioDenominator int64) []byte {
+ contractArgs := 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)
+ if err != nil {
+ panic(err)
+ }
+
+ return order
+ }
+
+ func hashPtr(hash bc.Hash) *bc.Hash {
+ return &hash
+ }
--- /dev/null
- "github.com/vapor/application/mov/common"
- "github.com/vapor/errors"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/bc/types"
+ package mock
+
+ import (
+ "sort"
+
++ "github.com/bytom/vapor/application/mov/common"
++ "github.com/bytom/vapor/errors"
++ "github.com/bytom/vapor/protocol/bc"
++ "github.com/bytom/vapor/protocol/bc/types"
+ )
+
+ type MovStore struct {
+ tradePairs []*common.TradePair
+ orderMap map[string][]*common.Order
+ dbState *common.MovDatabaseState
+ }
+
+ func NewMovStore(tradePairs []*common.TradePair, orders []*common.Order) *MovStore {
+ orderMap := make(map[string][]*common.Order)
+ for _, order := range orders {
+ orderMap[order.TradePair().Key()] = append(orderMap[order.TradePair().Key()], order)
+ }
+
+ for _, orders := range orderMap {
+ sort.Sort(common.OrderSlice(orders))
+ }
+ return &MovStore{
+ tradePairs: tradePairs,
+ orderMap: orderMap,
+ }
+ }
+
+ func (m *MovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
+ return m.dbState, nil
+ }
+
+ func (m *MovStore) InitDBState(height uint64, hash *bc.Hash) error {
+ return nil
+ }
+
+ func (m *MovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
+ tradePair := &common.TradePair{FromAssetID: orderAfter.FromAssetID, ToAssetID: orderAfter.ToAssetID}
+ orders := m.orderMap[tradePair.Key()]
+ begin := len(orders)
+ if orderAfter.Rate == 0 {
+ begin = 0
+ } else {
+ for i, order := range orders {
+ if order.Rate == orderAfter.Rate {
+ begin = i + 1
+ break
+ }
+ }
+ }
+ var result []*common.Order
+ for i := begin; i < len(orders) && len(result) < 3; i++ {
+ result = append(result, orders[i])
+ }
+ return result, nil
+ }
+
+ func (m *MovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
+ begin := len(m.tradePairs)
+ if fromAssetIDAfter == nil || toAssetIDAfter == nil {
+ begin = 0
+ } else {
+ for i, tradePair := range m.tradePairs {
+ if *tradePair.FromAssetID == *fromAssetIDAfter && *tradePair.ToAssetID == *toAssetIDAfter {
+ begin = i + 1
+ break
+ }
+ }
+ }
+ var result []*common.TradePair
+ for i := begin; i < len(m.tradePairs) && len(result) < 3; i++ {
+ result = append(result, m.tradePairs[i])
+ }
+ return result, nil
+ }
+
+ func (m *MovStore) ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error {
+ for _, order := range addOrders {
+ tradePair := &common.TradePair{FromAssetID: order.FromAssetID, ToAssetID: order.ToAssetID}
+ m.orderMap[tradePair.Key()] = append(m.orderMap[tradePair.Key()], order)
+ }
+ for _, delOrder := range delOrders {
+ tradePair := &common.TradePair{FromAssetID: delOrder.FromAssetID, ToAssetID: delOrder.ToAssetID}
+ orders := m.orderMap[tradePair.Key()]
+ for i, order := range orders {
+ if delOrder.Key() == order.Key() {
+ m.orderMap[tradePair.Key()] = append(orders[0:i], orders[i+1:]...)
+ }
+ }
+ }
+ for _, orders := range m.orderMap {
+ sort.Sort(common.OrderSlice(orders))
+ }
+
+ if blockHeader.Height == m.dbState.Height {
+ m.dbState = &common.MovDatabaseState{Height: blockHeader.Height - 1, Hash: &blockHeader.PreviousBlockHash}
+ } else if blockHeader.Height == m.dbState.Height+1 {
+ blockHash := blockHeader.Hash()
+ m.dbState = &common.MovDatabaseState{Height: blockHeader.Height, Hash: &blockHash}
+ } else {
+ return errors.New("error block header")
+ }
+ return nil
+ }
--- /dev/null
- "github.com/vapor/application/mov/common"
- "github.com/vapor/application/mov/contract"
- "github.com/vapor/application/mov/database"
- "github.com/vapor/application/mov/match"
- "github.com/vapor/consensus/segwit"
- dbm "github.com/vapor/database/leveldb"
- "github.com/vapor/errors"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/bc/types"
+ package mov
+
+ import (
++ "github.com/bytom/vapor/application/mov/common"
++ "github.com/bytom/vapor/application/mov/contract"
++ "github.com/bytom/vapor/application/mov/database"
++ "github.com/bytom/vapor/application/mov/match"
++ "github.com/bytom/vapor/consensus/segwit"
++ dbm "github.com/bytom/vapor/database/leveldb"
++ "github.com/bytom/vapor/errors"
++ "github.com/bytom/vapor/protocol/bc"
++ "github.com/bytom/vapor/protocol/bc/types"
+ )
+
+ const maxFeeRate = 0.05
+
+ var (
+ errInvalidTradePairs = errors.New("The trade pairs in the tx input is invalid")
+ errStatusFailMustFalse = errors.New("status fail of transaction does not allow to be true")
+ errInputProgramMustP2WMCScript = errors.New("input program of trade tx must p2wmc script")
+ errExistCancelOrderInMatchedTx = errors.New("can't exist cancel order in the matched transaction")
+ errExistTradeInCancelOrderTx = errors.New("can't exist trade in the cancel order transaction")
+ errAmountOfFeeGreaterThanMaximum = errors.New("amount of fee greater than max fee amount")
+ errAssetIDMustUniqueInMatchedTx = errors.New("asset id must unique in matched transaction")
+ errRatioOfTradeLessThanZero = errors.New("ratio arguments must greater than zero")
+ errLengthOfInputIsIncorrect = errors.New("length of matched tx input is not equals to actual matched tx input")
+ errSpendOutputIDIsIncorrect = errors.New("spend output id of matched tx is not equals to actual matched tx")
+ errRequestAmountMath = errors.New("request amount of order less than one or big than max of int64")
+ )
+
+ // MovCore represent the core logic of the match module, which include generate match transactions before packing the block,
+ // verify the match transaction in block is correct, and update the order table according to the transaction.
+ type MovCore struct {
+ movStore database.MovStore
+ startBlockHeight uint64
+ }
+
+ // NewMovCore return a instance of MovCore by path of mov db
+ func NewMovCore(dbBackend, dbDir string, startBlockHeight uint64) *MovCore {
+ movDB := dbm.NewDB("mov", dbBackend, dbDir)
+ return &MovCore{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
+ }
+
+ // ApplyBlock parse pending order and cancel from the the transactions of block
+ // and add pending order to the dex db, remove cancel order from dex db.
+ func (m *MovCore) ApplyBlock(block *types.Block) error {
+ if block.Height < m.startBlockHeight {
+ return nil
+ }
+
+ if block.Height == m.startBlockHeight {
+ blockHash := block.Hash()
+ 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)
+ if err != nil {
+ return err
+ }
+
+ return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
+ }
+
+ /*
+ @issue: I have two orders A and B, order A's seller program is order B and order B's seller program is order A.
+ Assume consensus node accept 0% fee and This two orders are the only two order of this trading pair, will this
+ become an infinite loop and DDoS attacks the whole network?
+ */
+ // BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
+ func (m *MovCore) BeforeProposalBlock(txs []*types.Tx, nodeProgram []byte, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
+ if blockHeight <= m.startBlockHeight {
+ return nil, nil
+ }
+
+ orderTable, err := buildOrderTable(m.movStore, txs)
+ if err != nil {
+ return nil, err
+ }
+
+ matchEngine := match.NewEngine(orderTable, maxFeeRate, nodeProgram)
+ tradePairMap := make(map[string]bool)
+ tradePairIterator := database.NewTradePairIterator(m.movStore)
+
+ var packagedTxs []*types.Tx
+ for gasLeft > 0 && !isTimeout() && tradePairIterator.HasNext() {
+ tradePair := tradePairIterator.Next()
+ if tradePairMap[tradePair.Key()] {
+ continue
+ }
+ tradePairMap[tradePair.Key()] = true
+ tradePairMap[tradePair.Reverse().Key()] = true
+
+ for gasLeft > 0 && !isTimeout() && matchEngine.HasMatchedTx(tradePair, tradePair.Reverse()) {
+ matchedTx, err := matchEngine.NextMatchedTx(tradePair, tradePair.Reverse())
+ if err != nil {
+ return nil, err
+ }
+
+ gasUsed := calcMatchedTxGasUsed(matchedTx)
+ if gasLeft-gasUsed >= 0 {
+ packagedTxs = append(packagedTxs, matchedTx)
+ }
+ gasLeft -= gasUsed
+ }
+ }
+ return packagedTxs, nil
+ }
+
+ // ChainStatus return the current block height and block hash in dex core
+ func (m *MovCore) ChainStatus() (uint64, *bc.Hash, error) {
+ state, err := m.movStore.GetMovDatabaseState()
+ if err != nil {
+ return 0, nil, err
+ }
+
+ return state.Height, state.Hash, nil
+ }
+
+ // 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 {
+ return nil
+ }
+
+ deleteOrders, addOrders, err := applyTransactions(block.Transactions)
+ if err != nil {
+ return err
+ }
+
+ return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
+ }
+
+ // IsDust block the transaction that are not generated by the match engine
+ func (m *MovCore) IsDust(tx *types.Tx) bool {
+ for _, input := range tx.Inputs {
+ if segwit.IsP2WMCScript(input.ControlProgram()) && !contract.IsCancelClauseSelector(input) {
+ return true
+ }
+ }
+ return false
+ }
+
+ // Name return the name of current module
+ func (m *MovCore) Name() string {
+ return "MOV"
+ }
+
+ // StartHeight return the start block height of current module
+ func (m *MovCore) StartHeight() uint64 {
+ return m.startBlockHeight
+ }
+
+ // ValidateBlock no need to verify the block header, because the first module has been verified.
+ // just need to verify the transactions in the block.
+ func (m *MovCore) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
+ return m.ValidateTxs(block.Transactions, verifyResults)
+ }
+
+ // ValidateTxs validate the trade transaction.
+ func (m *MovCore) ValidateTxs(txs []*types.Tx, verifyResults []*bc.TxVerifyResult) error {
+ for i, tx := range txs {
+ if err := m.ValidateTx(tx, verifyResults[i]); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
+ // ValidateTxs 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) {
+ if err := validateCancelOrderTx(tx, verifyResult); err != nil {
+ return err
+ }
+ }
+
+ for _, output := range tx.Outputs {
+ if !segwit.IsP2WMCScript(output.ControlProgram()) {
+ continue
+ }
+ if verifyResult.StatusFail {
+ return errStatusFailMustFalse
+ }
+
+ if err := validateMagneticContractArgs(output.AssetAmount(), output.ControlProgram()); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
+ func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
+ if verifyResult.StatusFail {
+ return errStatusFailMustFalse
+ }
+
+ for _, input := range tx.Inputs {
+ if !segwit.IsP2WMCScript(input.ControlProgram()) {
+ return errInputProgramMustP2WMCScript
+ }
+
+ if contract.IsTradeClauseSelector(input) {
+ return errExistTradeInCancelOrderTx
+ }
+ }
+ return nil
+ }
+
+ func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte) error {
+ contractArgs, err := segwit.DecodeP2WMCProgram(program)
+ if err != nil {
+ return err
+ }
+
+ if *fromAssetAmount.AssetId == contractArgs.RequestedAsset {
+ return errInvalidTradePairs
+ }
+
+ if contractArgs.RatioNumerator <= 0 || contractArgs.RatioDenominator <= 0 {
+ return errRatioOfTradeLessThanZero
+ }
+
+ if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs) < 1 {
+ return errRequestAmountMath
+ }
+ return nil
+ }
+
+ func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
+ if verifyResult.StatusFail {
+ return errStatusFailMustFalse
+ }
+
+ fromAssetIDMap := make(map[string]bool)
+ toAssetIDMap := make(map[string]bool)
+ for i, input := range tx.Inputs {
+ if !segwit.IsP2WMCScript(input.ControlProgram()) {
+ return errInputProgramMustP2WMCScript
+ }
+
+ if contract.IsCancelClauseSelector(input) {
+ return errExistCancelOrderInMatchedTx
+ }
+
+ order, err := common.NewOrderFromInput(tx, i)
+ if err != nil {
+ return err
+ }
+
+ fromAssetIDMap[order.FromAssetID.String()] = true
+ toAssetIDMap[order.ToAssetID.String()] = true
+ }
+
+ if len(fromAssetIDMap) != len(tx.Inputs) || len(toAssetIDMap) != len(tx.Inputs) {
+ return errAssetIDMustUniqueInMatchedTx
+ }
+
+ return validateMatchedTxFeeAmount(tx)
+ }
+
+ func validateMatchedTxFeeAmount(tx *types.Tx) error {
+ txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
+ if err != nil {
+ return err
+ }
+
+ for _, amount := range txFee {
+ if amount.FeeAmount > amount.MaxFeeAmount {
+ return errAmountOfFeeGreaterThanMaximum
+ }
+ }
+ return nil
+ }
+
+ /*
+ @issue: the match package didn't support circle yet
+ */
+ func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
+ orderTable, err := buildOrderTable(m.movStore, txs)
+ if err != nil {
+ return err
+ }
+
+ matchEngine := match.NewEngine(orderTable, maxFeeRate, nil)
+ for _, matchedTx := range txs {
+ if !common.IsMatchedTx(matchedTx) {
+ continue
+ }
+
+ tradePairs, err := getSortedTradePairsFromMatchedTx(matchedTx)
+ if err != nil {
+ return err
+ }
+
+ actualMatchedTx, err := matchEngine.NextMatchedTx(tradePairs...)
+ if err != nil {
+ return err
+ }
+
+ if len(matchedTx.Inputs) != len(actualMatchedTx.Inputs) {
+ return errLengthOfInputIsIncorrect
+ }
+
+ spendOutputIDs := make(map[string]bool)
+ for _, input := range matchedTx.Inputs {
+ spendOutputID, err := input.SpentOutputID()
+ if err != nil {
+ return err
+ }
+
+ spendOutputIDs[spendOutputID.String()] = true
+ }
+
+ for _, input := range actualMatchedTx.Inputs {
+ spendOutputID, err := input.SpentOutputID()
+ if err != nil {
+ return err
+ }
+
+ if _, ok := spendOutputIDs[spendOutputID.String()]; !ok {
+ return errSpendOutputIDIsIncorrect
+ }
+ }
+ }
+ return nil
+ }
+
+ func applyTransactions(txs []*types.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)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ for _, order := range addOrders {
+ addOrderMap[order.Key()] = order
+ }
+
+ deleteOrders, err := getDeleteOrdersFromTx(tx)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ for _, order := range deleteOrders {
+ deleteOrderMap[order.Key()] = order
+ }
+ }
+
+ addOrders, deleteOrders := mergeOrders(addOrderMap, deleteOrderMap)
+ return addOrders, deleteOrders, nil
+ }
+
+ /*
+ @issue: if consensus node packed match transaction first then packed regular tx, this function's logic may make a valid block invalid
+ */
+ func buildOrderTable(store database.MovStore, txs []*types.Tx) (*match.OrderTable, error) {
+ var nonMatchedTxs []*types.Tx
+ for _, tx := range txs {
+ if !common.IsMatchedTx(tx) {
+ nonMatchedTxs = append(nonMatchedTxs, tx)
+ }
+ }
+
+ var arrivalAddOrders, arrivalDelOrders []*common.Order
+ for _, tx := range nonMatchedTxs {
+ addOrders, err := getAddOrdersFromTx(tx)
+ if err != nil {
+ return nil, err
+ }
+
+ delOrders, err := getDeleteOrdersFromTx(tx)
+ if err != nil {
+ return nil, err
+ }
+
+ arrivalAddOrders = append(arrivalAddOrders, addOrders...)
+ arrivalDelOrders = append(arrivalDelOrders, delOrders...)
+ }
+
+ return match.NewOrderTable(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 {
+ if output.OutputType() != types.IntraChainOutputType || !segwit.IsP2WMCScript(output.ControlProgram()) {
+ continue
+ }
+
+ order, err := common.NewOrderFromOutput(tx, i)
+ if err != nil {
+ return nil, err
+ }
+
+ orders = append(orders, order)
+ }
+ return orders, nil
+ }
+
+ func getDeleteOrdersFromTx(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()) {
+ continue
+ }
+
+ order, err := common.NewOrderFromInput(tx, i)
+ if err != nil {
+ return nil, err
+ }
+
+ orders = append(orders, order)
+ }
+ return orders, nil
+ }
+
+ func getSortedTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
+ assetMap := make(map[bc.AssetID]bc.AssetID)
+ var firstTradePair *common.TradePair
+ for _, tx := range tx.Inputs {
+ contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
+ if err != nil {
+ return nil, err
+ }
+
+ assetMap[tx.AssetID()] = contractArgs.RequestedAsset
+ if firstTradePair == nil {
+ firstTradePair = &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset}
+ }
+ }
+
+ tradePairs := []*common.TradePair{firstTradePair}
+ for tradePair := firstTradePair; *tradePair.ToAssetID != *firstTradePair.FromAssetID; {
+ nextTradePairToAssetID, ok := assetMap[*tradePair.ToAssetID]
+ if !ok {
+ return nil, errInvalidTradePairs
+ }
+
+ tradePair = &common.TradePair{FromAssetID: tradePair.ToAssetID, ToAssetID: &nextTradePairToAssetID}
+ tradePairs = append(tradePairs, tradePair)
+ }
+
+ if len(tradePairs) != len(tx.Inputs) {
+ return nil, errInvalidTradePairs
+ }
+ return tradePairs, nil
+ }
+
+ func mergeOrders(addOrderMap, deleteOrderMap map[string]*common.Order) ([]*common.Order, []*common.Order) {
+ var deleteOrders, addOrders []*common.Order
+ for orderID, order := range addOrderMap {
+ if _, ok := deleteOrderMap[orderID]; ok {
+ delete(deleteOrderMap, orderID)
+ continue
+ }
+ addOrders = append(addOrders, order)
+ }
+
+ for _, order := range deleteOrderMap {
+ deleteOrders = append(deleteOrders, order)
+ }
+ return addOrders, deleteOrders
+ }
--- /dev/null
- "github.com/vapor/application/mov/common"
- "github.com/vapor/application/mov/database"
- "github.com/vapor/application/mov/mock"
- "github.com/vapor/consensus"
- dbm "github.com/vapor/database/leveldb"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/bc/types"
- "github.com/vapor/protocol/vm"
- "github.com/vapor/testutil"
+ package mov
+
+ import (
+ "math"
+ "os"
+ "testing"
+
++ "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/consensus"
++ dbm "github.com/bytom/vapor/database/leveldb"
++ "github.com/bytom/vapor/protocol/bc"
++ "github.com/bytom/vapor/protocol/bc/types"
++ "github.com/bytom/vapor/protocol/vm"
++ "github.com/bytom/vapor/testutil"
+ )
+
+ /*
+ @addTest:BeforeProposalBlock: will gas affect generate tx? will be packed tx affect generate tx?
+ @addTest:TestApplyBlock: one block has two different trade pairs & different trade pair won't affect each order(attach & detach)
+ @addTest:TestApplyBlock: node packed maker tx and match transaction in random order(attach & detach)
+ @addTest:TestValidateBlock: one tx has trade input and cancel input mixed
+ @addTest:TestValidateBlock: regular match transaction's seller program is also a P2WMCProgram
+ */
+ func TestApplyBlock(t *testing.T) {
+ initBlockHeader := &types.BlockHeader{Height: 1, PreviousBlockHash: bc.Hash{}}
+ cases := []struct {
+ desc string
+ block *types.Block
+ blockFunc testFun
+ initOrders []*common.Order
+ wantOrders []*common.Order
+ wantDBState *common.MovDatabaseState
+ wantError error
+ }{
+ {
+ desc: "apply block has pending order transaction",
+ block: &types.Block{
+ BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+ Transactions: []*types.Tx{
+ mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[0],
+ },
+ },
+ blockFunc: applyBlock,
+ wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0)},
+ wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+ },
+ {
+ desc: "apply block has full matched transaction",
+ block: &types.Block{
+ BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+ Transactions: []*types.Tx{
+ mock.MatchedTxs[1],
+ },
+ },
+ blockFunc: applyBlock,
+ initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
+ wantOrders: []*common.Order{mock.Btc2EthOrders[1]},
+ wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+ },
+ {
+ desc: "apply block has partial matched transaction",
+ block: &types.Block{
+ BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+ Transactions: []*types.Tx{
+ mock.MatchedTxs[0],
+ },
+ },
+ blockFunc: applyBlock,
+ initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
+ wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
+ wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+ },
+ {
+ desc: "apply block has two partial matched transaction",
+ block: &types.Block{
+ BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+ Transactions: []*types.Tx{
+ mock.MatchedTxs[2], mock.MatchedTxs[3],
+ },
+ },
+ blockFunc: applyBlock,
+ initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
+ wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
+ wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+ },
+ {
+ desc: "apply block has partial matched transaction by pending orders from tx pool",
+ block: &types.Block{
+ BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+ Transactions: []*types.Tx{
+ mock.Btc2EthMakerTxs[0],
+ mock.Eth2BtcMakerTxs[1],
+ mock.MatchedTxs[4],
+ },
+ },
+ blockFunc: applyBlock,
+ initOrders: []*common.Order{},
+ wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1)},
+ wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+ },
+ {
+ desc: "detach block has pending order transaction",
+ block: &types.Block{
+ BlockHeader: *initBlockHeader,
+ Transactions: []*types.Tx{
+ mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1],
+ },
+ },
+ blockFunc: detachBlock,
+ initOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[1], 0)},
+ wantOrders: []*common.Order{},
+ wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
+ },
+ {
+ desc: "detach block has full matched transaction",
+ block: &types.Block{
+ BlockHeader: *initBlockHeader,
+ Transactions: []*types.Tx{
+ mock.MatchedTxs[1],
+ },
+ },
+ blockFunc: detachBlock,
+ initOrders: []*common.Order{mock.Btc2EthOrders[1]},
+ wantOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
+ wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
+ },
+ {
+ desc: "detach block has partial matched transaction",
+ block: &types.Block{
+ BlockHeader: *initBlockHeader,
+ Transactions: []*types.Tx{
+ mock.MatchedTxs[0],
+ },
+ },
+ blockFunc: detachBlock,
+ initOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[0], 1)},
+ wantOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
+ wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
+ },
+ {
+ desc: "detach block has two partial matched transaction",
+ block: &types.Block{
+ BlockHeader: *initBlockHeader,
+ Transactions: []*types.Tx{
+ 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]},
+ wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
+ },
+ }
+
+ defer os.RemoveAll("temp")
+ for i, c := range cases {
+ testDB := dbm.NewDB("testdb", "leveldb", "temp")
+ store := database.NewLevelDBMovStore(testDB)
+ if err := store.InitDBState(0, &bc.Hash{}); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := store.ProcessOrders(c.initOrders, nil, initBlockHeader); err != nil {
+ t.Fatal(err)
+ }
+
+ movCore := &MovCore{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)
+ }
+
+ 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)
+ }
+
+ dbState, err := store.GetMovDatabaseState()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ 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)
+ }
+
+ testDB.Close()
+ os.RemoveAll("temp")
+ }
+ }
+
+ func TestValidateBlock(t *testing.T) {
+ cases := []struct {
+ desc string
+ block *types.Block
+ verifyResults []*bc.TxVerifyResult
+ wantError error
+ }{
+ {
+ desc: "block only has maker tx",
+ block: &types.Block{
+ Transactions: []*types.Tx{
+ mock.Eth2BtcMakerTxs[0],
+ mock.Btc2EthMakerTxs[0],
+ },
+ },
+ verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}},
+ wantError: nil,
+ },
+ {
+ desc: "block only has matched tx",
+ block: &types.Block{
+ Transactions: []*types.Tx{
+ mock.MatchedTxs[0],
+ mock.MatchedTxs[1],
+ mock.MatchedTxs[2],
+ },
+ },
+ verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
+ wantError: nil,
+ },
+ {
+ desc: "block has maker tx and matched tx",
+ block: &types.Block{
+ Transactions: []*types.Tx{
+ mock.Eth2BtcMakerTxs[0],
+ mock.Btc2EthMakerTxs[0],
+ mock.MatchedTxs[0],
+ mock.MatchedTxs[1],
+ mock.MatchedTxs[2],
+ },
+ },
+ verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
+ wantError: nil,
+ },
+ {
+ desc: "status fail of maker tx is true",
+ block: &types.Block{
+ Transactions: []*types.Tx{
+ mock.Eth2BtcMakerTxs[0],
+ mock.Btc2EthMakerTxs[0],
+ },
+ },
+ verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
+ wantError: errStatusFailMustFalse,
+ },
+ {
+ desc: "status fail of matched tx is true",
+ block: &types.Block{
+ Transactions: []*types.Tx{
+ mock.MatchedTxs[1],
+ mock.MatchedTxs[2],
+ },
+ },
+ verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
+ wantError: errStatusFailMustFalse,
+ },
+ {
+ desc: "asset id in matched tx is not unique",
+ block: &types.Block{
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *mock.Eth2BtcOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Eth2BtcOrders[0].Utxo.Amount, mock.Eth2BtcOrders[0].Utxo.SourcePos, mock.Eth2BtcOrders[0].Utxo.ControlProgram),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
+ },
+ }),
+ },
+ },
+ verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
+ wantError: errAssetIDMustUniqueInMatchedTx,
+ },
+ {
+ desc: "common input in the matched tx",
+ block: &types.Block{
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *mock.Eth2BtcOrders[0].Utxo.SourceID, *mock.Eth2BtcOrders[0].FromAssetID, mock.Eth2BtcOrders[0].Utxo.Amount, mock.Eth2BtcOrders[0].Utxo.SourcePos, mock.Eth2BtcOrders[0].Utxo.ControlProgram),
+ types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
+ types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
+ },
+ }),
+ },
+ },
+ verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+ wantError: errInputProgramMustP2WMCScript,
+ },
+ {
+ desc: "cancel order in the matched tx",
+ block: &types.Block{
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *mock.Eth2BtcOrders[0].Utxo.SourceID, *mock.Eth2BtcOrders[0].FromAssetID, mock.Eth2BtcOrders[0].Utxo.Amount, mock.Eth2BtcOrders[0].Utxo.SourcePos, mock.Eth2BtcOrders[0].Utxo.ControlProgram),
+ types.NewSpendInput([][]byte{{}, {}, vm.Int64Bytes(2)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 10, testutil.MustDecodeHexString("53")),
+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 10, []byte{0x51}),
+ types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
+ },
+ }),
+ },
+ },
+ verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+ wantError: errExistCancelOrderInMatchedTx,
+ },
+ {
+ desc: "common input in the cancel order tx",
+ block: &types.Block{
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{{}, {}, vm.Int64Bytes(2)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
+ types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
+ types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
+ },
+ }),
+ },
+ },
+ verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+ wantError: errInputProgramMustP2WMCScript,
+ },
+ {
+ desc: "amount of fee greater than max fee amount",
+ block: &types.Block{
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(10), vm.Int64Bytes(1), vm.Int64Bytes(0)}, *mock.Eth2BtcOrders[2].Utxo.SourceID, *mock.Eth2BtcOrders[2].FromAssetID, mock.Eth2BtcOrders[2].Utxo.Amount, mock.Eth2BtcOrders[2].Utxo.SourcePos, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 10, testutil.MustDecodeHexString("55")),
+ // re-order
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 270, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
+ // fee
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 40, []byte{0x59}),
+ },
+ }),
+ },
+ },
+ verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+ wantError: errAmountOfFeeGreaterThanMaximum,
+ },
+ {
+ desc: "ratio numerator is zero",
+ block: &types.Block{
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewSpendInput(nil, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, []byte{0x51})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 0, 1))},
+ }),
+ },
+ },
+ verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+ wantError: errRatioOfTradeLessThanZero,
+ },
+ {
+ desc: "ratio denominator is zero",
+ block: &types.Block{
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewSpendInput(nil, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, []byte{0x51})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 1, 0))},
+ }),
+ },
+ },
+ verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+ wantError: errRatioOfTradeLessThanZero,
+ },
+ {
+ desc: "want amount is overflow",
+ block: &types.Block{
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewSpendInput(nil, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, []byte{0x51})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), math.MaxInt64, 1))},
+ }),
+ },
+ },
+ verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+ wantError: errRequestAmountMath,
+ },
+ }
+
+ for i, c := range cases {
+ movCore := &MovCore{}
+ 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)
+ }
+ }
+ }
+
+ type testFun func(movCore *MovCore, block *types.Block) error
+
+ func applyBlock(movCore *MovCore, block *types.Block) error {
+ return movCore.ApplyBlock(block)
+ }
+
+ func detachBlock(movCore *MovCore, block *types.Block) error {
+ return movCore.DetachBlock(block)
+ }
+
+ func queryAllOrders(store *database.LevelDBMovStore) []*common.Order {
+ var orders []*common.Order
+ tradePairIterator := database.NewTradePairIterator(store)
+ for tradePairIterator.HasNext() {
+ orderIterator := database.NewOrderIterator(store, tradePairIterator.Next())
+ for orderIterator.HasNext() {
+ orders = append(orders, orderIterator.NextBatch()...)
+ }
+ }
+ return orders
+ }
+
+ func ordersEquals(orders1 []*common.Order, orders2 []*common.Order) bool {
+ orderMap1 := make(map[string]*common.Order)
+ for _, order := range orders1 {
+ orderMap1[order.Key()] = order
+ }
+
+ orderMap2 := make(map[string]*common.Order)
+ for _, order := range orders2 {
+ orderMap2[order.Key()] = order
+ }
+ return testutil.DeepEqual(orderMap1, orderMap2)
+ }
+
+ func hashPtr(hash bc.Hash) *bc.Hash {
+ return &hash
+ }
import (
"errors"
- "github.com/vapor/consensus"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/vm"
- "github.com/vapor/protocol/vm/vmutil"
+ "github.com/bytom/vapor/consensus"
++ "github.com/bytom/vapor/protocol/bc"
+ "github.com/bytom/vapor/protocol/vm"
+ "github.com/bytom/vapor/protocol/vm/vmutil"
)
+ // IsP2WScript is used to determine whether it is a P2WScript or not
func IsP2WScript(prog []byte) bool {
return IsP2WPKHScript(prog) || IsP2WSHScript(prog) || IsStraightforward(prog)
}
CREATE TABLE `assets` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`asset_id` varchar(64) NOT NULL,
- `issuance_program` varchar(128) NOT NULL,
+ `issuance_program` mediumtext NOT NULL,
`vm_version` int(11) NOT NULL DEFAULT '1',
`definition` text,
+ `is_open_federation_issue` tinyint(1) DEFAULT '0',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
cmn "github.com/tendermint/tmlibs/common"
browser "github.com/toqueteos/webbrowser"
- "github.com/vapor/accesstoken"
- "github.com/vapor/account"
- "github.com/vapor/api"
- "github.com/vapor/application/mov"
- "github.com/vapor/asset"
- "github.com/vapor/blockchain/pseudohsm"
- cfg "github.com/vapor/config"
- "github.com/vapor/consensus"
- "github.com/vapor/database"
- dbm "github.com/vapor/database/leveldb"
- "github.com/vapor/env"
- "github.com/vapor/event"
- vaporLog "github.com/vapor/log"
- "github.com/vapor/net/websocket"
- "github.com/vapor/netsync"
- "github.com/vapor/proposal/blockproposer"
- "github.com/vapor/protocol"
- "github.com/vapor/protocol/bc/types"
- w "github.com/vapor/wallet"
+ "github.com/bytom/vapor/accesstoken"
+ "github.com/bytom/vapor/account"
+ "github.com/bytom/vapor/api"
++ "github.com/bytom/vapor/application/mov"
+ "github.com/bytom/vapor/asset"
+ "github.com/bytom/vapor/blockchain/pseudohsm"
+ cfg "github.com/bytom/vapor/config"
+ "github.com/bytom/vapor/consensus"
+ "github.com/bytom/vapor/database"
+ dbm "github.com/bytom/vapor/database/leveldb"
+ "github.com/bytom/vapor/env"
+ "github.com/bytom/vapor/event"
+ vaporLog "github.com/bytom/vapor/log"
+ "github.com/bytom/vapor/net/websocket"
+ "github.com/bytom/vapor/netsync"
+ "github.com/bytom/vapor/proposal/blockproposer"
+ "github.com/bytom/vapor/protocol"
+ "github.com/bytom/vapor/protocol/bc/types"
+ w "github.com/bytom/vapor/wallet"
)
const (
log "github.com/sirupsen/logrus"
- "github.com/vapor/account"
- "github.com/vapor/blockchain/txbuilder"
- "github.com/vapor/consensus"
- "github.com/vapor/errors"
- "github.com/vapor/protocol"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/bc/types"
- "github.com/vapor/protocol/state"
- "github.com/vapor/protocol/validation"
- "github.com/vapor/protocol/vm/vmutil"
+ "github.com/bytom/vapor/account"
+ "github.com/bytom/vapor/blockchain/txbuilder"
+ "github.com/bytom/vapor/consensus"
+ "github.com/bytom/vapor/errors"
+ "github.com/bytom/vapor/protocol"
+ "github.com/bytom/vapor/protocol/bc"
+ "github.com/bytom/vapor/protocol/bc/types"
+ "github.com/bytom/vapor/protocol/state"
+ "github.com/bytom/vapor/protocol/validation"
+ "github.com/bytom/vapor/protocol/vm/vmutil"
)
- const logModule = "mining"
+ const (
+ logModule = "mining"
+ batchApplyNum = 64
+
+ timeoutOk = iota + 1
+ timeoutWarn
+ timeoutCritical
+ )
+
+ // NewBlockTemplate returns a new block template that is ready to be solved
+ func NewBlockTemplate(chain *protocol.Chain, accountManager *account.Manager, timestamp uint64, warnDuration, criticalDuration time.Duration) (*types.Block, error) {
+ builder := newBlockBuilder(chain, accountManager, timestamp, warnDuration, criticalDuration)
+ return builder.build()
+ }
+
+ type blockBuilder struct {
+ chain *protocol.Chain
+ accountManager *account.Manager
+
+ block *types.Block
+ txStatus *bc.TransactionStatus
+ utxoView *state.UtxoViewpoint
+
+ warnTimeoutCh <-chan time.Time
+ criticalTimeoutCh <-chan time.Time
+ timeoutStatus uint8
+ gasLeft int64
+ }
+
+ func newBlockBuilder(chain *protocol.Chain, accountManager *account.Manager, timestamp uint64, warnDuration, criticalDuration time.Duration) *blockBuilder {
+ preBlockHeader := chain.BestBlockHeader()
+ block := &types.Block{
+ BlockHeader: types.BlockHeader{
+ Version: 1,
+ Height: preBlockHeader.Height + 1,
+ PreviousBlockHash: preBlockHeader.Hash(),
+ Timestamp: timestamp,
+ BlockCommitment: types.BlockCommitment{},
+ BlockWitness: types.BlockWitness{Witness: make([][]byte, consensus.ActiveNetParams.NumOfConsensusNode)},
+ },
+ }
+
+ builder := &blockBuilder{
+ chain: chain,
+ accountManager: accountManager,
+ block: block,
+ txStatus: bc.NewTransactionStatus(),
+ utxoView: state.NewUtxoViewpoint(),
+ warnTimeoutCh: time.After(warnDuration),
+ criticalTimeoutCh: time.After(criticalDuration),
+ gasLeft: int64(consensus.ActiveNetParams.MaxBlockGas),
+ timeoutStatus: timeoutOk,
+ }
+ return builder
+ }
+
+ func (b *blockBuilder) applyCoinbaseTransaction() error {
+ coinbaseTx, err := b.createCoinbaseTx()
+ if err != nil {
+ return errors.Wrap(err, "fail on create coinbase tx")
+ }
+
+ gasState, err := validation.ValidateTx(coinbaseTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Height: b.block.Height}, Transactions: []*bc.Tx{coinbaseTx.Tx}})
+ if err != nil {
+ return err
+ }
+
+ b.block.Transactions = append(b.block.Transactions, coinbaseTx)
+ if err := b.txStatus.SetStatus(0, false); err != nil {
+ return err
+ }
+
+ 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 tempTxs = append(tempTxs, txs[i]); len(tempTxs) < batchApplyNum && i != len(txs)-1 {
+ continue
+ }
+
+ results, gasLeft := preValidateTxs(tempTxs, b.chain, b.utxoView, b.gasLeft)
+ for _, result := range results {
+ if result.err != nil && !result.gasOnly {
+ log.WithFields(log.Fields{"module": logModule, "error": result.err}).Error("mining block generation: skip tx due to")
+ b.chain.GetTxPool().RemoveTransaction(&result.tx.ID)
+ continue
+ }
+
+ if err := b.txStatus.SetStatus(len(b.block.Transactions), result.gasOnly); err != nil {
+ return err
+ }
+
+ b.block.Transactions = append(b.block.Transactions, result.tx)
+ }
+
+ b.gasLeft = gasLeft
+ tempTxs = []*types.Tx{}
+ if b.getTimeoutStatus() >= timeoutStatus {
+ break
+ }
+ }
+ return nil
+ }
+
+ func (b *blockBuilder) applyTransactionFromPool() error {
+ txDescList := b.chain.GetTxPool().GetTransactions()
+ sort.Sort(byTime(txDescList))
+
+ poolTxs := make([]*types.Tx, len(txDescList))
+ for i, txDesc := range txDescList {
+ poolTxs[i] = txDesc.Tx
+ }
+
+ return b.applyTransactions(poolTxs, timeoutWarn)
+ }
+
+ func (b *blockBuilder) applyTransactionFromSubProtocol() error {
+ cp, err := b.accountManager.GetCoinbaseControlProgram()
+ if err != nil {
+ return err
+ }
+
+ isTimeout := func() bool {
+ return b.getTimeoutStatus() > timeoutOk
+ }
+
+ for i, p := range b.chain.SubProtocols() {
+ if b.gasLeft <= 0 || isTimeout() {
+ break
+ }
+
+ subTxs, err := p.BeforeProposalBlock(b.block.Transactions, cp, b.block.Height, b.gasLeft, isTimeout)
+ if err != nil {
+ log.WithFields(log.Fields{"module": logModule, "index": i, "error": err}).Error("failed on sub protocol txs package")
+ continue
+ }
+
+ if err := b.applyTransactions(subTxs, timeoutCritical); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
+ func (b *blockBuilder) build() (*types.Block, error) {
+ if err := b.applyCoinbaseTransaction(); err != nil {
+ return nil, err
+ }
+
+ if err := b.applyTransactionFromPool(); err != nil {
+ return nil, err
+ }
+
+ if err := b.applyTransactionFromSubProtocol(); err != nil {
+ return nil, err
+ }
+
+ if err := b.calcBlockCommitment(); err != nil {
+ return nil, err
+ }
+
+ if err := b.chain.SignBlockHeader(&b.block.BlockHeader); err != nil {
+ return nil, err
+ }
+
+ return b.block, nil
+ }
+
+ func (b *blockBuilder) calcBlockCommitment() (err error) {
+ var txEntries []*bc.Tx
+ for _, tx := range b.block.Transactions {
+ txEntries = append(txEntries, tx.Tx)
+ }
+
+ b.block.BlockHeader.BlockCommitment.TransactionsMerkleRoot, err = types.TxMerkleRoot(txEntries)
+ if err != nil {
+ return err
+ }
+
+ b.block.BlockHeader.BlockCommitment.TransactionStatusHash, err = types.TxStatusMerkleRoot(b.txStatus.VerifyStatus)
+ return err
+ }
// createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy
// based on the passed block height to the provided address. When the address
import (
log "github.com/sirupsen/logrus"
- "github.com/bytom/vapor/config"
- "github.com/vapor/errors"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/bc/types"
- "github.com/vapor/protocol/state"
- "github.com/vapor/protocol/validation"
+ "github.com/bytom/vapor/errors"
- "github.com/bytom/vapor/event"
+ "github.com/bytom/vapor/protocol/bc"
+ "github.com/bytom/vapor/protocol/bc/types"
+ "github.com/bytom/vapor/protocol/state"
+ "github.com/bytom/vapor/protocol/validation"
)
var (
log "github.com/sirupsen/logrus"
- "github.com/vapor/common"
- "github.com/vapor/config"
- "github.com/vapor/errors"
- "github.com/vapor/event"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/bc/types"
- "github.com/vapor/protocol/state"
+ "github.com/bytom/vapor/common"
+ "github.com/bytom/vapor/config"
++ "github.com/bytom/vapor/errors"
+ "github.com/bytom/vapor/event"
+ "github.com/bytom/vapor/protocol/bc"
+ "github.com/bytom/vapor/protocol/bc/types"
+ "github.com/bytom/vapor/protocol/state"
)
const (
import (
"fmt"
"math"
+ "runtime"
"sync"
- "github.com/vapor/common"
- "github.com/vapor/config"
- "github.com/vapor/consensus"
- "github.com/vapor/errors"
- "github.com/vapor/math/checked"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/vm"
++ "github.com/bytom/vapor/common"
+ "github.com/bytom/vapor/config"
+ "github.com/bytom/vapor/consensus"
+ "github.com/bytom/vapor/errors"
+ "github.com/bytom/vapor/math/checked"
+ "github.com/bytom/vapor/protocol/bc"
+ "github.com/bytom/vapor/protocol/vm"
)
- const (
- validateWorkerNum = 32
- )
-
// validate transaction error
var (
ErrTxVersion = errors.New("invalid transaction version")
import (
"math"
+ "math/big"
- "github.com/vapor/math/checked"
+ "github.com/bytom/vapor/math/checked"
)
func op1Add(vm *virtualMachine) error {
package vmutil
import (
- "github.com/vapor/crypto/ed25519"
- "github.com/vapor/errors"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/vm"
+ "github.com/bytom/vapor/crypto/ed25519"
+ "github.com/bytom/vapor/errors"
++ "github.com/bytom/vapor/protocol/bc"
+ "github.com/bytom/vapor/protocol/vm"
)
// pre-define errors
"github.com/jinzhu/gorm"
log "github.com/sirupsen/logrus"
- vpCommon "github.com/vapor/common"
- "github.com/vapor/consensus"
- "github.com/vapor/errors"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/toolbar/federation/common"
- "github.com/vapor/toolbar/federation/config"
- "github.com/vapor/toolbar/federation/database"
- "github.com/vapor/toolbar/federation/database/orm"
- "github.com/vapor/toolbar/federation/service"
++ vpCommon "github.com/bytom/vapor/common"
+ "github.com/bytom/vapor/consensus"
+ "github.com/bytom/vapor/errors"
+ "github.com/bytom/vapor/protocol/bc"
+ "github.com/bytom/vapor/toolbar/federation/common"
+ "github.com/bytom/vapor/toolbar/federation/config"
+ "github.com/bytom/vapor/toolbar/federation/database"
+ "github.com/bytom/vapor/toolbar/federation/database/orm"
+ "github.com/bytom/vapor/toolbar/federation/service"
)
type mainchainKeeper struct {