# ADR 046: Lite Client Implementation

## Changelog
* 13-02-2020: Initial draft
* 26-02-2020: Cross-checking the first header
* 28-02-2020: Bisection algorithm details
* 31-03-2020: Verify signature got changed

## Context

A `Client` struct represents a light client, connected to a single blockchain.

The user has an option to verify headers using `VerifyHeader` or
`VerifyHeaderAtHeight` or `Update` methods. The latter method downloads the
latest header from primary and compares it with the currently trusted one.

```go
type Client interface {
	// verify new headers
	VerifyHeaderAtHeight(height int64, now time.Time) (*types.SignedHeader, error)
	VerifyHeader(newHeader *types.SignedHeader, newVals *types.ValidatorSet, now time.Time) error
	Update(now time.Time) (*types.SignedHeader, error)

	// get trusted headers & validators
	TrustedHeader(height int64) (*types.SignedHeader, error)
	TrustedValidatorSet(height int64) (valSet *types.ValidatorSet, heightUsed int64, err error)
	LastTrustedHeight() (int64, error)
	FirstTrustedHeight() (int64, error)

	// query configuration options
	ChainID() string
	Primary() provider.Provider
	Witnesses() []provider.Provider

	Cleanup() error
}
```

A new light client can either be created from scratch (via `NewClient`) or
using the trusted store (via `NewClientFromTrustedStore`). When there's some
data in the trusted store and `NewClient` is called, the light client will a)
check if stored header is more recent b) optionally ask the user whenever it
should rollback (no confirmation required by default).

```go
func NewClient(
	chainID string,
	trustOptions TrustOptions,
	primary provider.Provider,
	witnesses []provider.Provider,
	trustedStore store.Store,
	options ...Option) (*Client, error) {
```

`witnesses` as argument (as opposite to `Option`) is an intentional choice,
made to increase security by default. At least one witness is required,
although, right now, the light client does not check that primary != witness.
When cross-checking a new header with witnesses, minimum number of witnesses
required to respond: 1. Note the very first header (`TrustOptions.Hash`) is
also cross-checked with witnesses for additional security.

Due to bisection algorithm nature, some headers might be skipped. If the light
client does not have a header for height `X` and `VerifyHeaderAtHeight(X)` or
`VerifyHeader(H#X)` methods are called, these will perform either a) backwards
verification from the latest header back to the header at height `X` or b)
bisection verification from the first stored header to the header at height `X`.

`TrustedHeader`, `TrustedValidatorSet` only communicate with the trusted store.
If some header is not there, an error will be returned indicating that
verification is required.

```go
type Provider interface {
	ChainID() string

	SignedHeader(height int64) (*types.SignedHeader, error)
	ValidatorSet(height int64) (*types.ValidatorSet, error)
}
```

Provider is a full node usually, but can be another light client. The above
interface is thin and can accommodate many implementations.

If provider (primary or witness) becomes unavailable for a prolonged period of
time, it will be removed to ensure smooth operation.

Both `Client` and providers expose chain ID to track if there are on the same
chain. Note, when chain upgrades or intentionally forks, chain ID changes.

The light client stores headers & validators in the trusted store:

```go
type Store interface {
	SaveSignedHeaderAndValidatorSet(sh *types.SignedHeader, valSet *types.ValidatorSet) error
	DeleteSignedHeaderAndValidatorSet(height int64) error

	SignedHeader(height int64) (*types.SignedHeader, error)
	ValidatorSet(height int64) (*types.ValidatorSet, error)

	LastSignedHeaderHeight() (int64, error)
	FirstSignedHeaderHeight() (int64, error)

	SignedHeaderAfter(height int64) (*types.SignedHeader, error)

	Prune(size uint16) error

	Size() uint16
}
```

At the moment, the only implementation is the `db` store (wrapper around the KV
database, used in Tendermint). In the future, remote adapters are possible
(e.g. `Postgresql`).

```go
func Verify(
	chainID string,
	trustedHeader *types.SignedHeader, // height=X
	trustedVals *types.ValidatorSet, // height=X or height=X+1
	untrustedHeader *types.SignedHeader, // height=Y
	untrustedVals *types.ValidatorSet, // height=Y
	trustingPeriod time.Duration,
	now time.Time,
	maxClockDrift time.Duration,
	trustLevel tmmath.Fraction) error {
```

`Verify` pure function is exposed for a header verification. It handles both
cases of adjacent and non-adjacent headers. In the former case, it compares the
hashes directly (2/3+ signed transition). Otherwise, it verifies 1/3+
(`trustLevel`) of trusted validators are still present in new validators.

While `Verify` function is certainly handy, `VerifyAdjacent` and
`VerifyNonAdjacent` should be used most often to avoid logic errors.

### Bisection algorithm details

Non-recursive bisection algorithm was implemented despite the spec containing
the recursive version. There are two major reasons:

1) Constant memory consumption => no risk of getting OOM (Out-Of-Memory) exceptions;
2) Faster finality (see Fig. 1).

_Fig. 1: Differences between recursive and non-recursive bisections_

![Fig. 1](./img/adr-046-fig1.png)

Specification of the non-recursive bisection can be found
[here](https://github.com/tendermint/spec/blob/zm_non-recursive-verification/spec/consensus/light-client/non-recursive-verification.md).

## Status

Accepted.

## Consequences

### Positive

* single `Client` struct, which is easy to use
* flexible interfaces for header providers and trusted storage

### Negative

* `Verify` needs to be aligned with the current spec

### Neutral

* `Verify` function might be misused (called with non-adjacent headers in
  incorrectly implemented sequential verification)
