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) }