You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

168 lines
5.7 KiB

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