9 "github.com/bytom/vapor/application/mov/common"
10 "github.com/bytom/vapor/consensus/segwit"
11 dbm "github.com/bytom/vapor/database/leveldb"
12 "github.com/bytom/vapor/protocol/bc"
13 "github.com/bytom/vapor/protocol/bc/types"
16 // ErrNotInitDBState represent the database state of mov store is not initialized
17 var ErrNotInitDBState = errors.New("database state of mov store is not initialized")
19 // MovStore is the interface for mov's persistent storage
20 type MovStore interface {
22 GetMovDatabaseState() (*common.MovDatabaseState, error)
23 InitDBState(height uint64, hash *bc.Hash) error
24 ListOrders(orderAfter *common.Order) ([]*common.Order, error)
25 ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error)
26 ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error
44 movStore = []byte("MOV:")
45 ordersPrefix = append(movStore, order)
46 tradePairsPrefix = append(movStore, tradePair)
47 bestMatchStore = append(movStore, matchStatus)
50 type orderData struct {
53 RatioDenominator int64
58 func calcOrderKey(fromAssetID, toAssetID *bc.AssetID, utxoHash *bc.Hash, rate float64) []byte {
59 buf := make([]byte, 8)
60 binary.BigEndian.PutUint64(buf, math.Float64bits(rate))
61 key := append(ordersPrefix, fromAssetID.Bytes()...)
62 key = append(key, toAssetID.Bytes()...)
63 key = append(key, buf...)
64 return append(key, utxoHash.Bytes()...)
67 func calcTradePairKey(fromAssetID, toAssetID *bc.AssetID) []byte {
68 key := append(tradePairsPrefix, fromAssetID.Bytes()...)
69 return append(key, toAssetID.Bytes()...)
72 func getAssetIDFromTradePairKey(key []byte, posIndex int) *bc.AssetID {
74 pos := len(tradePairsPrefix) + assetIDLen*posIndex
75 copy(b[:], key[pos:pos+assetIDLen])
76 assetID := bc.NewAssetID(b)
80 func getRateFromOrderKey(key []byte) float64 {
81 ratePos := len(ordersPrefix) + assetIDLen*2
82 return math.Float64frombits(binary.BigEndian.Uint64(key[ratePos : ratePos+rateByteLen]))
85 type tradePairData struct {
89 // LevelDBMovStore is the LevelDB implementation for MovStore
90 type LevelDBMovStore struct {
94 // NewLevelDBMovStore create a new LevelDBMovStore object
95 func NewLevelDBMovStore(db dbm.DB) *LevelDBMovStore {
96 return &LevelDBMovStore{db: db}
99 // Clear will clear all the data of store
100 func (m *LevelDBMovStore) Clear() {
101 batch := m.db.NewBatch()
103 iter := m.db.Iterator()
107 batch.Delete(iter.Key())
112 // GetMovDatabaseState return the current DB's image status
113 func (m *LevelDBMovStore) GetMovDatabaseState() (*common.MovDatabaseState, error) {
114 if value := m.db.Get(bestMatchStore); value != nil {
115 state := &common.MovDatabaseState{}
116 return state, json.Unmarshal(value, state)
119 return nil, ErrNotInitDBState
122 // InitDBState set the DB's image status
123 func (m *LevelDBMovStore) InitDBState(height uint64, hash *bc.Hash) error {
124 state := &common.MovDatabaseState{Height: height, Hash: hash}
125 value, err := json.Marshal(state)
130 m.db.Set(bestMatchStore, value)
134 // ListOrders return n orders after the input order
135 func (m *LevelDBMovStore) ListOrders(orderAfter *common.Order) ([]*common.Order, error) {
136 if orderAfter.FromAssetID == nil || orderAfter.ToAssetID == nil {
137 return nil, errors.New("assetID is nil")
140 orderPrefix := append(ordersPrefix, orderAfter.FromAssetID.Bytes()...)
141 orderPrefix = append(orderPrefix, orderAfter.ToAssetID.Bytes()...)
144 if orderAfter.Rate() > 0 {
145 startKey = calcOrderKey(orderAfter.FromAssetID, orderAfter.ToAssetID, orderAfter.UTXOHash(), orderAfter.Rate())
148 itr := m.db.IteratorPrefixWithStart(orderPrefix, startKey, false)
151 var orders []*common.Order
152 for txNum := 0; txNum < ordersNum && itr.Next(); txNum++ {
153 orderData := &orderData{}
154 if err := json.Unmarshal(itr.Value(), orderData); err != nil {
158 contractArgs, err := segwit.DecodeP2WMCProgram(orderData.Utxo.ControlProgram)
163 orders = append(orders, &common.Order{
164 FromAssetID: orderAfter.FromAssetID,
165 ToAssetID: orderAfter.ToAssetID,
166 Utxo: orderData.Utxo,
167 RatioNumerator: orderData.RatioNumerator,
168 RatioDenominator: orderData.RatioDenominator,
169 BlockHeight: orderData.BlockHeight,
170 TxIndex: orderData.TxIndex,
171 SellerProgram: contractArgs.SellerProgram,
177 // ListTradePairsWithStart return n trade pairs after the input trade pair
178 func (m *LevelDBMovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
180 if fromAssetIDAfter != nil && toAssetIDAfter != nil {
181 startKey = calcTradePairKey(fromAssetIDAfter, toAssetIDAfter)
184 itr := m.db.IteratorPrefixWithStart(tradePairsPrefix, startKey, false)
187 var tradePairs []*common.TradePair
188 for txNum := 0; txNum < tradePairsNum && itr.Next(); txNum++ {
190 fromAssetID := getAssetIDFromTradePairKey(key, fromAssetIDPos)
191 toAssetID := getAssetIDFromTradePairKey(key, toAssetIDPos)
193 tradePairData := &tradePairData{}
194 if err := json.Unmarshal(itr.Value(), tradePairData); err != nil {
198 tradePairs = append(tradePairs, &common.TradePair{FromAssetID: fromAssetID, ToAssetID: toAssetID, Count: tradePairData.Count})
200 return tradePairs, nil
203 // ProcessOrders update the DB's image by add new orders, delete the used order
204 func (m *LevelDBMovStore) ProcessOrders(addOrders []*common.Order, delOrders []*common.Order, blockHeader *types.BlockHeader) error {
205 if err := m.checkMovDatabaseState(blockHeader); err != nil {
209 batch := m.db.NewBatch()
210 tradePairsCnt := make(map[string]*common.TradePair)
211 if err := m.addOrders(batch, addOrders, tradePairsCnt); err != nil {
215 m.deleteOrders(batch, delOrders, tradePairsCnt)
216 if err := m.updateTradePairs(batch, tradePairsCnt); err != nil {
220 state, err := m.calcNextDatabaseState(blockHeader)
225 if err := m.saveMovDatabaseState(batch, state); err != nil {
233 func (m *LevelDBMovStore) addOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[string]*common.TradePair) error {
234 for _, order := range orders {
235 orderData := &orderData{
237 RatioNumerator: order.RatioNumerator,
238 RatioDenominator: order.RatioDenominator,
239 BlockHeight: order.BlockHeight,
240 TxIndex: order.TxIndex,
242 data, err := json.Marshal(orderData)
247 key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate())
250 tradePair := &common.TradePair{
251 FromAssetID: order.FromAssetID,
252 ToAssetID: order.ToAssetID,
254 if _, ok := tradePairsCnt[tradePair.Key()]; !ok {
255 tradePairsCnt[tradePair.Key()] = tradePair
257 tradePairsCnt[tradePair.Key()].Count++
262 func (m *LevelDBMovStore) calcNextDatabaseState(blockHeader *types.BlockHeader) (*common.MovDatabaseState, error) {
263 hash := blockHeader.Hash()
264 height := blockHeader.Height
266 state, err := m.GetMovDatabaseState()
271 if *state.Hash == hash {
272 hash = blockHeader.PreviousBlockHash
273 height = blockHeader.Height - 1
276 return &common.MovDatabaseState{Height: height, Hash: &hash}, nil
279 func (m *LevelDBMovStore) checkMovDatabaseState(header *types.BlockHeader) error {
280 state, err := m.GetMovDatabaseState()
285 if (*state.Hash == header.PreviousBlockHash && (state.Height+1) == header.Height) || *state.Hash == header.Hash() {
289 return errors.New("the status of the block is inconsistent with that of mov-database")
292 func (m *LevelDBMovStore) deleteOrders(batch dbm.Batch, orders []*common.Order, tradePairsCnt map[string]*common.TradePair) {
293 for _, order := range orders {
294 key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate())
297 tradePair := &common.TradePair{
298 FromAssetID: order.FromAssetID,
299 ToAssetID: order.ToAssetID,
301 if _, ok := tradePairsCnt[tradePair.Key()]; !ok {
302 tradePairsCnt[tradePair.Key()] = tradePair
304 tradePairsCnt[tradePair.Key()].Count--
308 func (m *LevelDBMovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDatabaseState) error {
309 value, err := json.Marshal(state)
314 batch.Set(bestMatchStore, value)
318 func (m *LevelDBMovStore) updateTradePairs(batch dbm.Batch, tradePairs map[string]*common.TradePair) error {
319 for _, v := range tradePairs {
320 key := calcTradePairKey(v.FromAssetID, v.ToAssetID)
321 tradePairData := &tradePairData{}
322 if value := m.db.Get(key); value != nil {
323 if err := json.Unmarshal(value, tradePairData); err != nil {
328 if tradePairData.Count += v.Count; tradePairData.Count < 0 {
329 return errors.New("negative trade count")
332 if tradePairData.Count > 0 {
333 value, err := json.Marshal(tradePairData)
338 batch.Set(key, value)