OSDN Git Service

Mov (#518)
[bytom/vapor.git] / application / mov / database / mov_store.go
1 package database
2
3 import (
4         "encoding/binary"
5         "encoding/json"
6         "errors"
7         "math"
8
9         "github.com/bytom/vapor/application/mov/common"
10         dbm "github.com/bytom/vapor/database/leveldb"
11         "github.com/bytom/vapor/protocol/bc"
12         "github.com/bytom/vapor/protocol/bc/types"
13 )
14
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
22 }
23
24 const (
25         order byte = iota + 1
26         tradePair
27         matchStatus
28
29         fromAssetIDPos = 0
30         toAssetIDPos   = 1
31         assetIDLen     = 32
32         rateByteLen    = 8
33
34         tradePairsNum = 32
35         ordersNum     = 128
36 )
37
38 var (
39         movStore         = []byte("MOV:")
40         ordersPrefix     = append(movStore, order)
41         tradePairsPrefix = append(movStore, tradePair)
42         bestMatchStore   = append(movStore, matchStatus)
43 )
44
45 type orderData struct {
46         Utxo             *common.MovUtxo
47         RatioNumerator   int64
48         RatioDenominator int64
49 }
50
51 func calcOrderKey(fromAssetID, toAssetID *bc.AssetID, utxoHash *bc.Hash, rate float64) []byte {
52         buf := make([]byte, 8)
53         binary.BigEndian.PutUint64(buf, math.Float64bits(rate))
54         key := append(ordersPrefix, fromAssetID.Bytes()...)
55         key = append(key, toAssetID.Bytes()...)
56         key = append(key, buf...)
57         return append(key, utxoHash.Bytes()...)
58 }
59
60 func calcTradePairKey(fromAssetID, toAssetID *bc.AssetID) []byte {
61         key := append(tradePairsPrefix, fromAssetID.Bytes()...)
62         return append(key, toAssetID.Bytes()...)
63 }
64
65 func getAssetIDFromTradePairKey(key []byte, posIndex int) *bc.AssetID {
66         b := [32]byte{}
67         pos := len(tradePairsPrefix) + assetIDLen*posIndex
68         copy(b[:], key[pos:pos+assetIDLen])
69         assetID := bc.NewAssetID(b)
70         return &assetID
71 }
72
73 func getRateFromOrderKey(key []byte) float64 {
74         ratePos := len(ordersPrefix) + assetIDLen*2
75         return math.Float64frombits(binary.BigEndian.Uint64(key[ratePos : ratePos+rateByteLen]))
76 }
77
78 type tradePairData struct {
79         Count int
80 }
81
82 // LevelDBMovStore is the LevelDB implementation for MovStore
83 type LevelDBMovStore struct {
84         db dbm.DB
85 }
86
87 // NewLevelDBMovStore create a new LevelDBMovStore object
88 func NewLevelDBMovStore(db dbm.DB) *LevelDBMovStore {
89         return &LevelDBMovStore{db: db}
90 }
91
92 // GetMovDatabaseState return the current DB's image status
93 func (m *LevelDBMovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
94         if value := m.db.Get(bestMatchStore); value != nil {
95                 state := &common.MovDatabaseState{}
96                 return state, json.Unmarshal(value, state)
97         }
98
99         return nil, errors.New("don't find state of mov-database")
100 }
101
102 // InitDBState set the DB's image status
103 func (m *LevelDBMovStore) InitDBState(height uint64, hash *bc.Hash) error {
104         state := &common.MovDatabaseState{Height: height, Hash: hash}
105         value, err := json.Marshal(state)
106         if err != nil {
107                 return err
108         }
109
110         m.db.Set(bestMatchStore, value)
111         return nil
112 }
113
114 // ListOrders return n orders after the input order
115 func (m *LevelDBMovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
116         if orderAfter.FromAssetID == nil || orderAfter.ToAssetID == nil {
117                 return nil, errors.New("assetID is nil")
118         }
119
120         orderPrefix := append(ordersPrefix, orderAfter.FromAssetID.Bytes()...)
121         orderPrefix = append(orderPrefix, orderAfter.ToAssetID.Bytes()...)
122
123         var startKey []byte
124         if orderAfter.Rate() > 0 {
125                 startKey = calcOrderKey(orderAfter.FromAssetID, orderAfter.ToAssetID, orderAfter.UTXOHash(), orderAfter.Rate())
126         }
127
128         itr := m.db.IteratorPrefixWithStart(orderPrefix, startKey, false)
129         defer itr.Release()
130
131         var orders []*common.Order
132         for txNum := 0; txNum < ordersNum && itr.Next(); txNum++ {
133                 orderData := &orderData{}
134                 if err := json.Unmarshal(itr.Value(), orderData); err != nil {
135                         return nil, err
136                 }
137
138                 orders = append(orders, &common.Order{
139                         FromAssetID:      orderAfter.FromAssetID,
140                         ToAssetID:        orderAfter.ToAssetID,
141                         Utxo:             orderData.Utxo,
142                         RatioNumerator:   orderData.RatioNumerator,
143                         RatioDenominator: orderData.RatioDenominator,
144                 })
145         }
146         return orders, nil
147 }
148
149 // ListTradePairsWithStart return n trade pairs after the input trade pair
150 func (m *LevelDBMovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
151         var startKey []byte
152         if fromAssetIDAfter != nil && toAssetIDAfter != nil {
153                 startKey = calcTradePairKey(fromAssetIDAfter, toAssetIDAfter)
154         }
155
156         itr := m.db.IteratorPrefixWithStart(tradePairsPrefix, startKey, false)
157         defer itr.Release()
158
159         var tradePairs []*common.TradePair
160         for txNum := 0; txNum < tradePairsNum && itr.Next(); txNum++ {
161                 key := itr.Key()
162                 fromAssetID := getAssetIDFromTradePairKey(key, fromAssetIDPos)
163                 toAssetID := getAssetIDFromTradePairKey(key, toAssetIDPos)
164
165                 tradePairData := &tradePairData{}
166                 if err := json.Unmarshal(itr.Value(), tradePairData); err != nil {
167                         return nil, err
168                 }
169
170                 tradePairs = append(tradePairs, &common.TradePair{FromAssetID: fromAssetID, ToAssetID: toAssetID, Count: tradePairData.Count})
171         }
172         return tradePairs, nil
173 }
174
175 // ProcessOrders update the DB's image by add new orders, delete the used order
176 func (m *LevelDBMovStore) ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error {
177         if err := m.checkMovDatabaseState(blockHeader); err != nil {
178                 return err
179         }
180
181         batch := m.db.NewBatch()
182         tradePairsCnt := make(map[string]*common.TradePair)
183         if err := m.addOrders(batch, addOrders, tradePairsCnt); err != nil {
184                 return err
185         }
186
187         m.deleteOrders(batch, delOrders, tradePairsCnt)
188         if err := m.updateTradePairs(batch, tradePairsCnt); err != nil {
189                 return err
190         }
191
192         state, err := m.calcNextDatabaseState(blockHeader)
193         if err != nil {
194                 return err
195         }
196
197         if err := m.saveMovDatabaseState(batch, state); err != nil {
198                 return err
199         }
200
201         batch.Write()
202         return nil
203 }
204
205 func (m *LevelDBMovStore) addOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[string]*common.TradePair) error {
206         for _, order := range orders {
207                 orderData := &orderData{
208                         Utxo:             order.Utxo,
209                         RatioNumerator:   order.RatioNumerator,
210                         RatioDenominator: order.RatioDenominator,
211                 }
212                 data, err := json.Marshal(orderData)
213                 if err != nil {
214                         return err
215                 }
216
217                 key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate())
218                 batch.Set(key, data)
219
220                 tradePair := &common.TradePair{
221                         FromAssetID: order.FromAssetID,
222                         ToAssetID:   order.ToAssetID,
223                 }
224                 if _, ok := tradePairsCnt[tradePair.Key()]; !ok {
225                         tradePairsCnt[tradePair.Key()] = tradePair
226                 }
227                 tradePairsCnt[tradePair.Key()].Count++
228         }
229         return nil
230 }
231
232 func (m *LevelDBMovStore) calcNextDatabaseState(blockHeader *types.BlockHeader) (*common.MovDatabaseState, error) {
233         hash := blockHeader.Hash()
234         height := blockHeader.Height
235
236         state, err := m.GetMovDatabaseState()
237         if err != nil {
238                 return nil, err
239         }
240
241         if *state.Hash == hash {
242                 hash = blockHeader.PreviousBlockHash
243                 height = blockHeader.Height - 1
244         }
245
246         return &common.MovDatabaseState{Height: height, Hash: &hash}, nil
247 }
248
249 func (m *LevelDBMovStore) checkMovDatabaseState(header *types.BlockHeader) error {
250         state, err := m.GetMovDatabaseState()
251         if err != nil {
252                 return err
253         }
254
255         if (*state.Hash == header.PreviousBlockHash && (state.Height+1) == header.Height) || *state.Hash == header.Hash() {
256                 return nil
257         }
258
259         return errors.New("the status of the block is inconsistent with that of mov-database")
260 }
261
262 func (m *LevelDBMovStore) deleteOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[string]*common.TradePair) {
263         for _, order := range orders {
264                 key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate())
265                 batch.Delete(key)
266
267                 tradePair := &common.TradePair{
268                         FromAssetID: order.FromAssetID,
269                         ToAssetID:   order.ToAssetID,
270                 }
271                 if _, ok := tradePairsCnt[tradePair.Key()]; !ok {
272                         tradePairsCnt[tradePair.Key()] = tradePair
273                 }
274                 tradePairsCnt[tradePair.Key()].Count--
275         }
276 }
277
278 func (m *LevelDBMovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDatabaseState) error {
279         value, err := json.Marshal(state)
280         if err != nil {
281                 return err
282         }
283
284         batch.Set(bestMatchStore, value)
285         return nil
286 }
287
288 func (m *LevelDBMovStore) updateTradePairs(batch dbm.Batch, tradePairs map[string]*common.TradePair) error {
289         for _, v := range tradePairs {
290                 key := calcTradePairKey(v.FromAssetID, v.ToAssetID)
291                 tradePairData := &tradePairData{}
292                 if value := m.db.Get(key); value != nil {
293                         if err := json.Unmarshal(value, tradePairData); err != nil {
294                                 return err
295                         }
296                 }
297
298                 if tradePairData.Count += v.Count; tradePairData.Count < 0 {
299                         return errors.New("negative trade count")
300                 }
301
302                 if tradePairData.Count > 0 {
303                         value, err := json.Marshal(tradePairData)
304                         if err != nil {
305                                 return err
306                         }
307
308                         batch.Set(key, value)
309                 } else {
310                         batch.Delete(key)
311                 }
312         }
313         return nil
314 }