github.com/pokt-network/tendermint@v0.32.11-0.20230426215212-59310158d3e9/docs/architecture/adr-046-light-client-implementation.md (about)

     1  # ADR 046: Lite Client Implementation
     2  
     3  ## Changelog
     4  * 13-02-2020: Initial draft
     5  * 26-02-2020: Cross-checking the first header
     6  * 28-02-2020: Bisection algorithm details
     7  * 31-03-2020: Verify signature got changed
     8  
     9  ## Context
    10  
    11  A `Client` struct represents a light client, connected to a single blockchain.
    12  
    13  The user has an option to verify headers using `VerifyHeader` or
    14  `VerifyHeaderAtHeight` or `Update` methods. The latter method downloads the
    15  latest header from primary and compares it with the currently trusted one.
    16  
    17  ```go
    18  type Client interface {
    19  	// verify new headers
    20  	VerifyHeaderAtHeight(height int64, now time.Time) (*types.SignedHeader, error)
    21  	VerifyHeader(newHeader *types.SignedHeader, newVals *types.ValidatorSet, now time.Time) error
    22  	Update(now time.Time) (*types.SignedHeader, error)
    23  
    24  	// get trusted headers & validators
    25  	TrustedHeader(height int64) (*types.SignedHeader, error)
    26  	TrustedValidatorSet(height int64) (valSet *types.ValidatorSet, heightUsed int64, err error)
    27  	LastTrustedHeight() (int64, error)
    28  	FirstTrustedHeight() (int64, error)
    29  
    30  	// query configuration options
    31  	ChainID() string
    32  	Primary() provider.Provider
    33  	Witnesses() []provider.Provider
    34  
    35  	Cleanup() error
    36  }
    37  ```
    38  
    39  A new light client can either be created from scratch (via `NewClient`) or
    40  using the trusted store (via `NewClientFromTrustedStore`). When there's some
    41  data in the trusted store and `NewClient` is called, the light client will a)
    42  check if stored header is more recent b) optionally ask the user whenever it
    43  should rollback (no confirmation required by default).
    44  
    45  ```go
    46  func NewClient(
    47  	chainID string,
    48  	trustOptions TrustOptions,
    49  	primary provider.Provider,
    50  	witnesses []provider.Provider,
    51  	trustedStore store.Store,
    52  	options ...Option) (*Client, error) {
    53  ```
    54  
    55  `witnesses` as argument (as opposite to `Option`) is an intentional choice,
    56  made to increase security by default. At least one witness is required,
    57  although, right now, the light client does not check that primary != witness.
    58  When cross-checking a new header with witnesses, minimum number of witnesses
    59  required to respond: 1. Note the very first header (`TrustOptions.Hash`) is
    60  also cross-checked with witnesses for additional security.
    61  
    62  Due to bisection algorithm nature, some headers might be skipped. If the light
    63  client does not have a header for height `X` and `VerifyHeaderAtHeight(X)` or
    64  `VerifyHeader(H#X)` methods are called, these will perform either a) backwards
    65  verification from the latest header back to the header at height `X` or b)
    66  bisection verification from the first stored header to the header at height `X`.
    67  
    68  `TrustedHeader`, `TrustedValidatorSet` only communicate with the trusted store.
    69  If some header is not there, an error will be returned indicating that
    70  verification is required.
    71  
    72  ```go
    73  type Provider interface {
    74  	ChainID() string
    75  
    76  	SignedHeader(height int64) (*types.SignedHeader, error)
    77  	ValidatorSet(height int64) (*types.ValidatorSet, error)
    78  }
    79  ```
    80  
    81  Provider is a full node usually, but can be another light client. The above
    82  interface is thin and can accommodate many implementations.
    83  
    84  If provider (primary or witness) becomes unavailable for a prolonged period of
    85  time, it will be removed to ensure smooth operation.
    86  
    87  Both `Client` and providers expose chain ID to track if there are on the same
    88  chain. Note, when chain upgrades or intentionally forks, chain ID changes.
    89  
    90  The light client stores headers & validators in the trusted store:
    91  
    92  ```go
    93  type Store interface {
    94  	SaveSignedHeaderAndValidatorSet(sh *types.SignedHeader, valSet *types.ValidatorSet) error
    95  	DeleteSignedHeaderAndValidatorSet(height int64) error
    96  
    97  	SignedHeader(height int64) (*types.SignedHeader, error)
    98  	ValidatorSet(height int64) (*types.ValidatorSet, error)
    99  
   100  	LastSignedHeaderHeight() (int64, error)
   101  	FirstSignedHeaderHeight() (int64, error)
   102  
   103  	SignedHeaderAfter(height int64) (*types.SignedHeader, error)
   104  
   105  	Prune(size uint16) error
   106  
   107  	Size() uint16
   108  }
   109  ```
   110  
   111  At the moment, the only implementation is the `db` store (wrapper around the KV
   112  database, used in Tendermint). In the future, remote adapters are possible
   113  (e.g. `Postgresql`).
   114  
   115  ```go
   116  func Verify(
   117  	chainID string,
   118  	trustedHeader *types.SignedHeader, // height=X
   119  	trustedVals *types.ValidatorSet, // height=X or height=X+1
   120  	untrustedHeader *types.SignedHeader, // height=Y
   121  	untrustedVals *types.ValidatorSet, // height=Y
   122  	trustingPeriod time.Duration,
   123  	now time.Time,
   124  	maxClockDrift time.Duration,
   125  	trustLevel tmmath.Fraction) error {
   126  ```
   127  
   128  `Verify` pure function is exposed for a header verification. It handles both
   129  cases of adjacent and non-adjacent headers. In the former case, it compares the
   130  hashes directly (2/3+ signed transition). Otherwise, it verifies 1/3+
   131  (`trustLevel`) of trusted validators are still present in new validators.
   132  
   133  While `Verify` function is certainly handy, `VerifyAdjacent` and
   134  `VerifyNonAdjacent` should be used most often to avoid logic errors.
   135  
   136  ### Bisection algorithm details
   137  
   138  Non-recursive bisection algorithm was implemented despite the spec containing
   139  the recursive version. There are two major reasons:
   140  
   141  1) Constant memory consumption => no risk of getting OOM (Out-Of-Memory) exceptions;
   142  2) Faster finality (see Fig. 1).
   143  
   144  _Fig. 1: Differences between recursive and non-recursive bisections_
   145  
   146  ![Fig. 1](./img/adr-046-fig1.png)
   147  
   148  Specification of the non-recursive bisection can be found
   149  [here](https://github.com/tendermint/spec/blob/zm_non-recursive-verification/spec/consensus/light-client/non-recursive-verification.md).
   150  
   151  ## Status
   152  
   153  Accepted.
   154  
   155  ## Consequences
   156  
   157  ### Positive
   158  
   159  * single `Client` struct, which is easy to use
   160  * flexible interfaces for header providers and trusted storage
   161  
   162  ### Negative
   163  
   164  * `Verify` needs to be aligned with the current spec
   165  
   166  ### Neutral
   167  
   168  * `Verify` function might be misused (called with non-adjacent headers in
   169    incorrectly implemented sequential verification)