OSDN Git Service

Dex database logic implementation (#404)
[bytom/vapor.git] / application / mov / database / mov_store.go
diff --git a/application/mov/database/mov_store.go b/application/mov/database/mov_store.go
new file mode 100644 (file)
index 0000000..4aa831a
--- /dev/null
@@ -0,0 +1,267 @@
+package database
+
+import (
+       "encoding/binary"
+       "encoding/json"
+       "errors"
+       "math"
+
+       "github.com/vapor/application/mov/common"
+       dbm "github.com/vapor/database/leveldb"
+       "github.com/vapor/protocol/bc"
+       "github.com/vapor/protocol/bc/types"
+)
+
+const (
+       order byte = iota
+       tradePair
+       matchStatus
+
+       tradePairsNum = 1024
+       ordersNum     = 10240
+       assetIDLen    = 32
+       rateByteLen   = 8
+)
+
+var (
+       movStore         = []byte("MOV:")
+       ordersPrefix     = append(movStore, order)
+       tradePairsPrefix = append(movStore, tradePair)
+       bestMatchStore   = append(movStore, matchStatus)
+)
+
+func calcOrderKey(fromAssetID, toAssetID *bc.AssetID, utxoHash *bc.Hash, rate float64) []byte {
+       buf := make([]byte, 8)
+       binary.BigEndian.PutUint64(buf, math.Float64bits(rate))
+       key := append(ordersPrefix, fromAssetID.Bytes()...)
+       key = append(key, toAssetID.Bytes()...)
+       key = append(key, buf...)
+       return append(key, utxoHash.Bytes()...)
+}
+
+func calcTradePairKey(fromAssetID, toAssetID *bc.AssetID) []byte {
+       key := append(tradePairsPrefix, fromAssetID.Bytes()...)
+       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 {
+       b := [32]byte{}
+       pos := len(prefix) + 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
+       return math.Float64frombits(binary.BigEndian.Uint64(key[ratePos : ratePos+rateByteLen]))
+}
+
+type tradePairData struct {
+       Count int
+}
+
+type MovStore 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
+               }
+
+               db.Set(bestMatchStore, value)
+       }
+       return &MovStore{db: db}, nil
+}
+
+func (m *MovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
+       if orderAfter.FromAssetID == nil || orderAfter.ToAssetID == nil {
+               return nil, errors.New("assetID is nil")
+       }
+
+       orderPrefix := append(ordersPrefix, orderAfter.FromAssetID.Bytes()...)
+       orderPrefix = append(orderPrefix, orderAfter.ToAssetID.Bytes()...)
+
+       var startKey []byte
+       if orderAfter.Rate > 0 {
+               startKey = calcOrderKey(orderAfter.FromAssetID, orderAfter.ToAssetID, calcUTXOHash(orderAfter), orderAfter.Rate)
+       }
+
+       itr := m.db.IteratorPrefixWithStart(orderPrefix, startKey, false)
+       defer itr.Release()
+
+       var orders []*common.Order
+       for txNum := 0; txNum < ordersNum && itr.Next(); txNum++ {
+               movUtxo := &common.MovUtxo{}
+               if err := json.Unmarshal(itr.Value(), movUtxo); 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)
+       }
+       return orders, nil
+}
+
+func (m *MovStore) ProcessOrders(addOrders []*common.Order, delOreders []*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)
+       if err := m.addOrders(batch, addOrders, tradePairsCnt); err != nil {
+               return err
+       }
+
+       m.deleteOrders(batch, delOreders, 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 {
+               return err
+       }
+
+       batch.Write()
+       return nil
+}
+
+func (m *MovStore) addOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[common.TradePair]int) error {
+       for _, order := range orders {
+               data, err := json.Marshal(order.Utxo)
+               if err != nil {
+                       return err
+               }
+
+               key := calcOrderKey(order.FromAssetID, order.ToAssetID, calcUTXOHash(order), order.Rate)
+               batch.Set(key, data)
+
+               tradePair := common.TradePair{
+                       FromAssetID: order.FromAssetID,
+                       ToAssetID:   order.ToAssetID,
+               }
+               tradePairsCnt[tradePair] += 1
+       }
+       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)
+
+               tradePair := common.TradePair{
+                       FromAssetID: order.FromAssetID,
+                       ToAssetID:   order.ToAssetID,
+               }
+               tradePairsCnt[tradePair] -= 1
+       }
+}
+
+func (m *MovStore) 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")
+}
+
+func (m *MovStore) 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, tradePairsPrefix, 0)
+               toAssetID := getAssetIDFromTradePairKey(key, tradePairsPrefix, 1)
+
+               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
+}
+
+func (m *MovStore) updateTradePairs(batch dbm.Batch, tradePairs map[common.TradePair]int) error {
+       for k, v := range tradePairs {
+               key := calcTradePairKey(k.FromAssetID, k.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 > 0 {
+                       value, err := json.Marshal(tradePairData)
+                       if err != nil {
+                               return err
+                       }
+
+                       batch.Set(key, value)
+               } else {
+                       batch.Delete(key)
+               }
+       }
+       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
+}