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