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.