|
@ -478,7 +478,7 @@ func (c *Client) VerifyHeader(ctx context.Context, newHeader *types.Header, now |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if !bytes.Equal(l.Hash(), newHeader.Hash()) { |
|
|
if !bytes.Equal(l.Hash(), newHeader.Hash()) { |
|
|
return fmt.Errorf("light block header %X does not match newHeader %X", l.Hash(), newHeader.Hash()) |
|
|
|
|
|
|
|
|
return fmt.Errorf("header from primary %X does not match newHeader %X", l.Hash(), newHeader.Hash()) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return c.verifyLightBlock(ctx, l, now) |
|
|
return c.verifyLightBlock(ctx, l, now) |
|
@ -586,7 +586,7 @@ func (c *Client) verifySequential( |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// If some intermediate header is invalid, remove the primary and try again.
|
|
|
// If some intermediate header is invalid, remove the primary and try again.
|
|
|
c.logger.Error("primary sent invalid header -> removing", "err", err, "primary", c.primary) |
|
|
|
|
|
|
|
|
c.logger.Info("primary sent invalid header -> removing", "err", err, "primary", c.primary) |
|
|
|
|
|
|
|
|
replacementBlock, removeErr := c.findNewPrimary(ctx, newLightBlock.Height, true) |
|
|
replacementBlock, removeErr := c.findNewPrimary(ctx, newLightBlock.Height, true) |
|
|
if removeErr != nil { |
|
|
if removeErr != nil { |
|
@ -630,6 +630,10 @@ func (c *Client) verifySequential( |
|
|
// requested from source is kept such that when a verification is made, and the
|
|
|
// requested from source is kept such that when a verification is made, and the
|
|
|
// light client tries again to verify the new light block in the middle, the light
|
|
|
// light client tries again to verify the new light block in the middle, the light
|
|
|
// client does not need to ask for all the same light blocks again.
|
|
|
// client does not need to ask for all the same light blocks again.
|
|
|
|
|
|
//
|
|
|
|
|
|
// If this function errors, it should always wrap it in a `ErrVerifcationFailed`
|
|
|
|
|
|
// struct so that the calling function can determine where it failed and handle
|
|
|
|
|
|
// it accordingly.
|
|
|
func (c *Client) verifySkipping( |
|
|
func (c *Client) verifySkipping( |
|
|
ctx context.Context, |
|
|
ctx context.Context, |
|
|
source provider.Provider, |
|
|
source provider.Provider, |
|
@ -690,20 +694,10 @@ func (c *Client) verifySkipping( |
|
|
// schedule what the next height we need to fetch is
|
|
|
// schedule what the next height we need to fetch is
|
|
|
pivotHeight := c.schedule(verifiedBlock.Height, blockCache[depth].Height) |
|
|
pivotHeight := c.schedule(verifiedBlock.Height, blockCache[depth].Height) |
|
|
interimBlock, providerErr := source.LightBlock(ctx, pivotHeight) |
|
|
interimBlock, providerErr := source.LightBlock(ctx, pivotHeight) |
|
|
switch providerErr { |
|
|
|
|
|
case nil: |
|
|
|
|
|
blockCache = append(blockCache, interimBlock) |
|
|
|
|
|
|
|
|
|
|
|
// if the error is benign, the client does not need to replace the primary
|
|
|
|
|
|
case provider.ErrLightBlockNotFound, provider.ErrNoResponse, provider.ErrHeightTooHigh: |
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
|
|
|
|
// all other errors such as ErrBadLightBlock or ErrUnreliableProvider are seen as malevolent and the
|
|
|
|
|
|
// provider is removed
|
|
|
|
|
|
default: |
|
|
|
|
|
|
|
|
if providerErr != nil { |
|
|
return nil, ErrVerificationFailed{From: verifiedBlock.Height, To: pivotHeight, Reason: providerErr} |
|
|
return nil, ErrVerificationFailed{From: verifiedBlock.Height, To: pivotHeight, Reason: providerErr} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
blockCache = append(blockCache, interimBlock) |
|
|
} |
|
|
} |
|
|
depth++ |
|
|
depth++ |
|
|
|
|
|
|
|
@ -729,45 +723,71 @@ func (c *Client) verifySkippingAgainstPrimary( |
|
|
now time.Time) error { |
|
|
now time.Time) error { |
|
|
|
|
|
|
|
|
trace, err := c.verifySkipping(ctx, c.primary, trustedBlock, newLightBlock, now) |
|
|
trace, err := c.verifySkipping(ctx, c.primary, trustedBlock, newLightBlock, now) |
|
|
|
|
|
if err == nil { |
|
|
|
|
|
// Success! Now compare the header with the witnesses to ensure it's not a fork.
|
|
|
|
|
|
// More witnesses we have, more chance to notice one.
|
|
|
|
|
|
//
|
|
|
|
|
|
// CORRECTNESS ASSUMPTION: there's at least 1 correct full node
|
|
|
|
|
|
// (primary or one of the witnesses).
|
|
|
|
|
|
if cmpErr := c.detectDivergence(ctx, trace, now); cmpErr != nil { |
|
|
|
|
|
return cmpErr |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var e = &ErrVerificationFailed{} |
|
|
|
|
|
// all errors from verify skipping should be `ErrVerificationFailed`
|
|
|
|
|
|
// if it's not we just return the error directly
|
|
|
|
|
|
if !errors.As(err, e) { |
|
|
|
|
|
return err |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
switch errors.Unwrap(err).(type) { |
|
|
|
|
|
|
|
|
replace := true |
|
|
|
|
|
switch e.Reason.(type) { |
|
|
|
|
|
// Verification returned an invalid header
|
|
|
case ErrInvalidHeader: |
|
|
case ErrInvalidHeader: |
|
|
// If the target header is invalid, return immediately.
|
|
|
|
|
|
invalidHeaderHeight := err.(ErrVerificationFailed).To |
|
|
|
|
|
if invalidHeaderHeight == newLightBlock.Height { |
|
|
|
|
|
|
|
|
// If it was the target header, return immediately.
|
|
|
|
|
|
if e.To == newLightBlock.Height { |
|
|
c.logger.Debug("target header is invalid", "err", err) |
|
|
c.logger.Debug("target header is invalid", "err", err) |
|
|
return err |
|
|
return err |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// If some intermediate header is invalid, remove the primary and try again.
|
|
|
|
|
|
c.logger.Error("primary sent invalid header -> replacing", "err", err, "primary", c.primary) |
|
|
|
|
|
replacementBlock, removeErr := c.findNewPrimary(ctx, newLightBlock.Height, true) |
|
|
|
|
|
if removeErr != nil { |
|
|
|
|
|
c.logger.Error("failed to replace primary. Returning original error", "err", removeErr) |
|
|
|
|
|
return err |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// If some intermediate header is invalid, remove the primary and try
|
|
|
|
|
|
// again.
|
|
|
|
|
|
|
|
|
if !bytes.Equal(replacementBlock.Hash(), newLightBlock.Hash()) { |
|
|
|
|
|
c.logger.Debug("replaced primary but new primary has a different block to the initial one. Returning original error") |
|
|
|
|
|
return err |
|
|
|
|
|
|
|
|
// An intermediate header expired. We can no longer validate it as there is
|
|
|
|
|
|
// no longer the ability to punish invalid blocks as evidence of misbehavior
|
|
|
|
|
|
case ErrOldHeaderExpired: |
|
|
|
|
|
return err |
|
|
|
|
|
|
|
|
|
|
|
// This happens if there was a problem in finding the next block or a
|
|
|
|
|
|
// context was canceled.
|
|
|
|
|
|
default: |
|
|
|
|
|
if errors.Is(e.Reason, context.Canceled) || errors.Is(e.Reason, context.DeadlineExceeded) { |
|
|
|
|
|
return e.Reason |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// attempt to verify the header again
|
|
|
|
|
|
return c.verifySkippingAgainstPrimary(ctx, trustedBlock, replacementBlock, now) |
|
|
|
|
|
case nil: |
|
|
|
|
|
// Compare header with the witnesses to ensure it's not a fork.
|
|
|
|
|
|
// More witnesses we have, more chance to notice one.
|
|
|
|
|
|
//
|
|
|
|
|
|
// CORRECTNESS ASSUMPTION: there's at least 1 correct full node
|
|
|
|
|
|
// (primary or one of the witnesses).
|
|
|
|
|
|
if cmpErr := c.detectDivergence(ctx, trace, now); cmpErr != nil { |
|
|
|
|
|
return cmpErr |
|
|
|
|
|
|
|
|
if !c.providerShouldBeRemoved(e.Reason) { |
|
|
|
|
|
replace = false |
|
|
} |
|
|
} |
|
|
default: |
|
|
|
|
|
return err |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
|
|
|
|
// if we've reached here we're attempting to retry verification with a
|
|
|
|
|
|
// different provider
|
|
|
|
|
|
c.logger.Info("primary returned error", "err", e, "primary", c.primary, "replace", replace) |
|
|
|
|
|
|
|
|
|
|
|
replacementBlock, removeErr := c.findNewPrimary(ctx, newLightBlock.Height, replace) |
|
|
|
|
|
if removeErr != nil { |
|
|
|
|
|
c.logger.Error("failed to replace primary. Returning original error", "err", removeErr) |
|
|
|
|
|
return e.Reason |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if !bytes.Equal(replacementBlock.Hash(), newLightBlock.Hash()) { |
|
|
|
|
|
c.logger.Debug("replaced primary but new primary has a different block to the initial one. Returning original error") |
|
|
|
|
|
return e.Reason |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// attempt to verify the header again from the trusted block
|
|
|
|
|
|
return c.verifySkippingAgainstPrimary(ctx, trustedBlock, replacementBlock, now) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// LastTrustedHeight returns a last trusted height. -1 and nil are returned if
|
|
|
// LastTrustedHeight returns a last trusted height. -1 and nil are returned if
|
|
@ -811,6 +831,15 @@ func (c *Client) Witnesses() []provider.Provider { |
|
|
return c.witnesses |
|
|
return c.witnesses |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// AddProvider adds a providers to the light clients set
|
|
|
|
|
|
//
|
|
|
|
|
|
// NOTE: The light client does not check for uniqueness
|
|
|
|
|
|
func (c *Client) AddProvider(p provider.Provider) { |
|
|
|
|
|
c.providerMutex.Lock() |
|
|
|
|
|
defer c.providerMutex.Unlock() |
|
|
|
|
|
c.witnesses = append(c.witnesses, p) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Cleanup removes all the data (headers and validator sets) stored. Note: the
|
|
|
// Cleanup removes all the data (headers and validator sets) stored. Note: the
|
|
|
// client must be stopped at this point.
|
|
|
// client must be stopped at this point.
|
|
|
func (c *Client) Cleanup() error { |
|
|
func (c *Client) Cleanup() error { |
|
@ -865,7 +894,7 @@ func (c *Client) backwards( |
|
|
"newHash", interimHeader.Hash()) |
|
|
"newHash", interimHeader.Hash()) |
|
|
if err := VerifyBackwards(interimHeader, verifiedHeader); err != nil { |
|
|
if err := VerifyBackwards(interimHeader, verifiedHeader); err != nil { |
|
|
// verification has failed
|
|
|
// verification has failed
|
|
|
c.logger.Error("backwards verification failed, replacing primary...", "err", err, "primary", c.primary) |
|
|
|
|
|
|
|
|
c.logger.Info("backwards verification failed, replacing primary...", "err", err, "primary", c.primary) |
|
|
|
|
|
|
|
|
// the client tries to see if it can get a witness to continue with the request
|
|
|
// the client tries to see if it can get a witness to continue with the request
|
|
|
newPrimarysBlock, replaceErr := c.findNewPrimary(ctx, newHeader.Height, true) |
|
|
newPrimarysBlock, replaceErr := c.findNewPrimary(ctx, newHeader.Height, true) |
|
@ -915,14 +944,14 @@ func (c *Client) lightBlockFromPrimary(ctx context.Context, height int64) (*type |
|
|
|
|
|
|
|
|
case provider.ErrNoResponse, provider.ErrLightBlockNotFound, provider.ErrHeightTooHigh: |
|
|
case provider.ErrNoResponse, provider.ErrLightBlockNotFound, provider.ErrHeightTooHigh: |
|
|
// we find a new witness to replace the primary
|
|
|
// we find a new witness to replace the primary
|
|
|
c.logger.Debug("error from light block request from primary, replacing...", |
|
|
|
|
|
|
|
|
c.logger.Info("error from light block request from primary, replacing...", |
|
|
"error", err, "height", height, "primary", c.primary) |
|
|
"error", err, "height", height, "primary", c.primary) |
|
|
return c.findNewPrimary(ctx, height, false) |
|
|
return c.findNewPrimary(ctx, height, false) |
|
|
|
|
|
|
|
|
default: |
|
|
default: |
|
|
// The light client has most likely received either provider.ErrUnreliableProvider or provider.ErrBadLightBlock
|
|
|
// The light client has most likely received either provider.ErrUnreliableProvider or provider.ErrBadLightBlock
|
|
|
// These errors mean that the light client should drop the primary and try with another provider instead
|
|
|
// These errors mean that the light client should drop the primary and try with another provider instead
|
|
|
c.logger.Error("error from light block request from primary, removing...", |
|
|
|
|
|
|
|
|
c.logger.Info("error from light block request from primary, removing...", |
|
|
"error", err, "height", height, "primary", c.primary) |
|
|
"error", err, "height", height, "primary", c.primary) |
|
|
return c.findNewPrimary(ctx, height, true) |
|
|
return c.findNewPrimary(ctx, height, true) |
|
|
} |
|
|
} |
|
@ -1022,7 +1051,7 @@ func (c *Client) findNewPrimary(ctx context.Context, height int64, remove bool) |
|
|
// process benign errors by logging them only
|
|
|
// process benign errors by logging them only
|
|
|
case provider.ErrNoResponse, provider.ErrLightBlockNotFound, provider.ErrHeightTooHigh: |
|
|
case provider.ErrNoResponse, provider.ErrLightBlockNotFound, provider.ErrHeightTooHigh: |
|
|
lastError = response.err |
|
|
lastError = response.err |
|
|
c.logger.Debug("error on light block request from witness", |
|
|
|
|
|
|
|
|
c.logger.Info("error on light block request from witness", |
|
|
"error", response.err, "primary", c.witnesses[response.witnessIndex]) |
|
|
"error", response.err, "primary", c.witnesses[response.witnessIndex]) |
|
|
continue |
|
|
continue |
|
|
|
|
|
|
|
@ -1066,13 +1095,13 @@ func (c *Client) compareFirstHeaderWithWitnesses(ctx context.Context, h *types.S |
|
|
case nil: |
|
|
case nil: |
|
|
continue |
|
|
continue |
|
|
case errConflictingHeaders: |
|
|
case errConflictingHeaders: |
|
|
c.logger.Error(fmt.Sprintf(`witness #%d has a different header. Please check primary is correct |
|
|
|
|
|
and remove witness. Otherwise, use a different primary`, e.WitnessIndex), "witness", c.witnesses[e.WitnessIndex]) |
|
|
|
|
|
|
|
|
c.logger.Error(`witness has a different header. Please check primary is |
|
|
|
|
|
correct and remove witness. Otherwise, use a different primary`, |
|
|
|
|
|
"Witness", c.witnesses[e.WitnessIndex], "ExpHeader", h.Hash(), "GotHeader", e.Block.Hash()) |
|
|
return err |
|
|
return err |
|
|
case errBadWitness: |
|
|
case errBadWitness: |
|
|
// If witness sent us an invalid header, then remove it
|
|
|
// If witness sent us an invalid header, then remove it
|
|
|
c.logger.Info("witness sent an invalid light block or didn't respond, removing...", |
|
|
|
|
|
"witness", c.witnesses[e.WitnessIndex], |
|
|
|
|
|
|
|
|
c.logger.Info("witness returned an error, removing...", |
|
|
"err", err) |
|
|
"err", err) |
|
|
witnessesToRemove = append(witnessesToRemove, e.WitnessIndex) |
|
|
witnessesToRemove = append(witnessesToRemove, e.WitnessIndex) |
|
|
default: |
|
|
default: |
|
@ -1082,7 +1111,7 @@ and remove witness. Otherwise, use a different primary`, e.WitnessIndex), "witne |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// the witness either didn't respond or didn't have the block. We ignore it.
|
|
|
// the witness either didn't respond or didn't have the block. We ignore it.
|
|
|
c.logger.Debug("unable to compare first header with witness", |
|
|
|
|
|
|
|
|
c.logger.Debug("unable to compare first header with witness, ignoring", |
|
|
"err", err) |
|
|
"err", err) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -1091,3 +1120,11 @@ and remove witness. Otherwise, use a different primary`, e.WitnessIndex), "witne |
|
|
// remove all witnesses that misbehaved
|
|
|
// remove all witnesses that misbehaved
|
|
|
return c.removeWitnesses(witnessesToRemove) |
|
|
return c.removeWitnesses(witnessesToRemove) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// providerShouldBeRemoved analyzes the nature of the error and whether the provider
|
|
|
|
|
|
// should be removed from the light clients set
|
|
|
|
|
|
func (c *Client) providerShouldBeRemoved(err error) bool { |
|
|
|
|
|
return errors.As(err, &provider.ErrUnreliableProvider{}) || |
|
|
|
|
|
errors.As(err, &provider.ErrBadLightBlock{}) || |
|
|
|
|
|
errors.Is(err, provider.ErrConnectionClosed) |
|
|
|
|
|
} |