Skip to content
This repository has been archived by the owner on Feb 23, 2022. It is now read-only.

Commit

Permalink
Address reviewer's comments
Browse files Browse the repository at this point in the history
  • Loading branch information
milosevic committed Dec 12, 2019
1 parent 8528cdb commit 60303ef
Showing 1 changed file with 77 additions and 85 deletions.
162 changes: 77 additions & 85 deletions spec/consensus/light-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ if *l* has set *trust(h) = true*,

Upon initialization, the lite client is given a header *inithead* it trusts (by
social consensus). It is assumed that *inithead* satisfies the lite client invariant. (If *inithead* has been correctly generated by Tendermint consensus, the invariant follows from the Tendermint Failure Model.)
Note that the *inithead* should be within its trusted period during initialization.

When a lite clients sees a signed new header *snh*, it has to decide whether to trust the new
header. Trust can be obtained by (possibly) the combination of three methods.
Expand Down Expand Up @@ -186,7 +187,9 @@ We consider the following set-up:
- 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.
- 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.
* 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,
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).

**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.
Expand All @@ -211,20 +214,25 @@ true in case the header is within its lite client trusted period.
return votingpower_in(signers(h.Commit),h.Header.V) > 2/3 * vp_all
}

// Captures skipping condition. h1 and h2 has already passed basic validation (function `verify`).
// Captures skipping condition. h1 and h2 have already passed basic validation (function `verify`).
// returns nil in case h2 can be trusted based on h1, otherwise returns error.
// ErrHeaderExpired is used to signal that h1 has expired with respect lite client trusted period,
// ErrInvalidAdjacentHeaders that adjacent headers are not consistent and
// ErrTooMuchChange that there is not enough intersection between validator sets to have skipping condition true.
func CheckSupport(h1,h2,trustlevel) error {
assume h1.Header.Height < h2.header.Height and h1.Header.bfttime < h2.Header.bfttime and h2.Header.bfttime < now

if !isWithinTrustedPeriod(h1) return ErrHeaderNotWithinTrustedPeriod(h1)

// Although while executing the rest of CheckSupport function, h1 can expiry based on the lite client trusted period, this is not problem as
// lite client trusted period is smaller than trusted period of the header based on Tendermint Failure model, i.e., there is a significant
// time period (measure in days) during which validator set that has signed h1 can be trusted
// Furthermore, CheckSupport function is not doing expensive operation (neither rpc nor signature verification), so it should execute fast.
// ErrTooMuchChange that there is not enough intersection between validator sets to have skipping
// condition true.
func CheckSupport(h1,h2,trustThreshold) error {
assume h1.Header.Height < h2.header.Height and
h1.Header.bfttime < h2.Header.bfttime and
h2.Header.bfttime < now

if !isWithinTrustedPeriod(h1) return ErrHeaderNotWithinTrustedPeriod(h1)

// Although while executing the rest of CheckSupport function, h1 can expiry based on the
// lite client trusted period, this is not problem as lite client trusted period is smaller
// than trusted period of the header based on Tendermint Failure model, i.e., there is a significant
// time period (measure in days) during which validator set that has signed h1 can be trusted
// Furthermore, CheckSupport function is not doing expensive operation (neither rpc nor signature
// verification), so it should execute fast.

// total sum of voting power of validators in h1.NextV
vp_all := totalVotingPower(h1.Header.NextV)
Expand All @@ -236,7 +244,7 @@ true in case the header is within its lite client trusted period.
return ErrInvalidAdjacentHeaders
}
// check for non-adjacent headers
if votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all return nil
if votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustThreshold) * vp_all return nil
return ErrTooMuchChange

}
Expand All @@ -257,105 +265,89 @@ Towards Lite Client Completeness:

*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*.

*Remark*: The variable *trustlevel* 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 *trustlevel* 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.

**VerifyHeader.** The function *VerifyHeader* captures high level logic, i.e., application call to the lite client module to (optionally download) and
verify header for some height. The core verification logic is captured by *CanTrust* function that iteratively try to establish trust in given header
by relying on *CheckSupport* function.


