Browse Source

Address reviewer's comments

pull/7804/head
Zarko Milosevic 5 years ago
parent
commit
4f7c55507c
1 changed files with 86 additions and 90 deletions
  1. +86
    -90
      spec/consensus/light-client.md

+ 86
- 90
spec/consensus/light-client.md View File

@ -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.
@ -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.
@ -203,28 +206,35 @@ true in case the header is within its lite client trusted period.
return h.Header.bfttime + LITE_CLIENT_TRUSTED_PERIOD > now
}
// return true if header is correctly signed by 2/3+ voting power in the corresponding validator set;
// otherwise false. Additional checks should be done in the implementation
// return true if header is correctly signed by 2/3+ voting power in the corresponding
// validator set; otherwise false. Additional checks should be done in the implementation
// to ensure header is well formed.
func verify(h) bool {
vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h
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`).
// 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.
// 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 when h1 has expired with respect to lite client trusted period,
// ErrInvalidAdjacentHeaders when that adjacent headers are not consistent and
// ErrTooMuchChange when 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)
@ -236,7 +246,9 @@ 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
}
@ -257,7 +269,7 @@ 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
@ -265,97 +277,81 @@ 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
@ -366,10 +362,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
@ -423,7 +419,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
}
```


Loading…
Cancel
Save