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

     1  ---
     2  order: 1
     3  parent:
     4    title: Verification
     5    order: 2
     6  ---
     7  # Core Verification
     8  
     9  ## Problem statement
    10  
    11  We assume that the light client knows a (base) header `inithead` it trusts (by social consensus or because
    12  the light client has decided to trust the header before). The goal is to check whether another header
    13  `newhead` can be trusted based on the data in `inithead`.
    14  
    15  The correctness of the protocol is based on the assumption that `inithead` was generated by an instance of
    16  Tendermint consensus.
    17  
    18  ### Failure Model
    19  
    20  For the purpose of the following definitions we assume that there exists a function
    21  `validators` that returns the corresponding validator set for the given hash.
    22  
    23  The light client protocol is defined with respect to the following failure model:
    24  
    25  Given a known bound `TRUSTED_PERIOD`, and a block `b` with header `h` generated at time `Time`
    26  (i.e. `h.Time = Time`), a set of validators that hold more than 2/3 of the voting power
    27  in `validators(b.Header.NextValidatorsHash)` is correct until time `b.Header.Time + TRUSTED_PERIOD`.
    28  
    29  *Assumption*: "correct" is defined w.r.t. realtime (some Newtonian global notion of time, i.e., wall time),
    30  while `Header.Time` corresponds to the [BFT time](../../consensus/bft-time.md). In this note, we assume that clocks of correct processes
    31  are synchronized (for example using NTP), and therefore there is bounded clock drift (`CLOCK_DRIFT`) between local clocks and
    32  BFT time. More precisely, for every correct light client process and every `header.Time` (i.e. BFT Time, for a header correctly
    33  generated by the Tendermint consensus), the following inequality holds: `Header.Time < now + CLOCK_DRIFT`,
    34  where `now` corresponds to the system clock at the light client process.
    35  
    36  Furthermore, we assume that `TRUSTED_PERIOD` is (several) order of magnitude bigger than `CLOCK_DRIFT` (`TRUSTED_PERIOD >> CLOCK_DRIFT`),
    37  as `CLOCK_DRIFT` (using NTP) is in the order of milliseconds and `TRUSTED_PERIOD` is in the order of weeks.
    38  
    39  We expect a light client process defined in this document to be used in the context in which there is some
    40  larger period during which misbehaving validators can be detected and punished (we normally refer to it as `UNBONDING_PERIOD`
    41  due to the "bonding" mechanism in modern proof of stake systems). Furthermore, we assume that
    42  `TRUSTED_PERIOD < UNBONDING_PERIOD` and that they are normally of the same order of magnitude, for example
    43  `TRUSTED_PERIOD = UNBONDING_PERIOD / 2`.
    44  
    45  The specification in this document considers an implementation of the light client under the Failure Model defined above.
    46  Mechanisms like `fork accountability` and `evidence submission` are defined in the context of `UNBONDING_PERIOD` and
    47  they incentivize validators to follow the protocol specification defined in this document. If they don't,
    48  and we have 1/3 (or more) faulty validators, safety may be violated. Our approach then is
    49  to *detect* these cases (after the fact), and take suitable repair actions (automatic and social).
    50  This is discussed in document on [Fork accountability](../../consensus/light-client/accountability.md).
    51  
    52  The term "trusted" above indicates that the correctness of the protocol depends on
    53  this assumption. It is in the responsibility of the user that runs the light client to make sure that the risk
    54  of trusting a corrupted/forged `inithead` is negligible.
    55  
    56  *Remark*: This failure model might change to a hybrid version that takes heights into account in the future.
    57  
    58  ### High Level Solution
    59  
    60  Upon initialization, the light client is given a header `inithead` it trusts (by
    61  social consensus). When a light clients sees a new signed header `snh`, it has to decide whether to trust the new
    62  header. Trust can be obtained by (possibly) the combination of three methods.
    63  
    64  1. **Uninterrupted sequence of headers.** Given a trusted header `h` and an untrusted header `h1`,
    65  the light client trusts a header `h1` if it trusts all headers in between `h` and `h1`.
    66  
    67  2. **Trusted period.** Given a trusted header `h`, an untrusted header `h1 > h` and `TRUSTED_PERIOD` during which
    68  the failure model holds, we can check whether at least one validator, that has been continuously correct
    69  from `h.Time` until now, has signed `h1`. If this is the case, we can trust `h1`.
    70  
    71  3. **Bisection.** If a check according to 2. (trusted period) fails, the light client can try to
    72  obtain a header `hp` whose height lies between `h` and `h1` in order to check whether `h` can be used to
    73  get trust for `hp`, and `hp` can be used to get trust for `snh`. If this is the case we can trust `h1`;
    74  if not, we continue recursively until either we found set of headers that can build (transitively) trust relation
    75  between `h` and `h1`, or we failed as two consecutive headers don't verify against each other.
    76  
    77  ## Definitions
    78  
    79  ### Data structures
    80  
    81  In the following, only the details of the data structures needed for this specification are given.
    82  
    83   ```go
    84     type Header struct {
    85          Height               int64
    86          Time                 Time          // the chain time when the header (block) was generated
    87  
    88          LastBlockID          BlockID       // prev block info
    89          ValidatorsHash       []byte        // hash of the validators for the current block
    90          NextValidatorsHash   []byte        // hash of the validators for the next block
    91     }
    92  
    93     type SignedHeader struct {
    94          Header        Header
    95          Commit        Commit            // commit for the given header
    96     }
    97  
    98     type ValidatorSet struct {
    99          Validators         []Validator
   100          TotalVotingPower   int64
   101     }
   102  
   103     type Validator struct {
   104          Address       Address           // validator address (we assume validator's addresses are unique)
   105          VotingPower   int64             // validator's voting power
   106     }
   107  
   108     type TrustedState {
   109          SignedHeader   SignedHeader
   110          ValidatorSet   ValidatorSet
   111     }
   112   ```
   113  
   114  ### Functions
   115  
   116  For the purpose of this light client specification, we assume that the Cosmos Full Node
   117  exposes the following functions over RPC:
   118  
   119  ```go
   120      // returns signed header: Header with Commit, for the given height
   121      func Commit(height int64) (SignedHeader, error)
   122  
   123      // returns validator set for the given height
   124      func Validators(height int64) (ValidatorSet, error)
   125  ```
   126  
   127  Furthermore, we assume the following auxiliary functions:
   128  
   129  ```go
   130      // returns true if the commit is for the header, ie. if it contains
   131      // the correct hash of the header; otherwise false
   132      func matchingCommit(header Header, commit Commit) bool
   133  
   134      // returns the set of validators from the given validator set that
   135      // committed the block (that correctly signed the block)
   136      // it assumes signature verification so it can be computationally expensive
   137      func signers(commit Commit, validatorSet ValidatorSet) []Validator
   138  
   139      // returns the voting power the validators in v1 have according to their voting power in set v2
   140      // it does not assume signature verification
   141      func votingPowerIn(v1 []Validator, v2 ValidatorSet) int64
   142  
   143      // returns hash of the given validator set
   144      func hash(v2 ValidatorSet) []byte
   145  ```
   146  
   147  In the functions below we will be using `trustThreshold` as a parameter. For simplicity
   148  we assume that `trustThreshold` is a float between `1/3` and `2/3` and we will not be checking it
   149  in the pseudo-code.
   150  
   151  **VerifySingle.** The function `VerifySingle` attempts to validate given untrusted header and the corresponding validator sets
   152  based on a given trusted state. It ensures that the trusted state is still within its trusted period,
   153  and that the untrusted header is within assumed `clockDrift` bound of the passed time `now`.
   154  Note that this function is not making external (RPC) calls to the full node; the whole logic is
   155  based on the local (given) state. This function is supposed to be used by the IBC handlers.
   156  
   157  ```go
   158  func VerifySingle(untrustedSh SignedHeader,
   159                    untrustedVs ValidatorSet,
   160                    untrustedNextVs ValidatorSet,
   161                    trustedState TrustedState,
   162                    trustThreshold float,
   163                    trustingPeriod Duration,
   164                    clockDrift Duration,
   165                    now Time) (TrustedState, error) {
   166  
   167      if untrustedSh.Header.Time > now + clockDrift {
   168          return (trustedState, ErrInvalidHeaderTime)
   169      }
   170  
   171      trustedHeader = trustedState.SignedHeader.Header
   172      if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) {
   173          return (state, ErrHeaderNotWithinTrustedPeriod)
   174      }
   175  
   176      // we assume that time it takes to execute verifySingle function
   177      // is several order of magnitudes smaller than trustingPeriod
   178      error = verifySingle(
   179                  trustedState,
   180                  untrustedSh,
   181                  untrustedVs,
   182                  untrustedNextVs,
   183                  trustThreshold)
   184  
   185      if error != nil return (state, error)
   186  
   187      // the untrusted header is now trusted
   188      newTrustedState = TrustedState(untrustedSh, untrustedNextVs)
   189      return (newTrustedState, nil)
   190  }
   191  
   192  // return true if header is within its light client trusted period; otherwise returns false
   193  func isWithinTrustedPeriod(header Header,
   194                             trustingPeriod Duration,
   195                             now Time) bool {
   196  
   197      return header.Time + trustedPeriod > now
   198  }
   199  ```
   200  
   201  Note that in case `VerifySingle` returns without an error (untrusted header
   202  is successfully verified) then we have a guarantee that the transition of the trust
   203  from `trustedState` to `newTrustedState` happened during the trusted period of
   204  `trustedState.SignedHeader.Header`.
   205  
   206  TODO: Explain what happens in case `VerifySingle` returns with an error.
   207  
   208  **verifySingle.** The function `verifySingle` verifies a single untrusted header
   209  against a given trusted state. It includes all validations and signature verification.
   210  It is not publicly exposed since it does not check for header expiry (time constraints)
   211  and hence it's possible to use it incorrectly.
   212  
   213  ```go
   214  func verifySingle(trustedState TrustedState,
   215                    untrustedSh SignedHeader,
   216                    untrustedVs ValidatorSet,
   217                    untrustedNextVs ValidatorSet,
   218                    trustThreshold float) error {
   219  
   220      untrustedHeader = untrustedSh.Header
   221      untrustedCommit = untrustedSh.Commit
   222  
   223      trustedHeader = trustedState.SignedHeader.Header
   224      trustedVs = trustedState.ValidatorSet
   225  
   226      if trustedHeader.Height >= untrustedHeader.Height return ErrNonIncreasingHeight
   227      if trustedHeader.Time >= untrustedHeader.Time return ErrNonIncreasingTime
   228  
   229      // validate the untrusted header against its commit, vals, and next_vals
   230      error = validateSignedHeaderAndVals(untrustedSh, untrustedVs, untrustedNextVs)
   231      if error != nil return error
   232  
   233      // check for adjacent headers
   234      if untrustedHeader.Height == trustedHeader.Height + 1 {
   235          if trustedHeader.NextValidatorsHash != untrustedHeader.ValidatorsHash {
   236              return ErrInvalidAdjacentHeaders
   237          }
   238      } else {
   239          error = verifyCommitTrusting(trustedVs, untrustedCommit, untrustedVs, trustThreshold)
   240          if error != nil return error
   241      }
   242  
   243      // verify the untrusted commit
   244      return verifyCommitFull(untrustedVs, untrustedCommit)
   245  }
   246  
   247  // returns nil if header and validator sets are consistent; otherwise returns error
   248  func validateSignedHeaderAndVals(signedHeader SignedHeader, vs ValidatorSet, nextVs ValidatorSet) error {
   249      header = signedHeader.Header
   250      if hash(vs) != header.ValidatorsHash return ErrInvalidValidatorSet
   251      if hash(nextVs) != header.NextValidatorsHash return ErrInvalidNextValidatorSet
   252      if !matchingCommit(header, signedHeader.Commit) return ErrInvalidCommitValue
   253      return nil
   254  }
   255  
   256  // returns nil if at least single correst signer signed the commit; otherwise returns error
   257  func verifyCommitTrusting(trustedVs ValidatorSet,
   258                            commit Commit,
   259                            untrustedVs ValidatorSet,
   260                            trustLevel float) error {
   261  
   262      totalPower := trustedVs.TotalVotingPower
   263      signedPower := votingPowerIn(signers(commit, untrustedVs), trustedVs)
   264  
   265      // check that the signers account for more than max(1/3, trustLevel) of the voting power
   266      // this ensures that there is at least single correct validator in the set of signers
   267      if signedPower < max(1/3, trustLevel) * totalPower return ErrInsufficientVotingPower
   268      return nil
   269  }
   270  
   271  // returns nil if commit is signed by more than 2/3 of voting power of the given validator set
   272  // return error otherwise
   273  func verifyCommitFull(vs ValidatorSet, commit Commit) error {
   274      totalPower := vs.TotalVotingPower;
   275      signedPower := votingPowerIn(signers(commit, vs), vs)
   276  
   277      // check the signers account for +2/3 of the voting power
   278      if signedPower * 3 <= totalPower * 2 return ErrInvalidCommit
   279      return nil
   280  }
   281  ```
   282  
   283  **VerifyHeaderAtHeight.** The function `VerifyHeaderAtHeight` captures high level
   284  logic, i.e., application call to the light client module to download and verify header
   285  for some height.
   286  
   287  ```go
   288  func VerifyHeaderAtHeight(untrustedHeight int64,
   289                            trustedState TrustedState,
   290                            trustThreshold float,
   291                            trustingPeriod Duration,
   292                            clockDrift Duration) (TrustedState, error)) {
   293  
   294      trustedHeader := trustedState.SignedHeader.Header
   295  
   296      now := System.Time()
   297      if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) {
   298          return (trustedState, ErrHeaderNotWithinTrustedPeriod)
   299      }
   300  
   301      newTrustedState, err := VerifyBisection(untrustedHeight,
   302                                              trustedState,
   303                                              trustThreshold,
   304                                              trustingPeriod,
   305                                              clockDrift,
   306                                              now)
   307  
   308      if err != nil return (trustedState, err)
   309  
   310      now = System.Time()
   311      if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) {
   312          return (trustedState, ErrHeaderNotWithinTrustedPeriod)
   313      }
   314  
   315      return (newTrustedState, err)
   316  }
   317  ```
   318  
   319  Note that in case `VerifyHeaderAtHeight` returns without an error (untrusted header
   320  is successfully verified) then we have a guarantee that the transition of the trust
   321  from `trustedState` to `newTrustedState` happened during the trusted period of
   322  `trustedState.SignedHeader.Header`.
   323  
   324  In case `VerifyHeaderAtHeight` returns with an error, then either (i) the full node we are talking to is faulty
   325  or (ii) the trusted header has expired (it is outside its trusted period). In case (i) the full node is faulty so
   326  light client should disconnect and reinitialize with new peer. In the case (ii) as the trusted header has expired,
   327  we need to reinitialize light client with a new trusted header (that is within its trusted period),
   328  but we don't necessarily need to disconnect from the full node we are talking to (as we haven't observed full node misbehavior in this case).
   329  
   330  **VerifyBisection.** The function `VerifyBisection` implements
   331  recursive logic for checking if it is possible building trust
   332  relationship between `trustedState` and untrusted header at the given height over
   333  finite set of (downloaded and verified) headers.
   334  
   335  ```go
   336  func VerifyBisection(untrustedHeight int64,
   337                       trustedState TrustedState,
   338                       trustThreshold float,
   339                       trustingPeriod Duration,
   340                       clockDrift Duration,
   341                       now Time) (TrustedState, error) {
   342  
   343      untrustedSh, error := Commit(untrustedHeight)
   344      if error != nil return (trustedState, ErrRequestFailed)
   345  
   346      untrustedHeader = untrustedSh.Header
   347  
   348      // note that we pass now during the recursive calls. This is fine as
   349      // all other untrusted headers we download during recursion will be
   350      // for a smaller heights, and therefore should happen before.
   351      if untrustedHeader.Time > now + clockDrift {
   352          return (trustedState, ErrInvalidHeaderTime)
   353      }
   354  
   355      untrustedVs, error := Validators(untrustedHeight)
   356      if error != nil return (trustedState, ErrRequestFailed)
   357  
   358      untrustedNextVs, error := Validators(untrustedHeight + 1)
   359      if error != nil return (trustedState, ErrRequestFailed)
   360  
   361      error = verifySingle(
   362               trustedState,
   363               untrustedSh,
   364               untrustedVs,
   365               untrustedNextVs,
   366               trustThreshold)
   367  
   368      if fatalError(error) return (trustedState, error)
   369  
   370      if error == nil {
   371          // the untrusted header is now trusted.
   372          newTrustedState = TrustedState(untrustedSh, untrustedNextVs)
   373          return (newTrustedState, nil)
   374      }
   375  
   376      // at this point in time we need to do bisection
   377      pivotHeight := ceil((trustedHeader.Height + untrustedHeight) / 2)
   378  
   379      error, newTrustedState = VerifyBisection(pivotHeight,
   380                                               trustedState,
   381                                               trustThreshold,
   382                                               trustingPeriod,
   383                                               clockDrift,
   384                                               now)
   385      if error != nil return (newTrustedState, error)
   386  
   387      return VerifyBisection(untrustedHeight,
   388                             newTrustedState,
   389                             trustThreshold,
   390                             trustingPeriod,
   391                             clockDrift,
   392                             now)
   393  }
   394  
   395  func fatalError(err) bool {
   396      return err == ErrHeaderNotWithinTrustedPeriod OR
   397             err == ErrInvalidAdjacentHeaders OR
   398             err == ErrNonIncreasingHeight OR
   399             err == ErrNonIncreasingTime OR
   400             err == ErrInvalidValidatorSet OR
   401             err == ErrInvalidNextValidatorSet OR
   402             err == ErrInvalidCommitValue OR
   403             err == ErrInvalidCommit
   404  }
   405  ```
   406  
   407  ### The case `untrustedHeader.Height < trustedHeader.Height`
   408  
   409  In the use case where someone tells the light client that application data that is relevant for it
   410  can be read in the block of height `k` and the light client trusts a more recent header, we can use the
   411  hashes to verify headers "down the chain." That is, we iterate down the heights and check the hashes in each step.
   412  
   413  *Remark.* For the case were the light client trusts two headers `i` and `j` with `i < k < j`, we should
   414  discuss/experiment whether the forward or the backward method is more effective.
   415  
   416  ```go
   417  func VerifyHeaderBackwards(trustedHeader Header,
   418                             untrustedHeader Header,
   419                             trustingPeriod Duration,
   420                             clockDrift Duration) error {
   421  
   422    if untrustedHeader.Height >= trustedHeader.Height return ErrErrNonDecreasingHeight
   423    if untrustedHeader.Time >= trustedHeader.Time return ErrNonDecreasingTime
   424  
   425    now := System.Time()
   426    if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) {
   427      return ErrHeaderNotWithinTrustedPeriod
   428    }
   429  
   430    old := trustedHeader
   431    for i := trustedHeader.Height - 1; i > untrustedHeader.Height; i-- {
   432      untrustedSh, error := Commit(i)
   433      if error != nil return ErrRequestFailed
   434  
   435      if (hash(untrustedSh.Header) != old.LastBlockID.Hash) {
   436        return ErrInvalidAdjacentHeaders
   437      }
   438  
   439      old := untrustedSh.Header
   440    }
   441  
   442    if hash(untrustedHeader) != old.LastBlockID.Hash {
   443      return ErrInvalidAdjacentHeaders
   444    }
   445  
   446    now := System.Time()
   447    if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) {
   448      return ErrHeaderNotWithinTrustedPeriod
   449    }
   450  
   451    return nil
   452   }
   453  ```
   454  
   455  *Assumption*: In the following, we assume that *untrusted_h.Header.height > trusted_h.Header.height*. We will quickly discuss the other case in the next section.
   456  
   457  We consider the following set-up:
   458  
   459  - the light client communicates with one full node
   460  - the light client locally stores all the headers that has passed basic verification and that are within light client trust period. In the pseudo code below we
   461  write *Store.Add(header)* for this. If a header failed to verify, then
   462  the full node we are talking to is faulty and we should disconnect from it and reinitialize with new peer.
   463  - If `CanTrust` returns *error*, then the light client has seen a forged header or the trusted header has expired (it is outside its trusted period).
   464      - In case of forged header, the full node is faulty so light client should disconnect and reinitialize with new peer. If the trusted header has expired,
   465    we need to reinitialize light client with new trusted header (that is within its trusted period), but we don't necessarily need to disconnect from the full node
   466    we are talking to (as we haven't observed full node misbehavior in this case).
   467  
   468  ## Correctness of the Light Client Protocols
   469  
   470  ### Definitions
   471  
   472  - `TRUSTED_PERIOD`: trusted period
   473  - for realtime `t`, the predicate `correct(v,t)` is true if the validator `v`
   474    follows the protocol until time `t` (we will see about recovery later).
   475  - Validator fields. We will write a validator as a tuple `(v,p)` such that
   476      - `v` is the identifier (i.e., validator address; we assume identifiers are unique in each validator set)
   477      - `p` is its voting power
   478  - For each header `h`, we write `trust(h) = true` if the light client trusts `h`.
   479  
   480  ### Failure Model
   481  
   482  If a block `b` with a header `h` is generated at time `Time` (i.e. `h.Time = Time`), then a set of validators that
   483  hold more than `2/3` of the voting power in `validators(h.NextValidatorsHash)` is correct until time
   484  `h.Time + TRUSTED_PERIOD`.
   485  
   486  Formally,
   487  \[
   488  \sum_{(v,p) \in validators(h.NextValidatorsHash) \wedge correct(v,h.Time + TRUSTED_PERIOD)} p >
   489  2/3 \sum_{(v,p) \in validators(h.NextValidatorsHash)} p
   490  \]
   491  
   492  The light client communicates with a full node and learns new headers. The goal is to locally decide whether to trust a header. Our implementation needs to ensure the following two properties:
   493  
   494  - *Light Client Completeness*: If a header `h` was correctly generated by an instance of Tendermint consensus (and its age is less than the trusted period),
   495  then the light client should eventually set `trust(h)` to `true`.
   496  
   497  - *Light Client Accuracy*: If a header `h` was *not generated* by an instance of Tendermint consensus, then the light client should never set `trust(h)` to true.
   498  
   499  *Remark*: If in the course of the computation, the light client obtains certainty that some headers were forged by adversaries
   500  (that is were not generated by an instance of Tendermint consensus), it may submit (a subset of) the headers it has seen as evidence of misbehavior.
   501  
   502  *Remark*: In Completeness we use "eventually", while in practice `trust(h)` should be set to true before `h.Time + TRUSTED_PERIOD`. If not, the header
   503  cannot be trusted because it is too old.
   504  
   505  *Remark*: If a header `h` is marked with `trust(h)`, but it is too old at some point in time we denote with `now` (`h.Time + TRUSTED_PERIOD < now`),
   506  then the light client should set `trust(h)` to `false` again at time `now`.
   507  
   508  *Assumption*: Initially, the light client has a header `inithead` that it trusts, that is, `inithead` was correctly generated by the Tendermint consensus.
   509  
   510  To reason about the correctness, we may prove the following invariant.
   511  
   512  *Verification Condition: light Client Invariant.*
   513   For each light client `l` and each header `h`:
   514  if `l` has set `trust(h) = true`,
   515    then validators that are correct until time `h.Time + TRUSTED_PERIOD` have more than two thirds of the voting power in `validators(h.NextValidatorsHash)`.
   516  
   517    Formally,
   518    \[
   519    \sum_{(v,p) \in validators(h.NextValidatorsHash) \wedge correct(v,h.Time + TRUSTED_PERIOD)} p >
   520    2/3 \sum_{(v,p) \in validators(h.NextValidatorsHash)} p
   521    \]
   522  
   523  *Remark.* To prove the invariant, we will have to prove that the light client only trusts headers that were correctly generated by Tendermint consensus.
   524  Then the formula above follows from the failure model.
   525  
   526  ## Details
   527  
   528  **Observation 1.** If `h.Time + TRUSTED_PERIOD > now`, we trust the validator set `validators(h.NextValidatorsHash)`.
   529  
   530  When we say we trust `validators(h.NextValidatorsHash)` we do `not` trust that each individual validator in `validators(h.NextValidatorsHash)`
   531  is correct, but we only trust the fact that less than `1/3` of them are faulty (more precisely, the faulty ones have less than `1/3` of the total voting power).
   532  
   533  *`VerifySingle` correctness arguments*
   534  
   535  Light Client Accuracy:
   536  
   537  - Assume by contradiction that `untrustedHeader` was not generated correctly and the light client sets trust to true because `verifySingle` returns without error.
   538  - `trustedState` is trusted and sufficiently new
   539  - by the Failure Model, less than `1/3` of the voting power held by faulty validators => at least one correct validator `v` has signed `untrustedHeader`.
   540  - as `v` is correct up to now, it followed the Tendermint consensus protocol at least up to signing `untrustedHeader` => `untrustedHeader` was correctly generated.
   541  We arrive at the required contradiction.
   542  
   543  Light Client Completeness:
   544  
   545  - The check is successful if sufficiently many validators of `trustedState` are still validators in the height `untrustedHeader.Height` and signed `untrustedHeader`.
   546  - If `untrustedHeader.Height = trustedHeader.Height + 1`, and both headers were generated correctly, the test passes.
   547  
   548  *Verification Condition:* We may need an invariant stating that if `untrustedSignedHeader.Header.Height = trustedHeader.Height + 1` then
   549  `signers(untrustedSignedHeader.Commit) \subseteq validators(trustedHeader.NextValidatorsHash)`.
   550  
   551  *Remark*: The variable `trustThreshold` can be used if the user believes that relying on one correct validator is not sufficient.
   552  However, in case of (frequent) changes in the validator set, the higher the `trustThreshold` is chosen, the more unlikely it becomes that
   553  `verifySingle` returns with an error for non-adjacent headers.
   554  
   555  - `VerifyBisection` correctness arguments (sketch)*
   556  
   557  Light Client Accuracy:
   558  
   559  - Assume by contradiction that the header at `untrustedHeight` obtained from the full node was not generated correctly and
   560  the light client sets trust to true because `VerifyBisection` returns without an error.
   561  - `VerifyBisection` returns without error only if all calls to `verifySingle` in the recursion return without error (return `nil`).
   562  - Thus we have a sequence of headers that all satisfied the `verifySingle`
   563  - again a contradiction
   564  
   565  light Client Completeness:
   566  
   567  This is only ensured if upon `Commit(pivot)` the light client is always provided with a correctly generated header.
   568  
   569  *Stalling*
   570  
   571  With `VerifyBisection`, a faulty full node could stall a light client by creating a long sequence of headers that are queried one-by-one by the light client and look OK,
   572  before the light client eventually detects a problem. There are several ways to address this:
   573  
   574  - Each call to `Commit` could be issued to a different full node
   575  - Instead of querying header by header, the light client tells a full node which header it trusts, and the height of the header it needs. The full node responds with
   576  the header along with a proof consisting of intermediate headers that the light client can use to verify. Roughly, `VerifyBisection` would then be executed at the full node.
   577  - We may set a timeout how long `VerifyBisection` may take.