- package lite
-
- import (
- "github.com/tendermint/tendermint/types"
-
- liteErr "github.com/tendermint/tendermint/lite/errors"
- )
-
- // Inquiring wraps a dynamic certifier and implements an auto-update strategy. If a call to Certify
- // fails due to a change it validator set, Inquiring 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 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
- }
-
- // NewInquiring 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 NewInquiring(chainID string, fc FullCommit, trusted Provider, source Provider) *Inquiring {
- // store the data in trusted
- // TODO: StoredCommit() can return an error and we need to handle this.
- trusted.StoreCommit(fc)
-
- return &Inquiring{
- cert: NewDynamic(chainID, fc.Validators, fc.Height()),
- trusted: trusted,
- Source: source,
- }
- }
-
- // ChainID returns the chain id.
- func (c *Inquiring) ChainID() string {
- return c.cert.ChainID()
- }
-
- // Validators returns the validator set.
- func (c *Inquiring) Validators() *types.ValidatorSet {
- return c.cert.cert.vSet
- }
-
- // LastHeight returns the last height.
- func (c *Inquiring) LastHeight() int64 {
- 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 !liteErr.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
- return c.trusted.StoreCommit(NewFullCommit(commit, c.Validators()))
- }
-
- // Update will verify if this is a valid change and update
- // the certifying validator set if safe to do so.
- 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 {
- err = c.trusted.StoreCommit(fc)
- }
- return err
- }
-
- func (c *Inquiring) useClosestTrust(h int64) 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 liteErr.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 int64) 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 liteErr.ErrNoPathFound()
- }
- err = c.Update(fc)
-
- // we can handle IsTooMuchChangeErr specially
- if !liteErr.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)
- }
|