mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-08 06:15:33 +00:00
adr: light client implementation (#4397)
* adr: light client implementation Closes #2133 * note on chain IDs * explain why witnesses are required * if chain forks maliciously, chain ID stays the same * add a note about min witnesses while cross-checking
This commit is contained in:
@@ -64,5 +64,8 @@ Note the context/background should be written in the present tense.
|
||||
- [ADR-039-Peer-Behaviour](./adr-039-peer-behaviour.md)
|
||||
- [ADR-041-Proposer-Selection-via-ABCI](./adr-041-proposer-selection-via-abci.md)
|
||||
- [ADR-043-Blockchain-RiRi-Org](./adr-043-blockchain-riri-org.md)
|
||||
- [ADR-044-Lite-Client-With-Weak-Subjectivity](./adr-044-lite-client-with-weak-subjectivity.md)
|
||||
- [ADR-045-ABCI-Evidence](./adr-045-abci-evidence.md)
|
||||
- [ADR-046-Light-Client-Implementation](./adr-046-light-client-implementation.md)
|
||||
- [ADR-051-Double-Signing-Risk-Reduction](./adr-051-double-signing-risk-reduction.md)
|
||||
- [ADR-052-Tendermint-Mode](./adr-052-tendermint-mode.md)
|
||||
|
||||
146
docs/architecture/adr-046-light-client-implementation.md
Normal file
146
docs/architecture/adr-046-light-client-implementation.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# ADR 046: Lite Client Implementation
|
||||
|
||||
## Changelog
|
||||
* 13-02-2020: Initial draft
|
||||
|
||||
## Context
|
||||
|
||||
A `Client` struct represents a light client, connected to a single blockchain.
|
||||
As soon as it's started (via `Start`), it tries to update to the latest header
|
||||
(using bisection algorithm by default).
|
||||
|
||||
Cleaning routine is also started to remove headers outside of trusting period.
|
||||
NOTE: since it's periodic, we still need to check header is not expired in
|
||||
`TrustedHeader`, `TrustedValidatorSet` methods (and others which are using the
|
||||
latest trusted header).
|
||||
|
||||
The user has an option to manually verify headers using `VerifyHeader` and
|
||||
`VerifyHeaderAtHeight` methods. To avoid races, `UpdatePeriod(0)` needs to be
|
||||
passed when initializing the light client (it turns off the auto update).
|
||||
|
||||
```go
|
||||
type Client interface {
|
||||
// start and stop updating & cleaning goroutines
|
||||
Start() error
|
||||
Stop()
|
||||
Cleanup() error
|
||||
|
||||
// get trusted headers & validators
|
||||
TrustedHeader(height int64, now time.Time) (*types.SignedHeader, error)
|
||||
TrustedValidatorSet(height int64, now time.Time) (*types.ValidatorSet, error)
|
||||
LastTrustedHeight() (int64, error)
|
||||
FirstTrustedHeight() (int64, error)
|
||||
|
||||
// query configuration options
|
||||
ChainID() string
|
||||
Primary() provider.Provider
|
||||
Witnesses() []provider.Provider
|
||||
|
||||
// verify new headers
|
||||
VerifyHeaderAtHeight(height int64, now time.Time) (*types.SignedHeader, error)
|
||||
VerifyHeader(newHeader *types.SignedHeader, newVals *types.ValidatorSet, now time.Time) 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.
|
||||
|
||||
Due to bisection algorithm nature, some headers might be skipped. If the light
|
||||
client does not have a header for height `X` and `TrustedHeader(X)` or
|
||||
`TrustedValidatorSet(X)` methods are called, it will download the header from
|
||||
primary provider and perform a backwards verification.
|
||||
|
||||
```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 {
|
||||
SaveSignedHeaderAndNextValidatorSet(sh *types.SignedHeader, valSet *types.ValidatorSet) error
|
||||
DeleteSignedHeaderAndNextValidatorSet(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)
|
||||
}
|
||||
```
|
||||
|
||||
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,
|
||||
h1 *types.SignedHeader,
|
||||
h1NextVals *types.ValidatorSet,
|
||||
h2 *types.SignedHeader,
|
||||
h2Vals *types.ValidatorSet,
|
||||
trustingPeriod time.Duration,
|
||||
now time.Time,
|
||||
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.
|
||||
|
||||
## 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)
|
||||
Reference in New Issue
Block a user