github.com/aakash4dev/cometbft@v0.38.2/spec/light-client/verification/verification_001_published.md (about)

     1  # Light Client Verification
     2  
     3  The light client implements a read operation of a
     4  [header][#cmbc-header1] from the [blockchain][cmbc-seq1], by
     5  communicating with full nodes.  As some full nodes may be faulty, this
     6  functionality must be implemented in a fault-tolerant way.
     7  
     8  In a Cosmos blockchain, the validator set may change with every
     9  new block.  The staking and unbonding mechanism induces a [security
    10  model][CMBC-FM-2THIRDS-link]: starting at time *Time* of the
    11  [header][#cmbc-header1],
    12  more than two-thirds of the next validators of a new block are correct
    13  for the duration of *TrustedPeriod*. The fault-tolerant read
    14  operation is designed for this security model.
    15  
    16  The challenge addressed here is that the light client might have a
    17  block of height *h1* and needs to read the block of height *h2*
    18  greater than *h1*.  Checking all headers of heights from *h1* to *h2*
    19  might be too costly (e.g., in terms of energy for mobile devices).
    20  This specification tries to reduce the number of intermediate blocks
    21  that need to be checked, by exploiting the guarantees provided by the
    22  [security model][cmbc-fm-2thirds1].
    23  
    24  # Status
    25  
    26  This document is thoroughly reviewed, and the protocol has been
    27  formalized in TLA+ and model checked.
    28  
    29  ## Issues that need to be addressed
    30  
    31  As it is part of the larger light node, its data structures and
    32  functions interact with the fork dectection functionality of the light
    33  client. As a result of the work on
    34  [Pull Request 479](https://github.com/informalsystems/tendermint-rs/pull/479) we
    35  established the need for an update in the data structures in [Issue 499](https://github.com/informalsystems/tendermint-rs/issues/499). This
    36  will not change the verification logic, but it will record information
    37  about verification that can be used in fork detection (in particular
    38  in computing more efficiently the proof of fork).
    39  
    40  # Outline
    41  
    42  - [Part I](#part-i---cosmos-blockchain): Introduction of
    43   relevant terms of the Cosmos
    44  blockchain.
    45  
    46  - [Part II](#part-ii---sequential-definition-of-the-verification-problem): Introduction
    47  of the problem addressed by the Lightclient Verification protocol.
    48      - [Verification Informal Problem
    49        statement](#verification-informal-problem-statement): For the general
    50        audience, that is, engineers who want to get an overview over what
    51        the component is doing from a bird's eye view.
    52      - [Sequential Problem statement](#sequential-problem-statement):
    53        Provides a mathematical definition of the problem statement in
    54        its sequential form, that is, ignoring the distributed aspect of
    55        the implementation of the blockchain.
    56  
    57  - [Part III](#part-iii---light-client-as-distributed-system): Distributed
    58    aspects of the light client, system assumptions and temporal
    59    logic specifications.
    60  
    61      - [Incentives](#incentives): how faulty full nodes may benefit from
    62      misbehaving and how correct full nodes benefit from cooperating.
    63    
    64      - [Computational Model](#computational-model):
    65        timing and correctness assumptions.
    66  
    67      - [Distributed Problem Statement](#distributed-problem-statement):
    68        temporal properties that formalize safety and liveness
    69        properties in the distributed setting.
    70  
    71  - [Part IV](#part-iv---light-client-verification-protocol):
    72    Specification of the protocols.
    73  
    74      - [Definitions](#definitions): Describes inputs, outputs,
    75         variables used by the protocol, auxiliary functions
    76  
    77      - [Core Verification](#core-verification): gives an outline of the solution,
    78         and details of the functions used (with preconditions,
    79         postconditions, error conditions).
    80  
    81      - [Liveness Scenarios](#liveness-scenarios): when the light
    82         client makes progress depends heavily on the changes in the
    83         validator sets of the blockchain. We discuss some typical scenarios.
    84  
    85  - [Part V](#part-v---supporting-the-ibc-relayer): The above parts
    86    focus on a common case where the last verified block has height *h1*
    87    and the
    88    requested height *h2* satisfies *h2 > h1*. For IBC, there are
    89    scenarios where this might not be the case. In this part, we provide
    90    some preliminaries for supporting this. As not all details of the
    91    IBC requirements are clear by now, we do not provide a complete
    92    specification at this point. We mark with "Open Question" points
    93    that need to be addressed in order to finalize this specification.
    94    It should be noted that the technically
    95    most challenging case is the one specified in Part IV.
    96  
    97  In this document we quite extensively use tags in order to be able to
    98  reference assumptions, invariants, etc. in future communication. In
    99  these tags we frequently use the following short forms:
   100  
   101  - CMBC: Cosmos blockchain
   102  - SEQ: for sequential specifications
   103  - LCV: Lightclient Verification
   104  - LIVE: liveness
   105  - SAFE: safety
   106  - FUNC: function
   107  - INV: invariant
   108  - A: assumption
   109  
   110  # Part I - Cosmos Blockchain
   111  
   112  ## Header Fields necessary for the Light Client
   113  
   114  #### **[CMBC-HEADER.1]**
   115  
   116  A set of blockchain transactions is stored in a data structure called
   117  *block*, which contains a field called *header*. (The data structure
   118  *block* is defined [here][block]).  As the header contains hashes to
   119  the relevant fields of the block, for the purpose of this
   120  specification, we will assume that the blockchain is a list of
   121  headers, rather than a list of blocks.
   122  
   123  #### **[CMBC-HASH-UNIQUENESS.1]**
   124  
   125  We assume that every hash in the header identifies the data it hashes.
   126  Therefore, in this specification, we do not distinguish between hashes and the
   127  data they represent.
   128  
   129  #### **[CMBC-HEADER-FIELDS.1]**
   130  
   131  A header contains the following fields:
   132  
   133  - `Height`: non-negative integer
   134  - `Time`: time (integer)
   135  - `LastBlockID`: Hashvalue
   136  - `LastCommit` DomainCommit
   137  - `Validators`: DomainVal
   138  - `NextValidators`: DomainVal
   139  - `Data`: DomainTX
   140  - `AppState`: DomainApp
   141  - `LastResults`: DomainRes
   142  
   143  #### **[CMBC-SEQ.1]**
   144  
   145  The Cosmos blockchain is a list *chain* of headers.
   146  
   147  #### **[CMBC-VALIDATOR-PAIR.1]**
   148  
   149  Given a full node, a
   150  *validator pair* is a pair *(peerID, voting_power)*, where
   151  
   152  - *peerID* is the PeerID (public key) of a full node,
   153  - *voting_power* is an integer (representing the full node's
   154    voting power in a certain consensus instance).
   155    
   156  > In the Golang implementation the data type for *validator
   157  pair* is called `Validator`
   158  
   159  #### **[CMBC-VALIDATOR-SET.1]**
   160  
   161  A *validator set* is a set of validator pairs. For a validator set
   162  *vs*, we write *TotalVotingPower(vs)* for the sum of the voting powers
   163  of its validator pairs.
   164  
   165  #### **[CMBC-VOTE.1]**
   166  
   167  A *vote* contains a `prevote` or `precommit` message sent and signed by
   168  a validator node during the execution of [consensus][arXiv]. Each
   169  message contains the following fields
   170  
   171  - `Type`: prevote or precommit
   172  - `Height`: positive integer
   173  - `Round` a positive integer
   174  - `BlockID` a Hashvalue of a block (not necessarily a block of the chain)
   175  
   176  #### **[CMBC-COMMIT.1]**
   177  
   178  A commit is a set of `precommit` message.
   179  
   180  ## Cosmos Failure Model
   181  
   182  #### **[CMBC-AUTH-BYZ.1]**
   183  
   184  We assume the authenticated Byzantine fault model in which no node (faulty or
   185  correct) may break digital signatures, but otherwise, no additional
   186  assumption is made about the internal behavior of faulty
   187  nodes. That is, faulty nodes are only limited in that they cannot forge
   188  messages.
   189  
   190  #### **[CMBC-TIME-PARAMS.1]**
   191  
   192  A Cosmos blockchain has the following configuration parameters:
   193  
   194  - *unbondingPeriod*: a time duration.
   195  - *trustingPeriod*: a time duration smaller than *unbondingPeriod*.
   196  
   197  #### **[CMBC-CORRECT.1]**
   198  
   199  We define a predicate *correctUntil(n, t)*, where *n* is a node and *t* is a
   200  time point.
   201  The predicate *correctUntil(n, t)* is true if and only if the node *n*
   202  follows all the protocols (at least) until time *t*.
   203  
   204  #### **[CMBC-FM-2THIRDS.1]**
   205  
   206  If a block *h* is in the chain,
   207  then there exists a subset *CorrV*
   208  of *h.NextValidators*, such that:
   209  
   210  - *TotalVotingPower(CorrV) > 2/3
   211      TotalVotingPower(h.NextValidators)*; cf. [CMBC-VALIDATOR-SET.1]
   212  - For every validator pair *(n,p)* in *CorrV*, it holds *correctUntil(n,
   213      h.Time + trustingPeriod)*; cf. [CMBC-CORRECT.1]
   214  
   215  > The definition of correct
   216  > [**[CMBC-CORRECT.1]**][CMBC-CORRECT-link] refers to realtime, while it
   217  > is used here with *Time* and *trustingPeriod*, which are "hardware
   218  > times".  We do not make a distinction here.
   219  
   220  #### **[CMBC-CORR-FULL.1]**
   221  
   222  Every correct full node locally stores a prefix of the
   223  current list of headers from [**[CMBC-SEQ.1]**][CMBC-SEQ-link].
   224  
   225  ## What the Light Client Checks
   226  
   227  > From [CMBC-FM-2THIRDS.1] we directly derive the following observation:
   228  
   229  #### **[CMBC-VAL-CONTAINS-CORR.1]**
   230  
   231  Given a (trusted) block *tb* of the blockchain, a given set of full nodes
   232  *N* contains a correct node at a real-time *t*, if
   233  
   234  - *t - trustingPeriod < tb.Time < t*
   235  - the voting power in tb.NextValidators of nodes in *N* is more
   236       than 1/3 of *TotalVotingPower(tb.NextValidators)*
   237  
   238  > The following describes how a commit for a given block *b* must look
   239  > like.
   240  
   241  #### **[CMBC-SOUND-DISTR-POSS-COMMIT.1]**
   242  
   243  For a block *b*, each element *pc* of *PossibleCommit(b)* satisfies:
   244  
   245  - *pc* contains only votes (cf. [CMBC-VOTE.1])
   246    by validators from *b.Validators*
   247  - the sum of the voting powers in *pc* is greater than 2/3
   248    *TotalVotingPower(b.Validators)*
   249  - and there is an *r* such that  each vote *v* in *pc* satisfies
   250      - v.Type = precommit
   251      - v.Height = b.Height
   252      - v.Round = r
   253      - v.blockID = hash(b)
   254  
   255  > The following property comes from the validity of the [consensus][arXiv]: A
   256  > correct validator node only sends `prevote` or `precommit`, if
   257  > `BlockID` of the new (to-be-decided) block is equal to the hash of
   258  > the last block.
   259  
   260  #### **[CMBC-VAL-COMMIT.1]**
   261  
   262  If for a block *b*,  a commit *c*
   263  
   264  - contains at least one validator pair *(v,p)* such that *v* is a
   265      **correct** validator node, and
   266  - is contained in *PossibleCommit(b)*
   267    
   268  then the block *b* is on the blockchain.
   269  
   270  ## Context of this document
   271  
   272  In this document we specify the light client verification component,
   273  called *Core Verification*.  The *Core Verification* communicates with
   274  a full node.  As full nodes may be faulty, it cannot trust the
   275  received information, but the light client has to check whether the
   276  header it receives coincides with the one generated by Tendermint
   277  consensus.
   278  
   279  The two
   280   properties [[CMBC-VAL-CONTAINS-CORR.1]][CMBC-VAL-CONTAINS-CORR-link] and
   281  [[CMBC-VAL-COMMIT]][CMBC-VAL-COMMIT-link]  formalize the checks done
   282   by this specification:
   283  Given a trusted block *tb* and an untrusted block *ub* with a commit *cub*,
   284  one has to check that *cub* is in *PossibleCommit(ub)*, and that *cub*
   285  contains a correct node using *tb*.
   286  
   287  # Part II - Sequential Definition of the Verification Problem
   288  
   289  ## Verification Informal Problem statement
   290  
   291  Given a height *targetHeight* as an input, the *Verifier* eventually
   292  stores a header *h* of height *targetHeight* locally.  This header *h*
   293  is generated by the Cosmos [blockchain][block]. In
   294  particular, a header that was not generated by the blockchain should
   295  never be stored.
   296  
   297  ## Sequential Problem statement
   298  
   299  #### **[LCV-SEQ-LIVE.1]**
   300  
   301  The *Verifier* gets as input a height *targetHeight*, and eventually stores the
   302  header of height *targetHeight* of the blockchain.
   303  
   304  #### **[LCV-SEQ-SAFE.1]**
   305  
   306  The *Verifier* never stores a header which is not in the blockchain.
   307  
   308  # Part III - Light Client as Distributed System
   309  
   310  ## Incentives
   311  
   312  Faulty full nodes may benefit from lying to the light client, by making the
   313  light client accept a block that deviates (e.g., contains additional
   314  transactions) from the one generated by Tendermint consensus.
   315  Users using the light client might be harmed by accepting a forged header.
   316  
   317  The [fork detector][fork-detector] of the light client may help the
   318  correct full nodes to understand whether their header is a good one.
   319  Hence, in combination with the light client detector, the correct full
   320  nodes have the incentive to respond.  We can thus base liveness
   321  arguments on the assumption that correct full nodes reliably talk to
   322  the light client.
   323  
   324  ## Computational Model
   325  
   326  #### **[LCV-A-PEER.1]**
   327  
   328  The verifier communicates with a full node called *primary*. No assumption is made about the full node (it may be correct or faulty).
   329  
   330  #### **[LCV-A-COMM.1]**
   331  
   332  Communication between the light client and a correct full node is
   333  reliable and bounded in time. Reliable communication means that
   334  messages are not lost, not duplicated, and eventually delivered. There
   335  is a (known) end-to-end delay *Delta*, such that if a message is sent
   336  at time *t* then it is received and processes by time *t + Delta*.
   337  This implies that we need a timeout of at least *2 Delta* for remote
   338  procedure calls to ensure that the response of a correct peer arrives
   339  before the timeout expires.
   340  
   341  #### **[LCV-A-TFM.1]**
   342  
   343  The Cosmos blockchain satisfies the Cosmos failure model [**[CMBC-FM-2THIRDS.1]**][CMBC-FM-2THIRDS-link].
   344  
   345  #### **[LCV-A-VAL.1]**
   346  
   347  The system satisfies [**[CMBC-AUTH-BYZ.1]**][CMBC-Auth-Byz-link] and
   348  [**[CMBC-FM-2THIRDS.1]**][CMBC-FM-2THIRDS-link]. Thus, there is a
   349  blockchain that satisfies the soundness requirements (that is, the
   350  validation rules in [[block]]).
   351  
   352  ## Distributed Problem Statement
   353  
   354  ### Two Kinds of Termination
   355  
   356  We do not assume that *primary* is correct. Under this assumption no
   357  protocol can guarantee the combination of the sequential
   358  properties. Thus, in the (unreliable) distributed setting, we consider
   359  two kinds of termination (successful and failure) and we will specify
   360  below under what (favorable) conditions *Core Verification* ensures to
   361  terminate successfully, and satisfy the requirements of the sequential
   362  problem statement:
   363  
   364  #### **[LCV-DIST-TERM.1]**
   365  
   366  *Core Verification* either *terminates
   367  successfully* or it *terminates with failure*.
   368  
   369  ### Design choices
   370  
   371  #### **[LCV-DIST-STORE.1]**
   372  
   373  *Core Verification* has a local data structure called *LightStore* that
   374  contains light blocks (that contain a header). For each light block we
   375  record whether it is verified.
   376  
   377  #### **[LCV-DIST-PRIMARY.1]**
   378  
   379  *Core Verification* has a local variable *primary* that contains the PeerID of a full node.
   380  
   381  #### **[LCV-DIST-INIT.1]**
   382  
   383  *LightStore* is initialized with a header *trustedHeader* that was correctly
   384  generated by the Tendermint consensus. We say *trustedHeader* is verified.
   385  
   386  ### Temporal Properties
   387  
   388  #### **[LCV-DIST-SAFE.1]**
   389  
   390  It is always the case that every verified header in *LightStore* was
   391  generated by an instance of Tendermint consensus.
   392  
   393  #### **[LCV-DIST-LIVE.1]**
   394  
   395  From time to time, a new instance of *Core Verification* is called with a
   396  height *targetHeight* greater than the height of any header in *LightStore*.
   397  Each instance must eventually terminate.
   398  
   399  - If
   400      - the  *primary* is correct (and locally has the block of
   401         *targetHeight*), and
   402      - *LightStore* always contains a verified header whose age is less than the
   403          trusting period,  
   404      then *Core Verification* adds a verified header *hd* with height
   405      *targetHeight* to *LightStore* and it **terminates successfully**
   406  
   407  > These definitions imply that if the primary is faulty, a header may or
   408  > may not be added to *LightStore*. In any case,
   409  > [**[LCV-DIST-SAFE.1]**](https://github.com/aakash4dev/cometbft/blob/main/spec/light-client/verification/verification_001_published.md#lcv-dist-safe1) must hold.
   410  > The invariant [**[LCV-DIST-SAFE.1]**](https://github.com/aakash4dev/cometbft/blob/main/spec/light-client/verification/verification_001_published.md#lcv-dist-safe1) and the liveness
   411  > requirement [**[LCV-DIST-LIVE.1]**](https://github.com/aakash4dev/cometbft/blob/main/spec/light-client/verification/verification_001_published.md#lcv-dist-life1)
   412  > allow that verified headers are added to *LightStore* whose
   413  > height was not passed
   414  > to the verifier (e.g., intermediate headers used in bisection; see below).
   415  > Note that for liveness, initially having a *trustedHeader* within
   416  > the *trustinPeriod* is not sufficient. However, as this
   417  > specification will leave some freedom with respect to the strategy
   418  > in which order to download intermediate headers, we do not give a
   419  > more precise liveness specification here. After giving the
   420  > specification of the protocol, we will discuss some liveness
   421  > scenarios [below](#liveness-scenarios).
   422  
   423  ### Solving the sequential specification
   424  
   425  This specification provides a partial solution to the sequential specification.
   426  The *Verifier* solves the invariant of the sequential part
   427  
   428  [**[LCV-DIST-SAFE.1]**](https://github.com/aakash4dev/cometbft/blob/main/spec/light-client/verification/verification_001_published.md#lcv-dist-safe1) => [**[LCV-SEQ-SAFE.1]**](https://github.com/aakash4dev/cometbft/blob/main/spec/light-client/verification/verification_001_published.md#lcv-seq-inv)
   429  
   430  In the case the primary is correct, and there is a recent header in *LightStore*, the verifier satisfies the liveness requirements.
   431  
   432  ⋀ *primary is correct*  
   433  ⋀ always ∃ verified header in LightStore. *header.Time* > *now* - *trustingPeriod*  
   434  ⋀ [**[LCV-A-Comm.1]**](https://github.com/aakash4dev/cometbft/blob/main/spec/light-client/verification/verification_001_published.md#lcv-a-comm) ⋀ (
   435         ( [**[CMBC-CorrFull.1]**][CMBC-CorrFull-link] ⋀
   436           [**[LCV-DIST-LIVE.1]**](https://github.com/aakash4dev/cometbft/blob/main/spec/light-client/verification/verification_001_published.md#lcv-dist-live1) )
   437         ⟹ [**[LCV-SEQ-LIVE.1]**](https://github.com/aakash4dev/cometbft/blob/main/spec/light-client/verification/verification_001_published.md#lcv-seq-live1)
   438  )
   439  
   440  # Part IV - Light Client Verification Protocol
   441  
   442  We provide a specification for Light Client Verification. The local
   443  code for verification is presented by a sequential function
   444  `VerifyToTarget` to highlight the control flow of this functionality.
   445  We note that if a different concurrency model is considered for
   446  an implementation, the sequential flow of the function may be
   447  implemented with mutexes, etc. However, the light client verification
   448  is partitioned into three blocks that can be implemented and tested
   449  independently:
   450  
   451  - `FetchLightBlock` is called to download a light block (header) of a
   452    given height from a peer.
   453  - `ValidAndVerified` is a local code that checks the header.
   454  - `Schedule` decides which height to try to verify next. We keep this
   455    underspecified as different implementations (currently in Goland and
   456    Rust) may implement different optimizations here. We just provide
   457    necessary conditions on how the height may evolve.
   458    
   459  <!-- > `ValidAndVerified` is the function that is sometimes called "Light -->
   460  <!-- > Client" in the IBC context. -->
   461  
   462  ## Definitions
   463  
   464  ### Data Types
   465  
   466  The core data structure of the protocol is the LightBlock.
   467  
   468  #### **[LCV-DATA-LIGHTBLOCK.1]**
   469  
   470  ```go
   471  type LightBlock struct {
   472    Header          Header
   473    Commit          Commit
   474    Validators      ValidatorSet
   475  }
   476  ```
   477  
   478  #### **[LCV-DATA-LIGHTSTORE.1]**
   479  
   480  LightBlocks are stored in a structure which stores all LightBlock from
   481  initialization or received from peers.
   482  
   483  ```go
   484  type LightStore struct {
   485   ...
   486  }
   487  
   488  ```
   489  
   490  Each LightBlock is in one of the following states:
   491  
   492  ```go
   493  type VerifiedState int
   494  
   495  const (
   496   StateUnverified = iota + 1
   497   StateVerified
   498   StateFailed
   499   StateTrusted
   500  )
   501  ```
   502  
   503  > Only the detector module sets a lightBlock state to `StateTrusted`
   504  > and only if it was `StateVerified` before.
   505  
   506  The LightStore exposes the following functions to query stored LightBlocks.
   507  
   508  #### **[LCV-FUNC-GET.1]**
   509  
   510  ```go
   511  func (ls LightStore) Get(height Height) (LightBlock, bool)
   512  ```
   513  
   514  - Expected postcondition
   515      - returns a LightBlock at a given height or false in the second argument if
   516      the LightStore does not contain the specified LightBlock.
   517  
   518  #### **[LCV-FUNC-LATEST-VERIF.1]**
   519  
   520  ```go
   521  func (ls LightStore) LatestVerified() LightBlock
   522  ```
   523  
   524  - Expected postcondition
   525      - returns the highest light block whose state is `StateVerified`
   526       or `StateTrusted`
   527  
   528  #### **[LCV-FUNC-UPDATE.2]**
   529  
   530  ```go
   531  func (ls LightStore) Update(lightBlock LightBlock, 
   532                              verfiedState VerifiedState
   533         verifiedBy Height)
   534  ```
   535  
   536  - Expected postcondition
   537      - The state of the LightBlock is set to *verifiedState*.
   538      - verifiedBy of the Lightblock is set to *Height*
   539  
   540  > The following function is used only in the detector specification
   541  > listed here for completeness.
   542  
   543  #### **[LCV-FUNC-LATEST-TRUSTED.1]**
   544  
   545  ```go
   546  func (ls LightStore) LatestTrusted() LightBlock
   547  ```
   548  
   549  - Expected postcondition
   550      - returns the highest light block that has been verified and
   551       checked by the detector.
   552  
   553  #### **[LCV-FUNC-FILTER.1]**
   554  
   555  ```go
   556  func (ls LightStore) FilterVerified() LightSTore
   557  ```
   558  
   559  - Expected postcondition
   560      - returns only the LightBlocks with state verified.
   561  
   562  ### Inputs
   563  
   564  - *lightStore*: stores light blocks that have been downloaded and that
   565      passed verification. Initially it contains a light block with
   566   *trustedHeader*.
   567  - *primary*: peerID
   568  - *targetHeight*: the height of the needed header
   569  
   570  ### Configuration Parameters
   571  
   572  - *trustThreshold*: a float. Can be used if correctness should not be based on more voting power and 1/3.
   573  - *trustingPeriod*: a time duration [**[CMBC-TIME_PARAMS.1]**][CMBC-TIME_PARAMS-link].
   574  - *clockDrift*: a time duration. Correction parameter dealing with only approximately synchronized clocks.
   575  
   576  ### Variables
   577  
   578  - *nextHeight*: initially *targetHeight*
   579    > *nextHeight* should be thought of the "height of the next header we need
   580    > to download and verify"
   581  
   582  ### Assumptions
   583  
   584  #### **[LCV-A-INIT.1]**
   585  
   586  - *trustedHeader* is from the blockchain
   587  
   588  - *targetHeight > LightStore.LatestVerified.Header.Height*
   589  
   590  ### Invariants
   591  
   592  #### **[LCV-INV-TP.1]**
   593  
   594  It is always the case that *LightStore.LatestTrusted.Header.Time > now - trustingPeriod*.
   595  
   596  > If the invariant is violated, the light client does not have a
   597  > header it can trust. A trusted header must be obtained externally,
   598  > its trust can only be based on social consensus.
   599  
   600  ### Used Remote Functions
   601  
   602  We use the functions `commit` and `validators` that are provided
   603  by the [RPC client][RPC].
   604  
   605  ```go
   606  func Commit(height int64) (SignedHeader, error)
   607  ```
   608  
   609  - Implementation remark
   610      - RPC to full node *n*
   611      - JSON sent:
   612  
   613  ```javascript
   614  // POST /commit
   615  {
   616   "jsonrpc": "2.0",
   617   "id": "ccc84631-dfdb-4adc-b88c-5291ea3c2cfb", // UUID v4, unique per request
   618   "method": "commit",
   619   "params": {
   620    "height": 1234
   621   }
   622  }
   623  ```
   624  
   625  - Expected precondition
   626      - header of `height` exists on blockchain
   627  - Expected postcondition
   628      - if *n* is correct: Returns the signed header of height `height`
   629    from the blockchain if communication is timely (no timeout)
   630      - if *n* is faulty: Returns a signed header with arbitrary content
   631  - Error condition
   632      - if *n* is correct: precondition violated or timeout
   633      - if *n* is faulty: arbitrary error
   634  
   635  ----
   636  
   637  ```go
   638  func Validators(height int64) (ValidatorSet, error)
   639  ```
   640  
   641  - Implementation remark
   642      - RPC to full node *n*
   643      - JSON sent:
   644  
   645  ```javascript
   646  // POST /validators
   647  {
   648   "jsonrpc": "2.0",
   649   "id": "ccc84631-dfdb-4adc-b88c-5291ea3c2cfb", // UUID v4, unique per request
   650   "method": "validators",
   651   "params": {
   652    "height": 1234
   653   }
   654  }
   655  ```
   656  
   657  - Expected precondition
   658      - header of `height` exists on blockchain
   659  - Expected postcondition
   660      - if *n* is correct: Returns the validator set of height `height`
   661    from the blockchain if communication is timely (no timeout)
   662      - if *n* is faulty: Returns arbitrary validator set
   663  - Error condition
   664      - if *n* is correct: precondition violated or timeout
   665      - if *n* is faulty: arbitrary error
   666  
   667  ----
   668  
   669  ### Communicating Function
   670  
   671  #### **[LCV-FUNC-FETCH.1]**
   672  
   673    ```go
   674  func FetchLightBlock(peer PeerID, height Height) LightBlock
   675  ```
   676  
   677  - Implementation remark
   678      - RPC to peer at *PeerID*
   679      - calls `Commit` for *height* and `Validators` for *height* and *height+1*
   680  - Expected precondition
   681      - `height` is less than or equal to height of the peer **[LCV-IO-PRE-HEIGHT.1]**
   682  - Expected postcondition:
   683      - if *node* is correct:
   684          - Returns the LightBlock *lb* of height `height`
   685        that is consistent with the blockchain
   686          - *lb.provider = peer* **[LCV-IO-POST-PROVIDER.1]**
   687          - *lb.Header* is a header consistent with the blockchain
   688          - *lb.Validators* is the validator set of the blockchain at height *nextHeight*
   689          - *lb.NextValidators* is the validator set of the blockchain at height *nextHeight + 1*
   690      - if *node* is faulty: Returns a LightBlock with arbitrary content
   691      [**[CMBC-AUTH-BYZ.1]**][CMBC-Auth-Byz-link]
   692  - Error condition
   693      - if *n* is correct: precondition violated
   694      - if *n* is faulty: arbitrary error
   695      - if *lb.provider != peer*
   696      - times out after 2 Delta (by assumption *n* is faulty)
   697  
   698  ----
   699  
   700  ## Core Verification
   701  
   702  ### Outline
   703  
   704  The `VerifyToTarget` is the main function and uses the following functions.
   705  
   706  - `FetchLightBlock` is called to download the next light block. It is
   707    the only function that communicates with other nodes
   708  - `ValidAndVerified` checks whether header is valid and checks if a
   709    new lightBlock should be trusted
   710    based on a previously verified lightBlock.
   711  - `Schedule` decides which height to try to verify next
   712  
   713  In the following description of `VerifyToTarget` we do not deal with error
   714  handling. If any of the above function returns an error, VerifyToTarget just
   715  passes the error on.
   716  
   717  #### **[LCV-FUNC-MAIN.1]**
   718  
   719  ```go
   720  func VerifyToTarget(primary PeerID, lightStore LightStore,
   721                      targetHeight Height) (LightStore, Result) {
   722  
   723      nextHeight := targetHeight
   724  
   725      for lightStore.LatestVerified.height < targetHeight {
   726  
   727          // Get next LightBlock for verification
   728          current, found := lightStore.Get(nextHeight)
   729          if !found {
   730              current = FetchLightBlock(primary, nextHeight)
   731              lightStore.Update(current, StateUnverified)
   732          }
   733  
   734          // Verify
   735          verdict = ValidAndVerified(lightStore.LatestVerified, current)
   736  
   737          // Decide whether/how to continue
   738          if verdict == SUCCESS {
   739              lightStore.Update(current, StateVerified)
   740          }
   741          else if verdict == NOT_ENOUGH_TRUST {
   742              // do nothing
   743     // the light block current passed validation, but the validator
   744              // set is too different to verify it. We keep the state of
   745     // current at StateUnverified. For a later iteration, Schedule
   746     // might decide to try verification of that light block again.
   747          }
   748          else {
   749              // verdict is some error code
   750              lightStore.Update(current, StateFailed)
   751              // possibly remove all LightBlocks from primary
   752              return (lightStore, ResultFailure)
   753          }
   754          nextHeight = Schedule(lightStore, nextHeight, targetHeight)
   755      }
   756      return (lightStore, ResultSuccess)
   757  }
   758  ```
   759  
   760  - Expected precondition
   761      - *lightStore* contains a LightBlock within the *trustingPeriod*  **[LCV-PRE-TP.1]**
   762      - *targetHeight* is greater than the height of all the LightBlocks in *lightStore*
   763  - Expected postcondition:
   764      - returns *lightStore* that contains a LightBlock that corresponds to a block
   765       of the blockchain of height *targetHeight*
   766       (that is, the LightBlock has been added to *lightStore*) **[LCV-POST-LS.1]**
   767  - Error conditions
   768      - if the precondition is violated
   769      - if `ValidAndVerified` or `FetchLightBlock` report an error
   770      - if [**[LCV-INV-TP.1]**](https://github.com/aakash4dev/cometbft/blob/main/spec/light-client/verification/verification_001_published.md#lcv-inv-tp1) is violated
   771    
   772  ### Details of the Functions
   773  
   774  #### **[LCV-FUNC-VALID.1]**
   775  
   776  ```go
   777  func ValidAndVerified(trusted LightBlock, untrusted LightBlock) Result
   778  ```
   779  
   780  - Expected precondition:
   781      - *untrusted* is valid, that is, satisfies the soundness [checks][block]
   782      - *untrusted* is **well-formed**, that is,
   783          - *untrusted.Header.Time < now + clockDrift*
   784          - *untrusted.Validators = hash(untrusted.Header.Validators)*
   785          - *untrusted.NextValidators = hash(untrusted.Header.NextValidators)*
   786      - *trusted.Header.Time > now - trustingPeriod*
   787      - *trusted.Commit* is a commit for the header
   788       *trusted.Header*, i.e., it contains
   789       the correct hash of the header, and +2/3 of signatures
   790      - the `Height` and `Time` of `trusted` are smaller than the Height and
   791    `Time` of `untrusted`, respectively
   792      - the *untrusted.Header* is well-formed (passes the tests from
   793       [[block]]), and in particular
   794          - if the untrusted header `unstrusted.Header` is the immediate
   795     successor  of  `trusted.Header`, then it holds that
   796              - *trusted.Header.NextValidators =
   797      untrusted.Header.Validators*, and
   798      moreover,
   799              - *untrusted.Header.Commit*
   800                  - contains signatures by more than two-thirds of the validators
   801                  - contains no signature from nodes that are not in *trusted.Header.NextValidators*
   802  - Expected postcondition:
   803      - Returns `SUCCESS`:
   804          - if *untrusted* is the immediate successor of *trusted*, or otherwise,
   805          - if the signatures of a set of validators that have more than
   806               *max(1/3,trustThreshold)* of voting power in
   807               *trusted.Nextvalidators* is contained in
   808               *untrusted.Commit* (that is, header passes the tests
   809               [**[CMBC-VAL-CONTAINS-CORR.1]**][CMBC-VAL-CONTAINS-CORR-link]
   810               and [**[CMBC-VAL-COMMIT.1]**][CMBC-VAL-COMMIT-link])
   811      - Returns `NOT_ENOUGH_TRUST` if:
   812          - *untrusted* is *not* the immediate successor of
   813             *trusted*
   814       and the  *max(1/3,trustThreshold)* threshold is not reached
   815             (that is, if
   816        [**[CMBC-VAL-CONTAINS-CORR.1]**][CMBC-VAL-CONTAINS-CORR-link]
   817        fails and header is does not violate the soundness
   818           checks [[block]]).
   819  - Error condition:
   820      - if precondition violated
   821  
   822  ----
   823  
   824  #### **[LCV-FUNC-SCHEDULE.1]**
   825  
   826  ```go
   827  func Schedule(lightStore, nextHeight, targetHeight) Height
   828  ```
   829  
   830  - Implementation remark: If picks the next height to be verified.
   831    We keep the precise choice of the next header under-specified. It is
   832    subject to performance optimizations that do not influence the correctness
   833  - Expected postcondition: **[LCV-SCHEDULE-POST.1]**
   834     Return *H* s.t.
   835     1. if *lightStore.LatestVerified.Height = nextHeight* and
   836        *lightStore.LatestVerified < targetHeight* then  
   837     *nextHeight < H <= targetHeight*
   838     2. if *lightStore.LatestVerified.Height < nextHeight* and
   839        *lightStore.LatestVerified.Height < targetHeight* then  
   840     *lightStore.LatestVerified.Height < H < nextHeight*
   841     3. if *lightStore.LatestVerified.Height = targetHeight* then  
   842       *H =  targetHeight*
   843  
   844  > Case i. captures the case where the light block at height *nextHeight*
   845  > has been verified, and we can choose a height closer to the *targetHeight*.
   846  > As we get the *lightStore* as parameter, the choice of the next height can
   847  > depend on the *lightStore*, e.g., we can pick a height for which we have
   848  > already downloaded a light block.
   849  > In Case ii. the header of *nextHeight* could not be verified, and we need to pick a smaller height.
   850  > In Case iii. is a special case when we have verified the *targetHeight*.
   851  
   852  ### Solving the distributed specification
   853  
   854  *trustedStore* is implemented by the light blocks in lightStore that
   855  have the state *StateVerified*.
   856  
   857  #### Argument for [**[LCV-DIST-SAFE.1]**](https://github.com/aakash4dev/cometbft/blob/main/spec/light-client/verification/verification_001_published.md#lcv-dist-safe)
   858  
   859  - `ValidAndVerified` implements the soundness checks and the checks
   860    [**[CMBC-VAL-CONTAINS-CORR.1]**][CMBC-VAL-CONTAINS-CORR-link] and
   861    [**[CMBC-VAL-COMMIT.1]**][CMBC-VAL-COMMIT-link] under
   862    the assumption [**[CMBC-FM-2THIRDS.1]**][CMBC-FM-2THIRDS-link]
   863  - Only if `ValidAndVerified` returns with `SUCCESS`, the state of a light block is
   864    set to *StateVerified*.
   865  
   866  #### Argument for [**[LCV-DIST-LIVE.1]**](https://github.com/aakash4dev/cometbft/blob/main/spec/light-client/verification/verification_001_published.md#lcv-dist-life)
   867  
   868  - If *primary* is correct,
   869      - `FetchLightBlock` will always return a light block consistent
   870        with the blockchain
   871      - `ValidAndVerified` either verifies the header using the trusting
   872        period or falls back to sequential
   873        verification
   874      - If [**[LCV-INV-TP.1]**](https://github.com/aakash4dev/cometbft/blob/main/spec/light-client/verification/verification_001_published.md#lcv-inv-tp1) holds, eventually every
   875     header will be verified and core verification **terminates successfully**.
   876      - successful termination depends on the age of *lightStore.LatestVerified*
   877        (for instance, initially on the age of  *trustedHeader*) and the
   878        changes of the validator sets on the blockchain.
   879     We will give some examples [below](#liveness-scenarios).
   880  - If *primary* is faulty,
   881      - it either provides headers that pass all the tests, and we
   882        return with the header
   883      - it provides one header that fails a test, core verification
   884        **terminates with failure**.
   885      - it times out and core verification
   886        **terminates with failure**.
   887  
   888  ## Liveness Scenarios
   889  
   890  The liveness argument above assumes [**[LCV-INV-TP.1]**](https://github.com/aakash4dev/cometbft/blob/main/spec/light-client/verification/verification_001_published.md#lcv-inv-tp1)
   891  
   892  which requires that there is a header that does not expire before the
   893  target height is reached. Here we discuss scenarios to ensure this.
   894  
   895  Let *startHeader* be *LightStore.LatestVerified* when core
   896  verification is called (*trustedHeader*) and *startTime* be the time
   897  core verification is invoked.
   898  
   899  In order to ensure liveness, *LightStore* always needs to contain a
   900  verified (or initially trusted) header whose time is within the
   901  trusting period. To ensure this, core verification needs to add new
   902  headers to *LightStore* and verify them, before all headers in
   903  *LightStore* expire.
   904  
   905  #### Many changes in validator set
   906  
   907   Let's consider `Schedule` implements
   908   bisection, that is, it halves the distance.
   909   Assume the case where the validator set changes completely in each
   910  block. Then the
   911   method in this specification needs to
   912  sequentially verify all headers. That is, for
   913  
   914  - *W = log_2 (targetHeight - startHeader.Height)*,
   915  
   916  *W* headers need to be downloaded and checked before the
   917  header of height *startHeader.Height + 1* is added to *LightStore*.
   918  
   919  - Let *Comp*
   920    be the local computation time needed to check headers and signatures
   921    for one header.
   922  - Then we need in the worst case *Comp + 2 Delta* to download and
   923    check one header.
   924  - Then the first time a verified header could be added to *LightStore* is
   925    startTime + W * (Comp + 2 Delta)
   926  - [TP.1] However, it can only be added if we still have a header in
   927    *LightStore*,
   928    which is not
   929    expired, that is only the case if
   930      - startHeader.Time > startTime + WCG * (Comp + 2 Delta) -
   931        trustingPeriod,
   932      - that is, if core verification is started at  
   933     startTime < startHeader.Time + trustingPeriod -  WCG * (Comp + 2 Delta)
   934  
   935  - one may then do an inductive argument from this point on, depending
   936    on the implementation of `Schedule`. We may have to account for the
   937    headers that are already
   938    downloaded, but they are checked against the new *LightStore.LatestVerified*.
   939  
   940  > We observe that
   941  > the worst case time it needs to verify the header of height
   942  > *targetHeight* depends mainly on how frequent the validator set on the
   943  > blockchain changes. That core verification terminates successfully
   944  > crucially depends on the check [TP.1], that is, that the headers in
   945  > *LightStore* do not expire in the time needed to download more
   946  > headers, which depends on the creation time of the headers in
   947  > *LightStore*. That is, termination of core verification is highly
   948  > depending on the data stored in the blockchain.
   949  > The current light client core verification protocol exploits that, in
   950  > practice, changes in the validator set are rare. For instance,
   951  > consider the following scenario.
   952  
   953  #### No change in validator set
   954  
   955  If on the blockchain the validator set of the block at height
   956  *targetHeight* is equal to *startHeader.NextValidators*:
   957  
   958  - there is one round trip in `FetchLightBlock` to download the light
   959   block
   960   of height
   961    *targetHeight*, and *Comp* to check it.
   962  - as the validator sets are equal, `Verify` returns `SUCCESS`, if
   963    *startHeader.Time > now - trustingPeriod*.
   964  - that is, if *startTime < startHeader.Header.Time + trustingPeriod -
   965    2 Delta - Comp*, then core verification terminates successfully
   966  
   967  # Part V - Supporting the IBC Relayer
   968  
   969  The above specification focuses on the most common case, which also
   970  constitutes the most challenging task: using the Cosmos [security
   971  model][CMBC-FM-2THIRDS-link] to verify light blocks without
   972  downloading all intermediate blocks. To focus on this challenge, above
   973  we have restricted ourselves to the case where  *targetHeight* is
   974  greater than the height of any trusted header. This simplified
   975  presentation of the algorithm as initially
   976  `lightStore.LatestVerified()` is less than *targetHeight*, and in the
   977  process of verification `lightStore.LatestVerified()` increases until
   978  *targetHeight* is reached.
   979  
   980  For [IBC][ibc-rs] it might be that some "older" header is
   981  needed, that is,  *targetHeight < lightStore.LatestVerified()*. In this section we present a preliminary design, and we mark some
   982  remaining open questions.
   983  If  *targetHeight < lightStore.LatestVerified()* our design separates
   984  the following cases:
   985  
   986  - A previous instance of `VerifyToTarget` has already downloaded the
   987    light block of *targetHeight*. There are two cases
   988      - the light block has been verified
   989      - the light block has not been verified yet
   990  - No light block of *targetHeight* had been downloaded before. There
   991    are two cases:
   992      - there exists a verified light block of height less than  *targetHeight*
   993      - otherwise. In this case we need to do "backwards verification"
   994       using the hash of the previous block in the `LastBlockID` field
   995       of a header.
   996    
   997  **Open Question:** what are the security assumptions for backward
   998  verification. Should we check that the light block we verify from
   999  (and/or the checked light block) is within the trusting period?
  1000  
  1001  The design just presents the above case
  1002  distinction as a function, and defines some auxiliary functions in the
  1003  same way the protocol was presented in
  1004  [Part IV](#part-iv---light-client-verification-protocol).
  1005  
  1006  ```go
  1007  func (ls LightStore) LatestPrevious(height Height) (LightBlock, bool)
  1008  ```
  1009  
  1010  - Expected postcondition
  1011      - returns a light block *lb* that satisfies:
  1012          - *lb* is in lightStore
  1013          - *lb* is verified and not expired
  1014          - *lb.Header.Height < height*
  1015          - for all *b* in lightStore s.t. *b* is verified and not expired it
  1016            holds *lb.Header.Height >= b.Header.Height*
  1017      - *false* in the second argument if
  1018        the LightStore does not contain such an *lb*.
  1019  
  1020  ```go
  1021  func (ls LightStore) MinVerified() (LightBlock, bool)
  1022  ```
  1023  
  1024  - Expected postcondition
  1025      - returns a light block *lb* that satisfies:
  1026          - *lb* is in lightStore
  1027          - *lb* is verified **Open Question:** replace by trusted?
  1028          - *lb.Header.Height* is minimal in the lightStore
  1029          - **Open Question:** according to this, it might be expired (outside the
  1030            trusting period). This approach appears safe. Are there reasons we
  1031            should not do that?
  1032      - *false* in the second argument if
  1033        the LightStore does not contain such an *lb*.
  1034  
  1035  If a height that is smaller than the smallest height in the lightstore
  1036  is required, we check the hashes backwards. This is done with the
  1037  following function:
  1038  
  1039  #### **[LCV-FUNC-BACKWARDS.1]**
  1040  
  1041  ```go
  1042  func Backwards (primary PeerID, lightStore LightStore, targetHeight Height)
  1043                 (LightStore, Result) {
  1044    
  1045      lb,res = lightStore.MinVerified()
  1046      if res = false {
  1047          return (lightStore, ResultFailure)
  1048      }
  1049  
  1050      latest := lb.Header
  1051      for i := lb.Header.height - 1; i >= targetHeight; i-- {
  1052          // here we download height-by-height. We might first download all
  1053          // headers down to targetHeight and then check them.
  1054          current := FetchLightBlock(primary,i)
  1055          if (hash(current) != latest.Header.LastBlockId) {
  1056              return (lightStore, ResultFailure)
  1057          }
  1058          else {
  1059              lightStore.Update(current, StateVerified)
  1060              // **Open Question:** Do we need a new state type for
  1061              // backwards verified light blocks?
  1062          }
  1063          latest = current
  1064      }
  1065      return (lightStore, ResultSuccess)
  1066  }
  1067  ```
  1068  
  1069  The following function just decided based on the required height which
  1070  method should be used.
  1071  
  1072  #### **[LCV-FUNC-IBCMAIN.1]**
  1073  
  1074  ```go
  1075  func Main (primary PeerID, lightStore LightStore, targetHeight Height)
  1076            (LightStore, Result) {
  1077  
  1078      b1, r1 = lightStore.Get(targetHeight)
  1079      if r1 = true and b1.State = StateVerified {
  1080          // block already there
  1081          return (lightStore, ResultSuccess)
  1082      }
  1083  
  1084      if targetHeight > lightStore.LatestVerified.height {
  1085       // case of Part IV
  1086          return VerifyToTarget(primary, lightStore, targetHeight)
  1087      }
  1088      else {
  1089          b2, r2 = lightStore.LatestPrevious(targetHeight);
  1090          if r2 = true {
  1091              // make auxiliary lightStore auxLS to call VerifyToTarget.
  1092     // VerifyToTarget uses LatestVerified of the given lightStore
  1093              // For that we need:
  1094              // auxLS.LatestVerified = lightStore.LatestPrevious(targetHeight)
  1095              auxLS.Init;
  1096              auxLS.Update(b2,StateVerified);
  1097              if r1 = true {
  1098                  // we need to verify a previously downloaded light block.
  1099                  // we add it to the auxiliary store so that VerifyToTarget
  1100                  // does not download it again
  1101                  auxLS.Update(b1,b1.State);
  1102              }
  1103              auxLS, res2 = VerifyToTarget(primary, auxLS, targetHeight)
  1104              // move all lightblocks from auxLS to lightStore,
  1105              // maintain state
  1106     // we do that whether VerifyToTarget was successful or not
  1107              for i, s range auxLS {
  1108                  lighStore.Update(s,s.State)
  1109              }
  1110              return (lightStore, res2)
  1111          }
  1112          else {
  1113              return Backwards(primary, lightStore, targetHeight)
  1114          }
  1115      }
  1116  }
  1117  ```
  1118  <!-- - Expected postcondition: -->
  1119  <!--   - if targetHeight > lightStore.LatestVerified.height then -->
  1120  <!--     return VerifyToTarget(primary, lightStore, targetHeight) -->
  1121  <!--   - if targetHeight = lightStore.LatestVerified.height then -->
  1122  <!--     return (lightStore, ResultSuccess) -->
  1123  <!--   - if targetHeight < lightStore.LatestVerified.height -->
  1124  <!--      - let b2 be in lightStore  -->
  1125  <!--         - that is verified and not expired -->
  1126  <!-- 	    - b2.Header.Height < targetHeight -->
  1127  <!-- 	    - for all b in lightStore s.t. b  is verified and not expired it -->
  1128  <!--         holds b2.Header.Height >= b.Header.Height -->
  1129  <!-- 	 - if b2 does not exists -->
  1130  <!--          return Backwards(primary, lightStore, targetHeight) -->
  1131  <!-- 	 - if b2 exists -->
  1132  <!--           - make auxiliary light store auxLS containing only b2 -->
  1133    
  1134  <!-- 	       VerifyToTarget(primary, auxLS, targetHeight) -->
  1135  <!--      - if b2  -->
  1136  
  1137  # References
  1138  
  1139  [[block]] Specification of the block data structure.
  1140  
  1141  [[RPC]] RPC client
  1142  
  1143  [[fork-detector]] The specification of the light client fork detector.
  1144  
  1145  [[fullnode]] Specification of the full node API
  1146  
  1147  [[ibc-rs]] Rust implementation of IBC modules and relayer.
  1148  
  1149  [[lightclient]] The light client ADR [77d2651 on Dec 27, 2019].
  1150  
  1151  [RPC]: https://docs.cometbft.com/v0.34/rpc/
  1152  
  1153  [block]: https://github.com/aakash4dev/cometbft/blob/main/spec/core/data_structures.md
  1154  
  1155  [CMBC-SEQ-link]: #cmbc-seq1
  1156  [CMBC-CorrFull-link]: #cmbc-corr-full1
  1157  [CMBC-Auth-Byz-link]: #cmbc-auth-byz1
  1158  [CMBC-TIME_PARAMS-link]: #cmbc-time-params1
  1159  [CMBC-FM-2THIRDS-link]: #cmbc-fm-2thirds1
  1160  [CMBC-VAL-CONTAINS-CORR-link]: #cmbc-val-contains-corr1
  1161  [CMBC-VAL-COMMIT-link]: #cmbc-val-commit1
  1162  
  1163  [lightclient]: https://github.com/interchainio/tendermint-rs/blob/e2cb9aca0b95430fca2eac154edddc9588038982/docs/architecture/adr-002-lite-client.md
  1164  [fork-detector]: https://github.com/aakash4dev/cometbft/tree/main/spec/light-client/detection
  1165  [fullnode]: https://github.com/aakash4dev/cometbft/blob/main/spec/blockchain
  1166  
  1167  [ibc-rs]:https://github.com/informalsystems/ibc-rs
  1168  
  1169  
  1170  [arXiv]: https://arxiv.org/abs/1807.04938