- package lite
-
- import (
- "bytes"
- "fmt"
- "sync"
-
- log "github.com/tendermint/tendermint/libs/log"
- lerr "github.com/tendermint/tendermint/lite/errors"
- "github.com/tendermint/tendermint/types"
- )
-
- const sizeOfPendingMap = 1024
-
- 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
-
- // pending map to synchronize concurrent verification requests
- mtx sync.Mutex
- pendingVerifications map[int64]chan struct{}
- }
-
- // 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,
- pendingVerifications: make(map[int64]chan struct{}, sizeOfPendingMap),
- }
- }
-
- 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) Verify(shdr types.SignedHeader) error {
-
- // Performs synchronization for multi-threads verification at the same height.
- ic.mtx.Lock()
- if pending := ic.pendingVerifications[shdr.Height]; pending != nil {
- ic.mtx.Unlock()
- <-pending // pending is chan struct{}
- } else {
- pending := make(chan struct{})
- ic.pendingVerifications[shdr.Height] = pending
- defer func() {
- close(pending)
- ic.mtx.Lock()
- delete(ic.pendingVerifications, shdr.Height)
- ic.mtx.Unlock()
- }()
- ic.mtx.Unlock()
- }
- //Get the exact trusted commit for h, and if it is
- // equal to shdr, then don't even verify it,
- // and just return nil.
- trustedFCSameHeight, err := ic.trusted.LatestFullCommit(ic.chainID, shdr.Height, shdr.Height)
- if err == nil {
- // If loading trust commit successfully, and trust commit equal to shdr, then don't verify it,
- // just return nil.
- if bytes.Equal(trustedFCSameHeight.SignedHeader.Hash(), shdr.Hash()) {
- ic.logger.Info(fmt.Sprintf("Load full commit at height %d from cache, there is not need to verify.", shdr.Height))
- return nil
- }
- } else if !lerr.IsErrCommitNotFound(err) {
- // Return error if it is not CommitNotFound error
- ic.logger.Info(fmt.Sprintf("Encountered unknown error in loading full commit at height %d.", shdr.Height))
- return err
- }
-
- // 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)
- }
- }
- }
-
- // Verify the signed header using the matching valset.
- cert := NewBaseVerifier(ic.chainID, trustedFC.Height()+1, trustedFC.NextValidators)
- err = cert.Verify(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()
- }
|