github.com/DFWallet/tendermint-cosmos@v0.0.2/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/tendermint/tm-db"
    10  
    11  	tmsync "github.com/DFWallet/tendermint-cosmos/libs/sync"
    12  	"github.com/DFWallet/tendermint-cosmos/light/store"
    13  	tmproto "github.com/DFWallet/tendermint-cosmos/proto/tendermint/types"
    14  	"github.com/DFWallet/tendermint-cosmos/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  }