OSDN Git Service

Dex database logic implementation (#404)
[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/vapor/application/mov/common"
10         dbm "github.com/vapor/database/leveldb"
11         "github.com/vapor/protocol/bc"
12         "github.com/vapor/protocol/bc/types"
13 )
14
15 const (
16         order byte = iota
17         tradePair
18         matchStatus
19
20         tradePairsNum = 1024
21         ordersNum     = 10240
22         assetIDLen    = 32
23         rateByteLen   = 8
24 )
25
26 var (
27         movStore         = []byte("MOV:")
28         ordersPrefix     = append(movStore, order)
29         tradePairsPrefix = append(movStore, tradePair)
30         bestMatchStore   = append(movStore, matchStatus)
31 )
32
33 func calcOrderKey(fromAssetID, toAssetID *bc.AssetID, utxoHash *bc.Hash, rate float64) []byte {
34         buf := make([]byte, 8)
35         binary.BigEndian.PutUint64(buf, math.Float64bits(rate))
36         key := append(ordersPrefix, fromAssetID.Bytes()...)
37         key = append(key, toAssetID.Bytes()...)
38         key = append(key, buf...)
39         return append(key, utxoHash.Bytes()...)
40 }
41
42 func calcTradePairKey(fromAssetID, toAssetID *bc.AssetID) []byte {
43         key := append(tradePairsPrefix, fromAssetID.Bytes()...)
44         return append(key, toAssetID.Bytes()...)
45 }
46
47 func calcUTXOHash(order *common.Order) *bc.Hash {
48         prog := &bc.Program{VmVersion: 1, Code: order.Utxo.ControlProgram}
49         src := &bc.ValueSource{
50                 Ref:      order.Utxo.SourceID,
51                 Value:    &bc.AssetAmount{AssetId: order.FromAssetID, Amount: order.Utxo.Amount},
52                 Position: order.Utxo.SourcePos,
53         }
54         hash := bc.EntryID(bc.NewIntraChainOutput(src, prog, 0))
55         return &hash
56 }
57
58 func getAssetIDFromTradePairKey(key []byte, prefix []byte, posIndex int) *bc.AssetID {
59         b := [32]byte{}
60         pos := len(prefix) + assetIDLen*posIndex
61         copy(b[:], key[pos:pos+assetIDLen])
62         assetID := bc.NewAssetID(b)
63         return &assetID
64 }
65
66 func getRateFromOrderKey(key []byte, prefix []byte) float64 {
67         ratePos := len(prefix) + assetIDLen*2
68         return math.Float64frombits(binary.BigEndian.Uint64(key[ratePos : ratePos+rateByteLen]))
69 }
70
71 type tradePairData struct {
72         Count int
73 }
74
75 type MovStore struct {
76         db dbm.DB
77 }
78
79 func NewMovStore(db dbm.DB, height uint64, hash *bc.Hash) (*MovStore, error) {
80         if value := db.Get(bestMatchStore); value == nil {
81                 state := &common.MovDatabaseState{Height: height, Hash: hash}
82                 value, err := json.Marshal(state)
83                 if err != nil {
84                         return nil, err
85                 }
86
87                 db.Set(bestMatchStore, value)
88         }
89         return &MovStore{db: db}, nil
90 }
91
92 func (m *MovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
93         if orderAfter.FromAssetID == nil || orderAfter.ToAssetID == nil {
94                 return nil, errors.New("assetID is nil")
95         }
96
97         orderPrefix := append(ordersPrefix, orderAfter.FromAssetID.Bytes()...)
98         orderPrefix = append(orderPrefix, orderAfter.ToAssetID.Bytes()...)
99
100         var startKey []byte
101         if orderAfter.Rate > 0 {
102                 startKey = calcOrderKey(orderAfter.FromAssetID, orderAfter.ToAssetID, calcUTXOHash(orderAfter), orderAfter.Rate)
103         }
104
105         itr := m.db.IteratorPrefixWithStart(orderPrefix, startKey, false)
106         defer itr.Release()
107
108         var orders []*common.Order
109         for txNum := 0; txNum < ordersNum && itr.Next(); txNum++ {
110                 movUtxo := &common.MovUtxo{}
111                 if err := json.Unmarshal(itr.Value(), movUtxo); err != nil {
112                         return nil, err
113                 }
114
115                 order := &common.Order{
116                         FromAssetID: orderAfter.FromAssetID,
117                         ToAssetID:   orderAfter.ToAssetID,
118                         Rate:        getRateFromOrderKey(itr.Key(), ordersPrefix),
119                         Utxo:        movUtxo,
120                 }
121                 orders = append(orders, order)
122         }
123         return orders, nil
124 }
125
126 func (m *MovStore) ProcessOrders(addOrders []*common.Order, delOreders []*common.Order, blockHeader *types.BlockHeader) error {
127         if err := m.checkMovDatabaseState(blockHeader); err != nil {
128                 return err
129         }
130
131         batch := m.db.NewBatch()
132         tradePairsCnt := make(map[common.TradePair]int)
133         if err := m.addOrders(batch, addOrders, tradePairsCnt); err != nil {
134                 return err
135         }
136
137         m.deleteOrders(batch, delOreders, tradePairsCnt)
138
139         if err := m.updateTradePairs(batch, tradePairsCnt); err != nil {
140                 return err
141         }
142
143         hash := blockHeader.Hash()
144         if err := m.saveMovDatabaseState(batch, &common.MovDatabaseState{Height: blockHeader.Height, Hash: &hash}); err != nil {
145                 return err
146         }
147
148         batch.Write()
149         return nil
150 }
151
152 func (m *MovStore) addOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[common.TradePair]int) error {
153         for _, order := range orders {
154                 data, err := json.Marshal(order.Utxo)
155                 if err != nil {
156                         return err
157                 }
158
159                 key := calcOrderKey(order.FromAssetID, order.ToAssetID, calcUTXOHash(order), order.Rate)
160                 batch.Set(key, data)
161
162                 tradePair := common.TradePair{
163                         FromAssetID: order.FromAssetID,
164                         ToAssetID:   order.ToAssetID,
165                 }
166                 tradePairsCnt[tradePair] += 1
167         }
168         return nil
169 }
170
171 func (m *MovStore) deleteOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[common.TradePair]int) {
172         for _, order := range orders {
173                 key := calcOrderKey(order.FromAssetID, order.ToAssetID, calcUTXOHash(order), order.Rate)
174                 batch.Delete(key)
175
176                 tradePair := common.TradePair{
177                         FromAssetID: order.FromAssetID,
178                         ToAssetID:   order.ToAssetID,
179                 }
180                 tradePairsCnt[tradePair] -= 1
181         }
182 }
183
184 func (m *MovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
185         if value := m.db.Get(bestMatchStore); value != nil {
186                 state := &common.MovDatabaseState{}
187                 return state, json.Unmarshal(value, state)
188         }
189
190         return nil, errors.New("don't find state of mov-database")
191 }
192
193 func (m *MovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
194         var startKey []byte
195         if fromAssetIDAfter != nil && toAssetIDAfter != nil {
196                 startKey = calcTradePairKey(fromAssetIDAfter, toAssetIDAfter)
197         }
198
199         itr := m.db.IteratorPrefixWithStart(tradePairsPrefix, startKey, false)
200         defer itr.Release()
201
202         var tradePairs []*common.TradePair
203         for txNum := 0; txNum < tradePairsNum && itr.Next(); txNum++ {
204                 key := itr.Key()
205                 fromAssetID := getAssetIDFromTradePairKey(key, tradePairsPrefix, 0)
206                 toAssetID := getAssetIDFromTradePairKey(key, tradePairsPrefix, 1)
207
208                 tradePairData := &tradePairData{}
209                 if err := json.Unmarshal(itr.Value(), tradePairData); err != nil {
210                         return nil, err
211                 }
212
213                 tradePairs = append(tradePairs, &common.TradePair{FromAssetID: fromAssetID, ToAssetID: toAssetID, Count: tradePairData.Count})
214         }
215         return tradePairs, nil
216 }
217
218 func (m *MovStore) updateTradePairs(batch dbm.Batch, tradePairs map[common.TradePair]int) error {
219         for k, v := range tradePairs {
220                 key := calcTradePairKey(k.FromAssetID, k.ToAssetID)
221                 tradePairData := &tradePairData{}
222                 if value := m.db.Get(key); value != nil {
223                         if err := json.Unmarshal(value, tradePairData); err != nil {
224                                 return err
225                         }
226                 } else if v < 0 {
227                         return errors.New("don't find trade pair")
228                 }
229
230                 tradePairData.Count += v
231                 if tradePairData.Count > 0 {
232                         value, err := json.Marshal(tradePairData)
233                         if err != nil {
234                                 return err
235                         }
236
237                         batch.Set(key, value)
238                 } else {
239                         batch.Delete(key)
240                 }
241         }
242         return nil
243 }
244
245 func (m *MovStore) checkMovDatabaseState(header *types.BlockHeader) error {
246         state, err := m.GetMovDatabaseState()
247         if err != nil {
248                 return err
249         }
250
251         blockHash := header.Hash()
252         if (state.Hash.String() == header.PreviousBlockHash.String() && (state.Height+1) == header.Height) || state.Hash.String() == blockHash.String() {
253                 return nil
254         }
255
256         return errors.New("the status of the block is inconsistent with that of mov-database")
257 }
258
259 func (m *MovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDatabaseState) error {
260         value, err := json.Marshal(state)
261         if err != nil {
262                 return err
263         }
264
265         batch.Set(bestMatchStore, value)
266         return nil
267 }