github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/light/store/db/db.go (about) 1 package db 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "regexp" 7 "strconv" 8 9 dbm "github.com/lazyledger/lazyledger-core/libs/db" 10 tmsync "github.com/lazyledger/lazyledger-core/libs/sync" 11 "github.com/lazyledger/lazyledger-core/light/store" 12 tmproto "github.com/lazyledger/lazyledger-core/proto/tendermint/types" 13 "github.com/lazyledger/lazyledger-core/types" 14 ) 15 16 var ( 17 sizeKey = []byte("size") 18 ) 19 20 type dbs struct { 21 db dbm.DB 22 prefix string 23 24 mtx tmsync.RWMutex 25 size uint16 26 } 27 28 // New returns a Store that wraps any DB (with an optional prefix in case you 29 // want to use one DB with many light clients). 30 func New(db dbm.DB, prefix string) store.Store { 31 32 size := uint16(0) 33 bz, err := db.Get(sizeKey) 34 if err == nil && len(bz) > 0 { 35 size = unmarshalSize(bz) 36 } 37 38 return &dbs{db: db, prefix: prefix, size: size} 39 } 40 41 // SaveLightBlock persists LightBlock to the db. 42 // 43 // Safe for concurrent use by multiple goroutines. 44 func (s *dbs) SaveLightBlock(lb *types.LightBlock) error { 45 if lb.Height <= 0 { 46 panic("negative or zero height") 47 } 48 49 lbpb, err := lb.ToProto() 50 if err != nil { 51 return fmt.Errorf("unable to convert light block to protobuf: %w", err) 52 } 53 54 lbBz, err := lbpb.Marshal() 55 if err != nil { 56 return fmt.Errorf("marshalling LightBlock: %w", err) 57 } 58 59 s.mtx.Lock() 60 defer s.mtx.Unlock() 61 62 b := s.db.NewBatch() 63 defer b.Close() 64 if err = b.Set(s.lbKey(lb.Height), lbBz); err != nil { 65 return err 66 } 67 if err = b.Set(sizeKey, marshalSize(s.size+1)); err != nil { 68 return err 69 } 70 if err = b.WriteSync(); err != nil { 71 return err 72 } 73 s.size++ 74 75 return nil 76 } 77 78 // DeleteLightBlockAndValidatorSet deletes the LightBlock from 79 // the db. 80 // 81 // Safe for concurrent use by multiple goroutines. 82 func (s *dbs) DeleteLightBlock(height int64) error { 83 if height <= 0 { 84 panic("negative or zero height") 85 } 86 87 s.mtx.Lock() 88 defer s.mtx.Unlock() 89 90 b := s.db.NewBatch() 91 defer b.Close() 92 if err := b.Delete(s.lbKey(height)); err != nil { 93 return err 94 } 95 if err := b.Set(sizeKey, marshalSize(s.size-1)); err != nil { 96 return err 97 } 98 if err := b.WriteSync(); err != nil { 99 return err 100 } 101 s.size-- 102 103 return nil 104 } 105 106 // LightBlock retrieves the LightBlock at the given height. 107 // 108 // Safe for concurrent use by multiple goroutines. 109 func (s *dbs) LightBlock(height int64) (*types.LightBlock, error) { 110 if height <= 0 { 111 panic("negative or zero height") 112 } 113 114 bz, err := s.db.Get(s.lbKey(height)) 115 if err != nil { 116 panic(err) 117 } 118 if len(bz) == 0 { 119 return nil, store.ErrLightBlockNotFound 120 } 121 122 var lbpb tmproto.LightBlock 123 err = lbpb.Unmarshal(bz) 124 if err != nil { 125 return nil, fmt.Errorf("unmarshal error: %w", err) 126 } 127 128 lightBlock, err := types.LightBlockFromProto(&lbpb) 129 if err != nil { 130 return nil, fmt.Errorf("proto conversion error: %w", err) 131 } 132 133 return lightBlock, err 134 } 135 136 // LastLightBlockHeight returns the last LightBlock height stored. 137 // 138 // Safe for concurrent use by multiple goroutines. 139 func (s *dbs) LastLightBlockHeight() (int64, error) { 140 itr, err := s.db.ReverseIterator( 141 s.lbKey(1), 142 append(s.lbKey(1<<63-1), byte(0x00)), 143 ) 144 if err != nil { 145 panic(err) 146 } 147 defer itr.Close() 148 149 for itr.Valid() { 150 key := itr.Key() 151 _, height, ok := parseLbKey(key) 152 if ok { 153 return height, nil 154 } 155 itr.Next() 156 } 157 158 return -1, itr.Error() 159 } 160 161 // FirstLightBlockHeight returns the first LightBlock height stored. 162 // 163 // Safe for concurrent use by multiple goroutines. 164 func (s *dbs) FirstLightBlockHeight() (int64, error) { 165 itr, err := s.db.Iterator( 166 s.lbKey(1), 167 append(s.lbKey(1<<63-1), byte(0x00)), 168 ) 169 if err != nil { 170 panic(err) 171 } 172 defer itr.Close() 173 174 for itr.Valid() { 175 key := itr.Key() 176 _, height, ok := parseLbKey(key) 177 if ok { 178 return height, nil 179 } 180 itr.Next() 181 } 182 183 return -1, itr.Error() 184 } 185 186 // LightBlockBefore iterates over light blocks until it finds a block before 187 // the given height. It returns ErrLightBlockNotFound if no such block exists. 188 // 189 // Safe for concurrent use by multiple goroutines. 190 func (s *dbs) LightBlockBefore(height int64) (*types.LightBlock, error) { 191 if height <= 0 { 192 panic("negative or zero height") 193 } 194 195 itr, err := s.db.ReverseIterator( 196 s.lbKey(1), 197 s.lbKey(height), 198 ) 199 if err != nil { 200 panic(err) 201 } 202 defer itr.Close() 203 204 for itr.Valid() { 205 key := itr.Key() 206 _, existingHeight, ok := parseLbKey(key) 207 if ok { 208 return s.LightBlock(existingHeight) 209 } 210 itr.Next() 211 } 212 if err = itr.Error(); err != nil { 213 return nil, err 214 } 215 216 return nil, store.ErrLightBlockNotFound 217 } 218 219 // Prune prunes header & validator set pairs until there are only size pairs 220 // left. 221 // 222 // Safe for concurrent use by multiple goroutines. 223 func (s *dbs) Prune(size uint16) error { 224 // 1) Check how many we need to prune. 225 s.mtx.RLock() 226 sSize := s.size 227 s.mtx.RUnlock() 228 229 if sSize <= size { // nothing to prune 230 return nil 231 } 232 numToPrune := sSize - size 233 234 // 2) Iterate over headers and perform a batch operation. 235 itr, err := s.db.Iterator( 236 s.lbKey(1), 237 append(s.lbKey(1<<63-1), byte(0x00)), 238 ) 239 if err != nil { 240 panic(err) 241 } 242 defer itr.Close() 243 244 b := s.db.NewBatch() 245 defer b.Close() 246 247 pruned := 0 248 for itr.Valid() && numToPrune > 0 { 249 key := itr.Key() 250 _, height, ok := parseLbKey(key) 251 if ok { 252 if err = b.Delete(s.lbKey(height)); err != nil { 253 return err 254 } 255 } 256 itr.Next() 257 numToPrune-- 258 pruned++ 259 } 260 if err = itr.Error(); err != nil { 261 return err 262 } 263 264 err = b.WriteSync() 265 if err != nil { 266 return err 267 } 268 269 // 3) Update size. 270 s.mtx.Lock() 271 defer s.mtx.Unlock() 272 273 s.size -= uint16(pruned) 274 275 if wErr := s.db.SetSync(sizeKey, marshalSize(s.size)); wErr != nil { 276 return fmt.Errorf("failed to persist size: %w", wErr) 277 } 278 279 return nil 280 } 281 282 // Size returns the number of header & validator set pairs. 283 // 284 // Safe for concurrent use by multiple goroutines. 285 func (s *dbs) Size() uint16 { 286 s.mtx.RLock() 287 defer s.mtx.RUnlock() 288 return s.size 289 } 290 291 func (s *dbs) lbKey(height int64) []byte { 292 return []byte(fmt.Sprintf("lb/%s/%020d", s.prefix, height)) 293 } 294 295 var keyPattern = regexp.MustCompile(`^(lb)/([^/]*)/([0-9]+)$`) 296 297 func parseKey(key []byte) (part string, prefix string, height int64, ok bool) { 298 submatch := keyPattern.FindSubmatch(key) 299 if submatch == nil { 300 return "", "", 0, false 301 } 302 part = string(submatch[1]) 303 prefix = string(submatch[2]) 304 height, err := strconv.ParseInt(string(submatch[3]), 10, 64) 305 if err != nil { 306 return "", "", 0, false 307 } 308 ok = true // good! 309 return 310 } 311 312 func parseLbKey(key []byte) (prefix string, height int64, ok bool) { 313 var part string 314 part, prefix, height, ok = parseKey(key) 315 if part != "lb" { 316 return "", 0, false 317 } 318 return 319 } 320 321 func marshalSize(size uint16) []byte { 322 bs := make([]byte, 2) 323 binary.LittleEndian.PutUint16(bs, size) 324 return bs 325 } 326 327 func unmarshalSize(bz []byte) uint16 { 328 return binary.LittleEndian.Uint16(bz) 329 }