@ -18,12 +18,17 @@ var _ Verifier = (*DynamicVerifier)(nil)
// "source" provider to obtain the needed FullCommits to securely sync with
// "source" provider to obtain the needed FullCommits to securely sync with
// validator set changes. It stores properly validated data on the
// validator set changes. It stores properly validated data on the
// "trusted" local system.
// "trusted" local system.
// TODO: make this single threaded and create a new
// ConcurrentDynamicVerifier that wraps it with concurrency.
// see https://github.com/tendermint/tendermint/issues/3170
type DynamicVerifier struct {
type DynamicVerifier struct {
logger log . Logger
chainID string
chainID string
// These are only properly validated data, from local system.
logger log . Logger
// Already validated, stored locally
trusted PersistentProvider
trusted PersistentProvider
// This is a source of new info, like a node rpc, or other import method.
// New info, like a node rpc, or other import method.
source Provider
source Provider
// pending map to synchronize concurrent verification requests
// pending map to synchronize concurrent verification requests
@ -35,8 +40,8 @@ type DynamicVerifier struct {
// trusted provider to store validated data and the source provider to
// trusted provider to store validated data and the source provider to
// obtain missing data (e.g. FullCommits).
// obtain missing data (e.g. FullCommits).
//
//
// The trusted provider should a CacheProvider, MemProvider or
// files.Provider. The source provider should be a client.HTTPProvider.
// The trusted provider should be a DBProvider.
// The source provider should be a client.HTTPProvider.
func NewDynamicVerifier ( chainID string , trusted PersistentProvider , source Provider ) * DynamicVerifier {
func NewDynamicVerifier ( chainID string , trusted PersistentProvider , source Provider ) * DynamicVerifier {
return & DynamicVerifier {
return & DynamicVerifier {
logger : log . NewNopLogger ( ) ,
logger : log . NewNopLogger ( ) ,
@ -47,68 +52,71 @@ func NewDynamicVerifier(chainID string, trusted PersistentProvider, source Provi
}
}
}
}
func ( ic * DynamicVerifier ) SetLogger ( logger log . Logger ) {
func ( dv * DynamicVerifier ) SetLogger ( logger log . Logger ) {
logger = logger . With ( "module" , "lite" )
logger = logger . With ( "module" , "lite" )
ic . logger = logger
ic . trusted . SetLogger ( logger )
ic . source . SetLogger ( logger )
dv . logger = logger
dv . trusted . SetLogger ( logger )
dv . source . SetLogger ( logger )
}
}
// Implements Verifier.
// Implements Verifier.
func ( ic * DynamicVerifier ) ChainID ( ) string {
return ic . chainID
func ( dv * DynamicVerifier ) ChainID ( ) string {
return dv . chainID
}
}
// Implements Verifier.
// Implements Verifier.
//
//
// If the validators have changed since the last known time, it looks to
// 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
// dv.trusted and dv .source to prove the new validators. On success, it will
// try to store the SignedHeader in dv .trusted if the next
// validator can be sourced.
// validator can be sourced.
func ( ic * DynamicVerifier ) Verify ( shdr types . SignedHeader ) error {
func ( dv * DynamicVerifier ) Verify ( shdr types . SignedHeader ) error {
// Performs synchronization for multi-threads verification at the same height.
// Performs synchronization for multi-threads verification at the same height.
ic . mtx . Lock ( )
if pending := ic . pendingVerifications [ shdr . Height ] ; pending != nil {
ic . mtx . Unlock ( )
dv . mtx . Lock ( )
if pending := dv . pendingVerifications [ shdr . Height ] ; pending != nil {
dv . mtx . Unlock ( )
<- pending // pending is chan struct{}
<- pending // pending is chan struct{}
} else {
} else {
pending := make ( chan struct { } )
pending := make ( chan struct { } )
ic . pendingVerifications [ shdr . Height ] = pending
dv . pendingVerifications [ shdr . Height ] = pending
defer func ( ) {
defer func ( ) {
close ( pending )
close ( pending )
ic . mtx . Lock ( )
delete ( ic . pendingVerifications , shdr . Height )
ic . mtx . Unlock ( )
dv . mtx . Lock ( )
delete ( dv . pendingVerifications , shdr . Height )
dv . mtx . Unlock ( )
} ( )
} ( )
ic . mtx . Unlock ( )
dv . mtx . Unlock ( )
}
}
//Get the exact trusted commit for h, and if it is
//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 )
// equal to shdr, then it's already trusted, so
// just return nil.
trustedFCSameHeight , err := dv . trusted . LatestFullCommit ( dv . chainID , shdr . Height , shdr . Height )
if err == nil {
if err == nil {
// If loading trust commit successfully, and trust commit equal to shdr, then don't verify it,
// If loading trust commit successfully, and trust commit equal to shdr, then don't verify it,
// just return nil.
// just return nil.
if bytes . Equal ( trustedFCSameHeight . SignedHeader . Hash ( ) , shdr . Hash ( ) ) {
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 ) )
dv . logger . Info ( fmt . Sprintf ( "Load full commit at height %d from cache, there is not need to verify." , shdr . Height ) )
return nil
return nil
}
}
} else if ! lerr . IsErrCommitNotFound ( err ) {
} else if ! lerr . IsErrCommitNotFound ( err ) {
// Return error if it is not CommitNotFound error
// 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 ) )
dv . logger . Info ( fmt . Sprintf ( "Encountered unknown error in loading full commit at height %d." , shdr . Height ) )
return err
return err
}
}
// Get the latest known full commit <= h-1 from our trusted providers.
// 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.
// 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 )
prevHeig ht := shdr . Height - 1
trustedFC , err := dv . trusted . LatestFullCommit ( dv . chainID , 1 , prevHeig ht )
if err != nil {
if err != nil {
return err
return err
}
}
if trustedFC . Height ( ) == h {
// sync up to the prevHeight and assert our latest NextValidatorSet
// is the ValidatorSet for the SignedHeader
if trustedFC . Height ( ) == prevHeight {
// Return error if valset doesn't match.
// Return error if valset doesn't match.
if ! bytes . Equal (
if ! bytes . Equal (
trustedFC . NextValidators . Hash ( ) ,
trustedFC . NextValidators . Hash ( ) ,
@ -118,11 +126,12 @@ func (ic *DynamicVerifier) Verify(shdr types.SignedHeader) error {
shdr . Header . ValidatorsHash )
shdr . Header . ValidatorsHash )
}
}
} else {
} else {
// If valset doesn't match...
if ! bytes . Equal ( trustedFC . NextValidators . Hash ( ) ,
// If valset doesn't match, try to update
if ! bytes . Equal (
trustedFC . NextValidators . Hash ( ) ,
shdr . Header . ValidatorsHash ) {
shdr . Header . ValidatorsHash ) {
// ... update.
// ... update.
trustedFC , err = ic . updateToHeight ( h )
trustedFC , err = dv . updateToHeight ( prevHeig ht )
if err != nil {
if err != nil {
return err
return err
}
}
@ -137,14 +146,21 @@ func (ic *DynamicVerifier) Verify(shdr types.SignedHeader) error {
}
}
// Verify the signed header using the matching valset.
// Verify the signed header using the matching valset.
cert := NewBaseVerifier ( ic . chainID , trustedFC . Height ( ) + 1 , trustedFC . NextValidators )
cert := NewBaseVerifier ( dv . chainID , trustedFC . Height ( ) + 1 , trustedFC . NextValidators )
err = cert . Verify ( shdr )
err = cert . Verify ( shdr )
if err != nil {
if err != nil {
return err
return err
}
}
// By now, the SignedHeader is fully validated and we're synced up to
// SignedHeader.Height - 1. To sync to SignedHeader.Height, we need
// the validator set at SignedHeader.Height + 1 so we can verify the
// SignedHeader.NextValidatorSet.
// TODO: is the ValidateFull below mostly redundant with the BaseVerifier.Verify above?
// See https://github.com/tendermint/tendermint/issues/3174.
// Get the next validator set.
// Get the next validator set.
nextValset , err := ic . source . ValidatorSet ( ic . chainID , shdr . Height + 1 )
nextValset , err := dv . source . ValidatorSet ( dv . chainID , shdr . Height + 1 )
if lerr . IsErrUnknownValidators ( err ) {
if lerr . IsErrUnknownValidators ( err ) {
// Ignore this error.
// Ignore this error.
return nil
return nil
@ -160,31 +176,31 @@ func (ic *DynamicVerifier) Verify(shdr types.SignedHeader) error {
}
}
// Validate the full commit. This checks the cryptographic
// Validate the full commit. This checks the cryptographic
// signatures of Commit against Validators.
// signatures of Commit against Validators.
if err := nfc . ValidateFull ( ic . chainID ) ; err != nil {
if err := nfc . ValidateFull ( dv . chainID ) ; err != nil {
return err
return err
}
}
// Trust it.
// Trust it.
return ic . trusted . SaveFullCommit ( nfc )
return dv . trusted . SaveFullCommit ( nfc )
}
}
// verifyAndSave will verify if this is a valid source full commit given the
// 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.
// best match trusted full commit, and if good, persist to dv .trusted.
// Returns ErrTooMuchChange when >2/3 of trustedFC did not sign sourceFC.
// Returns ErrTooMuchChange when >2/3 of trustedFC did not sign sourceFC.
// Panics if trustedFC.Height() >= sourceFC.Height().
// Panics if trustedFC.Height() >= sourceFC.Height().
func ( ic * DynamicVerifier ) verifyAndSave ( trustedFC , sourceFC FullCommit ) error {
func ( dv * DynamicVerifier ) verifyAndSave ( trustedFC , sourceFC FullCommit ) error {
if trustedFC . Height ( ) >= sourceFC . Height ( ) {
if trustedFC . Height ( ) >= sourceFC . Height ( ) {
panic ( "should not happen" )
panic ( "should not happen" )
}
}
err := trustedFC . NextValidators . VerifyFutureCommit (
err := trustedFC . NextValidators . VerifyFutureCommit (
sourceFC . Validators ,
sourceFC . Validators ,
ic . chainID , sourceFC . SignedHeader . Commit . BlockID ,
dv . chainID , sourceFC . SignedHeader . Commit . BlockID ,
sourceFC . SignedHeader . Height , sourceFC . SignedHeader . Commit ,
sourceFC . SignedHeader . Height , sourceFC . SignedHeader . Commit ,
)
)
if err != nil {
if err != nil {
return err
return err
}
}
return ic . trusted . SaveFullCommit ( sourceFC )
return dv . trusted . SaveFullCommit ( sourceFC )
}
}
// updateToHeight will use divide-and-conquer to find a path to h.
// updateToHeight will use divide-and-conquer to find a path to h.
@ -192,29 +208,30 @@ func (ic *DynamicVerifier) verifyAndSave(trustedFC, sourceFC FullCommit) error {
// for height h, using repeated applications of bisection if necessary.
// for height h, using repeated applications of bisection if necessary.
//
//
// Returns ErrCommitNotFound if source provider doesn't have the commit for h.
// Returns ErrCommitNotFound if source provider doesn't have the commit for h.
func ( ic * DynamicVerifier ) updateToHeight ( h int64 ) ( FullCommit , error ) {
func ( dv * DynamicVerifier ) updateToHeight ( h int64 ) ( FullCommit , error ) {
// Fetch latest full commit from source.
// Fetch latest full commit from source.
sourceFC , err := ic . source . LatestFullCommit ( ic . chainID , h , h )
sourceFC , err := dv . source . LatestFullCommit ( dv . chainID , h , h )
if err != nil {
if err != nil {
return FullCommit { } , err
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, we can't do it.
if sourceFC . Height ( ) != h {
if sourceFC . Height ( ) != h {
return FullCommit { } , lerr . ErrCommitNotFound ( )
return FullCommit { } , lerr . ErrCommitNotFound ( )
}
}
// Validate the full commit. This checks the cryptographic
// signatures of Commit against Validators.
if err := sourceFC . ValidateFull ( dv . chainID ) ; err != nil {
return FullCommit { } , err
}
// Verify latest FullCommit against trusted FullCommits
FOR_LOOP :
FOR_LOOP :
for {
for {
// Fetch latest full commit from trusted.
// Fetch latest full commit from trusted.
trustedFC , err := ic . trusted . LatestFullCommit ( ic . chainID , 1 , h )
trustedFC , err := dv . trusted . LatestFullCommit ( dv . chainID , 1 , h )
if err != nil {
if err != nil {
return FullCommit { } , err
return FullCommit { } , err
}
}
@ -224,21 +241,21 @@ FOR_LOOP:
}
}
// Try to update to full commit with checks.
// Try to update to full commit with checks.
err = ic . verifyAndSave ( trustedFC , sourceFC )
err = dv . verifyAndSave ( trustedFC , sourceFC )
if err == nil {
if err == nil {
// All good!
// All good!
return sourceFC , nil
return sourceFC , nil
}
}
// Handle special case when err is ErrTooMuchChange.
// Handle special case when err is ErrTooMuchChange.
if lerr . IsErrTooMuchChange ( err ) {
if types . IsErrTooMuchChange ( err ) {
// Divide and conquer.
// Divide and conquer.
start , end := trustedFC . Height ( ) , sourceFC . Height ( )
start , end := trustedFC . Height ( ) , sourceFC . Height ( )
if ! ( start < end ) {
if ! ( start < end ) {
panic ( "should not happen" )
panic ( "should not happen" )
}
}
mid := ( start + end ) / 2
mid := ( start + end ) / 2
_ , err = ic . updateToHeight ( mid )
_ , err = dv . updateToHeight ( mid )
if err != nil {
if err != nil {
return FullCommit { } , err
return FullCommit { } , err
}
}
@ -249,8 +266,8 @@ FOR_LOOP:
}
}
}
}
func ( ic * DynamicVerifier ) LastTrustedHeight ( ) int64 {
fc , err := ic . trusted . LatestFullCommit ( ic . chainID , 1 , 1 << 63 - 1 )
func ( dv * DynamicVerifier ) LastTrustedHeight ( ) int64 {
fc , err := dv . trusted . LatestFullCommit ( dv . chainID , 1 , 1 << 63 - 1 )
if err != nil {
if err != nil {
panic ( "should not happen" )
panic ( "should not happen" )
}
}