- package lite
-
- import (
- "bytes"
-
- lerr "github.com/tendermint/tendermint/lite/errors"
- "github.com/tendermint/tendermint/types"
- log "github.com/tendermint/tmlibs/log"
- )
-
- var _ Certifier = (*InquiringCertifier)(nil)
-
- // InquiringCertifier implements an auto-updating certifier. It uses a
- // "source" provider to obtain the needed FullCommits to securely sync with
- // validator set changes. It stores properly validated data on the
- // "trusted" local system.
- type InquiringCertifier struct {
- logger log.Logger
- chainID string
- // These are only properly validated data, from local system.
- trusted PersistentProvider
- // This is a source of new info, like a node rpc, or other import method.
- source Provider
- }
-
- // NewInquiringCertifier returns a new InquiringCertifier. It uses the
- // trusted provider to store validated data and the source provider to
- // obtain missing data (e.g. FullCommits).
- //
- // The trusted provider should a CacheProvider, MemProvider or
- // files.Provider. The source provider should be a client.HTTPProvider.
- func NewInquiringCertifier(chainID string, trusted PersistentProvider, source Provider) *InquiringCertifier {
- return &InquiringCertifier{
- logger: log.NewNopLogger(),
- chainID: chainID,
- trusted: trusted,
- source: source,
- }
- }
-
- func (ic *InquiringCertifier) SetLogger(logger log.Logger) {
- logger = logger.With("module", "lite")
- ic.logger = logger
- ic.trusted.SetLogger(logger)
- ic.source.SetLogger(logger)
- }
-
- // Implements Certifier.
- func (ic *InquiringCertifier) ChainID() string {
- return ic.chainID
- }
-
- // Implements Certifier.
- //
- // If the validators have changed since the last know time, it looks to
- // ic.trusted and ic.source to prove the new validators. On success, it will
- // try to store the SignedHeader in ic.trusted if the next
- // validator can be sourced.
- func (ic *InquiringCertifier) Certify(shdr types.SignedHeader) error {
-
- // Get the latest known full commit <= h-1 from our trusted providers.
- // The full commit at h-1 contains the valset to sign for h.
- h := shdr.Height - 1
- tfc, err := ic.trusted.LatestFullCommit(ic.chainID, 1, h)
- if err != nil {
- return err
- }
-
- if tfc.Height() == h {
- // Return error if valset doesn't match.
- if !bytes.Equal(
- tfc.NextValidators.Hash(),
- shdr.Header.ValidatorsHash) {
- return lerr.ErrUnexpectedValidators(
- tfc.NextValidators.Hash(),
- shdr.Header.ValidatorsHash)
- }
- } else {
- // If valset doesn't match...
- if !bytes.Equal(tfc.NextValidators.Hash(),
- shdr.Header.ValidatorsHash) {
- // ... update.
- tfc, err = ic.updateToHeight(h)
- if err != nil {
- return err
- }
- // Return error if valset _still_ doesn't match.
- if !bytes.Equal(tfc.NextValidators.Hash(),
- shdr.Header.ValidatorsHash) {
- return lerr.ErrUnexpectedValidators(
- tfc.NextValidators.Hash(),
- shdr.Header.ValidatorsHash)
- }
- }
- }
-
- // Certify the signed header using the matching valset.
- cert := NewBaseCertifier(ic.chainID, tfc.Height()+1, tfc.NextValidators)
- err = cert.Certify(shdr)
- if err != nil {
- return err
- }
-
- // Get the next validator set.
- nextValset, err := ic.source.ValidatorSet(ic.chainID, shdr.Height+1)
- if lerr.IsErrMissingValidators(err) {
- // Ignore this error.
- return nil
- } else if err != nil {
- return err
- }
-
- // Create filled FullCommit.
- nfc := FullCommit{
- SignedHeader: shdr,
- Validators: tfc.NextValidators,
- NextValidators: nextValset,
- }
- // Validate the full commit. This checks the cryptographic
- // signatures of Commit against Validators.
- if err := nfc.ValidateFull(ic.chainID); err != nil {
- return err
- }
- // Trust it.
- return ic.trusted.SaveFullCommit(nfc)
- }
-
- // verifyAndSave will verify if this is a valid source full commit given the
- // best match trusted full commit, and if good, persist to ic.trusted.
- // Returns ErrTooMuchChange when >2/3 of tfc did not sign sfc.
- // Panics if tfc.Height() >= sfc.Height().
- func (ic *InquiringCertifier) verifyAndSave(tfc, sfc FullCommit) error {
- if tfc.Height() >= sfc.Height() {
- panic("should not happen")
- }
- err := tfc.NextValidators.VerifyFutureCommit(
- sfc.Validators,
- ic.chainID, sfc.SignedHeader.Commit.BlockID,
- sfc.SignedHeader.Height, sfc.SignedHeader.Commit,
- )
- if err != nil {
- return err
- }
-
- return ic.trusted.SaveFullCommit(sfc)
- }
-
- // updateToHeight will use divide-and-conquer to find a path to h.
- // Returns nil error iff we successfully verify and persist a full commit
- // for height h, using repeated applications of bisection if necessary.
- //
- // Returns ErrCommitNotFound if source provider doesn't have the commit for h.
- func (ic *InquiringCertifier) updateToHeight(h int64) (FullCommit, error) {
-
- // Fetch latest full commit from source.
- sfc, err := ic.source.LatestFullCommit(ic.chainID, h, h)
- if err != nil {
- return FullCommit{}, err
- }
-
- // Validate the full commit. This checks the cryptographic
- // signatures of Commit against Validators.
- if err := sfc.ValidateFull(ic.chainID); err != nil {
- return FullCommit{}, err
- }
-
- // If sfc.Height() != h, we can't do it.
- if sfc.Height() != h {
- return FullCommit{}, lerr.ErrCommitNotFound()
- }
-
- FOR_LOOP:
- for {
- // Fetch latest full commit from trusted.
- tfc, err := ic.trusted.LatestFullCommit(ic.chainID, 1, h)
- if err != nil {
- return FullCommit{}, err
- }
- // We have nothing to do.
- if tfc.Height() == h {
- return tfc, nil
- }
-
- // Try to update to full commit with checks.
- err = ic.verifyAndSave(tfc, sfc)
- if err == nil {
- // All good!
- return sfc, nil
- }
-
- // Handle special case when err is ErrTooMuchChange.
- if lerr.IsErrTooMuchChange(err) {
- // Divide and conquer.
- start, end := tfc.Height(), sfc.Height()
- if !(start < end) {
- panic("should not happen")
- }
- mid := (start + end) / 2
- _, err = ic.updateToHeight(mid)
- if err != nil {
- return FullCommit{}, err
- }
- // If we made it to mid, we retry.
- continue FOR_LOOP
- }
- return FullCommit{}, err
- }
- }
-
- func (ic *InquiringCertifier) LastTrustedHeight() int64 {
- fc, err := ic.trusted.LatestFullCommit(ic.chainID, 1, 1<<63-1)
- if err != nil {
- panic("should not happen")
- }
- return fc.Height()
- }
|