github.com/status-im/status-go@v1.1.0/mailserver/mailserver_db_leveldb.go (about) 1 package mailserver 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/syndtr/goleveldb/leveldb" 8 "github.com/syndtr/goleveldb/leveldb/errors" 9 "github.com/syndtr/goleveldb/leveldb/iterator" 10 "github.com/syndtr/goleveldb/leveldb/util" 11 12 "github.com/ethereum/go-ethereum/log" 13 "github.com/ethereum/go-ethereum/rlp" 14 15 "github.com/status-im/status-go/eth-node/types" 16 waku "github.com/status-im/status-go/waku/common" 17 ) 18 19 type LevelDB struct { 20 // We can't embed as there are some state problems with go-routines 21 ldb *leveldb.DB 22 name string 23 done chan struct{} 24 } 25 26 type LevelDBIterator struct { 27 iterator.Iterator 28 } 29 30 func (i *LevelDBIterator) DBKey() (*DBKey, error) { 31 return &DBKey{ 32 raw: i.Key(), 33 }, nil 34 } 35 36 func (i *LevelDBIterator) GetEnvelopeByTopicsMap(topics map[types.TopicType]bool) ([]byte, error) { 37 rawValue := make([]byte, len(i.Value())) 38 copy(rawValue, i.Value()) 39 40 key, err := i.DBKey() 41 if err != nil { 42 return nil, err 43 } 44 45 if !topics[key.Topic()] { 46 return nil, nil 47 } 48 49 return rawValue, nil 50 } 51 52 func (i *LevelDBIterator) GetEnvelopeByBloomFilter(bloom []byte) ([]byte, error) { 53 var envelopeBloom []byte 54 rawValue := make([]byte, len(i.Value())) 55 copy(rawValue, i.Value()) 56 57 key, err := i.DBKey() 58 if err != nil { 59 return nil, err 60 } 61 62 if len(key.Bytes()) != DBKeyLength { 63 var err error 64 envelopeBloom, err = extractBloomFromEncodedEnvelope(rawValue) 65 if err != nil { 66 return nil, err 67 } 68 } else { 69 envelopeBloom = types.TopicToBloom(key.Topic()) 70 } 71 if !types.BloomFilterMatch(bloom, envelopeBloom) { 72 return nil, nil 73 } 74 return rawValue, nil 75 } 76 77 func (i *LevelDBIterator) Release() error { 78 i.Iterator.Release() 79 return nil 80 } 81 82 func NewLevelDB(dataDir string) (*LevelDB, error) { 83 // Open opens an existing leveldb database 84 db, err := leveldb.OpenFile(dataDir, nil) 85 if _, corrupted := err.(*errors.ErrCorrupted); corrupted { 86 log.Info("database is corrupted trying to recover", "path", dataDir) 87 db, err = leveldb.RecoverFile(dataDir, nil) 88 } 89 90 instance := LevelDB{ 91 ldb: db, 92 name: dataDir, // name is used for metrics labels 93 done: make(chan struct{}), 94 } 95 96 // initialize the metric value 97 instance.updateArchivedEnvelopesCount() 98 // checking count on every insert is inefficient 99 go func() { 100 for { 101 select { 102 case <-instance.done: 103 return 104 case <-time.After(time.Second * envelopeCountCheckInterval): 105 instance.updateArchivedEnvelopesCount() 106 } 107 } 108 }() 109 return &instance, err 110 } 111 112 // GetEnvelope get an envelope by its key 113 func (db *LevelDB) GetEnvelope(key *DBKey) ([]byte, error) { 114 defer recoverLevelDBPanics("GetEnvelope") 115 return db.ldb.Get(key.Bytes(), nil) 116 } 117 118 func (db *LevelDB) updateArchivedEnvelopesCount() { 119 if count, err := db.envelopesCount(); err != nil { 120 log.Warn("db query for envelopes count failed", "err", err) 121 } else { 122 archivedEnvelopesGauge.WithLabelValues(db.name).Set(float64(count)) 123 } 124 } 125 126 // Build iterator returns an iterator given a start/end and a cursor 127 func (db *LevelDB) BuildIterator(query CursorQuery) (Iterator, error) { 128 defer recoverLevelDBPanics("BuildIterator") 129 130 i := db.ldb.NewIterator(&util.Range{Start: query.start, Limit: query.end}, nil) 131 132 envelopeQueriesCounter.WithLabelValues("unknown", "unknown").Inc() 133 // seek to the end as we want to return envelopes in a descending order 134 if len(query.cursor) == CursorLength { 135 i.Seek(query.cursor) 136 } 137 return &LevelDBIterator{i}, nil 138 } 139 140 // Prune removes envelopes older than time 141 func (db *LevelDB) Prune(t time.Time, batchSize int) (int, error) { 142 defer recoverLevelDBPanics("Prune") 143 144 var zero types.Hash 145 var emptyTopic types.TopicType 146 kl := NewDBKey(0, emptyTopic, zero) 147 ku := NewDBKey(uint32(t.Unix()), emptyTopic, zero) 148 query := CursorQuery{ 149 start: kl.Bytes(), 150 end: ku.Bytes(), 151 } 152 i, err := db.BuildIterator(query) 153 if err != nil { 154 return 0, err 155 } 156 defer func() { _ = i.Release() }() 157 158 batch := leveldb.Batch{} 159 removed := 0 160 161 for i.Next() { 162 dbKey, err := i.DBKey() 163 if err != nil { 164 return 0, err 165 } 166 167 batch.Delete(dbKey.Bytes()) 168 169 if batch.Len() == batchSize { 170 if err := db.ldb.Write(&batch, nil); err != nil { 171 return removed, err 172 } 173 174 removed = removed + batch.Len() 175 batch.Reset() 176 } 177 } 178 179 if batch.Len() > 0 { 180 if err := db.ldb.Write(&batch, nil); err != nil { 181 return removed, err 182 } 183 184 removed = removed + batch.Len() 185 } 186 187 return removed, nil 188 } 189 190 func (db *LevelDB) envelopesCount() (int, error) { 191 defer recoverLevelDBPanics("envelopesCount") 192 iterator, err := db.BuildIterator(CursorQuery{}) 193 if err != nil { 194 return 0, err 195 } 196 // LevelDB does not have API for getting a count 197 var count int 198 for iterator.Next() { 199 count++ 200 } 201 return count, nil 202 } 203 204 // SaveEnvelope stores an envelope in leveldb and increments the metrics 205 func (db *LevelDB) SaveEnvelope(env types.Envelope) error { 206 defer recoverLevelDBPanics("SaveEnvelope") 207 208 key := NewDBKey(env.Expiry()-env.TTL(), env.Topic(), env.Hash()) 209 rawEnvelope, err := rlp.EncodeToBytes(env.Unwrap()) 210 if err != nil { 211 log.Error(fmt.Sprintf("rlp.EncodeToBytes failed: %s", err)) 212 archivedErrorsCounter.WithLabelValues(db.name).Inc() 213 return err 214 } 215 216 if err = db.ldb.Put(key.Bytes(), rawEnvelope, nil); err != nil { 217 log.Error(fmt.Sprintf("Writing to DB failed: %s", err)) 218 archivedErrorsCounter.WithLabelValues(db.name).Inc() 219 } 220 archivedEnvelopesGauge.WithLabelValues(db.name).Inc() 221 archivedEnvelopeSizeMeter.WithLabelValues(db.name).Observe( 222 float64(waku.EnvelopeHeaderLength + env.Size())) 223 return err 224 } 225 226 func (db *LevelDB) Close() error { 227 select { 228 case <-db.done: 229 default: 230 close(db.done) 231 } 232 return db.ldb.Close() 233 } 234 235 func recoverLevelDBPanics(calleMethodName string) { 236 // Recover from possible goleveldb panics 237 if r := recover(); r != nil { 238 if errString, ok := r.(string); ok { 239 log.Error(fmt.Sprintf("recovered from panic in %s: %s", calleMethodName, errString)) 240 } 241 } 242 }