From 3b5794ffe97fd94538eee9e3d013d6046eb72057 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 14 Feb 2020 11:35:14 +0100 Subject: [PATCH] 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 --- docs/architecture/README.md | 3 + .../adr-046-light-client-implementation.md | 146 ++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 docs/architecture/adr-046-light-client-implementation.md diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 7055a3676..9b3c2f661 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -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) diff --git a/docs/architecture/adr-046-light-client-implementation.md b/docs/architecture/adr-046-light-client-implementation.md new file mode 100644 index 000000000..11dc79fb9 --- /dev/null +++ b/docs/architecture/adr-046-light-client-implementation.md @@ -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)