Files
tendermint/lite2/store/db/db.go
Callum Waters bb7a80ec7e lite2: fetch missing headers (#4362)
Closes #4328

When TrustedHeader(height) is called, if the height is less than the trusted height but the header is not in the trusted store then a function finds the previous lowest height with a trusted header and performs a forwards sequential verification to the header of the height that was given. If no error is found it updates the trusted store with the header and validator set for that height and can then return them to the user.

Commits:

* drafted trusted header

* created function to find previous trusted height

* updates missing headers less than the trusted height

* minor cosmetic tweaks

* incorporated suggestions

* lite2: implement Backwards verification

and add SignedHeaderAfter func to Store interface

Refs https://github.com/tendermint/tendermint/issues/4328#issuecomment-581878549

* remove unused method

* write tests

* start with next height in SignedHeaderAfter func

* fix linter errors

* address Callum's comments

Co-authored-by: Anton Kaliaev <anton.kalyaev@gmail.com>
2020-02-06 12:30:37 +01:00

216 lines
4.7 KiB
Go

package db
import (
"fmt"
"regexp"
"strconv"
"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"
)
type dbs struct {
db dbm.DB
prefix string
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)
return &dbs{db: db, prefix: prefix, cdc: cdc}
}
// SaveSignedHeaderAndNextValidatorSet persists SignedHeader and ValidatorSet
// to the db.
func (s *dbs) SaveSignedHeaderAndNextValidatorSet(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")
}
b := s.db.NewBatch()
b.Set(s.shKey(sh.Height), shBz)
b.Set(s.vsKey(sh.Height+1), valSetBz)
err = b.WriteSync()
b.Close()
return err
}
// DeleteSignedHeaderAndNextValidatorSet deletes SignedHeader and ValidatorSet
// from the db.
func (s *dbs) DeleteSignedHeaderAndNextValidatorSet(height int64) error {
if height <= 0 {
panic("negative or zero height")
}
b := s.db.NewBatch()
b.Delete(s.shKey(height))
b.Delete(s.vsKey(height + 1))
err := b.WriteSync()
b.Close()
return err
}
// SignedHeader loads SignedHeader at the given height.
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.
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.
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.
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
}
func (s *dbs) SignedHeaderAfter(height int64) (*types.SignedHeader, error) {
if height <= 0 {
panic("negative or zero height")
}
itr, err := s.db.ReverseIterator(
s.shKey(height+1),
append(s.shKey(1<<63-1), byte(0x00)),
)
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()
}
panic(fmt.Sprintf("no header after height %d. make sure height is not greater than latest existing height", height))
}
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
}