only:
- master
- dev
- - v0.1
+ - mov
script:
- make ci
package common
-import "github.com/bytom/vapor/protocol/bc"
+import (
+ "encoding/hex"
+ "fmt"
+ "math/big"
+ "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
ControlProgram []byte
}
+// Order store all the order information
type Order struct {
- FromAssetID *bc.AssetID
- ToAssetID *bc.AssetID
- Utxo *MovUtxo
- Rate float64
+ FromAssetID *bc.AssetID
+ ToAssetID *bc.AssetID
+ Utxo *MovUtxo
+ RatioNumerator int64
+ RatioDenominator int64
+}
+
+// Rate return the exchange represented by float64
+func (o *Order) Rate() float64 {
+ if o.RatioDenominator == 0 {
+ return 0
+ }
+ rate := big.NewRat(o.RatioNumerator, o.RatioDenominator)
+ result, _ := rate.Float64()
+ return result
+}
+
+// cmpRate compares rate of x and y and returns -1 if x < y, 0 if x == y, +1 if x > y
+func (o *Order) cmpRate(other *Order) int {
+ rate := big.NewRat(o.RatioNumerator, o.RatioDenominator)
+ otherRate := big.NewRat(other.RatioNumerator, other.RatioDenominator)
+ return rate.Cmp(otherRate)
+}
+
+// Cmp first compare the rate, if rate is equals, then compare the utxo hash
+func (o *Order) Cmp(other *Order) int {
+ cmp := o.cmpRate(other)
+ if cmp == 0 {
+ if hex.EncodeToString(o.UTXOHash().Bytes()) < hex.EncodeToString(other.UTXOHash().Bytes()) {
+ return -1
+ }
+ return 1
+ }
+ return cmp
+}
+
+// OrderSlice is define for order's sort
+type OrderSlice []*Order
+
+func (o OrderSlice) Len() int { return len(o) }
+func (o OrderSlice) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
+func (o OrderSlice) Less(i, j int) bool {
+ return o[i].Cmp(o[j]) < 0
+}
+
+// NewOrderFromOutput convert txinput to order
+func NewOrderFromOutput(tx *types.Tx, outputIndex int) (*Order, error) {
+ outputID := tx.OutputID(outputIndex)
+ output, err := tx.IntraChainOutput(*outputID)
+ if err != nil {
+ return nil, err
+ }
+
+ contractArgs, err := segwit.DecodeP2WMCProgram(output.ControlProgram.Code)
+ if err != nil {
+ return nil, err
+ }
+
+ assetAmount := output.Source.Value
+ return &Order{
+ FromAssetID: assetAmount.AssetId,
+ ToAssetID: &contractArgs.RequestedAsset,
+ RatioNumerator: contractArgs.RatioNumerator,
+ RatioDenominator: contractArgs.RatioDenominator,
+ Utxo: &MovUtxo{
+ SourceID: output.Source.Ref,
+ Amount: assetAmount.Amount,
+ SourcePos: uint64(outputIndex),
+ ControlProgram: output.ControlProgram.Code,
+ },
+ }, nil
+}
+
+// NewOrderFromInput convert txoutput to order
+func NewOrderFromInput(tx *types.Tx, inputIndex int) (*Order, error) {
+ input, ok := tx.Inputs[inputIndex].TypedInput.(*types.SpendInput)
+ if !ok {
+ return nil, errors.New("input is not type of spend input")
+ }
+
+ contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Order{
+ FromAssetID: input.AssetId,
+ ToAssetID: &contractArgs.RequestedAsset,
+ RatioNumerator: contractArgs.RatioNumerator,
+ RatioDenominator: contractArgs.RatioDenominator,
+ Utxo: &MovUtxo{
+ SourceID: &input.SourceID,
+ Amount: input.Amount,
+ SourcePos: input.SourcePosition,
+ ControlProgram: input.ControlProgram,
+ },
+ }, nil
+}
+
+// Key return the unique key for representing this order
+func (o *Order) Key() string {
+ return fmt.Sprintf("%s:%d", o.Utxo.SourceID, o.Utxo.SourcePos)
}
+// TradePair return the trade pair info
+func (o *Order) TradePair() *TradePair {
+ return &TradePair{FromAssetID: o.FromAssetID, ToAssetID: o.ToAssetID}
+}
+
+// UTXOHash calculate the utxo hash of this order
+func (o *Order) UTXOHash() *bc.Hash {
+ prog := &bc.Program{VmVersion: 1, Code: o.Utxo.ControlProgram}
+ src := &bc.ValueSource{
+ Ref: o.Utxo.SourceID,
+ Value: &bc.AssetAmount{AssetId: o.FromAssetID, Amount: o.Utxo.Amount},
+ Position: o.Utxo.SourcePos,
+ }
+ hash := bc.EntryID(bc.NewIntraChainOutput(src, prog, 0))
+ return &hash
+}
+
+// TradePair is the object for record trade pair info
type TradePair struct {
FromAssetID *bc.AssetID
ToAssetID *bc.AssetID
Count int
}
+// Key return the unique key for representing this trade pair
+func (t *TradePair) Key() string {
+ return fmt.Sprintf("%s:%s", t.FromAssetID, t.ToAssetID)
+}
+
+// Reverse return the reverse trade pair object
+func (t *TradePair) Reverse() *TradePair {
+ return &TradePair{
+ FromAssetID: t.ToAssetID,
+ ToAssetID: t.FromAssetID,
+ }
+}
+
+// MovDatabaseState is object to record DB image status
type MovDatabaseState struct {
Height uint64
Hash *bc.Hash
--- /dev/null
+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
+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 && segwit.IsP2WMCScript(input.ControlProgram()) && contract.IsTradeClauseSelector(input) {
+ 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 && segwit.IsP2WMCScript(input.ControlProgram()) && contract.IsCancelClauseSelector(input) {
+ return true
+ }
+ }
+ return false
+}
--- /dev/null
+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
+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
+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, RatioNumerator: 1, RatioDenominator: 10}
+ order2 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, RatioNumerator: 2, RatioDenominator: 10}
+ order3 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, RatioNumerator: 3, RatioDenominator: 10}
+ order4 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, RatioNumerator: 4, RatioDenominator: 10}
+ order5 = &common.Order{FromAssetID: assetID1, ToAssetID: assetID2, RatioNumerator: 5, RatioDenominator: 10}
+)
+
+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)
+ }
+ }
+}
"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
- tradePairsNum = 1024
- ordersNum = 10240
- assetIDLen = 32
- rateByteLen = 8
+ fromAssetIDPos = 0
+ toAssetIDPos = 1
+ assetIDLen = 32
+ rateByteLen = 8
+
+ tradePairsNum = 32
+ ordersNum = 128
)
var (
bestMatchStore = append(movStore, matchStatus)
)
+type orderData struct {
+ Utxo *common.MovUtxo
+ RatioNumerator int64
+ RatioDenominator int64
+}
+
func calcOrderKey(fromAssetID, toAssetID *bc.AssetID, utxoHash *bc.Hash, rate float64) []byte {
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, math.Float64bits(rate))
return append(key, toAssetID.Bytes()...)
}
-func calcUTXOHash(order *common.Order) *bc.Hash {
- prog := &bc.Program{VmVersion: 1, Code: order.Utxo.ControlProgram}
- src := &bc.ValueSource{
- Ref: order.Utxo.SourceID,
- Value: &bc.AssetAmount{AssetId: order.FromAssetID, Amount: order.Utxo.Amount},
- Position: order.Utxo.SourcePos,
- }
- hash := bc.EntryID(bc.NewIntraChainOutput(src, prog, 0))
- return &hash
-}
-
-func getAssetIDFromTradePairKey(key []byte, prefix []byte, posIndex int) *bc.AssetID {
+func getAssetIDFromTradePairKey(key []byte, posIndex int) *bc.AssetID {
b := [32]byte{}
- pos := len(prefix) + assetIDLen*posIndex
+ pos := len(tradePairsPrefix) + assetIDLen*posIndex
copy(b[:], key[pos:pos+assetIDLen])
assetID := bc.NewAssetID(b)
return &assetID
}
-func getRateFromOrderKey(key []byte, prefix []byte) float64 {
- ratePos := len(prefix) + assetIDLen*2
+func getRateFromOrderKey(key []byte) float64 {
+ ratePos := len(ordersPrefix) + assetIDLen*2
return math.Float64frombits(binary.BigEndian.Uint64(key[ratePos : ratePos+rateByteLen]))
}
Count int
}
-type MovStore struct {
+// LevelDBMovStore is the LevelDB implementation for MovStore
+type LevelDBMovStore struct {
db dbm.DB
}
-func NewMovStore(db dbm.DB, height uint64, hash *bc.Hash) (*MovStore, error) {
- if value := db.Get(bestMatchStore); value == nil {
- state := &common.MovDatabaseState{Height: height, Hash: hash}
- value, err := json.Marshal(state)
- if err != nil {
- return nil, err
- }
+// NewLevelDBMovStore create a new LevelDBMovStore object
+func NewLevelDBMovStore(db dbm.DB) *LevelDBMovStore {
+ return &LevelDBMovStore{db: db}
+}
+
+// GetMovDatabaseState return the current DB's image status
+func (m *LevelDBMovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
+ if value := m.db.Get(bestMatchStore); value != nil {
+ state := &common.MovDatabaseState{}
+ return state, json.Unmarshal(value, state)
+ }
+
+ return nil, errors.New("don't find state of mov-database")
+}
- db.Set(bestMatchStore, value)
+// InitDBState set the DB's image status
+func (m *LevelDBMovStore) InitDBState(height uint64, hash *bc.Hash) error {
+ state := &common.MovDatabaseState{Height: height, Hash: hash}
+ value, err := json.Marshal(state)
+ if err != nil {
+ return err
}
- return &MovStore{db: db}, nil
+
+ m.db.Set(bestMatchStore, value)
+ return nil
}
-func (m *MovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
+// ListOrders return n orders after the input order
+func (m *LevelDBMovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
if orderAfter.FromAssetID == nil || orderAfter.ToAssetID == nil {
return nil, errors.New("assetID is nil")
}
orderPrefix = append(orderPrefix, orderAfter.ToAssetID.Bytes()...)
var startKey []byte
- if orderAfter.Rate > 0 {
- startKey = calcOrderKey(orderAfter.FromAssetID, orderAfter.ToAssetID, calcUTXOHash(orderAfter), orderAfter.Rate)
+ if orderAfter.Rate() > 0 {
+ startKey = calcOrderKey(orderAfter.FromAssetID, orderAfter.ToAssetID, orderAfter.UTXOHash(), orderAfter.Rate())
}
itr := m.db.IteratorPrefixWithStart(orderPrefix, startKey, false)
var orders []*common.Order
for txNum := 0; txNum < ordersNum && itr.Next(); txNum++ {
- movUtxo := &common.MovUtxo{}
- if err := json.Unmarshal(itr.Value(), movUtxo); err != nil {
+ orderData := &orderData{}
+ if err := json.Unmarshal(itr.Value(), orderData); err != nil {
return nil, err
}
- order := &common.Order{
- FromAssetID: orderAfter.FromAssetID,
- ToAssetID: orderAfter.ToAssetID,
- Rate: getRateFromOrderKey(itr.Key(), ordersPrefix),
- Utxo: movUtxo,
- }
- orders = append(orders, order)
+ orders = append(orders, &common.Order{
+ FromAssetID: orderAfter.FromAssetID,
+ ToAssetID: orderAfter.ToAssetID,
+ Utxo: orderData.Utxo,
+ RatioNumerator: orderData.RatioNumerator,
+ RatioDenominator: orderData.RatioDenominator,
+ })
}
return orders, nil
}
-func (m *MovStore) ProcessOrders(addOrders []*common.Order, delOreders []*common.Order, blockHeader *types.BlockHeader) error {
+// ListTradePairsWithStart return n trade pairs after the input trade pair
+func (m *LevelDBMovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
+ var startKey []byte
+ if fromAssetIDAfter != nil && toAssetIDAfter != nil {
+ startKey = calcTradePairKey(fromAssetIDAfter, toAssetIDAfter)
+ }
+
+ itr := m.db.IteratorPrefixWithStart(tradePairsPrefix, startKey, false)
+ defer itr.Release()
+
+ var tradePairs []*common.TradePair
+ for txNum := 0; txNum < tradePairsNum && itr.Next(); txNum++ {
+ key := itr.Key()
+ fromAssetID := getAssetIDFromTradePairKey(key, fromAssetIDPos)
+ toAssetID := getAssetIDFromTradePairKey(key, toAssetIDPos)
+
+ tradePairData := &tradePairData{}
+ if err := json.Unmarshal(itr.Value(), tradePairData); err != nil {
+ return nil, err
+ }
+
+ tradePairs = append(tradePairs, &common.TradePair{FromAssetID: fromAssetID, ToAssetID: toAssetID, Count: tradePairData.Count})
+ }
+ return tradePairs, nil
+}
+
+// ProcessOrders update the DB's image by add new orders, delete the used order
+func (m *LevelDBMovStore) ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error {
if err := m.checkMovDatabaseState(blockHeader); err != nil {
return err
}
batch := m.db.NewBatch()
- tradePairsCnt := make(map[common.TradePair]int)
+ tradePairsCnt := make(map[string]*common.TradePair)
if err := m.addOrders(batch, addOrders, tradePairsCnt); err != nil {
return err
}
- m.deleteOrders(batch, delOreders, tradePairsCnt)
-
+ m.deleteOrders(batch, delOrders, tradePairsCnt)
if err := m.updateTradePairs(batch, tradePairsCnt); err != nil {
return err
}
- hash := blockHeader.Hash()
- if err := m.saveMovDatabaseState(batch, &common.MovDatabaseState{Height: blockHeader.Height, Hash: &hash}); err != nil {
+ state, err := m.calcNextDatabaseState(blockHeader)
+ if err != nil {
+ return err
+ }
+
+ if err := m.saveMovDatabaseState(batch, state); err != nil {
return err
}
return nil
}
-func (m *MovStore) addOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[common.TradePair]int) error {
+func (m *LevelDBMovStore) addOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[string]*common.TradePair) error {
for _, order := range orders {
- data, err := json.Marshal(order.Utxo)
+ orderData := &orderData{
+ Utxo: order.Utxo,
+ RatioNumerator: order.RatioNumerator,
+ RatioDenominator: order.RatioDenominator,
+ }
+ data, err := json.Marshal(orderData)
if err != nil {
return err
}
- key := calcOrderKey(order.FromAssetID, order.ToAssetID, calcUTXOHash(order), order.Rate)
+ key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate())
batch.Set(key, data)
- tradePair := common.TradePair{
+ tradePair := &common.TradePair{
FromAssetID: order.FromAssetID,
ToAssetID: order.ToAssetID,
}
- tradePairsCnt[tradePair] += 1
+ if _, ok := tradePairsCnt[tradePair.Key()]; !ok {
+ tradePairsCnt[tradePair.Key()] = tradePair
+ }
+ tradePairsCnt[tradePair.Key()].Count++
}
return nil
}
-func (m *MovStore) deleteOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[common.TradePair]int) {
- for _, order := range orders {
- key := calcOrderKey(order.FromAssetID, order.ToAssetID, calcUTXOHash(order), order.Rate)
- batch.Delete(key)
+func (m *LevelDBMovStore) calcNextDatabaseState(blockHeader *types.BlockHeader) (*common.MovDatabaseState, error) {
+ hash := blockHeader.Hash()
+ height := blockHeader.Height
- tradePair := common.TradePair{
- FromAssetID: order.FromAssetID,
- ToAssetID: order.ToAssetID,
- }
- tradePairsCnt[tradePair] -= 1
+ state, err := m.GetMovDatabaseState()
+ if err != nil {
+ return nil, err
}
-}
-func (m *MovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
- if value := m.db.Get(bestMatchStore); value != nil {
- state := &common.MovDatabaseState{}
- return state, json.Unmarshal(value, state)
+ if *state.Hash == hash {
+ hash = blockHeader.PreviousBlockHash
+ height = blockHeader.Height - 1
}
- return nil, errors.New("don't find state of mov-database")
+ return &common.MovDatabaseState{Height: height, Hash: &hash}, nil
}
-func (m *MovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
- var startKey []byte
- if fromAssetIDAfter != nil && toAssetIDAfter != nil {
- startKey = calcTradePairKey(fromAssetIDAfter, toAssetIDAfter)
+func (m *LevelDBMovStore) checkMovDatabaseState(header *types.BlockHeader) error {
+ state, err := m.GetMovDatabaseState()
+ if err != nil {
+ return err
}
- itr := m.db.IteratorPrefixWithStart(tradePairsPrefix, startKey, false)
- defer itr.Release()
+ if (*state.Hash == header.PreviousBlockHash && (state.Height+1) == header.Height) || *state.Hash == header.Hash() {
+ return nil
+ }
- var tradePairs []*common.TradePair
- for txNum := 0; txNum < tradePairsNum && itr.Next(); txNum++ {
- key := itr.Key()
- fromAssetID := getAssetIDFromTradePairKey(key, tradePairsPrefix, 0)
- toAssetID := getAssetIDFromTradePairKey(key, tradePairsPrefix, 1)
+ return errors.New("the status of the block is inconsistent with that of mov-database")
+}
- tradePairData := &tradePairData{}
- if err := json.Unmarshal(itr.Value(), tradePairData); err != nil {
- return nil, err
+func (m *LevelDBMovStore) deleteOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[string]*common.TradePair) {
+ for _, order := range orders {
+ key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate())
+ batch.Delete(key)
+
+ tradePair := &common.TradePair{
+ FromAssetID: order.FromAssetID,
+ ToAssetID: order.ToAssetID,
+ }
+ if _, ok := tradePairsCnt[tradePair.Key()]; !ok {
+ tradePairsCnt[tradePair.Key()] = tradePair
}
+ tradePairsCnt[tradePair.Key()].Count--
+ }
+}
- tradePairs = append(tradePairs, &common.TradePair{FromAssetID: fromAssetID, ToAssetID: toAssetID, Count: tradePairData.Count})
+func (m *LevelDBMovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDatabaseState) error {
+ value, err := json.Marshal(state)
+ if err != nil {
+ return err
}
- return tradePairs, nil
+
+ batch.Set(bestMatchStore, value)
+ return nil
}
-func (m *MovStore) updateTradePairs(batch dbm.Batch, tradePairs map[common.TradePair]int) error {
- for k, v := range tradePairs {
- key := calcTradePairKey(k.FromAssetID, k.ToAssetID)
+func (m *LevelDBMovStore) updateTradePairs(batch dbm.Batch, tradePairs map[string]*common.TradePair) error {
+ for _, v := range tradePairs {
+ key := calcTradePairKey(v.FromAssetID, v.ToAssetID)
tradePairData := &tradePairData{}
if value := m.db.Get(key); value != nil {
if err := json.Unmarshal(value, tradePairData); err != nil {
return err
}
- } else if v < 0 {
- return errors.New("don't find trade pair")
}
- tradePairData.Count += v
+ if tradePairData.Count += v.Count; tradePairData.Count < 0 {
+ return errors.New("negative trade count")
+ }
+
if tradePairData.Count > 0 {
value, err := json.Marshal(tradePairData)
if err != nil {
}
return nil
}
-
-func (m *MovStore) checkMovDatabaseState(header *types.BlockHeader) error {
- state, err := m.GetMovDatabaseState()
- if err != nil {
- return err
- }
-
- blockHash := header.Hash()
- if (state.Hash.String() == header.PreviousBlockHash.String() && (state.Height+1) == header.Height) || state.Hash.String() == blockHash.String() {
- return nil
- }
-
- return errors.New("the status of the block is inconsistent with that of mov-database")
-}
-
-func (m *MovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDatabaseState) error {
- value, err := json.Marshal(state)
- if err != nil {
- return err
- }
-
- batch.Set(bestMatchStore, value)
- return nil
-}
"github.com/stretchr/testify/require"
"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"
assetID6 = &bc.AssetID{V0: 6}
assetID7 = &bc.AssetID{V0: 7}
assetID8 = &bc.AssetID{V0: 8}
-)
-func TestCalcUTXOHash(t *testing.T) {
- wantHash := "7cbaf92f950f2a6bededd6cc5ec08c924505f5365b0a8af963e1d52912c99667"
- controlProgramStr := "0014ab5acbea076f269bfdc8ededbed7d0a13e6e0b19"
-
- var controlProgram chainjson.HexBytes
- controlProgram.UnmarshalText([]byte(controlProgramStr))
-
- sourceID := testutil.MustDecodeHash("ca2faf5fcbf8ee2b43560a32594f608528b12a1fe79cee85252564f886f91060")
- order := &common.Order{
- FromAssetID: consensus.BTMAssetID,
- Utxo: &common.MovUtxo{
- SourceID: &sourceID,
- SourcePos: 0,
- Amount: 31249300000,
- ControlProgram: controlProgram[:],
+ mockOrders = []*common.Order{
+ &common.Order{
+ FromAssetID: assetID1,
+ ToAssetID: assetID2,
+ RatioNumerator: 100090,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 21},
+ Amount: 1,
+ SourcePos: 0,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: assetID1,
+ ToAssetID: assetID2,
+ RatioNumerator: 90,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 22},
+ Amount: 1,
+ SourcePos: 0,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: assetID1,
+ ToAssetID: assetID2,
+ RatioNumerator: 97,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 23},
+ Amount: 1,
+ SourcePos: 0,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: assetID1,
+ ToAssetID: assetID2,
+ RatioNumerator: 98,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 13},
+ Amount: 1,
+ SourcePos: 0,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: assetID1,
+ ToAssetID: assetID2,
+ RatioNumerator: 98,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 24},
+ Amount: 10,
+ SourcePos: 1,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: assetID1,
+ ToAssetID: assetID2,
+ RatioNumerator: 99,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 24},
+ Amount: 1,
+ SourcePos: 0,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: assetID1,
+ ToAssetID: assetID2,
+ RatioNumerator: 96,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 25},
+ Amount: 1,
+ SourcePos: 0,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: assetID1,
+ ToAssetID: assetID2,
+ RatioNumerator: 95,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 26},
+ Amount: 1,
+ SourcePos: 0,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: assetID1,
+ ToAssetID: assetID2,
+ RatioNumerator: 90,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 1},
+ Amount: 1,
+ SourcePos: 0,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: assetID1,
+ ToAssetID: assetID2,
+ RatioNumerator: 90,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 2},
+ Amount: 1,
+ SourcePos: 0,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: assetID3,
+ ToAssetID: assetID2,
+ RatioNumerator: 96,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 33},
+ Amount: 1,
+ SourcePos: 0,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: assetID4,
+ ToAssetID: assetID2,
+ RatioNumerator: 95,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 34},
+ Amount: 1,
+ SourcePos: 0,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: assetID4,
+ ToAssetID: assetID2,
+ RatioNumerator: 96,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 36},
+ Amount: 1,
+ SourcePos: 0,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: assetID5,
+ ToAssetID: assetID2,
+ RatioNumerator: 96,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 37},
+ Amount: 1,
+ SourcePos: 0,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: assetID6,
+ ToAssetID: assetID2,
+ RatioNumerator: 98,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 38},
+ Amount: 1,
+ SourcePos: 0,
+ ControlProgram: []byte("aa"),
+ },
},
}
+)
+
+func TestGetAssetIDFromTradePairKey(t *testing.T) {
+ b := calcTradePairKey(assetID1, assetID2)
+ gotA := getAssetIDFromTradePairKey(b, fromAssetIDPos)
+ gotB := getAssetIDFromTradePairKey(b, toAssetIDPos)
- hash := calcUTXOHash(order)
- if hash.String() != wantHash {
- t.Fatal("The function is incorrect")
+ if *gotA != *assetID1 {
+ t.Fatalf("got wrong from asset id got %s, want %s", gotA.String(), assetID1.String())
}
+ if *gotB != *assetID2 {
+ t.Fatalf("got wrong to asset id got %s, want %s", gotB.String(), assetID2.String())
+ }
}
func TestSortOrderKey(t *testing.T) {
}
cases := []struct {
- orders []common.Order
+ orders []*common.Order
want []expectedData
}{
{
- orders: []common.Order{
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 1.00090,
+ orders: []*common.Order{
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 100090,
+ RatioDenominator: 100000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 21},
Amount: 1,
ControlProgram: []byte("aa"),
},
},
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 0.00090,
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 90,
+ RatioDenominator: 100000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 22},
Amount: 1,
ControlProgram: []byte("aa"),
},
},
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 0.00097,
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 97,
+ RatioDenominator: 100000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 23},
Amount: 1,
ControlProgram: []byte("aa"),
},
},
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 0.00098,
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 98,
+ RatioDenominator: 100000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 13},
Amount: 1,
ControlProgram: []byte("aa"),
},
},
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 0.00098,
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 98,
+ RatioDenominator: 100000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 24},
Amount: 10,
ControlProgram: []byte("aa"),
},
},
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 0.00099,
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 98,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 25},
+ Amount: 10,
+ SourcePos: 1,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 98,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 26},
+ Amount: 10,
+ SourcePos: 1,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 98,
+ RatioDenominator: 100000,
+ Utxo: &common.MovUtxo{
+ SourceID: &bc.Hash{V0: 27},
+ Amount: 10,
+ SourcePos: 1,
+ ControlProgram: []byte("aa"),
+ },
+ },
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 99,
+ RatioDenominator: 100000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 24},
Amount: 1,
ControlProgram: []byte("aa"),
},
},
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 0.00096,
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 96,
+ RatioDenominator: 100000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 25},
Amount: 1,
ControlProgram: []byte("aa"),
},
},
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 0.00095,
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 95,
+ RatioDenominator: 100000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 26},
Amount: 1,
ControlProgram: []byte("aa"),
},
},
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 0.00091,
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 91,
+ RatioDenominator: 100000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 26},
Amount: 1,
ControlProgram: []byte("aa"),
},
},
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 0.00092,
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 92,
+ RatioDenominator: 100000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 27},
Amount: 1,
ControlProgram: []byte("aa"),
},
},
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 0.00093,
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 93,
+ RatioDenominator: 100000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 28},
Amount: 1,
ControlProgram: []byte("aa"),
},
},
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 0.00094,
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 94,
+ RatioDenominator: 100000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 29},
Amount: 1,
ControlProgram: []byte("aa"),
},
},
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 0.00077,
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 77,
+ RatioDenominator: 100000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 30},
Amount: 1,
ControlProgram: []byte("aa"),
},
},
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 0.00088,
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 88,
+ RatioDenominator: 100000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 31},
Amount: 1,
ControlProgram: []byte("aa"),
},
},
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 999999.9521,
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 9999999521,
+ RatioDenominator: 10000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 32},
Amount: 1,
ControlProgram: []byte("aa"),
},
},
- common.Order{
- FromAssetID: &bc.AssetID{V0: 1},
- ToAssetID: &bc.AssetID{V0: 0},
- Rate: 888888.7954,
+ &common.Order{
+ FromAssetID: &bc.AssetID{V0: 1},
+ ToAssetID: &bc.AssetID{V0: 0},
+ RatioNumerator: 8888887954,
+ RatioDenominator: 10000,
Utxo: &common.MovUtxo{
SourceID: &bc.Hash{V0: 33},
Amount: 1,
},
expectedData{
rate: 0.00098,
+ utxoHash: "14b51a6103f75d9cacdf0f9551467588c687ed3b029e25c646d276720569e227",
+ },
+ expectedData{
+ rate: 0.00098,
utxoHash: "1fa9fae83d0a5401a4e92f80636966486e763eecca588aa11dff02b415320602",
},
expectedData{
rate: 0.00098,
+ utxoHash: "6687d18ddbe4e7381a844e393ca3032a412285c9da6988eff182106e28ba09ca",
+ },
+ expectedData{
+ rate: 0.00098,
+ utxoHash: "841b1de7c871dfe6e2d1886809d9ae12ec45e570233b03879305232b096fda43",
+ },
+ expectedData{
+ rate: 0.00098,
utxoHash: "a4bc534c267d35a9eafc25cd66e0cb270a2537a51186605b7f7591bc567ab4c6",
},
expectedData{
for i, c := range cases {
for _, order := range c.orders {
- key := calcOrderKey(order.FromAssetID, order.ToAssetID, calcUTXOHash(&order), order.Rate)
+ key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate())
data, err := json.Marshal(order.Utxo)
if err != nil {
t.Fatal(err)
}
got := []expectedData{}
-
itr := db.IteratorPrefixWithStart(nil, nil, false)
for itr.Next() {
key := itr.Key()
copy(b[:], key[pos+8:])
utxoHash := bc.NewHash(b)
- rate := getRateFromOrderKey(key, ordersPrefix)
got = append(got, expectedData{
- rate: rate,
+ rate: getRateFromOrderKey(key),
utxoHash: utxoHash.String(),
})
}
if !testutil.DeepEqual(c.want, got) {
t.Errorf("case %v: got recovery status, got: %v, want: %v.", i, got, c.want)
}
-
}
}
{
desc: "add order",
addOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
+ mockOrders[0],
+ mockOrders[1],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[6],
+ mockOrders[7],
+ },
+ blockHeader: &types.BlockHeader{Height: 1, PreviousBlockHash: bc.Hash{V0: 524821139490765641, V1: 2484214155808702787, V2: 9108473449351508820, V3: 7972721253564512122}},
+ wantOrders: []*common.Order{
+ mockOrders[1],
+ mockOrders[7],
+ mockOrders[6],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[0],
+ },
+ wantTradePairs: []*common.TradePair{
+ &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 8},
+ },
+ wantDBState: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
+ },
+ {
+ desc: "del some order",
+ beforeOrders: []*common.Order{
+ mockOrders[0],
+ mockOrders[1],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[6],
+ mockOrders[7],
+ },
+ beforeTradePairs: []*common.TradePair{
+ &common.TradePair{
FromAssetID: assetID1,
ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
+ Count: 8,
},
- &common.Order{
+ },
+ beforeDBStatus: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
+ delOrders: []*common.Order{
+ mockOrders[0],
+ mockOrders[1],
+ mockOrders[2],
+ },
+ blockHeader: &types.BlockHeader{Height: 2, PreviousBlockHash: bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
+ wantOrders: []*common.Order{
+ mockOrders[7],
+ mockOrders[6],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ },
+ wantTradePairs: []*common.TradePair{
+ &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 5},
+ },
+ wantDBState: &common.MovDatabaseState{Height: 2, Hash: &bc.Hash{V0: 3724755213446347384, V1: 158878632373345042, V2: 18283800951484248781, V3: 7520797730449067221}},
+ },
+ {
+ desc: "del all order",
+ beforeOrders: []*common.Order{
+ mockOrders[0],
+ mockOrders[1],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[6],
+ mockOrders[7],
+ },
+ beforeTradePairs: []*common.TradePair{
+ &common.TradePair{
FromAssetID: assetID1,
ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
+ Count: 8,
},
- &common.Order{
+ },
+ beforeDBStatus: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
+ delOrders: []*common.Order{
+ mockOrders[0],
+ mockOrders[1],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[6],
+ mockOrders[7],
+ },
+ blockHeader: &types.BlockHeader{Height: 2, PreviousBlockHash: bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
+ wantOrders: []*common.Order{},
+ wantTradePairs: []*common.TradePair{},
+ wantDBState: &common.MovDatabaseState{Height: 2, Hash: &bc.Hash{V0: 3724755213446347384, V1: 158878632373345042, V2: 18283800951484248781, V3: 7520797730449067221}},
+ },
+ {
+ desc: "Add and delete the same trade pair", // Add and delete different transaction pairs
+ beforeOrders: []*common.Order{
+ mockOrders[0],
+ mockOrders[1],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[6],
+ mockOrders[7],
+ },
+ beforeTradePairs: []*common.TradePair{
+ &common.TradePair{
FromAssetID: assetID1,
ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
+ Count: 8,
},
},
- blockHeader: &types.BlockHeader{Height: 1, PreviousBlockHash: bc.Hash{V0: 524821139490765641, V1: 2484214155808702787, V2: 9108473449351508820, V3: 7972721253564512122}},
+ beforeDBStatus: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
+ addOrders: []*common.Order{
+ mockOrders[8],
+ mockOrders[9],
+ },
+ delOrders: []*common.Order{
+ mockOrders[0],
+ mockOrders[1],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[6],
+ mockOrders[7],
+ },
+ blockHeader: &types.BlockHeader{Height: 2, PreviousBlockHash: bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
wantOrders: []*common.Order{
- &common.Order{
+ mockOrders[9],
+ mockOrders[8],
+ },
+ wantTradePairs: []*common.TradePair{
+ &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 2},
+ },
+ wantDBState: &common.MovDatabaseState{Height: 2, Hash: &bc.Hash{V0: 3724755213446347384, V1: 158878632373345042, V2: 18283800951484248781, V3: 7520797730449067221}},
+ },
+ {
+ desc: "Add and delete different transaction pairs",
+ beforeOrders: []*common.Order{
+ mockOrders[0],
+ mockOrders[1],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[6],
+ mockOrders[7],
+ mockOrders[10],
+ mockOrders[11],
+ },
+ beforeTradePairs: []*common.TradePair{
+ &common.TradePair{
FromAssetID: assetID1,
ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
+ Count: 8,
},
- &common.Order{
- FromAssetID: assetID1,
+ &common.TradePair{
+ FromAssetID: assetID3,
ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
+ Count: 1,
},
- &common.Order{
- FromAssetID: assetID1,
+ &common.TradePair{
+ FromAssetID: assetID4,
ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- wantTradePairs: []*common.TradePair{
- &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 8},
- },
- wantDBState: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
- },
- {
- desc: "del some order",
- beforeOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- beforeTradePairs: []*common.TradePair{
- &common.TradePair{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Count: 8,
+ Count: 1,
},
},
beforeDBStatus: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
+ addOrders: []*common.Order{
+ mockOrders[12],
+ mockOrders[13],
+ mockOrders[14],
+ },
delOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
+ mockOrders[0],
+ mockOrders[1],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[6],
+ mockOrders[7],
+ mockOrders[10],
},
blockHeader: &types.BlockHeader{Height: 2, PreviousBlockHash: bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
wantOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
+ mockOrders[11],
+ mockOrders[12],
+ mockOrders[13],
+ mockOrders[14],
},
wantTradePairs: []*common.TradePair{
- &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 5},
+ &common.TradePair{FromAssetID: assetID4, ToAssetID: assetID2, Count: 2},
+ &common.TradePair{FromAssetID: assetID5, ToAssetID: assetID2, Count: 1},
+ &common.TradePair{FromAssetID: assetID6, ToAssetID: assetID2, Count: 1},
},
wantDBState: &common.MovDatabaseState{Height: 2, Hash: &bc.Hash{V0: 3724755213446347384, V1: 158878632373345042, V2: 18283800951484248781, V3: 7520797730449067221}},
},
- {
- desc: "del all order",
- beforeOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- beforeTradePairs: []*common.TradePair{
- &common.TradePair{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Count: 8,
- },
- },
- beforeDBStatus: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
- delOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- blockHeader: &types.BlockHeader{Height: 2, PreviousBlockHash: bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
- wantOrders: []*common.Order{},
- wantTradePairs: []*common.TradePair{},
- wantDBState: &common.MovDatabaseState{Height: 2, Hash: &bc.Hash{V0: 3724755213446347384, V1: 158878632373345042, V2: 18283800951484248781, V3: 7520797730449067221}},
- },
- {
- desc: "Add and delete the same trade pair", //Add and delete different transaction pairs
- beforeOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- beforeTradePairs: []*common.TradePair{
- &common.TradePair{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Count: 8,
- },
- },
- beforeDBStatus: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
- addOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 1},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 2},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- delOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- blockHeader: &types.BlockHeader{Height: 2, PreviousBlockHash: bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
- wantOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 2},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 1},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- wantTradePairs: []*common.TradePair{
- &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 2},
- },
- wantDBState: &common.MovDatabaseState{Height: 2, Hash: &bc.Hash{V0: 3724755213446347384, V1: 158878632373345042, V2: 18283800951484248781, V3: 7520797730449067221}},
- },
- {
- desc: "Add and delete different transaction pairs",
- beforeOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID3,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 33},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID4,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 34},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- beforeTradePairs: []*common.TradePair{
- &common.TradePair{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Count: 8,
- },
- &common.TradePair{
- FromAssetID: assetID3,
- ToAssetID: assetID2,
- Count: 1,
- },
- &common.TradePair{
- FromAssetID: assetID4,
- ToAssetID: assetID2,
- Count: 1,
- },
- },
- beforeDBStatus: &common.MovDatabaseState{Height: 1, Hash: &bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
- addOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID4,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 36},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID5,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 37},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID6,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 38},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- delOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID3,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 33},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- blockHeader: &types.BlockHeader{Height: 2, PreviousBlockHash: bc.Hash{V0: 14213576368347360351, V1: 16287398171800437029, V2: 9513543230620030445, V3: 8534035697182508177}},
- wantOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID4,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 34},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID4,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 36},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID5,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 37},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID6,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 38},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- wantTradePairs: []*common.TradePair{
- &common.TradePair{FromAssetID: assetID4, ToAssetID: assetID2, Count: 2},
- &common.TradePair{FromAssetID: assetID5, ToAssetID: assetID2, Count: 1},
- &common.TradePair{FromAssetID: assetID6, ToAssetID: assetID2, Count: 1},
- },
- wantDBState: &common.MovDatabaseState{Height: 2, Hash: &bc.Hash{V0: 3724755213446347384, V1: 158878632373345042, V2: 18283800951484248781, V3: 7520797730449067221}},
- },
- }
-
- initBlockHeader := &types.BlockHeader{
- Height: 0,
- Version: 1,
- }
-
- height := initBlockHeader.Height
- hash := initBlockHeader.Hash()
-
- defer os.RemoveAll("temp")
- for i, c := range cases {
- testDB := dbm.NewDB("testdb", "leveldb", "temp")
- movStore, err := NewMovStore(testDB, height, &hash)
- if err != nil {
- t.Fatalf("case %d: NewMovStore error %v.", i, err)
- }
-
- batch := movStore.db.NewBatch()
- tradePairsCnt := make(map[common.TradePair]int)
- movStore.addOrders(batch, c.beforeOrders, tradePairsCnt)
- if len(c.beforeOrders) > 0 {
- tradePairsCnt = make(map[common.TradePair]int)
- for _, tradePair := range c.beforeTradePairs {
- tradePairsCnt[*tradePair] = tradePair.Count
- }
- movStore.updateTradePairs(batch, tradePairsCnt)
- movStore.saveMovDatabaseState(batch, c.beforeDBStatus)
- }
- batch.Write()
-
- if err := movStore.ProcessOrders(c.addOrders, c.delOrders, c.blockHeader); err != nil {
- t.Fatalf("case %d: ProcessOrders error %v.", i, err)
- }
-
- var gotOrders []*common.Order
-
- tmp, err := movStore.ListOrders(&common.Order{FromAssetID: assetID1, ToAssetID: assetID2, Rate: 0})
- if err != nil {
- t.Fatalf("case %d: ListOrders(assetID1 and assetID2) error %v.", i, err)
- }
-
- gotOrders = append(gotOrders, tmp...)
-
- tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID3, ToAssetID: assetID2, Rate: 0})
- if err != nil {
- t.Fatalf("case %d: ListOrders(assetID3 and assetID2) error %v.", i, err)
- }
-
- gotOrders = append(gotOrders, tmp...)
-
- tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID4, ToAssetID: assetID2, Rate: 0})
- if err != nil {
- t.Fatalf("case %d: ListOrders(assetID4 and assetID2) error %v.", i, err)
- }
-
- gotOrders = append(gotOrders, tmp...)
-
- tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID5, ToAssetID: assetID2, Rate: 0})
- if err != nil {
- t.Fatalf("case %d: ListOrders(assetID5 and assetID2) error %v.", i, err)
- }
-
- gotOrders = append(gotOrders, tmp...)
-
- tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID6, ToAssetID: assetID2, Rate: 0})
- if err != nil {
- t.Fatalf("case %d: ListOrders(assetID6 and assetID2) error %v.", i, err)
- }
-
- gotOrders = append(gotOrders, tmp...)
-
- if !testutil.DeepEqual(gotOrders, c.wantOrders) {
- t.Fatalf("case %d: got orders , gotOrders: %v, wantOrders: %v.", i, gotOrders, c.wantOrders)
- }
-
- gotTradePairs, err := movStore.ListTradePairsWithStart(nil, nil)
- if err != nil {
- t.Fatalf("case %d: ListTradePairsWithStart error %v.", i, err)
- }
-
- if !testutil.DeepEqual(gotTradePairs, c.wantTradePairs) {
- t.Fatalf("case %d: got tradePairs, gotTradePairs: %v, wantTradePairs: %v.", i, gotTradePairs, c.wantTradePairs)
- }
-
- gotDBState, err := movStore.GetMovDatabaseState()
- if err != nil {
- t.Fatalf("case %d: GetMovDatabaseState error %v.", i, err)
- }
-
- if !testutil.DeepEqual(gotDBState, c.wantDBState) {
- t.Fatalf("case %d: got tradePairs, gotDBState: %v, wantDBStatus: %v.", i, gotDBState, c.wantDBState)
- }
-
- testDB.Close()
- os.RemoveAll("temp")
- }
-}
-
-func TestListOrders(t *testing.T) {
- cases := []struct {
- desc string
- storeOrders []*common.Order
- query *common.Order
- wantOrders []*common.Order
- }{
- {
- desc: "empty",
- query: &common.Order{FromAssetID: assetID1, ToAssetID: assetID2},
- wantOrders: []*common.Order{},
- },
- {
- desc: "query from first",
- storeOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- query: &common.Order{FromAssetID: assetID1, ToAssetID: assetID2},
- wantOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- },
- {
- desc: "query from middle",
- storeOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- query: &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- wantOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- },
}
initBlockHeader := &types.BlockHeader{
defer os.RemoveAll("temp")
for i, c := range cases {
testDB := dbm.NewDB("testdb", "leveldb", "temp")
- movStore, err := NewMovStore(testDB, height, &hash)
- if err != nil {
- t.Fatalf("case %d: NewMovStore error %v.", i, err)
+ movStore := NewLevelDBMovStore(testDB)
+ if err := movStore.InitDBState(height, &hash); err != nil {
+ t.Fatalf("case %d: InitDBState error %v.", i, err)
}
batch := movStore.db.NewBatch()
- tradePairsCnt := make(map[common.TradePair]int)
- movStore.addOrders(batch, c.storeOrders, tradePairsCnt)
- movStore.updateTradePairs(batch, tradePairsCnt)
+ tradePairsCnt := make(map[string]*common.TradePair)
+ movStore.addOrders(batch, c.beforeOrders, tradePairsCnt)
+ if len(c.beforeOrders) > 0 {
+ tradePairsCnt = make(map[string]*common.TradePair)
+ for _, tradePair := range c.beforeTradePairs {
+ tradePairsCnt[tradePair.Key()] = tradePair
+ }
+ movStore.updateTradePairs(batch, tradePairsCnt)
+ movStore.saveMovDatabaseState(batch, c.beforeDBStatus)
+ }
batch.Write()
- gotOrders, err := movStore.ListOrders(c.query)
+ if err := movStore.ProcessOrders(c.addOrders, c.delOrders, c.blockHeader); err != nil {
+ t.Fatalf("case %d: ProcessOrders error %v.", i, err)
+ }
+
+ var gotOrders []*common.Order
+
+ tmp, err := movStore.ListOrders(&common.Order{FromAssetID: assetID1, ToAssetID: assetID2, RatioNumerator: 0, RatioDenominator: 1})
if err != nil {
- t.Fatalf("case %d: ListOrders error %v.", i, err)
+ t.Fatalf("case %d: ListOrders(assetID1 and assetID2) error %v.", i, err)
}
- if !testutil.DeepEqual(gotOrders, c.wantOrders) {
- t.Fatalf("case %d: got orders , gotOrders: %v, wantOrders: %v.", i, gotOrders, c.wantOrders)
+ gotOrders = append(gotOrders, tmp...)
+
+ tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID3, ToAssetID: assetID2, RatioNumerator: 0, RatioDenominator: 1})
+ if err != nil {
+ t.Fatalf("case %d: ListOrders(assetID3 and assetID2) error %v.", i, err)
}
- testDB.Close()
- os.RemoveAll("temp")
- }
-}
+ gotOrders = append(gotOrders, tmp...)
-func TestAddOrders(t *testing.T) {
- cases := []struct {
- desc string
- beforeOrders []*common.Order
- addOrders []*common.Order
- wantOrders []*common.Order
- }{
- {
- desc: "empty",
- addOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- },
- wantOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
+ tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID4, ToAssetID: assetID2, RatioNumerator: 0, RatioDenominator: 1})
+ if err != nil {
+ t.Fatalf("case %d: ListOrders(assetID4 and assetID2) error %v.", i, err)
+ }
+
+ gotOrders = append(gotOrders, tmp...)
+
+ tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID5, ToAssetID: assetID2, RatioNumerator: 0, RatioDenominator: 1})
+ if err != nil {
+ t.Fatalf("case %d: ListOrders(assetID5 and assetID2) error %v.", i, err)
+ }
+
+ gotOrders = append(gotOrders, tmp...)
+
+ tmp, err = movStore.ListOrders(&common.Order{FromAssetID: assetID6, ToAssetID: assetID2, RatioNumerator: 0, RatioDenominator: 1})
+ if err != nil {
+ t.Fatalf("case %d: ListOrders(assetID6 and assetID2) error %v.", i, err)
+ }
+
+ gotOrders = append(gotOrders, tmp...)
+
+ if !testutil.DeepEqual(gotOrders, c.wantOrders) {
+ t.Fatalf("case %d: got orders , gotOrders: %v, wantOrders: %v.", i, gotOrders, c.wantOrders)
+ }
+
+ gotTradePairs, err := movStore.ListTradePairsWithStart(nil, nil)
+ if err != nil {
+ t.Fatalf("case %d: ListTradePairsWithStart error %v.", i, err)
+ }
+
+ if !testutil.DeepEqual(gotTradePairs, c.wantTradePairs) {
+ t.Fatalf("case %d: got tradePairs, gotTradePairs: %v, wantTradePairs: %v.", i, gotTradePairs, c.wantTradePairs)
+ }
+
+ gotDBState, err := movStore.GetMovDatabaseState()
+ if err != nil {
+ t.Fatalf("case %d: GetMovDatabaseState error %v.", i, err)
+ }
+
+ if !testutil.DeepEqual(gotDBState, c.wantDBState) {
+ t.Fatalf("case %d: got tradePairs, gotDBState: %v, wantDBStatus: %v.", i, gotDBState, c.wantDBState)
+ }
+
+ testDB.Close()
+ os.RemoveAll("temp")
+ }
+}
+
+func TestListOrders(t *testing.T) {
+ cases := []struct {
+ desc string
+ storeOrders []*common.Order
+ query *common.Order
+ wantOrders []*common.Order
+ }{
+ {
+ desc: "empty",
+ query: &common.Order{FromAssetID: assetID1, ToAssetID: assetID2},
+ wantOrders: []*common.Order{},
+ },
+ {
+ desc: "query from first",
+ storeOrders: []*common.Order{
+ mockOrders[0],
+ mockOrders[1],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[6],
+ mockOrders[7],
+ mockOrders[10],
+ },
+ query: &common.Order{FromAssetID: assetID1, ToAssetID: assetID2},
+ wantOrders: []*common.Order{
+ mockOrders[1],
+ mockOrders[7],
+ mockOrders[6],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[0],
},
},
{
- desc: "Stored data already exists",
- beforeOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
+ desc: "query from middle",
+ storeOrders: []*common.Order{
+ mockOrders[0],
+ mockOrders[1],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[6],
+ mockOrders[7],
+ },
+ query: mockOrders[3],
+ wantOrders: []*common.Order{
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[0],
},
+ },
+ }
+
+ initBlockHeader := &types.BlockHeader{
+ Height: 0,
+ Version: 1,
+ }
+
+ height := initBlockHeader.Height
+ hash := initBlockHeader.Hash()
+
+ defer os.RemoveAll("temp")
+ for i, c := range cases {
+ testDB := dbm.NewDB("testdb", "leveldb", "temp")
+ movStore := NewLevelDBMovStore(testDB)
+ if err := movStore.InitDBState(height, &hash); err != nil {
+ t.Fatalf("case %d: InitDBState error %v.", i, err)
+ }
+
+ batch := movStore.db.NewBatch()
+ tradePairsCnt := make(map[string]*common.TradePair)
+ movStore.addOrders(batch, c.storeOrders, tradePairsCnt)
+ movStore.updateTradePairs(batch, tradePairsCnt)
+ batch.Write()
+
+ gotOrders, err := movStore.ListOrders(c.query)
+ if err != nil {
+ t.Fatalf("case %d: ListOrders error %v.", i, err)
+ }
+
+ if !testutil.DeepEqual(gotOrders, c.wantOrders) {
+ t.Fatalf("case %d: got orders , gotOrders: %v, wantOrders: %v.", i, gotOrders, c.wantOrders)
+ }
+
+ testDB.Close()
+ os.RemoveAll("temp")
+ }
+}
+
+func TestAddOrders(t *testing.T) {
+ cases := []struct {
+ desc string
+ beforeOrders []*common.Order
+ addOrders []*common.Order
+ wantOrders []*common.Order
+ }{
+ {
+ desc: "empty",
addOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
+ mockOrders[0],
+ mockOrders[1],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[6],
+ mockOrders[7],
},
- wantOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
+ wantOrders: []*common.Order{
+ mockOrders[1],
+ mockOrders[7],
+ mockOrders[6],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[0],
+ },
+ },
+ {
+ desc: "Stored data already exists",
+ beforeOrders: []*common.Order{
+ mockOrders[0],
+ mockOrders[1],
+ mockOrders[2],
+ mockOrders[3],
+ },
+ addOrders: []*common.Order{
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[6],
+ mockOrders[7],
+ },
+ wantOrders: []*common.Order{
+ mockOrders[1],
+ mockOrders[7],
+ mockOrders[6],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[0],
},
},
}
defer os.RemoveAll("temp")
for i, c := range cases {
testDB := dbm.NewDB("testdb", "leveldb", "temp")
- movStore, err := NewMovStore(testDB, height, &hash)
- if err != nil {
- t.Fatalf("case %d: NewMovStore error %v.", i, err)
+ movStore := NewLevelDBMovStore(testDB)
+ if err := movStore.InitDBState(height, &hash); err != nil {
+ t.Fatalf("case %d: InitDBState error %v.", i, err)
}
batch := movStore.db.NewBatch()
- tradePairsCnt := make(map[common.TradePair]int)
+ tradePairsCnt := make(map[string]*common.TradePair)
movStore.addOrders(batch, c.beforeOrders, tradePairsCnt)
movStore.updateTradePairs(batch, tradePairsCnt)
batch.Write()
- tradePairsCnt = make(map[common.TradePair]int)
+ tradePairsCnt = make(map[string]*common.TradePair)
movStore.addOrders(batch, c.addOrders, tradePairsCnt)
batch.Write()
{
desc: "empty",
delOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
+ mockOrders[0],
+ mockOrders[1],
},
wantOrders: []*common.Order{},
err: errors.New("don't find trade pair"),
{
desc: "Delete existing data",
beforeOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
+ mockOrders[1],
+ mockOrders[7],
+ mockOrders[6],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[0],
},
delOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 10,
- SourcePos: 1,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
+ mockOrders[4],
+ mockOrders[5],
+ mockOrders[6],
+ mockOrders[7],
},
wantOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 22},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00097,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 23},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00098,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 13},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 1.00090,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 21},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
+ mockOrders[1],
+ mockOrders[2],
+ mockOrders[3],
+ mockOrders[0],
},
err: nil,
},
{
desc: "Delete all data",
beforeOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
+ mockOrders[7],
+ mockOrders[6],
+ mockOrders[5],
},
delOrders: []*common.Order{
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00099,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 24},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00096,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 25},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
- &common.Order{
- FromAssetID: assetID1,
- ToAssetID: assetID2,
- Rate: 0.00095,
- Utxo: &common.MovUtxo{
- SourceID: &bc.Hash{V0: 26},
- Amount: 1,
- SourcePos: 0,
- ControlProgram: []byte("aa"),
- },
- },
+ mockOrders[5],
+ mockOrders[6],
+ mockOrders[7],
},
wantOrders: []*common.Order{},
err: nil,
defer os.RemoveAll("temp")
for i, c := range cases {
testDB := dbm.NewDB("testdb", "leveldb", "temp")
- movStore, err := NewMovStore(testDB, height, &hash)
- if err != nil {
- t.Fatalf("case %d: NewMovStore error %v.", i, err)
+ movStore := NewLevelDBMovStore(testDB)
+ if err := movStore.InitDBState(height, &hash); err != nil {
+ t.Fatalf("case %d: InitDBState error %v.", i, err)
}
batch := movStore.db.NewBatch()
- tradePairsCnt := make(map[common.TradePair]int)
+ tradePairsCnt := make(map[string]*common.TradePair)
movStore.addOrders(batch, c.beforeOrders, tradePairsCnt)
movStore.updateTradePairs(batch, tradePairsCnt)
batch.Write()
- tradePairsCnt = make(map[common.TradePair]int)
+ tradePairsCnt = make(map[string]*common.TradePair)
movStore.deleteOrders(batch, c.delOrders, tradePairsCnt)
movStore.updateTradePairs(batch, tradePairsCnt)
batch.Write()
func TestListTradePairsWithStart(t *testing.T) {
cases := []struct {
desc string
- storeTradePairs map[common.TradePair]int
+ storeTradePairs map[string]*common.TradePair
query *common.TradePair
wantTradePairs []*common.TradePair
}{
},
{
desc: "query from first",
- storeTradePairs: map[common.TradePair]int{
- common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}: 1,
- common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}: 2,
- common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}: 3,
- common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}: 4,
- common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}: 5,
- common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}: 6,
+ storeTradePairs: map[string]*common.TradePair{
+ (&common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}).Key(): {FromAssetID: assetID1, ToAssetID: assetID2, Count: 1},
+ (&common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}).Key(): {FromAssetID: assetID2, ToAssetID: assetID3, Count: 2},
+ (&common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}).Key(): {FromAssetID: assetID3, ToAssetID: assetID4, Count: 3},
+ (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}).Key(): {FromAssetID: assetID4, ToAssetID: assetID5, Count: 4},
+ (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}).Key(): {FromAssetID: assetID4, ToAssetID: assetID6, Count: 5},
+ (&common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}).Key(): {FromAssetID: assetID5, ToAssetID: assetID7, Count: 6},
},
query: &common.TradePair{},
wantTradePairs: []*common.TradePair{
},
{
desc: "query from middle",
- storeTradePairs: map[common.TradePair]int{
- common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}: 1,
- common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}: 2,
- common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}: 3,
- common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}: 4,
- common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}: 5,
- common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}: 6,
- common.TradePair{FromAssetID: assetID6, ToAssetID: assetID8}: 7,
+ storeTradePairs: map[string]*common.TradePair{
+ (&common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}).Key(): {FromAssetID: assetID1, ToAssetID: assetID2, Count: 1},
+ (&common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}).Key(): {FromAssetID: assetID2, ToAssetID: assetID3, Count: 2},
+ (&common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}).Key(): {FromAssetID: assetID3, ToAssetID: assetID4, Count: 3},
+ (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}).Key(): {FromAssetID: assetID4, ToAssetID: assetID5, Count: 4},
+ (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}).Key(): {FromAssetID: assetID4, ToAssetID: assetID6, Count: 5},
+ (&common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}).Key(): {FromAssetID: assetID5, ToAssetID: assetID7, Count: 6},
+ (&common.TradePair{FromAssetID: assetID6, ToAssetID: assetID8}).Key(): {FromAssetID: assetID6, ToAssetID: assetID8, Count: 7},
},
query: &common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4, Count: 3},
wantTradePairs: []*common.TradePair{
defer os.RemoveAll("temp")
for i, c := range cases {
testDB := dbm.NewDB("testdb", "leveldb", "temp")
- movStore, err := NewMovStore(testDB, height, &hash)
- if err != nil {
- t.Fatalf("case %d: NewMovStore error %v.", i, err)
+ movStore := NewLevelDBMovStore(testDB)
+ if err := movStore.InitDBState(height, &hash); err != nil {
+ t.Fatalf("case %d: InitDBState error %v.", i, err)
}
batch := movStore.db.NewBatch()
func TestUpdateTradePairs(t *testing.T) {
cases := []struct {
desc string
- beforeTradePairs map[common.TradePair]int
- addTradePairs map[common.TradePair]int
- delTradePairs map[common.TradePair]int
+ beforeTradePairs map[string]*common.TradePair
+ addTradePairs map[string]*common.TradePair
+ delTradePairs map[string]*common.TradePair
wantTradePairs []*common.TradePair
}{
{
desc: "empty",
- addTradePairs: map[common.TradePair]int{
- common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}: 1,
- common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}: 2,
- common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}: 3,
- common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}: 4,
- common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}: 5,
- common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}: 6,
+ addTradePairs: map[string]*common.TradePair{
+ (&common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}).Key(): {FromAssetID: assetID1, ToAssetID: assetID2, Count: 1},
+ (&common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}).Key(): {FromAssetID: assetID2, ToAssetID: assetID3, Count: 2},
+ (&common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}).Key(): {FromAssetID: assetID3, ToAssetID: assetID4, Count: 3},
+ (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}).Key(): {FromAssetID: assetID4, ToAssetID: assetID5, Count: 4},
+ (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}).Key(): {FromAssetID: assetID4, ToAssetID: assetID6, Count: 5},
+ (&common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}).Key(): {FromAssetID: assetID5, ToAssetID: assetID7, Count: 6},
},
wantTradePairs: []*common.TradePair{
&common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 1},
},
{
desc: "Stored data already exists",
- beforeTradePairs: map[common.TradePair]int{
- common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}: 1,
- common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}: 2,
- common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}: 3,
+ beforeTradePairs: map[string]*common.TradePair{
+ (&common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}).Key(): {FromAssetID: assetID1, ToAssetID: assetID2, Count: 1},
+ (&common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}).Key(): {FromAssetID: assetID2, ToAssetID: assetID3, Count: 2},
+ (&common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}).Key(): {FromAssetID: assetID3, ToAssetID: assetID4, Count: 3},
},
- addTradePairs: map[common.TradePair]int{
- common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}: 4,
- common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}: 5,
- common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}: 6,
+ addTradePairs: map[string]*common.TradePair{
+ (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}).Key(): {FromAssetID: assetID4, ToAssetID: assetID5, Count: 4},
+ (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}).Key(): {FromAssetID: assetID4, ToAssetID: assetID6, Count: 5},
+ (&common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}).Key(): {FromAssetID: assetID5, ToAssetID: assetID7, Count: 6},
},
wantTradePairs: []*common.TradePair{
&common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2, Count: 1},
},
{
desc: "delete some data",
- beforeTradePairs: map[common.TradePair]int{
- common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}: 1,
- common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}: 2,
- common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}: 3,
- common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}: 4,
- common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}: 5,
- common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}: 6,
- },
- delTradePairs: map[common.TradePair]int{
- common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}: -1,
- common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}: -4,
- common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}: -2,
- common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}: -4,
+ beforeTradePairs: map[string]*common.TradePair{
+ (&common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}).Key(): {FromAssetID: assetID1, ToAssetID: assetID2, Count: 1},
+ (&common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3}).Key(): {FromAssetID: assetID2, ToAssetID: assetID3, Count: 2},
+ (&common.TradePair{FromAssetID: assetID3, ToAssetID: assetID4}).Key(): {FromAssetID: assetID3, ToAssetID: assetID4, Count: 3},
+ (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}).Key(): {FromAssetID: assetID4, ToAssetID: assetID5, Count: 4},
+ (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}).Key(): {FromAssetID: assetID4, ToAssetID: assetID6, Count: 5},
+ (&common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}).Key(): {FromAssetID: assetID5, ToAssetID: assetID7, Count: 6},
+ },
+ delTradePairs: map[string]*common.TradePair{
+ (&common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}).Key(): {FromAssetID: assetID1, ToAssetID: assetID2, Count: -1},
+ (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID5}).Key(): {FromAssetID: assetID4, ToAssetID: assetID5, Count: -4},
+ (&common.TradePair{FromAssetID: assetID4, ToAssetID: assetID6}).Key(): {FromAssetID: assetID4, ToAssetID: assetID6, Count: -2},
+ (&common.TradePair{FromAssetID: assetID5, ToAssetID: assetID7}).Key(): {FromAssetID: assetID5, ToAssetID: assetID7, Count: -4},
},
wantTradePairs: []*common.TradePair{
&common.TradePair{FromAssetID: assetID2, ToAssetID: assetID3, Count: 2},
defer os.RemoveAll("temp")
for i, c := range cases {
testDB := dbm.NewDB("testdb", "leveldb", "temp")
- movStore, err := NewMovStore(testDB, height, &hash)
- if err != nil {
- t.Fatalf("case %d: NewMovStore error %v.", i, err)
+ movStore := NewLevelDBMovStore(testDB)
+ if err := movStore.InitDBState(height, &hash); err != nil {
+ t.Fatalf("case %d: InitDBState error %v.", i, err)
}
batch := movStore.db.NewBatch()
defer os.RemoveAll("temp")
for i, c := range cases {
testDB := dbm.NewDB("testdb", "leveldb", "temp")
- movStore, err := NewMovStore(testDB, height, &hash)
- if err != nil {
- t.Fatalf("case %d: NewMovStore error %v.", i, err)
+ movStore := NewLevelDBMovStore(testDB)
+ if err := movStore.InitDBState(height, &hash); err != nil {
+ t.Fatalf("case %d: InitDBState error %v.", i, err)
}
batch := movStore.db.NewBatch()
--- /dev/null
+package match
+
+import (
+ "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"
+)
+
+// Engine is used to generate math transactions
+type Engine struct {
+ orderBook *OrderBook
+ feeStrategy FeeStrategy
+ rewardProgram []byte
+}
+
+// NewEngine return a new Engine
+func NewEngine(orderBook *OrderBook, feeStrategy FeeStrategy, rewardProgram []byte) *Engine {
+ return &Engine{orderBook: orderBook, feeStrategy: feeStrategy, rewardProgram: rewardProgram}
+}
+
+// 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.orderBook.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(sortOrders(e.orderBook.PeekOrders(tradePairs)))
+ if err != nil {
+ return nil, err
+ }
+
+ for _, tradePair := range tradePairs {
+ e.orderBook.PopOrder(tradePair)
+ }
+
+ if err := e.addPartialTradeOrder(tx); err != nil {
+ return nil, err
+ }
+ return tx, nil
+}
+
+func (e *Engine) addMatchTxFeeOutput(txData *types.TxData, refunds []RefundAssets, fees []*bc.AssetAmount) error {
+ for _, feeAmount := range fees {
+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*feeAmount.AssetId, feeAmount.Amount, e.rewardProgram))
+ }
+
+ for i, refund := range refunds {
+ // each trading participant may be refunded multiple assets
+ for _, assetAmount := range refund {
+ contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram())
+ if err != nil {
+ return err
+ }
+
+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*assetAmount.AssetId, assetAmount.Amount, contractArgs.SellerProgram))
+ }
+ }
+ return nil
+}
+
+func (e *Engine) addPartialTradeOrder(tx *types.Tx) error {
+ for i, output := range tx.Outputs {
+ if !segwit.IsP2WMCScript(output.ControlProgram()) || output.AssetAmount().Amount == 0 {
+ continue
+ }
+
+ order, err := common.NewOrderFromOutput(tx, i)
+ if err != nil {
+ return err
+ }
+
+ e.orderBook.AddOrder(order)
+ }
+ return nil
+}
+
+func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
+ txData := &types.TxData{Version: 1}
+ for _, order := range orders {
+ input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram)
+ txData.Inputs = append(txData.Inputs, input)
+ }
+
+ receivedAmounts, priceDiffs := CalcReceivedAmount(orders)
+ allocatedAssets := e.feeStrategy.Allocate(receivedAmounts, priceDiffs)
+ if err := addMatchTxOutput(txData, orders, receivedAmounts, allocatedAssets.Receives); err != nil {
+ return nil, err
+ }
+
+ if err := e.addMatchTxFeeOutput(txData, allocatedAssets.Refunds, allocatedAssets.Fees); 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 addMatchTxOutput(txData *types.TxData, orders []*common.Order, receivedAmounts, deductFeeReceives []*bc.AssetAmount) error {
+ for i, order := range orders {
+ contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
+ if err != nil {
+ return err
+ }
+
+ requestAmount := CalcRequestAmount(order.Utxo.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
+ receivedAmount := receivedAmounts[i].Amount
+ shouldPayAmount := calcShouldPayAmount(receivedAmount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
+ isPartialTrade := requestAmount > receivedAmount
+
+ setMatchTxArguments(txData.Inputs[i], isPartialTrade, len(txData.Outputs), receivedAmounts[i].Amount)
+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, deductFeeReceives[i].Amount, contractArgs.SellerProgram))
+ if isPartialTrade {
+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram))
+ }
+ }
+ return nil
+}
+
+func calcOppositeIndex(size int, selfIdx int) int {
+ return (selfIdx + 1) % size
+}
+
+// CalcRequestAmount is from amount * numerator / ratioDenominator
+func CalcRequestAmount(fromAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
+ res := big.NewInt(0).SetUint64(fromAmount)
+ res.Mul(res, big.NewInt(ratioNumerator)).Quo(res, big.NewInt(ratioDenominator))
+ if !res.IsUint64() {
+ return 0
+ }
+ return res.Uint64()
+}
+
+func calcShouldPayAmount(receiveAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
+ res := big.NewInt(0).SetUint64(receiveAmount)
+ res.Mul(res, big.NewInt(ratioDenominator)).Quo(res, big.NewInt(ratioNumerator))
+ if !res.IsUint64() {
+ return 0
+ }
+ return res.Uint64()
+}
+
+// CalcReceivedAmount return amount of assets received by each participant in the matching transaction and the price difference
+func CalcReceivedAmount(orders []*common.Order) ([]*bc.AssetAmount, []*bc.AssetAmount) {
+ var receivedAmounts, priceDiffs, shouldPayAmounts []*bc.AssetAmount
+ for i, order := range orders {
+ requestAmount := CalcRequestAmount(order.Utxo.Amount, order.RatioNumerator, order.RatioDenominator)
+ oppositeOrder := orders[calcOppositeIndex(len(orders), i)]
+ receiveAmount := vprMath.MinUint64(oppositeOrder.Utxo.Amount, requestAmount)
+ shouldPayAmount := calcShouldPayAmount(receiveAmount, order.RatioNumerator, order.RatioDenominator)
+ receivedAmounts = append(receivedAmounts, &bc.AssetAmount{AssetId: order.ToAssetID, Amount: receiveAmount})
+ shouldPayAmounts = append(shouldPayAmounts, &bc.AssetAmount{AssetId: order.FromAssetID, Amount: shouldPayAmount})
+ }
+
+ for i, receivedAmount := range receivedAmounts {
+ oppositeShouldPayAmount := shouldPayAmounts[calcOppositeIndex(len(orders), i)]
+ if oppositeShouldPayAmount.Amount > receivedAmount.Amount {
+ assetID := oppositeShouldPayAmount.AssetId
+ amount := oppositeShouldPayAmount.Amount - receivedAmount.Amount
+ priceDiffs = append(priceDiffs, &bc.AssetAmount{AssetId: assetID, Amount: amount})
+ }
+ }
+ return receivedAmounts, priceDiffs
+}
+
+// IsMatched check does the orders can be exchange
+func IsMatched(orders []*common.Order) bool {
+ sortedOrders := sortOrders(orders)
+ if len(sortedOrders) == 0 {
+ return false
+ }
+
+ product := big.NewRat(1, 1)
+ for _, order := range orders {
+ product.Mul(product, big.NewRat(order.RatioNumerator, order.RatioDenominator))
+ }
+ one := big.NewRat(1, 1)
+ return product.Cmp(one) <= 0
+}
+
+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 sortOrders(orders []*common.Order) []*common.Order {
+ if len(orders) == 0 {
+ return nil
+ }
+
+ orderMap := make(map[bc.AssetID]*common.Order)
+ firstOrder := orders[0]
+ for i := 1; i < len(orders); i++ {
+ orderMap[*orders[i].FromAssetID] = orders[i]
+ }
+
+ sortedOrders := []*common.Order{firstOrder}
+ for order := firstOrder; *order.ToAssetID != *firstOrder.FromAssetID; {
+ nextOrder, ok := orderMap[*order.ToAssetID]
+ if !ok {
+ return nil
+ }
+
+ sortedOrders = append(sortedOrders, nextOrder)
+ order = nextOrder
+ }
+ return sortedOrders
+}
+
+func validateTradePairs(tradePairs []*common.TradePair) error {
+ if len(tradePairs) < 2 {
+ return errors.New("size of trade pairs at least 2")
+ }
+
+ assetMap := make(map[string]bool)
+ for _, tradePair := range tradePairs {
+ assetMap[tradePair.FromAssetID.String()] = true
+ if *tradePair.FromAssetID == *tradePair.ToAssetID {
+ return errors.New("from asset id can't equal to asset id")
+ }
+ }
+
+ for _, tradePair := range tradePairs {
+ key := tradePair.ToAssetID.String()
+ if _, ok := assetMap[key]; !ok {
+ return errors.New("invalid trade pairs")
+ }
+ delete(assetMap, key)
+ }
+ return nil
+}
--- /dev/null
+package match
+
+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/protocol/bc/types"
+ "github.com/bytom/vapor/protocol/validation"
+)
+
+func TestGenerateMatchedTxs(t *testing.T) {
+ btc2eth := &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH}
+ eth2btc := &common.TradePair{FromAssetID: &mock.ETH, ToAssetID: &mock.BTC}
+ eth2eos := &common.TradePair{FromAssetID: &mock.ETH, ToAssetID: &mock.EOS}
+ eos2btc := &common.TradePair{FromAssetID: &mock.EOS, ToAssetID: &mock.BTC}
+
+ cases := []struct {
+ desc string
+ tradePairs []*common.TradePair
+ initStoreOrders []*common.Order
+ wantMatchedTxs []*types.Tx
+ }{
+ {
+ desc: "full matched",
+ tradePairs: []*common.TradePair{btc2eth, eth2btc},
+ initStoreOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1],
+ mock.Eth2BtcOrders[0],
+ },
+ wantMatchedTxs: []*types.Tx{
+ mock.MatchedTxs[1],
+ },
+ },
+ {
+ desc: "partial matched",
+ tradePairs: []*common.TradePair{btc2eth, eth2btc},
+ 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",
+ tradePairs: []*common.TradePair{btc2eth, eth2btc},
+ 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",
+ tradePairs: []*common.TradePair{btc2eth, eth2btc},
+ initStoreOrders: []*common.Order{
+ mock.Btc2EthOrders[1],
+ mock.Eth2BtcOrders[0],
+ },
+ wantMatchedTxs: []*types.Tx{},
+ },
+ {
+ desc: "cycle match",
+ tradePairs: []*common.TradePair{btc2eth, eth2eos, eos2btc},
+ initStoreOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Eth2EosOrders[0], mock.Eos2BtcOrders[0],
+ },
+ wantMatchedTxs: []*types.Tx{
+ mock.MatchedTxs[6],
+ },
+ },
+ {
+ desc: "multiple assets as a fee",
+ tradePairs: []*common.TradePair{btc2eth, eth2btc},
+ initStoreOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Eth2BtcOrders[3],
+ },
+ wantMatchedTxs: []*types.Tx{
+ mock.MatchedTxs[11],
+ },
+ },
+ }
+
+ for i, c := range cases {
+ movStore := mock.NewMovStore([]*common.TradePair{btc2eth, eth2btc}, c.initStoreOrders)
+ matchEngine := NewEngine(NewOrderBook(movStore, nil, nil), NewDefaultFeeStrategy(), mock.RewardProgram)
+ var gotMatchedTxs []*types.Tx
+ for matchEngine.HasMatchedTx(c.tradePairs...) {
+ matchedTx, err := matchEngine.NextMatchedTx(c.tradePairs...)
+ 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 j, gotMatchedTx := range gotMatchedTxs {
+ if _, err := validation.ValidateTx(gotMatchedTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Version: 1}}); err != nil {
+ t.Fatal(err)
+ }
+
+ c.wantMatchedTxs[j].Version = 1
+ byteData, err := c.wantMatchedTxs[j].MarshalText()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ c.wantMatchedTxs[j].SerializedSize = uint64(len(byteData))
+ wantMatchedTx := types.NewTx(c.wantMatchedTxs[j].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 TestValidateTradePairs(t *testing.T) {
+ cases := []struct {
+ desc string
+ tradePairs []*common.TradePair
+ wantError bool
+ }{
+ {
+ desc: "valid case of two trade pairs",
+ tradePairs: []*common.TradePair{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.BTC,
+ },
+ },
+ wantError: false,
+ },
+ {
+ desc: "invalid case of two trade pairs",
+ tradePairs: []*common.TradePair{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.EOS,
+ },
+ },
+ wantError: true,
+ },
+ {
+ desc: "valid case of three trade pairs",
+ tradePairs: []*common.TradePair{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.EOS,
+ },
+ {
+ FromAssetID: &mock.EOS,
+ ToAssetID: &mock.BTC,
+ },
+ },
+ wantError: false,
+ },
+ {
+ desc: "invalid case of three trade pairs",
+ tradePairs: []*common.TradePair{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.BTC,
+ },
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.BTC,
+ },
+ },
+ wantError: true,
+ },
+ {
+ desc: "valid case 2 of three trade pairs",
+ tradePairs: []*common.TradePair{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ },
+ {
+ FromAssetID: &mock.EOS,
+ ToAssetID: &mock.BTC,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.EOS,
+ },
+ },
+ wantError: false,
+ },
+ }
+
+ for i, c := range cases {
+ err := validateTradePairs(c.tradePairs)
+ if c.wantError && err == nil {
+ t.Errorf("#%d(%s): want error, got no error", i, c.desc)
+ }
+
+ if !c.wantError && err != nil {
+ t.Errorf("#%d(%s): want no error, got error (%v)", i, c.desc, err)
+ }
+ }
+}
+
+func TestIsMatched(t *testing.T) {
+ cases := []struct {
+ desc string
+ orders []*common.Order
+ wantMatched bool
+ }{
+ {
+ desc: "ratio is equals",
+ orders: []*common.Order{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ RatioNumerator: 6250,
+ RatioDenominator: 3,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.BTC,
+ RatioNumerator: 3,
+ RatioDenominator: 6250,
+ },
+ },
+ wantMatched: true,
+ },
+ {
+ desc: "ratio is equals, and numerator and denominator are multiples of the opposite",
+ orders: []*common.Order{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ RatioNumerator: 6250,
+ RatioDenominator: 3,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.BTC,
+ RatioNumerator: 9,
+ RatioDenominator: 18750,
+ },
+ },
+ wantMatched: true,
+ },
+ {
+ desc: "matched with a slight diff",
+ orders: []*common.Order{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ RatioNumerator: 62500000000000000,
+ RatioDenominator: 30000000000001,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.BTC,
+ RatioNumerator: 3,
+ RatioDenominator: 6250,
+ },
+ },
+ wantMatched: true,
+ },
+ {
+ desc: "not matched with a slight diff",
+ orders: []*common.Order{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ RatioNumerator: 62500000000000001,
+ RatioDenominator: 30000000000000,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.BTC,
+ RatioNumerator: 3,
+ RatioDenominator: 6250,
+ },
+ },
+ wantMatched: false,
+ },
+ {
+ desc: "ring matched",
+ orders: []*common.Order{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ RatioNumerator: 2000000003,
+ RatioDenominator: 100000001,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.EOS,
+ RatioNumerator: 400000000001,
+ RatioDenominator: 2000000003,
+ },
+ {
+ FromAssetID: &mock.EOS,
+ ToAssetID: &mock.BTC,
+ RatioNumerator: 100000001,
+ RatioDenominator: 400000000001,
+ },
+ },
+ wantMatched: true,
+ },
+ {
+ desc: "ring matched with a slight diff",
+ orders: []*common.Order{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ RatioNumerator: 2000000003,
+ RatioDenominator: 100000001,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.EOS,
+ RatioNumerator: 400000000001,
+ RatioDenominator: 2000000003,
+ },
+ {
+ FromAssetID: &mock.EOS,
+ ToAssetID: &mock.BTC,
+ RatioNumerator: 100000000,
+ RatioDenominator: 400000000001,
+ },
+ },
+ wantMatched: true,
+ },
+ {
+ desc: "ring fail matched with a slight diff",
+ orders: []*common.Order{
+ {
+ FromAssetID: &mock.BTC,
+ ToAssetID: &mock.ETH,
+ RatioNumerator: 2000000003,
+ RatioDenominator: 100000001,
+ },
+ {
+ FromAssetID: &mock.ETH,
+ ToAssetID: &mock.EOS,
+ RatioNumerator: 400000000001,
+ RatioDenominator: 2000000003,
+ },
+ {
+ FromAssetID: &mock.EOS,
+ ToAssetID: &mock.BTC,
+ RatioNumerator: 100000002,
+ RatioDenominator: 400000000001,
+ },
+ },
+ wantMatched: false,
+ },
+ }
+
+ for i, c := range cases {
+ gotMatched := IsMatched(c.orders)
+ if gotMatched != c.wantMatched {
+ t.Errorf("#%d(%s) got matched:%v, want matched:%v", i, c.desc, gotMatched, c.wantMatched)
+ }
+ }
+}
--- /dev/null
+package match
+
+import (
+ "math"
+
+ "github.com/bytom/vapor/errors"
+ "github.com/bytom/vapor/protocol/bc"
+)
+
+var (
+ // ErrAmountOfFeeOutOfRange represent The fee charged is out of range
+ ErrAmountOfFeeOutOfRange = errors.New("amount of fee is out of range")
+)
+
+// AllocatedAssets represent reallocated assets after calculating fees
+type AllocatedAssets struct {
+ Receives []*bc.AssetAmount
+ Refunds []RefundAssets
+ Fees []*bc.AssetAmount
+}
+
+// RefundAssets represent alias for assetAmount array, because each transaction participant can be refunded multiple assets
+type RefundAssets []*bc.AssetAmount
+
+// FeeStrategy used to indicate how to charge a matching fee
+type FeeStrategy interface {
+ // Allocate will allocate the price differential in matching transaction to the participants and the fee
+ // @param receiveAmounts the amount of assets that the participants in the matching transaction can received when no fee is considered
+ // @param priceDiffs price differential of matching transaction
+ // @return reallocated assets after calculating fees
+ Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets
+
+ // Validate verify that the fee charged for a matching transaction is correct
+ Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error
+}
+
+// DefaultFeeStrategy represent the default fee charge strategy
+type DefaultFeeStrategy struct {}
+
+// NewDefaultFeeStrategy return a new instance of DefaultFeeStrategy
+func NewDefaultFeeStrategy() *DefaultFeeStrategy {
+ return &DefaultFeeStrategy{}
+}
+
+// Allocate will allocate the price differential in matching transaction to the participants and the fee
+func (d *DefaultFeeStrategy) Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets {
+ feeMap := make(map[bc.AssetID]uint64)
+ for _, priceDiff := range priceDiffs {
+ feeMap[*priceDiff.AssetId] = priceDiff.Amount
+ }
+
+ var fees []*bc.AssetAmount
+ refunds := make([]RefundAssets, len(receiveAmounts))
+ receives := make([]*bc.AssetAmount, len(receiveAmounts))
+
+ for i, receiveAmount := range receiveAmounts {
+ amount := receiveAmount.Amount
+ minFeeAmount := d.calcMinFeeAmount(amount)
+ receives[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: amount - minFeeAmount}
+ feeMap[*receiveAmount.AssetId] += minFeeAmount
+
+ maxFeeAmount := d.calcMaxFeeAmount(amount)
+ feeAmount, reminder := feeMap[*receiveAmount.AssetId], uint64(0)
+ if feeAmount > maxFeeAmount {
+ reminder = feeAmount - maxFeeAmount
+ feeAmount = maxFeeAmount
+ }
+
+ fees = append(fees, &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: feeAmount})
+
+ // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
+ averageAmount := reminder / uint64(len(receiveAmounts))
+ if averageAmount == 0 {
+ averageAmount = 1
+ }
+
+ for j := 0; j < len(receiveAmounts) && reminder > 0; j++ {
+ refundAmount := averageAmount
+ if j == len(receiveAmounts)-1 {
+ refundAmount = reminder
+ }
+ refunds[j] = append(refunds[j], &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: refundAmount})
+ reminder -= averageAmount
+ }
+ }
+ return &AllocatedAssets{Receives: receives, Refunds: refunds, Fees: fees}
+}
+
+// Validate verify that the fee charged for a matching transaction is correct
+func (d *DefaultFeeStrategy) Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error {
+ for _, receiveAmount := range receiveAmounts {
+ feeAmount := feeAmounts[*receiveAmount.AssetId]
+ maxFeeAmount := d.calcMaxFeeAmount(receiveAmount.Amount)
+ minFeeAmount := d.calcMinFeeAmount(receiveAmount.Amount)
+ if feeAmount < minFeeAmount || feeAmount > maxFeeAmount {
+ return ErrAmountOfFeeOutOfRange
+ }
+ }
+ return nil
+}
+
+func (d *DefaultFeeStrategy) calcMinFeeAmount(amount uint64) uint64 {
+ return uint64(math.Ceil(float64(amount) / 1000))
+}
+
+func (d *DefaultFeeStrategy) calcMaxFeeAmount(amount uint64) uint64 {
+ return uint64(math.Ceil(float64(amount) * 0.05))
+}
--- /dev/null
+package match
+
+import (
+ "sort"
+ "sync"
+
+ "github.com/bytom/vapor/application/mov/common"
+ "github.com/bytom/vapor/application/mov/database"
+)
+
+// OrderBook is used to handle the mov orders in memory like stack
+type OrderBook struct {
+ movStore database.MovStore
+ // key of tradePair -> []order
+ dbOrders *sync.Map
+ // key of tradePair -> iterator
+ orderIterators *sync.Map
+
+ // key of tradePair -> []order
+ arrivalAddOrders *sync.Map
+ // key of order -> order
+ arrivalDelOrders *sync.Map
+}
+
+func arrangeArrivalAddOrders(orders []*common.Order) *sync.Map {
+ orderMap := make(map[string][]*common.Order)
+ for _, order := range orders {
+ orderMap[order.TradePair().Key()] = append(orderMap[order.TradePair().Key()], order)
+ }
+
+ arrivalOrderMap := &sync.Map{}
+ for key, orders := range orderMap {
+ sort.Sort(sort.Reverse(common.OrderSlice(orders)))
+ arrivalOrderMap.Store(key, orders)
+ }
+ return arrivalOrderMap
+}
+
+func arrangeArrivalDelOrders(orders []*common.Order) *sync.Map {
+ arrivalDelOrderMap := &sync.Map{}
+ for _, order := range orders {
+ arrivalDelOrderMap.Store(order.Key(), order)
+ }
+ return arrivalDelOrderMap
+}
+
+// NewOrderBook create a new OrderBook object
+func NewOrderBook(movStore database.MovStore, arrivalAddOrders, arrivalDelOrders []*common.Order) *OrderBook {
+ return &OrderBook{
+ movStore: movStore,
+ dbOrders: &sync.Map{},
+ orderIterators: &sync.Map{},
+
+ arrivalAddOrders: arrangeArrivalAddOrders(arrivalAddOrders),
+ arrivalDelOrders: arrangeArrivalDelOrders(arrivalDelOrders),
+ }
+}
+
+// AddOrder add the in memory temp order to order table, because temp order is what left for the
+// partial trade order, so the price should be lowest.
+func (o *OrderBook) AddOrder(order *common.Order) {
+ tradePairKey := order.TradePair().Key()
+ orders := o.getArrivalAddOrders(tradePairKey)
+ // use binary search to find the insert position
+ i := sort.Search(len(orders), func(i int) bool { return order.Cmp(orders[i]) > 0 })
+ orders = append(orders, order)
+ copy(orders[i+1:], orders[i:])
+ orders[i] = order
+
+ o.arrivalAddOrders.Store(tradePairKey, orders)
+}
+
+// DelOrder mark the order has been deleted in order book
+func (o *OrderBook) DelOrder(order *common.Order) {
+ o.arrivalDelOrders.Store(order.Key(), order)
+}
+
+// PeekOrder return the next lowest order of given trade pair
+func (o *OrderBook) PeekOrder(tradePair *common.TradePair) *common.Order {
+ if len(o.getDBOrders(tradePair.Key())) == 0 {
+ o.extendDBOrders(tradePair)
+ }
+
+ var nextOrder *common.Order
+ orders := o.getDBOrders(tradePair.Key())
+ if len(orders) != 0 {
+ nextOrder = orders[len(orders)-1]
+ }
+
+ if nextOrder != nil && o.getArrivalDelOrders(nextOrder.Key()) != nil {
+ o.dbOrders.Store(tradePair.Key(), orders[0:len(orders)-1])
+ return o.PeekOrder(tradePair)
+ }
+
+ arrivalOrder := o.peekArrivalOrder(tradePair)
+ if nextOrder == nil || (arrivalOrder != nil && arrivalOrder.Cmp(nextOrder) < 0) {
+ nextOrder = arrivalOrder
+ }
+ return nextOrder
+}
+
+// PeekOrders return the next lowest orders by given array of trade pairs
+func (o *OrderBook) PeekOrders(tradePairs []*common.TradePair) []*common.Order {
+ var orders []*common.Order
+ for _, tradePair := range tradePairs {
+ order := o.PeekOrder(tradePair)
+ if order == nil {
+ return nil
+ }
+
+ orders = append(orders, order)
+ }
+ return orders
+}
+
+// PopOrder delete the next lowest order of given trade pair
+func (o *OrderBook) PopOrder(tradePair *common.TradePair) {
+ order := o.PeekOrder(tradePair)
+ if order == nil {
+ return
+ }
+
+ orders := o.getDBOrders(tradePair.Key())
+ if len(orders) != 0 && orders[len(orders)-1].Key() == order.Key() {
+ o.dbOrders.Store(tradePair.Key(), orders[0:len(orders)-1])
+ return
+ }
+
+ arrivalOrders := o.getArrivalAddOrders(tradePair.Key())
+ if len(arrivalOrders) != 0 && arrivalOrders[len(arrivalOrders)-1].Key() == order.Key() {
+ o.arrivalAddOrders.Store(tradePair.Key(), arrivalOrders[0:len(arrivalOrders)-1])
+ }
+}
+
+// PopOrders delete the next lowest orders by given trade pairs
+func (o *OrderBook) PopOrders(tradePairs []*common.TradePair) []*common.Order {
+ var orders []*common.Order
+ for _, tradePair := range tradePairs {
+ o.PopOrder(tradePair)
+ }
+ return orders
+}
+
+func (o *OrderBook) getDBOrders(tradePairKey string) []*common.Order {
+ if orders, ok := o.dbOrders.Load(tradePairKey); ok {
+ return orders.([]*common.Order)
+ }
+ return []*common.Order{}
+}
+
+func (o *OrderBook) getArrivalAddOrders(tradePairKey string) []*common.Order {
+ if orders, ok := o.arrivalAddOrders.Load(tradePairKey); ok {
+ return orders.([]*common.Order)
+ }
+ return []*common.Order{}
+}
+
+func (o *OrderBook) getArrivalDelOrders(orderKey string) *common.Order {
+ if order, ok := o.arrivalDelOrders.Load(orderKey); ok {
+ return order.(*common.Order)
+ }
+ return nil
+}
+
+func (o *OrderBook) extendDBOrders(tradePair *common.TradePair) {
+ iterator, ok := o.orderIterators.Load(tradePair.Key())
+ if !ok {
+ iterator = database.NewOrderIterator(o.movStore, tradePair)
+ o.orderIterators.Store(tradePair.Key(), iterator)
+ }
+
+ nextOrders := iterator.(*database.OrderIterator).NextBatch()
+ orders := o.getDBOrders(tradePair.Key())
+ for i := len(nextOrders) - 1; i >= 0; i-- {
+ orders = append(orders, nextOrders[i])
+ }
+ o.dbOrders.Store(tradePair.Key(), orders)
+}
+
+func (o *OrderBook) peekArrivalOrder(tradePair *common.TradePair) *common.Order {
+ orders := o.getArrivalAddOrders(tradePair.Key())
+ delPos := len(orders)
+ for i := delPos - 1; i >= 0; i-- {
+ if o.getArrivalDelOrders(orders[i].Key()) != nil {
+ delPos = i
+ } else {
+ break
+ }
+ }
+
+ if delPos < len(orders) {
+ orders = orders[:delPos]
+ o.arrivalAddOrders.Store(tradePair.Key(), orders)
+ }
+
+ if size := len(orders); size > 0 {
+ return orders[size-1]
+ }
+ return nil
+}
--- /dev/null
+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"
+ "github.com/bytom/vapor/testutil"
+)
+
+var (
+ btc2eth = &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH}
+)
+
+func TestOrderBook(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 {
+ orderBook := NewOrderBook(c.initMovStore, c.initArrivalAddOrders, c.initArrivalDelOrders)
+ for _, order := range c.addOrders {
+ orderBook.AddOrder(order)
+ }
+
+ for _, tradePair := range c.popOrders {
+ orderBook.PopOrder(tradePair)
+ }
+
+ for tradePair, wantOrder := range c.wantPeekedOrders {
+ gotOrder := orderBook.PeekOrder(&tradePair)
+ if wantOrder == gotOrder && wantOrder == nil {
+ continue
+ }
+
+ if gotOrder.Key() != wantOrder.Key() {
+ t.Fatalf("#%d(%s):the key of got order(%v) is not equals key of want order(%v)", i, c.desc, gotOrder, wantOrder)
+ }
+ }
+ }
+}
+
+func TestPeekArrivalOrder(t *testing.T) {
+ cases := []struct {
+ desc string
+ initArrivalAddOrders []*common.Order
+ initArrivalDelOrders []*common.Order
+ peekTradePair *common.TradePair
+ wantArrivalAddOrders []*common.Order
+ wantOrder *common.Order
+ }{
+ {
+ desc: "empty peek",
+ initArrivalAddOrders: []*common.Order{},
+ initArrivalDelOrders: []*common.Order{},
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{},
+ wantOrder: nil,
+ },
+ {
+ desc: "1 element regular peek",
+ initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ initArrivalDelOrders: []*common.Order{},
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ wantOrder: mock.Btc2EthOrders[0],
+ },
+ {
+ desc: "4 element regular peek with",
+ initArrivalAddOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ },
+ initArrivalDelOrders: []*common.Order{},
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ },
+ wantOrder: mock.Btc2EthOrders[3],
+ },
+ {
+ desc: "1 element peek with 1 unrelated deleted order",
+ initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[1]},
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ wantOrder: mock.Btc2EthOrders[0],
+ },
+ {
+ desc: "1 element peek with 1 related deleted order",
+ initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{},
+ wantOrder: nil,
+ },
+ {
+ desc: "4 element peek with first 3 deleted order",
+ initArrivalAddOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ },
+ initArrivalDelOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ },
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[1]},
+ wantOrder: mock.Btc2EthOrders[1],
+ },
+ {
+ desc: "4 element peek with first 1 deleted order",
+ initArrivalAddOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ },
+ initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3]},
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2],
+ },
+ wantOrder: mock.Btc2EthOrders[0],
+ },
+ {
+ desc: "4 element peek with first 2th deleted order",
+ initArrivalAddOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ },
+ initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ peekTradePair: btc2eth,
+ wantArrivalAddOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3],
+ },
+ wantOrder: mock.Btc2EthOrders[3],
+ },
+ }
+
+ for i, c := range cases {
+ orderBook := NewOrderBook(mock.NewMovStore(nil, nil), c.initArrivalAddOrders, c.initArrivalDelOrders)
+ gotOrder := orderBook.PeekOrder(c.peekTradePair)
+ if !testutil.DeepEqual(gotOrder, c.wantOrder) {
+ t.Fatalf("#%d(%s):the key of got order(%v) is not equals key of want order(%v)", i, c.desc, gotOrder, c.wantOrder)
+ }
+
+ wantAddOrders, _ := arrangeArrivalAddOrders(c.wantArrivalAddOrders).Load(c.peekTradePair.Key())
+ gotAddOrders := orderBook.getArrivalAddOrders(c.peekTradePair.Key())
+ if !testutil.DeepEqual(gotAddOrders, wantAddOrders) {
+ t.Fatalf("#%d(%s): the got arrivalAddOrders(%v) is differnt than want arrivalAddOrders(%v)", i, c.desc, gotAddOrders, wantAddOrders)
+ }
+ }
+}
+
+func TestAddOrder(t *testing.T) {
+ cases := []struct {
+ initOrders []*common.Order
+ wantOrders []*common.Order
+ addOrder *common.Order
+ }{
+ {
+ initOrders: []*common.Order{},
+ addOrder: &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 53, RatioDenominator: 1},
+ wantOrders: []*common.Order{
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 53, RatioDenominator: 1},
+ },
+ },
+ {
+ initOrders: []*common.Order{
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+ },
+ addOrder: &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+ wantOrders: []*common.Order{
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+ },
+ },
+ {
+ initOrders: []*common.Order{
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 52, RatioDenominator: 1},
+ },
+ addOrder: &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 53, RatioDenominator: 1},
+ wantOrders: []*common.Order{
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 53, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 52, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+ },
+ },
+ {
+ initOrders: []*common.Order{
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 52, RatioDenominator: 1},
+ },
+ addOrder: &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 49, RatioDenominator: 1},
+ wantOrders: []*common.Order{
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 52, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 49, RatioDenominator: 1},
+ },
+ },
+ {
+ initOrders: []*common.Order{
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 52, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 49, RatioDenominator: 1},
+ },
+ addOrder: &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+ wantOrders: []*common.Order{
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 52, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 51, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 50, RatioDenominator: 1},
+ &common.Order{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH, RatioNumerator: 49, RatioDenominator: 1},
+ },
+ },
+ }
+
+ for i, c := range cases {
+ orderBook := NewOrderBook(mock.NewMovStore(nil, nil), c.initOrders, nil)
+ orderBook.AddOrder(c.addOrder)
+ if gotOrders := orderBook.getArrivalAddOrders(btc2eth.Key()); !testutil.DeepEqual(gotOrders, c.wantOrders) {
+ t.Fatalf("#%d: the gotOrders(%v) is differnt than wantOrders(%v)", i, gotOrders, c.wantOrders)
+ }
+ }
+}
--- /dev/null
+package mov
+
+import (
+ "runtime"
+ "sync"
+
+ "github.com/bytom/vapor/application/mov/common"
+ "github.com/bytom/vapor/application/mov/database"
+ "github.com/bytom/vapor/application/mov/match"
+ "github.com/bytom/vapor/protocol/bc/types"
+)
+
+type matchCollector struct {
+ engine *match.Engine
+ tradePairIterator *database.TradePairIterator
+ gasLeft int64
+ isTimeout func() bool
+
+ workerNum int
+ endWorkCh chan int
+ tradePairCh chan *common.TradePair
+ matchResultCh chan *matchTxResult
+ closeCh chan struct{}
+}
+
+type matchTxResult struct {
+ matchedTx *types.Tx
+ err error
+}
+
+func newMatchTxCollector(engine *match.Engine, iterator *database.TradePairIterator, gasLeft int64, isTimeout func() bool) *matchCollector {
+ workerNum := runtime.NumCPU()
+ return &matchCollector{
+ engine: engine,
+ tradePairIterator: iterator,
+ gasLeft: gasLeft,
+ isTimeout: isTimeout,
+ workerNum: workerNum,
+ endWorkCh: make(chan int, workerNum),
+ tradePairCh: make(chan *common.TradePair, workerNum),
+ matchResultCh: make(chan *matchTxResult),
+ closeCh: make(chan struct{}),
+ }
+}
+
+func (m *matchCollector) result() ([]*types.Tx, error) {
+ var wg sync.WaitGroup
+ for i := 0; i < int(m.workerNum); i++ {
+ wg.Add(1)
+ go m.matchTxWorker(&wg)
+ }
+
+ wg.Add(1)
+ go m.tradePairProducer(&wg)
+
+ matchedTxs, err := m.collect()
+ // wait for all goroutine release
+ wg.Wait()
+ return matchedTxs, err
+}
+
+func (m *matchCollector) collect() ([]*types.Tx, error) {
+ defer close(m.closeCh)
+
+ var matchedTxs []*types.Tx
+ for completed := 0; !m.isTimeout(); {
+ select {
+ case data := <-m.matchResultCh:
+ if data.err != nil {
+ return nil, data.err
+ }
+
+ gasUsed := calcMatchedTxGasUsed(data.matchedTx)
+ if m.gasLeft -= gasUsed; m.gasLeft >= 0 {
+ matchedTxs = append(matchedTxs, data.matchedTx)
+ } else {
+ return matchedTxs, nil
+ }
+ case <-m.endWorkCh:
+ if completed++; completed == m.workerNum {
+ return matchedTxs, nil
+ }
+ }
+ }
+ return matchedTxs, nil
+}
+
+func (m *matchCollector) tradePairProducer(wg *sync.WaitGroup) {
+ defer func() {
+ close(m.tradePairCh)
+ wg.Done()
+ }()
+
+ tradePairMap := make(map[string]bool)
+
+ for m.tradePairIterator.HasNext() {
+ tradePair := m.tradePairIterator.Next()
+ if tradePairMap[tradePair.Key()] {
+ continue
+ }
+
+ tradePairMap[tradePair.Key()] = true
+ tradePairMap[tradePair.Reverse().Key()] = true
+
+ select {
+ case <-m.closeCh:
+ return
+ case m.tradePairCh <- tradePair:
+ }
+ }
+}
+
+func (m *matchCollector) matchTxWorker(wg *sync.WaitGroup) {
+ defer func() {
+ m.endWorkCh <- 1
+ wg.Done()
+ }()
+
+ for {
+ select {
+ case <-m.closeCh:
+ return
+ case tradePair := <-m.tradePairCh:
+ // end worker due to all trade pair has been matched
+ if tradePair == nil {
+ return
+ }
+
+ for m.engine.HasMatchedTx(tradePair, tradePair.Reverse()) {
+ matchedTx, err := m.engine.NextMatchedTx(tradePair, tradePair.Reverse())
+ select {
+ case <-m.closeCh:
+ return
+ case m.matchResultCh <- &matchTxResult{matchedTx: matchedTx, err: err}:
+ if err != nil {
+ return
+ }
+ }
+ }
+ }
+ }
+}
+
+func calcMatchedTxGasUsed(tx *types.Tx) int64 {
+ return int64(len(tx.Inputs))*150 + int64(tx.SerializedSize)
+}
--- /dev/null
+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})
+ EOS = bc.NewAssetID([32]byte{3})
+ ETC = bc.NewAssetID([32]byte{4})
+ RewardProgram = []byte{0x58}
+
+ Btc2EthOrders = []*common.Order{
+ {
+ FromAssetID: &BTC,
+ ToAssetID: Ð,
+ RatioNumerator: 50,
+ RatioDenominator: 1,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("37b8edf656e45a7addf47f5626e114a8c394d918a36f61b5a2905675a09b40ae")),
+ SourcePos: 0,
+ Amount: 10,
+ ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 50, 1),
+ },
+ },
+ {
+ FromAssetID: &BTC,
+ ToAssetID: Ð,
+ RatioNumerator: 53,
+ RatioDenominator: 1,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("3ec2bbfb499a8736d377b547eee5392bcddf7ec2b287e9ed20b5938c3d84e7cd")),
+ SourcePos: 0,
+ Amount: 20,
+ ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"), 53, 1),
+ },
+ },
+ {
+ FromAssetID: &BTC,
+ ToAssetID: Ð,
+ RatioNumerator: 52,
+ RatioDenominator: 1,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("1232bbfb499a8736d377b547eee5392bcddf7ec2b287e9ed20b5938c3d84e7cd")),
+ SourcePos: 0,
+ Amount: 15,
+ ControlProgram: MustCreateP2WMCProgram(ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252"), 53, 1),
+ },
+ },
+ {
+ FromAssetID: &BTC,
+ ToAssetID: Ð,
+ RatioNumerator: 49,
+ RatioDenominator: 1,
+ 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,
+ RatioNumerator: 1,
+ RatioDenominator: 51,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("fba43ff5155209cb1769e2ec0e1d4a33accf899c740865edfc6d1de39b873b29")),
+ SourcePos: 0,
+ Amount: 510,
+ ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253"), 1, 51.0),
+ },
+ },
+ {
+ FromAssetID: Ð,
+ ToAssetID: &BTC,
+ RatioNumerator: 1,
+ RatioDenominator: 52,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("05f24bb847db823075d81786aa270748e02602199cd009c0284f928503846a5a")),
+ SourcePos: 0,
+ Amount: 416,
+ ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254"), 1, 52.0),
+ },
+ },
+ {
+ FromAssetID: Ð,
+ ToAssetID: &BTC,
+ RatioNumerator: 1,
+ RatioDenominator: 54,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("119a02980796dc352cf6475457463aef5666f66622088de3551fa73a65f0d201")),
+ SourcePos: 0,
+ Amount: 810,
+ ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"), 1, 54.0),
+ },
+ },
+ {
+ FromAssetID: Ð,
+ ToAssetID: &BTC,
+ RatioNumerator: 1,
+ RatioDenominator: 150,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("82752cda63c877a8529d7a7461da6096673e45b3e0b019ce44aa18687ad20445")),
+ SourcePos: 0,
+ Amount: 600,
+ ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256"), 1, 150.0),
+ },
+ },
+ }
+
+ Eos2EtcOrders = []*common.Order{
+ {
+ FromAssetID: &EOS,
+ ToAssetID: &ETC,
+ RatioNumerator: 1,
+ RatioDenominator: 2,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("119a02980796dc352cf6475457463aef5666f66622088de3551fa73a65f0d202")),
+ SourcePos: 0,
+ Amount: 100,
+ ControlProgram: MustCreateP2WMCProgram(ETC, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"), 1, 2.0),
+ },
+ },
+ }
+
+ Etc2EosOrders = []*common.Order{
+ {
+ FromAssetID: &ETC,
+ ToAssetID: &EOS,
+ RatioNumerator: 2,
+ RatioDenominator: 1,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("119a02980796dc352cf6475457463aef5666f66622088de3551fa73a65f0d203")),
+ SourcePos: 0,
+ Amount: 50,
+ ControlProgram: MustCreateP2WMCProgram(EOS, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3"), 2, 1.0),
+ },
+ },
+ }
+
+ Eth2EosOrders = []*common.Order{
+ {
+ FromAssetID: Ð,
+ ToAssetID: &EOS,
+ RatioNumerator: 2,
+ RatioDenominator: 1,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("c1502d03946e4ea92abdb33f51638b181839bd0d8767acc2ee5c665b659c4b13")),
+ SourcePos: 0,
+ Amount: 500,
+ ControlProgram: MustCreateP2WMCProgram(EOS, testutil.MustDecodeHexString("0014e3178c0f294a9a8f4b304236406507913091df86"), 2, 1.0),
+ },
+ },
+ }
+
+ Eos2BtcOrders = []*common.Order{
+ {
+ FromAssetID: &EOS,
+ ToAssetID: &BTC,
+ RatioNumerator: 1,
+ RatioDenominator: 100,
+ Utxo: &common.MovUtxo{
+ SourceID: hashPtr(testutil.MustDecodeHash("27cf8a0877dc858968cc06396fe6aa9e02d15f3e44c862fe29fa5fd50497cf20")),
+ SourcePos: 0,
+ Amount: 1000,
+ ControlProgram: MustCreateP2WMCProgram(BTC, testutil.MustDecodeHexString("00144d0dfc8a0c5ce41d31d4f61d99aff70588bff8bc"), 1, 100.0),
+ },
+ },
+ }
+
+ Btc2EthCancelTxs = []*types.Tx{
+ // Btc2EthOrders[0]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewSpendInput([][]byte{{}, {}, vm.Int64Bytes(2)}, *Btc2EthOrders[0].Utxo.SourceID, *Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, Btc2EthOrders[0].Utxo.SourcePos, Btc2EthOrders[0].Utxo.ControlProgram)},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, Btc2EthOrders[0].Utxo.Amount, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"))},
+ }),
+
+ // output 2 of MatchedTxs[2]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewSpendInput([][]byte{{}, {}, vm.Int64Bytes(2)}, *MustNewOrderFromOutput(MatchedTxs[2], 2).Utxo.SourceID, *Eth2BtcOrders[2].FromAssetID, 270, 2, Eth2BtcOrders[2].Utxo.ControlProgram)},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, Eth2BtcOrders[2].Utxo.Amount, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255"))},
+ }),
+ }
+
+ 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)},
+ }),
+ }
+
+ Eos2EtcMakerTxs = []*types.Tx{
+ // Eos2Etc[0]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewSpendInput(nil, *Eos2EtcOrders[0].Utxo.SourceID, *Eos2EtcOrders[0].FromAssetID, Eos2EtcOrders[0].Utxo.Amount, Eos2EtcOrders[0].Utxo.SourcePos, []byte{0x51})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Eos2EtcOrders[0].FromAssetID, Eos2EtcOrders[0].Utxo.Amount, Eos2EtcOrders[0].Utxo.ControlProgram)},
+ }),
+ }
+
+ Etc2EosMakerTxs = []*types.Tx{
+ // Etc2Eos[0]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewSpendInput(nil, *Etc2EosOrders[0].Utxo.SourceID, *Etc2EosOrders[0].FromAssetID, Etc2EosOrders[0].Utxo.Amount, Etc2EosOrders[0].Utxo.SourcePos, []byte{0x51})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Etc2EosOrders[0].FromAssetID, Etc2EosOrders[0].Utxo.Amount, Etc2EosOrders[0].Utxo.ControlProgram)},
+ }),
+ }
+
+ Eth2EosMakerTxs = []*types.Tx{
+ // Eth2Eos[0]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewSpendInput(nil, *Eth2EosOrders[0].Utxo.SourceID, *Eth2EosOrders[0].FromAssetID, Eth2EosOrders[0].Utxo.Amount, Eth2EosOrders[0].Utxo.SourcePos, []byte{0x51})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*Eth2EosOrders[0].FromAssetID, Eth2EosOrders[0].Utxo.Amount, Eth2EosOrders[0].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, 415, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ // re-order
+ types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 2, Btc2EthOrders[0].Utxo.ControlProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 7, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254")),
+ // fee
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 1, RewardProgram),
+ },
+ }),
+
+ // 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, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
+ // fee
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 11, RewardProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, RewardProgram),
+ },
+ }),
+
+ // 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, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+ // re-order
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 270, Eth2BtcOrders[2].Utxo.ControlProgram),
+ // fee
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 25, RewardProgram),
+ types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 1, RewardProgram),
+ // refund
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+ },
+ }),
+ 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, 269, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")),
+ // re-order
+ types.NewIntraChainOutput(*Btc2EthOrders[1].FromAssetID, 15, Btc2EthOrders[1].Utxo.ControlProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 4, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+ // fee
+ types.NewIntraChainOutput(*Btc2EthOrders[1].ToAssetID, 1, RewardProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 1, RewardProgram),
+ },
+ }),
+
+ // 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, 415, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ // re-order
+ types.NewIntraChainOutput(*Btc2EthOrders[0].FromAssetID, 2, Btc2EthOrders[0].Utxo.ControlProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 7, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19254")),
+ // fee
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[1].ToAssetID, 1, RewardProgram),
+ },
+ }),
+
+ // full matched transaction from Eos2EtcMakerTxs[0] Etc2EosMakerTxs[0]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Eos2EtcMakerTxs[0], 0).Utxo.SourceID, *Eos2EtcOrders[0].FromAssetID, Eos2EtcOrders[0].Utxo.Amount, Eos2EtcOrders[0].Utxo.SourcePos, Eos2EtcOrders[0].Utxo.ControlProgram),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Etc2EosMakerTxs[0], 0).Utxo.SourceID, *Etc2EosOrders[0].FromAssetID, Etc2EosOrders[0].Utxo.Amount, Etc2EosOrders[0].Utxo.SourcePos, Etc2EosOrders[0].Utxo.ControlProgram),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 49, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+ types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 99, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3")),
+ // fee
+ types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 1, RewardProgram),
+ types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 1, RewardProgram),
+ },
+ }),
+
+ // cycle matched from Btc2Eth Eth2Eos Eos2Btc
+ 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)}, *Eth2EosOrders[0].Utxo.SourceID, *Eth2EosOrders[0].FromAssetID, Eth2EosOrders[0].Utxo.Amount, Eth2EosOrders[0].Utxo.SourcePos, Eth2EosOrders[0].Utxo.ControlProgram),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, *Eos2BtcOrders[0].Utxo.SourceID, *Eos2BtcOrders[0].FromAssetID, Eos2BtcOrders[0].Utxo.Amount, Eos2BtcOrders[0].Utxo.SourcePos, Eos2BtcOrders[0].Utxo.ControlProgram),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(ETH, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(EOS, 999, testutil.MustDecodeHexString("0014e3178c0f294a9a8f4b304236406507913091df86")),
+ types.NewIntraChainOutput(BTC, 9, testutil.MustDecodeHexString("00144d0dfc8a0c5ce41d31d4f61d99aff70588bff8bc")),
+ // fee
+ types.NewIntraChainOutput(ETH, 1, RewardProgram),
+ types.NewIntraChainOutput(EOS, 1, RewardProgram),
+ types.NewIntraChainOutput(BTC, 1, RewardProgram),
+ },
+ }),
+
+ // partial matched transaction from MatchedTxs[4], Eth2BtcMakerTxs[0]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, testutil.MustDecodeHash("ed810e1672c3b9de27a1db23e017e6b9cc23334b6e3dbd25dfe8857e289b7f06"), *Btc2EthOrders[0].FromAssetID, 2, 1, Btc2EthOrders[0].Utxo.ControlProgram),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *MustNewOrderFromOutput(Eth2BtcMakerTxs[0], 0).Utxo.SourceID, *Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, 0, Eth2BtcOrders[0].Utxo.ControlProgram),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 99, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
+ // re-order
+ types.NewIntraChainOutput(*Eth2BtcOrders[0].FromAssetID, 404, Eth2BtcOrders[0].Utxo.ControlProgram),
+ // fee
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, RewardProgram),
+ },
+ }),
+
+ // partial matched transaction from Btc2EthOrders[3], Eth2BtcOrders[2]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(810), vm.Int64Bytes(0), vm.Int64Bytes(0)}, *Btc2EthOrders[3].Utxo.SourceID, *Btc2EthOrders[3].FromAssetID, Btc2EthOrders[3].Utxo.Amount, Btc2EthOrders[3].Utxo.SourcePos, Btc2EthOrders[3].Utxo.ControlProgram),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, *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[3].ToAssetID, 809, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19252")),
+ // re-order
+ types.NewIntraChainOutput(*Btc2EthOrders[3].FromAssetID, 1, Btc2EthOrders[3].Utxo.ControlProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 14, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+ // fee
+ types.NewIntraChainOutput(*Btc2EthOrders[3].FromAssetID, 2, RewardProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[2].ToAssetID, 1, RewardProgram),
+ },
+ }),
+
+ // full matched transaction from Eos2EtcOrders[0] Etc2EosOrders[0]
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *Eos2EtcOrders[0].Utxo.SourceID, *Eos2EtcOrders[0].FromAssetID, Eos2EtcOrders[0].Utxo.Amount, Eos2EtcOrders[0].Utxo.SourcePos, Eos2EtcOrders[0].Utxo.ControlProgram),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *Etc2EosOrders[0].Utxo.SourceID, *Etc2EosOrders[0].FromAssetID, Etc2EosOrders[0].Utxo.Amount, Etc2EosOrders[0].Utxo.SourcePos, Etc2EosOrders[0].Utxo.ControlProgram),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 49, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+ types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 99, testutil.MustDecodeHexString("0014df7a97e53bbe278e4e44810b0a760fb472daa9a3")),
+ // fee
+ types.NewIntraChainOutput(*Eos2EtcOrders[0].ToAssetID, 1, RewardProgram),
+ types.NewIntraChainOutput(*Etc2EosOrders[0].ToAssetID, 1, RewardProgram),
+ },
+ }),
+
+ // full matched transaction from Btc2EthOrders[0], Eth2BtcMakerTxs[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)}, *MustNewOrderFromOutput(Eth2BtcMakerTxs[0], 0).Utxo.SourceID, *Eth2BtcOrders[0].FromAssetID, Eth2BtcOrders[0].Utxo.Amount, 0, Eth2BtcOrders[0].Utxo.ControlProgram),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19253")),
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 10, RewardProgram),
+ // fee
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 1, RewardProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[0].ToAssetID, 1, RewardProgram),
+ },
+ }),
+
+ // full matched transaction from Btc2EthOrders[0] Eth2BtcOrders[3]
+ 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[3].Utxo.SourceID, *Eth2BtcOrders[3].FromAssetID, Eth2BtcOrders[3].Utxo.Amount, Eth2BtcOrders[3].Utxo.SourcePos, Eth2BtcOrders[3].Utxo.ControlProgram),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
+ // fee
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 25, RewardProgram),
+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 1, RewardProgram),
+ // refund
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 38, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*Btc2EthOrders[0].ToAssetID, 38, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
+ types.NewIntraChainOutput(*Eth2BtcOrders[3].ToAssetID, 3, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19256")),
+ },
+ }),
+ }
+)
+
+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
+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
+package mov
+
+import (
+ "encoding/hex"
+
+ "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"
+ "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"
+)
+
+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")
+ errAssetIDMustUniqueInMatchedTx = errors.New("asset id must unique in matched transaction")
+ errRatioOfTradeLessThanZero = errors.New("ratio arguments must greater than zero")
+ 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")
+ errNotMatchedOrder = errors.New("order in matched tx is not matched")
+ errNotConfiguredRewardProgram = errors.New("reward program is not configured properly")
+ errRewardProgramIsWrong = errors.New("the reward program is not correct")
+)
+
+// Core 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 Core struct {
+ movStore database.MovStore
+ startBlockHeight uint64
+}
+
+// NewCore return a instance of Core by path of mov db
+func NewCore(dbBackend, dbDir string, startBlockHeight uint64) *Core {
+ movDB := dbm.NewDB("mov", dbBackend, dbDir)
+ return &Core{movStore: database.NewLevelDBMovStore(movDB), startBlockHeight: startBlockHeight}
+}
+
+// NewCoreWithDB return a instance of Core by movStore
+func NewCoreWithDB(store *database.LevelDBMovStore, startBlockHeight uint64) *Core {
+ return &Core{movStore: store, 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 *Core) ApplyBlock(block *types.Block) error {
+ if block.Height < m.startBlockHeight {
+ return nil
+ }
+
+ if block.Height == m.startBlockHeight {
+ blockHash := block.Hash()
+ return m.movStore.InitDBState(block.Height, &blockHash)
+ }
+
+ if err := m.validateMatchedTxSequence(block.Transactions); err != nil {
+ return err
+ }
+
+ addOrders, deleteOrders, err := decodeTxsOrders(block.Transactions)
+ if err != nil {
+ return err
+ }
+
+ return m.movStore.ProcessOrders(addOrders, deleteOrders, &block.BlockHeader)
+}
+
+// BeforeProposalBlock return all transactions than can be matched, and the number of transactions cannot exceed the given capacity.
+func (m *Core) BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
+ if blockHeight <= m.startBlockHeight {
+ return nil, nil
+ }
+
+ orderBook, err := buildOrderBook(m.movStore, txs)
+ if err != nil {
+ return nil, err
+ }
+
+ program, _ := getRewardProgram(blockHeight)
+ rewardProgram, err := hex.DecodeString(program)
+ if err != nil {
+ return nil, errNotConfiguredRewardProgram
+ }
+
+ matchEngine := match.NewEngine(orderBook, match.NewDefaultFeeStrategy(), rewardProgram)
+ tradePairIterator := database.NewTradePairIterator(m.movStore)
+ matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout)
+ return matchCollector.result()
+}
+
+// ChainStatus return the current block height and block hash in dex core
+func (m *Core) 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 *Core) DetachBlock(block *types.Block) error {
+ if block.Height < m.startBlockHeight {
+ return nil
+ }
+
+ deleteOrders, addOrders, err := decodeTxsOrders(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 *Core) 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 *Core) Name() string {
+ return "MOV"
+}
+
+// StartHeight return the start block height of current module
+func (m *Core) 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 *Core) ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error {
+ for i, tx := range block.Transactions {
+ if err := m.ValidateTx(tx, verifyResults[i], block.Height); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// ValidateTx validate one transaction.
+func (m *Core) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
+ if blockHeight <= m.startBlockHeight {
+ return nil
+ }
+
+ if verifyResult.StatusFail {
+ return errStatusFailMustFalse
+ }
+
+ if common.IsMatchedTx(tx) {
+ if err := validateMatchedTx(tx, verifyResult, blockHeight); err != nil {
+ return err
+ }
+ } else 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 err := validateMagneticContractArgs(output.AssetAmount(), output.ControlProgram()); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// matchedTxFee is object to record the mov tx's fee information
+type matchedTxFee struct {
+ rewardProgram []byte
+ amount uint64
+}
+
+// calcFeeAmount return the amount of fee in the matching transaction
+func calcFeeAmount(matchedTx *types.Tx) (map[bc.AssetID]*matchedTxFee, error) {
+ assetFeeMap := make(map[bc.AssetID]*matchedTxFee)
+ dealProgMaps := make(map[string]bool)
+
+ for _, input := range matchedTx.Inputs {
+ assetFeeMap[input.AssetID()] = &matchedTxFee{amount: input.AssetAmount().Amount}
+ contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
+ if err != nil {
+ return nil, err
+ }
+
+ dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
+ }
+
+ for _, output := range matchedTx.Outputs {
+ assetAmount := output.AssetAmount()
+ if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
+ assetFeeMap[*assetAmount.AssetId].amount -= assetAmount.Amount
+ if assetFeeMap[*assetAmount.AssetId].amount <= 0 {
+ delete(assetFeeMap, *assetAmount.AssetId)
+ }
+ } else if assetFeeMap[*assetAmount.AssetId].rewardProgram == nil {
+ assetFeeMap[*assetAmount.AssetId].rewardProgram = output.ControlProgram()
+ } else {
+ return nil, errors.Wrap(errRewardProgramIsWrong, "double reward program")
+ }
+ }
+ return assetFeeMap, nil
+}
+
+func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
+ 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.RatioNumerator, contractArgs.RatioDenominator) < 1 {
+ return errRequestAmountMath
+ }
+ return nil
+}
+
+func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error {
+ 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 validateMatchedTxFee(tx, blockHeight)
+}
+
+func validateMatchedTxFee(tx *types.Tx, blockHeight uint64) error {
+ matchedTxFees, err := calcFeeAmount(tx)
+ if err != nil {
+ return err
+ }
+
+ for _, fee := range matchedTxFees {
+ if err := validateRewardProgram(blockHeight, hex.EncodeToString(fee.rewardProgram)); err != nil {
+ return err
+ }
+ }
+
+ orders, err := getDeleteOrdersFromTx(tx)
+ if err != nil {
+ return err
+ }
+
+ receivedAmount, _ := match.CalcReceivedAmount(orders)
+ feeAmounts := make(map[bc.AssetID]uint64)
+ for assetID, fee := range matchedTxFees {
+ feeAmounts[assetID] = fee.amount
+ }
+
+ feeStrategy := match.NewDefaultFeeStrategy()
+ return feeStrategy.Validate(receivedAmount, feeAmounts)
+}
+
+func (m *Core) validateMatchedTxSequence(txs []*types.Tx) error {
+ orderBook := match.NewOrderBook(m.movStore, nil, nil)
+ for _, tx := range txs {
+ if common.IsMatchedTx(tx) {
+ tradePairs, err := getTradePairsFromMatchedTx(tx)
+ if err != nil {
+ return err
+ }
+
+ orders := orderBook.PeekOrders(tradePairs)
+ if err := validateSpendOrders(tx, orders); err != nil {
+ return err
+ }
+
+ orderBook.PopOrders(tradePairs)
+ } else if common.IsCancelOrderTx(tx) {
+ orders, err := getDeleteOrdersFromTx(tx)
+ if err != nil {
+ return err
+ }
+
+ for _, order := range orders {
+ orderBook.DelOrder(order)
+ }
+ }
+
+ addOrders, err := getAddOrdersFromTx(tx)
+ if err != nil {
+ return err
+ }
+
+ for _, order := range addOrders {
+ orderBook.AddOrder(order)
+ }
+ }
+ return nil
+}
+
+func validateSpendOrders(tx *types.Tx, orders []*common.Order) error {
+ if len(tx.Inputs) != len(orders) {
+ return errNotMatchedOrder
+ }
+
+ spendOutputIDs := make(map[string]bool)
+ for _, input := range tx.Inputs {
+ spendOutputID, err := input.SpentOutputID()
+ if err != nil {
+ return err
+ }
+
+ spendOutputIDs[spendOutputID.String()] = true
+ }
+
+ for _, order := range orders {
+ outputID := order.UTXOHash().String()
+ if _, ok := spendOutputIDs[outputID]; !ok {
+ return errSpendOutputIDIsIncorrect
+ }
+ }
+ return nil
+}
+
+func decodeTxsOrders(txs []*types.Tx) ([]*common.Order, []*common.Order, error) {
+ deleteOrderMap := make(map[string]*common.Order)
+ addOrderMap := make(map[string]*common.Order)
+ for _, tx := range txs {
+ 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
+}
+
+func buildOrderBook(store database.MovStore, txs []*types.Tx) (*match.OrderBook, error) {
+ var arrivalAddOrders, arrivalDelOrders []*common.Order
+ for _, tx := range txs {
+ 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.NewOrderBook(store, arrivalAddOrders, arrivalDelOrders), nil
+}
+
+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
+ }
+
+ if output.AssetAmount().Amount == 0 {
+ 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 getTradePairsFromMatchedTx(tx *types.Tx) ([]*common.TradePair, error) {
+ var tradePairs []*common.TradePair
+ for _, tx := range tx.Inputs {
+ contractArgs, err := segwit.DecodeP2WMCProgram(tx.ControlProgram())
+ if err != nil {
+ return nil, err
+ }
+
+ tradePairs = append(tradePairs, &common.TradePair{FromAssetID: tx.AssetAmount().AssetId, ToAssetID: &contractArgs.RequestedAsset})
+ }
+ 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
+}
+
+// getRewardProgram return the reward program by specified block height
+// if no reward program configured, then will return empty string
+// if reward program of 0-100 height is configured, but the specified height is 200, then will return 0-100's reward program
+// the second return value represent whether to find exactly
+func getRewardProgram(height uint64) (string, bool) {
+ rewardPrograms := consensus.ActiveNetParams.MovRewardPrograms
+ if len(rewardPrograms) == 0 {
+ return "", false
+ }
+
+ var program string
+ for _, rewardProgram := range rewardPrograms {
+ program = rewardProgram.Program
+ if height >= rewardProgram.BeginBlock && height <= rewardProgram.EndBlock {
+ return program, true
+ }
+ }
+ return program, false
+}
+
+func validateRewardProgram(height uint64, program string) error {
+ rewardProgram, exact := getRewardProgram(height)
+ if exact && rewardProgram != program {
+ return errRewardProgramIsWrong
+ }
+ return nil
+}
--- /dev/null
+package mov
+
+import (
+ "encoding/hex"
+ "math"
+ "os"
+ "testing"
+
+ "github.com/bytom/vapor/application/mov/common"
+ "github.com/bytom/vapor/application/mov/database"
+ "github.com/bytom/vapor/application/mov/match"
+ "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"
+)
+
+var initBlockHeader = &types.BlockHeader{Height: 1, PreviousBlockHash: bc.Hash{}}
+
+func TestApplyBlock(t *testing.T) {
+ 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 two different trade pairs & different trade pair won't affect each order",
+ block: &types.Block{
+ BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+ Transactions: []*types.Tx{
+ mock.Btc2EthMakerTxs[0],
+ mock.Eth2BtcMakerTxs[0],
+ mock.Eos2EtcMakerTxs[0],
+ mock.Eth2EosMakerTxs[0],
+ },
+ },
+ blockFunc: applyBlock,
+ wantOrders: []*common.Order{
+ mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0),
+ mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0),
+ mock.MustNewOrderFromOutput(mock.Eos2EtcMakerTxs[0], 0),
+ mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
+ },
+ 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: "apply block which node packed maker tx and match transaction in random orde",
+ block: &types.Block{
+ BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+ Transactions: []*types.Tx{
+ mock.Eos2EtcMakerTxs[0],
+ mock.Btc2EthMakerTxs[0],
+ mock.Eth2BtcMakerTxs[1],
+ mock.MatchedTxs[4],
+ mock.Eth2EosMakerTxs[0],
+ mock.Etc2EosMakerTxs[0],
+ mock.MatchedTxs[5],
+ },
+ },
+ blockFunc: applyBlock,
+ initOrders: []*common.Order{},
+ wantOrders: []*common.Order{
+ mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1),
+ mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
+ },
+ wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+ },
+ {
+ desc: "apply block has partial matched transaction chain",
+ block: &types.Block{
+ BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
+ Transactions: []*types.Tx{
+ mock.Btc2EthMakerTxs[0],
+ mock.Eth2BtcMakerTxs[1],
+ mock.MatchedTxs[4],
+ mock.Eth2BtcMakerTxs[0],
+ mock.MatchedTxs[7],
+ },
+ },
+ blockFunc: applyBlock,
+ initOrders: []*common.Order{},
+ wantOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[7], 2)},
+ wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
+ },
+ {
+ desc: "detach block has pending order transaction",
+ block: &types.Block{
+ BlockHeader: *initBlockHeader,
+ 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 two different trade pairs & different trade pair won't affect each order",
+ block: &types.Block{
+ BlockHeader: *initBlockHeader,
+ Transactions: []*types.Tx{
+ mock.Btc2EthMakerTxs[0],
+ mock.Eth2BtcMakerTxs[0],
+ mock.Eos2EtcMakerTxs[0],
+ mock.Eth2EosMakerTxs[0],
+ },
+ },
+ blockFunc: detachBlock,
+ initOrders: []*common.Order{
+ mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0),
+ mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0),
+ mock.MustNewOrderFromOutput(mock.Eos2EtcMakerTxs[0], 0),
+ mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
+ },
+ 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{}},
+ },
+ {
+ desc: "detach block which node packed maker tx and match transaction in random orde",
+ block: &types.Block{
+ BlockHeader: *initBlockHeader,
+ Transactions: []*types.Tx{
+ mock.Eos2EtcMakerTxs[0],
+ mock.Btc2EthMakerTxs[0],
+ mock.MatchedTxs[4],
+ mock.Eth2EosMakerTxs[0],
+ mock.Eth2BtcMakerTxs[1],
+ mock.MatchedTxs[5],
+ mock.Etc2EosMakerTxs[0],
+ },
+ },
+ blockFunc: detachBlock,
+ initOrders: []*common.Order{
+ mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1),
+ mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
+ },
+ wantOrders: []*common.Order{},
+ 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 := &Core{movStore: store}
+ if err := c.blockFunc(movCore, c.block); err != c.wantError {
+ t.Errorf("#%d(%s):apply block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
+ }
+
+ 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) {
+ consensus.ActiveNetParams.MovRewardPrograms = []consensus.MovRewardProgram{
+ {
+ BeginBlock: 0,
+ EndBlock: 100,
+ Program: hex.EncodeToString(mock.RewardProgram),
+ },
+ }
+
+ 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, 499, testutil.MustDecodeHexString("51")),
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("53")),
+ // fee
+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 11, mock.RewardProgram),
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 1, mock.RewardProgram),
+ },
+ }),
+ },
+ },
+ 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, 499, testutil.MustDecodeHexString("51")),
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("53")),
+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 11, mock.RewardProgram),
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 1, mock.RewardProgram),
+ 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, 499, testutil.MustDecodeHexString("51")),
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("53")),
+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 11, mock.RewardProgram),
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 1, mock.RewardProgram),
+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
+ },
+ }),
+ },
+ },
+ 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, mock.RewardProgram),
+ },
+ }),
+ },
+ },
+ 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, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
+ // re-order
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 270, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
+ // fee
+ types.NewIntraChainOutput(*mock.Btc2EthOrders[2].ToAssetID, 41, mock.RewardProgram),
+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 1, mock.RewardProgram),
+ },
+ }),
+ },
+ },
+ verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
+ wantError: match.ErrAmountOfFeeOutOfRange,
+ },
+ {
+ 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 := &Core{}
+ c.block.Height = 3456786543
+ 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)
+ }
+ }
+}
+
+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: {amount: 11, rewardProgram: mock.RewardProgram},
+ mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
+ },
+ tx: mock.MatchedTxs[1].TxData,
+ },
+ {
+ desc: "fee refund in tx",
+ maxFeeRate: 0.05,
+ wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
+ mock.ETH: {amount: 25, rewardProgram: mock.RewardProgram},
+ mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
+ },
+ tx: mock.MatchedTxs[2].TxData,
+ },
+ {
+ desc: "no price diff",
+ maxFeeRate: 0.05,
+ wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
+ mock.ETH: {amount: 1, rewardProgram: mock.RewardProgram},
+ mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
+ },
+ tx: mock.MatchedTxs[0].TxData,
+ },
+ }
+
+ for i, c := range cases {
+ gotMatchedTxFee, err := calcFeeAmount(types.NewTx(c.tx))
+ 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)
+ }
+ }
+}
+
+func TestBeforeProposalBlock(t *testing.T) {
+ consensus.ActiveNetParams.MovRewardPrograms = []consensus.MovRewardProgram{
+ {
+ BeginBlock: 0,
+ EndBlock: 100,
+ Program: hex.EncodeToString(mock.RewardProgram),
+ },
+ }
+
+ cases := []struct {
+ desc string
+ initOrders []*common.Order
+ gasLeft int64
+ wantMatchedTxs []*types.Tx
+ }{
+ {
+ desc: "has matched tx, but gas left is zero",
+ initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
+ gasLeft: 0,
+ wantMatchedTxs: []*types.Tx{},
+ },
+ {
+ desc: "has one matched tx, and gas is sufficient",
+ initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
+ gasLeft: 2000,
+ wantMatchedTxs: []*types.Tx{mock.MatchedTxs[1]},
+ },
+ {
+ desc: "has two matched tx, but gas is only enough to pack a matched tx",
+ initOrders: []*common.Order{
+ mock.Btc2EthOrders[0],
+ mock.Btc2EthOrders[1],
+ mock.Eth2BtcOrders[2],
+ },
+ gasLeft: 2000,
+ wantMatchedTxs: []*types.Tx{mock.MatchedTxs[2]},
+ },
+ {
+ desc: "has two matched tx, and gas left is sufficient",
+ initOrders: []*common.Order{
+ mock.Btc2EthOrders[0],
+ mock.Btc2EthOrders[1],
+ mock.Eth2BtcOrders[2],
+ },
+ gasLeft: 4000,
+ wantMatchedTxs: []*types.Tx{mock.MatchedTxs[2], mock.MatchedTxs[3]},
+ },
+ {
+ desc: "has multiple trade pairs, and gas left is sufficient",
+ initOrders: []*common.Order{
+ mock.Btc2EthOrders[0],
+ mock.Btc2EthOrders[1],
+ mock.Eth2BtcOrders[2],
+ mock.Eos2EtcOrders[0],
+ mock.Etc2EosOrders[0],
+ },
+ gasLeft: 6000,
+ wantMatchedTxs: []*types.Tx{mock.MatchedTxs[2], mock.MatchedTxs[3], mock.MatchedTxs[5]},
+ },
+ }
+
+ 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 := &Core{movStore: store}
+ gotMatchedTxs, err := movCore.BeforeProposalBlock(nil, 2, c.gasLeft, func() bool { return false })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ gotMatchedTxMap := make(map[string]interface{})
+ for _, matchedTx := range gotMatchedTxs {
+ gotMatchedTxMap[matchedTx.ID.String()] = nil
+ }
+
+ wantMatchedTxMap := make(map[string]interface{})
+ for _, matchedTx := range c.wantMatchedTxs {
+ wantMatchedTxMap[matchedTx.ID.String()] = nil
+ }
+
+ if !testutil.DeepEqual(gotMatchedTxMap, wantMatchedTxMap) {
+ t.Errorf("#%d(%s):want matched tx(%v) is not equals got matched tx(%v)", i, c.desc, c.wantMatchedTxs, gotMatchedTxs)
+ }
+
+ testDB.Close()
+ os.RemoveAll("temp")
+ }
+}
+
+func TestValidateMatchedTxSequence(t *testing.T) {
+ cases := []struct {
+ desc string
+ initOrders []*common.Order
+ transactions []*types.Tx
+ wantError error
+ }{
+ {
+ desc: "both db orders and transactions is empty",
+ initOrders: []*common.Order{},
+ transactions: []*types.Tx{},
+ wantError: nil,
+ },
+ {
+ desc: "existing matched orders in db, and transactions is empty",
+ initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
+ transactions: []*types.Tx{},
+ wantError: nil,
+ },
+ {
+ desc: "db orders is empty, but transactions has matched tx",
+ initOrders: []*common.Order{},
+ transactions: []*types.Tx{mock.MatchedTxs[1]},
+ wantError: errNotMatchedOrder,
+ },
+ {
+ desc: "existing matched orders in db, and corresponding matched tx in transactions",
+ initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
+ transactions: []*types.Tx{mock.MatchedTxs[1]},
+ wantError: nil,
+ },
+ {
+ desc: "package matched tx, one order from db, and the another order from transactions",
+ initOrders: []*common.Order{mock.Btc2EthOrders[0]},
+ transactions: []*types.Tx{mock.Eth2BtcMakerTxs[0], mock.MatchedTxs[10]},
+ wantError: nil,
+ },
+ {
+ desc: "two matched txs use the same orders",
+ initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
+ transactions: []*types.Tx{mock.MatchedTxs[1], mock.MatchedTxs[1]},
+ wantError: errNotMatchedOrder,
+ },
+ {
+ desc: "existing two matched orders in db, and only one corresponding matched tx in transactions",
+ initOrders: []*common.Order{
+ mock.Btc2EthOrders[3], mock.Eth2BtcOrders[2],
+ mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
+ },
+ transactions: []*types.Tx{mock.MatchedTxs[8]},
+ wantError: nil,
+ },
+ {
+ desc: "existing two matched orders in db, and the sequence of match txs in incorrect",
+ initOrders: []*common.Order{
+ mock.Btc2EthOrders[3], mock.Eth2BtcOrders[2],
+ mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
+ },
+ transactions: []*types.Tx{mock.MatchedTxs[1], mock.MatchedTxs[8]},
+ wantError: errSpendOutputIDIsIncorrect,
+ },
+ {
+ desc: "matched tx and orders from packaged transactions",
+ initOrders: []*common.Order{},
+ transactions: []*types.Tx{mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1], mock.MatchedTxs[4]},
+ wantError: nil,
+ },
+ {
+ desc: "package the matched tx first, then package match orders",
+ initOrders: []*common.Order{},
+ transactions: []*types.Tx{mock.MatchedTxs[4], mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1]},
+ wantError: errNotMatchedOrder,
+ },
+ {
+ desc: "cancel order in transactions",
+ initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
+ transactions: []*types.Tx{mock.Btc2EthCancelTxs[0], mock.MatchedTxs[1]},
+ wantError: errNotMatchedOrder,
+ },
+ {
+ desc: "package cancel order after match tx",
+ initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
+ transactions: []*types.Tx{mock.MatchedTxs[1], mock.Btc2EthCancelTxs[0]},
+ wantError: nil,
+ },
+ {
+ desc: "package matched txs of different trade pairs",
+ initOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
+ mock.Eos2EtcOrders[0], mock.Etc2EosOrders[0],
+ },
+ transactions: []*types.Tx{mock.MatchedTxs[1], mock.MatchedTxs[9]},
+ wantError: nil,
+ },
+ {
+ desc: "package matched txs of different trade pairs in different sequence",
+ initOrders: []*common.Order{
+ mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
+ mock.Eos2EtcOrders[0], mock.Etc2EosOrders[0],
+ },
+ transactions: []*types.Tx{mock.MatchedTxs[9], mock.MatchedTxs[1]},
+ wantError: nil,
+ },
+ {
+ desc: "package partial matched tx from db orders, and the re-pending order continue to match",
+ initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
+ transactions: []*types.Tx{mock.MatchedTxs[2], mock.MatchedTxs[3]},
+ wantError: nil,
+ },
+ {
+ desc: "cancel the re-pending order",
+ initOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
+ transactions: []*types.Tx{mock.MatchedTxs[2], mock.Btc2EthCancelTxs[1], mock.MatchedTxs[3]},
+ wantError: errNotMatchedOrder,
+ },
+ }
+
+ 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 := &Core{movStore: store}
+ if err := movCore.validateMatchedTxSequence(c.transactions); err != c.wantError {
+ t.Errorf("#%d(%s):wanet error(%v), got error(%v)", i, c.desc, c.wantError, err)
+ }
+
+ testDB.Close()
+ os.RemoveAll("temp")
+ }
+}
+
+type testFun func(movCore *Core, block *types.Block) error
+
+func applyBlock(movCore *Core, block *types.Block) error {
+ return movCore.ApplyBlock(block)
+}
+
+func detachBlock(movCore *Core, 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
+}
type crossOutAction struct {
bc.AssetAmount
- Address string `json:"address"`
+ Address string `json:"address"`
+ Program json.HexBytes `json:"control_program"`
}
func (a *crossOutAction) Build(ctx context.Context, b *TemplateBuilder) error {
var missing []string
- if a.Address == "" {
- missing = append(missing, "address")
+ if a.Address == "" && len(a.Program) == 0 {
+ missing = append(missing, "address or program")
}
if a.AssetId.IsZero() {
missing = append(missing, "asset_id")
return MissingFieldsError(missing...)
}
- address, err := common.DecodeAddress(a.Address, consensus.BytomMainNetParams(&consensus.ActiveNetParams))
- if err != nil {
- return err
- }
+ program := a.Program
+ if a.Address != "" {
+ address, err := common.DecodeAddress(a.Address, consensus.BytomMainNetParams(&consensus.ActiveNetParams))
+ if err != nil {
+ return err
+ }
- redeemContract := address.ScriptAddress()
- program := []byte{}
- switch address.(type) {
- case *common.AddressWitnessPubKeyHash:
- program, err = vmutil.P2WPKHProgram(redeemContract)
- case *common.AddressWitnessScriptHash:
- program, err = vmutil.P2WSHProgram(redeemContract)
- default:
- return errors.New("unsupport address type")
- }
- if err != nil {
- return err
+ redeemContract := address.ScriptAddress()
+ switch address.(type) {
+ case *common.AddressWitnessPubKeyHash:
+ program, err = vmutil.P2WPKHProgram(redeemContract)
+ case *common.AddressWitnessScriptHash:
+ program, err = vmutil.P2WSHProgram(redeemContract)
+ default:
+ return errors.New("unsupport address type")
+ }
+ if err != nil {
+ return err
+ }
}
out := types.NewCrossChainOutput(*a.AssetId, a.Amount, program)
IssuanceProgram json.HexBytes `json:"issuance_program"`
}
-func (a *crossInAction) Build(ctx context.Context, builder *TemplateBuilder) error {
+func (c *crossInAction) Build(ctx context.Context, builder *TemplateBuilder) error {
var missing []string
- if a.SourceID.IsZero() {
+ if c.SourceID.IsZero() {
missing = append(missing, "source_id")
}
- if a.AssetId.IsZero() {
+ if c.AssetId.IsZero() {
missing = append(missing, "asset_id")
}
- if a.Amount == 0 {
+ if c.Amount == 0 {
missing = append(missing, "amount")
}
return MissingFieldsError(missing...)
}
- if err := a.checkAssetID(); err != nil {
+ if err := c.checkAssetID(); err != nil {
return err
}
// arguments will be set when materializeWitnesses
- txin := types.NewCrossChainInput(nil, a.SourceID, *a.AssetId, a.Amount, a.SourcePos, a.VMVersion, a.RawDefinitionByte, a.IssuanceProgram)
+ txin := types.NewCrossChainInput(nil, c.SourceID, *c.AssetId, c.Amount, c.SourcePos, c.VMVersion, c.RawDefinitionByte, c.IssuanceProgram)
tplIn := &SigningInstruction{}
fed := cfg.CommonConfig.Federation
- tplIn.AddRawWitnessKeys(fed.Xpubs, cfg.FedAddressPath, fed.Quorum)
- tplIn.AddDataWitness(cfg.FederationPMultiSigScript(cfg.CommonConfig))
+
+ if !common.IsOpenFederationIssueAsset(c.RawDefinitionByte) {
+ tplIn.AddRawWitnessKeys(fed.Xpubs, cfg.FedAddressPath, fed.Quorum)
+ tplIn.AddDataWitness(cfg.FederationPMultiSigScript(cfg.CommonConfig))
+ }
+
return builder.AddInput(txin, tplIn)
}
-func (a *crossInAction) ActionType() string {
+func (c *crossInAction) ActionType() string {
return "cross_chain_in"
}
"github.com/bytom/vapor/errors"
)
+// AddDataWitness append data to the witness array
func (si *SigningInstruction) AddDataWitness(data chainjson.HexBytes) {
dw := DataWitness(data)
si.WitnessComponents = append(si.WitnessComponents, &dw)
--- /dev/null
+package commands
+
+import (
+ "strconv"
+
+ log "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+
+ "github.com/bytom/vapor/node"
+)
+
+var rollbackCmd = &cobra.Command{
+ Use: "rollback",
+ Short: "Rollback chain to target height!",
+ Args: cobra.ExactArgs(1),
+ Run: func(cmd *cobra.Command, args []string) {
+ setLogLevel(config.LogLevel)
+
+ height, err := strconv.ParseInt(args[0], 10, 64)
+ if err != nil {
+ log.WithFields(log.Fields{"module": logModule, "err": err}).Fatal("failed to parse int")
+ }
+
+ if height < 0 {
+ log.WithFields(log.Fields{"module": logModule}).Fatal("height should >= 0")
+ }
+
+ if err = node.Rollback(config, uint64(height)); err != nil {
+ log.WithFields(log.Fields{"module": logModule, "err": err}).Fatal("failed to rollback")
+ }
+
+ log.WithFields(log.Fields{"module": logModule}).Infof("success to rollback height of %d", height)
+ },
+}
+
+func init() {
+ RootCmd.AddCommand(rollbackCmd)
+}
config = cfg.DefaultConfig()
)
+// RootCmd is the command for run node
var RootCmd = &cobra.Command{
Use: "vapord",
Short: "Multiple asset management.",
func init() {
runNodeCmd.Flags().String("prof_laddr", config.ProfListenAddress, "Use http to profile vapord programs")
runNodeCmd.Flags().Bool("mining", config.Mining, "Enable mining")
+ runNodeCmd.Flags().String("cross_chain.asset_whitelist", config.CrossChain.AssetWhitelist, "Cross-chain-allowed asset whitelist")
runNodeCmd.Flags().Bool("auth.disable", config.Auth.Disable, "Disable rpc access authenticate")
"encoding/hex"
)
+// FromHex convert hex byte string to []byte
func FromHex(s string) []byte {
if len(s) > 1 {
if s[0:2] == "0x" {
return nil
}
+// Bytes2Hex convert byte array to string
func Bytes2Hex(d []byte) string {
return hex.EncodeToString(d)
}
+// Hex2Bytes convert hex string to byte array
func Hex2Bytes(str string) []byte {
h, _ := hex.DecodeString(str)
return h
}
+// Unit64ToBytes convert uint64 to bytes
func Unit64ToBytes(n uint64) []byte {
buf := make([]byte, 8)
binary.LittleEndian.PutUint64(buf, n)
return buf
}
+// BytesToUnit64 convert bytes to uint64
func BytesToUnit64(b []byte) uint64 {
return binary.LittleEndian.Uint64(b)
}
--- /dev/null
+package common
+
+import (
+ "encoding/json"
+)
+
+// IsOpenFederationIssueAsset check if the asset definition satisfy ofmf asset
+func IsOpenFederationIssueAsset(rawDefinitionByte []byte) bool {
+ var defMap map[string]interface{}
+ if err := json.Unmarshal(rawDefinitionByte, &defMap); err != nil {
+ return false
+ }
+
+ description, ok := defMap["description"].(map[string]interface{})
+ if !ok {
+ return false
+ }
+
+ issueAssetAction, ok := description["issue_asset_action"].(string)
+ if !ok {
+ return false
+ }
+
+ return issueAssetAction == "open_federation_cross_chain"
+}
+++ /dev/null
-package common
-
-// timeSorter implements sort.Interface to allow a slice of timestamps to
-// be sorted.
-type TimeSorter []uint64
-
-// Len returns the number of timestamps in the slice. It is part of the
-// sort.Interface implementation.
-func (s TimeSorter) Len() int {
- return len(s)
-}
-
-// Swap swaps the timestamps at the passed indices. It is part of the
-// sort.Interface implementation.
-func (s TimeSorter) Swap(i, j int) {
- s[i], s[j] = s[j], s[i]
-}
-
-// Less returns whether the timstamp with index i should sort before the
-// timestamp with index j. It is part of the sort.Interface implementation.
-func (s TimeSorter) Less(i, j int) bool {
- return s[i] < s[j]
-}
Web *WebConfig `mapstructure:"web"`
Websocket *WebsocketConfig `mapstructure:"ws"`
Federation *FederationConfig `mapstructure:"federation"`
+ CrossChain *CrossChainConfig `mapstructure:"cross_chain"`
}
// Default configurable parameters.
Web: DefaultWebConfig(),
Websocket: DefaultWebsocketConfig(),
Federation: DefaultFederationConfig(),
+ CrossChain: DefaultCrossChainConfig(),
}
}
Quorum int `json:"quorum"`
}
+type CrossChainConfig struct {
+ AssetWhitelist string `mapstructure:"asset_whitelist"`
+}
+
// Default configurable rpc's auth parameters.
func DefaultRPCAuthConfig() *RPCAuthConfig {
return &RPCAuthConfig{
}
}
+// Default configurable websocket parameters.
func DefaultWebsocketConfig() *WebsocketConfig {
return &WebsocketConfig{
MaxNumWebsockets: 25,
}
}
+// Default configurable federation parameters.
func DefaultFederationConfig() *FederationConfig {
return &FederationConfig{
Xpubs: []chainkd.XPub{
}
}
+// Default configurable crosschain parameters.
+func DefaultCrossChainConfig() *CrossChainConfig {
+ return &CrossChainConfig{}
+}
+
func xpub(str string) (xpub chainkd.XPub) {
if err := xpub.UnmarshalText([]byte(str)); err != nil {
log.Panicf("Fail converts a string to xpub")
)
func TestFederation(t *testing.T) {
-
tmpDir, err := ioutil.TempDir(".", "")
if err != nil {
t.Fatalf("failed to create temporary data folder: %v", err)
[p2p]
laddr = "tcp://0.0.0.0:56656"
seeds = "47.103.79.68:56656,47.103.13.86:56656,47.102.193.119:56656,47.103.17.22:56656"
+[cross_chain]
+asset_whitelist = ""
`
var testNetConfigTmpl = `chain_id = "testnet"
[p2p]
laddr = "tcp://0.0.0.0:56657"
-seeds = "52.82.28.25:56657,52.82.31.195:56657,52.82.31.247:56657"
+seeds = "52.82.7.233:56657,52.82.109.252:56657,52.82.29.30:56657"
+[cross_chain]
+asset_whitelist = ""
`
var soloNetConfigTmpl = `chain_id = "solonet"
[p2p]
laddr = "tcp://0.0.0.0:56658"
seeds = ""
+[cross_chain]
+asset_whitelist = ""
`
// Select network seeds to merge a new string.
Subsidy uint64
}
+// MovRewardProgram is a reward address corresponding to the range of the specified block height when matching transactions
+type MovRewardProgram struct {
+ BeginBlock uint64
+ EndBlock uint64
+ Program string
+}
+
// Params store the config for different network
type Params struct {
// Name defines a human-readable identifier for the network.
ProducerSubsidys []ProducerSubsidy
SoftForkPoint map[uint64]uint64
+
+ // Mov will only start when the block height is greater than this value
+ MovStartHeight uint64
+
+ // Used to receive rewards for matching transactions
+ MovRewardPrograms []MovRewardProgram
}
// ActiveNetParams is the active NetParams
ProducerSubsidys: []ProducerSubsidy{
{BeginBlock: 1, EndBlock: 63072000, Subsidy: 9512938},
},
- SoftForkPoint: map[uint64]uint64{SoftFork001: 10461600},
+ SoftForkPoint: map[uint64]uint64{SoftFork001: 10461600},
+ MovStartHeight: 43354800,
+ MovRewardPrograms: []MovRewardProgram{
+ {
+ BeginBlock: 1,
+ EndBlock: 126144000,
+ Program: "00141d00f85e220e35a23282cfc7f91fe7b34bf6dc18",
+ },
+ },
}
// TestNetParams is the config for vapor-testnet
DNSSeeds: []string{"www.testnetseed.vapor.io"},
BasicConfig: BasicConfig{
MaxBlockGas: uint64(10000000),
- MaxGasAmount: int64(200000),
+ MaxGasAmount: int64(640000),
DefaultGasCredit: int64(160000),
StorageGasRate: int64(1),
VMGasRate: int64(200),
- VotePendingBlockNumber: uint64(10000),
- CoinbasePendingBlockNumber: uint64(1200),
+ VotePendingBlockNumber: uint64(3456000),
+ CoinbasePendingBlockNumber: uint64(7200),
CoinbaseArbitrarySizeLimit: 128,
},
DPOSConfig: DPOSConfig{
return &Params{Bech32HRPSegwit: bech32HRPSegwit}
}
+// InitActiveNetParams load the config by chain ID
func InitActiveNetParams(chainID string) error {
var exist bool
if ActiveNetParams, exist = NetParams[chainID]; !exist {
"errors"
"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)
}
+// IsStraightforward is used to determine whether it is a Straightforward script or not
func IsStraightforward(prog []byte) bool {
insts, err := vm.ParseProgram(prog)
if err != nil {
return insts[0].Op == vm.OP_TRUE || insts[0].Op == vm.OP_FAIL
}
+// IsP2WPKHScript is used to determine whether it is a P2WPKH script or not
func IsP2WPKHScript(prog []byte) bool {
insts, err := vm.ParseProgram(prog)
if err != nil {
return insts[1].Op == vm.OP_DATA_20 && len(insts[1].Data) == consensus.PayToWitnessPubKeyHashDataSize
}
+// IsP2WSHScript is used to determine whether it is a P2WSH script or not
func IsP2WSHScript(prog []byte) bool {
insts, err := vm.ParseProgram(prog)
if err != nil {
return insts[1].Op == vm.OP_DATA_32 && len(insts[1].Data) == consensus.PayToWitnessScriptHashDataSize
}
+// IsP2WMCScript is used to determine whether it is a P2WMC script or not
+func IsP2WMCScript(prog []byte) bool {
+ insts, err := vm.ParseProgram(prog)
+ if err != nil {
+ return false
+ }
+
+ if len(insts) != 6 {
+ return false
+ }
+
+ if insts[0].Op > vm.OP_16 {
+ return false
+ }
+
+ if insts[1].Op != vm.OP_DATA_32 || len(insts[1].Data) != 32 {
+ return false
+ }
+
+ if !(insts[2].IsPushdata() && insts[3].IsPushdata() && insts[4].IsPushdata()) {
+ return false
+ }
+
+ if _, err = vm.AsInt64(insts[2].Data); err != nil {
+ return false
+ }
+
+ if _, err = vm.AsInt64(insts[3].Data); err != nil {
+ return false
+ }
+
+ if !IsP2WScript(insts[4].Data) {
+ return false
+ }
+
+ return insts[5].Op == vm.OP_DATA_32 && len(insts[5].Data) == 32
+}
+
+// ConvertP2PKHSigProgram convert standard P2WPKH program into P2PKH program
func ConvertP2PKHSigProgram(prog []byte) ([]byte, error) {
insts, err := vm.ParseProgram(prog)
if err != nil {
return nil, errors.New("unknow P2PKH version number")
}
+// ConvertP2SHProgram convert standard P2WSH program into P2SH program
func ConvertP2SHProgram(prog []byte) ([]byte, error) {
insts, err := vm.ParseProgram(prog)
if err != nil {
return nil, errors.New("unknow P2SHP version number")
}
+// ConvertP2MCProgram convert standard P2WMC program into P2MC program
+func ConvertP2MCProgram(prog []byte) ([]byte, error) {
+ magneticContractArgs, err := DecodeP2WMCProgram(prog)
+ if err != nil {
+ return nil, err
+ }
+ return vmutil.P2MCProgram(*magneticContractArgs)
+}
+
+// DecodeP2WMCProgram parse standard P2WMC arguments to magneticContractArgs
+func DecodeP2WMCProgram(prog []byte) (*vmutil.MagneticContractArgs, error) {
+ if !IsP2WMCScript(prog) {
+ return nil, errors.New("invalid P2MC program")
+ }
+
+ insts, err := vm.ParseProgram(prog)
+ if err != nil {
+ return nil, err
+ }
+
+ magneticContractArgs := &vmutil.MagneticContractArgs{
+ SellerProgram: insts[4].Data,
+ SellerKey: insts[5].Data,
+ }
+ requestedAsset := [32]byte{}
+ copy(requestedAsset[:], insts[1].Data)
+ magneticContractArgs.RequestedAsset = bc.NewAssetID(requestedAsset)
+
+ if magneticContractArgs.RatioNumerator, err = vm.AsInt64(insts[2].Data); err != nil {
+ return nil, err
+ }
+
+ if magneticContractArgs.RatioDenominator, err = vm.AsInt64(insts[3].Data); err != nil {
+ return nil, err
+ }
+
+ return magneticContractArgs, nil
+}
+
+// GetHashFromStandardProg get hash from standard program
func GetHashFromStandardProg(prog []byte) ([]byte, error) {
insts, err := vm.ParseProgram(prog)
if err != nil {
return append(AccountIndexPrefix, hash[:]...)
}
-func Bip44ContractIndexKey(accountID string, change bool) []byte {
+func bip44ContractIndexKey(accountID string, change bool) []byte {
key := append(ContractIndexPrefix, []byte(accountID)...)
if change {
return append(key, 0x01)
// CommitBatch commit batch
func (store *AccountStore) CommitBatch() error {
if store.batch == nil {
- return errors.New("AccountStore commit fail, store batch is nil.")
+ return errors.New("accountStore commit fail, store batch is nil")
}
store.batch.Write()
store.batch = nil
}
// delete bip44 contract index
- batch.Delete(Bip44ContractIndexKey(account.ID, false))
- batch.Delete(Bip44ContractIndexKey(account.ID, true))
+ batch.Delete(bip44ContractIndexKey(account.ID, false))
+ batch.Delete(bip44ContractIndexKey(account.ID, true))
// delete contract index
batch.Delete(contractIndexKey(account.ID))
// GetBip44ContractIndex get bip44 contract index
func (store *AccountStore) GetBip44ContractIndex(accountID string, change bool) uint64 {
index := uint64(0)
- if rawIndexBytes := store.db.Get(Bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
+ if rawIndexBytes := store.db.Get(bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
index = common.BytesToUnit64(rawIndexBytes)
}
return index
// SetBip44ContractIndex set contract index
func (store *AccountStore) SetBip44ContractIndex(accountID string, change bool, index uint64) {
if store.batch == nil {
- store.db.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
+ store.db.Set(bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
} else {
- store.batch.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
+ store.batch.Set(bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
}
}
return consensusResult, nil
}
+// DeleteConsensusResult delete a consensusResult from cache and database
+func (s *Store) DeleteConsensusResult(seq uint64) error {
+ consensusResult, err := GetConsensusResult(s.db, seq)
+ if err != nil {
+ return err
+ }
+
+ s.db.Delete(calcConsensusResultKey(seq))
+ s.cache.removeConsensusResult(consensusResult)
+ return nil
+}
+
+// DeleteBlock delete a new block in the protocol.
+func (s *Store) DeleteBlock(block *types.Block) error {
+ blockHash := block.Hash()
+ blockHashes, err := s.GetBlockHashesByHeight(block.Height)
+ if err != nil {
+ return err
+ }
+
+ for i := 0; i < len(blockHashes); i++ {
+ if blockHashes[i].String() == blockHash.String() {
+ blockHashes = append(blockHashes[0:i], blockHashes[i+1:len(blockHashes)]...)
+ break
+ }
+ }
+
+ batch := s.db.NewBatch()
+ if len(blockHashes) == 0 {
+ batch.Delete(calcBlockHashesPrefix(block.Height))
+ } else {
+ binaryBlockHashes, err := json.Marshal(blockHashes)
+ if err != nil {
+ return errors.Wrap(err, "Marshal block hashes")
+ }
+
+ batch.Set(calcBlockHashesPrefix(block.Height), binaryBlockHashes)
+ }
+
+ batch.Delete(calcBlockHeaderKey(&blockHash))
+ batch.Delete(calcBlockTransactionsKey(&blockHash))
+ batch.Delete(calcTxStatusKey(&blockHash))
+ batch.Write()
+
+ s.cache.removeBlockHashes(block.Height)
+ s.cache.removeBlockHeader(&block.BlockHeader)
+
+ return nil
+}
+
// NewStore creates and returns a new Store object.
func NewStore(db dbm.DB) *Store {
fillBlockHeaderFn := func(hash *bc.Hash) (*types.BlockHeader, error) {
"os"
"testing"
+ "github.com/stretchr/testify/require"
+
"github.com/bytom/vapor/consensus"
dbm "github.com/bytom/vapor/database/leveldb"
"github.com/bytom/vapor/database/storage"
}
}
}
+
+func TestDeleteBlock(t *testing.T) {
+ cases := []struct {
+ initBlocks []*types.BlockHeader
+ deleteBlock *types.BlockHeader
+ wantBlocks []*types.BlockHeader
+ }{
+ {
+ initBlocks: []*types.BlockHeader{},
+ deleteBlock: &types.BlockHeader{
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945000),
+ },
+ wantBlocks: []*types.BlockHeader{},
+ },
+ {
+ initBlocks: []*types.BlockHeader{
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945000),
+ },
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945005),
+ },
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945010),
+ },
+ },
+ deleteBlock: &types.BlockHeader{
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945000),
+ },
+ wantBlocks: []*types.BlockHeader{
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945005),
+ },
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945010),
+ },
+ },
+ },
+ {
+ initBlocks: []*types.BlockHeader{
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945000),
+ },
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945005),
+ },
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945010),
+ },
+ },
+ deleteBlock: &types.BlockHeader{
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945005),
+ },
+ wantBlocks: []*types.BlockHeader{
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945000),
+ },
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945010),
+ },
+ },
+ },
+ {
+ initBlocks: []*types.BlockHeader{
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945000),
+ },
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945005),
+ },
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945010),
+ },
+ },
+ deleteBlock: &types.BlockHeader{
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945010),
+ },
+ wantBlocks: []*types.BlockHeader{
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945000),
+ },
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945005),
+ },
+ },
+ },
+ {
+ initBlocks: []*types.BlockHeader{},
+ deleteBlock: &types.BlockHeader{
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945030),
+ },
+ wantBlocks: []*types.BlockHeader{},
+ },
+ {
+ initBlocks: []*types.BlockHeader{
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945000),
+ },
+ },
+ deleteBlock: &types.BlockHeader{
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945030),
+ },
+ wantBlocks: []*types.BlockHeader{
+ {
+ Version: uint64(1),
+ Height: uint64(1),
+ Timestamp: uint64(1528945000),
+ },
+ },
+ },
+ }
+
+ for _, c := range cases {
+ verifyStatus := &bc.TransactionStatus{
+ VerifyStatus: []*bc.TxVerifyResult{
+ {StatusFail: false},
+ },
+ }
+ deleteBlock := &types.Block{
+ BlockHeader: types.BlockHeader{
+ Version: c.deleteBlock.Version,
+ Height: c.deleteBlock.Height,
+ Timestamp: c.deleteBlock.Timestamp,
+ },
+ }
+
+ dbA := dbm.NewDB("dbu", "leveldb", "tempA")
+ dbB := dbm.NewDB("dbc", "leveldb", "tempB")
+
+ storeA := NewStore(dbA)
+ storeB := NewStore(dbB)
+
+ for i := 0; i < len(c.initBlocks); i++ {
+ block := &types.Block{
+ BlockHeader: types.BlockHeader{
+ Version: c.initBlocks[i].Version,
+ Height: c.initBlocks[i].Height,
+ Timestamp: c.initBlocks[i].Timestamp,
+ },
+ }
+ if err := storeA.SaveBlock(block, verifyStatus); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ if err := storeA.DeleteBlock(deleteBlock); err != nil {
+ t.Fatal(err)
+ }
+
+ for i := 0; i < len(c.wantBlocks); i++ {
+ block := &types.Block{
+ BlockHeader: types.BlockHeader{
+ Version: c.wantBlocks[i].Version,
+ Height: c.wantBlocks[i].Height,
+ Timestamp: c.wantBlocks[i].Timestamp,
+ },
+ }
+ if err := storeB.SaveBlock(block, verifyStatus); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ iterA := dbA.Iterator()
+ iterB := dbB.Iterator()
+
+ for iterA.Next() && iterB.Next() {
+ require.Equal(t, iterA.Key(), iterB.Key())
+ require.Equal(t, iterA.Value(), iterB.Value())
+ }
+
+ if iterA.Next() || iterB.Next() {
+ t.Fatalf("why iterator is not finished")
+ }
+
+ dbA.Close()
+ os.RemoveAll("tempA")
+ dbB.Close()
+ os.RemoveAll("tempB")
+ }
+}
package database
import (
- "github.com/golang/protobuf/proto"
dbm "github.com/bytom/vapor/database/leveldb"
"github.com/bytom/vapor/database/storage"
"github.com/bytom/vapor/errors"
"github.com/bytom/vapor/protocol/bc"
"github.com/bytom/vapor/protocol/state"
+ "github.com/golang/protobuf/proto"
)
const utxoPreFix = "UT:"
return nil
}
+// SaveUtxoView is export for intergation test
func SaveUtxoView(batch dbm.Batch, view *state.UtxoViewpoint) error {
return saveUtxoView(batch, view)
}
recoveryKey //recoveryKey key for db store recovery info.
)
+// pre-define variables
var (
walletStore = []byte("WS:")
SUTXOPrefix = append(walletStore, sutxoPrefix, colon)
return append(UnconfirmedTxPrefix, []byte(formatKey)...)
}
+// CalcGlobalTxIndexKey calculate tx hash index key
func CalcGlobalTxIndexKey(txID string) []byte {
return append(GlobalTxIndexPrefix, []byte(txID)...)
}
+// CalcGlobalTxIndex calcuate the block index + position index key
func CalcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte {
txIdx := make([]byte, 40)
copy(txIdx[:32], blockHash.Bytes())
// CommitBatch commit batch
func (store *WalletStore) CommitBatch() error {
if store.batch == nil {
- return errors.New("WalletStore commit fail, store batch is nil.")
+ return errors.New("walletStore commit fail, store batch is nil")
}
store.batch.Write()
return confirmedUTXOs, nil
}
+// ListTransactions list tx by filter args
func (store *WalletStore) ListTransactions(accountID string, StartTxID string, count uint, unconfirmed bool) ([]*query.AnnotatedTx, error) {
annotatedTxs := []*query.AnnotatedTx{}
var startKey []byte
`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`),
--- /dev/null
+package math
+
+// MinUint64 return the min of x and y
+func MinUint64(x, y uint64) uint64 {
+ if x < y {
+ return x
+ }
+ return y
+}
maxNumOfBlocksRegularSync = uint64(128)
)
-type FastSync interface {
- process() error
- setSyncPeer(peer *peers.Peer)
-}
-
+// Fetcher is the interface for fetch struct
type Fetcher interface {
processBlock(peerID string, block *types.Block)
processBlocks(peerID string, blocks []*types.Block)
type blockKeeper struct {
chain Chain
- fastSync FastSync
+ fastSync *fastSync
msgFetcher Fetcher
peers *peers.PeerSet
syncPeer *peers.Peer
}
}
-func (bk *blockKeeper) locateBlocks(locator []*bc.Hash, stopHash *bc.Hash) ([]*types.Block, error) {
+func (bk *blockKeeper) locateBlocks(locator []*bc.Hash, stopHash *bc.Hash, isTimeout func() bool) ([]*types.Block, error) {
headers, err := bk.locateHeaders(locator, stopHash, 0, maxNumOfBlocksPerMsg)
if err != nil {
return nil, err
}
blocks = append(blocks, block)
+ if isTimeout() {
+ break
+ }
}
return blocks, nil
}
want = append(want, blocks[i])
}
- got, err := bk.locateBlocks(locator, &c.stopHash)
+ mockTimeout := func() bool { return false }
+ got, err := bk.locateBlocks(locator, &c.stopHash, mockTimeout)
if err != c.wantErr {
t.Errorf("case %d: got %v want err = %v", i, err, c.wantErr)
}
var errOrphanBlock = errors.New("fast sync inserting orphan block")
-type BlockProcessor interface {
- process(chan struct{}, chan struct{}, uint64, *sync.WaitGroup)
-}
-
type blockProcessor struct {
chain Chain
- storage Storage
+ storage *storage
peers *peers.PeerSet
}
-func newBlockProcessor(chain Chain, storage Storage, peers *peers.PeerSet) *blockProcessor {
+func newBlockProcessor(chain Chain, storage *storage, peers *peers.PeerSet) *blockProcessor {
return &blockProcessor{
chain: chain,
peers: peers,
type fastSync struct {
chain Chain
msgFetcher MsgFetcher
- blockProcessor BlockProcessor
+ blockProcessor *blockProcessor
peers *peers.PeerSet
mainSyncPeer *peers.Peer
}
-func newFastSync(chain Chain, msgFetcher MsgFetcher, storage Storage, peers *peers.PeerSet) *fastSync {
+func newFastSync(chain Chain, msgFetcher MsgFetcher, storage *storage, peers *peers.PeerSet) *fastSync {
return &fastSync{
chain: chain,
msgFetcher: msgFetcher,
// sync length cannot be greater than maxFastSyncBlocksNum.
func (fs *fastSync) findSyncRange() (*types.Block, error) {
bestHeight := fs.chain.BestBlockHeight()
- length := fs.mainSyncPeer.IrreversibleHeight() - fastSyncPivotGap - bestHeight
+ length := fs.mainSyncPeer.Height() - fastSyncPivotGap - bestHeight
if length > maxNumOfBlocksPerSync {
length = maxNumOfBlocksPerSync
}
import (
"errors"
"reflect"
+ "time"
log "github.com/sirupsen/logrus"
ValidateTx(*types.Tx) (bool, error)
}
+// Switch is the interface for network layer
type Switch interface {
AddReactor(name string, reactor p2p.Reactor) p2p.Reactor
Start() (bool, error)
// Mempool is the interface for Bytom mempool
type Mempool interface {
GetTransactions() []*core.TxDesc
+ IsDust(tx *types.Tx) bool
}
//Manager is responsible for the business layer information synchronization
return manager, nil
}
+// AddPeer add the network layer peer to logic layer
func (m *Manager) AddPeer(peer peers.BasePeer) {
m.peers.AddPeer(peer)
}
}
func (m *Manager) handleGetBlocksMsg(peer *peers.Peer, msg *msgs.GetBlocksMessage) {
- blocks, err := m.blockKeeper.locateBlocks(msg.GetBlockLocator(), msg.GetStopHash())
+ endTime := time.Now().Add(requireBlocksTimeout / 2)
+ isTimeout := func() bool {
+ return time.Now().After(endTime)
+ }
+
+ blocks, err := m.blockKeeper.locateBlocks(msg.GetBlockLocator(), msg.GetStopHash(), isTimeout)
if err != nil || len(blocks) == 0 {
return
}
return
}
+ if m.mempool.IsDust(tx) {
+ m.peers.ProcessIllegal(peer.ID(), security.LevelMsgIllegal, "receive dust tx msg")
+ return
+ }
+
m.peers.MarkTx(peer.ID(), tx.ID)
if isOrphan, err := m.chain.ValidateTx(tx); err != nil && err != core.ErrDustTx && !isOrphan {
m.peers.ProcessIllegal(peer.ID(), security.LevelMsgIllegal, "fail on validate tx transaction")
}
for _, tx := range txs {
+ if m.mempool.IsDust(tx) {
+ m.peers.ProcessIllegal(peer.ID(), security.LevelMsgIllegal, "receive dust txs msg")
+ continue
+ }
+
m.peers.MarkTx(peer.ID(), tx.ID)
if isOrphan, err := m.chain.ValidateTx(tx); err != nil && !isOrphan {
m.peers.ProcessIllegal(peer.ID(), security.LevelMsgIllegal, "fail on validate tx transaction")
}
}
+// RemovePeer delete peer for peer set
func (m *Manager) RemovePeer(peerID string) {
m.peers.RemovePeer(peerID)
}
+// SendStatus sent the current self status to remote peer
func (m *Manager) SendStatus(peer peers.BasePeer) error {
p := m.peers.GetPeer(peer.ID())
if p == nil {
return nil
}
+// Start the network logic layer
func (m *Manager) Start() error {
var err error
m.txMsgSub, err = m.eventDispatcher.Subscribe(core.TxMsgEvent{})
var (
requireBlockTimeout = 20 * time.Second
requireHeadersTimeout = 30 * time.Second
- requireBlocksTimeout = 50 * time.Second
+ requireBlocksTimeout = 90 * time.Second
checkSyncPeerNumInterval = 5 * time.Second
errRequestBlocksTimeout = errors.New("request blocks timeout")
errSendMsg = errors.New("send message error")
)
+// MsgFetcher is the interface for msg fetch struct
type MsgFetcher interface {
resetParameter()
addSyncPeer(peerID string)
}
type msgFetcher struct {
- storage Storage
+ storage *storage
syncPeers *fastSyncPeers
peers *peers.PeerSet
blockProcessCh chan *blockMsg
mux sync.RWMutex
}
-func newMsgFetcher(storage Storage, peers *peers.PeerSet) *msgFetcher {
+func newMsgFetcher(storage *storage, peers *peers.PeerSet) *msgFetcher {
return &msgFetcher{
storage: storage,
syncPeers: newFastSyncPeers(),
errDBFindBlock = errors.New("can't find block from DB")
)
-type Storage interface {
- resetParameter()
- writeBlocks(peerID string, blocks []*types.Block) error
- readBlock(height uint64) (*blockStorage, error)
- deleteBlock(height uint64)
-}
-
+// LocalStore is the interface for persistent storage
type LocalStore interface {
writeBlock(block *types.Block) error
readBlock(height uint64) (*types.Block, error)
return txs
}
+func (m *mempool) IsDust(tx *types.Tx) bool {
+ return false
+}
+
func TestSyncMempool(t *testing.T) {
tmpDir, err := ioutil.TempDir(".", "")
if err != nil {
"errors"
"net"
"net/http"
+ // debug tool
_ "net/http/pprof"
"path/filepath"
"reflect"
"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"
// NewNode create bytom node
func NewNode(config *cfg.Config) *Node {
- if err := lockDataDirectory(config); err != nil {
- cmn.Exit("Error: " + err.Error())
- }
+ initNodeConfig(config)
- if err := cfg.LoadFederationFile(config.FederationFile(), config); err != nil {
- cmn.Exit(cmn.Fmt("Failed to load federated information:[%s]", err.Error()))
- }
-
- if err:=vaporLog.InitLogFile(config);err!=nil{
- log.WithField("err",err).Fatalln("InitLogFile failed")
+ if err := vaporLog.InitLogFile(config); err != nil {
+ log.WithField("err", err).Fatalln("InitLogFile failed")
}
log.WithFields(log.Fields{
"fed_controlprogram": hex.EncodeToString(cfg.FederationWScript(config)),
}).Info()
- if err := consensus.InitActiveNetParams(config.ChainID); err != nil {
- log.Fatalf("Failed to init ActiveNetParams:[%s]", err.Error())
- }
-
- initCommonConfig(config)
-
// Get store
if config.DBBackend != "memdb" && config.DBBackend != "leveldb" {
cmn.Exit(cmn.Fmt("Param db_backend [%v] is invalid, use leveldb or memdb", config.DBBackend))
accessTokens := accesstoken.NewStore(tokenDB)
dispatcher := event.NewDispatcher()
- txPool := protocol.NewTxPool(store, dispatcher)
- chain, err := protocol.NewChain(store, txPool, dispatcher)
+ movCore := mov.NewCore(config.DBBackend, config.DBDir(), consensus.ActiveNetParams.MovStartHeight)
+ assetFilter := protocol.NewAssetFilter(config.CrossChain.AssetWhitelist)
+ txPool := protocol.NewTxPool(store, []protocol.DustFilterer{movCore, assetFilter}, dispatcher)
+ chain, err := protocol.NewChain(store, txPool, []protocol.Protocoler{movCore}, dispatcher)
if err != nil {
cmn.Exit(cmn.Fmt("Failed to create chain structure: %v", err))
}
log.WithFields(log.Fields{"module": logModule, "error": err}).Error("init NewWallet")
}
+ if err = wallet.Run(); err != nil {
+ log.WithFields(log.Fields{"module": logModule, "error": err}).Error("init NewWallet work running thread")
+ }
+
// trigger rescan wallet
if config.Wallet.Rescan {
wallet.RescanBlocks()
return node
}
+// Rollback rollback chain from one height to targetHeight
+func Rollback(config *cfg.Config, targetHeight uint64) error {
+ if err := initNodeConfig(config); err != nil {
+ return err
+ }
+
+ // Get store
+ if config.DBBackend != "leveldb" {
+ return errors.New("Param db_backend is invalid, use leveldb")
+ }
+
+ coreDB := dbm.NewDB("core", config.DBBackend, config.DBDir())
+ store := database.NewStore(coreDB)
+
+ dispatcher := event.NewDispatcher()
+ movCore := mov.NewCore(config.DBBackend, config.DBDir(), consensus.ActiveNetParams.MovStartHeight)
+ txPool := protocol.NewTxPool(store, []protocol.DustFilterer{movCore}, dispatcher)
+ chain, err := protocol.NewChain(store, txPool, []protocol.Protocoler{movCore}, dispatcher)
+ if err != nil {
+ return err
+ }
+
+ hsm, err := pseudohsm.New(config.KeysDir())
+ if err != nil {
+ return err
+ }
+
+ walletDB := dbm.NewDB("wallet", config.DBBackend, config.DBDir())
+ walletStore := database.NewWalletStore(walletDB)
+ accountStore := database.NewAccountStore(walletDB)
+ accounts := account.NewManager(accountStore, chain)
+ assets := asset.NewRegistry(walletDB, chain)
+ wallet, err := w.NewWallet(walletStore, accounts, assets, hsm, chain, dispatcher, config.Wallet.TxIndex)
+ if err != nil {
+ return err
+ }
+
+ if err := wallet.Rollback(targetHeight); err != nil {
+ return err
+ }
+
+ return chain.Rollback(targetHeight)
+}
+
+func initNodeConfig(config *cfg.Config) error {
+ if err := lockDataDirectory(config); err != nil {
+ log.WithField("err", err).Info("Error: " + err.Error())
+ return err
+ }
+
+ if err := cfg.LoadFederationFile(config.FederationFile(), config); err != nil {
+ log.WithField("err", err).Info("Failed to load federated information")
+ return err
+ }
+
+ if err := consensus.InitActiveNetParams(config.ChainID); err != nil {
+ log.Fatalf("Failed to init ActiveNetParams:[%s]", err.Error())
+ }
+
+ cfg.CommonConfig = config
+ return nil
+}
+
// find whether config xpubs equal genesis block xpubs
func checkConfig(chain *protocol.Chain, config *cfg.Config) error {
fedpegScript := cfg.FederationWScript(config)
typedInput := genesisBlock.Transactions[0].Inputs[0].TypedInput
if v, ok := typedInput.(*types.CoinbaseInput); ok {
if !reflect.DeepEqual(fedpegScript, v.Arbitrary) {
- return errors.New("config xpubs don't equal genesis block xpubs.")
+ return errors.New("config xpubs don't equal genesis block xpubs")
}
}
return nil
n.api.StartServer(*listenAddr)
}
+// OnStart implements BaseService
func (n *Node) OnStart() error {
if n.miningEnable {
if _, err := n.wallet.AccountMgr.GetMiningAddress(); err != nil {
return nil
}
+// OnStop implements BaseService
func (n *Node) OnStop() {
n.notificationMgr.Shutdown()
n.notificationMgr.WaitForShutdown()
n.eventDispatcher.Stop()
}
+// RunForever listen to the stop signal
func (n *Node) RunForever() {
// Sleep forever and then...
cmn.TrapSignal(func() {
)
const (
- logModule = "blockproposer"
+ logModule = "blockproposer"
+ warnTimeNum = 2
+ warnTimeDenom = 5
+ criticalTimeNum = 4
+ criticalTimeDenom = 5
)
// BlockProposer propose several block in specified time range
continue
}
- block, err := proposal.NewBlockTemplate(b.chain, b.txPool, b.accountManager, nextBlockTime)
+ warnDuration := time.Duration(consensus.ActiveNetParams.BlockTimeInterval*warnTimeNum/warnTimeDenom) * time.Millisecond
+ criticalDuration := time.Duration(consensus.ActiveNetParams.BlockTimeInterval*criticalTimeNum/criticalTimeDenom) * time.Millisecond
+ block, err := proposal.NewBlockTemplate(b.chain, b.accountManager, nextBlockTime, warnDuration, criticalDuration)
if err != nil {
log.WithFields(log.Fields{"module": logModule, "error": err}).Error("failed on create NewBlockTemplate")
continue
"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 := b.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 {
+ 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, 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
// is nil, the coinbase transaction will instead be redeemable by anyone.
-func createCoinbaseTx(accountManager *account.Manager, blockHeight uint64, rewards []state.CoinbaseReward) (tx *types.Tx, err error) {
+func (b *blockBuilder) createCoinbaseTx() (*types.Tx, error) {
+ consensusResult, err := b.chain.GetConsensusResultByHash(&b.block.PreviousBlockHash)
+ if err != nil {
+ return nil, err
+ }
+
+ rewards, err := consensusResult.GetCoinbaseRewards(b.block.Height - 1)
+ if err != nil {
+ return nil, err
+ }
+
+ return createCoinbaseTxByReward(b.accountManager, b.block.Height, rewards)
+}
+
+func (b *blockBuilder) getTimeoutStatus() uint8 {
+ if b.timeoutStatus == timeoutCritical {
+ return b.timeoutStatus
+ }
+
+ select {
+ case <-b.criticalTimeoutCh:
+ b.timeoutStatus = timeoutCritical
+ case <-b.warnTimeoutCh:
+ b.timeoutStatus = timeoutWarn
+ default:
+ }
+
+ return b.timeoutStatus
+}
+
+func createCoinbaseTxByReward(accountManager *account.Manager, blockHeight uint64, rewards []state.CoinbaseReward) (tx *types.Tx, err error) {
arbitrary := append([]byte{0x00}, []byte(strconv.FormatUint(blockHeight, 10))...)
var script []byte
if accountManager == nil {
if err = builder.AddInput(types.NewCoinbaseInput(arbitrary), &txbuilder.SigningInstruction{}); err != nil {
return nil, err
}
+
if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, 0, script)); err != nil {
return nil, err
}
return tx, nil
}
-// NewBlockTemplate returns a new block template that is ready to be solved
-func NewBlockTemplate(c *protocol.Chain, txPool *protocol.TxPool, accountManager *account.Manager, timestamp uint64) (b *types.Block, err error) {
- view := state.NewUtxoViewpoint()
- txStatus := bc.NewTransactionStatus()
- if err := txStatus.SetStatus(0, false); err != nil {
- return nil, err
- }
- txEntries := []*bc.Tx{nil}
- gasUsed := uint64(0)
-
- // get preblock info for generate next block
- preBlockHeader := c.BestBlockHeader()
- preBlockHash := preBlockHeader.Hash()
- nextBlockHeight := preBlockHeader.Height + 1
-
- b = &types.Block{
- BlockHeader: types.BlockHeader{
- Version: 1,
- Height: nextBlockHeight,
- PreviousBlockHash: preBlockHash,
- Timestamp: timestamp,
- BlockCommitment: types.BlockCommitment{},
- BlockWitness: types.BlockWitness{Witness: make([][]byte, consensus.ActiveNetParams.NumOfConsensusNode)},
- },
- }
- bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: nextBlockHeight}}
- b.Transactions = []*types.Tx{nil}
-
- txs := txPool.GetTransactions()
- sort.Sort(byTime(txs))
+type validateTxResult struct {
+ tx *types.Tx
+ gasOnly bool
+ err error
+}
- entriesTxs := []*bc.Tx{}
- for _, txDesc := range txs {
- entriesTxs = append(entriesTxs, txDesc.Tx.Tx)
+func (b *blockBuilder) preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoViewpoint, gasLeft int64) ([]*validateTxResult, int64) {
+ var results []*validateTxResult
+ bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: chain.BestBlockHeight() + 1}}
+ bcTxs := make([]*bc.Tx, len(txs))
+ for i, tx := range txs {
+ bcTxs[i] = tx.Tx
}
- validateResults := validation.ValidateTxs(entriesTxs, bcBlock)
- for i, validateResult := range validateResults {
- txDesc := txs[i]
- tx := txDesc.Tx.Tx
+ validateResults := validation.ValidateTxs(bcTxs, bcBlock)
+ for i := 0; i < len(validateResults) && gasLeft > 0; i++ {
gasOnlyTx := false
-
- gasStatus := validateResult.GetGasState()
- if validateResult.GetError() != nil {
+ gasStatus := validateResults[i].GetGasState()
+ if err := validateResults[i].GetError(); err != nil {
if !gasStatus.GasValid {
- blkGenSkipTxForErr(txPool, &tx.ID, err)
+ results = append(results, &validateTxResult{tx: txs[i], err: err})
continue
}
gasOnlyTx = true
}
- if err := c.GetTransactionsUtxo(view, []*bc.Tx{tx}); err != nil {
- blkGenSkipTxForErr(txPool, &tx.ID, err)
+ if err := chain.GetTransactionsUtxo(view, []*bc.Tx{bcTxs[i]}); err != nil {
+ results = append(results, &validateTxResult{tx: txs[i], err: err})
continue
}
- if gasUsed+uint64(gasStatus.GasUsed) > consensus.ActiveNetParams.MaxBlockGas {
+ if gasLeft-gasStatus.GasUsed < 0 {
break
}
- if err := view.ApplyTransaction(bcBlock, tx, gasOnlyTx); err != nil {
- blkGenSkipTxForErr(txPool, &tx.ID, err)
+ if err := view.ApplyTransaction(bcBlock, bcTxs[i], gasOnlyTx); err != nil {
+ results = append(results, &validateTxResult{tx: txs[i], err: err})
continue
}
- if err := txStatus.SetStatus(len(b.Transactions), gasOnlyTx); err != nil {
- return nil, err
- }
-
- b.Transactions = append(b.Transactions, txDesc.Tx)
- txEntries = append(txEntries, tx)
- gasUsed += uint64(gasStatus.GasUsed)
- if gasUsed == consensus.ActiveNetParams.MaxBlockGas {
- break
+ if err := b.validateBySubProtocols(txs[i], validateResults[i].GetError() != nil, chain.SubProtocols()); err != nil {
+ results = append(results, &validateTxResult{tx: txs[i], err: err})
+ continue
}
+ results = append(results, &validateTxResult{tx: txs[i], gasOnly: gasOnlyTx, err: validateResults[i].GetError()})
+ gasLeft -= gasStatus.GasUsed
}
-
- consensusResult, err := c.GetConsensusResultByHash(&preBlockHash)
- if err != nil {
- return nil, err
- }
-
- rewards, err := consensusResult.GetCoinbaseRewards(preBlockHeader.Height)
- if err != nil {
- return nil, err
- }
-
- // create coinbase transaction
- b.Transactions[0], err = createCoinbaseTx(accountManager, nextBlockHeight, rewards)
- if err != nil {
- return nil, errors.Wrap(err, "fail on createCoinbaseTx")
- }
-
- txEntries[0] = b.Transactions[0].Tx
- b.BlockHeader.BlockCommitment.TransactionsMerkleRoot, err = types.TxMerkleRoot(txEntries)
- if err != nil {
- return nil, err
- }
-
- b.BlockHeader.BlockCommitment.TransactionStatusHash, err = types.TxStatusMerkleRoot(txStatus.VerifyStatus)
-
- _, err = c.SignBlock(b)
- return b, err
+ return results, gasLeft
}
-func blkGenSkipTxForErr(txPool *protocol.TxPool, txHash *bc.Hash, err error) {
- log.WithFields(log.Fields{"module": logModule, "error": err}).Error("mining block generation: skip tx due to")
- txPool.RemoveTransaction(txHash)
+func (b *blockBuilder) validateBySubProtocols(tx *types.Tx, statusFail bool, subProtocols []protocol.Protocoler) error {
+ for _, subProtocol := range subProtocols {
+ verifyResult := &bc.TxVerifyResult{StatusFail: statusFail}
+ if err := subProtocol.ValidateTx(tx, verifyResult, b.block.Height); err != nil {
+ return err
+ }
+ }
+ return nil
}
}
// create coinbase transaction
- c.block.Transactions[0], err = createCoinbaseTx(nil, c.block.Height, rewards)
+ c.block.Transactions[0], err = createCoinbaseTxByReward(nil, c.block.Height, rewards)
if err != nil {
t.Fatal(err)
}
--- /dev/null
+package protocol
+
+import (
+ "strings"
+
+ "github.com/bytom/vapor/consensus"
+ "github.com/bytom/vapor/protocol/bc/types"
+)
+
+// AssetFilter is struct for allow open federation asset cross chain
+type AssetFilter struct {
+ whitelist map[string]struct{}
+}
+
+// NewAssetFilter returns a assetFilter according a whitelist,
+// which is a strings list cancated via comma
+func NewAssetFilter(whitelist string) *AssetFilter {
+ af := &AssetFilter{whitelist: make(map[string]struct{})}
+ af.whitelist[consensus.BTMAssetID.String()] = struct{}{}
+ for _, assetID := range strings.Split(whitelist, ",") {
+ af.whitelist[strings.ToLower(assetID)] = struct{}{}
+ }
+ return af
+}
+
+// IsDust implements the DustFilterer interface.
+// It filters a transaction as long as there is one asset neither BTM or in the whitelist
+// No need to check the output assets types becauese they must have been cover in input assets types
+func (af *AssetFilter) IsDust(tx *types.Tx) bool {
+ for _, input := range tx.Inputs {
+ if _, ok := input.TypedInput.(*types.CrossChainInput); !ok {
+ continue
+ }
+
+ assetID := input.AssetID()
+ if _, ok := af.whitelist[assetID.String()]; !ok {
+ return true
+ }
+ }
+
+ return false
+}
if err := c.checkNodeSign(&block.BlockHeader, node, block.Get(node.Order)); err == errDoubleSignBlock {
log.WithFields(log.Fields{"module": logModule, "blockHash": blockHash.String(), "pubKey": pubKey}).Warn("the consensus node double sign the same height of different block")
- block.BlockWitness.Delete(node.Order)
- continue
+ // if the blocker double sign & become the mainchain, that means
+ // all the side chain will reject the main chain make the chain
+ // fork. All the node will ban each other & can't roll back
+ if blocker != pubKey {
+ block.BlockWitness.Delete(node.Order)
+ continue
+ }
} else if err != nil {
return err
}
return c.eventDispatcher.Post(event.BlockSignatureEvent{BlockHash: *blockHash, Signature: signature, XPub: xPub})
}
-// SignBlock signing the block if current node is consensus node
-func (c *Chain) SignBlock(block *types.Block) ([]byte, error) {
+// SignBlockHeader signing the block if current node is consensus node
+func (c *Chain) SignBlockHeader(blockHeader *types.BlockHeader) error {
+ _, err := c.signBlockHeader(blockHeader)
+ return err
+}
+
+func (c *Chain) applyBlockSign(blockHeader *types.BlockHeader) error {
+ signature, err := c.signBlockHeader(blockHeader)
+ if err != nil {
+ return err
+ }
+
+ if len(signature) == 0 {
+ return nil
+ }
+
+ if err := c.store.SaveBlockHeader(blockHeader); err != nil {
+ return err
+ }
+
+ xpub := config.CommonConfig.PrivateKey().XPub()
+ return c.eventDispatcher.Post(event.BlockSignatureEvent{BlockHash: blockHeader.Hash(), Signature: signature, XPub: xpub[:]})
+}
+
+func (c *Chain) signBlockHeader(blockHeader *types.BlockHeader) ([]byte, error) {
xprv := config.CommonConfig.PrivateKey()
- xpubStr := xprv.XPub().String()
- node, err := c.getConsensusNode(&block.PreviousBlockHash, xpubStr)
+ xpub := xprv.XPub()
+ node, err := c.getConsensusNode(&blockHeader.PreviousBlockHash, xpub.String())
+ blockHash := blockHeader.Hash()
if err == errNotFoundConsensusNode {
+ log.WithFields(log.Fields{"module": logModule, "blockHash": blockHash.String()}).Debug("can't find consensus node of current node")
return nil, nil
} else if err != nil {
return nil, err
}
- if err := c.checkDoubleSign(&block.BlockHeader, node.XPub.String()); err == errDoubleSignBlock {
+ if len(blockHeader.Get(node.Order)) != 0 {
+ return nil, nil
+ }
+
+ if err := c.checkDoubleSign(blockHeader, node.XPub.String()); err == errDoubleSignBlock {
+ log.WithFields(log.Fields{"module": logModule, "blockHash": blockHash.String()}).Warn("current node has double sign the block")
return nil, nil
} else if err != nil {
return nil, err
}
- signature := block.Get(node.Order)
- if len(signature) == 0 {
- signature = xprv.Sign(block.Hash().Bytes())
- block.Set(node.Order, signature)
- }
+ signature := xprv.Sign(blockHeader.Hash().Bytes())
+ blockHeader.Set(node.Order, signature)
return signature, nil
}
AssetDefinition *AssetDefinition `protobuf:"bytes,4,opt,name=asset_definition,json=assetDefinition" json:"asset_definition,omitempty"`
WitnessArguments [][]byte `protobuf:"bytes,5,rep,name=witness_arguments,json=witnessArguments,proto3" json:"witness_arguments,omitempty"`
Ordinal uint64 `protobuf:"varint,6,opt,name=ordinal" json:"ordinal,omitempty"`
+ RawDefinitionByte []byte `protobuf:"bytes,7,opt,name=rawDefinitionByte,proto3" json:"rawDefinitionByte,omitempty"`
}
func (m *CrossChainInput) Reset() { *m = CrossChainInput{} }
return 0
}
+func (m *CrossChainInput) GetRawDefinitionByte() []byte {
+ if m != nil {
+ return m.RawDefinitionByte
+ }
+ return nil
+}
+
func init() {
proto.RegisterType((*Hash)(nil), "bc.Hash")
proto.RegisterType((*Program)(nil), "bc.Program")
func init() { proto.RegisterFile("bc.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
- // 951 bytes of a gzipped FileDescriptorProto
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x56, 0x5f, 0x6f, 0x23, 0x35,
- 0x10, 0x57, 0x36, 0xdb, 0x24, 0x9d, 0xf4, 0x9a, 0xc4, 0xbd, 0x83, 0xd5, 0xe9, 0x10, 0xd5, 0x4a,
- 0x47, 0x0f, 0x21, 0x55, 0xfd, 0x73, 0xfc, 0x79, 0x40, 0x88, 0xd2, 0x72, 0x5c, 0x1e, 0x4e, 0x87,
- 0xdc, 0x2a, 0xaf, 0x2b, 0x67, 0xd7, 0x49, 0x2c, 0x92, 0x75, 0xb0, 0xbd, 0xa1, 0xd7, 0xaf, 0xc0,
- 0x33, 0x0f, 0x7c, 0x22, 0x1e, 0x10, 0xe2, 0x23, 0x81, 0x3c, 0xeb, 0x4d, 0x36, 0x7f, 0xda, 0x82,
- 0x00, 0x01, 0x6f, 0x3b, 0xe3, 0xf1, 0x6f, 0x7e, 0xf3, 0xf3, 0x8c, 0xd7, 0xd0, 0xe8, 0xc7, 0x87,
- 0x53, 0x25, 0x8d, 0x24, 0x5e, 0x3f, 0x0e, 0x5f, 0x80, 0xff, 0x92, 0xe9, 0x11, 0xd9, 0x05, 0x6f,
- 0x76, 0x14, 0x54, 0xf6, 0x2b, 0xcf, 0x6a, 0xd4, 0x9b, 0x1d, 0xa1, 0x7d, 0x1c, 0x78, 0xce, 0x3e,
- 0x46, 0xfb, 0x24, 0xa8, 0x3a, 0xfb, 0x04, 0xed, 0xd3, 0xc0, 0x77, 0xf6, 0x69, 0xf8, 0x29, 0xd4,
- 0xbf, 0x56, 0x72, 0xa8, 0xd8, 0x84, 0xbc, 0x03, 0x30, 0x9b, 0x44, 0x33, 0xae, 0xb4, 0x90, 0x29,
- 0x42, 0xfa, 0x74, 0x7b, 0x36, 0xe9, 0xe5, 0x0e, 0x42, 0xc0, 0x8f, 0x65, 0xc2, 0x11, 0x7b, 0x87,
- 0xe2, 0x77, 0xd8, 0x85, 0xfa, 0x99, 0xd6, 0xdc, 0x74, 0x2f, 0xfe, 0x32, 0x91, 0x57, 0xd0, 0x44,
- 0xa8, 0xb3, 0x89, 0xcc, 0x52, 0x43, 0xde, 0x83, 0x06, 0xb3, 0x66, 0x24, 0x12, 0x04, 0x6d, 0x9e,
- 0x34, 0x0f, 0xfb, 0xf1, 0xa1, 0xcb, 0x46, 0xeb, 0xb8, 0xd8, 0x4d, 0xc8, 0x5b, 0x50, 0x63, 0xb8,
- 0x03, 0x53, 0xf9, 0xd4, 0x59, 0xe1, 0x10, 0x5a, 0x18, 0x7b, 0xc1, 0x07, 0x22, 0x15, 0xc6, 0x16,
- 0xf0, 0x11, 0xb4, 0x85, 0xd6, 0x19, 0x4b, 0x63, 0x1e, 0x4d, 0xf3, 0x9a, 0xcb, 0xd0, 0x4e, 0x06,
- 0xda, 0x2a, 0x82, 0x0a, 0x5d, 0x9e, 0x80, 0x9f, 0x30, 0xc3, 0x30, 0x41, 0xf3, 0xa4, 0x61, 0x63,
- 0xad, 0xf4, 0x14, 0xbd, 0xe1, 0x18, 0x9a, 0x3d, 0x36, 0xce, 0xf8, 0xa5, 0xcc, 0x54, 0xcc, 0xc9,
- 0x63, 0xa8, 0x2a, 0x3e, 0x70, 0xb8, 0x8b, 0x58, 0xeb, 0x24, 0x4f, 0x61, 0x6b, 0x66, 0x43, 0x1d,
- 0x52, 0x6b, 0x5e, 0x50, 0x5e, 0x33, 0xcd, 0x57, 0xc9, 0x63, 0x68, 0x4c, 0xa5, 0x46, 0xce, 0xa8,
- 0x97, 0x4f, 0xe7, 0x76, 0xf8, 0x2d, 0xb4, 0x31, 0xdb, 0x05, 0xd7, 0x46, 0xa4, 0x0c, 0xeb, 0xfa,
- 0x87, 0x53, 0xfe, 0xe6, 0x41, 0xf3, 0x8b, 0xb1, 0x8c, 0xbf, 0x79, 0xc9, 0x59, 0xc2, 0x15, 0x09,
- 0xa0, 0xbe, 0xdc, 0x23, 0x85, 0x69, 0xcf, 0x62, 0xc4, 0xc5, 0x70, 0x34, 0x3f, 0x8b, 0xdc, 0x22,
- 0xcf, 0xa1, 0x33, 0x55, 0x7c, 0x26, 0x64, 0xa6, 0xa3, 0xbe, 0x45, 0xb2, 0x87, 0x5a, 0x5d, 0xa1,
- 0xdb, 0x2a, 0x42, 0x30, 0x57, 0x37, 0x21, 0x4f, 0x60, 0xdb, 0x88, 0x09, 0xd7, 0x86, 0x4d, 0xa6,
- 0xd8, 0x27, 0x3e, 0x5d, 0x38, 0xc8, 0x87, 0xd0, 0x31, 0x8a, 0xa5, 0x9a, 0xc5, 0x96, 0xa4, 0x8e,
- 0x94, 0x94, 0x26, 0xd8, 0x5a, 0xc1, 0x6c, 0x97, 0x43, 0xa8, 0x94, 0x86, 0x7c, 0x0e, 0x6f, 0x97,
- 0x7c, 0x91, 0x36, 0xcc, 0x64, 0x3a, 0x1a, 0x31, 0x3d, 0x0a, 0x6a, 0x2b, 0x9b, 0x1f, 0x95, 0x02,
- 0x2f, 0x31, 0x0e, 0x07, 0xee, 0x02, 0xc8, 0x3a, 0x42, 0x50, 0xc7, 0xcd, 0x8f, 0xec, 0xe6, 0xab,
- 0xd5, 0x6d, 0xb4, 0xb3, 0x86, 0x44, 0x3e, 0x80, 0xce, 0x77, 0xc2, 0xa4, 0x5c, 0xeb, 0x88, 0xa9,
- 0x61, 0x36, 0xe1, 0xa9, 0xd1, 0x41, 0x63, 0xbf, 0xfa, 0x6c, 0x87, 0xb6, 0xdd, 0xc2, 0x59, 0xe1,
- 0x0f, 0x7f, 0xa8, 0x40, 0xe3, 0xea, 0xfa, 0x5e, 0xf9, 0x0f, 0xa0, 0xa5, 0xb9, 0x12, 0x6c, 0x2c,
- 0x6e, 0x78, 0x12, 0x69, 0x71, 0xc3, 0xdd, 0x39, 0xec, 0x2e, 0xdc, 0x97, 0xe2, 0x86, 0xdb, 0x41,
- 0xb7, 0x42, 0x46, 0x8a, 0xa5, 0x43, 0xee, 0xce, 0x1b, 0xa5, 0xa5, 0xd6, 0x41, 0x0e, 0x00, 0x14,
- 0xd7, 0xd9, 0xd8, 0xce, 0x9e, 0x0e, 0xfc, 0xfd, 0xea, 0x92, 0x2c, 0xdb, 0xf9, 0x5a, 0x37, 0xd1,
- 0xe1, 0x31, 0xec, 0x5e, 0x5d, 0xf7, 0xb8, 0x12, 0x83, 0x37, 0x14, 0x9d, 0xe4, 0x5d, 0x68, 0x3a,
- 0x49, 0x07, 0x4c, 0x8c, 0x91, 0x60, 0x83, 0x42, 0xee, 0x7a, 0xc1, 0xc4, 0x38, 0x1c, 0x40, 0x67,
- 0x4d, 0x9f, 0x3b, 0x4a, 0xfa, 0x18, 0x1e, 0xcc, 0x10, 0xbf, 0xd0, 0xd9, 0x43, 0x36, 0x04, 0x75,
- 0x5e, 0x4a, 0x4d, 0x77, 0xf2, 0xc0, 0x1c, 0x32, 0xfc, 0xa5, 0x02, 0xd5, 0x57, 0xd9, 0x35, 0x79,
- 0x1f, 0xea, 0x1a, 0x07, 0x53, 0x07, 0x15, 0xdc, 0x8a, 0x13, 0x50, 0x1a, 0x58, 0x5a, 0xac, 0x93,
- 0xa7, 0x50, 0x2f, 0x6e, 0x05, 0x6f, 0xfd, 0x56, 0x28, 0xd6, 0xc8, 0x57, 0xf0, 0xb0, 0x38, 0xb9,
- 0x64, 0x31, 0x84, 0x3a, 0xa8, 0x22, 0xfc, 0xc3, 0x39, 0x7c, 0x69, 0x42, 0xe9, 0x9e, 0xdb, 0x51,
- 0xf2, 0xdd, 0xd2, 0x02, 0xfe, 0x2d, 0x2d, 0x20, 0xa1, 0x71, 0x2e, 0x45, 0xda, 0x67, 0x9a, 0x93,
- 0x2f, 0x61, 0x6f, 0x03, 0x03, 0x37, 0xff, 0x9b, 0x09, 0x90, 0x75, 0x02, 0x76, 0xbe, 0x98, 0xea,
- 0x0b, 0xa3, 0x98, 0x7a, 0xe3, 0x2e, 0xf5, 0x85, 0x23, 0xfc, 0xbe, 0x02, 0xed, 0x6e, 0x6a, 0x14,
- 0x3b, 0x1f, 0x31, 0x91, 0xbe, 0xce, 0xcc, 0x34, 0x33, 0xe4, 0x00, 0x6a, 0xb9, 0x5a, 0x2e, 0xd9,
- 0x9a, 0x98, 0x6e, 0x99, 0x3c, 0x87, 0x56, 0x2c, 0x53, 0xa3, 0xe4, 0x38, 0xba, 0x43, 0xd3, 0x5d,
- 0x17, 0x53, 0x5c, 0xb4, 0x01, 0xd4, 0xa5, 0x4a, 0x44, 0xca, 0xc6, 0xae, 0x29, 0x0b, 0x13, 0xd9,
- 0x9c, 0x2b, 0xa9, 0xf5, 0x7f, 0x82, 0xcd, 0x8f, 0x15, 0x80, 0x9e, 0x34, 0xfc, 0x5f, 0xe6, 0x61,
- 0xff, 0xc8, 0x33, 0x69, 0x38, 0x5e, 0x8e, 0x3b, 0x14, 0xbf, 0xc3, 0x9f, 0x2b, 0xb0, 0xdd, 0xe3,
- 0x46, 0x76, 0x53, 0x4b, 0xed, 0x08, 0x5a, 0x7a, 0xca, 0x53, 0x13, 0x49, 0xa4, 0xba, 0xf8, 0x99,
- 0x2e, 0xe6, 0xf9, 0x01, 0x06, 0xe4, 0xa5, 0x74, 0x93, 0xdb, 0x9a, 0xcb, 0xfb, 0x93, 0xcd, 0xb5,
- 0xb1, 0xb9, 0xab, 0x9b, 0x9b, 0xbb, 0x5c, 0xa1, 0xbf, 0xac, 0xf4, 0x6b, 0x00, 0xca, 0x8d, 0x50,
- 0xdc, 0x06, 0xfe, 0x71, 0xa1, 0x4b, 0x80, 0xde, 0x32, 0xe0, 0x4f, 0x15, 0xd8, 0xba, 0x9c, 0xf2,
- 0x34, 0xf9, 0xdf, 0x4b, 0xf3, 0xab, 0x07, 0xad, 0xc5, 0x48, 0xe4, 0xc7, 0xfd, 0x09, 0xec, 0x4d,
- 0x98, 0x48, 0x63, 0xeb, 0xb9, 0xa3, 0xae, 0xce, 0x3c, 0xe8, 0xef, 0xae, 0x6d, 0x43, 0x87, 0x57,
- 0xef, 0xef, 0xf0, 0xcf, 0xa0, 0x9d, 0xbf, 0xf5, 0x92, 0xf9, 0x63, 0x0d, 0xab, 0x6d, 0x9e, 0xec,
- 0xcd, 0xdf, 0x2b, 0x8b, 0x77, 0x1c, 0x6d, 0xb1, 0x95, 0x87, 0xdd, 0x46, 0x45, 0xb7, 0xee, 0x57,
- 0xb4, 0xb6, 0xa4, 0x68, 0xbf, 0x86, 0xaf, 0xeb, 0xd3, 0xdf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x20,
- 0xd9, 0x30, 0x59, 0x69, 0x0b, 0x00, 0x00,
+ // 967 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x56, 0x4b, 0x6f, 0x23, 0x45,
+ 0x10, 0x96, 0xc7, 0x13, 0xdb, 0x29, 0x7b, 0x63, 0xbb, 0xb3, 0x0b, 0xa3, 0xd5, 0x22, 0xa2, 0x91,
+ 0x96, 0x2c, 0x02, 0x45, 0x89, 0xb3, 0x3c, 0x0e, 0x08, 0x91, 0x4d, 0x58, 0xd6, 0x87, 0xd5, 0xa2,
+ 0x4e, 0xe4, 0xeb, 0xa8, 0x3d, 0xd3, 0xb6, 0x5b, 0xd8, 0xd3, 0xa6, 0xbb, 0xc7, 0x79, 0xfc, 0x05,
+ 0xce, 0x1c, 0xf8, 0x45, 0x1c, 0x10, 0x3f, 0x09, 0x81, 0xba, 0x66, 0xc6, 0xe3, 0x57, 0x12, 0x10,
+ 0x20, 0xd8, 0xdb, 0xd4, 0xa3, 0xbf, 0xaa, 0xfa, 0xaa, 0xaa, 0xa7, 0xa1, 0xd6, 0x0f, 0x0f, 0xa6,
+ 0x4a, 0x1a, 0x49, 0x9c, 0x7e, 0xe8, 0xbf, 0x04, 0xf7, 0x15, 0xd3, 0x23, 0xb2, 0x03, 0xce, 0xec,
+ 0xd0, 0x2b, 0xed, 0x95, 0x9e, 0x55, 0xa8, 0x33, 0x3b, 0x44, 0xf9, 0xc8, 0x73, 0x32, 0xf9, 0x08,
+ 0xe5, 0x8e, 0x57, 0xce, 0xe4, 0x0e, 0xca, 0xc7, 0x9e, 0x9b, 0xc9, 0xc7, 0xfe, 0x17, 0x50, 0xfd,
+ 0x56, 0xc9, 0xa1, 0x62, 0x13, 0xf2, 0x1e, 0xc0, 0x6c, 0x12, 0xcc, 0xb8, 0xd2, 0x42, 0xc6, 0x08,
+ 0xe9, 0xd2, 0xed, 0xd9, 0xa4, 0x97, 0x2a, 0x08, 0x01, 0x37, 0x94, 0x11, 0x47, 0xec, 0x06, 0xc5,
+ 0x6f, 0xbf, 0x0b, 0xd5, 0x13, 0xad, 0xb9, 0xe9, 0x9e, 0xfd, 0xed, 0x44, 0x5e, 0x43, 0x1d, 0xa1,
+ 0x4e, 0x26, 0x32, 0x89, 0x0d, 0xf9, 0x00, 0x6a, 0xcc, 0x8a, 0x81, 0x88, 0x10, 0xb4, 0xde, 0xa9,
+ 0x1f, 0xf4, 0xc3, 0x83, 0x2c, 0x1a, 0xad, 0xa2, 0xb1, 0x1b, 0x91, 0x77, 0xa0, 0xc2, 0xf0, 0x04,
+ 0x86, 0x72, 0x69, 0x26, 0xf9, 0x43, 0x68, 0xa2, 0xef, 0x19, 0x1f, 0x88, 0x58, 0x18, 0x5b, 0xc0,
+ 0xa7, 0xd0, 0x12, 0x5a, 0x27, 0x2c, 0x0e, 0x79, 0x30, 0x4d, 0x6b, 0x5e, 0x84, 0xce, 0x68, 0xa0,
+ 0xcd, 0xdc, 0x29, 0xe7, 0xe5, 0x09, 0xb8, 0x11, 0x33, 0x0c, 0x03, 0xd4, 0x3b, 0x35, 0xeb, 0x6b,
+ 0xa9, 0xa7, 0xa8, 0xf5, 0xc7, 0x50, 0xef, 0xb1, 0x71, 0xc2, 0xcf, 0x65, 0xa2, 0x42, 0x4e, 0x1e,
+ 0x43, 0x59, 0xf1, 0x41, 0x86, 0x5b, 0xf8, 0x5a, 0x25, 0x79, 0x0a, 0x5b, 0x33, 0xeb, 0x9a, 0x21,
+ 0x35, 0xe7, 0x05, 0xa5, 0x35, 0xd3, 0xd4, 0x4a, 0x1e, 0x43, 0x6d, 0x2a, 0x35, 0xe6, 0x8c, 0x7c,
+ 0xb9, 0x74, 0x2e, 0xfb, 0xdf, 0x43, 0x0b, 0xa3, 0x9d, 0x71, 0x6d, 0x44, 0xcc, 0xb0, 0xae, 0x7f,
+ 0x39, 0xe4, 0xef, 0x0e, 0xd4, 0x5f, 0x8c, 0x65, 0xf8, 0xdd, 0x2b, 0xce, 0x22, 0xae, 0x88, 0x07,
+ 0xd5, 0xe5, 0x19, 0xc9, 0x45, 0xdb, 0x8b, 0x11, 0x17, 0xc3, 0xd1, 0xbc, 0x17, 0xa9, 0x44, 0x9e,
+ 0x43, 0x7b, 0xaa, 0xf8, 0x4c, 0xc8, 0x44, 0x07, 0x7d, 0x8b, 0x64, 0x9b, 0x5a, 0x5e, 0x49, 0xb7,
+ 0x99, 0xbb, 0x60, 0xac, 0x6e, 0x44, 0x9e, 0xc0, 0xb6, 0x11, 0x13, 0xae, 0x0d, 0x9b, 0x4c, 0x71,
+ 0x4e, 0x5c, 0x5a, 0x28, 0xc8, 0x27, 0xd0, 0x36, 0x8a, 0xc5, 0x9a, 0x85, 0x36, 0x49, 0x1d, 0x28,
+ 0x29, 0x8d, 0xb7, 0xb5, 0x82, 0xd9, 0x5a, 0x74, 0xa1, 0x52, 0x1a, 0xf2, 0x15, 0xbc, 0xbb, 0xa0,
+ 0x0b, 0xb4, 0x61, 0x26, 0xd1, 0xc1, 0x88, 0xe9, 0x91, 0x57, 0x59, 0x39, 0xfc, 0x68, 0xc1, 0xf1,
+ 0x1c, 0xfd, 0x70, 0xe1, 0xce, 0x80, 0xac, 0x23, 0x78, 0x55, 0x3c, 0xfc, 0xc8, 0x1e, 0xbe, 0x58,
+ 0x3d, 0x46, 0xdb, 0x6b, 0x48, 0xe4, 0x23, 0x68, 0x5f, 0x0a, 0x13, 0x73, 0xad, 0x03, 0xa6, 0x86,
+ 0xc9, 0x84, 0xc7, 0x46, 0x7b, 0xb5, 0xbd, 0xf2, 0xb3, 0x06, 0x6d, 0x65, 0x86, 0x93, 0x5c, 0xef,
+ 0xff, 0x58, 0x82, 0xda, 0xc5, 0xd5, 0xbd, 0xf4, 0xef, 0x43, 0x53, 0x73, 0x25, 0xd8, 0x58, 0xdc,
+ 0xf0, 0x28, 0xd0, 0xe2, 0x86, 0x67, 0x7d, 0xd8, 0x29, 0xd4, 0xe7, 0xe2, 0x86, 0xdb, 0x45, 0xb7,
+ 0x44, 0x06, 0x8a, 0xc5, 0x43, 0x9e, 0xf5, 0x1b, 0xa9, 0xa5, 0x56, 0x41, 0xf6, 0x01, 0x14, 0xd7,
+ 0xc9, 0xd8, 0xee, 0x9e, 0xf6, 0xdc, 0xbd, 0xf2, 0x12, 0x2d, 0xdb, 0xa9, 0xad, 0x1b, 0x69, 0xff,
+ 0x08, 0x76, 0x2e, 0xae, 0x7a, 0x5c, 0x89, 0xc1, 0x35, 0x45, 0x25, 0x79, 0x1f, 0xea, 0x19, 0xa5,
+ 0x03, 0x26, 0xc6, 0x98, 0x60, 0x8d, 0x42, 0xaa, 0x7a, 0xc9, 0xc4, 0xd8, 0x1f, 0x40, 0x7b, 0x8d,
+ 0x9f, 0x3b, 0x4a, 0xfa, 0x0c, 0x1e, 0xcc, 0x10, 0x3f, 0xe7, 0xd9, 0xc1, 0x6c, 0x08, 0xf2, 0xbc,
+ 0x14, 0x9a, 0x36, 0x52, 0xc7, 0x14, 0xd2, 0xff, 0xb5, 0x04, 0xe5, 0xd7, 0xc9, 0x15, 0xf9, 0x10,
+ 0xaa, 0x1a, 0x17, 0x53, 0x7b, 0x25, 0x3c, 0x8a, 0x1b, 0xb0, 0xb0, 0xb0, 0x34, 0xb7, 0x93, 0xa7,
+ 0x50, 0xcd, 0x6f, 0x05, 0x67, 0xfd, 0x56, 0xc8, 0x6d, 0xe4, 0x1b, 0x78, 0x98, 0x77, 0x2e, 0x2a,
+ 0x96, 0x50, 0x7b, 0x65, 0x84, 0x7f, 0x38, 0x87, 0x5f, 0xd8, 0x50, 0xba, 0x9b, 0x9d, 0x58, 0xd0,
+ 0xdd, 0x32, 0x02, 0xee, 0x2d, 0x23, 0x20, 0xa1, 0x76, 0x2a, 0x45, 0xdc, 0x67, 0x9a, 0x93, 0xaf,
+ 0x61, 0x77, 0x43, 0x06, 0xd9, 0xfe, 0x6f, 0x4e, 0x80, 0xac, 0x27, 0x60, 0xf7, 0x8b, 0xa9, 0xbe,
+ 0x30, 0x8a, 0xa9, 0xeb, 0xec, 0x52, 0x2f, 0x14, 0xfe, 0x0f, 0x25, 0x68, 0x75, 0x63, 0xa3, 0xd8,
+ 0xe9, 0x88, 0x89, 0xf8, 0x4d, 0x62, 0xa6, 0x89, 0x21, 0xfb, 0x50, 0x49, 0xd9, 0xca, 0x82, 0xad,
+ 0x91, 0x99, 0x99, 0xc9, 0x73, 0x68, 0x86, 0x32, 0x36, 0x4a, 0x8e, 0x83, 0x3b, 0x38, 0xdd, 0xc9,
+ 0x7c, 0xf2, 0x8b, 0xd6, 0x83, 0xaa, 0x54, 0x91, 0x88, 0xd9, 0x38, 0x1b, 0xca, 0x5c, 0xc4, 0x6c,
+ 0x4e, 0x95, 0xd4, 0xfa, 0x7f, 0x91, 0xcd, 0x4f, 0x25, 0x80, 0x9e, 0x34, 0xfc, 0x3f, 0xce, 0xc3,
+ 0xfe, 0x91, 0x67, 0xd2, 0x70, 0xbc, 0x1c, 0x1b, 0x14, 0xbf, 0xfd, 0x5f, 0x4a, 0xb0, 0xdd, 0xe3,
+ 0x46, 0x76, 0x63, 0x9b, 0xda, 0x21, 0x34, 0xf5, 0x94, 0xc7, 0x26, 0x90, 0x98, 0x6a, 0xf1, 0x33,
+ 0x2d, 0xf6, 0xf9, 0x01, 0x3a, 0xa4, 0xa5, 0x74, 0xa3, 0xdb, 0x86, 0xcb, 0xf9, 0x8b, 0xc3, 0xb5,
+ 0x71, 0xb8, 0xcb, 0x9b, 0x87, 0x7b, 0xb1, 0x42, 0x77, 0x99, 0xe9, 0x37, 0x00, 0x94, 0x1b, 0xa1,
+ 0xb8, 0x75, 0xfc, 0xf3, 0x44, 0x2f, 0x00, 0x3a, 0xcb, 0x80, 0x3f, 0x97, 0x60, 0xeb, 0x7c, 0xca,
+ 0xe3, 0xe8, 0xad, 0xa7, 0xe6, 0x37, 0x07, 0x9a, 0xc5, 0x4a, 0xa4, 0xed, 0xfe, 0x1c, 0x76, 0x27,
+ 0x4c, 0xc4, 0xa1, 0xd5, 0xdc, 0x51, 0x57, 0x7b, 0xee, 0xf4, 0x4f, 0xd7, 0xb6, 0x61, 0xc2, 0xcb,
+ 0xf7, 0x4f, 0xf8, 0x97, 0xd0, 0x4a, 0xdf, 0x7a, 0xd1, 0xfc, 0xb1, 0x86, 0xd5, 0xd6, 0x3b, 0xbb,
+ 0xf3, 0xf7, 0x4a, 0xf1, 0x8e, 0xa3, 0x4d, 0xb6, 0xf2, 0xb0, 0xdb, 0xc8, 0xe8, 0xd6, 0xfd, 0x8c,
+ 0x56, 0x96, 0xd7, 0xe9, 0x63, 0x68, 0x2b, 0x76, 0x59, 0xe0, 0xbe, 0xb8, 0x36, 0x1c, 0x7f, 0xec,
+ 0x0d, 0xba, 0x6e, 0xe8, 0x57, 0xf0, 0x2d, 0x7e, 0xfc, 0x47, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2a,
+ 0xe9, 0x2b, 0xb1, 0x97, 0x0b, 0x00, 0x00,
}
AssetDefinition asset_definition = 4;
repeated bytes witness_arguments = 5;
uint64 ordinal = 6;
+ bytes rawDefinitionByte = 7;
}
}
// NewCrossChainInput creates a new CrossChainInput.
-func NewCrossChainInput(mainchainOutputID *Hash, prog *Program, ordinal uint64, assetDef *AssetDefinition) *CrossChainInput {
+func NewCrossChainInput(mainchainOutputID *Hash, prog *Program, ordinal uint64, assetDef *AssetDefinition, rawDefinitionByte []byte) *CrossChainInput {
return &CrossChainInput{
MainchainOutputId: mainchainOutputID,
Ordinal: ordinal,
ControlProgram: prog,
AssetDefinition: assetDef,
+ RawDefinitionByte: rawDefinitionByte,
}
}
IssuanceProgram: &Program{VmVersion: 1, Code: []byte{1, 2, 3, 4}},
Data: &Hash{V0: 0, V1: 1, V2: 2, V3: 3},
},
+ []byte{},
),
expectEntryID: "14bb3f6e68f37d037b1f1539a21ab41e182b8d59d703a1af6c426d52cfc775d9",
},
return nil
}
+// WriteTo write block to io.Writer
func (b *Block) WriteTo(w io.Writer) (int64, error) {
ew := errors.NewWriter(w)
if err := b.writeTo(ew, SerBlockFull); err != nil {
"github.com/bytom/vapor/encoding/blockchain"
)
+// BlockWitness save the consensus node sign
type BlockWitness struct {
// Witness is a vector of arguments for validating this block.
Witness [][]byte
return err
}
+// Set save data to index position
func (bw *BlockWitness) Set(index uint64, data []byte) {
if uint64(len(bw.Witness)) <= index {
newWitness := make([][]byte, index+1, index+1)
bw.Witness[index] = data
}
+// Delete remove data from index position
func (bw *BlockWitness) Delete(index uint64) {
if uint64(len(bw.Witness)) > index {
bw.Witness[index] = nil
}
}
+// Get return data from index position
func (bw *BlockWitness) Get(index uint64) []byte {
if uint64(len(bw.Witness)) > index {
return bw.Witness[index]
}
}
+// OutputType implement the txout interface
func (it *CrossChainOutput) OutputType() uint8 { return CrossChainOutputType }
}
}
+// OutputType implement the txout interface
func (it *IntraChainOutput) OutputType() uint8 { return IntraChainOutputType }
},
}
- crossIn := bc.NewCrossChainInput(&mainchainOutputID, prog, uint64(i), assetDef)
+ crossIn := bc.NewCrossChainInput(&mainchainOutputID, prog, uint64(i), assetDef, inp.AssetDefinition)
crossIn.WitnessArguments = inp.Arguments
crossInID := addEntry(crossIn)
muxSources[i] = &bc.ValueSource{
}
}
+// OutputType implement the txout interface
func (it *VoteOutput) OutputType() uint8 { return VoteOutputType }
import (
log "github.com/sirupsen/logrus"
- "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"
if err != nil {
return err
}
+
if err := consensusResult.ApplyBlock(block); err != nil {
return err
}
+ for _, p := range c.subProtocols {
+ if err := c.syncProtocolStatus(p); err != nil {
+ return errors.Wrap(err, p.Name(), "sync sub protocol status")
+ }
+
+ if err := p.ApplyBlock(block); err != nil {
+ return errors.Wrap(err, p.Name(), "sub protocol connect block")
+ }
+ }
+
+ if err := c.applyBlockSign(&block.BlockHeader); err != nil {
+ return err
+ }
+
irrBlockHeader := c.lastIrrBlockHeader
if c.isIrreversible(&block.BlockHeader) && block.Height > irrBlockHeader.Height {
irrBlockHeader = &block.BlockHeader
return nil
}
-func (c *Chain) reorganizeChain(blockHeader *types.BlockHeader) error {
- attachBlockHeaders, detachBlockHeaders, err := c.calcReorganizeChain(blockHeader, c.bestBlockHeader)
+func (c *Chain) detachBlock(detachBlockHeader *types.BlockHeader, consensusResult *state.ConsensusResult, utxoView *state.UtxoViewpoint) (*types.Block, error) {
+ detachHash := detachBlockHeader.Hash()
+ block, err := c.store.GetBlock(&detachHash)
if err != nil {
- return err
+ return block, err
}
+ detachBlock := types.MapBlock(block)
+ if err := consensusResult.DetachBlock(block); err != nil {
+ return block, err
+ }
+
+ if err := c.store.GetTransactionsUtxo(utxoView, detachBlock.Transactions); err != nil {
+ return block, err
+ }
+
+ txStatus, err := c.GetTransactionStatus(&detachBlock.ID)
+ if err != nil {
+ return block, err
+ }
+
+ if err := utxoView.DetachBlock(detachBlock, txStatus); err != nil {
+ return block, err
+ }
+
+ for _, p := range c.subProtocols {
+ if err := p.DetachBlock(block); err != nil {
+ return block, errors.Wrap(err, p.Name(), "sub protocol detach block")
+ }
+ }
+
+ log.WithFields(log.Fields{"module": logModule, "height": detachBlockHeader.Height, "hash": detachHash.String()}).Debug("detach from mainchain")
+ return block, nil
+}
+
+func (c *Chain) syncSubProtocols() error {
+ for _, p := range c.subProtocols {
+ if err := c.syncProtocolStatus(p); err != nil {
+ return errors.Wrap(err, p.Name(), "sync sub protocol status")
+ }
+ }
+ return nil
+}
+
+// Rollback rollback the chain from one blockHeight to targetBlockHeight
+// WARNING: we recommend to use this only in commond line
+func (c *Chain) Rollback(targetHeight uint64) error {
+ c.cond.L.Lock()
+ defer c.cond.L.Unlock()
+
utxoView := state.NewUtxoViewpoint()
- consensusResults := []*state.ConsensusResult{}
consensusResult, err := c.getBestConsensusResult()
if err != nil {
return err
}
- txsToRestore := map[bc.Hash]*types.Tx{}
- for _, detachBlockHeader := range detachBlockHeaders {
- detachHash := detachBlockHeader.Hash()
- b, err := c.store.GetBlock(&detachHash)
+ if err = c.syncSubProtocols(); err != nil {
+ return err
+ }
+
+ targetBlockHeader, err := c.GetHeaderByHeight(targetHeight)
+ if err != nil {
+ return err
+ }
+
+ _, deletedBlockHeaders, err := c.calcReorganizeChain(targetBlockHeader, c.bestBlockHeader)
+ if err != nil {
+ return err
+ }
+
+ deletedBlocks := []*types.Block{}
+ for _, deletedBlockHeader := range deletedBlockHeaders {
+ block, err := c.detachBlock(deletedBlockHeader, consensusResult, utxoView)
if err != nil {
return err
}
- detachBlock := types.MapBlock(b)
- if err := c.store.GetTransactionsUtxo(utxoView, detachBlock.Transactions); err != nil {
- return err
- }
+ deletedBlocks = append(deletedBlocks, block)
+ }
- txStatus, err := c.GetTransactionStatus(&detachBlock.ID)
- if err != nil {
+ setIrrBlockHeader := c.lastIrrBlockHeader
+ if c.lastIrrBlockHeader.Height > targetBlockHeader.Height {
+ setIrrBlockHeader = targetBlockHeader
+ }
+
+ startSeq := state.CalcVoteSeq(c.bestBlockHeader.Height)
+
+ if err = c.setState(targetBlockHeader, setIrrBlockHeader, nil, utxoView, []*state.ConsensusResult{consensusResult.Fork()}); err != nil {
+ return err
+ }
+
+ for _, block := range deletedBlocks {
+ if err := c.store.DeleteBlock(block); err != nil {
return err
}
+ }
- if err := utxoView.DetachBlock(detachBlock, txStatus); err != nil {
+ endSeq := state.CalcVoteSeq(targetHeight)
+ for nowSeq := startSeq; nowSeq > endSeq; nowSeq-- {
+ if err := c.store.DeleteConsensusResult(nowSeq); err != nil {
return err
}
+ }
- if err := consensusResult.DetachBlock(b); err != nil {
+ return nil
+}
+
+func (c *Chain) reorganizeChain(blockHeader *types.BlockHeader) error {
+ attachBlockHeaders, detachBlockHeaders, err := c.calcReorganizeChain(blockHeader, c.bestBlockHeader)
+ if err != nil {
+ return err
+ }
+
+ utxoView := state.NewUtxoViewpoint()
+ consensusResults := []*state.ConsensusResult{}
+ consensusResult, err := c.getBestConsensusResult()
+ if err != nil {
+ return err
+ }
+
+ if err = c.syncSubProtocols(); err != nil {
+ return err
+ }
+
+ txsToRestore := map[bc.Hash]*types.Tx{}
+ for _, detachBlockHeader := range detachBlockHeaders {
+ b, err := c.detachBlock(detachBlockHeader, consensusResult, utxoView)
+ if err != nil {
return err
}
for _, tx := range b.Transactions {
txsToRestore[tx.ID] = tx
}
-
- blockHash := blockHeader.Hash()
- log.WithFields(log.Fields{"module": logModule, "height": blockHeader.Height, "hash": blockHash.String()}).Debug("detach from mainchain")
}
txsToRemove := map[bc.Hash]*types.Tx{}
return err
}
+ for _, p := range c.subProtocols {
+ if err := p.ApplyBlock(b); err != nil {
+ return errors.Wrap(err, p.Name(), "sub protocol attach block")
+ }
+ }
+
if consensusResult.IsFinalize() {
consensusResults = append(consensusResults, consensusResult.Fork())
}
+ if err := c.applyBlockSign(attachBlockHeader); err != nil {
+ return err
+ }
+
if c.isIrreversible(attachBlockHeader) && attachBlockHeader.Height > irrBlockHeader.Height {
irrBlockHeader = attachBlockHeader
}
return errors.Sub(ErrBadBlock, err)
}
- signature, err := c.SignBlock(block)
- if err != nil {
- return errors.Sub(ErrBadBlock, err)
+ for _, p := range c.subProtocols {
+ if err := p.ValidateBlock(block, bcBlock.TransactionStatus.GetVerifyStatus()); err != nil {
+ return errors.Wrap(err, "sub protocol save block")
+ }
}
if err := c.store.SaveBlock(block, bcBlock.TransactionStatus); err != nil {
return err
}
- c.orphanManage.Delete(&bcBlock.ID)
- if len(signature) != 0 {
- xPub := config.CommonConfig.PrivateKey().XPub()
- if err := c.eventDispatcher.Post(event.BlockSignatureEvent{BlockHash: block.Hash(), Signature: signature, XPub: xPub[:]}); err != nil {
- return err
- }
- }
+ c.orphanManage.Delete(&bcBlock.ID)
return nil
}
func (s *mStore) GetTransactionStatus(*bc.Hash) (*bc.TransactionStatus, error) { return nil, nil }
func (s *mStore) GetTransactionsUtxo(*state.UtxoViewpoint, []*bc.Tx) error { return nil }
func (s *mStore) GetUtxo(*bc.Hash) (*storage.UtxoEntry, error) { return nil, nil }
-func (s *mStore) GetConsensusResult(uint64) (*state.ConsensusResult, error) { return nil, nil }
+func (s *mStore) GetConsensusResult(uint64) (*state.ConsensusResult, error) { return nil, nil }
func (s *mStore) GetMainChainHash(uint64) (*bc.Hash, error) { return nil, nil }
func (s *mStore) GetBlockHashesByHeight(uint64) ([]*bc.Hash, error) { return nil, nil }
+func (s *mStore) DeleteConsensusResult(seq uint64) error { return nil }
+func (s *mStore) DeleteBlock(*types.Block) error { return nil }
func (s *mStore) SaveBlock(*types.Block, *bc.TransactionStatus) error { return nil }
func (s *mStore) SaveBlockHeader(blockHeader *types.BlockHeader) error {
s.blockHeaders[blockHeader.Hash()] = blockHeader
Height: 1202,
PreviousBlockHash: testutil.MustDecodeHash("a5be1d1177eb027327baedb869f902f74850476d0b9432a30391a3165d3af7cc"),
},
- // fork chain, fork height in 1198, rollback 1200, 1199, append 1199, 1200
+ // fork chain, fork height in 1198, rollback 1200, 1199, append 1199, 1200
{
Height: 1199,
PreviousBlockHash: testutil.MustDecodeHash("ef24de31371b4d34363011b6c8b065b1acaad9264d9abae2253d584e0d3a8739"),
return nil, nil
}
+func (s *dummyStore) DeleteConsensusResult(seq uint64) error {
+ return nil
+}
+
func (s *dummyStore) SaveBlock(block *types.Block, _ *bc.TransactionStatus) error {
hash := block.Hash()
s.blocks[hash.String()] = block
return nil
}
+func (s *dummyStore) DeleteBlock(block *types.Block) error {
+ return nil
+}
+
func (s *dummyStore) SaveBlockHeader(header *types.BlockHeader) error {
hash := header.Hash()
s.blockHeaders[hash.String()] = header
"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"
maxKnownTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS)
)
+// Protocoler is interface for layer 2 consensus protocol
+type Protocoler interface {
+ Name() string
+ StartHeight() uint64
+ BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error)
+ ChainStatus() (uint64, *bc.Hash, error)
+ ValidateBlock(block *types.Block, verifyResults []*bc.TxVerifyResult) error
+ ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult, blockHeight uint64) error
+ ApplyBlock(block *types.Block) error
+ DetachBlock(block *types.Block) error
+}
+
// Chain provides functions for working with the Bytom block chain.
type Chain struct {
orphanManage *OrphanManage
txPool *TxPool
store Store
processBlockCh chan *processBlockMsg
+ subProtocols []Protocoler
signatureCache *common.Cache
eventDispatcher *event.Dispatcher
}
// NewChain returns a new Chain using store as the underlying storage.
-func NewChain(store Store, txPool *TxPool, eventDispatcher *event.Dispatcher) (*Chain, error) {
+func NewChain(store Store, txPool *TxPool, subProtocols []Protocoler, eventDispatcher *event.Dispatcher) (*Chain, error) {
knownTxs, _ := common.NewOrderedSet(maxKnownTxs)
c := &Chain{
orphanManage: NewOrphanManage(),
txPool: txPool,
store: store,
+ subProtocols: subProtocols,
signatureCache: common.NewCache(maxSignatureCacheSize),
eventDispatcher: eventDispatcher,
processBlockCh: make(chan *processBlockMsg, maxProcessBlockChSize),
if err != nil {
return nil, err
}
+
+ for _, p := range c.subProtocols {
+ if err := c.syncProtocolStatus(p); err != nil {
+ return nil, errors.Wrap(err, p.Name(), "sync sub protocol status")
+ }
+ }
+
go c.blockProcesser()
return c, nil
}
return err
}
- consensusResults := []*state.ConsensusResult{&state.ConsensusResult{
+ for _, subProtocol := range c.subProtocols {
+ if err := subProtocol.ApplyBlock(genesisBlock); err != nil {
+ return err
+ }
+ }
+
+ consensusResults := []*state.ConsensusResult{{
Seq: 0,
NumOfVote: make(map[string]uint64),
CoinbaseReward: make(map[string]uint64),
return *blockHash == hash
}
+// SubProtocols return list of layer 2 consensus protocol
+func (c *Chain) SubProtocols() []Protocoler {
+ return c.subProtocols
+}
+
// trace back to the tail of the chain from the given block header
func (c *Chain) traceLongestChainTail(blockHeader *types.BlockHeader) (*types.BlockHeader, error) {
longestTail, workQueue := blockHeader, []*types.BlockHeader{blockHeader}
}
}
+func (c *Chain) syncProtocolStatus(subProtocol Protocoler) error {
+ if c.bestBlockHeader.Height < subProtocol.StartHeight() {
+ return nil
+ }
+
+ protocolHeight, protocolHash, err := subProtocol.ChainStatus()
+ if err != nil {
+ return errors.Wrap(err, "failed on get sub protocol status")
+ }
+
+ if *protocolHash == c.bestBlockHeader.Hash() {
+ return nil
+ }
+
+ for !c.InMainChain(*protocolHash) {
+ block, err := c.GetBlockByHash(protocolHash)
+ if err != nil {
+ return errors.Wrap(err, subProtocol.Name(), "can't get block by hash in chain")
+ }
+
+ if err := subProtocol.DetachBlock(block); err != nil {
+ return errors.Wrap(err, subProtocol.Name(), "sub protocol detach block err")
+ }
+
+ protocolHeight, protocolHash = block.Height-1, &block.PreviousBlockHash
+ }
+
+ for height := protocolHeight + 1; height <= c.bestBlockHeader.Height; height++ {
+ block, err := c.GetBlockByHeight(height)
+ if err != nil {
+ return errors.Wrap(err, subProtocol.Name(), "can't get block by height in chain")
+ }
+
+ if err := subProtocol.ApplyBlock(block); err != nil {
+ return errors.Wrap(err, subProtocol.Name(), "sub protocol apply block err")
+ }
+
+ blockHash := block.Hash()
+ protocolHeight, protocolHash = block.Height, &blockHash
+ }
+
+ return nil
+}
+
// This function must be called with mu lock in above level
func (c *Chain) setState(blockHeader, irrBlockHeader *types.BlockHeader, mainBlockHeaders []*types.BlockHeader, view *state.UtxoViewpoint, consensusResults []*state.ConsensusResult) error {
if err := c.store.SaveChainStatus(blockHeader, irrBlockHeader, mainBlockHeaders, view, consensusResults); err != nil {
"github.com/bytom/vapor/protocol/state"
)
+// predefine errors
var (
ErrNotFoundConsensusResult = errors.New("can't find the vote result by given sequence")
)
GetMainChainHash(uint64) (*bc.Hash, error)
GetBlockHashesByHeight(uint64) ([]*bc.Hash, error)
+ DeleteConsensusResult(uint64) error
+ DeleteBlock(*types.Block) error
SaveBlock(*types.Block, *bc.TransactionStatus) error
SaveBlockHeader(*types.BlockHeader) error
SaveChainStatus(*types.BlockHeader, *types.BlockHeader, []*types.BlockHeader, *state.UtxoViewpoint, []*state.ConsensusResult) error
return false, err
}
+ txVerifyResult := &bc.TxVerifyResult{StatusFail: err != nil}
+ for _, p := range c.subProtocols {
+ if err := p.ValidateTx(tx, txVerifyResult, bh.Height); err != nil {
+ c.txPool.AddErrCache(&tx.ID, err)
+ return false, err
+ }
+ }
+
if err != nil {
log.WithFields(log.Fields{"module": logModule, "tx_id": tx.Tx.ID.String(), "error": err}).Info("transaction status fail")
}
ErrDustTx = errors.New("transaction is dust tx")
)
+// DustFilterer is inerface for dust transaction filter rule
+type DustFilterer interface {
+ IsDust(tx *types.Tx) bool
+}
+
+// TxMsgEvent is message wrap for subscribe event
type TxMsgEvent struct{ TxMsg *TxPoolMsg }
// TxDesc store tx and related info for mining strategy
orphans map[bc.Hash]*orphanTx
orphansByPrev map[bc.Hash]map[bc.Hash]*orphanTx
errCache *lru.Cache
+ filters []DustFilterer
eventDispatcher *event.Dispatcher
}
// NewTxPool init a new TxPool
-func NewTxPool(store Store, dispatcher *event.Dispatcher) *TxPool {
+func NewTxPool(store Store, filters []DustFilterer, dispatcher *event.Dispatcher) *TxPool {
tp := &TxPool{
lastUpdated: time.Now().Unix(),
store: store,
orphans: make(map[bc.Hash]*orphanTx),
orphansByPrev: make(map[bc.Hash]map[bc.Hash]*orphanTx),
errCache: lru.New(maxCachedErrTxs),
+ filters: filters,
eventDispatcher: dispatcher,
}
go tp.orphanExpireWorker()
return false
}
+func isNoGasStatusFail(tx *types.Tx, statusFail bool) bool {
+ if !statusFail {
+ return false
+ }
+
+ for _, input := range tx.TxData.Inputs {
+ if *consensus.BTMAssetID == input.AssetID() {
+ return false
+ }
+ }
+ return true
+}
+
//IsDust checks if a tx has zero output
func (tp *TxPool) IsDust(tx *types.Tx) bool {
- return isTransactionZeroOutput(tx)
+ if ok := isTransactionZeroOutput(tx); ok {
+ return ok
+ }
+
+ for _, filter := range tp.filters {
+ if ok := filter.IsDust(tx); ok {
+ return ok
+ }
+ }
+ return false
}
func (tp *TxPool) processTransaction(tx *types.Tx, statusFail bool, height, fee uint64) (bool, error) {
log.WithFields(log.Fields{"module": logModule, "tx_id": tx.ID.String()}).Warn("dust tx")
return false, nil
}
+
+ if isNoGasStatusFail(tx, statusFail) {
+ log.WithFields(log.Fields{"module": logModule, "tx_id": tx.ID.String()}).Warn("drop no gas status fail tx")
+ return false, nil
+ }
return tp.processTransaction(tx, statusFail, height, fee)
}
func (s *mockStore) GetMainChainHash(uint64) (*bc.Hash, error) { return nil, nil }
func (s *mockStore) GetBlockHashesByHeight(uint64) ([]*bc.Hash, error) { return nil, nil }
func (s *mockStore) SaveBlock(*types.Block, *bc.TransactionStatus) error { return nil }
+func (s *mockStore) DeleteConsensusResult(seq uint64) error { return nil }
+func (s *mockStore) DeleteBlock(*types.Block) error { return nil }
func (s *mockStore) SaveBlockHeader(*types.BlockHeader) error { return nil }
func (s *mockStore) SaveChainStatus(*types.BlockHeader, *types.BlockHeader, []*types.BlockHeader, *state.UtxoViewpoint, []*state.ConsensusResult) error {
return nil
func (s *mockStore1) GetConsensusResult(uint64) (*state.ConsensusResult, error) { return nil, nil }
func (s *mockStore1) GetMainChainHash(uint64) (*bc.Hash, error) { return nil, nil }
func (s *mockStore1) GetBlockHashesByHeight(uint64) ([]*bc.Hash, error) { return nil, nil }
+func (s *mockStore1) DeleteBlock(*types.Block) error { return nil }
func (s *mockStore1) SaveBlock(*types.Block, *bc.TransactionStatus) error { return nil }
+func (s *mockStore1) DeleteConsensusResult(seq uint64) error { return nil }
func (s *mockStore1) SaveBlockHeader(*types.BlockHeader) error { return nil }
func (s *mockStore1) SaveChainStatus(*types.BlockHeader, *types.BlockHeader, []*types.BlockHeader, *state.UtxoViewpoint, []*state.ConsensusResult) error {
return nil
import (
"fmt"
"math"
+ "runtime"
"sync"
+ "github.com/bytom/vapor/common"
"github.com/bytom/vapor/config"
"github.com/bytom/vapor/consensus"
"github.com/bytom/vapor/errors"
"github.com/bytom/vapor/protocol/vm"
)
-const (
- validateWorkerNum = 32
-)
-
// validate transaction error
var (
ErrTxVersion = errors.New("invalid transaction version")
}
prog := &bc.Program{
- VmVersion: e.ControlProgram.VmVersion,
- Code: config.FederationWScript(config.CommonConfig),
+ VmVersion: e.AssetDefinition.IssuanceProgram.VmVersion,
+ Code: e.AssetDefinition.IssuanceProgram.Code,
+ }
+
+ if !common.IsOpenFederationIssueAsset(e.RawDefinitionByte) {
+ prog.Code = config.FederationWScript(config.CommonConfig)
}
if _, err := vm.Verify(NewTxVMContext(vs, e, prog, e.WitnessArguments), consensus.ActiveNetParams.DefaultGasCredit); err != nil {
// ValidateTxs validates txs in async mode
func ValidateTxs(txs []*bc.Tx, block *bc.Block) []*ValidateTxResult {
txSize := len(txs)
+ validateWorkerNum := runtime.NumCPU()
//init the goroutine validate worker
var wg sync.WaitGroup
workCh := make(chan *validateTxWork, txSize)
"github.com/davecgh/go-spew/spew"
"github.com/bytom/vapor/consensus"
+ "github.com/bytom/vapor/crypto/ed25519/chainkd"
"github.com/bytom/vapor/crypto/sha3pool"
"github.com/bytom/vapor/errors"
"github.com/bytom/vapor/protocol/bc"
}
}
+func TestMagneticContractTx(t *testing.T) {
+ buyerArgs := vmutil.MagneticContractArgs{
+ RequestedAsset: bc.AssetID{V0: 1},
+ RatioNumerator: 1,
+ RatioDenominator: 2,
+ SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19204"),
+ SellerKey: testutil.MustDecodeHexString("af1927316233365dd525d3b48f2869f125a656958ee3946286f42904c35b9c91"),
+ }
+
+ sellerArgs := vmutil.MagneticContractArgs{
+ RequestedAsset: bc.AssetID{V0: 2},
+ RatioNumerator: 2,
+ RatioDenominator: 1,
+ SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19204"),
+ SellerKey: testutil.MustDecodeHexString("af1927316233365dd525d3b48f2869f125a656958ee3946286f42904c35b9c91"),
+ }
+
+ programBuyer, err := vmutil.P2WMCProgram(buyerArgs)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ programSeller, err := vmutil.P2WMCProgram(sellerArgs)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cases := []struct {
+ desc string
+ block *bc.Block
+ err error
+ }{
+ {
+ desc: "contracts all full trade",
+ block: &bc.Block{
+ BlockHeader: &bc.BlockHeader{Version: 0},
+ Transactions: []*bc.Tx{
+ types.MapTx(&types.TxData{
+ SerializedSize: 1,
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 199800000, sellerArgs.SellerProgram),
+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 99900000, buyerArgs.SellerProgram),
+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000, []byte{0x51}),
+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000, []byte{0x51}),
+ },
+ }),
+ },
+ },
+ err: nil,
+ },
+ {
+ desc: "first contract partial trade, second contract full trade",
+ block: &bc.Block{
+ BlockHeader: &bc.BlockHeader{Version: 0},
+ Transactions: []*bc.Tx{
+ types.MapTx(&types.TxData{
+ SerializedSize: 1,
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(0), vm.Int64Bytes(0)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 200000000, 1, programSeller),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 99900000, sellerArgs.SellerProgram),
+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 150000000, programSeller),
+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 49950000, buyerArgs.SellerProgram),
+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000, []byte{0x51}),
+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000, []byte{0x51}),
+ },
+ }),
+ },
+ },
+ err: nil,
+ },
+ {
+ desc: "first contract full trade, second contract partial trade",
+ block: &bc.Block{
+ BlockHeader: &bc.BlockHeader{Version: 0},
+ Transactions: []*bc.Tx{
+ types.MapTx(&types.TxData{
+ SerializedSize: 1,
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(1), vm.Int64Bytes(0)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 300000000, 0, programBuyer),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 199800000, sellerArgs.SellerProgram),
+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 99900000, buyerArgs.SellerProgram),
+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, programBuyer),
+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000, []byte{0x51}),
+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000, []byte{0x51}),
+ },
+ }),
+ },
+ },
+ err: nil,
+ },
+ {
+ desc: "cancel magnetic contract",
+ block: &bc.Block{
+ BlockHeader: &bc.BlockHeader{Version: 0},
+ Transactions: []*bc.Tx{
+ types.MapTx(&types.TxData{
+ SerializedSize: 1,
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{testutil.MustDecodeHexString("851a14d69076507e202a94a884cdfb3b9f1ecbc1fb0634d2f0d1f9c1a275fdbdf921af0c5309d2d0a0deb85973cba23a4076d2c169c7f08ade2af4048d91d209"), vm.Int64Bytes(0), vm.Int64Bytes(2)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 0, programSeller),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
+ },
+ }),
+ },
+ },
+ err: nil,
+ },
+ {
+ desc: "wrong signature with cancel magnetic contract",
+ block: &bc.Block{
+ BlockHeader: &bc.BlockHeader{Version: 0},
+ Transactions: []*bc.Tx{
+ types.MapTx(&types.TxData{
+ SerializedSize: 1,
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{testutil.MustDecodeHexString("686b983a8de1893ef723144389fd1f07b12b048f52f389faa863243195931d5732dbfd15470b43ed63d5067900718cf94f137073f4a972d277bbd967b022545d"), vm.Int64Bytes(0), vm.Int64Bytes(2)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 0, programSeller),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
+ },
+ }),
+ },
+ },
+ err: vm.ErrFalseVMResult,
+ },
+ {
+ desc: "wrong output amount with contracts all full trade",
+ block: &bc.Block{
+ BlockHeader: &bc.BlockHeader{Version: 0},
+ Transactions: []*bc.Tx{
+ types.MapTx(&types.TxData{
+ SerializedSize: 1,
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, sellerArgs.SellerProgram),
+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, buyerArgs.SellerProgram),
+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, []byte{0x55}),
+ },
+ }),
+ },
+ },
+ err: vm.ErrFalseVMResult,
+ },
+ {
+ desc: "wrong output assetID with contracts all full trade",
+ block: &bc.Block{
+ BlockHeader: &bc.BlockHeader{Version: 0},
+ Transactions: []*bc.Tx{
+ types.MapTx(&types.TxData{
+ SerializedSize: 1,
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
+ types.NewSpendInput(nil, bc.Hash{V0: 30}, bc.AssetID{V0: 1}, 200000000, 0, []byte{0x51}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(bc.AssetID{V0: 1}, 200000000, sellerArgs.SellerProgram),
+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, buyerArgs.SellerProgram),
+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, []byte{0x55}),
+ },
+ }),
+ },
+ },
+ err: vm.ErrFalseVMResult,
+ },
+ {
+ desc: "wrong output change program with first contract partial trade and second contract full trade",
+ block: &bc.Block{
+ BlockHeader: &bc.BlockHeader{Version: 0},
+ Transactions: []*bc.Tx{
+ types.MapTx(&types.TxData{
+ SerializedSize: 1,
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(0), vm.Int64Bytes(0)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 200000000, 1, programSeller),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 99900000, sellerArgs.SellerProgram),
+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 150000000, []byte{0x55}),
+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 49950000, buyerArgs.SellerProgram),
+ types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000, []byte{0x51}),
+ types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000, []byte{0x51}),
+ },
+ }),
+ },
+ },
+ err: vm.ErrFalseVMResult,
+ },
+ }
+
+ for i, c := range cases {
+ if _, err := ValidateTx(c.block.Transactions[0], c.block); rootErr(err) != c.err {
+ t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, rootErr(err), c.err)
+ }
+ }
+}
+
+func TestRingMagneticContractTx(t *testing.T) {
+ aliceArgs := vmutil.MagneticContractArgs{
+ RequestedAsset: bc.AssetID{V0: 1},
+ RatioNumerator: 2,
+ RatioDenominator: 1,
+ SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19204"),
+ SellerKey: testutil.MustDecodeHexString("960ecabafb88ba460a40912841afecebf0e84884178611ac97210e327c0d1173"),
+ }
+
+ bobArgs := vmutil.MagneticContractArgs{
+ RequestedAsset: bc.AssetID{V0: 2},
+ RatioNumerator: 2,
+ RatioDenominator: 1,
+ SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19204"),
+ SellerKey: testutil.MustDecodeHexString("ad79ec6bd3a6d6dbe4d0ee902afc99a12b9702fb63edce5f651db3081d868b75"),
+ }
+
+ jackArgs := vmutil.MagneticContractArgs{
+ RequestedAsset: bc.AssetID{V0: 3},
+ RatioNumerator: 1,
+ RatioDenominator: 4,
+ SellerProgram: testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19204"),
+ SellerKey: testutil.MustDecodeHexString("9c19a91988c62046c2767bd7e9999b0c142891b9ebf467bfa59210b435cb0de7"),
+ }
+
+ aliceProgram, err := vmutil.P2WMCProgram(aliceArgs)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ bobProgram, err := vmutil.P2WMCProgram(bobArgs)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ jackProgram, err := vmutil.P2WMCProgram(jackArgs)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cases := []struct {
+ desc string
+ block *bc.Block
+ err error
+ }{
+ {
+ desc: "contracts all full trade",
+ block: &bc.Block{
+ BlockHeader: &bc.BlockHeader{Version: 0},
+ Transactions: []*bc.Tx{
+ types.MapTx(&types.TxData{
+ SerializedSize: 1,
+ Inputs: []*types.TxInput{
+ types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, jackArgs.RequestedAsset, 100000000, 0, aliceProgram),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, aliceArgs.RequestedAsset, 200000000, 0, bobProgram),
+ types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 30}, bobArgs.RequestedAsset, 400000000, 0, jackProgram),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(aliceArgs.RequestedAsset, 199800000, aliceArgs.SellerProgram),
+ types.NewIntraChainOutput(bobArgs.RequestedAsset, 399600000, bobArgs.SellerProgram),
+ types.NewIntraChainOutput(jackArgs.RequestedAsset, 99900000, jackArgs.SellerProgram),
+ types.NewIntraChainOutput(aliceArgs.RequestedAsset, 200000, []byte{0x51}),
+ types.NewIntraChainOutput(bobArgs.RequestedAsset, 400000, []byte{0x51}),
+ types.NewIntraChainOutput(jackArgs.RequestedAsset, 100000, []byte{0x51}),
+ },
+ }),
+ },
+ },
+ err: nil,
+ },
+ }
+
+ for i, c := range cases {
+ if _, err := ValidateTx(c.block.Transactions[0], c.block); rootErr(err) != c.err {
+ t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, rootErr(err), c.err)
+ }
+ }
+}
+
+func TestValidateOpenFederationIssueAsset(t *testing.T) {
+ tx := &types.Tx{TxData: types.TxData{Version: 1}}
+ tx.Inputs = append(tx.Inputs, types.NewCrossChainInput(nil,
+ testutil.MustDecodeHash("449143cb95389d19a1939879681168f78cc62614f4e0fb41f17b3232528a709d"),
+ testutil.MustDecodeAsset("60b550a772ddde42717ef3ab1178cf4f712a02fc9faf3678aa5468facff128f5"),
+ 100000000,
+ 0,
+ 1,
+ testutil.MustDecodeHexString("7b0a202022646563696d616c73223a20382c0a2020226465736372697074696f6e223a207b0a202020202269737375655f61737365745f616374696f6e223a20226f70656e5f66656465726174696f6e5f63726f73735f636861696e220a20207d2c0a2020226e616d65223a2022454f53222c0a20202271756f72756d223a20312c0a20202272656973737565223a202274727565222c0a20202273796d626f6c223a2022454f53220a7d"),
+ testutil.MustDecodeHexString("ae20d827c281d47f5de93f98544b20468feaac046bf8b89bd51102f6e971f09d215920be43bb856fe337b37f5f09040c2b6cdbe23eaf5aa4770b16ea51fdfc45514c295152ad"),
+ ))
+
+ tx.Outputs = append(tx.Outputs, types.NewIntraChainOutput(
+ testutil.MustDecodeAsset("60b550a772ddde42717ef3ab1178cf4f712a02fc9faf3678aa5468facff128f5"),
+ 100000000,
+ testutil.MustDecodeHexString("0014d8dd58f374f58cffb1b1a7cc1e18a712b4fe67b5"),
+ ))
+
+ byteData, err := tx.MarshalText()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ tx.SerializedSize = uint64(len(byteData))
+ tx = types.NewTx(tx.TxData)
+
+ xPrv := chainkd.XPrv(toByte64("f0293101b509a0e919b4775d849372f97c688af8bd85a9d369fc1a4528baa94c0d74dd09aa6eaeed582df47d391c816b916a0537302291b09743903b730333f9"))
+ signature := xPrv.Sign(tx.SigHash(0).Bytes())
+ tx.Inputs[0].SetArguments([][]byte{signature})
+ tx = types.NewTx(tx.TxData)
+
+ if _, err := ValidateTx(tx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Version: 1}}); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func toByte64(str string) [64]byte {
+ var result [64]byte
+ bytes := testutil.MustDecodeHexString(str)
+ for i := range bytes {
+ result[i] = bytes[i]
+ }
+ return result
+}
+
// A txFixture is returned by sample (below) to produce a sample
// transaction, which takes a separate, optional _input_ txFixture to
// affect the transaction that's built. The components of the
}
func witnessProgram(prog []byte) []byte {
- if segwit.IsP2WPKHScript(prog) {
- if witnessProg, err := segwit.ConvertP2PKHSigProgram([]byte(prog)); err == nil {
+ switch {
+ case segwit.IsP2WPKHScript(prog):
+ if witnessProg, err := segwit.ConvertP2PKHSigProgram(prog); err == nil {
return witnessProg
}
- } else if segwit.IsP2WSHScript(prog) {
- if witnessProg, err := segwit.ConvertP2SHProgram([]byte(prog)); err == nil {
+ case segwit.IsP2WSHScript(prog):
+ if witnessProg, err := segwit.ConvertP2SHProgram(prog); err == nil {
+ return witnessProg
+ }
+ case segwit.IsP2WMCScript(prog):
+ if witnessProg, err := segwit.ConvertP2MCProgram(prog); err == nil {
return witnessProg
}
}
import (
"math"
+ "math/big"
"github.com/bytom/vapor/math/checked"
)
}
return vm.pushBool(x >= min && x < max, true)
}
+
+func opMulFraction(vm *virtualMachine) error {
+ if err := vm.applyCost(8); err != nil {
+ return err
+ }
+
+ z, err := vm.popInt64(true)
+ if err != nil {
+ return err
+ }
+
+ if z == 0 {
+ return ErrDivZero
+ }
+
+ y, err := vm.popInt64(true)
+ if err != nil {
+ return err
+ }
+
+ x, err := vm.popInt64(true)
+ if err != nil {
+ return err
+ }
+
+ res := big.NewInt(x)
+ res.Mul(res, big.NewInt(y)).Quo(res, big.NewInt(z))
+ if !res.IsInt64() {
+ return ErrRange
+ }
+
+ return vm.pushInt64(res.Int64(), true)
+}
deferredCost: -18,
dataStack: [][]byte{{1}},
},
+ }, {
+ op: OP_MULFRACTION,
+ startVM: &virtualMachine{
+ runLimit: 50000,
+ dataStack: [][]byte{{15}, {2}, {3}},
+ },
+ wantVM: &virtualMachine{
+ runLimit: 49992,
+ deferredCost: -18,
+ dataStack: [][]byte{{10}},
+ },
+ }, {
+ op: OP_MULFRACTION,
+ startVM: &virtualMachine{
+ runLimit: 50000,
+ dataStack: [][]byte{Int64Bytes(-654), {3}, {2}},
+ },
+ wantVM: &virtualMachine{
+ runLimit: 49992,
+ deferredCost: -18,
+ dataStack: [][]byte{Int64Bytes(-981)},
+ },
+ }, {
+ op: OP_MULFRACTION,
+ startVM: &virtualMachine{
+ runLimit: 50000,
+ dataStack: [][]byte{Int64Bytes(-654), {3}, {}},
+ },
+ wantVM: &virtualMachine{
+ runLimit: 49992,
+ deferredCost: -18,
+ dataStack: [][]byte{},
+ },
+ wantErr: ErrDivZero,
}}
numops := []Op{
OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_MOD, OP_LSHIFT, OP_RSHIFT, OP_BOOLAND,
OP_BOOLOR, OP_NUMEQUAL, OP_NUMEQUALVERIFY, OP_NUMNOTEQUAL, OP_LESSTHAN,
OP_LESSTHANOREQUAL, OP_GREATERTHAN, OP_GREATERTHANOREQUAL, OP_MIN, OP_MAX, OP_WITHIN,
+ OP_MULFRACTION,
}
for _, op := range numops {
{fmt.Sprintf("%d 1 LSHIFT", int64(math.MinInt64)), true},
{fmt.Sprintf("%d 1 LSHIFT", int64(math.MinInt64)/2), false},
{fmt.Sprintf("%d 2 LSHIFT", int64(math.MinInt64)/2), true},
+ {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MaxInt64)/2-1, 2, 1), false},
+ {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MaxInt64)/2+1, 2, 1), true},
+ {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MinInt64)/2+1, 2, 1), false},
+ {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MinInt64)/2-1, 2, 1), true},
+ {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MaxInt64), 3, 2), true},
+ {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MaxInt64), 2, 3), false},
+ {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MinInt64), 3, 2), true},
+ {fmt.Sprintf("%d %d %d MULFRACTION", int64(math.MinInt64), 2, 3), false},
}
for i, c := range cases {
OP_MIN Op = 0xa3
OP_MAX Op = 0xa4
OP_WITHIN Op = 0xa5
+ OP_MULFRACTION Op = 0xa6
OP_SHA256 Op = 0xa8
OP_SHA3 Op = 0xaa
OP_MIN: {OP_MIN, "MIN", opMin},
OP_MAX: {OP_MAX, "MAX", opMax},
OP_WITHIN: {OP_WITHIN, "WITHIN", opWithin},
+ OP_MULFRACTION: {OP_MULFRACTION, "MULFRACTION", opMulFraction},
OP_SHA256: {OP_SHA256, "SHA256", opSha256},
OP_SHA3: {OP_SHA3, "SHA3", opSha3},
import (
"github.com/bytom/vapor/crypto/ed25519"
"github.com/bytom/vapor/errors"
+ "github.com/bytom/vapor/protocol/bc"
"github.com/bytom/vapor/protocol/vm"
)
ErrMultisigFormat = errors.New("bad multisig program format")
)
+// MagneticContractArgs is a struct for magnetic contract arguments
+type MagneticContractArgs struct {
+ RequestedAsset bc.AssetID
+ RatioNumerator int64
+ RatioDenominator int64
+ SellerProgram []byte
+ SellerKey []byte
+}
+
// IsUnspendable checks if a contorl program is absolute failed
func IsUnspendable(prog []byte) bool {
return len(prog) > 0 && prog[0] == byte(vm.OP_FAIL)
}
return nil
}
+
+// P2WMCProgram return the segwit pay to magnetic contract
+func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
+ builder := NewBuilder()
+ builder.AddInt64(0)
+ builder.AddData(magneticContractArgs.RequestedAsset.Bytes())
+ builder.AddInt64(magneticContractArgs.RatioNumerator)
+ builder.AddInt64(magneticContractArgs.RatioDenominator)
+ builder.AddData(magneticContractArgs.SellerProgram)
+ builder.AddData(magneticContractArgs.SellerKey)
+ return builder.Build()
+}
+
+// P2MCProgram generates the script for control with magnetic contract
+//
+// MagneticContract source code:
+// contract MagneticContract(requestedAsset: Asset,
+// ratioNumerator: Integer,
+// ratioDenominator: Integer,
+// sellerProgram: Program,
+// standardProgram: Program,
+// sellerKey: PublicKey) locks valueAmount of valueAsset {
+// clause partialTrade(exchangeAmount: Amount) {
+// define actualAmount: Integer = exchangeAmount * ratioDenominator / ratioNumerator
+// verify actualAmount > 0 && actualAmount < valueAmount
+// define receiveAmount: Integer = exchangeAmount * 999 / 1000
+// lock receiveAmount of requestedAsset with sellerProgram
+// lock valueAmount-actualAmount of valueAsset with standardProgram
+// unlock actualAmount of valueAsset
+// }
+// clause fullTrade() {
+// define requestedAmount: Integer = valueAmount * ratioNumerator / ratioDenominator
+// define requestedAmount: Integer = requestedAmount * 999 / 1000
+// verify requestedAmount > 0
+// lock requestedAmount of requestedAsset with sellerProgram
+// unlock valueAmount of valueAsset
+// }
+// clause cancel(sellerSig: Signature) {
+// verify checkTxSig(sellerKey, sellerSig)
+// unlock valueAmount of valueAsset
+// }
+// }
+//
+// contract stack flow:
+// 7 [... <position> <clause selector> sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset 7]
+// ROLL [... <clause selector> sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset <position>]
+// TOALTSTACK [... <clause selector> sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+// 6 [... <clause selector> sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset 6]
+// ROLL [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset <clause selector>]
+// DUP [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset <clause selector> <clause selector>]
+// 2 [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset <clause selector> <clause selector> 2]
+// NUMEQUAL [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset <clause selector> (<clause selector> == 2)]
+// JUMPIF:$cancel [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset <clause selector>]
+// JUMPIF:$fullTrade [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+// $partialTrade [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+// 6 [... exchangeAmount sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset 6]
+// PICK [... exchangeAmount sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset exchangeAmount]
+// 3 [... exchangeAmount sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset exchangeAmount 3]
+// ROLL [... exchangeAmount sellerKey standardProgram sellerProgram ratioNumerator requestedAsset exchangeAmount ratioDenominator]
+// 3 [... exchangeAmount sellerKey standardProgram sellerProgram ratioNumerator requestedAsset exchangeAmount ratioDenominator 3]
+// ROLL [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset exchangeAmount ratioDenominator ratioNumerator]
+// MULFRACTION [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount]
+// AMOUNT [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount]
+// OVER [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount actualAmount]
+// 0 [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount actualAmount 0]
+// GREATERTHAN [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0)]
+// 2 [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0) 2]
+// PICK [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0) actualAmount]
+// 2 [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0) actualAmount 2]
+// ROLL [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount (actualAmount > 0) actualAmount valueAmount]
+// LESSTHAN [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount (actualAmount > 0) (actualAmount < valueAmount)]
+// BOOLAND [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount ((actualAmount > 0) && (actualAmount < valueAmount))]
+// VERIFY [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount]
+// FROMALTSTACK [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position>]
+// DUP [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> <position>]
+// TOALTSTACK [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position>]
+// 6 [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> 6]
+// ROLL [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount]
+// 999 [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount 999]
+// 1000 [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount 1000]
+// MULFRACTION [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> receiveAmount]
+// 3 [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> receiveAmount 3]
+// ROLL [... sellerKey standardProgram sellerProgram actualAmount <position> receiveAmount requestedAsset]
+// 1 [... sellerKey standardProgram sellerProgram actualAmount <position> receiveAmount requestedAsset 1]
+// 5 [... sellerKey standardProgram sellerProgram actualAmount <position> receiveAmount requestedAsset 1 5]
+// ROLL [... sellerKey standardProgram actualAmount <position> receiveAmount requestedAsset 1 sellerProgram]
+// CHECKOUTPUT [... sellerKey standardProgram actualAmount checkOutput(receiveAmount, requestedAsset, sellerProgram)]
+// VERIFY [... sellerKey standardProgram actualAmount]
+// FROMALTSTACK [... sellerKey standardProgram actualAmount <position>]
+// 1 [... sellerKey standardProgram actualAmount <position> 1]
+// ADD [... sellerKey standardProgram actualAmount (<position> + 1)]
+// AMOUNT [... sellerKey standardProgram actualAmount (<position> + 1) valueAmount]
+// 2 [... sellerKey standardProgram actualAmount (<position> + 1) valueAmount 2]
+// ROLL [... sellerKey standardProgram (<position> + 1) valueAmount actualAmount]
+// SUB [... sellerKey standardProgram (<position> + 1) (valueAmount - actualAmount)]
+// ASSET [... sellerKey standardProgram (<position> + 1) (valueAmount - actualAmount) valueAsset]
+// 1 [... sellerKey standardProgram (<position> + 1) (valueAmount - actualAmount) valueAsset 1]
+// 4 [... sellerKey standardProgram (<position> + 1) (valueAmount - actualAmount) valueAsset 1 4]
+// ROLL [... sellerKey (<position> + 1) (valueAmount - actualAmount) valueAsset 1 standardProgram]
+// CHECKOUTPUT [... sellerKey checkOutput((valueAmount - actualAmount), valueAsset, standardProgram)]
+// JUMP:$_end [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+// $fullTrade [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+// AMOUNT [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset valueAmount]
+// 2 [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset valueAmount 2]
+// ROLL [... sellerKey standardProgram sellerProgram ratioDenominator requestedAsset valueAmount ratioNumerator]
+// 3 [... sellerKey standardProgram sellerProgram ratioDenominator requestedAsset valueAmount ratioNumerator 3]
+// ROLL [... sellerKey standardProgram sellerProgram requestedAsset valueAmount ratioNumerator ratioDenominator]
+// MULFRACTION [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount]
+// 999 [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount 999]
+// 1000 [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount 999 1000]
+// MULFRACTION [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount]
+// DUP [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount requestedAmount]
+// 0 [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount requestedAmount 0]
+// GREATERTHAN [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount (requestedAmount > 0)]
+// VERIFY [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount]
+// FROMALTSTACK [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount <position>]
+// SWAP [... sellerKey standardProgram sellerProgram requestedAsset <position> requestedAmount]
+// 2 [... sellerKey standardProgram sellerProgram requestedAsset <position> requestedAmount 2]
+// ROLL [... sellerKey standardProgram sellerProgram <position> requestedAmount requestedAsset]
+// 1 [... sellerKey standardProgram sellerProgram <position> requestedAmount requestedAsset 1]
+// 4 [... sellerKey standardProgram sellerProgram <position> requestedAmount requestedAsset 1 4]
+// ROLL [... sellerKey standardProgram <position> requestedAmount requestedAsset 1 sellerProgram]
+// CHECKOUTPUT [... sellerKey standardProgram checkOutput(requestedAmount, requestedAsset, sellerProgram)]
+// JUMP:$_end [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+// $cancel [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset <clause selector>]
+// DROP [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+// 6 [... sellerSig sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset 6]
+// ROLL [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset sellerSig]
+// 6 [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset sellerSig 6]
+// ROLL [... standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset sellerSig sellerKey]
+// TXSIGHASH SWAP CHECKSIG [... standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset checkTxSig(sellerKey, sellerSig)]
+// $_end [... sellerKey standardProgram sellerProgram ratioDenominator ratioNumerator requestedAsset]
+func P2MCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
+ standardProgram, err := P2WMCProgram(magneticContractArgs)
+ if err != nil {
+ return nil, err
+ }
+
+ builder := NewBuilder()
+ // contract arguments
+ builder.AddData(magneticContractArgs.SellerKey)
+ builder.AddData(standardProgram)
+ builder.AddData(magneticContractArgs.SellerProgram)
+ builder.AddInt64(magneticContractArgs.RatioDenominator)
+ builder.AddInt64(magneticContractArgs.RatioNumerator)
+ builder.AddData(magneticContractArgs.RequestedAsset.Bytes())
+
+ // contract instructions
+ builder.AddOp(vm.OP_7)
+ builder.AddOp(vm.OP_ROLL)
+ builder.AddOp(vm.OP_TOALTSTACK)
+ builder.AddOp(vm.OP_6)
+ builder.AddOp(vm.OP_ROLL)
+ builder.AddOp(vm.OP_DUP)
+ builder.AddOp(vm.OP_2)
+ builder.AddOp(vm.OP_NUMEQUAL)
+ builder.AddJumpIf(0)
+ builder.AddJumpIf(1)
+ builder.AddOp(vm.OP_6)
+ builder.AddOp(vm.OP_PICK)
+ builder.AddOp(vm.OP_3)
+ builder.AddOp(vm.OP_ROLL)
+ builder.AddOp(vm.OP_3)
+ builder.AddOp(vm.OP_ROLL)
+ builder.AddOp(vm.OP_MULFRACTION)
+ builder.AddOp(vm.OP_AMOUNT)
+ builder.AddOp(vm.OP_OVER)
+ builder.AddOp(vm.OP_0)
+ builder.AddOp(vm.OP_GREATERTHAN)
+ builder.AddOp(vm.OP_2)
+ builder.AddOp(vm.OP_PICK)
+ builder.AddOp(vm.OP_ROT)
+ builder.AddOp(vm.OP_LESSTHAN)
+ builder.AddOp(vm.OP_BOOLAND)
+ builder.AddOp(vm.OP_VERIFY)
+ builder.AddOp(vm.OP_FROMALTSTACK)
+ builder.AddOp(vm.OP_DUP)
+ builder.AddOp(vm.OP_TOALTSTACK)
+ builder.AddOp(vm.OP_6)
+ builder.AddOp(vm.OP_ROLL)
+ builder.AddInt64(999)
+ builder.AddInt64(1000)
+ builder.AddOp(vm.OP_MULFRACTION)
+ builder.AddOp(vm.OP_3)
+ builder.AddOp(vm.OP_ROLL)
+ builder.AddOp(vm.OP_1)
+ builder.AddOp(vm.OP_5)
+ builder.AddOp(vm.OP_ROLL)
+ builder.AddOp(vm.OP_CHECKOUTPUT)
+ builder.AddOp(vm.OP_VERIFY)
+ builder.AddOp(vm.OP_FROMALTSTACK)
+ builder.AddOp(vm.OP_1)
+ builder.AddOp(vm.OP_ADD)
+ builder.AddOp(vm.OP_AMOUNT)
+ builder.AddOp(vm.OP_ROT)
+ builder.AddOp(vm.OP_SUB)
+ builder.AddOp(vm.OP_ASSET)
+ builder.AddOp(vm.OP_1)
+ builder.AddOp(vm.OP_4)
+ builder.AddOp(vm.OP_ROLL)
+ builder.AddOp(vm.OP_CHECKOUTPUT)
+ builder.AddJump(2)
+ builder.SetJumpTarget(1)
+ builder.AddOp(vm.OP_AMOUNT)
+ builder.AddOp(vm.OP_ROT)
+ builder.AddOp(vm.OP_3)
+ builder.AddOp(vm.OP_ROLL)
+ builder.AddOp(vm.OP_MULFRACTION)
+ builder.AddInt64(999)
+ builder.AddInt64(1000)
+ builder.AddOp(vm.OP_MULFRACTION)
+ builder.AddOp(vm.OP_DUP)
+ builder.AddOp(vm.OP_0)
+ builder.AddOp(vm.OP_GREATERTHAN)
+ builder.AddOp(vm.OP_VERIFY)
+ builder.AddOp(vm.OP_FROMALTSTACK)
+ builder.AddOp(vm.OP_SWAP)
+ builder.AddOp(vm.OP_ROT)
+ builder.AddOp(vm.OP_1)
+ builder.AddOp(vm.OP_4)
+ builder.AddOp(vm.OP_ROLL)
+ builder.AddOp(vm.OP_CHECKOUTPUT)
+ builder.AddJump(3)
+ builder.SetJumpTarget(0)
+ builder.AddOp(vm.OP_DROP)
+ builder.AddOp(vm.OP_6)
+ builder.AddOp(vm.OP_ROLL)
+ builder.AddOp(vm.OP_6)
+ builder.AddOp(vm.OP_ROLL)
+ builder.AddOp(vm.OP_TXSIGHASH)
+ builder.AddOp(vm.OP_SWAP)
+ builder.AddOp(vm.OP_CHECKSIG)
+ builder.SetJumpTarget(2)
+ builder.SetJumpTarget(3)
+ return builder.Build()
+}
dispatcher := event.NewDispatcher()
store := database.NewStore(testDB)
accountStore := database.NewAccountStore(testDB)
- txPool := protocol.NewTxPool(store, dispatcher)
+ txPool := protocol.NewTxPool(store, nil, dispatcher)
config.CommonConfig = config.DefaultConfig()
- chain, err := protocol.NewChain(store, txPool, dispatcher)
+ chain, err := protocol.NewChain(store, txPool, nil, dispatcher)
if err != nil {
t.Fatal(err)
}
config.CommonConfig = config.DefaultConfig()
store := database.NewStore(testDB)
dispatcher := event.NewDispatcher()
- txPool := protocol.NewTxPool(store, dispatcher)
- chain, err := protocol.NewChain(store, txPool, dispatcher)
+ txPool := protocol.NewTxPool(store, nil, dispatcher)
+ chain, err := protocol.NewChain(store, txPool, nil, dispatcher)
if err != nil {
return nil, nil, nil, err
}
}
}
- block, err := proposal.NewBlockTemplate(chain, txPool, nil, uint64(time.Now().UnixNano()/1e6))
+ block, err := proposal.NewBlockTemplate(chain, nil, uint64(time.Now().UnixNano()/1e6), time.Minute, time.Minute)
if err != nil {
return err
}
func (m *Mempool) GetTransactions() []*protocol.TxDesc {
return m.txs
}
+
+func (m *Mempool) IsDust(tx *types.Tx) bool {
+ return false
+}
testDB := dbm.NewDB("testdb", "leveldb", "temp")
defer os.RemoveAll("temp")
- chain, _, txPool, err := test.MockChain(testDB)
+ chain, _, _, err := test.MockChain(testDB)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
- proposal.NewBlockTemplate(chain, txPool, accountManager, uint64(time.Now().UnixNano()/1e6))
+ proposal.NewBlockTemplate(chain, accountManager, uint64(time.Now().UnixNano()/1e6), time.Minute, time.Minute)
}
}
--- /dev/null
+package test
+
+import (
+ "os"
+ "testing"
+
+ "github.com/bytom/vapor/application/mov"
+ movDatabase "github.com/bytom/vapor/application/mov/database"
+ "github.com/bytom/vapor/consensus"
+ "github.com/bytom/vapor/database"
+ dbm "github.com/bytom/vapor/database/leveldb"
+ "github.com/bytom/vapor/database/storage"
+ "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/testutil"
+)
+
+type chainData struct {
+ bestBlockHeader *types.BlockHeader
+ lastIrrBlockHeader *types.BlockHeader
+ utxoViewPoint *state.UtxoViewpoint
+ storedBlocks []*types.Block
+ consensusResults []*state.ConsensusResult
+}
+
+func TestRollback(t *testing.T) {
+ cases := []struct {
+ desc string
+ movStartHeight uint64
+ RoundVoteBlockNums uint64
+ beforeChainData *chainData
+ wantChainData *chainData
+ rollbackToTargetHeight uint64
+ }{
+ {
+ desc: "rollback from height 1 to 0",
+ movStartHeight: 10,
+ RoundVoteBlockNums: 1200,
+ rollbackToTargetHeight: 0,
+ beforeChainData: &chainData{
+ bestBlockHeader: &types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ lastIrrBlockHeader: &types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ utxoViewPoint: &state.UtxoViewpoint{
+ Entries: map[bc.Hash]*storage.UtxoEntry{
+ testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+ },
+ },
+ storedBlocks: []*types.Block{
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 0,
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ },
+ consensusResults: []*state.ConsensusResult{
+ {
+ Seq: 1,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100002000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+ },
+ BlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ BlockHeight: 1,
+ CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(1) + 10000000000},
+ },
+ {
+ Seq: 0,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+ },
+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ BlockHeight: 0,
+ CoinbaseReward: map[string]uint64{},
+ },
+ },
+ },
+ wantChainData: &chainData{
+ bestBlockHeader: &types.BlockHeader{
+ Height: 0,
+ },
+ lastIrrBlockHeader: &types.BlockHeader{
+ Height: 0,
+ },
+ storedBlocks: []*types.Block{
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 0,
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+ },
+ }),
+ },
+ },
+ },
+ utxoViewPoint: &state.UtxoViewpoint{
+ Entries: map[bc.Hash]*storage.UtxoEntry{
+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+ },
+ },
+ consensusResults: []*state.ConsensusResult{
+ {
+ Seq: 0,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+ },
+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ BlockHeight: 0,
+ CoinbaseReward: map[string]uint64{},
+ },
+ },
+ },
+ },
+ {
+ desc: "rollback from height 2 to 0",
+ movStartHeight: 10,
+ RoundVoteBlockNums: 1200,
+ rollbackToTargetHeight: 0,
+ beforeChainData: &chainData{
+ bestBlockHeader: &types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ lastIrrBlockHeader: &types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ utxoViewPoint: &state.UtxoViewpoint{
+ Entries: map[bc.Hash]*storage.UtxoEntry{
+ testutil.MustDecodeHash("afee09925bea1695424450a91ad082a378f20534627fa5cb63f036846347ee08"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+ testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+ },
+ },
+ storedBlocks: []*types.Block{
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 0,
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 3000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 2500, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ },
+ consensusResults: []*state.ConsensusResult{
+ {
+ Seq: 1,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100004500,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+ },
+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ BlockHeight: 2,
+ CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(2) + 10000000000},
+ },
+ {
+ Seq: 0,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+ },
+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ BlockHeight: 0,
+ CoinbaseReward: map[string]uint64{},
+ },
+ },
+ },
+ wantChainData: &chainData{
+ bestBlockHeader: &types.BlockHeader{
+ Height: 0,
+ },
+ lastIrrBlockHeader: &types.BlockHeader{
+ Height: 0,
+ },
+ storedBlocks: []*types.Block{
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 0,
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+ },
+ }),
+ },
+ },
+ },
+ utxoViewPoint: &state.UtxoViewpoint{
+ Entries: map[bc.Hash]*storage.UtxoEntry{
+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+ },
+ },
+ consensusResults: []*state.ConsensusResult{
+ {
+ Seq: 0,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+ },
+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ BlockHeight: 0,
+ CoinbaseReward: map[string]uint64{},
+ },
+ },
+ },
+ },
+ {
+ desc: "rollback from height 2 to 1",
+ movStartHeight: 10,
+ RoundVoteBlockNums: 1200,
+ rollbackToTargetHeight: 1,
+ beforeChainData: &chainData{
+ bestBlockHeader: &types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ lastIrrBlockHeader: &types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ utxoViewPoint: &state.UtxoViewpoint{
+ Entries: map[bc.Hash]*storage.UtxoEntry{
+ testutil.MustDecodeHash("afee09925bea1695424450a91ad082a378f20534627fa5cb63f036846347ee08"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+ testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+ },
+ },
+ storedBlocks: []*types.Block{
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 0,
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 3000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 2500, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ },
+ consensusResults: []*state.ConsensusResult{
+ {
+ Seq: 1,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100004500,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+ },
+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ BlockHeight: 2,
+ CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 500},
+ },
+ {
+ Seq: 0,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+ },
+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ BlockHeight: 0,
+ CoinbaseReward: map[string]uint64{},
+ },
+ },
+ },
+ wantChainData: &chainData{
+ bestBlockHeader: &types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ lastIrrBlockHeader: &types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ storedBlocks: []*types.Block{
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 0,
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ },
+ utxoViewPoint: &state.UtxoViewpoint{
+ Entries: map[bc.Hash]*storage.UtxoEntry{
+ testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+ },
+ },
+ consensusResults: []*state.ConsensusResult{
+ {
+ Seq: 1,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100002000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+ },
+ BlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ BlockHeight: 1,
+ CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(1)},
+ },
+ {
+ Seq: 0,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+ },
+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ BlockHeight: 0,
+ CoinbaseReward: map[string]uint64{},
+ },
+ },
+ },
+ },
+ {
+ desc: "rollback from height 2 to 1, RoundVoteBlockNums is 2",
+ movStartHeight: 10,
+ RoundVoteBlockNums: 2,
+ rollbackToTargetHeight: 1,
+ beforeChainData: &chainData{
+ bestBlockHeader: &types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ lastIrrBlockHeader: &types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ utxoViewPoint: &state.UtxoViewpoint{
+ Entries: map[bc.Hash]*storage.UtxoEntry{
+ testutil.MustDecodeHash("afee09925bea1695424450a91ad082a378f20534627fa5cb63f036846347ee08"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+ testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+ },
+ },
+ storedBlocks: []*types.Block{
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 0,
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 3000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 2500, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ },
+ consensusResults: []*state.ConsensusResult{
+ {
+ Seq: 1,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100004500,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+ },
+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ BlockHeight: 2,
+ CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 500},
+ },
+ {
+ Seq: 0,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+ },
+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ BlockHeight: 0,
+ CoinbaseReward: map[string]uint64{},
+ },
+ },
+ },
+ wantChainData: &chainData{
+ bestBlockHeader: &types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ lastIrrBlockHeader: &types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ storedBlocks: []*types.Block{
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 0,
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 1000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 1000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 2000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ },
+ utxoViewPoint: &state.UtxoViewpoint{
+ Entries: map[bc.Hash]*storage.UtxoEntry{
+ testutil.MustDecodeHash("51f538be366172bed5359a016dce26b952024c9607caf6af609ad723982c2e06"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+ testutil.MustDecodeHash("e2370262a129b90174195a76c298d872a56af042eae17657e154bcc46d41b3ba"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+ },
+ },
+ consensusResults: []*state.ConsensusResult{
+ {
+ Seq: 1,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100002000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+ },
+ BlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ BlockHeight: 1,
+ CoinbaseReward: map[string]uint64{"0001": consensus.BlockSubsidy(1)},
+ },
+ {
+ Seq: 0,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 100002000,
+ },
+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ BlockHeight: 0,
+ CoinbaseReward: map[string]uint64{},
+ },
+ },
+ },
+ },
+ {
+ desc: "rollback from height 3 to 1, RoundVoteBlockNums is 2",
+ movStartHeight: 10,
+ RoundVoteBlockNums: 2,
+ rollbackToTargetHeight: 1,
+ beforeChainData: &chainData{
+ bestBlockHeader: &types.BlockHeader{
+ Height: 3,
+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ },
+ lastIrrBlockHeader: &types.BlockHeader{
+ Height: 3,
+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ },
+ utxoViewPoint: &state.UtxoViewpoint{
+ Entries: map[bc.Hash]*storage.UtxoEntry{
+ testutil.MustDecodeHash("5b4d53fbc2a489847f34dd0e0c085797fe7cf0a3a9a2f3231d11bdad16dea2be"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 3, Spent: true},
+ testutil.MustDecodeHash("4c2b719d10fc6b9c2a7c343491ddd8c0d6bd57f9c6680bfda557689c182cf685"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+ testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+ testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+ },
+ },
+ storedBlocks: []*types.Block{
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 0,
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+ },
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 300000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 250000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 3,
+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+ types.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(1)+consensus.BlockSubsidy(2)+50002000, []byte{0x51}),
+ },
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 400000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 160000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ },
+ consensusResults: []*state.ConsensusResult{
+ {
+ Seq: 2,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 609998000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("0c1cd1c0a6e6161f437c382cca21ce28921234ed7c4f252f7e4bbc9a523b74ac"),
+ BlockHeight: 3,
+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(3) + 240000000},
+ },
+ {
+ Seq: 1,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 449998000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ BlockHeight: 2,
+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 50002000},
+ },
+ {
+ Seq: 0,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ BlockHeight: 0,
+ CoinbaseReward: map[string]uint64{},
+ },
+ },
+ },
+ wantChainData: &chainData{
+ bestBlockHeader: &types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ lastIrrBlockHeader: &types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ storedBlocks: []*types.Block{
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 0,
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ },
+ utxoViewPoint: &state.UtxoViewpoint{
+ Entries: map[bc.Hash]*storage.UtxoEntry{
+ testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+ testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+ },
+ },
+ consensusResults: []*state.ConsensusResult{
+ {
+ Seq: 1,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000 + 100000000 - 2000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ BlockHeight: 1,
+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + 2000},
+ },
+ {
+ Seq: 0,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ BlockHeight: 0,
+ CoinbaseReward: map[string]uint64{},
+ },
+ },
+ },
+ },
+ {
+ desc: "rollback from height 3 to 2, RoundVoteBlockNums is 2",
+ movStartHeight: 10,
+ RoundVoteBlockNums: 2,
+ rollbackToTargetHeight: 2,
+ beforeChainData: &chainData{
+ bestBlockHeader: &types.BlockHeader{
+ Height: 3,
+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ },
+ lastIrrBlockHeader: &types.BlockHeader{
+ Height: 3,
+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ },
+ utxoViewPoint: &state.UtxoViewpoint{
+ Entries: map[bc.Hash]*storage.UtxoEntry{
+ testutil.MustDecodeHash("5b4d53fbc2a489847f34dd0e0c085797fe7cf0a3a9a2f3231d11bdad16dea2be"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 3, Spent: true},
+ testutil.MustDecodeHash("4c2b719d10fc6b9c2a7c343491ddd8c0d6bd57f9c6680bfda557689c182cf685"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+ testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+ testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+ },
+ },
+ storedBlocks: []*types.Block{
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 0,
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+ },
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 300000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 250000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 3,
+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+ types.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(1)+consensus.BlockSubsidy(2)+50002000, []byte{0x51}),
+ },
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 400000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 160000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ },
+ consensusResults: []*state.ConsensusResult{
+ {
+ Seq: 2,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 609998000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("0c1cd1c0a6e6161f437c382cca21ce28921234ed7c4f252f7e4bbc9a523b74ac"),
+ BlockHeight: 3,
+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(3) + 240000000},
+ },
+ {
+ Seq: 1,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 449998000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ BlockHeight: 2,
+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 50002000},
+ },
+ {
+ Seq: 0,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ BlockHeight: 0,
+ CoinbaseReward: map[string]uint64{},
+ },
+ },
+ },
+ wantChainData: &chainData{
+ bestBlockHeader: &types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ lastIrrBlockHeader: &types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ storedBlocks: []*types.Block{
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 0,
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+ },
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 300000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 250000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ },
+ utxoViewPoint: &state.UtxoViewpoint{
+ Entries: map[bc.Hash]*storage.UtxoEntry{
+ testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+ testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+ testutil.MustDecodeHash("4c2b719d10fc6b9c2a7c343491ddd8c0d6bd57f9c6680bfda557689c182cf685"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+ },
+ },
+ consensusResults: []*state.ConsensusResult{
+ {
+ Seq: 1,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000 + 100000000 - 2000 + 250000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ BlockHeight: 2,
+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 50002000},
+ },
+ {
+ Seq: 0,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ BlockHeight: 0,
+ CoinbaseReward: map[string]uint64{},
+ },
+ },
+ },
+ },
+ {
+ desc: "rollback from height 4 to 2, there is two chain , and round vote block nums is 2",
+ movStartHeight: 10,
+ RoundVoteBlockNums: 2,
+ rollbackToTargetHeight: 2,
+ beforeChainData: &chainData{
+ bestBlockHeader: &types.BlockHeader{
+ Height: 5,
+ Timestamp: uint64(1528945008),
+ PreviousBlockHash: testutil.MustDecodeHash("64a41230412f26a5c0a1734515d9e177bd3573be2ae1d55c4533509a7c9cce8e"),
+ },
+ lastIrrBlockHeader: &types.BlockHeader{
+ Height: 5,
+ Timestamp: uint64(1528945008),
+ PreviousBlockHash: testutil.MustDecodeHash("64a41230412f26a5c0a1734515d9e177bd3573be2ae1d55c4533509a7c9cce8e"),
+ },
+ utxoViewPoint: &state.UtxoViewpoint{
+ Entries: map[bc.Hash]*storage.UtxoEntry{
+ testutil.MustDecodeHash("3c07f3159d4e2a0527129d644a8fcd09ce26555e94c9c7f348464120ef463275"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 5, Spent: true},
+ testutil.MustDecodeHash("927144d2a391e17dc12184f5ae163b994984132ad72c34d854bb9009b68cd4cc"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 4, Spent: true},
+ testutil.MustDecodeHash("fa43f4ca43bcb0e94d43b52c56d1740dea1329b59a44f6ee045d70446881c514"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 3, Spent: true},
+ testutil.MustDecodeHash("f081ccd0c97ae34bc5580a0405d9b1ed0b0ed9e1410f1786b7112b348a412e3d"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 4, Spent: true},
+ testutil.MustDecodeHash("2704fa67c76e020b08ffa3f93a500acebcaf68b45ba43d8b3b08b68c5bb1eff1"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 3, Spent: true},
+ testutil.MustDecodeHash("4c2b719d10fc6b9c2a7c343491ddd8c0d6bd57f9c6680bfda557689c182cf685"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+ testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+ testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+ },
+ },
+ storedBlocks: []*types.Block{
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 0,
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+ },
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 300000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 250000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 3,
+ Timestamp: uint64(1528945000),
+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+ types.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(1)+consensus.BlockSubsidy(2)+50002000, []byte{0x51}),
+ },
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 440000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 160000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 4,
+ Timestamp: uint64(1528945005),
+ PreviousBlockHash: testutil.MustDecodeHash("bec3dd0d6fecb80a6f3a0373ec2ae676cc1ce72af83546f3d4672231c9b080e6"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+ },
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 500000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 160000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 3,
+ Timestamp: uint64(1528945001),
+ PreviousBlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+ types.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(1)+consensus.BlockSubsidy(2)+50002000, []byte{0x51}),
+ },
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 402000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 200000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 4,
+ Timestamp: uint64(1528945006),
+ PreviousBlockHash: testutil.MustDecodeHash("1d2d01a97d1239de51b4e7d0fb522f71771d2d4f9a0a559154519859cc44a230"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+ },
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 410000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 170000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 5,
+ Timestamp: uint64(1528945008),
+ PreviousBlockHash: testutil.MustDecodeHash("64a41230412f26a5c0a1734515d9e177bd3573be2ae1d55c4533509a7c9cce8e"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+ types.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(3)+consensus.BlockSubsidy(4)+520000000, []byte{0x51}),
+ },
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 400004000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 160004000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ },
+ consensusResults: []*state.ConsensusResult{
+ {
+ Seq: 3,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 980002000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("075ce54f7d4c1b524474265219be52238beec98138f0c0a4d21f1a6b0047914a"),
+ BlockHeight: 5,
+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(5) + 240000000},
+ },
+ {
+ Seq: 2,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 819998000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("64a41230412f26a5c0a1734515d9e177bd3573be2ae1d55c4533509a7c9cce8e"),
+ BlockHeight: 4,
+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(3) + consensus.BlockSubsidy(4) + 442000000},
+ },
+ {
+ Seq: 1,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 449998000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ BlockHeight: 2,
+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 50002000},
+ },
+ {
+ Seq: 0,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ BlockHeight: 0,
+ CoinbaseReward: map[string]uint64{},
+ },
+ },
+ },
+ wantChainData: &chainData{
+ bestBlockHeader: &types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ lastIrrBlockHeader: &types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ storedBlocks: []*types.Block{
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 0,
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 100000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 100000000, []byte{0, 1}, testutil.MustDecodeHexString("36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 1,
+ PreviousBlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 200000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 200000000-2000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ {
+ BlockHeader: types.BlockHeader{
+ Height: 2,
+ PreviousBlockHash: testutil.MustDecodeHash("52463075c66259098f2a1fa711288cf3b866d7c57b4a7a78cd22a1dcd69a0514"),
+ },
+ Transactions: []*types.Tx{
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+ Outputs: []*types.TxOutput{
+ types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51}),
+ },
+ }),
+ types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, bc.NewHash([32]byte{8}), *consensus.BTMAssetID, 300000000, 0, []byte{0, 1}),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(*consensus.BTMAssetID, 250000000, []byte{0, 1}, testutil.MustDecodeHexString("b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9")),
+ },
+ }),
+ },
+ },
+ },
+ consensusResults: []*state.ConsensusResult{
+ {
+ Seq: 1,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000 + 100000000 - 2000 + 250000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("699d3f59d4afe7eea85df31814628d7d34ace7f5e76d6c9ebf4c54482d2cd333"),
+ BlockHeight: 2,
+ CoinbaseReward: map[string]uint64{"51": consensus.BlockSubsidy(1) + consensus.BlockSubsidy(2) + 50002000},
+ },
+ {
+ Seq: 0,
+ NumOfVote: map[string]uint64{
+ "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9": 100000000,
+ "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67": 200000000,
+ },
+ BlockHash: testutil.MustDecodeHash("39dee75363127a2857f554d2ad2706eb876407a2e09fbe0338683ca4ad4c2f90"),
+ BlockHeight: 0,
+ CoinbaseReward: map[string]uint64{},
+ },
+ },
+ utxoViewPoint: &state.UtxoViewpoint{
+ Entries: map[bc.Hash]*storage.UtxoEntry{
+ testutil.MustDecodeHash("9fb6f213e3130810e755675707d0e9870c79a91c575638a580fae65568ca9e99"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 1, Spent: true},
+ testutil.MustDecodeHash("3d1617908e624a2042c23be4f671b261d5b8a2a61b8421ee6a702c6e071428a8"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 0, Spent: true},
+ testutil.MustDecodeHash("4c2b719d10fc6b9c2a7c343491ddd8c0d6bd57f9c6680bfda557689c182cf685"): &storage.UtxoEntry{Type: storage.VoteUTXOType, BlockHeight: 2, Spent: true},
+ },
+ },
+ },
+ },
+ }
+
+ for i, c := range cases {
+ consensus.ActiveNetParams.RoundVoteBlockNums = c.RoundVoteBlockNums
+
+ movDB := dbm.NewDB("mov_db", "leveldb", "mov_db")
+ movCore := mov.NewCoreWithDB(movDatabase.NewLevelDBMovStore(movDB), c.movStartHeight)
+
+ blockDB := dbm.NewDB("block_db", "leveldb", "block_db")
+ store := database.NewStore(blockDB)
+
+ mustSaveBlocks(c.beforeChainData.storedBlocks, store)
+
+ var mainChainBlockHeaders []*types.BlockHeader
+ for _, block := range c.beforeChainData.storedBlocks {
+ mainChainBlockHeaders = append(mainChainBlockHeaders, &block.BlockHeader)
+ }
+ if err := store.SaveChainStatus(c.beforeChainData.bestBlockHeader, c.beforeChainData.lastIrrBlockHeader, mainChainBlockHeaders, c.beforeChainData.utxoViewPoint, c.beforeChainData.consensusResults); err != nil {
+ t.Fatal(err)
+ }
+
+ chain, err := protocol.NewChain(store, nil, []protocol.Protocoler{movCore}, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := chain.Rollback(c.rollbackToTargetHeight); err != nil {
+ t.Fatal(err)
+ }
+
+ if !testutil.DeepEqual(chain.LastIrreversibleHeader(), c.wantChainData.lastIrrBlockHeader) {
+ t.Errorf("lastIrrBlockHeader is not right!")
+ }
+
+ if !testutil.DeepEqual(chain.BestBlockHeader(), c.wantChainData.bestBlockHeader) {
+ t.Errorf("wantBestBlockHeader is not right!")
+ }
+
+ gotConsensusResults := mustGetConsensusResultFromStore(store, chain)
+ if !testutil.DeepEqual(gotConsensusResults, c.wantChainData.consensusResults) {
+ t.Errorf("cases#%d(%s) wantBestConsensusResult is not right!", i, c.desc)
+ }
+
+ gotBlocks := mustGetBlocksFromStore(chain)
+ if !blocksEquals(gotBlocks, c.wantChainData.storedBlocks) {
+ t.Errorf("cases#%d(%s) the blocks is not same!", i, c.desc)
+ }
+
+ gotTransactions := getBcTransactions(gotBlocks)
+ gotUtxoViewPoint := state.NewUtxoViewpoint()
+ if err = store.GetTransactionsUtxo(gotUtxoViewPoint, gotTransactions); err != nil {
+ t.Fatal(err)
+ }
+
+ if !testutil.DeepEqual(gotUtxoViewPoint, c.wantChainData.utxoViewPoint) {
+ t.Fatal(err)
+ }
+
+ blockDB.Close()
+ os.RemoveAll("block_db")
+ movDB.Close()
+ os.RemoveAll("mov_db")
+
+ }
+}
+
+func blocksEquals(blocks1 []*types.Block, blocks2 []*types.Block) bool {
+ blockHashMap1 := make(map[string]interface{})
+ for _, block := range blocks1 {
+ hash := block.Hash()
+ blockHashMap1[hash.String()] = nil
+ }
+
+ blockHashMap2 := make(map[string]interface{})
+ for _, block := range blocks2 {
+ hash := block.Hash()
+ blockHashMap2[hash.String()] = nil
+ }
+ return testutil.DeepEqual(blockHashMap1, blockHashMap2)
+}
+
+func getBcTransactions(blocks []*types.Block) []*bc.Tx {
+ var txs []*bc.Tx
+ for _, block := range blocks {
+ for _, tx := range block.Transactions {
+ txs = append(txs, tx.Tx)
+ }
+ }
+ return txs
+}
+
+func mustSaveBlocks(blocks []*types.Block, store *database.Store) {
+ for _, block := range blocks {
+ status := bc.NewTransactionStatus()
+ for index := range block.Transactions {
+ if err := status.SetStatus(index, false); err != nil {
+ panic(err)
+ }
+ }
+ if err := store.SaveBlock(block, status); err != nil {
+ panic(err)
+ }
+ }
+}
+
+func mustGetBlocksFromStore(chain *protocol.Chain) []*types.Block {
+ var blocks []*types.Block
+ for height := int64(chain.BestBlockHeight()); height >= 0; height-- {
+ block, err := chain.GetBlockByHeight(uint64(height))
+ if err != nil {
+ panic(err)
+ }
+
+ blocks = append(blocks, block)
+ }
+ return blocks
+}
+
+func mustGetConsensusResultFromStore(store *database.Store, chain *protocol.Chain) []*state.ConsensusResult {
+ var consensusResults []*state.ConsensusResult
+ for seq := int64(state.CalcVoteSeq(chain.BestBlockHeight())); seq >= 0; seq-- {
+ consensusResult, err := store.GetConsensusResult(uint64(seq))
+ if err != nil {
+ panic(err)
+ }
+
+ consensusResults = append(consensusResults, consensusResult)
+ }
+ return consensusResults
+}
config.CommonConfig = config.DefaultConfig()
store := database.NewStore(testDB)
dispatcher := event.NewDispatcher()
- txPool := protocol.NewTxPool(store, dispatcher)
- chain, err := protocol.NewChain(store, txPool, dispatcher)
+ txPool := protocol.NewTxPool(store, nil, dispatcher)
+ chain, err := protocol.NewChain(store, txPool, nil, dispatcher)
return chain, store, txPool, err
}
store := database.NewStore(testDB)
walletStore := database.NewWalletStore(testDB)
dispatcher := event.NewDispatcher()
- txPool := protocol.NewTxPool(store, dispatcher)
+ txPool := protocol.NewTxPool(store, nil, dispatcher)
- chain, err := protocol.NewChain(store, txPool, dispatcher)
+ chain, err := protocol.NewChain(store, txPool, nil, dispatcher)
if err != nil {
t.Fatal(err)
}
store := database.NewStore(testDB)
dispatcher := event.NewDispatcher()
- txPool := protocol.NewTxPool(store, dispatcher)
- chain, err := protocol.NewChain(store, txPool, dispatcher)
+ txPool := protocol.NewTxPool(store, nil, dispatcher)
+ chain, err := protocol.NewChain(store, txPool, nil, dispatcher)
if err != nil {
t.Fatal(err)
}
store := database.NewStore(testDB)
dispatcher := event.NewDispatcher()
- txPool := protocol.NewTxPool(store, dispatcher)
+ txPool := protocol.NewTxPool(store, nil, dispatcher)
- chain, err := protocol.NewChain(store, txPool, dispatcher)
+ chain, err := protocol.NewChain(store, txPool, nil, dispatcher)
if err != nil {
t.Fatal(err)
}
if err != nil {
return err
}
+
+ if err = wallet.Run(); err != nil {
+ return err
+ }
+
ctx := &walletTestContext{
Wallet: wallet,
Chain: chain,
txFilter.SourceTxHash = txHash
}
if txHash, err := listTxsReq.GetFilterString("dest_tx_hash"); err == nil && txHash != "" {
- txFilter.DestTxHash = sql.NullString{txHash, true}
+ txFilter.DestTxHash = sql.NullString{String: txHash, Valid: true}
}
txQuery := s.db.Preload("Chain").Preload("Reqs").Preload("Reqs.Asset").Where(txFilter)
)
type Asset struct {
- ID uint64 `gorm:"primary_key;foreignkey:ID" json:"-"`
- AssetID string `json:"asset_id"`
- IssuanceProgram string `json:"-"`
- VMVersion uint64 `json:"-"`
- Definition string `json:"-"`
- CreatedAt common.Timestamp `json:"-"`
- UpdatedAt common.Timestamp `json:"-"`
+ ID uint64 `gorm:"primary_key;foreignkey:ID" json:"-"`
+ AssetID string `json:"asset_id"`
+ IssuanceProgram string `json:"-"`
+ VMVersion uint64 `json:"-"`
+ Definition string `json:"-"`
+ IsOpenFederationIssue bool `json:"_"`
+ CreatedAt common.Timestamp `json:"-"`
+ UpdatedAt common.Timestamp `json:"-"`
}
"github.com/jinzhu/gorm"
log "github.com/sirupsen/logrus"
+ vpCommon "github.com/bytom/vapor/common"
"github.com/bytom/vapor/consensus"
"github.com/bytom/vapor/errors"
"github.com/bytom/vapor/protocol/bc"
func (m *mainchainKeeper) Run() {
ticker := time.NewTicker(time.Duration(m.cfg.SyncSeconds) * time.Second)
+ defer ticker.Stop()
+
for ; true; <-ticker.C {
for {
isUpdate, err := m.syncBlock()
return err
}
+ if asset.IsOpenFederationIssue {
+ continue
+ }
+
req := &orm.CrossTransactionReq{
CrossTransactionID: crossTransactionID,
SourcePos: uint64(i),
return nil
}
-func (m *mainchainKeeper) isDepositTx(tx *types.Tx) bool {
+func (m *mainchainKeeper) isDepositTx(tx *types.Tx) (bool, error) {
for _, input := range tx.Inputs {
if bytes.Equal(input.ControlProgram(), m.federationProg) {
- return false
+ return false, nil
}
}
for _, output := range tx.Outputs {
- if bytes.Equal(output.OutputCommitment.ControlProgram, m.federationProg) {
- return true
+ if !bytes.Equal(output.OutputCommitment.ControlProgram, m.federationProg) {
+ continue
+ }
+
+ if isOFAsset, err := m.isOpenFederationAsset(output.AssetId); err != nil {
+ return false, err
+ } else if !isOFAsset {
+ return true, nil
}
}
- return false
+ return false, nil
}
-func (m *mainchainKeeper) isWithdrawalTx(tx *types.Tx) bool {
+func (m *mainchainKeeper) isWithdrawalTx(tx *types.Tx) (bool, error) {
for _, input := range tx.Inputs {
if !bytes.Equal(input.ControlProgram(), m.federationProg) {
- return false
+ return false, nil
+ }
+
+ if isOFAsset, err := m.isOpenFederationAsset(input.AssetAmount().AssetId); err != nil {
+ return false, err
+ } else if isOFAsset {
+ return false, nil
}
}
sourceTxHash := locateSideChainTx(tx.Outputs[len(tx.Outputs)-1])
- return sourceTxHash != ""
+ return sourceTxHash != "", nil
}
func locateSideChainTx(output *types.TxOutput) string {
return err
}
- if m.isDepositTx(tx) {
+ if isDeposit, err := m.isDepositTx(tx); err != nil {
+ return err
+ } else if isDeposit {
if err := m.processDepositTx(db, block, txStatus, i); err != nil {
return err
}
}
- if m.isWithdrawalTx(tx) {
+ if isWithdrawal, err := m.isWithdrawalTx(tx); err != nil {
+ return err
+ } else if isWithdrawal {
if err := m.processWithdrawalTx(db, block, i); err != nil {
return err
}
return nil
}
+func (m *mainchainKeeper) isOpenFederationAsset(assetID *btmBc.AssetID) (bool, error) {
+ asset, err := m.assetStore.GetByAssetID(assetID.String())
+ if err != nil {
+ return false, err
+ }
+
+ return asset.IsOpenFederationIssue, nil
+}
+
func (m *mainchainKeeper) processDepositTx(db *gorm.DB, block *types.Block, txStatus *bc.TransactionStatus, txIndex int) error {
tx := block.Transactions[txIndex]
var muxID btmBc.Hash
}
asset := &orm.Asset{
- AssetID: assetID.String(),
- IssuanceProgram: hex.EncodeToString(issuance.IssuanceProgram),
- VMVersion: issuance.VMVersion,
- Definition: string(issuance.AssetDefinition),
+ AssetID: assetID.String(),
+ IssuanceProgram: hex.EncodeToString(issuance.IssuanceProgram),
+ VMVersion: issuance.VMVersion,
+ Definition: string(issuance.AssetDefinition),
+ IsOpenFederationIssue: vpCommon.IsOpenFederationIssueAsset(issuance.AssetDefinition),
}
if err := m.db.Create(asset).Error; err != nil {
return err
}
+ if asset.IsOpenFederationIssue {
+ continue
+ }
+
prog := rawOutput.ControlProgram()
req := &orm.CrossTransactionReq{
CrossTransactionID: crossTransactionID,
return nil
}
-func (s *sidechainKeeper) isDepositTx(tx *types.Tx) bool {
+func (s *sidechainKeeper) isDepositTx(tx *types.Tx) (bool, error) {
for _, input := range tx.Inputs {
- if input.InputType() == types.CrossChainInputType {
- return true
+ if input.InputType() != types.CrossChainInputType {
+ continue
+ }
+
+ if isOFAsset, err := s.isOpenFederationAsset(input.AssetAmount().AssetId); err != nil {
+ return false, err
+ } else if !isOFAsset {
+ return true, nil
}
}
- return false
+ return false, nil
}
-func (s *sidechainKeeper) isWithdrawalTx(tx *types.Tx) bool {
+func (s *sidechainKeeper) isWithdrawalTx(tx *types.Tx) (bool, error) {
for _, output := range tx.Outputs {
- if output.OutputType() == types.CrossChainOutputType {
- return true
+ if output.OutputType() != types.CrossChainOutputType {
+ continue
+ }
+
+ if isOFAsset, err := s.isOpenFederationAsset(output.AssetAmount().AssetId); err != nil {
+ return false, err
+ } else if !isOFAsset {
+ return true, nil
}
}
- return false
+ return false, nil
}
func (s *sidechainKeeper) processBlock(db *gorm.DB, block *types.Block, txStatus *bc.TransactionStatus) error {
for i, tx := range block.Transactions {
- if s.isDepositTx(tx) {
+ if isDeposit, err := s.isDepositTx(tx); err != nil {
+ return err
+ } else if isDeposit {
if err := s.processDepositTx(db, block, i); err != nil {
return err
}
}
- if s.isWithdrawalTx(tx) {
+ if isWithdrawal, err := s.isWithdrawalTx(tx); err != nil {
+ return err
+ } else if isWithdrawal {
if err := s.processWithdrawalTx(db, block, txStatus, i); err != nil {
return err
}
return nil
}
+func (s *sidechainKeeper) isOpenFederationAsset(assetID *bc.AssetID) (bool, error) {
+ asset, err := s.assetStore.GetByAssetID(assetID.String())
+ if err != nil {
+ return false, err
+ }
+
+ return asset.IsOpenFederationIssue, nil
+}
+
func (s *sidechainKeeper) processWithdrawalTx(db *gorm.DB, block *types.Block, txStatus *bc.TransactionStatus, txIndex int) error {
tx := block.Transactions[txIndex]
var muxID bc.Hash
log "github.com/sirupsen/logrus"
"github.com/bytom/vapor/crypto/ed25519/chainkd"
-
"github.com/bytom/vapor/toolbar/common"
)
var (
// The full version string
- Version = "1.0.4"
+ Version = "1.1.0"
// GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse HEAD)"
GitCommit string
Status *UpdateStatus
return nil, err
}
+ return w, nil
+}
+
+// Run go to run some wallet recorvery and clean tx thread
+func (w *Wallet) Run() error {
var err error
w.TxMsgSub, err = w.EventDispatcher.Subscribe(protocol.TxMsgEvent{})
if err != nil {
- return nil, err
+ return err
}
go w.walletUpdater()
go w.delUnconfirmedTx()
go w.MemPoolTxQueryLoop()
- return w, nil
+
+ return nil
}
// MemPoolTxQueryLoop constantly pass a transaction accepted by mempool to the wallet.
return nil
}
+// Rollback wallet to target height
+func (w *Wallet) Rollback(targetHeight uint64) error {
+ for w.Status.WorkHeight > targetHeight {
+ block, err := w.Chain.GetBlockByHash(&w.Status.WorkHash)
+ if err != nil {
+ return err
+ }
+
+ if err = w.DetachBlock(block); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
func (w *Wallet) UpdateAccountAlias(accountID string, newAlias string) (err error) {
w.rw.Lock()
defer w.rw.Unlock()