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"
15 // ErrNotInitDBState represent the database state of mov store is not initialized
16 var ErrNotInitDBState = errors.New("database state of mov store is not initialized")
18 // MovStore is the interface for mov's persistent storage
19 type MovStore interface {
20 GetMovDatabaseState() (*common.MovDatabaseState, error)
21 InitDBState(height uint64, hash *bc.Hash) error
22 ListOrders(orderAfter *common.Order) ([]*common.Order, error)
23 ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error)
24 ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error
42 movStore = []byte("MOV:")
43 ordersPrefix = append(movStore, order)
44 tradePairsPrefix = append(movStore, tradePair)
45 bestMatchStore = append(movStore, matchStatus)
48 type orderData struct {
51 RatioDenominator int64
54 func calcOrderKey(fromAssetID, toAssetID *bc.AssetID, utxoHash *bc.Hash, rate float64) []byte {
55 buf := make([]byte, 8)
56 binary.BigEndian.PutUint64(buf, math.Float64bits(rate))
57 key := append(ordersPrefix, fromAssetID.Bytes()...)
58 key = append(key, toAssetID.Bytes()...)
59 key = append(key, buf...)
60 return append(key, utxoHash.Bytes()...)
63 func calcTradePairKey(fromAssetID, toAssetID *bc.AssetID) []byte {
64 key := append(tradePairsPrefix, fromAssetID.Bytes()...)
65 return append(key, toAssetID.Bytes()...)
68 func getAssetIDFromTradePairKey(key []byte, posIndex int) *bc.AssetID {
70 pos := len(tradePairsPrefix) + assetIDLen*posIndex
71 copy(b[:], key[pos:pos+assetIDLen])
72 assetID := bc.NewAssetID(b)
76 func getRateFromOrderKey(key []byte) float64 {
77 ratePos := len(ordersPrefix) + assetIDLen*2
78 return math.Float64frombits(binary.BigEndian.Uint64(key[ratePos : ratePos+rateByteLen]))
81 type tradePairData struct {
85 // LevelDBMovStore is the LevelDB implementation for MovStore
86 type LevelDBMovStore struct {
90 // NewLevelDBMovStore create a new LevelDBMovStore object
91 func NewLevelDBMovStore(db dbm.DB) *LevelDBMovStore {
92 return &LevelDBMovStore{db: db}
95 // GetMovDatabaseState return the current DB's image status
96 func (m *LevelDBMovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
97 if value := m.db.Get(bestMatchStore); value != nil {
98 state := &common.MovDatabaseState{}
99 return state, json.Unmarshal(value, state)
102 return nil, ErrNotInitDBState
105 // InitDBState set the DB's image status
106 func (m *LevelDBMovStore) InitDBState(height uint64, hash *bc.Hash) error {
107 state := &common.MovDatabaseState{Height: height, Hash: hash}
108 value, err := json.Marshal(state)
113 m.db.Set(bestMatchStore, value)
117 // ListOrders return n orders after the input order
118 func (m *LevelDBMovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
119 if orderAfter.FromAssetID == nil || orderAfter.ToAssetID == nil {
120 return nil, errors.New("assetID is nil")
123 orderPrefix := append(ordersPrefix, orderAfter.FromAssetID.Bytes()...)
124 orderPrefix = append(orderPrefix, orderAfter.ToAssetID.Bytes()...)
127 if orderAfter.Rate() > 0 {
128 startKey = calcOrderKey(orderAfter.FromAssetID, orderAfter.ToAssetID, orderAfter.UTXOHash(), orderAfter.Rate())
131 itr := m.db.IteratorPrefixWithStart(orderPrefix, startKey, false)
134 var orders []*common.Order
135 for txNum := 0; txNum < ordersNum && itr.Next(); txNum++ {
136 orderData := &orderData{}
137 if err := json.Unmarshal(itr.Value(), orderData); err != nil {
141 orders = append(orders, &common.Order{
142 FromAssetID: orderAfter.FromAssetID,
143 ToAssetID: orderAfter.ToAssetID,
144 Utxo: orderData.Utxo,
145 RatioNumerator: orderData.RatioNumerator,
146 RatioDenominator: orderData.RatioDenominator,
152 // ListTradePairsWithStart return n trade pairs after the input trade pair
153 func (m *LevelDBMovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
155 if fromAssetIDAfter != nil && toAssetIDAfter != nil {
156 startKey = calcTradePairKey(fromAssetIDAfter, toAssetIDAfter)
159 itr := m.db.IteratorPrefixWithStart(tradePairsPrefix, startKey, false)
162 var tradePairs []*common.TradePair
163 for txNum := 0; txNum < tradePairsNum && itr.Next(); txNum++ {
165 fromAssetID := getAssetIDFromTradePairKey(key, fromAssetIDPos)
166 toAssetID := getAssetIDFromTradePairKey(key, toAssetIDPos)
168 tradePairData := &tradePairData{}
169 if err := json.Unmarshal(itr.Value(), tradePairData); err != nil {
173 tradePairs = append(tradePairs, &common.TradePair{FromAssetID: fromAssetID, ToAssetID: toAssetID, Count: tradePairData.Count})
175 return tradePairs, nil
178 // ProcessOrders update the DB's image by add new orders, delete the used order
179 func (m *LevelDBMovStore) ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error {
180 if err := m.checkMovDatabaseState(blockHeader); err != nil {
184 batch := m.db.NewBatch()
185 tradePairsCnt := make(map[string]*common.TradePair)
186 if err := m.addOrders(batch, addOrders, tradePairsCnt); err != nil {
190 m.deleteOrders(batch, delOrders, tradePairsCnt)
191 if err := m.updateTradePairs(batch, tradePairsCnt); err != nil {
195 state, err := m.calcNextDatabaseState(blockHeader)
200 if err := m.saveMovDatabaseState(batch, state); err != nil {
208 func (m *LevelDBMovStore) addOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[string]*common.TradePair) error {
209 for _, order := range orders {
210 orderData := &orderData{
212 RatioNumerator: order.RatioNumerator,
213 RatioDenominator: order.RatioDenominator,
215 data, err := json.Marshal(orderData)
220 key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate())
223 tradePair := &common.TradePair{
224 FromAssetID: order.FromAssetID,
225 ToAssetID: order.ToAssetID,
227 if _, ok := tradePairsCnt[tradePair.Key()]; !ok {
228 tradePairsCnt[tradePair.Key()] = tradePair
230 tradePairsCnt[tradePair.Key()].Count++
235 func (m *LevelDBMovStore) calcNextDatabaseState(blockHeader *types.BlockHeader) (*common.MovDatabaseState, error) {
236 hash := blockHeader.Hash()
237 height := blockHeader.Height
239 state, err := m.GetMovDatabaseState()
244 if *state.Hash == hash {
245 hash = blockHeader.PreviousBlockHash
246 height = blockHeader.Height - 1
249 return &common.MovDatabaseState{Height: height, Hash: &hash}, nil
252 func (m *LevelDBMovStore) checkMovDatabaseState(header *types.BlockHeader) error {
253 state, err := m.GetMovDatabaseState()
258 if (*state.Hash == header.PreviousBlockHash && (state.Height+1) == header.Height) || *state.Hash == header.Hash() {
262 return errors.New("the status of the block is inconsistent with that of mov-database")
265 func (m *LevelDBMovStore) deleteOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[string]*common.TradePair) {
266 for _, order := range orders {
267 key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate())
270 tradePair := &common.TradePair{
271 FromAssetID: order.FromAssetID,
272 ToAssetID: order.ToAssetID,
274 if _, ok := tradePairsCnt[tradePair.Key()]; !ok {
275 tradePairsCnt[tradePair.Key()] = tradePair
277 tradePairsCnt[tradePair.Key()].Count--
281 func (m *LevelDBMovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDatabaseState) error {
282 value, err := json.Marshal(state)
287 batch.Set(bestMatchStore, value)
291 func (m *LevelDBMovStore) updateTradePairs(batch dbm.Batch, tradePairs map[string]*common.TradePair) error {
292 for _, v := range tradePairs {
293 key := calcTradePairKey(v.FromAssetID, v.ToAssetID)
294 tradePairData := &tradePairData{}
295 if value := m.db.Get(key); value != nil {
296 if err := json.Unmarshal(value, tradePairData); err != nil {
301 if tradePairData.Count += v.Count; tradePairData.Count < 0 {
302 return errors.New("negative trade count")
305 if tradePairData.Count > 0 {
306 value, err := json.Marshal(tradePairData)
311 batch.Set(key, value)