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