github.com/vipernet-xyz/tendermint-core@v0.32.0/lite/dynamic_verifier.go (about)

     1  package lite
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"sync"
     7  
     8  	log "github.com/tendermint/tendermint/libs/log"
     9  	lerr "github.com/tendermint/tendermint/lite/errors"
    10  	"github.com/tendermint/tendermint/types"
    11  )
    12  
    13  const sizeOfPendingMap = 1024
    14  
    15  var _ Verifier = (*DynamicVerifier)(nil)
    16  
    17  // DynamicVerifier implements an auto-updating Verifier.  It uses a
    18  // "source" provider to obtain the needed FullCommits to securely sync with
    19  // validator set changes.  It stores properly validated data on the
    20  // "trusted" local system.
    21  // TODO: make this single threaded and create a new
    22  // ConcurrentDynamicVerifier that wraps it with concurrency.
    23  // see https://github.com/tendermint/tendermint/issues/3170
    24  type DynamicVerifier struct {
    25  	chainID string
    26  	logger  log.Logger
    27  
    28  	// Already validated, stored locally
    29  	trusted PersistentProvider
    30  
    31  	// New info, like a node rpc, or other import method.
    32  	source Provider
    33  
    34  	// pending map to synchronize concurrent verification requests
    35  	mtx                  sync.Mutex
    36  	pendingVerifications map[int64]chan struct{}
    37  }
    38  
    39  // NewDynamicVerifier returns a new DynamicVerifier. It uses the
    40  // trusted provider to store validated data and the source provider to
    41  // obtain missing data (e.g. FullCommits).
    42  //
    43  // The trusted provider should be a DBProvider.
    44  // The source provider should be a client.HTTPProvider.
    45  func NewDynamicVerifier(chainID string, trusted PersistentProvider, source Provider) *DynamicVerifier {
    46  	return &DynamicVerifier{
    47  		logger:               log.NewNopLogger(),
    48  		chainID:              chainID,
    49  		trusted:              trusted,
    50  		source:               source,
    51  		pendingVerifications: make(map[int64]chan struct{}, sizeOfPendingMap),
    52  	}
    53  }
    54  
    55  func (dv *DynamicVerifier) SetLogger(logger log.Logger) {
    56  	logger = logger.With("module", "lite")
    57  	dv.logger = logger
    58  	dv.trusted.SetLogger(logger)
    59  	dv.source.SetLogger(logger)
    60  }
    61  
    62  // Implements Verifier.
    63  func (dv *DynamicVerifier) ChainID() string {
    64  	return dv.chainID
    65  }
    66  
    67  // Implements Verifier.
    68  //
    69  // If the validators have changed since the last known time, it looks to
    70  // dv.trusted and dv.source to prove the new validators.  On success, it will
    71  // try to store the SignedHeader in dv.trusted if the next
    72  // validator can be sourced.
    73  func (dv *DynamicVerifier) Verify(shdr types.SignedHeader) error {
    74  
    75  	// Performs synchronization for multi-threads verification at the same height.
    76  	dv.mtx.Lock()
    77  	if pending := dv.pendingVerifications[shdr.Height]; pending != nil {
    78  		dv.mtx.Unlock()
    79  		<-pending // pending is chan struct{}
    80  	} else {
    81  		pending := make(chan struct{})
    82  		dv.pendingVerifications[shdr.Height] = pending
    83  		defer func() {
    84  			close(pending)
    85  			dv.mtx.Lock()
    86  			delete(dv.pendingVerifications, shdr.Height)
    87  			dv.mtx.Unlock()
    88  		}()
    89  		dv.mtx.Unlock()
    90  	}
    91  
    92  	//Get the exact trusted commit for h, and if it is
    93  	// equal to shdr, then it's already trusted, so
    94  	// just return nil.
    95  	trustedFCSameHeight, err := dv.trusted.LatestFullCommit(dv.chainID, shdr.Height, shdr.Height)
    96  	if err == nil {
    97  		// If loading trust commit successfully, and trust commit equal to shdr, then don't verify it,
    98  		// just return nil.
    99  		if bytes.Equal(trustedFCSameHeight.SignedHeader.Hash(), shdr.Hash()) {
   100  			dv.logger.Info(fmt.Sprintf("Load full commit at height %d from cache, there is not need to verify.", shdr.Height))
   101  			return nil
   102  		}
   103  	} else if !lerr.IsErrCommitNotFound(err) {
   104  		// Return error if it is not CommitNotFound error
   105  		dv.logger.Info(fmt.Sprintf("Encountered unknown error in loading full commit at height %d.", shdr.Height))
   106  		return err
   107  	}
   108  
   109  	// Get the latest known full commit <= h-1 from our trusted providers.
   110  	// The full commit at h-1 contains the valset to sign for h.
   111  	prevHeight := shdr.Height - 1
   112  	trustedFC, err := dv.trusted.LatestFullCommit(dv.chainID, 1, prevHeight)
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	// sync up to the prevHeight and assert our latest NextValidatorSet
   118  	// is the ValidatorSet for the SignedHeader
   119  	if trustedFC.Height() == prevHeight {
   120  		// Return error if valset doesn't match.
   121  		if !bytes.Equal(
   122  			trustedFC.NextValidators.Hash(),
   123  			shdr.Header.ValidatorsHash) {
   124  			return lerr.ErrUnexpectedValidators(
   125  				trustedFC.NextValidators.Hash(),
   126  				shdr.Header.ValidatorsHash)
   127  		}
   128  	} else {
   129  		// If valset doesn't match, try to update
   130  		if !bytes.Equal(
   131  			trustedFC.NextValidators.Hash(),
   132  			shdr.Header.ValidatorsHash) {
   133  			// ... update.
   134  			trustedFC, err = dv.updateToHeight(prevHeight)
   135  			if err != nil {
   136  				return err
   137  			}
   138  			// Return error if valset _still_ doesn't match.
   139  			if !bytes.Equal(trustedFC.NextValidators.Hash(),
   140  				shdr.Header.ValidatorsHash) {
   141  				return lerr.ErrUnexpectedValidators(
   142  					trustedFC.NextValidators.Hash(),
   143  					shdr.Header.ValidatorsHash)
   144  			}
   145  		}
   146  	}
   147  
   148  	// Verify the signed header using the matching valset.
   149  	cert := NewBaseVerifier(dv.chainID, trustedFC.Height()+1, trustedFC.NextValidators)
   150  	err = cert.Verify(shdr)
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	// By now, the SignedHeader is fully validated and we're synced up to
   156  	// SignedHeader.Height - 1. To sync to SignedHeader.Height, we need
   157  	// the validator set at SignedHeader.Height + 1 so we can verify the
   158  	// SignedHeader.NextValidatorSet.
   159  	// TODO: is the ValidateFull below mostly redundant with the BaseVerifier.Verify above?
   160  	// See https://github.com/tendermint/tendermint/issues/3174.
   161  
   162  	// Get the next validator set.
   163  	nextValset, err := dv.source.ValidatorSet(dv.chainID, shdr.Height+1)
   164  	if lerr.IsErrUnknownValidators(err) {
   165  		// Ignore this error.
   166  		return nil
   167  	} else if err != nil {
   168  		return err
   169  	}
   170  
   171  	// Create filled FullCommit.
   172  	nfc := FullCommit{
   173  		SignedHeader:   shdr,
   174  		Validators:     trustedFC.NextValidators,
   175  		NextValidators: nextValset,
   176  	}
   177  	// Validate the full commit.  This checks the cryptographic
   178  	// signatures of Commit against Validators.
   179  	if err := nfc.ValidateFull(dv.chainID); err != nil {
   180  		return err
   181  	}
   182  	// Trust it.
   183  	return dv.trusted.SaveFullCommit(nfc)
   184  }
   185  
   186  // verifyAndSave will verify if this is a valid source full commit given the
   187  // best match trusted full commit, and if good, persist to dv.trusted.
   188  // Returns ErrNotEnoughVotingPowerSigned when >2/3 of trustedFC did not sign sourceFC.
   189  // Panics if trustedFC.Height() >= sourceFC.Height().
   190  func (dv *DynamicVerifier) verifyAndSave(trustedFC, sourceFC FullCommit) error {
   191  	if trustedFC.Height() >= sourceFC.Height() {
   192  		panic("should not happen")
   193  	}
   194  	err := trustedFC.NextValidators.VerifyFutureCommit(
   195  		sourceFC.Validators,
   196  		dv.chainID, sourceFC.SignedHeader.Commit.BlockID,
   197  		sourceFC.SignedHeader.Height, sourceFC.SignedHeader.Commit,
   198  	)
   199  	if err != nil {
   200  		return err
   201  	}
   202  
   203  	return dv.trusted.SaveFullCommit(sourceFC)
   204  }
   205  
   206  // updateToHeight will use divide-and-conquer to find a path to h.
   207  // Returns nil error iff we successfully verify and persist a full commit
   208  // for height h, using repeated applications of bisection if necessary.
   209  //
   210  // Returns ErrCommitNotFound if source provider doesn't have the commit for h.
   211  func (dv *DynamicVerifier) updateToHeight(h int64) (FullCommit, error) {
   212  
   213  	// Fetch latest full commit from source.
   214  	sourceFC, err := dv.source.LatestFullCommit(dv.chainID, h, h)
   215  	if err != nil {
   216  		return FullCommit{}, err
   217  	}
   218  
   219  	// If sourceFC.Height() != h, we can't do it.
   220  	if sourceFC.Height() != h {
   221  		return FullCommit{}, lerr.ErrCommitNotFound()
   222  	}
   223  
   224  	// Validate the full commit.  This checks the cryptographic
   225  	// signatures of Commit against Validators.
   226  	if err := sourceFC.ValidateFull(dv.chainID); err != nil {
   227  		return FullCommit{}, err
   228  	}
   229  
   230  	// Verify latest FullCommit against trusted FullCommits
   231  FOR_LOOP:
   232  	for {
   233  		// Fetch latest full commit from trusted.
   234  		trustedFC, err := dv.trusted.LatestFullCommit(dv.chainID, 1, h)
   235  		if err != nil {
   236  			return FullCommit{}, err
   237  		}
   238  		// We have nothing to do.
   239  		if trustedFC.Height() == h {
   240  			return trustedFC, nil
   241  		}
   242  
   243  		// Try to update to full commit with checks.
   244  		err = dv.verifyAndSave(trustedFC, sourceFC)
   245  		if err == nil {
   246  			// All good!
   247  			return sourceFC, nil
   248  		}
   249  
   250  		// Handle special case when err is ErrNotEnoughVotingPowerSigned.
   251  		if types.IsErrNotEnoughVotingPowerSigned(err) {
   252  			// Divide and conquer.
   253  			start, end := trustedFC.Height(), sourceFC.Height()
   254  			if !(start < end) {
   255  				panic("should not happen")
   256  			}
   257  			mid := (start + end) / 2
   258  			_, err = dv.updateToHeight(mid)
   259  			if err != nil {
   260  				return FullCommit{}, err
   261  			}
   262  			// If we made it to mid, we retry.
   263  			continue FOR_LOOP
   264  		}
   265  		return FullCommit{}, err
   266  	}
   267  }
   268  
   269  func (dv *DynamicVerifier) LastTrustedHeight() int64 {
   270  	fc, err := dv.trusted.LatestFullCommit(dv.chainID, 1, 1<<63-1)
   271  	if err != nil {
   272  		panic("should not happen")
   273  	}
   274  	return fc.Height()
   275  }