Files
tendermint/lite2/store/db/db.go
Callum Waters 5c380cdacb lite2: use bisection for some of backward verification (#4575)
Closes: #4537

Uses SignedHeaderBefore to find header before unverified header and then bisection to verify the header. Only when header is between first and last trusted header height else if before the first trusted header height then regular backwards verification is used.
2020-03-31 16:20:22 +04:00

348 lines
7.0 KiB
Go

package db
import (
"encoding/binary"
"fmt"
"regexp"
"strconv"
"sync"
"github.com/pkg/errors"
"github.com/tendermint/go-amino"
dbm "github.com/tendermint/tm-db"
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
"github.com/tendermint/tendermint/lite2/store"
"github.com/tendermint/tendermint/types"
)
var (
sizeKey = []byte("size")
)
type dbs struct {
db dbm.DB
prefix string
mtx sync.RWMutex
size uint16
cdc *amino.Codec
}
// New returns a Store that wraps any DB (with an optional prefix in case you
// want to use one DB with many light clients).
//
// Objects are marshalled using amino (github.com/tendermint/go-amino)
func New(db dbm.DB, prefix string) store.Store {
cdc := amino.NewCodec()
cryptoAmino.RegisterAmino(cdc)
size := uint16(0)
bz, err := db.Get(sizeKey)
if err == nil && len(bz) > 0 {
size = unmarshalSize(bz)
}
return &dbs{db: db, prefix: prefix, cdc: cdc, size: size}
}
// SaveSignedHeaderAndValidatorSet persists SignedHeader and ValidatorSet to
// the db.
//
// Safe for concurrent use by multiple goroutines.
func (s *dbs) SaveSignedHeaderAndValidatorSet(sh *types.SignedHeader, valSet *types.ValidatorSet) error {
if sh.Height <= 0 {
panic("negative or zero height")
}
shBz, err := s.cdc.MarshalBinaryLengthPrefixed(sh)
if err != nil {
return errors.Wrap(err, "marshalling header")
}
valSetBz, err := s.cdc.MarshalBinaryLengthPrefixed(valSet)
if err != nil {
return errors.Wrap(err, "marshalling validator set")
}
s.mtx.Lock()
defer s.mtx.Unlock()
b := s.db.NewBatch()
b.Set(s.shKey(sh.Height), shBz)
b.Set(s.vsKey(sh.Height), valSetBz)
b.Set(sizeKey, marshalSize(s.size+1))
err = b.WriteSync()
b.Close()
if err == nil {
s.size++
}
return err
}
// DeleteSignedHeaderAndValidatorSet deletes SignedHeader and ValidatorSet from
// the db.
//
// Safe for concurrent use by multiple goroutines.
func (s *dbs) DeleteSignedHeaderAndValidatorSet(height int64) error {
if height <= 0 {
panic("negative or zero height")
}
s.mtx.Lock()
defer s.mtx.Unlock()
b := s.db.NewBatch()
b.Delete(s.shKey(height))
b.Delete(s.vsKey(height))
b.Set(sizeKey, marshalSize(s.size-1))
err := b.WriteSync()
b.Close()
if err == nil {
s.size--
}
return err
}
// SignedHeader loads SignedHeader at the given height.
//
// Safe for concurrent use by multiple goroutines.
func (s *dbs) SignedHeader(height int64) (*types.SignedHeader, error) {
if height <= 0 {
panic("negative or zero height")
}
bz, err := s.db.Get(s.shKey(height))
if err != nil {
panic(err)
}
if len(bz) == 0 {
return nil, store.ErrSignedHeaderNotFound
}
var signedHeader *types.SignedHeader
err = s.cdc.UnmarshalBinaryLengthPrefixed(bz, &signedHeader)
return signedHeader, err
}
// ValidatorSet loads ValidatorSet at the given height.
//
// Safe for concurrent use by multiple goroutines.
func (s *dbs) ValidatorSet(height int64) (*types.ValidatorSet, error) {
if height <= 0 {
panic("negative or zero height")
}
bz, err := s.db.Get(s.vsKey(height))
if err != nil {
panic(err)
}
if len(bz) == 0 {
return nil, store.ErrValidatorSetNotFound
}
var valSet *types.ValidatorSet
err = s.cdc.UnmarshalBinaryLengthPrefixed(bz, &valSet)
return valSet, err
}
// LastSignedHeaderHeight returns the last SignedHeader height stored.
//
// Safe for concurrent use by multiple goroutines.
func (s *dbs) LastSignedHeaderHeight() (int64, error) {
itr, err := s.db.ReverseIterator(
s.shKey(1),
append(s.shKey(1<<63-1), byte(0x00)),
)
if err != nil {
panic(err)
}
defer itr.Close()
for itr.Valid() {
key := itr.Key()
_, height, ok := parseShKey(key)
if ok {
return height, nil
}
itr.Next()
}
return -1, nil
}
// FirstSignedHeaderHeight returns the first SignedHeader height stored.
//
// Safe for concurrent use by multiple goroutines.
func (s *dbs) FirstSignedHeaderHeight() (int64, error) {
itr, err := s.db.Iterator(
s.shKey(1),
append(s.shKey(1<<63-1), byte(0x00)),
)
if err != nil {
panic(err)
}
defer itr.Close()
for itr.Valid() {
key := itr.Key()
_, height, ok := parseShKey(key)
if ok {
return height, nil
}
itr.Next()
}
return -1, nil
}
// SignedHeaderBefore iterates over headers until it finds a header before
// the given height. It returns ErrSignedHeaderNotFound if no such header exists.
//
// Safe for concurrent use by multiple goroutines.
func (s *dbs) SignedHeaderBefore(height int64) (*types.SignedHeader, error) {
if height <= 0 {
panic("negative or zero height")
}
itr, err := s.db.ReverseIterator(
s.shKey(1),
s.shKey(height),
)
if err != nil {
panic(err)
}
defer itr.Close()
for itr.Valid() {
key := itr.Key()
_, existingHeight, ok := parseShKey(key)
if ok {
return s.SignedHeader(existingHeight)
}
itr.Next()
}
return nil, store.ErrSignedHeaderNotFound
}
// Prune prunes header & validator set pairs until there are only size pairs
// left.
//
// Safe for concurrent use by multiple goroutines.
func (s *dbs) Prune(size uint16) error {
// 1) Check how many we need to prune.
s.mtx.RLock()
sSize := s.size
s.mtx.RUnlock()
if sSize <= size { // nothing to prune
return nil
}
numToPrune := sSize - size
// 2) Iterate over headers and perform a batch operation.
itr, err := s.db.Iterator(
s.shKey(1),
append(s.shKey(1<<63-1), byte(0x00)),
)
if err != nil {
panic(err)
}
b := s.db.NewBatch()
pruned := 0
for itr.Valid() && numToPrune > 0 {
key := itr.Key()
_, height, ok := parseShKey(key)
if ok {
b.Delete(s.shKey(height))
b.Delete(s.vsKey(height))
}
itr.Next()
numToPrune--
pruned++
}
itr.Close()
err = b.WriteSync()
b.Close()
if err != nil {
return err
}
// 3) Update size.
s.mtx.Lock()
defer s.mtx.Unlock()
s.size -= uint16(pruned)
if wErr := s.db.SetSync(sizeKey, marshalSize(s.size)); wErr != nil {
return errors.Wrap(wErr, "failed to persist size")
}
return nil
}
// Size returns the number of header & validator set pairs.
//
// Safe for concurrent use by multiple goroutines.
func (s *dbs) Size() uint16 {
s.mtx.RLock()
defer s.mtx.RUnlock()
return s.size
}
func (s *dbs) shKey(height int64) []byte {
return []byte(fmt.Sprintf("sh/%s/%020d", s.prefix, height))
}
func (s *dbs) vsKey(height int64) []byte {
return []byte(fmt.Sprintf("vs/%s/%020d", s.prefix, height))
}
var keyPattern = regexp.MustCompile(`^(sh|vs)/([^/]*)/([0-9]+)$`)
func parseKey(key []byte) (part string, prefix string, height int64, ok bool) {
submatch := keyPattern.FindSubmatch(key)
if submatch == nil {
return "", "", 0, false
}
part = string(submatch[1])
prefix = string(submatch[2])
height, err := strconv.ParseInt(string(submatch[3]), 10, 64)
if err != nil {
return "", "", 0, false
}
ok = true // good!
return
}
func parseShKey(key []byte) (prefix string, height int64, ok bool) {
var part string
part, prefix, height, ok = parseKey(key)
if part != "sh" {
return "", 0, false
}
return
}
func marshalSize(size uint16) []byte {
bs := make([]byte, 2)
binary.LittleEndian.PutUint16(bs, size)
return bs
}
func unmarshalSize(bz []byte) uint16 {
return binary.LittleEndian.Uint16(bz)
}