From 4f7c55507cb99c35c7774c583f182859283f3017 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Thu, 12 Dec 2019 12:29:12 +0100 Subject: [PATCH] Address reviewer's comments --- spec/consensus/light-client.md | 176 ++++++++++++++++----------------- 1 file changed, 86 insertions(+), 90 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 1e4ed66f2..ec91c5aa3 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -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 + // 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) + 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. + // 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) - } - } - - // 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) + 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 { - Store.Add(h2) - return nil + th = h + Store.Add(h) + untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) + if th == h2 { return nil } } - if err != ErrTooMuchChange return err + if (err != ErrTooMuchChange) { return err } + } + + 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 { + th = hp + Store.Add(hp) + foundPivot = true + } + 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 } ```