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  }