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 // 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 type orderData struct {
48 RatioDenominator int64
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()...)
60 func calcTradePairKey(fromAssetID, toAssetID *bc.AssetID) []byte {
61 key := append(tradePairsPrefix, fromAssetID.Bytes()...)
62 return append(key, toAssetID.Bytes()...)
65 func getAssetIDFromTradePairKey(key []byte, posIndex int) *bc.AssetID {
67 pos := len(tradePairsPrefix) + assetIDLen*posIndex
68 copy(b[:], key[pos:pos+assetIDLen])
69 assetID := bc.NewAssetID(b)
73 func getRateFromOrderKey(key []byte) float64 {
74 ratePos := len(ordersPrefix) + assetIDLen*2
75 return math.Float64frombits(binary.BigEndian.Uint64(key[ratePos : ratePos+rateByteLen]))
78 type tradePairData struct {
82 // LevelDBMovStore is the LevelDB implementation for MovStore
83 type LevelDBMovStore struct {
87 // NewLevelDBMovStore create a new LevelDBMovStore object
88 func NewLevelDBMovStore(db dbm.DB) *LevelDBMovStore {
89 return &LevelDBMovStore{db: db}
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)
99 return nil, errors.New("don't find state of mov-database")
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)
110 m.db.Set(bestMatchStore, value)
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")
120 orderPrefix := append(ordersPrefix, orderAfter.FromAssetID.Bytes()...)
121 orderPrefix = append(orderPrefix, orderAfter.ToAssetID.Bytes()...)
124 if orderAfter.Rate() > 0 {
125 startKey = calcOrderKey(orderAfter.FromAssetID, orderAfter.ToAssetID, orderAfter.UTXOHash(), orderAfter.Rate())
128 itr := m.db.IteratorPrefixWithStart(orderPrefix, startKey, false)
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 {
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,
149 // ListTradePairsWithStart return n trade pairs after the input trade pair
150 func (m *LevelDBMovStore) ListTradePairsWithStart(fromAssetIDAfter, toAssetIDAfter *bc.AssetID) ([]*common.TradePair, error) {
152 if fromAssetIDAfter != nil && toAssetIDAfter != nil {
153 startKey = calcTradePairKey(fromAssetIDAfter, toAssetIDAfter)
156 itr := m.db.IteratorPrefixWithStart(tradePairsPrefix, startKey, false)
159 var tradePairs []*common.TradePair
160 for txNum := 0; txNum < tradePairsNum && itr.Next(); txNum++ {
162 fromAssetID := getAssetIDFromTradePairKey(key, fromAssetIDPos)
163 toAssetID := getAssetIDFromTradePairKey(key, toAssetIDPos)
165 tradePairData := &tradePairData{}
166 if err := json.Unmarshal(itr.Value(), tradePairData); err != nil {
170 tradePairs = append(tradePairs, &common.TradePair{FromAssetID: fromAssetID, ToAssetID: toAssetID, Count: tradePairData.Count})
172 return tradePairs, nil
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 {
181 batch := m.db.NewBatch()
182 tradePairsCnt := make(map[string]*common.TradePair)
183 if err := m.addOrders(batch, addOrders, tradePairsCnt); err != nil {
187 m.deleteOrders(batch, delOrders, tradePairsCnt)
188 if err := m.updateTradePairs(batch, tradePairsCnt); err != nil {
192 state, err := m.calcNextDatabaseState(blockHeader)
197 if err := m.saveMovDatabaseState(batch, state); err != nil {
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{
209 RatioNumerator: order.RatioNumerator,
210 RatioDenominator: order.RatioDenominator,
212 data, err := json.Marshal(orderData)
217 key := calcOrderKey(order.FromAssetID, order.ToAssetID, order.UTXOHash(), order.Rate())
220 tradePair := &common.TradePair{
221 FromAssetID: order.FromAssetID,
222 ToAssetID: order.ToAssetID,
224 if _, ok := tradePairsCnt[tradePair.Key()]; !ok {
225 tradePairsCnt[tradePair.Key()] = tradePair
227 tradePairsCnt[tradePair.Key()].Count++
232 func (m *LevelDBMovStore) calcNextDatabaseState(blockHeader *types.BlockHeader) (*common.MovDatabaseState, error) {
233 hash := blockHeader.Hash()
234 height := blockHeader.Height
236 state, err := m.GetMovDatabaseState()
241 if *state.Hash == hash {
242 hash = blockHeader.PreviousBlockHash
243 height = blockHeader.Height - 1
246 return &common.MovDatabaseState{Height: height, Hash: &hash}, nil
249 func (m *LevelDBMovStore) checkMovDatabaseState(header *types.BlockHeader) error {
250 state, err := m.GetMovDatabaseState()
255 if (*state.Hash == header.PreviousBlockHash && (state.Height+1) == header.Height) || *state.Hash == header.Hash() {
259 return errors.New("the status of the block is inconsistent with that of mov-database")
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())
267 tradePair := &common.TradePair{
268 FromAssetID: order.FromAssetID,
269 ToAssetID: order.ToAssetID,
271 if _, ok := tradePairsCnt[tradePair.Key()]; !ok {
272 tradePairsCnt[tradePair.Key()] = tradePair
274 tradePairsCnt[tradePair.Key()].Count--
278 func (m *LevelDBMovStore) saveMovDatabaseState(batch dbm.Batch, state *common.MovDatabaseState) error {
279 value, err := json.Marshal(state)
284 batch.Set(bestMatchStore, value)
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 {
298 if tradePairData.Count += v.Count; tradePairData.Count < 0 {
299 return errors.New("negative trade count")
302 if tradePairData.Count > 0 {
303 value, err := json.Marshal(tradePairData)
308 batch.Set(key, value)