github.com/status-im/status-go@v1.1.0/db/history.go (about)

     1  package db
     2  
     3  import (
     4  	"encoding/binary"
     5  	"encoding/json"
     6  	"errors"
     7  	"time"
     8  
     9  	"github.com/syndtr/goleveldb/leveldb/util"
    10  
    11  	"github.com/status-im/status-go/eth-node/types"
    12  )
    13  
    14  var (
    15  	// ErrEmptyKey returned if key is not expected to be empty.
    16  	ErrEmptyKey = errors.New("TopicHistoryKey is empty")
    17  )
    18  
    19  // DB is a common interface for DB operations.
    20  type DB interface {
    21  	Get([]byte) ([]byte, error)
    22  	Put([]byte, []byte) error
    23  	Delete([]byte) error
    24  	Range([]byte, []byte) *util.Range
    25  	NewIterator(*util.Range) NamespaceIterator
    26  }
    27  
    28  // TopicHistoryKey defines bytes that are used as unique key for TopicHistory.
    29  // first 4 bytes are types.TopicType bytes
    30  // next 8 bytes are time.Duration encoded in big endian notation.
    31  type TopicHistoryKey [12]byte
    32  
    33  // LoadTopicHistoryFromKey unmarshalls key into topic and duration and loads value of topic history
    34  // from given database.
    35  func LoadTopicHistoryFromKey(db DB, key TopicHistoryKey) (th TopicHistory, err error) {
    36  	if (key == TopicHistoryKey{}) {
    37  		return th, ErrEmptyKey
    38  	}
    39  	topic := types.TopicType{}
    40  	copy(topic[:], key[:4])
    41  	duration := binary.BigEndian.Uint64(key[4:])
    42  	th = TopicHistory{db: db, Topic: topic, Duration: time.Duration(duration)}
    43  	return th, th.Load()
    44  }
    45  
    46  // TopicHistory stores necessary information.
    47  type TopicHistory struct {
    48  	db DB
    49  	// whisper topic
    50  	Topic types.TopicType
    51  
    52  	Duration time.Duration
    53  	// Timestamp that was used for the first request with this topic.
    54  	// Used to identify overlapping ranges.
    55  	First time.Time
    56  	// Timestamp of the last synced envelope.
    57  	Current time.Time
    58  	End     time.Time
    59  
    60  	RequestID types.Hash
    61  }
    62  
    63  // Key returns unique identifier for this TopicHistory.
    64  func (t TopicHistory) Key() TopicHistoryKey {
    65  	key := TopicHistoryKey{}
    66  	copy(key[:], t.Topic[:])
    67  	binary.BigEndian.PutUint64(key[4:], uint64(t.Duration))
    68  	return key
    69  }
    70  
    71  // Value marshalls TopicHistory into bytes.
    72  func (t TopicHistory) Value() ([]byte, error) {
    73  	return json.Marshal(t)
    74  }
    75  
    76  // Load TopicHistory from db using key and unmarshalls it.
    77  func (t *TopicHistory) Load() error {
    78  	key := t.Key()
    79  	if (key == TopicHistoryKey{}) {
    80  		return errors.New("key is empty")
    81  	}
    82  	value, err := t.db.Get(key[:])
    83  	if err != nil {
    84  		return err
    85  	}
    86  	return json.Unmarshal(value, t)
    87  }
    88  
    89  // Save persists TopicHistory on disk.
    90  func (t TopicHistory) Save() error {
    91  	key := t.Key()
    92  	val, err := t.Value()
    93  	if err != nil {
    94  		return err
    95  	}
    96  	return t.db.Put(key[:], val)
    97  }
    98  
    99  // Delete removes topic history from database.
   100  func (t TopicHistory) Delete() error {
   101  	key := t.Key()
   102  	return t.db.Delete(key[:])
   103  }
   104  
   105  // SameRange returns true if topic has same range, which means:
   106  // true if Current is zero and Duration is the same
   107  // and true if Current is the same
   108  func (t TopicHistory) SameRange(other TopicHistory) bool {
   109  	zero := time.Time{}
   110  	if t.Current == zero && other.Current == zero {
   111  		return t.Duration == other.Duration
   112  	}
   113  	return t.Current == other.Current
   114  }
   115  
   116  // Pending returns true if this topic was requested from a mail server.
   117  func (t TopicHistory) Pending() bool {
   118  	return t.RequestID != types.Hash{}
   119  }
   120  
   121  // HistoryRequest is kept in the database while request is in the progress.
   122  // Stores necessary information to identify topics with associated ranges included in the request.
   123  type HistoryRequest struct {
   124  	requestDB DB
   125  	topicDB   DB
   126  
   127  	histories []TopicHistory
   128  
   129  	// Generated ID
   130  	ID types.Hash
   131  	// List of the topics
   132  	TopicHistoryKeys []TopicHistoryKey
   133  }
   134  
   135  // AddHistory adds instance to internal list of instance and add instance key to the list
   136  // which will be persisted on disk.
   137  func (req *HistoryRequest) AddHistory(history TopicHistory) {
   138  	req.histories = append(req.histories, history)
   139  	req.TopicHistoryKeys = append(req.TopicHistoryKeys, history.Key())
   140  }
   141  
   142  // Histories returns internal lsit of topic histories.
   143  func (req *HistoryRequest) Histories() []TopicHistory {
   144  	// TODO Lazy load from database on first access
   145  	return req.histories
   146  }
   147  
   148  // Value returns content of HistoryRequest as bytes.
   149  func (req HistoryRequest) Value() ([]byte, error) {
   150  	return json.Marshal(req)
   151  }
   152  
   153  // Save persists all attached histories and request itself on the disk.
   154  func (req HistoryRequest) Save() error {
   155  	for i := range req.histories {
   156  		th := &req.histories[i]
   157  		th.RequestID = req.ID
   158  		if err := th.Save(); err != nil {
   159  			return err
   160  		}
   161  	}
   162  	val, err := req.Value()
   163  	if err != nil {
   164  		return err
   165  	}
   166  	return req.requestDB.Put(req.ID.Bytes(), val)
   167  }
   168  
   169  // Replace saves request with new ID and all data attached to the old one.
   170  func (req HistoryRequest) Replace(id types.Hash) error {
   171  	if (req.ID != types.Hash{}) {
   172  		if err := req.Delete(); err != nil {
   173  			return err
   174  		}
   175  	}
   176  	req.ID = id
   177  	return req.Save()
   178  }
   179  
   180  // Delete HistoryRequest from store and update every topic.
   181  func (req HistoryRequest) Delete() error {
   182  	return req.requestDB.Delete(req.ID.Bytes())
   183  }
   184  
   185  // Load reads request and topic histories content from disk and unmarshalls them.
   186  func (req *HistoryRequest) Load() error {
   187  	val, err := req.requestDB.Get(req.ID.Bytes())
   188  	if err != nil {
   189  		return err
   190  	}
   191  	return req.RawUnmarshall(val)
   192  }
   193  
   194  func (req *HistoryRequest) loadHistories() error {
   195  	for _, hk := range req.TopicHistoryKeys {
   196  		th, err := LoadTopicHistoryFromKey(req.topicDB, hk)
   197  		if err != nil {
   198  			return err
   199  		}
   200  		req.histories = append(req.histories, th)
   201  	}
   202  	return nil
   203  }
   204  
   205  // RawUnmarshall unmarshall given bytes into the structure.
   206  // Used in range queries to unmarshall content of the iter.Value directly into request struct.
   207  func (req *HistoryRequest) RawUnmarshall(val []byte) error {
   208  	err := json.Unmarshal(val, req)
   209  	if err != nil {
   210  		return err
   211  	}
   212  	return req.loadHistories()
   213  }
   214  
   215  // Includes checks if TopicHistory is included into the request.
   216  func (req *HistoryRequest) Includes(history TopicHistory) bool {
   217  	key := history.Key()
   218  	for i := range req.TopicHistoryKeys {
   219  		if key == req.TopicHistoryKeys[i] {
   220  			return true
   221  		}
   222  	}
   223  	return false
   224  }