- package lite
-
- import (
- "bytes"
-
- log "github.com/tendermint/tendermint/libs/log"
- lerr "github.com/tendermint/tendermint/lite/errors"
- "github.com/tendermint/tendermint/types"
- )
-
- var _ Verifier = (*DynamicVerifier)(nil)
-
- // DynamicVerifier implements an auto-updating Verifier. 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 DynamicVerifier 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
- }
-
- // NewDynamicVerifier returns a new DynamicVerifier. 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 NewDynamicVerifier(chainID string, trusted PersistentProvider, source Provider) *DynamicVerifier {
- return &DynamicVerifier{
- logger: log.NewNopLogger(),
- chainID: chainID,
- trusted: trusted,
- source: source,
- }
- }
-
- func (ic *DynamicVerifier) SetLogger(logger log.Logger) {
- logger = logger.With("module", "lite")
- ic.logger = logger
- ic.trusted.SetLogger(logger)
- ic.source.SetLogger(logger)
- }
-
- // Implements Verifier.
- func (ic *DynamicVerifier) ChainID() string {
- return ic.chainID
- }
-
- // Implements Verifier.
- //
- // If the validators have changed since the last known 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 *DynamicVerifier) 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
- trustedFC, err := ic.trusted.LatestFullCommit(ic.chainID, 1, h)
- if err != nil {
- return err
- }
-
- if trustedFC.Height() == h {
- // Return error if valset doesn't match.
- if !bytes.Equal(
- trustedFC.NextValidators.Hash(),
- shdr.Header.ValidatorsHash) {
- return lerr.ErrUnexpectedValidators(
- trustedFC.NextValidators.Hash(),
- shdr.Header.ValidatorsHash)
- }
- } else {
- // If valset doesn't match...
- if !bytes.Equal(trustedFC.NextValidators.Hash(),
- shdr.Header.ValidatorsHash) {
- // ... update.
- trustedFC, err = ic.updateToHeight(h)
- if err != nil {
- return err
- }
- // Return error if valset _still_ doesn't match.
- if !bytes.Equal(trustedFC.NextValidators.Hash(),
- shdr.Header.ValidatorsHash) {
- return lerr.ErrUnexpectedValidators(
- trustedFC.NextValidators.Hash(),
- shdr.Header.ValidatorsHash)
- }
- }
- }
-
- // Certify the signed header using the matching valset.
- cert := NewBaseVerifier(ic.chainID, trustedFC.Height()+1, trustedFC.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.IsErrUnknownValidators(err) {
- // Ignore this error.
- return nil
- } else if err != nil {
- return err
- }
-
- // Create filled FullCommit.
- nfc := FullCommit{
- SignedHeader: shdr,
- Validators: trustedFC.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 trustedFC did not sign sourceFC.
- // Panics if trustedFC.Height() >= sourceFC.Height().
- func (ic *DynamicVerifier) verifyAndSave(trustedFC, sourceFC FullCommit) error {
- if trustedFC.Height() >= sourceFC.Height() {
- panic("should not happen")
- }
- err := trustedFC.NextValidators.VerifyFutureCommit(
- sourceFC.Validators,
- ic.chainID, sourceFC.SignedHeader.Commit.BlockID,
- sourceFC.SignedHeader.Height, sourceFC.SignedHeader.Commit,
- )
- if err != nil {
- return err
- }
-
- return ic.trusted.SaveFullCommit(sourceFC)
- }
-
- // 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 *DynamicVerifier) updateToHeight(h int64) (FullCommit, error) {
-
- // Fetch latest full commit from source.
- sourceFC, 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 := sourceFC.ValidateFull(ic.chainID); err != nil {
- return FullCommit{}, err
- }
-
- // If sourceFC.Height() != h, we can't do it.
- if sourceFC.Height() != h {
- return FullCommit{}, lerr.ErrCommitNotFound()
- }
-
- FOR_LOOP:
- for {
- // Fetch latest full commit from trusted.
- trustedFC, err := ic.trusted.LatestFullCommit(ic.chainID, 1, h)
- if err != nil {
- return FullCommit{}, err
- }
- // We have nothing to do.
- if trustedFC.Height() == h {
- return trustedFC, nil
- }
-
- // Try to update to full commit with checks.
- err = ic.verifyAndSave(trustedFC, sourceFC)
- if err == nil {
- // All good!
- return sourceFC, nil
- }
-
- // Handle special case when err is ErrTooMuchChange.
- if lerr.IsErrTooMuchChange(err) {
- // Divide and conquer.
- start, end := trustedFC.Height(), sourceFC.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 *DynamicVerifier) LastTrustedHeight() int64 {
- fc, err := ic.trusted.LatestFullCommit(ic.chainID, 1, 1<<63-1)
- if err != nil {
- panic("should not happen")
- }
- return fc.Height()
- }
|