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