package certifiers
|
|
|
|
import (
|
|
"github.com/tendermint/tendermint/types"
|
|
|
|
certerr "github.com/tendermint/tendermint/certifiers/errors"
|
|
)
|
|
|
|
type Inquiring struct {
|
|
cert *Dynamic
|
|
// 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
|
|
}
|
|
|
|
func NewInquiring(chainID string, fc FullCommit, trusted Provider, source Provider) *Inquiring {
|
|
// store the data in trusted
|
|
trusted.StoreCommit(fc)
|
|
|
|
return &Inquiring{
|
|
cert: NewDynamic(chainID, fc.Validators, fc.Height()),
|
|
trusted: trusted,
|
|
Source: source,
|
|
}
|
|
}
|
|
|
|
func (c *Inquiring) ChainID() string {
|
|
return c.cert.ChainID()
|
|
}
|
|
|
|
func (c *Inquiring) Validators() *types.ValidatorSet {
|
|
return c.cert.cert.vSet
|
|
}
|
|
|
|
func (c *Inquiring) LastHeight() int {
|
|
return c.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
|
|
func (c *Inquiring) Certify(commit Commit) error {
|
|
err := c.useClosestTrust(commit.Height())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = c.cert.Certify(commit)
|
|
if !certerr.IsValidatorsChangedErr(err) {
|
|
return err
|
|
}
|
|
err = c.updateToHash(commit.Header.ValidatorsHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = c.cert.Certify(commit)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// store the new checkpoint
|
|
c.trusted.StoreCommit(
|
|
NewFullCommit(commit, c.Validators()))
|
|
return nil
|
|
}
|
|
|
|
func (c *Inquiring) Update(fc FullCommit) error {
|
|
err := c.useClosestTrust(fc.Height())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = c.cert.Update(fc)
|
|
if err == nil {
|
|
c.trusted.StoreCommit(fc)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *Inquiring) useClosestTrust(h int) error {
|
|
closest, err := c.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() != c.LastHeight() {
|
|
c.cert = NewDynamic(c.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 (c *Inquiring) updateToHash(vhash []byte) error {
|
|
// try to get the match, and update
|
|
fc, err := c.Source.GetByHash(vhash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = c.cert.Update(fc)
|
|
// handle IsTooMuchChangeErr by using divide and conquer
|
|
if certerr.IsTooMuchChangeErr(err) {
|
|
err = c.updateToHeight(fc.Height())
|
|
}
|
|
return err
|
|
}
|
|
|
|
// updateToHeight will use divide-and-conquer to find a path to h
|
|
func (c *Inquiring) updateToHeight(h int) error {
|
|
// try to update to this height (with checks)
|
|
fc, err := c.Source.GetByHeight(h)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
start, end := c.LastHeight(), fc.Height()
|
|
if end <= start {
|
|
return certerr.ErrNoPathFound()
|
|
}
|
|
err = c.Update(fc)
|
|
|
|
// we can handle IsTooMuchChangeErr specially
|
|
if !certerr.IsTooMuchChangeErr(err) {
|
|
return err
|
|
}
|
|
|
|
// try to update to mid
|
|
mid := (start + end) / 2
|
|
err = c.updateToHeight(mid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// if we made it to mid, we recurse
|
|
return c.updateToHeight(h)
|
|
}
|