@ -27,13 +27,16 @@ The correctness of the protocol is based on the assumption that *inithead* was g
In the following, only the details of the data structures needed for this specification are given.
In the following, only the details of the data structures needed for this specification are given.
* header fields
* header fields
- *height*
- *bfttime*: the chain time when the header (block) was generated
- *V*: validator set containing validators for this block.
- *NextV*: validator set for next block.
- *commit*: evidence that block with height *height* - 1 was committed by a set of validators (canonical commit). We will use ```signers(commit)``` to refer to the set of validators that committed the block.
- *Height*
- *Time*: the chain time when the header (block) was generated
- *ValidatorsHash*: hash of the validators for the current block
- *NextValidatorsHash*: hash of the validators for the next block
- *LastCommitHash*: hash commit from validators from the last block
- *commit*: evidence that block with height *height* - 1 was committed by a set of validators (canonical commit).
We will use ```signers(commit)``` to refer to the set of validators that committed the block.
* signed header fields: contains a header and a *commit* for the current header; a "seen commit". In the Tendermint consensus the "canonical commit" is stored in header *height* + 1.
* signed header fields: contains a header and a *commit* for the current header; a "seen commit".
In Tendermint consensus the "canonical commit" is stored in header *height* + 1.
* For each header *h* it has locally stored, the lite client stores whether
* For each header *h* it has locally stored, the lite client stores whether
it trusts *h*. We write *trust(h) = true*, if this is the case.
it trusts *h*. We write *trust(h) = true*, if this is the case.
@ -45,20 +48,42 @@ In the following, only the details of the data structures needed for this specif
### Functions
### Functions
For the purpose of this lite client specification, we assume that the Tendermint Full Node exposes the following function over Tendermint RPC:
For the purpose of this lite client specification, we assume that the Tendermint Full Node exposes the following functions over Tendermint RPC:
```go
```go
// returns signed header: header (with the fields from above) with Commit
// that include signatures of validators that signed the header
func Commit(height int64) (SignedHeader, error)
func Commit(height int64) (SignedHeader, error)
// returns signed header: header (with the fields from
// TODO: define precisely what this functions is supposed to be doing
func signers(commit) []Validator
```
### Definitions
### Definitions
* *TRUSTED_PERIOD*: trusting period
* *TRUSTED_PERIOD*: trusting period
@ -70,7 +95,9 @@ For the purpose of this lite client specification, we assume that the Tendermint
### Tendermint Failure Model
### Tendermint Failure Model
If a block *h* is generated at time *bfttime* (and this time is stored in the block), then a set of validators that hold more than 2/3 of the voting power in h.Header.NextV is correct until time h.Header.bfttime + TRUSTED_PERIOD.
If a block *b* is generated at time *Time* (and this time is stored in the block), then a set of validators that
hold more than 2/3 of the voting power in ```validators(b.Header.NextValidatorsHash)``` is correct until time
```b.Header.Time + TRUSTED_PERIOD```.
Formally,
Formally,
\[
\[
@ -82,8 +109,12 @@ Formally,
*Remark*: This failure model might change to a hybrid version that takes heights into account in the future.
*Remark*: This failure model might change to a hybrid version that takes heights into account in the future.
The specification in this document considers an implementation of the lite client under this assumption. Issues like *counter-factual signing* and *fork accountability* and *evidence submission* are mechanisms that justify this assumption by incentivizing validators to follow the protocol.
If they don't, and we have more that 1/3 faults, safety may be violated. Our approach then is to *detect* these cases (after the fact), and take suitable repair actions (automatic and social). This is discussed in an upcoming document on "Fork accountability". (These safety violations include the lite client wrongly trusting a header, a fork in the blockchain, etc.)
The specification in this document considers an implementation of the lite client under the Tendermint Failure Model. Issues
like *counter-factual signing* and *fork accountability* and *evidence submission* are mechanisms that justify this assumption by
incentivizing validators to follow the protocol. If they don't, and we have more that 1/3 faults, safety may be violated.
Our approach then is to *detect* these cases (after the fact), and take suitable repair actions (automatic and social).
This is discussed in an upcoming document on "Fork accountability". (These safety violations include the lite client wrongly
trusting a header, a fork in the blockchain, etc.)
## Lite Client Trusting Spec
## Lite Client Trusting Spec
@ -148,6 +179,8 @@ We consider the following use case:
This can be used in several settings:
This can be used in several settings:
- someone tells the lite client that application data that is relevant for it can be read in the block of height *k*.
- someone tells the lite client that application data that is relevant for it can be read in the block of height *k*.
- the lite clients wants the latest state. It asks a full nude for the current height, and uses the response for *k*.
- the lite clients wants the latest state. It asks a full nude for the current height, and uses the response for *k*.
- in case of inter-blockchain communication protocol (IBC) the light client runs on a chain and someone feeds it
signed headers as input and it computes whether it can trust it.
## Details
## Details
@ -155,20 +188,21 @@ This can be used in several settings:
**Observation 1.** If *h.Header.bfttime + tp > now*, we trust the old
**Observation 1.** If *h.Header.bfttime + tp > now*, we trust the old
validator set *h.Header.NextV*.
validator set *h.Header.NextV*.
When we say we trust *h.Header.NextV* we do *not* trust that each individual validator in *h.Header.NextV* is correct, but we only trust the fact that at most 1/3 of them are faulty (more precisely, the faulty ones have at most 1/3 of the total voting power).
When we say we trust *h.Header.NextV* we do *not* trust that each individual validator in *h.Header.NextV* 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).
### Functions
### Functions
The function *CanTrust* checks whether to trust header *h2* based on the trusted header *h1*. It does so by (potentially)
building transitive trust relation between *h1* and *h2*, over some intermediate headers. For example, in case we cannot trust
header *h2* based on the trusted header *h1*, the function *CanTrust* will try to find headers such that we can transition trust
from *h1* over intermediate headers to *h2*. We will give two implementations of *CanTrust*, the one based
The function *CanTrust* checks whether to trust header *untrusted_h* based on the trusted header *trusted_h*. It does so by (potentially)
building transitive trust relation between *trusted_h* and *untrusted_h*, over some intermediate headers. For example, in case we cannot trust
header *untrusted_h* based on the trusted header *trusted_h*, the function *CanTrust* will try to find headers such that we can transition trust
from *trusted_h* over intermediate headers to *untrusted_h*. We will give two implementations of *CanTrust*, the one based
on bisection that is recursive and the other that is non-recursive. We give two implementations as recursive version might be easier
on bisection that is recursive and the other that is non-recursive. We give two implementations as recursive version might be easier
to understand but non-recursive version might be simpler to formally express and verify using TLA+/TLC.
to understand but non-recursive version might be simpler to formally express and verify using TLA+/TLC.
Both implementations of *CanTrust* function are based on *CheckSupport* function that implements the skipping conditions under which we can trust a
Both implementations of *CanTrust* function are based on *CheckSupport* function that implements the skipping conditions under which we can trust a
header *h2* given the trust in the header *h1* as a single step,
header *untrusted_h* given the trust in the header *trusted_h* as a single step,
i.e., it does not assume ensuring transitive trust relation between headers through some intermediate headers.
i.e., it does not assume ensuring transitive trust relation between headers through some intermediate headers.
In order to incentivize correct behavior of validators that run Tendermint consensus protocol, fork detection protocol (it will be explained in different document) is executed in case of a fork (conflicting
In order to incentivize correct behavior of validators that run Tendermint consensus protocol, fork detection protocol (it will be explained in different document) is executed in case of a fork (conflicting
*Assumption*: In the following, we assume that *h2.Header.height > h1.Header.height*. We will quickly discuss the other case in the next section.
*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.
We consider the following set-up:
We consider the following set-up:
- the lite client communicates with one full node
- the lite client communicates with one full node
- the lite client locally stores all the headers that has passed basic verification and that are within lite client trust period. In the pseudo code below we write *Store(header)* for this. If a header failed to verify, then
the full node we are talking to is faulty and we should disconnect from it and reinitialise lite client.
- the lite client locally stores all the headers that has passed basic verification and that are within lite client trust period. In the pseudo code below we
write *Store.Add(header)* for this. If a header failed to verify, then
the full node we are talking to is faulty and we should disconnect from it and reinitialise with new peer.
- If *CanTrust* returns *error*, then the lite client has seen a forged header or the trusted header has expired (it is outside its trusted period).
- If *CanTrust* returns *error*, then the lite client has seen a forged header or the trusted header has expired (it is outside its trusted period).
* In case of forged header, the full node is faulty so lite client should disconnect and reinitialise with new trusted header. If the trusted header has expired,
* In case of forged header, the full node is faulty so lite client should disconnect and reinitialise with new peer. If the trusted header has expired,
we need to reinitialise lite client with new trusted header (that is within its trusted period), but we don't necessarily need to disconnect from the full node
we need to reinitialise lite client with new trusted header (that is within its trusted period), 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).
we are talking to (as we haven't observed full node misbehavior in this case).
**Auxiliary Functions.** We will use the function ```votingpower_in(V1,V2)``` to compute the voting power the validators in set V1 have according to their voting power in set V2;
we will write ```totalVotingPower(V)``` for ```votingpower_in(V,V)```, which returns the total voting power in V.
**Auxiliary Functions.** We will use the function ```votingPowerIn(V1,V2)``` to compute the voting power the validators in set V1 have according to their voting power in set V2;
we will write ```totalVotingPower(V)``` for ```votingPowerIn(V,V)```, which returns the total voting power in V.
We further use the function ```signers(Commit)``` that returns the set of validators that signed the Commit.
We further use the function ```signers(Commit)``` that returns the set of validators that signed the Commit.
**CheckSupport.** The following function defines skipping condition under the Tendermint Failure model, i.e., it defines when we can trust the header h2 based on header h1.
Time validity of a header is captured by the ```isWithinTrustedPeriodWithin``` function that depends on lite client trusted period (`LITE_CLIENT_TRUSTED_PERIOD`) and it returns
**CheckSupport.** The following function defines skipping condition under the Tendermint Failure model, i.e., it defines when we can trust the header untrusted_h based on header trusted_h.
Time validity of a header is captured by the ```isWithinTrustedPeriod``` function that depends on lite client trusted period (`LITE_CLIENT_TRUSTED_PERIOD`) and it returns
true in case the header is within its lite client trusted period.
true in case the header is within its lite client trusted period.
```verify``` function is capturing basic header verification, i.e., it ensures that the header is signed by more than 2/3 of the voting power of the corresponding validator set.
```verify``` function is capturing basic header verification, i.e., it ensures that the header is signed by more than 2/3 of the voting power of the corresponding validator set.
```go
```go
// return true if header is within its lite client trusted period; otherwise it returns false
// return true if header is within its lite client trusted period; otherwise it returns false
func isWithinTrustedPeriod(h) bool {
func isWithinTrustedPeriod(h, now) bool {
return h.Header.bfttime + LITE_CLIENT_TRUSTED_PERIOD > now
return h.Header.bfttime + LITE_CLIENT_TRUSTED_PERIOD > now
}
}
@ -211,63 +246,63 @@ true in case the header is within its lite client trusted period.
// to ensure header is well formed.
// to ensure header is well formed.
func verify(h) bool {
func verify(h) bool {
vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h
vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h
if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all {
return nil
}
return ErrTooMuchChange
}
}
```
```
*Correctness arguments*
*Correctness arguments*
Towards Lite Client Accuracy:
Towards Lite Client Accuracy:
- Assume by contradiction that *h2* was not generated correctly and the lite client sets trust to true because *CheckSupport* returns true.
- h1 is trusted and sufficiently new
- by Tendermint Fault Model, less than 1/3 of voting power held by faulty validators => at least one correct validator *v* has signed *h2*.
- as *v* is correct up to now, it followed the Tendermint consensus protocol at least up to signing *h2* => *h2* was correctly generated, we arrive at the required contradiction.
- Assume by contradiction that *untrusted_h* was not generated correctly and the lite client sets trust to true because *CheckSupport* returns true.
- trusted_h is trusted and sufficiently new
- by Tendermint Fault Model, less than 1/3 of voting power held by faulty validators => at least one correct validator *v* has signed *untrusted_h*.
- as *v* is correct up to now, it followed the Tendermint consensus protocol at least up to signing *untrusted_h* => *untrusted_h* was correctly generated, we arrive at the required contradiction.
Towards Lite Client Completeness:
Towards Lite Client Completeness:
- The check is successful if sufficiently many validators of *h1* are still validators in *h2* and signed *h2*.
- If *h2.Header.height = h1.Header.height + 1*, and both headers were generated correctly, the test passes
- The check is successful if sufficiently many validators of *trusted_h* are still validators in *untrusted_h* and signed *untrusted_h*.
- If *untrusted_h.Header.height = trusted_h.Header.height + 1*, and both headers were generated correctly, the test passes
*Verification Condition:* We may need a Tendermint invariant stating that if *h2.Header.height = h1.Header.height + 1* then *signers(h2.Commit) \subseteq h1.Header.NextV*.
*Verification Condition:* We may need a Tendermint invariant stating that if *untrusted_h.Header.height = trusted_h.Header.height + 1* then *signers(untrusted_h.Commit) \subseteq trusted_h.Header.NextV*.
*Remark*: The variable *trustThreshold* can be used if the user believes that relying on one correct validator is not sufficient. However, in case of (frequent) changes in the validator set, the higher the *trustThreshold* is chosen, the more unlikely it becomes that CheckSupport returns true for non-adjacent headers.
*Remark*: The variable *trustThreshold* can be used if the user believes that relying on one correct validator is not sufficient. However, in case of (frequent) changes in the validator set, the higher the *trustThreshold* is chosen, the more unlikely it becomes that CheckSupport returns true for non-adjacent headers.
@ -278,39 +313,41 @@ by relying on *CheckSupport* function.
```go
```go
func VerifyHeader(height, trustThreshold) error {
func VerifyHeader(height, trustThreshold) error {
if h2, exists := Store.Get(height); exists {
if isWithinTrustedPeriod(h2) { return nil }
return ErrHeaderNotWithinTrustedPeriod(h2)
if untrusted_h, exists := Store.Get(height); exists {
- Assume by contradiction that *h2* was not generated correctly and the lite client sets trust to true because CanTrustBisection returns nil.
- Assume by contradiction that *untrusted_h* was not generated correctly and the lite client sets trust to true because CanTrustBisection returns nil.
- CanTrustBisection returns true only if all calls to CheckSupport in the recursion return nil.
- CanTrustBisection returns true only if all calls to CheckSupport in the recursion return nil.
- Thus we have a sequence of headers that all satisfied the CheckSupport
- Thus we have a sequence of headers that all satisfied the CheckSupport
- again a contradiction
- again a contradiction
@ -399,27 +436,27 @@ With CanTrustBisection, a faulty full node could stall a lite client by creating
* We may set a timeout how long bisection may take.
* We may set a timeout how long bisection may take.
### The case *h2.Header.height <h1.Header.height*
### The case *untrusted_h.Header.height <trusted_h.Header.height*
In the use case where someone tells the lite client that application data that is relevant for it can be read in the block of height *k* and the lite client trusts a more recent header, we can use the hashes to verify headers "down the chain." That is, we iterate down the heights and check the hashes in each step.
In the use case where someone tells the lite client that application data that is relevant for it can be read in the block of height *k* and the lite client trusts a more recent header, we can use the hashes to verify headers "down the chain." That is, we iterate down the heights and check the hashes in each step.
*Remark.* For the case were the lite client trusts two headers *i* and *j* with *i < k < j*, we should discuss/experiment whether the forward or the backward method is more effective.
*Remark.* For the case were the lite client trusts two headers *i* and *j* with *i < k < j*, we should discuss/experiment whether the forward or the backward method is more effective.
```go
```go
func Backwards(h1,h2) error {
assert (h2.Header.height <h1.Header.height)
if !isWithinTrustedPeriod(h1) return ErrHeaderNotTrusted(h1)