* 13-04-2020: Add PotentialAmnesiaEvidence and a few remarks
* 31-07-2020: Remove PhantomValidatorEvidence
* 14-08-2020: Introduce light traces
* 14-08-2020: Introduce light traces (listed now as an alternative approach)
* 20-08-2020: Light client produces evidence when detected instead of passing to full node
* 16-09-2020: Post-implementation revision
### Glossary of Terms
- a `LightBlock` is the unit of data that a light client receives, verifies and stores.
It is composed of a validator set, commit and header all at the same height.
- a **Trace** is seen as an array of light blocks across a range of heights that were
created as a result of skipping verification.
- a **Provider** is a full node that a light client is connected to and serves the light
client signed headers and validator sets.
- `VerifySkipping` (sometimes known as bisection or verify non-adjacent) is a method the
light client uses to verify a target header from a trusted header. The process involves verifying
intermediate headers in between the two by making sure that 1/3 of the validators that signed
the trusted header also signed the untrusted one.
- **Light Bifurcation Point**: If the light client was to run `VerifySkipping` with two providers
(i.e. a primary and a witness), the bifurcation point is the height that the headers
from each of these providers are different yet valid. This signals that one of the providers
may be trying to fool the light client.
## Context
The bisection method of header verification used by the light client exposes
itself to a potential attack if any block within the light clients trusted period has
a malicious group of validators with power that exceeds the light clients trust level
a malicious group of validators with power that exceeds the light clients trust level
(default is 1/3). To improve light client (and overall network) security, the light
client has a detector component that compares the verified header provided by the
primary against witness headers. This ADR outlines the decision that ensues when
the light client detector receives two conflicting headers
primary against witness headers. This ADR outlines the process of mitigating attacks
on the light client by using witness nodes to cross reference with.
## Alternative Approaches
One of the key arguments surrounding the decision is whether the processing required
to extract verifiable evidence should be on the light client side or the full node side.
As light client, indicated in it's name, is supposed to stay light, the natural
inclination is to avoid any load and pass it directly to the full node who also has
the advantage of having a full state. It remains possible in future discussions to have the
light client form the evidence types itself. The other minor downsides apart from the
load will be that around half the evidence produced by the light client will be invalid and
that, in the event of modified logic, it's easier and expected that the full node be up
to date than the light clients.
## Decision
When two headers have different hashes, the light client must first verify via
bisection that the signed header provided by the witness is also valid.
It then collects all the headers that were fetched as part of bisection into two traces.
One containing the primary's headers and the other containing the witness's headers.
The light client sends the trace to the opposite provider (i.e. the primary's trace
to the witness and the witness's trace to the primary) as the light client is
incapable of deciding which one is malicious.
Assuming one of the two providers is honest, that full node will then take the trace
and extract out the relevant evidence and propogate it until it ends up on chain.
*NOTE: We do not send evidence to other connected peers*
*NOTE: The light client halts then and does not verify with any other witness*
## Detailed Design
The traces will have the following data structure:
A previously discussed approach to handling evidence was to pass all the data that the
light client had witnessed when it had observed diverging headers for the full node to
process.This was known as a light trace and had the following structure:
```go
type ConflictingHeadersTrace struct {
@ -57,158 +48,169 @@ type ConflictingHeadersTrace struct {
}
```
When a full node receives a `ConflictingHeadersTrace`, it should
a) validate it b) figure out if malicious behaviour is obvious (immediately
slashable) or run the amnesia protocol.
This approach has the advantage of not requiring as much processing on the light
client side in the event that an attack happens. Although, this is not a significant
difference as the light client would in any case have to validate all the headers
from both witness and primary. Using traces would consume a large amount of bandwidth
and adds a DDOS vector to the full node.
### Validating headers
Check headers are valid (`ValidateBasic`), are in order of ascending height, and do not exceed the `MaxTraceSize`.
### Finding Block Bifurcation
## Decision
The node pulls the block ID's for the respective heights of the trace headers from its own block store.
The light client will be divided into two components: a `Verifier` (either sequential or
skipping) and a `Detector` (see [Informal's Detector](https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/detection/detection.md))
. The detector will take the trace of headers from the primary and check it against all
witnesses. For a witness with a diverging header, the detector will first verify the header
by bisecting through all the heights defined by the trace that the primary provided. If valid,
the light client will trawl through both traces and find the point of bifurcation where it
can proceed to extract any evidence (as is discussed in detail later).
First it checks to see that the first header hash matches its first `BlockID` else it can discard it.
Upon successfully detecting the evidence, the light client will send it to both primary and
witness before halting. It will not send evidence to other peers nor continue to verify the
primary's header against any other header.
If the last header hash matches the nodes last `BlockID` then it can also discard it on the assumption that a fork can not remerge and hence this is just a trace of valid headers.
The node then continues to loop in descending order checking that the headers hash doesn't match it's own blockID for that height. Once it reaches the height that the block ID matches the hash it then sends the common header, the trusted header and the diverged header (common header is needed for lunatic evidence) to determine if the divergence is a real offense to the tendermint protocol or if it is just fabricated.
## Detailed Design
### Figuring out if malicious behaviour
The verification process of the light client will start from a trusted header and use a bisectional
algorithm to verify up to a header at a given height. This becomes the verified header (does not
mean that it is trusted yet). All headers that were verified in between are cached and known as
intermediary headers and the entire array is sometimes referred to as a trace.
The node first examines the case of a lunatic attack:
The light client's detector then takes all the headers and runs the detect function.
* The validator set of the common header must have at least 1/3 validator power that signed in the divergedHeaders commit
```golang
func (c *Client) detectDivergence(primaryTrace []*types.LightBlock, now time.Time) error
```
* One of the deterministically derived hashes (`ValidatorsHash`, `NextValidatorsHash`, `ConsensusHash`,
`AppHash`, or `LastResultsHash`) of the header must not match:
The function takes the last header it received, the target header and compares it against all the witnesses
it has through the following function:
* We then take every validator that voted for the invalid header and was a validator in the common headers validator set and create `LunaticValidatorEvidence`
```golang
func (c *Client) compareNewHeaderWithWitness(errc chan error, h *types.SignedHeader,
witness provider.Provider, witnessIndex int)
```
If this fails then we examine the case of Equivocation (either duplicate vote or amnesia):
The err channel is used to send back all the outcomes so that they can be processed in parallel.
Invalid headers result in dropping the witness, lack of response or not having the headers is ignored
just as headers that have the same hash. Headers, however,
of a different hash then trigger the detection process between the primary and that particular witness.
*This only requires the trustedHeader and the divergedHeader*
This begins with verification of the witness's header via skipping verification which is run in tande
with locating the Light Bifurcation Point
* if `trustedHeader.Round == divergedHeader.Round`, and a validator signed for the block in both headers then DuplicateVoteEvidence can be immediately formed
![](../imgs/light-client-detector.png)
* if `trustedHeader.Round != divergedHeader.Round` then we form PotentialAmnesiaEvidence as some validators in this set have behaved maliciously and protocol in ADR 56 needs to be run.
This is done with:
*The node does not check that there is a 1/3 overlap between headers as this may not be point of the fork and validator sets may have since changed*