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