Browse Source

light: expand on errors and docs (#5443)

pull/5454/head
Callum Waters 4 years ago
committed by GitHub
parent
commit
a4b7018732
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 48 additions and 16 deletions
  1. +25
    -6
      docs/tendermint-core/light-client.md
  2. +14
    -8
      light/detector.go
  3. +2
    -2
      light/detector_test.go
  4. +7
    -0
      light/errors.go

+ 25
- 6
docs/tendermint-core/light-client.md View File

@ -13,9 +13,10 @@ package](https://pkg.go.dev/github.com/tendermint/tendermint/light?tab=doc).
## Overview ## 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). [merkle proofs](https://github.com/tendermint/spec/blob/953523c3cb99fdb8c8f7a2d21e3a99094279e9de/spec/blockchain/encoding.md#iavl-tree).
## Properties ## 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 name-registry without worrying about fork censorship attacks, without posting
a commit and waiting for confirmations. It's fast, secure, and free! a commit and waiting for confirmations. It's fast, secure, and free!
## Where to obtain trusted height & hash
## 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.
[Trust Options](https://pkg.go.dev/github.com/tendermint/tendermint/light?tab=doc#TrustOptions)
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
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: and compare their hashes:
```bash ```bash


+ 14
- 8
light/detector.go View File

@ -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 // 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 // and generate evidence against the primary that we can send to the witness
ev := &types.LightClientAttackEvidence{
primaryEv := &types.LightClientAttackEvidence{
ConflictingBlock: primaryBlock, ConflictingBlock: primaryBlock,
CommonHeight: commonHeight, // the first block in the bisection is common to both providers 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) "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 // 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 // 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 { if err != nil {
c.logger.Info("Error validating primary's divergent header", "primary", c.primary, "err", err) 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 // 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 // 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 // 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, ConflictingBlock: witnessBlock,
CommonHeight: commonHeight, // the first block in the bisection is common to both providers 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) "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 // We return the error and don't process anymore witnesses
return e
return ErrLightClientAttack
case errBadWitness: case errBadWitness:
c.logger.Info("Witness returned an error during header comparison", "witness", c.witnesses[e.WitnessIndex], c.logger.Info("Witness returned an error during header comparison", "witness", c.witnesses[e.WitnessIndex],


+ 2
- 2
light/detector_test.go View File

@ -63,7 +63,7 @@ func TestLightClientAttackEvidence_Lunatic(t *testing.T) {
// Check verification returns an error. // Check verification returns an error.
_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour))
if assert.Error(t, err) { 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. // Check evidence was sent to both full nodes.
@ -137,7 +137,7 @@ func TestLightClientAttackEvidence_Equivocation(t *testing.T) {
// Check verification returns an error. // Check verification returns an error.
_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour))
if assert.Error(t, err) { 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. // Check evidence was sent to both full nodes.


+ 7
- 0
light/errors.go View File

@ -65,6 +65,13 @@ func (e ErrVerificationFailed) Error() string {
e.From, e.To, e.Reason) 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 --------------------------------- // ----------------------------- INTERNAL ERRORS ---------------------------------
// ErrConflictingHeaders is thrown when two conflicting headers are discovered. // ErrConflictingHeaders is thrown when two conflicting headers are discovered.


Loading…
Cancel
Save