9 "github.com/vapor/application/mov/common"
10 dbm "github.com/vapor/database/leveldb"
11 "github.com/vapor/protocol/bc"
12 "github.com/vapor/protocol/bc/types"
15 // MovStore is the interface for mov's persistent storage
16 type MovStore interface {
17 GetMovDatabaseState() (*common.MovDatabaseState, error)
18 InitDBState(height uint64, hash *bc.Hash) error
19 ListOrders(orderAfter *common.Order) ([]*common.Order, error)
20 ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error)
21 ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error
39 movStore = []byte("MOV:")
40 ordersPrefix = append(movStore, order)
41 tradePairsPrefix = append(movStore, tradePair)
42 bestMatchStore = append(movStore, matchStatus)
45 func calcOrderKey(fromAssetID, toAssetID *bc.AssetID, utxoHash *bc.Hash, rate float64) []byte {
46 buf := make([]byte, 8)
47 binary.BigEndian.PutUint64(buf, math.Float64bits(rate))
48 key := append(ordersPrefix, fromAssetID.Bytes()...)
49 key = append(key, toAssetID.Bytes()...)
50 key = append(key, buf...)
51 return append(key, utxoHash.Bytes()...)
54 func calcTradePairKey(fromAssetID, toAssetID *bc.AssetID) []byte {
55 key := append(tradePairsPrefix, fromAssetID.Bytes()...)
56 return append(key, toAssetID.Bytes()...)
59 func getAssetIDFromTradePairKey(key []byte, posIndex int) *bc.AssetID {
61 pos := len(tradePairsPrefix) + assetIDLen*posIndex
62 copy(b[:], key[pos:pos+assetIDLen])
63 assetID := bc.NewAssetID(b)
67 func getRateFromOrderKey(key []byte) float64 {
68 ratePos := len(ordersPrefix) + assetIDLen*2
69 return math.Float64frombits(binary.BigEndian.Uint64(key[ratePos : ratePos+rateByteLen]))
72 type tradePairData struct {
76 // LevelDBMovStore is the LevelDB implementation for MovStore
77 type LevelDBMovStore struct {
81 // NewLevelDBMovStore create a new LevelDBMovStore object
82 func NewLevelDBMovStore(db dbm.DB) *LevelDBMovStore {
83 return &LevelDBMovStore{db: db}
86 // GetMovDatabaseState return the current DB's image status
87 func (m *LevelDBMovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
88 if value := m.db.Get(bestMatchStore); value != nil {
89 state := &common.MovDatabaseState{}
90 return state, json.Unmarshal(value, state)
93 return nil, errors.New("don't find state of mov-database")
96 // InitDBState set the DB's image status
97 func (m *LevelDBMovStore) InitDBState(height uint64, hash *bc.Hash) error {
98 state := &common.MovDatabaseState{Height: height, Hash: hash}
99 value, err := json.Marshal(state)
104 m.db.Set(bestMatchStore, value)
108 // ListOrders return n orders after the input order
109 func (m *LevelDBMovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
110 if orderAfter.FromAssetID == nil || orderAfter.ToAssetID == nil {
111 return nil, errors.New("assetID is nil")
114 orderPrefix := append(ordersPrefix, orderAfter.FromAssetID.Bytes()...)
115 orderPrefix = append(orderPrefix, orderAfter.ToAssetID.Bytes()...)
119 if orderAfter.Rate > 0 {
120 startKey = calcOrderKey(orderAfter.FromAssetID, orderAfter.ToAssetID, orderAfter.UTXOHash(), orderAfter.Rate)
123 itr := m.db.IteratorPrefixWithStart(orderPrefix, startKey, false)
126 var orders []*common.Order
127 for txNum := 0; txNum < ordersNum && itr.Next(); txNum++ {
128 movUtxo := &common.MovUtxo{}
129 if err := json.Unmarshal(itr.Value(), movUtxo); err != nil {
133 orders = append(orders, &common.Order{
134 FromAssetID: orderAfter.FromAssetID,
135 ToAssetID: orderAfter.ToAssetID,
136 Rate: getRateFromOrderKey(itr.Key()),
143 // ListTradePairsWithStart return n trade pairs after the input trade pair
144 func (m *LevelDBMovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
146 if fromAssetIDAfter != nil && toAssetIDAfter != nil {
147 startKey = calcTradePairKey(fromAssetIDAfter, toAssetIDAfter)
150 itr := m.db.IteratorPrefixWithStart(tradePairsPrefix, startKey, false)
153 var tradePairs []*common.TradePair
154 for txNum := 0; txNum < tradePairsNum && itr.Next(); txNum++ {
156 fromAssetID := getAssetIDFromTradePairKey(key, fromAssetIDPos)
157 toAssetID := getAssetIDFromTradePairKey(key, toAssetIDPos)
159 tradePairData := &tradePairData{}
160 if err := json.Unmarshal(itr.Value(), tradePairData); err != nil {
164 tradePairs = append(tradePairs, &common.TradePair{FromAssetID: fromAssetID, ToAssetID: toAssetID, Count: tradePairData.Count})
166 return tradePairs, nil
169 // ProcessOrders update the DB's image by add new orders, delete the used order
170 func (m *LevelDBMovStore) ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error {
171 if err := m.checkMovDatabaseState(blockHeader); err != nil {
175 batch := m.db.NewBatch()
176 tradePairsCnt := make(map[string]*common.TradePair)
177 if err := m.addOrders(batch, addOrders, tradePairsCnt); err != nil {
181 m.deleteOrders(batch, delOrders, tradePairsCnt)
182 if err := m.updateTradePairs(batch, tradePairsCnt); err != nil {
186 state, err := m.calcNextDatabaseState(blockHeader)
191 if err := m.saveMovDatabaseState(batch, state); err != nil {
199 func (m *LevelDBMovStore) addOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[string]*common.TradePair) error {
200 for _, order := range orders {
201 data, err := json.Marshal(order.Utxo)
206 key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate)
209 tradePair := &common.TradePair{
210 FromAssetID: order.FromAssetID,
211 ToAssetID: order.ToAssetID,
213 if _, ok := tradePairsCnt[tradePair.Key()]; !ok {
214 tradePairsCnt[tradePair.Key()] = tradePair
216 tradePairsCnt[tradePair.Key()].Count++
221 func (m *LevelDBMovStore) calcNextDatabaseState(blockHeader *types.BlockHeader) (*common.MovDatabaseState, error) {
222 hash := blockHeader.Hash()
223 height := blockHeader.Height
225 state, err := m.GetMovDatabaseState()
230 if *state.Hash == hash {
231 hash = blockHeader.PreviousBlockHash
232 height = blockHeader.Height - 1
235 return &common.MovDatabaseState{Height: height, Hash: &hash}, nil
238 func (m *LevelDBMovStore) checkMovDatabaseState(header *types.BlockHeader) error {
239 state, err := m.GetMovDatabaseState()
244 if (*state.Hash == header.PreviousBlockHash && (state.Height+1) == header.Height) || *state.Hash == header.Hash() {
248 return errors.New("the status of the block is inconsistent with that of mov-database")
251 func (m *LevelDBMovStore) deleteOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[string]*common.TradePair) {
252 for _, order := range orders {
253 key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate)
256 tradePair := &common.TradePair{
257 FromAssetID: order.FromAssetID,
258 ToAssetID: order.ToAssetID,
260 if _, ok := tradePairsCnt[tradePair.Key()]; !ok {
261 tradePairsCnt[tradePair.Key()] = tradePair
263 tradePairsCnt[tradePair.Key()].Count--
267 func (m *LevelDBMovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDatabaseState) error {
268 value, err := json.Marshal(state)
273 batch.Set(bestMatchStore, value)
277 func (m *LevelDBMovStore) updateTradePairs(batch dbm.Batch, tradePairs map[string]*common.TradePair) error {
278 for _, v := range tradePairs {
279 key := calcTradePairKey(v.FromAssetID, v.ToAssetID)
280 tradePairData := &tradePairData{}
281 if value := m.db.Get(key); value != nil {
282 if err := json.Unmarshal(value, tradePairData); err != nil {
287 if tradePairData.Count += v.Count; tradePairData.Count < 0 {
288 return errors.New("negative trade count")
291 if tradePairData.Count > 0 {
292 value, err := json.Marshal(tradePairData)
297 batch.Set(key, value)