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