OSDN Git Service

Mov (#518)
[bytom/vapor.git] / application / mov / database / mov_store.go
index 252886a..20d3101 100644 (file)
@@ -12,15 +12,27 @@ import (
        "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 (
@@ -30,6 +42,12 @@ 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))
@@ -44,27 +62,16 @@ func calcTradePairKey(fromAssetID, toAssetID *bc.AssetID) []byte {
        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]))
 }
 
@@ -72,24 +79,40 @@ type tradePairData struct {
        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")
        }
@@ -98,8 +121,8 @@ func (m *MovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error)
        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)
@@ -107,41 +130,71 @@ func (m *MovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error)
 
        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
        }
 
@@ -149,85 +202,103 @@ func (m *MovStore) ProcessOrders(addOrders []*common.Order, delOreders []*common
        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 {
@@ -241,27 +312,3 @@ func (m *MovStore) updateTradePairs(batch dbm.Batch, tradePairs map[common.Trade
        }
        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
-}