github.com/okex/exchain@v1.8.0/libs/tendermint/lite/dynamic_verifier.go (about)

     1  package lite
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"sync"
     7  
     8  	log "github.com/okex/exchain/libs/tendermint/libs/log"
     9  	lerr "github.com/okex/exchain/libs/tendermint/lite/errors"
    10  	"github.com/okex/exchain/libs/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  	commit := func() error {
   114  		return nil
   115  	}
   116  	if err != nil {
   117  		// if the commit is not found in the local database , fetch the acommit from the source node
   118  		if !lerr.IsErrCommitNotFound(err) {
   119  			return err
   120  		}
   121  		if trustedFC, err = dv.source.LatestFullCommit(dv.chainID, 1, prevHeight); err != nil {
   122  			return err
   123  		} else {
   124  			commit = func() error {
   125  				return dv.trusted.SaveFullCommit(trustedFC)
   126  			}
   127  		}
   128  	}
   129  
   130  	// sync up to the prevHeight and assert our latest NextValidatorSet
   131  	// is the ValidatorSet for the SignedHeader
   132  	if trustedFC.Height() == prevHeight {
   133  		// Return error if valset doesn't match.
   134  		if !bytes.Equal(
   135  			trustedFC.NextValidators.Hash(trustedFC.Height()+1),
   136  			shdr.Header.ValidatorsHash) {
   137  			return lerr.ErrUnexpectedValidators(
   138  				trustedFC.NextValidators.Hash(trustedFC.Height()+1),
   139  				shdr.Header.ValidatorsHash)
   140  		}
   141  	} else {
   142  		// If valset doesn't match, try to update
   143  		if !bytes.Equal(
   144  			trustedFC.NextValidators.Hash(trustedFC.Height()+1),
   145  			shdr.Header.ValidatorsHash) {
   146  			// ... update.
   147  			trustedFC, err = dv.updateToHeight(prevHeight)
   148  			if err != nil {
   149  				return err
   150  			}
   151  			// Return error if valset _still_ doesn't match.
   152  			if !bytes.Equal(trustedFC.NextValidators.Hash(trustedFC.Height()+1),
   153  				shdr.Header.ValidatorsHash) {
   154  				return lerr.ErrUnexpectedValidators(
   155  					trustedFC.NextValidators.Hash(trustedFC.Height()+1),
   156  					shdr.Header.ValidatorsHash)
   157  			}
   158  		}
   159  	}
   160  
   161  	// Verify the signed header using the matching valset.
   162  	cert := NewBaseVerifier(dv.chainID, trustedFC.Height()+1, trustedFC.NextValidators)
   163  	err = cert.Verify(shdr)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	// By now, the SignedHeader is fully validated and we're synced up to
   169  	// SignedHeader.Height - 1. To sync to SignedHeader.Height, we need
   170  	// the validator set at SignedHeader.Height + 1 so we can verify the
   171  	// SignedHeader.NextValidatorSet.
   172  	// TODO: is the ValidateFull below mostly redundant with the BaseVerifier.Verify above?
   173  	// See https://github.com/tendermint/tendermint/issues/3174.
   174  
   175  	// Get the next validator set.
   176  	nextValset, err := dv.source.ValidatorSet(dv.chainID, shdr.Height+1)
   177  	if lerr.IsErrUnknownValidators(err) {
   178  		// Ignore this error.
   179  		return nil
   180  	} else if err != nil {
   181  		return err
   182  	}
   183  
   184  	// Create filled FullCommit.
   185  	nfc := FullCommit{
   186  		SignedHeader:   shdr,
   187  		Validators:     trustedFC.NextValidators,
   188  		NextValidators: nextValset,
   189  	}
   190  	// Validate the full commit.  This checks the cryptographic
   191  	// signatures of Commit against Validators.
   192  	if err := nfc.ValidateFull(dv.chainID); err != nil {
   193  		return err
   194  	}
   195  	// Trust it.
   196  	if err = dv.trusted.SaveFullCommit(nfc); err != nil {
   197  		return err
   198  	}
   199  	return commit()
   200  }
   201  
   202  // verifyAndSave will verify if this is a valid source full commit given the
   203  // best match trusted full commit, and if good, persist to dv.trusted.
   204  // Returns ErrNotEnoughVotingPowerSigned when >2/3 of trustedFC did not sign sourceFC.
   205  // Panics if trustedFC.Height() >= sourceFC.Height().
   206  func (dv *DynamicVerifier) verifyAndSave(trustedFC, sourceFC FullCommit) error {
   207  	if trustedFC.Height() >= sourceFC.Height() {
   208  		panic("should not happen")
   209  	}
   210  	err := trustedFC.NextValidators.VerifyFutureCommit(
   211  		sourceFC.Validators,
   212  		dv.chainID, sourceFC.SignedHeader.Commit.BlockID,
   213  		sourceFC.SignedHeader.Height, sourceFC.SignedHeader.Commit,
   214  	)
   215  	if err != nil {
   216  		return err
   217  	}
   218  
   219  	return dv.trusted.SaveFullCommit(sourceFC)
   220  }
   221  
   222  // updateToHeight will use divide-and-conquer to find a path to h.
   223  // Returns nil error iff we successfully verify and persist a full commit
   224  // for height h, using repeated applications of bisection if necessary.
   225  //
   226  // Returns ErrCommitNotFound if source provider doesn't have the commit for h.
   227  func (dv *DynamicVerifier) updateToHeight(h int64) (FullCommit, error) {
   228  
   229  	// Fetch latest full commit from source.
   230  	sourceFC, err := dv.source.LatestFullCommit(dv.chainID, h, h)
   231  	if err != nil {
   232  		return FullCommit{}, err
   233  	}
   234  
   235  	// If sourceFC.Height() != h, we can't do it.
   236  	if sourceFC.Height() != h {
   237  		return FullCommit{}, lerr.ErrCommitNotFound()
   238  	}
   239  
   240  	// Validate the full commit.  This checks the cryptographic
   241  	// signatures of Commit against Validators.
   242  	if err := sourceFC.ValidateFull(dv.chainID); err != nil {
   243  		return FullCommit{}, err
   244  	}
   245  
   246  	// Verify latest FullCommit against trusted FullCommits
   247  FOR_LOOP:
   248  	for {
   249  		// Fetch latest full commit from trusted.
   250  		trustedFC, err := dv.trusted.LatestFullCommit(dv.chainID, 1, h)
   251  		if err != nil {
   252  			return FullCommit{}, err
   253  		}
   254  		// We have nothing to do.
   255  		if trustedFC.Height() == h {
   256  			return trustedFC, nil
   257  		}
   258  
   259  		// Try to update to full commit with checks.
   260  		err = dv.verifyAndSave(trustedFC, sourceFC)
   261  		if err == nil {
   262  			// All good!
   263  			return sourceFC, nil
   264  		}
   265  
   266  		// Handle special case when err is ErrNotEnoughVotingPowerSigned.
   267  		if types.IsErrNotEnoughVotingPowerSigned(err) {
   268  			// Divide and conquer.
   269  			start, end := trustedFC.Height(), sourceFC.Height()
   270  			if !(start < end) {
   271  				panic("should not happen")
   272  			}
   273  			mid := (start + end) / 2
   274  			_, err = dv.updateToHeight(mid)
   275  			if err != nil {
   276  				return FullCommit{}, err
   277  			}
   278  			// If we made it to mid, we retry.
   279  			continue FOR_LOOP
   280  		}
   281  		return FullCommit{}, err
   282  	}
   283  }
   284  
   285  func (dv *DynamicVerifier) LastTrustedHeight() int64 {
   286  	fc, err := dv.trusted.LatestFullCommit(dv.chainID, 1, 1<<63-1)
   287  	if err != nil {
   288  		panic("should not happen")
   289  	}
   290  	return fc.Height()
   291  }