package lite
|
|
|
|
import (
|
|
"github.com/tendermint/tendermint/types"
|
|
|
|
liteErr "github.com/tendermint/tendermint/lite/errors"
|
|
)
|
|
|
|
var _ Certifier = (*InquiringCertifier)(nil)
|
|
|
|
// InquiringCertifier wraps a dynamic certifier and implements an auto-update strategy. If a call
|
|
// to Certify fails due to a change it validator set, InquiringCertifier will try and find a
|
|
// previous FullCommit which it can use to safely update the validator set. It uses a source
|
|
// provider to obtain the needed FullCommits. It stores properly validated data on the local system.
|
|
type InquiringCertifier struct {
|
|
cert *DynamicCertifier
|
|
// These are only properly validated data, from local system
|
|
trusted Provider
|
|
// This is a source of new info, like a node rpc, or other import method
|
|
Source Provider
|
|
}
|
|
|
|
// NewInquiringCertifier returns a new Inquiring object. It uses the trusted provider to store
|
|
// validated data and the source provider to obtain missing FullCommits.
|
|
//
|
|
// Example: The trusted provider should a CacheProvider, MemProvider or files.Provider. The source
|
|
// provider should be a client.HTTPProvider.
|
|
func NewInquiringCertifier(chainID string, fc FullCommit, trusted Provider,
|
|
source Provider) (*InquiringCertifier, error) {
|
|
|
|
// store the data in trusted
|
|
err := trusted.StoreCommit(fc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &InquiringCertifier{
|
|
cert: NewDynamicCertifier(chainID, fc.Validators, fc.Height()),
|
|
trusted: trusted,
|
|
Source: source,
|
|
}, nil
|
|
}
|
|
|
|
// ChainID returns the chain id.
|
|
// Implements Certifier.
|
|
func (ic *InquiringCertifier) ChainID() string {
|
|
return ic.cert.ChainID()
|
|
}
|
|
|
|
// Validators returns the validator set.
|
|
func (ic *InquiringCertifier) Validators() *types.ValidatorSet {
|
|
return ic.cert.cert.vSet
|
|
}
|
|
|
|
// LastHeight returns the last height.
|
|
func (ic *InquiringCertifier) LastHeight() int64 {
|
|
return ic.cert.lastHeight
|
|
}
|
|
|
|
// Certify makes sure this is checkpoint is valid.
|
|
//
|
|
// If the validators have changed since the last know time, it looks
|
|
// for a path to prove the new validators.
|
|
//
|
|
// On success, it will store the checkpoint in the store for later viewing
|
|
// Implements Certifier.
|
|
func (ic *InquiringCertifier) Certify(commit Commit) error {
|
|
err := ic.useClosestTrust(commit.Height())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ic.cert.Certify(commit)
|
|
if !liteErr.IsValidatorsChangedErr(err) {
|
|
return err
|
|
}
|
|
err = ic.updateToHash(commit.Header.ValidatorsHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ic.cert.Certify(commit)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// store the new checkpoint
|
|
return ic.trusted.StoreCommit(NewFullCommit(commit, ic.Validators()))
|
|
}
|
|
|
|
// Update will verify if this is a valid change and update
|
|
// the certifying validator set if safe to do so.
|
|
func (ic *InquiringCertifier) Update(fc FullCommit) error {
|
|
err := ic.useClosestTrust(fc.Height())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ic.cert.Update(fc)
|
|
if err == nil {
|
|
err = ic.trusted.StoreCommit(fc)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (ic *InquiringCertifier) useClosestTrust(h int64) error {
|
|
closest, err := ic.trusted.GetByHeight(h)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// if the best seed is not the one we currently use,
|
|
// let's just reset the dynamic validator
|
|
if closest.Height() != ic.LastHeight() {
|
|
ic.cert = NewDynamicCertifier(ic.ChainID(), closest.Validators, closest.Height())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// updateToHash gets the validator hash we want to update to
|
|
// if IsTooMuchChangeErr, we try to find a path by binary search over height
|
|
func (ic *InquiringCertifier) updateToHash(vhash []byte) error {
|
|
// try to get the match, and update
|
|
fc, err := ic.Source.GetByHash(vhash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = ic.cert.Update(fc)
|
|
// handle IsTooMuchChangeErr by using divide and conquer
|
|
if liteErr.IsTooMuchChangeErr(err) {
|
|
err = ic.updateToHeight(fc.Height())
|
|
}
|
|
return err
|
|
}
|
|
|
|
// updateToHeight will use divide-and-conquer to find a path to h
|
|
func (ic *InquiringCertifier) updateToHeight(h int64) error {
|
|
// try to update to this height (with checks)
|
|
fc, err := ic.Source.GetByHeight(h)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
start, end := ic.LastHeight(), fc.Height()
|
|
if end <= start {
|
|
return liteErr.ErrNoPathFound()
|
|
}
|
|
err = ic.Update(fc)
|
|
|
|
// we can handle IsTooMuchChangeErr specially
|
|
if !liteErr.IsTooMuchChangeErr(err) {
|
|
return err
|
|
}
|
|
|
|
// try to update to mid
|
|
mid := (start + end) / 2
|
|
err = ic.updateToHeight(mid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// if we made it to mid, we recurse
|
|
return ic.updateToHeight(h)
|
|
}
|