```go
func VerifyHeader(height, trustlevel) error {
if h2, exists := Store.Get(height); exists {
if isWithinTrustedPeriod(h2) return nil
return ErrHeaderNotWithinTrustedPeriod(h2)
}
else {
h2 := Commit(height)
if !verify(h2) return ErrInvalidHeader(h2)
if !isWithinTrustedPeriod(h2) return ErrHeaderNotWithinTrustedPeriod(h2)
}
func VerifyHeader(height, trustThreshold) error {
if h2, exists := Store.Get(height); exists {
if isWithinTrustedPeriod(h2) { return nil }
return ErrHeaderNotWithinTrustedPeriod(h2)
}

// get the highest trusted headers lower than h2
h1 = Store.HighestTrustedSmallerThan(height)
if h1 == nil
return ErrNoTrustedHeader
h2 := Commit(height)
if !verify(h2) { return ErrInvalidHeader(h2) }
if !isWithinTrustedPeriod(h2) { return ErrHeaderNotWithinTrustedPeriod(h2) }

err = CanTrust(h1, h2, trustlevel) // or CanTrustBisection((h1, h2, trustlevel)
if err != nil return err
// get the highest trusted headers lower than h2
h1 = Store.HighestTrustedSmallerThan(height)
if h1 == nil { return ErrNoTrustedHeader }

if isWithinTrustedPeriod(h2) {
Store.add(h2) // we store only trusted headers, as we assume that only trusted headers are influencing end user business decisions.
return nil
}
return ErrHeaderNotTrusted(h2)
err = CanTrust(h1, h2, trustThreshold) // or CanTrustBisection((h1, h2, trustThreshold)
if err != nil { return err }

if isWithinTrustedPeriod(h2) {
Store.add(h2)
// we store only trusted headers, as we assume that only trusted headers
// are influencing end user business decisions.
return nil
}
return ErrHeaderNotTrusted(h2)
}


// return nil in case we can trust header h2 based on header h1; otherwise return error where error captures the nature of the error.
func CanTrust(h1,h2,trustlevel) error {
// return nil in case we can trust header h2 based on header h1; otherwise return error
// where error captures the nature of the error.
func CanTrust(h1,h2,trustThreshold) error {
assume h1.Header.Height < h2.header.Height

err = CheckSupport(h1,h2,trustlevel)
if err == nil {
Store.Add(h2)
return nil
}
if err != ErrTooMuchChange return err

// we cannot verify h2 based on h1, so we try to move trusted header closer to h2 so we can verify h2
th := h1 // th is trusted header
untrustedHeaders := []
untrustedHeaders := [h2]

while true {
endHeight = h2.Header.height
foundPivot = false
while(!foundPivot) {
pivot := (th.Header.height + endHeight) / 2
hp := Commit(pivot)
if !verify(hp) return ErrInvalidHeader(hp)
// try to move trusted header forward to hp
err = CheckSupport(th,hp,trustlevel)
if (err != nil and err != ErrTooMuchChange) return err
if err == nil {
th = hp
Store.Add(hp)
foundPivot = true
}
untrustedHeaders.add(hp)
endHeight = pivot
}

// try to move trusted header forward
for h in untrustedHeaders {
// we assume here that iteration is done in the order of header heights
err = CheckSupport(th,h,trustlevel)
if (err != nil and err != ErrTooMuchChange) return err
if err == nil {
th = h
Store.Add(h)
untrustedHeaders.Remove(h)
}
for h in untrustedHeaders {
// we assume here that iteration is done in the order of header heights
err = CheckSupport(th,h,trustThreshold)
if err == nil {
th = h
Store.Add(h)
untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height)
if th == h2 { return nil }
}
if (err != ErrTooMuchChange) { return err }
}

// at this point we have potentially updated th based on stored headers so we try to verify h2
// based on new trusted header
err = CheckSupport(h1,h2,trustlevel)
endHeight = min(untrustedHeaders)
foundPivot = false
while(!foundPivot) {
pivot := (th.Header.height + endHeight) / 2
hp := Commit(pivot)
if !verify(hp) { return ErrInvalidHeader(hp) }
// try to move trusted header forward to hp
err = CheckSupport(th,hp,trustThreshold)
if (err != nil and err != ErrTooMuchChange) return err
if err == nil {
Store.Add(h2)
return nil
th = hp
Store.Add(hp)
foundPivot = true
}
if err != ErrTooMuchChange return err
untrustedHeaders.add(hp)
endHeight = pivot
}
}
return nil // this line should never be reached
}
```

```go
func CanTrustBisection(h1,h2,trustlevel) error {
func CanTrustBisection(h1,h2,trustThreshold) error {
assume h1.Header.Height < h2.header.Height

err = CheckSupport(h1,h2,trustlevel)
err = CheckSupport(h1,h2,trustThreshold)
if err == nil {
Store.Add(h2)
return nil
Expand All @@ -366,10 +358,10 @@ func CanTrustBisection(h1,h2,trustlevel) error {
hp := Commit(pivot)
if !verify(hp) return ErrInvalidHeader(hp)

err = CanTrustBisection(h1,hp,trustlevel)
err = CanTrustBisection(h1,hp,trustThreshold)
if err == nil {
Store.Add(hp)
err2 = CanTrustBisection(hp,h2,trustlevel)
err2 = CanTrustBisection(hp,h2,trustThreshold)
if err2 == nil {
Store.Add(h2)
return nil
Expand Down Expand Up @@ -423,7 +415,7 @@ func Backwards(h1,h2) error {
old := new
if !isWithinTrustedPeriod(h1) return ErrHeaderNotTrusted(h1)
}
if hash(h2) == old.Header.hash return ErrInvalidAdjacentHeaders
if hash(h2) != old.Header.hash return ErrInvalidAdjacentHeaders
return nil
}
```
Expand Down

0 comments on commit 60303ef

Please sign in to comment.