- package db
-
- import (
- "encoding/binary"
- "fmt"
-
- "github.com/google/orderedcode"
- dbm "github.com/tendermint/tm-db"
-
- tmsync "github.com/tendermint/tendermint/libs/sync"
- "github.com/tendermint/tendermint/light/store"
- tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
- "github.com/tendermint/tendermint/types"
- )
-
- const (
- prefixLightBlock = int64(11)
- prefixSize = int64(12)
- )
-
- type dbs struct {
- db dbm.DB
-
- mtx tmsync.RWMutex
- size uint16
- }
-
- // New returns a Store that wraps any DB
- // If you want to share one DB across many light clients consider using PrefixDB
- func New(db dbm.DB) store.Store {
-
- lightStore := &dbs{db: db}
-
- // retrieve the size of the db
- size := uint16(0)
- bz, err := lightStore.db.Get(lightStore.sizeKey())
- if err == nil && len(bz) > 0 {
- size = unmarshalSize(bz)
- }
- lightStore.size = size
-
- return lightStore
- }
-
- // SaveLightBlock persists LightBlock to the db.
- //
- // Safe for concurrent use by multiple goroutines.
- func (s *dbs) SaveLightBlock(lb *types.LightBlock) error {
- if lb.Height <= 0 {
- panic("negative or zero height")
- }
-
- lbpb, err := lb.ToProto()
- if err != nil {
- return fmt.Errorf("unable to convert light block to protobuf: %w", err)
- }
-
- lbBz, err := lbpb.Marshal()
- if err != nil {
- return fmt.Errorf("marshaling LightBlock: %w", err)
- }
-
- s.mtx.Lock()
- defer s.mtx.Unlock()
-
- b := s.db.NewBatch()
- defer b.Close()
- if err = b.Set(s.lbKey(lb.Height), lbBz); err != nil {
- return err
- }
- if err = b.Set(s.sizeKey(), marshalSize(s.size+1)); err != nil {
- return err
- }
- if err = b.WriteSync(); err != nil {
- return err
- }
- s.size++
-
- return nil
- }
-
- // DeleteLightBlockAndValidatorSet deletes the LightBlock from
- // the db.
- //
- // Safe for concurrent use by multiple goroutines.
- func (s *dbs) DeleteLightBlock(height int64) error {
- if height <= 0 {
- panic("negative or zero height")
- }
-
- s.mtx.Lock()
- defer s.mtx.Unlock()
-
- b := s.db.NewBatch()
- defer b.Close()
- if err := b.Delete(s.lbKey(height)); err != nil {
- return err
- }
- if err := b.Set(s.sizeKey(), marshalSize(s.size-1)); err != nil {
- return err
- }
- if err := b.WriteSync(); err != nil {
- return err
- }
- s.size--
-
- return nil
- }
-
- // LightBlock retrieves the LightBlock at the given height.
- //
- // Safe for concurrent use by multiple goroutines.
- func (s *dbs) LightBlock(height int64) (*types.LightBlock, error) {
- if height <= 0 {
- panic("negative or zero height")
- }
-
- bz, err := s.db.Get(s.lbKey(height))
- if err != nil {
- panic(err)
- }
- if len(bz) == 0 {
- return nil, store.ErrLightBlockNotFound
- }
-
- var lbpb tmproto.LightBlock
- err = lbpb.Unmarshal(bz)
- if err != nil {
- return nil, fmt.Errorf("unmarshal error: %w", err)
- }
-
- lightBlock, err := types.LightBlockFromProto(&lbpb)
- if err != nil {
- return nil, fmt.Errorf("proto conversion error: %w", err)
- }
-
- return lightBlock, err
- }
-
- // LastLightBlockHeight returns the last LightBlock height stored.
- //
- // Safe for concurrent use by multiple goroutines.
- func (s *dbs) LastLightBlockHeight() (int64, error) {
- itr, err := s.db.ReverseIterator(
- s.lbKey(1),
- append(s.lbKey(1<<63-1), byte(0x00)),
- )
- if err != nil {
- panic(err)
- }
- defer itr.Close()
-
- if itr.Valid() {
- return s.decodeLbKey(itr.Key())
- }
-
- return -1, itr.Error()
- }
-
- // FirstLightBlockHeight returns the first LightBlock height stored.
- //
- // Safe for concurrent use by multiple goroutines.
- func (s *dbs) FirstLightBlockHeight() (int64, error) {
- itr, err := s.db.Iterator(
- s.lbKey(1),
- append(s.lbKey(1<<63-1), byte(0x00)),
- )
- if err != nil {
- panic(err)
- }
- defer itr.Close()
-
- if itr.Valid() {
- return s.decodeLbKey(itr.Key())
- }
-
- return -1, itr.Error()
- }
-
- // LightBlockBefore iterates over light blocks until it finds a block before
- // the given height. It returns ErrLightBlockNotFound if no such block exists.
- //
- // Safe for concurrent use by multiple goroutines.
- func (s *dbs) LightBlockBefore(height int64) (*types.LightBlock, error) {
- if height <= 0 {
- panic("negative or zero height")
- }
-
- itr, err := s.db.ReverseIterator(
- s.lbKey(1),
- s.lbKey(height),
- )
- if err != nil {
- panic(err)
- }
- defer itr.Close()
-
- if itr.Valid() {
- var lbpb tmproto.LightBlock
- err = lbpb.Unmarshal(itr.Value())
- if err != nil {
- return nil, fmt.Errorf("unmarshal error: %w", err)
- }
-
- lightBlock, err := types.LightBlockFromProto(&lbpb)
- if err != nil {
- return nil, fmt.Errorf("proto conversion error: %w", err)
- }
- return lightBlock, nil
- }
- if err = itr.Error(); err != nil {
- return nil, err
- }
-
- return nil, store.ErrLightBlockNotFound
- }
-
- // Prune prunes header & validator set pairs until there are only size pairs
- // left.
- //
- // Safe for concurrent use by multiple goroutines.
- func (s *dbs) Prune(size uint16) error {
- // 1) Check how many we need to prune.
- s.mtx.Lock()
- defer s.mtx.Unlock()
- sSize := s.size
-
- if sSize <= size { // nothing to prune
- return nil
- }
- numToPrune := sSize - size
-
- b := s.db.NewBatch()
- defer b.Close()
-
- // 2) use an iterator to batch together all the blocks that need to be deleted
- if err := s.batchDelete(b, numToPrune); err != nil {
- return err
- }
-
- // 3) // update size
- s.size = size
- if err := b.Set(s.sizeKey(), marshalSize(size)); err != nil {
- return fmt.Errorf("failed to persist size: %w", err)
- }
-
- // 4) write batch deletion to disk
- return b.WriteSync()
- }
-
- // Size returns the number of header & validator set pairs.
- //
- // Safe for concurrent use by multiple goroutines.
- func (s *dbs) Size() uint16 {
- s.mtx.RLock()
- defer s.mtx.RUnlock()
- return s.size
- }
-
- func (s *dbs) batchDelete(batch dbm.Batch, numToPrune uint16) error {
- itr, err := s.db.Iterator(
- s.lbKey(1),
- append(s.lbKey(1<<63-1), byte(0x00)),
- )
- if err != nil {
- return err
- }
- defer itr.Close()
-
- for itr.Valid() && numToPrune > 0 {
- if err = batch.Delete(itr.Key()); err != nil {
- return err
- }
- itr.Next()
- numToPrune--
- }
-
- return itr.Error()
- }
-
- func (s *dbs) sizeKey() []byte {
- key, err := orderedcode.Append(nil, prefixSize)
- if err != nil {
- panic(err)
- }
- return key
- }
-
- func (s *dbs) lbKey(height int64) []byte {
- key, err := orderedcode.Append(nil, prefixLightBlock, height)
- if err != nil {
- panic(err)
- }
- return key
- }
-
- func (s *dbs) decodeLbKey(key []byte) (height int64, err error) {
- var lightBlockPrefix int64
- remaining, err := orderedcode.Parse(string(key), &lightBlockPrefix, &height)
- if err != nil {
- err = fmt.Errorf("failed to parse light block key: %w", err)
- }
- if len(remaining) != 0 {
- err = fmt.Errorf("expected no remainder when parsing light block key but got: %s", remaining)
- }
- if lightBlockPrefix != prefixLightBlock {
- err = fmt.Errorf("expected light block prefix but got: %d", lightBlockPrefix)
- }
- return
- }
-
- func marshalSize(size uint16) []byte {
- bs := make([]byte, 2)
- binary.LittleEndian.PutUint16(bs, size)
- return bs
- }
-
- func unmarshalSize(bz []byte) uint16 {
- return binary.LittleEndian.Uint16(bz)
- }
|