github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/light/store/db/db.go (about) 1 package db 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "sync" 7 8 "github.com/google/orderedcode" 9 dbm "github.com/tendermint/tm-db" 10 11 "github.com/ari-anchor/sei-tendermint/light/store" 12 tmproto "github.com/ari-anchor/sei-tendermint/proto/tendermint/types" 13 "github.com/ari-anchor/sei-tendermint/types" 14 ) 15 16 // key prefixes 17 // NB: Before modifying these, cross-check them with those in 18 // * internal/store/store.go [0..4, 13] 19 // * internal/state/store.go [5..8, 14] 20 // * internal/evidence/pool.go [9..10] 21 // * light/store/db/db.go [11..12] 22 // TODO(sergio): Move all these to their own package. 23 // TODO: what about these (they already collide): 24 // * scripts/scmigrate/migrate.go [3] 25 // * internal/p2p/peermanager.go [1] 26 const ( 27 prefixLightBlock = int64(11) 28 prefixSize = int64(12) 29 ) 30 31 type dbs struct { 32 db dbm.DB 33 34 mtx sync.RWMutex 35 size uint16 36 } 37 38 // New returns a Store that wraps any DB 39 // If you want to share one DB across many light clients consider using PrefixDB 40 func New(db dbm.DB) store.Store { 41 42 lightStore := &dbs{db: db} 43 44 // retrieve the size of the db 45 size := uint16(0) 46 bz, err := lightStore.db.Get(lightStore.sizeKey()) 47 if err == nil && len(bz) > 0 { 48 size = unmarshalSize(bz) 49 } 50 lightStore.size = size 51 52 return lightStore 53 } 54 55 // SaveLightBlock persists LightBlock to the db. 56 // 57 // Safe for concurrent use by multiple goroutines. 58 func (s *dbs) SaveLightBlock(lb *types.LightBlock) error { 59 if lb.Height <= 0 { 60 panic("negative or zero height") 61 } 62 63 lbpb, err := lb.ToProto() 64 if err != nil { 65 return fmt.Errorf("unable to convert light block to protobuf: %w", err) 66 } 67 68 lbBz, err := lbpb.Marshal() 69 if err != nil { 70 return fmt.Errorf("marshaling LightBlock: %w", err) 71 } 72 73 s.mtx.Lock() 74 defer s.mtx.Unlock() 75 76 b := s.db.NewBatch() 77 defer b.Close() 78 if err = b.Set(s.lbKey(lb.Height), lbBz); err != nil { 79 return err 80 } 81 if err = b.Set(s.sizeKey(), marshalSize(s.size+1)); err != nil { 82 return err 83 } 84 if err = b.WriteSync(); err != nil { 85 return err 86 } 87 s.size++ 88 89 return nil 90 } 91 92 // DeleteLightBlockAndValidatorSet deletes the LightBlock from 93 // the db. 94 // 95 // Safe for concurrent use by multiple goroutines. 96 func (s *dbs) DeleteLightBlock(height int64) error { 97 if height <= 0 { 98 panic("negative or zero height") 99 } 100 101 s.mtx.Lock() 102 defer s.mtx.Unlock() 103 104 b := s.db.NewBatch() 105 defer b.Close() 106 if err := b.Delete(s.lbKey(height)); err != nil { 107 return err 108 } 109 if err := b.Set(s.sizeKey(), marshalSize(s.size-1)); err != nil { 110 return err 111 } 112 if err := b.WriteSync(); err != nil { 113 return err 114 } 115 s.size-- 116 117 return nil 118 } 119 120 // LightBlock retrieves the LightBlock at the given height. 121 // 122 // Safe for concurrent use by multiple goroutines. 123 func (s *dbs) LightBlock(height int64) (*types.LightBlock, error) { 124 if height <= 0 { 125 panic("negative or zero height") 126 } 127 128 bz, err := s.db.Get(s.lbKey(height)) 129 if err != nil { 130 panic(err) 131 } 132 if len(bz) == 0 { 133 return nil, store.ErrLightBlockNotFound 134 } 135 136 var lbpb tmproto.LightBlock 137 err = lbpb.Unmarshal(bz) 138 if err != nil { 139 return nil, fmt.Errorf("unmarshal error: %w", err) 140 } 141 142 lightBlock, err := types.LightBlockFromProto(&lbpb) 143 if err != nil { 144 return nil, fmt.Errorf("proto conversion error: %w", err) 145 } 146 147 return lightBlock, err 148 } 149 150 // LastLightBlockHeight returns the last LightBlock height stored. 151 // 152 // Safe for concurrent use by multiple goroutines. 153 func (s *dbs) LastLightBlockHeight() (int64, error) { 154 itr, err := s.db.ReverseIterator( 155 s.lbKey(1), 156 append(s.lbKey(1<<63-1), byte(0x00)), 157 ) 158 if err != nil { 159 panic(err) 160 } 161 defer itr.Close() 162 163 if itr.Valid() { 164 return s.decodeLbKey(itr.Key()) 165 } 166 167 return -1, itr.Error() 168 } 169 170 // FirstLightBlockHeight returns the first LightBlock height stored. 171 // 172 // Safe for concurrent use by multiple goroutines. 173 func (s *dbs) FirstLightBlockHeight() (int64, error) { 174 itr, err := s.db.Iterator( 175 s.lbKey(1), 176 append(s.lbKey(1<<63-1), byte(0x00)), 177 ) 178 if err != nil { 179 panic(err) 180 } 181 defer itr.Close() 182 183 if itr.Valid() { 184 return s.decodeLbKey(itr.Key()) 185 } 186 187 return -1, itr.Error() 188 } 189 190 // LightBlockBefore iterates over light blocks until it finds a block before 191 // the given height. It returns ErrLightBlockNotFound if no such block exists. 192 // 193 // Safe for concurrent use by multiple goroutines. 194 func (s *dbs) LightBlockBefore(height int64) (*types.LightBlock, error) { 195 if height <= 0 { 196 panic("negative or zero height") 197 } 198 199 itr, err := s.db.ReverseIterator( 200 s.lbKey(1), 201 s.lbKey(height), 202 ) 203 if err != nil { 204 panic(err) 205 } 206 defer itr.Close() 207 208 if itr.Valid() { 209 var lbpb tmproto.LightBlock 210 err = lbpb.Unmarshal(itr.Value()) 211 if err != nil { 212 return nil, fmt.Errorf("unmarshal error: %w", err) 213 } 214 215 lightBlock, err := types.LightBlockFromProto(&lbpb) 216 if err != nil { 217 return nil, fmt.Errorf("proto conversion error: %w", err) 218 } 219 return lightBlock, nil 220 } 221 if err = itr.Error(); err != nil { 222 return nil, err 223 } 224 225 return nil, store.ErrLightBlockNotFound 226 } 227 228 // Prune prunes header & validator set pairs until there are only size pairs 229 // left. 230 // 231 // Safe for concurrent use by multiple goroutines. 232 func (s *dbs) Prune(size uint16) error { 233 // 1) Check how many we need to prune. 234 s.mtx.Lock() 235 defer s.mtx.Unlock() 236 sSize := s.size 237 238 if sSize <= size { // nothing to prune 239 return nil 240 } 241 numToPrune := sSize - size 242 243 b := s.db.NewBatch() 244 defer b.Close() 245 246 // 2) use an iterator to batch together all the blocks that need to be deleted 247 if err := s.batchDelete(b, numToPrune); err != nil { 248 return err 249 } 250 251 // 3) // update size 252 s.size = size 253 if err := b.Set(s.sizeKey(), marshalSize(size)); err != nil { 254 return fmt.Errorf("failed to persist size: %w", err) 255 } 256 257 // 4) write batch deletion to disk 258 return b.WriteSync() 259 } 260 261 // Size returns the number of header & validator set pairs. 262 // 263 // Safe for concurrent use by multiple goroutines. 264 func (s *dbs) Size() uint16 { 265 s.mtx.RLock() 266 defer s.mtx.RUnlock() 267 return s.size 268 } 269 270 func (s *dbs) batchDelete(batch dbm.Batch, numToPrune uint16) error { 271 itr, err := s.db.Iterator( 272 s.lbKey(1), 273 append(s.lbKey(1<<63-1), byte(0x00)), 274 ) 275 if err != nil { 276 return err 277 } 278 defer itr.Close() 279 280 for itr.Valid() && numToPrune > 0 { 281 if err = batch.Delete(itr.Key()); err != nil { 282 return err 283 } 284 itr.Next() 285 numToPrune-- 286 } 287 288 return itr.Error() 289 } 290 291 func (s *dbs) sizeKey() []byte { 292 key, err := orderedcode.Append(nil, prefixSize) 293 if err != nil { 294 panic(err) 295 } 296 return key 297 } 298 299 func (s *dbs) lbKey(height int64) []byte { 300 key, err := orderedcode.Append(nil, prefixLightBlock, height) 301 if err != nil { 302 panic(err) 303 } 304 return key 305 } 306 307 func (s *dbs) decodeLbKey(key []byte) (height int64, err error) { 308 var lightBlockPrefix int64 309 remaining, err := orderedcode.Parse(string(key), &lightBlockPrefix, &height) 310 if err != nil { 311 err = fmt.Errorf("failed to parse light block key: %w", err) 312 } 313 if len(remaining) != 0 { 314 err = fmt.Errorf("expected no remainder when parsing light block key but got: %s", remaining) 315 } 316 if lightBlockPrefix != prefixLightBlock { 317 err = fmt.Errorf("expected light block prefix but got: %d", lightBlockPrefix) 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 }