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