mirror of
https://github.com/tendermint/tendermint.git
synced 2026-04-18 14:55:02 +00:00
light: expand on errors and docs (#5443)
This commit is contained in:
@@ -13,9 +13,10 @@ package](https://pkg.go.dev/github.com/tendermint/tendermint/light?tab=doc).
|
||||
|
||||
## Overview
|
||||
|
||||
The objective of the light client protocol is to get a commit for a recent
|
||||
block hash where the commit includes a majority of signatures from the last
|
||||
known validator set. From there, all the application state is verifiable with
|
||||
The light client protocol verifies headers by retrieving a chain of headers,
|
||||
commits and validator sets from a trusted height to the target height, verifying
|
||||
the signatures of each of these intermediary signed headers till it reaches the
|
||||
target height. From there, all the application state is verifiable with
|
||||
[merkle proofs](https://github.com/tendermint/spec/blob/953523c3cb99fdb8c8f7a2d21e3a99094279e9de/spec/blockchain/encoding.md#iavl-tree).
|
||||
|
||||
## Properties
|
||||
@@ -30,11 +31,29 @@ known validator set. From there, all the application state is verifiable with
|
||||
name-registry without worrying about fork censorship attacks, without posting
|
||||
a commit and waiting for confirmations. It's fast, secure, and free!
|
||||
|
||||
## Security
|
||||
|
||||
A light client is initialized from a point of trust using [Trust Options](https://pkg.go.dev/github.com/tendermint/tendermint/light?tab=doc#TrustOptions),
|
||||
a provider and a set of witnesses. This sets the trust period: the period that
|
||||
full nodes should be accountable for faulty behavior and a trust level: the
|
||||
fraction of validators in a validator set with which we trust that at least one
|
||||
is correct. As Tendermint consensus can withstand 1/3 byzantine faults, this is
|
||||
the default trust level, however, for greater security you can increase it (max:
|
||||
1).
|
||||
|
||||
Similar to a full node, light clients can also be subject to byzantine attacks.
|
||||
A light client also runs a detector process which cross verifies headers from a
|
||||
primary with witnesses. Therefore light clients should be set with enough witnesses.
|
||||
|
||||
If the light client observes a faulty provider it will report it to another provider
|
||||
and return an error.
|
||||
|
||||
In summary, the light client is not safe when a) more than the trust level of
|
||||
validators are malicious and b) all witnesses are malicious.
|
||||
|
||||
## Where to obtain trusted height & hash
|
||||
|
||||
[Trust Options](https://pkg.go.dev/github.com/tendermint/tendermint/light?tab=doc#TrustOptions)
|
||||
|
||||
One way to obtain semi-trusted hash & height is to query multiple full nodes
|
||||
One way to obtain a semi-trusted hash & height is to query multiple full nodes
|
||||
and compare their hashes:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -90,13 +90,19 @@ func (c *Client) detectDivergence(ctx context.Context, primaryTrace []*types.Lig
|
||||
|
||||
// We are suspecting that the primary is faulty, hence we hold the witness as the source of truth
|
||||
// and generate evidence against the primary that we can send to the witness
|
||||
ev := &types.LightClientAttackEvidence{
|
||||
primaryEv := &types.LightClientAttackEvidence{
|
||||
ConflictingBlock: primaryBlock,
|
||||
CommonHeight: commonHeight, // the first block in the bisection is common to both providers
|
||||
}
|
||||
c.logger.Error("Attack detected. Sending evidence againt primary by witness", "ev", ev,
|
||||
c.logger.Error("Attempted attack detected. Sending evidence againt primary by witness", "ev", primaryEv,
|
||||
"primary", c.primary, "witness", supportingWitness)
|
||||
c.sendEvidence(ctx, ev, supportingWitness)
|
||||
c.sendEvidence(ctx, primaryEv, supportingWitness)
|
||||
|
||||
if primaryBlock.Commit.Round != witnessTrace[len(witnessTrace)-1].Commit.Round {
|
||||
c.logger.Info("The light client has detected, and prevented, an attempted amnesia attack." +
|
||||
" We think this attack is pretty unlikely, so if you see it, that's interesting to us." +
|
||||
" Can you let us know by opening an issue through https://github.com/tendermint/tendermint/issues/new?")
|
||||
}
|
||||
|
||||
// This may not be valid because the witness itself is at fault. So now we reverse it, examining the
|
||||
// trace provided by the witness and holding the primary as the source of truth. Note: primary may not
|
||||
@@ -110,7 +116,7 @@ func (c *Client) detectDivergence(ctx context.Context, primaryTrace []*types.Lig
|
||||
)
|
||||
if err != nil {
|
||||
c.logger.Info("Error validating primary's divergent header", "primary", c.primary, "err", err)
|
||||
continue
|
||||
return ErrLightClientAttack
|
||||
}
|
||||
// if this is an equivocation or amnesia attack, i.e. the validator sets are the same, then we
|
||||
// return the height of the conflicting block else if it is a lunatic attack and the validator sets
|
||||
@@ -122,15 +128,15 @@ func (c *Client) detectDivergence(ctx context.Context, primaryTrace []*types.Lig
|
||||
}
|
||||
|
||||
// We now use the primary trace to create evidence against the witness and send it to the primary
|
||||
ev = &types.LightClientAttackEvidence{
|
||||
witnessEv := &types.LightClientAttackEvidence{
|
||||
ConflictingBlock: witnessBlock,
|
||||
CommonHeight: commonHeight, // the first block in the bisection is common to both providers
|
||||
}
|
||||
c.logger.Error("Sending evidence against witness by primary", "ev", ev,
|
||||
c.logger.Error("Sending evidence against witness by primary", "ev", witnessEv,
|
||||
"primary", c.primary, "witness", supportingWitness)
|
||||
c.sendEvidence(ctx, ev, c.primary)
|
||||
c.sendEvidence(ctx, witnessEv, c.primary)
|
||||
// We return the error and don't process anymore witnesses
|
||||
return e
|
||||
return ErrLightClientAttack
|
||||
|
||||
case errBadWitness:
|
||||
c.logger.Info("Witness returned an error during header comparison", "witness", c.witnesses[e.WitnessIndex],
|
||||
|
||||
@@ -63,7 +63,7 @@ func TestLightClientAttackEvidence_Lunatic(t *testing.T) {
|
||||
// Check verification returns an error.
|
||||
_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour))
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "does not match primary")
|
||||
assert.Equal(t, err, light.ErrLightClientAttack)
|
||||
}
|
||||
|
||||
// Check evidence was sent to both full nodes.
|
||||
@@ -137,7 +137,7 @@ func TestLightClientAttackEvidence_Equivocation(t *testing.T) {
|
||||
// Check verification returns an error.
|
||||
_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour))
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "does not match primary")
|
||||
assert.Equal(t, err, light.ErrLightClientAttack)
|
||||
}
|
||||
|
||||
// Check evidence was sent to both full nodes.
|
||||
|
||||
@@ -65,6 +65,13 @@ func (e ErrVerificationFailed) Error() string {
|
||||
e.From, e.To, e.Reason)
|
||||
}
|
||||
|
||||
// ErrLightClientAttack is returned when the light client has detected an attempt
|
||||
// to verify a false header and has sent the evidence to either a witness or primary.
|
||||
var ErrLightClientAttack = errors.New("attempted attack detected." +
|
||||
" Light client received valid conflicting header from witness." +
|
||||
" Unable to verify header. Evidence has been sent to both providers." +
|
||||
" Check logs for full evidence and trace")
|
||||
|
||||
// ----------------------------- INTERNAL ERRORS ---------------------------------
|
||||
|
||||
// ErrConflictingHeaders is thrown when two conflicting headers are discovered.
|
||||
|
||||
Reference in New Issue
Block a user