From a4b68ec2fbca4eb06d383a7d06cf37b3e59a4680 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Fri, 15 Nov 2019 12:23:35 +0100 Subject: [PATCH 01/20] Add non-recursive specification of Bisection algorithm - Fix timing issues by introducing Delta parameter --- spec/consensus/light-client.md | 117 +++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 42 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 18dc280a..8f2d7974 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -193,33 +193,34 @@ We consider the following set-up: we will write ```totalVotingPower(V)``` for ```votingpower_in(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. -**CheckSupport.** The following function checks whether we can trust the header h2 based on header h1 following the trusting period method. +**CheckSupport.** The following function checks whether we can trust the header h2 based on header h1 following the trusting period method. Time constraint is +captured by the `hasExpired` function that depends on trusted period (`tp`) and a parameter `Delta` that denotes minimum duration of header so it is +not considered expired. ```go + func hasExpired(h) { + if h.Header.bfttime + tp - Delta < now { // Observation 1 + return true + } + + // basic validation (function `verify`) has already been called on h2 func CheckSupport(h1,h2,trustlevel) bool { - if h1.Header.bfttime + tp < now { // Observation 1 - return false // old header was once trusted but it is expired - } + if hasExpired(h1) then return false //old header was once trusted but it is expired + vp_all := totalVotingPower(h1.Header.NextV) - // total sum of voting power of validators in h2 + // total sum of voting power of validators in h1 if h2.Header.height == h1.Header.height + 1 { - // specific check for adjacent headers; everything must be - // properly signed. - // also check that h2.Header.V == h1.Header.NextV - // Plus the following check that 2/3 of the voting power - // in h1 signed h2 - return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > - 2/3 * vp_all) - // signing validators are more than two third in h1. - } - - return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > - max(1/3,trustlevel) * vp_all) - // get validators in h1 that signed h2 - // sum of voting powers in h1 of - // validators that signed h2 - // is more than a third in h1 + // specific check for adjacent headers + if h1.Header.NextV == h2.Header.V then + return hasExpired(h2) + } + } + + // validators that signed h2 are more than a third of voting power in h1 + if (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all) { + return hasExpired(h2) + } } ``` @@ -257,28 +258,60 @@ Towards Lite Client Completeness: ```go -func Bisection(h1,h2,trustlevel) bool{ - if CheckSupport(h1,h2,trustlevel) { - return true - } - if h2.Header.height == h1.Header.height + 1 { - // we have adjacent headers that are not matching (failed - // the CheckSupport) - // we could submit evidence here - return false - } - pivot := (h1.Header.height + h2.Header.height) / 2 - hp := Commit(pivot) - // ask a full node for header of height pivot - Store(hp) - // store header hp locally - if Bisection(h1,hp,trustlevel) { - // only check right branch if hp is trusted - // (otherwise a lot of unnecessary computation may be done) - return Bisection(hp,h2,trustlevel) +func verify(h) { + if hasExpired(h) return false + + vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h + + if votingpower_in(signers(h.Commit),h.Header.V) > 2/3 * vp_all { + return hasExpired(h) + } else { + return false } - else { - return false +} + +func Bisection(h1,h2,trustlevel) bool{ + th := h1 // th is trusted header + while th.Header.Height <= h2.Header.height do { + // try to move trusted header forward with stored headers + // we assume here that iteration will be done in order of header heights + ih := th + for all stored headers h s.t ih.Header.Height < h.Header.height < h2.Header.height do { + if CheckSupport(th,h,trustlevel) { + th = h + } else if h.Header.height == th.Header.height + 1 { + return false // fail to verify succesive headers! + } else break // for + } + + if CheckSupport(th,h2,trustlevel) { + return hasExpired(h2) + } + + if h2.Header.height == th.Header.height + 1 { + // we have adjacent headers that are not matching (failed the CheckSupport) + // we could submit evidence here + return false + } + + // try to move th + endHeight = h2.Header.height + foundPivot = false + while(!foundPivot) { + pivot := (th.Header.height + endHeight) / 2 + hp := Commit(pivot) + if !verify(hp) return false + Store(hp) + + if CheckSupport(th,hd,trustlevel) { + th = hd + foundPivot = true + } else if pivot.Header.height == th.Header.height + 1 { + return false // fail to verify succesive headers! + } + + endHeight = pivot + } } } ``` From 4ee393c3daa6d0278fb49af55e48641515b0f270 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Thu, 28 Nov 2019 15:42:35 +0100 Subject: [PATCH 02/20] Clean up error conditions and simplify pseudocode --- spec/consensus/light-client.md | 102 ++++++++++++++++----------------- 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 8f2d7974..9832eefc 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -198,29 +198,38 @@ captured by the `hasExpired` function that depends on trusted period (`tp`) and not considered expired. ```go - func hasExpired(h) { + // return true if header has expired, i.e., it is outside its trusted period; otherwise it returns false + func hasExpired(h) bool { if h.Header.bfttime + tp - Delta < now { // Observation 1 return true } - // basic validation (function `verify`) has already been called on h2 - func CheckSupport(h1,h2,trustlevel) bool { - if hasExpired(h1) then return false //old header was once trusted but it is expired - - vp_all := totalVotingPower(h1.Header.NextV) - // total sum of voting power of validators in h1 - - if h2.Header.height == h1.Header.height + 1 { - // specific check for adjacent headers - if h1.Header.NextV == h2.Header.V then - return hasExpired(h2) - } - } + // 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 + } - // validators that signed h2 are more than a third of voting power in h1 - if (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all) { - return hasExpired(h2) - } + // Captures skipping condition. h1 and h2 has already passed basic validation (function `verify`). + // returns (true, nil) in case h2 can be trusted based on h1, (false, nil) in case it cannot be trusted but no errors are observed during check and (false, error) in case + // an error is detected (for example adjacent headers are not consistent). + func CheckSupport(h1,h2,trustlevel) (bool, error) { + assume h1.Header.Height < h2.header.Height + + if hasExpired(h1) return (false, ErrHeaderExpired(h1)) + + // total sum of voting power of validators in h1.NextV + vp_all := totalVotingPower(h1.Header.NextV) + + // check for adjacent headers + if (h2.Header.height == h1.Header.height + 1) { + if h1.Header.NextV == h2.Header.V return (true, nil) + else return (false, ErrInvalidAdjacentHeaders) + } else { + // check for non-adjacent headers + return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all, nil) + } } ``` @@ -258,58 +267,47 @@ Towards Lite Client Completeness: ```go -func verify(h) { - if hasExpired(h) return false - - vp_all := totalVotingPower(h.Header.V) // total sum of voting power of validators in h +// return (true, nil) in case we can trust header h2 based on header h1; otherwise return (false, error) where error captures the nature of the error. +func Bisection(h1,h2,trustlevel) (bool, error) { + assume h1.Header.Height < h2.header.Height - if votingpower_in(signers(h.Commit),h.Header.V) > 2/3 * vp_all { - return hasExpired(h) - } else { - return false - } -} + ok, err = CheckSupport(h1,h2,trustlevel) + if (ok or err != nil) return (ok, err) -func Bisection(h1,h2,trustlevel) bool{ + // 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 - while th.Header.Height <= h2.Header.height do { + while th.Header.Height <= h2.Header.height - 1 do { // try to move trusted header forward with stored headers - // we assume here that iteration will be done in order of header heights ih := th - for all stored headers h s.t ih.Header.Height < h.Header.height < h2.Header.height do { - if CheckSupport(th,h,trustlevel) { + for all stored headers h s.t ih.Header.Height < h.Header.height < h2.Header.height do { // try to move trusted header forward + // we assume here that iteration is done in the order of header heights + ok, err = CheckSupport(th,h,trustlevel) + if err != nil { return (ok, err) } + if ok { th = h - } else if h.Header.height == th.Header.height + 1 { - return false // fail to verify succesive headers! - } else break // for - } - - if CheckSupport(th,h2,trustlevel) { - return hasExpired(h2) + } } - if h2.Header.height == th.Header.height + 1 { - // we have adjacent headers that are not matching (failed the CheckSupport) - // we could submit evidence here - return false - } + // at this point we have potentially updated th based on stored headers so we try to verify h2 based on new trusted header + ok, err = CheckSupport(th,h2,trustlevel) + if (ok or err != nil) return (ok, err) - // try to move th + // we cannot verify h2 based on th, so we try to move trusted header closer to h2 by downloading header(s) between th and h2 endHeight = h2.Header.height foundPivot = false while(!foundPivot) { pivot := (th.Header.height + endHeight) / 2 hp := Commit(pivot) - if !verify(hp) return false + if !verify(hp) return (false, ErrInvalidHeader(hp)) Store(hp) - if CheckSupport(th,hd,trustlevel) { - th = hd + // try to move trusted header forward to hp + ok, err = CheckSupport(th,hp,trustlevel) + if err != nil { return (ok, err) } + if ok { + th = hp foundPivot = true - } else if pivot.Header.height == th.Header.height + 1 { - return false // fail to verify succesive headers! } - endHeight = pivot } } From 2306108d8ab5565337efcb7a3e1ef2b9902dc5f7 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Mon, 2 Dec 2019 12:12:45 +0100 Subject: [PATCH 03/20] Apply suggestions from code review Co-Authored-By: Anca Zamfir --- spec/consensus/light-client.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 9832eefc..42b0102e 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -202,9 +202,11 @@ not considered expired. func hasExpired(h) bool { if h.Header.bfttime + tp - Delta < now { // Observation 1 return true + } } - // 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 @@ -224,11 +226,12 @@ not considered expired. // check for adjacent headers if (h2.Header.height == h1.Header.height + 1) { - if h1.Header.NextV == h2.Header.V return (true, nil) - else return (false, ErrInvalidAdjacentHeaders) + if h1.Header.NextV == h2.Header.V + return (true, nil) + return (false, ErrInvalidAdjacentHeaders) } else { // check for non-adjacent headers - return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all, nil) + return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all, nil) } } ``` @@ -279,7 +282,8 @@ func Bisection(h1,h2,trustlevel) (bool, error) { while th.Header.Height <= h2.Header.height - 1 do { // try to move trusted header forward with stored headers ih := th - for all stored headers h s.t ih.Header.Height < h.Header.height < h2.Header.height do { // try to move trusted header forward + // try to move trusted header forward + for h in stored headers s.t ih.Header.Height < h.Header.height < h2.Header.height do { // we assume here that iteration is done in the order of header heights ok, err = CheckSupport(th,h,trustlevel) if err != nil { return (ok, err) } @@ -288,7 +292,8 @@ func Bisection(h1,h2,trustlevel) (bool, error) { } } - // at this point we have potentially updated th based on stored headers so we try to verify h2 based on new trusted header + // at this point we have potentially updated th based on stored headers so we try to verify h2 + // based on new trusted header ok, err = CheckSupport(th,h2,trustlevel) if (ok or err != nil) return (ok, err) From afda2d39b6a29104f483da8896b5c3de8667e53d Mon Sep 17 00:00:00 2001 From: Anca Zamfir Date: Fri, 6 Dec 2019 12:43:16 +0100 Subject: [PATCH 04/20] some suggestions for pseuodocode changes --- spec/consensus/light-client.md | 151 +++++++++++++++++++++------------ 1 file changed, 95 insertions(+), 56 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 42b0102e..97f0a356 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -203,6 +203,7 @@ not considered expired. if h.Header.bfttime + tp - Delta < now { // Observation 1 return true } + return false } // return true if header is correctly signed by 2/3+ voting power in the corresponding validator set; @@ -266,62 +267,6 @@ Towards Lite Client Completeness: **Bisection.** The following function uses CheckSupport in a recursion to find intermediate headers that allow to establish a sequence of trust. - - - -```go -// return (true, nil) in case we can trust header h2 based on header h1; otherwise return (false, error) where error captures the nature of the error. -func Bisection(h1,h2,trustlevel) (bool, error) { - assume h1.Header.Height < h2.header.Height - - ok, err = CheckSupport(h1,h2,trustlevel) - if (ok or err != nil) return (ok, 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 - while th.Header.Height <= h2.Header.height - 1 do { - // try to move trusted header forward with stored headers - ih := th - // try to move trusted header forward - for h in stored headers s.t ih.Header.Height < h.Header.height < h2.Header.height do { - // we assume here that iteration is done in the order of header heights - ok, err = CheckSupport(th,h,trustlevel) - if err != nil { return (ok, err) } - if ok { - th = h - } - } - - // at this point we have potentially updated th based on stored headers so we try to verify h2 - // based on new trusted header - ok, err = CheckSupport(th,h2,trustlevel) - if (ok or err != nil) return (ok, err) - - // we cannot verify h2 based on th, so we try to move trusted header closer to h2 by downloading header(s) between th and h2 - endHeight = h2.Header.height - foundPivot = false - while(!foundPivot) { - pivot := (th.Header.height + endHeight) / 2 - hp := Commit(pivot) - if !verify(hp) return (false, ErrInvalidHeader(hp)) - Store(hp) - - // try to move trusted header forward to hp - ok, err = CheckSupport(th,hp,trustlevel) - if err != nil { return (ok, err) } - if ok { - th = hp - foundPivot = true - } - endHeight = pivot - } - } -} -``` - - - - *Correctness arguments (sketch)* Lite Client Accuracy: @@ -363,3 +308,97 @@ func Backwards(h1,h2) bool { return (hash(h2) == old.Header.hash) } ``` + +```go +// return true if header has expired, i.e., it is outside its trusted period; otherwise it returns false +func hasExpired(h) bool { + if now - h.Header.bfttime > tp - Delta + return true // Observation 1 + return false +} + +// 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 (true, nil) in case h2 can be trusted based on h1, (false, nil) in case it cannot be trusted but no errors +// are observed during check and (false, error) in case an error is detected (for example adjacent +// headers are not consistent). +func CheckSupport(h1,h2,trustlevel) (bool, error) { + assume h1.Header.Height < h2.header.Height + + // check for adjacent headers + if h2.Header.height == h1.Header.height + 1 { + if h1.Header.NextV == h2.Header.V return nil + return false, ErrInvalidAdjacentHeaders + } + + // total sum of voting power of validators in h1.NextV + vp_all := totalVotingPower(h1.Header.NextV) + // check for non-adjacent headers + return votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all, nil +} + +// return nil in case we can trust header h2 based on header h1, error otherwise +func Bisection(h1,h2,trustlevel) error { + assume h1.Header.Height < h2.header.Height + + th := h1 + pivot = h2.Header.height + untrustedHeaders = [] + + while h2 not in Store.Headers() { // could be replaced with while true and check if h2 is stored in the loop + // check support for pivot, bisect if no support until a higher trusted header is found + while pivot < h1.Header.height { + hp := Commit(pivot) + if !verify(hp) return ErrInvalidHeader(hp) + done, err = CheckSupport(th,hp,trustlevel) + if err != nil return err + if done { + th = hp + Store.Add(hp) + break + } + untrustedHeaders.add(hp) + pivot := (th.Header.height + endHeight) / 2 + } + + // try to move trusted header forward + for h in untrustedHeaders { + // we assume here that iteration is done in the order of header heights + done, err = CheckSupport(th,h,trustlevel) + if err != nil return err + if done { + th = h + Store.Add(h) + } + } + } + return nil +} + +func VerifyHeader(h2, trustlevel) error { + if h2 in Store.Headers() + return nil + + Store.DisableExpiration() + // get the highest trusted headers lower than h2 + h1 = Store.HighestTrusted(h2.Header.height) + if h1 == nil + return ErrNoTrustedHeader + + err = Bisection(h1, h2, trustlevel) + if err != nil return err + + // remove all expired headers and start the expiration timer + Store.EnableExpiration() + if h2 in Store.Headers() return nil + return ErrHeaderExpired +} + +``` \ No newline at end of file From 5c580846bb19cd7af7f718658f9d8365f7aeddd3 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Fri, 6 Dec 2019 15:56:05 +0100 Subject: [PATCH 05/20] Improved error handling --- spec/consensus/light-client.md | 86 +++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 42b0102e..ee9f792c 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -168,38 +168,38 @@ When we say we trust *h.Header.NextV* we do *not* trust that each individual val ### Functions -The function *Bisection* checks whether to trust header *h2* based on the trusted header *h1*. It does so by calling -the function *CheckSupport* in the process of -bisection/recursion. *CheckSupport* implements the trusted period method and, for two adjacent headers (in term of heights), it checks uninterrupted sequence of proof. +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* based on *CheckSupport*, 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 +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 conditions under which we can trust a +header *h2* given the trust in the header *h1* as a single step, +i.e., it does not assume ensuring transitive trust relation between headers through some intermediate headers. + *Assumption*: In the following, we assume that *h2.Header.height > h1.Header.height*. We will quickly discuss the other case in the next section. We consider the following set-up: - the lite client communicates with one full node -- the lite client locally stores all the signed headers it obtained (trusted or not). In the pseudo code below we write *Store(header)* for this. -- If *Bisection* returns *false*, then the lite client has seen a forged header. - * However, it does not know which header(s) is/are the problematic one(s). - * In this case, the lite client can submit (some of) the headers it has seen as evidence. As the lite client communicates with one full node only when executing Bisection, there are two cases - - the full node is faulty - - the full node is correct and there was a fork in Tendermint consensus. Header *h1* is from a different branch than the one taken by the full node. This case is not focus of this document, but will be treated in the document on fork accountability. - -- the lite client must retry to retrieve correct headers from another full node - * it picks a new full node - * it restarts *Bisection* - * there might be optimizations; a lite client may not need to call *Commit(k)*, for a height *k* for which it already has a signed header it trusts. - * how to make sure that a lite client can communicate with a correct full node will be the focus of a separate document (recall Issue 3 from "Context of this document"). +- the lite client locally stores all the headers that has passed basic verification. 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. **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. We further use the function ```signers(Commit)``` that returns the set of validators that signed the Commit. **CheckSupport.** The following function checks whether we can trust the header h2 based on header h1 following the trusting period method. Time constraint is -captured by the `hasExpired` function that depends on trusted period (`tp`) and a parameter `Delta` that denotes minimum duration of header so it is -not considered expired. +captured by the ```hasExpired``` function that depends on trusted period (`tp`) and it returns true in case the header is outside its 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. ```go // return true if header has expired, i.e., it is outside its trusted period; otherwise it returns false - func hasExpired(h) bool { + func hasExpired(h, Delta) bool { if h.Header.bfttime + tp - Delta < now { // Observation 1 return true } @@ -214,12 +214,14 @@ not considered expired. } // Captures skipping condition. h1 and h2 has already passed basic validation (function `verify`). - // returns (true, nil) in case h2 can be trusted based on h1, (false, nil) in case it cannot be trusted but no errors are observed during check and (false, error) in case - // an error is detected (for example adjacent headers are not consistent). - func CheckSupport(h1,h2,trustlevel) (bool, error) { + // returns nil in case h2 can be trusted based on h1, otherwise returns error. + // ErrHeaderExpired is used to signal that h1 has expired, + // 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 - if hasExpired(h1) return (false, ErrHeaderExpired(h1)) + if hasExpired(h1) return ErrHeaderExpired(h1) // total sum of voting power of validators in h1.NextV vp_all := totalVotingPower(h1.Header.NextV) @@ -227,11 +229,14 @@ not considered expired. // check for adjacent headers if (h2.Header.height == h1.Header.height + 1) { if h1.Header.NextV == h2.Header.V - return (true, nil) - return (false, ErrInvalidAdjacentHeaders) + return nil + return ErrInvalidAdjacentHeaders } else { // check for non-adjacent headers - return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all, nil) + if votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all + return nil + else + return ErrTooMuchChange(h1, h2) } } ``` @@ -267,11 +272,9 @@ Towards Lite Client Completeness: **Bisection.** The following function uses CheckSupport in a recursion to find intermediate headers that allow to establish a sequence of trust. - - ```go // return (true, nil) in case we can trust header h2 based on header h1; otherwise return (false, error) where error captures the nature of the error. -func Bisection(h1,h2,trustlevel) (bool, error) { +func CanTrust(h1,h2,trustlevel) (bool, error) { assume h1.Header.Height < h2.header.Height ok, err = CheckSupport(h1,h2,trustlevel) @@ -319,6 +322,33 @@ func Bisection(h1,h2,trustlevel) (bool, error) { } ``` +```go +func CanTrustBisection(h1,h2,trustlevel) bool{ + if CheckSupport(h1,h2,trustlevel) { + return true + } + if h2.Header.height == h1.Header.height + 1 { + // we have adjacent headers that are not matching (failed + // the CheckSupport) + // we could submit evidence here + return false + } + pivot := (h1.Header.height + h2.Header.height) / 2 + hp := Commit(pivot) + // ask a full node for header of height pivot + Store(hp) + // store header hp locally + if Bisection(h1,hp,trustlevel) { + // only check right branch if hp is trusted + // (otherwise a lot of unnecessary computation may be done) + return Bisection(hp,h2,trustlevel) + } + else { + return false + } +} +``` + From 9ddfc798139ad5c77c92d4cc0cbebd12fc573327 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Wed, 11 Dec 2019 16:13:47 +0100 Subject: [PATCH 06/20] Add explanation on difference between trusted models --- spec/consensus/light-client.md | 334 ++++++++++++++------------------- 1 file changed, 138 insertions(+), 196 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 76ad8c82..1e4ed66f 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -6,18 +6,18 @@ A lite client is a process that connects to Tendermint full nodes and then tries In order to make sure that full nodes have the incentive to follow the protocol, we have to address the following three Issues -1) The lite client needs a method to verify headers it obtains from full nodes according to trust assumptions -- this document. +1) The lite client needs a method to verify headers it obtains from a full node it connects to according to trust assumptions -- this document. -2) The lite client must be able to connect to one correct full node to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document. +2) The lite client must be able to connect to other full nodes to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document (see #4215). -3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- see #3840 +3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- a future document (see #3840). ## Problem statement We assume that the lite client knows a (base) header *inithead* it trusts (by social consensus or because the lite client has decided to trust the header before). The goal is to check whether another header *newhead* can be trusted based on the data in *inithead*. -The correctness of the protocol is based on the assumption that *inithead* was generated by an instance of Tendermint consensus. The term "trusting" above indicates that the correctness on the protocol depends on this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk of trusting a corrupted/forged *inithead* is negligible. +The correctness of the protocol is based on the assumption that *inithead* was generated by an instance of Tendermint consensus. The term "trusting" above indicates that the correctness of the protocol depends on this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk of trusting a corrupted/forged *inithead* is negligible. ## Definitions @@ -61,7 +61,7 @@ For the purpose of this lite client specification, we assume that the Tendermint ### Definitions -* *tp*: trusting period +* *TRUSTED_PERIOD*: trusting period * for realtime *t*, the predicate *correct(v,t)* is true if the validator *v* follows the protocol until time *t* (we will see about recovery later). @@ -70,11 +70,11 @@ For the purpose of this lite client specification, we assume that the Tendermint ### 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 + tp. +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. Formally, \[ -\sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > +\sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + TRUSTED_PERIOD)} p > 2/3 \sum_{(v,p) \in h.Header.NextV} p \] @@ -151,40 +151,39 @@ This can be used in several settings: ## Details -*Assumptions* - -1. *tp < unbonding period*. -2. *snh.Header.bfttime < now* -3. *snh.Header.bfttime < h.Header.bfttime+tp* -4. *trust(h)=true* - - **Observation 1.** If *h.Header.bfttime + tp > now*, we trust the old 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). - ### 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* based on *CheckSupport*, the one based +from *h1* over intermediate headers to *h2*. 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 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 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, 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 +headers are detected). As detecting conflicting headers, its propagation through the network (by the gossip protocol) and execution of the fork accountability +protocol on the chain takes time, the lite client logic assumes conservative value for trusted period. More precisely, in the context of lite client we always +operate with a smaller trusted period that we call *lite client trusted period* (LITE_CLIENT_TRUSTED_PERIOD). If we assume that upper bound +for fork detection, propagation and processing on the chain is denoted with *fork procession period* (FORK_PROCESSING_PERIOD), then the following formula +holds: +```LITE_CLIENT_TRUSTED_PERIOD + FORK_PROCESSING_PERIOD < TRUSTED_PERIOD```, where TRUSTED_PERIOD comes from the Tendermint Failure Model. + *Assumption*: In the following, we assume that *h2.Header.height > h1.Header.height*. We will quickly discuss the other case in the next section. We consider the following set-up: - the lite client communicates with one full node -- the lite client locally stores all the headers that has passed basic verification. In the pseudo code below we write *Store(header)* for this. If a header failed to verify, then +- 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. @@ -193,17 +192,15 @@ the full node we are talking to is faulty and we should disconnect from it and r we will write ```totalVotingPower(V)``` for ```votingpower_in(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. -**CheckSupport.** The following function checks whether we can trust the header h2 based on header h1 following the trusting period method. Time constraint is -captured by the ```hasExpired``` function that depends on trusted period (`tp`) and it returns true in case the header is outside its trusted period. +**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 +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. ```go - // return true if header has expired, i.e., it is outside its trusted period; otherwise it returns false - func hasExpired(h, Delta) bool { - if h.Header.bfttime + tp - Delta < now { // Observation 1 - return true - } - return false + // return true if header is within its lite client trusted period; otherwise it returns false + func isWithinTrustedPeriod(h) bool { + 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; @@ -216,13 +213,18 @@ captured by the ```hasExpired``` function that depends on trusted period (`tp`) // 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, + // 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 + 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 hasExpired(h1) return ErrHeaderExpired(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) @@ -232,27 +234,14 @@ captured by the ```hasExpired``` function that depends on trusted period (`tp`) if h1.Header.NextV == h2.Header.V return nil return ErrInvalidAdjacentHeaders - } else { - // check for non-adjacent headers - if votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all - return nil - else - return ErrTooMuchChange(h1, h2) } + // check for non-adjacent headers + if votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all return nil + return ErrTooMuchChange + } ``` - *Remark*: Basic header verification must be done for *h2*. Similar checks are done in: - https://github.com/tendermint/tendermint/blob/master/types/validator_set.go#L591-L633 - - *Remark*: There are some sanity checks which are not in the code: - *h2.Header.height > h1.Header.height* and *h2.Header.bfttime > h1.Header.bfttime* and *h2.Header.bfttime < now*. - - *Remark*: ```return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all)``` may return false even if *h2* was properly generated by Tendermint consensus in the case of big changes in the validator sets. However, the check ```return (votingpower_in(signers(h2.Commit),h1.Header.NextV) > - 2/3 * vp_all)``` must return true if *h1* and *h2* were generated by Tendermint consensus. - -*Remark*: The 1/3 check differs from a previously proposed method that was based on intersecting validator sets and checking that the new validator set contains "enough" correct validators. We found that the old check is not suited for realistic changes in the validator sets. The new method is not only based on cardinalities, but also exploits that we can trust what is signed by a correct validator (i.e., signed by more than 1/3 of the voting power). - *Correctness arguments* Towards Lite Client Accuracy: @@ -270,83 +259,124 @@ Towards Lite Client Completeness: *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. -**Bisection.** The following function uses CheckSupport in a recursion to find intermediate headers that allow to establish a sequence of trust. +**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 -// return (true, nil) in case we can trust header h2 based on header h1; otherwise return (false, error) where error captures the nature of the error. -func CanTrust(h1,h2,trustlevel) (bool, error) { +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) + } + + // get the highest trusted headers lower than h2 + h1 = Store.HighestTrustedSmallerThan(height) + if h1 == nil + return ErrNoTrustedHeader + + err = CanTrust(h1, h2, trustlevel) // or CanTrustBisection((h1, h2, trustlevel) + 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 { assume h1.Header.Height < h2.header.Height - ok, err = CheckSupport(h1,h2,trustlevel) - if (ok or err != nil) return (ok, err) + 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 - while th.Header.Height <= h2.Header.height - 1 do { - // try to move trusted header forward with stored headers - ih := th - // try to move trusted header forward - for h in stored headers s.t ih.Header.Height < h.Header.height < h2.Header.height do { - // we assume here that iteration is done in the order of header heights - ok, err = CheckSupport(th,h,trustlevel) - if err != nil { return (ok, err) } - if ok { - th = h - } - } - - // at this point we have potentially updated th based on stored headers so we try to verify h2 - // based on new trusted header - ok, err = CheckSupport(th,h2,trustlevel) - if (ok or err != nil) return (ok, err) + untrustedHeaders := [] - // we cannot verify h2 based on th, so we try to move trusted header closer to h2 by downloading header(s) between th and h2 - endHeight = h2.Header.height - foundPivot = false - while(!foundPivot) { + while true { + endHeight = h2.Header.height + foundPivot = false + while(!foundPivot) { pivot := (th.Header.height + endHeight) / 2 hp := Commit(pivot) - if !verify(hp) return (false, ErrInvalidHeader(hp)) - Store(hp) - + if !verify(hp) return ErrInvalidHeader(hp) // try to move trusted header forward to hp - ok, err = CheckSupport(th,hp,trustlevel) - if err != nil { return (ok, err) } - if ok { + 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) + if err == nil { + Store.Add(h2) + return nil } + if err != ErrTooMuchChange return err } + return nil // this line should never be reached } ``` ```go -func CanTrustBisection(h1,h2,trustlevel) bool{ - if CheckSupport(h1,h2,trustlevel) { - return true - } - if h2.Header.height == h1.Header.height + 1 { - // we have adjacent headers that are not matching (failed - // the CheckSupport) - // we could submit evidence here - return false +func CanTrustBisection(h1,h2,trustlevel) 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 + pivot := (h1.Header.height + h2.Header.height) / 2 hp := Commit(pivot) - // ask a full node for header of height pivot - Store(hp) - // store header hp locally - if Bisection(h1,hp,trustlevel) { - // only check right branch if hp is trusted - // (otherwise a lot of unnecessary computation may be done) - return Bisection(hp,h2,trustlevel) - } - else { - return false + if !verify(hp) return ErrInvalidHeader(hp) + + err = CanTrustBisection(h1,hp,trustlevel) + if err == nil { + Store.Add(hp) + err2 = CanTrustBisection(hp,h2,trustlevel) + if err2 == nil { + Store.Add(h2) + return nil + } + return err2 } + return err } ``` @@ -356,8 +386,8 @@ func CanTrustBisection(h1,h2,trustlevel) bool{ *Correctness arguments (sketch)* Lite Client Accuracy: -- Assume by contradiction that *h2* was not generated correctly and the lite client sets trust to true because Bisection returns true. -- Bisection returns true only if all calls to CheckSupport in the recursion return true. +- Assume by contradiction that *h2* 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. - Thus we have a sequence of headers that all satisfied the CheckSupport - again a contradiction @@ -367,7 +397,7 @@ This is only ensured if upon *Commit(pivot)* the lite client is always provided *Stalling* -With Bisection, a faulty full node could stall a lite client by creating a long sequence of headers that are queried one-by-one by the lite client and look OK, before the lite client eventually detects a problem. There are several ways to address this: +With CanTrustBisection, a faulty full node could stall a lite client by creating a long sequence of headers that are queried one-by-one by the lite client and look OK, before the lite client eventually detects a problem. There are several ways to address this: * Each call to ```Commit``` could be issued to a different full node * Instead of querying header by header, the lite client tells a full node which header it trusts, and the height of the header it needs. The full node responds with the header along with a proof consisting of intermediate headers that the light client can use to verify. Roughly, Bisection would then be executed at the full node. * We may set a timeout how long bisection may take. @@ -380,111 +410,23 @@ In the use case where someone tells the lite client that application data that i *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 -func Backwards(h1,h2) bool { +func Backwards(h1,h2) error { assert (h2.Header.height < h1.Header.height) + if !isWithinTrustedPeriod(h1) return ErrHeaderNotTrusted(h1) + old := h1 for i := h1.Header.height - 1; i > h2.Header.height; i-- { new := Commit(i) - Store(new) if (hash(new) != old.Header.hash) { - return false + return ErrInvalidAdjacentHeaders } old := new + if !isWithinTrustedPeriod(h1) return ErrHeaderNotTrusted(h1) } - return (hash(h2) == old.Header.hash) + if hash(h2) == old.Header.hash return ErrInvalidAdjacentHeaders + return nil } ``` -```go -// return true if header has expired, i.e., it is outside its trusted period; otherwise it returns false -func hasExpired(h) bool { - if now - h.Header.bfttime > tp - Delta - return true // Observation 1 - return false -} - -// 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 (true, nil) in case h2 can be trusted based on h1, (false, nil) in case it cannot be trusted but no errors -// are observed during check and (false, error) in case an error is detected (for example adjacent -// headers are not consistent). -func CheckSupport(h1,h2,trustlevel) (bool, error) { - assume h1.Header.Height < h2.header.Height - - // check for adjacent headers - if h2.Header.height == h1.Header.height + 1 { - if h1.Header.NextV == h2.Header.V return nil - return false, ErrInvalidAdjacentHeaders - } - // total sum of voting power of validators in h1.NextV - vp_all := totalVotingPower(h1.Header.NextV) - // check for non-adjacent headers - return votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustlevel) * vp_all, nil -} - -// return nil in case we can trust header h2 based on header h1, error otherwise -func Bisection(h1,h2,trustlevel) error { - assume h1.Header.Height < h2.header.Height - - th := h1 - pivot = h2.Header.height - untrustedHeaders = [] - - while h2 not in Store.Headers() { // could be replaced with while true and check if h2 is stored in the loop - // check support for pivot, bisect if no support until a higher trusted header is found - while pivot < h1.Header.height { - hp := Commit(pivot) - if !verify(hp) return ErrInvalidHeader(hp) - done, err = CheckSupport(th,hp,trustlevel) - if err != nil return err - if done { - th = hp - Store.Add(hp) - break - } - untrustedHeaders.add(hp) - pivot := (th.Header.height + endHeight) / 2 - } - - // try to move trusted header forward - for h in untrustedHeaders { - // we assume here that iteration is done in the order of header heights - done, err = CheckSupport(th,h,trustlevel) - if err != nil return err - if done { - th = h - Store.Add(h) - } - } - } - return nil -} - -func VerifyHeader(h2, trustlevel) error { - if h2 in Store.Headers() - return nil - - Store.DisableExpiration() - // get the highest trusted headers lower than h2 - h1 = Store.HighestTrusted(h2.Header.height) - if h1 == nil - return ErrNoTrustedHeader - - err = Bisection(h1, h2, trustlevel) - if err != nil return err - - // remove all expired headers and start the expiration timer - Store.EnableExpiration() - if h2 in Store.Headers() return nil - return ErrHeaderExpired -} -``` \ No newline at end of file From 4f7c55507cb99c35c7774c583f182859283f3017 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Thu, 12 Dec 2019 12:29:12 +0100 Subject: [PATCH 07/20] 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 1e4ed66f..ec91c5aa 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 - - 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 } ``` From ee0cc537b8cb6b7052b39e0b0c84a7c1d03d821a Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Wed, 25 Dec 2019 13:58:27 +0100 Subject: [PATCH 08/20] Addressing reviewer's comments --- spec/consensus/light-client.md | 227 +++++++++++++++++++-------------- 1 file changed, 132 insertions(+), 95 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index ec91c5aa..e1bc8619 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -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. * 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 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 -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 + // 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) - // returns signed header: header (with the fields from - // above) with Commit that include signatures of - // validators that signed the header + // returns validator set for the given height + func Validators(height int64) (ValidatorSet, error) type SignedHeader struct { Header Header Commit Commit } + + type ValidatorSet struct { + Validators []Validator + } + + type Validator struct { + Address Address + VotingPower int64 + } ``` +Furthermore, we assume the following auxiliary functions: +```go + + // returns the validator set for the given validator hash + func validators(validatorsHash []byte) ValidatorSet + + // TODO: define precisely what this functions is supposed to be doing + func signers(commit) []Validator + +``` + + ### Definitions * *TRUSTED_PERIOD*: trusting period @@ -70,7 +95,9 @@ For the purpose of this lite client specification, we assume that the Tendermint ### 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, \[ @@ -82,8 +109,12 @@ Formally, *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 @@ -148,6 +179,8 @@ We consider the following use case: 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*. - 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 @@ -155,20 +188,21 @@ This can be used in several settings: **Observation 1.** If *h.Header.bfttime + tp > now*, we trust the old 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 -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 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 -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. 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 @@ -180,29 +214,30 @@ holds: ```LITE_CLIENT_TRUSTED_PERIOD + FORK_PROCESSING_PERIOD < TRUSTED_PERIOD```, where TRUSTED_PERIOD comes from the Tendermint Failure Model. -*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: - 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). - * 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 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. -**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. ```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 // 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 } @@ -211,63 +246,63 @@ true in case the header is within its lite client trusted period. // 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 + return votingPowerIn(signers(h.Commit),h.Header.V) > 2/3 * vp_all } - // Captures skipping condition. h1 and h2 have already passed basic validation + // Captures skipping condition. trusted_h and untrusted_h 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, + // Returns nil in case untrusted_h can be trusted based on trusted_h, otherwise returns error. + // ErrHeaderNotWithinTrustedPeriod is used when trusted_h 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 + func CheckSupport(trusted_h,untrusted_h,trustThreshold) error { + assert trusted_h.Header.Height < untrusted_h.header.Height and + trusted_h.Header.bfttime < untrusted_h.Header.bfttime and + untrusted_h.Header.bfttime < now - if !isWithinTrustedPeriod(h1) return ErrHeaderNotWithinTrustedPeriod(h1) + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) - // Although while executing the rest of CheckSupport function, h1 can expiry based + // Although while executing the rest of CheckSupport function, trusted_h can expire 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 + // validator set that has signed trusted_h 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) + // check for adjacent headers + if untrusted_h.Header.height == trusted_h.Header.height + 1 { + if trusted_h.Header.NextV == untrusted_h.Header.V + return nil + return ErrInvalidAdjacentHeaders + } - // check for adjacent headers - if (h2.Header.height == h1.Header.height + 1) { - if h1.Header.NextV == h2.Header.V - return nil - return ErrInvalidAdjacentHeaders - } - // check for non-adjacent headers - if votingpower_in(signers(h2.Commit),h1.Header.NextV) > max(1/3,trustThreshold) * vp_all { - return nil - } - return ErrTooMuchChange + // total sum of voting power of validators in trusted_h.NextV + vp_all := totalVotingPower(trusted_h.Header.NextV) + // check for non-adjacent headers + if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { + return nil + } + return ErrTooMuchChange } ``` *Correctness arguments* 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: -- 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. @@ -278,39 +313,41 @@ by relying on *CheckSupport* function. ```go 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 { + if isWithinTrustedPeriod(untrusted_h) return nil + return ErrHeaderNotWithinTrustedPeriod(untrusted_h) } - h2 := Commit(height) - if !verify(h2) { return ErrInvalidHeader(h2) } - if !isWithinTrustedPeriod(h2) { return ErrHeaderNotWithinTrustedPeriod(h2) } + untrusted_h := Commit(height) + if !verify(untrusted_h) { return ErrInvalidHeader(untrusted_h) } + if !isWithinTrustedPeriod(untrusted_h) { return ErrHeaderNotWithinTrustedPeriod(untrusted_h) } - // get the highest trusted headers lower than h2 - h1 = Store.HighestTrustedSmallerThan(height) - if h1 == nil { return ErrNoTrustedHeader } + // get the highest trusted headers lower than untrusted_h + trusted_h = Store.HighestTrustedSmallerThan(height) + if trusted_h == nil { return ErrNoTrustedHeader } - err = CanTrust(h1, h2, trustThreshold) // or CanTrustBisection((h1, h2, trustThreshold) + err = CanTrust(trusted_h, untrusted_h, trustThreshold) // or CanTrustBisection((trusted_h, untrusted_h, trustThreshold) if err != nil { return err } - if isWithinTrustedPeriod(h2) { - Store.add(h2) + if isWithinTrustedPeriod(untrusted_h) { + Store.add(untrusted_h) // we store only trusted headers, as we assume that only trusted headers // are influencing end user business decisions. return nil } - return ErrHeaderNotTrusted(h2) + return ErrHeaderNotTrusted(untrusted_h) } -// return nil in case we can trust header h2 based on header h1; otherwise return error +// return nil in case we can trust header untrusted_h based on header trusted_h; otherwise return error // where error captures the nature of the error. -func CanTrust(h1,h2,trustThreshold) error { - assume h1.Header.Height < h2.header.Height +// Note that untrusted_h must have been verified by the caller, i.e. verify(untrusted_h) was successful. +func CanTrust(trusted_h,untrusted_h,trustThreshold) error { + assume trusted_h.Header.Height < untrusted_h.header.Height - th := h1 // th is trusted header - untrustedHeaders := [h2] + th := trusted_h // th is trusted header + // untrustedHeader is a list of verified headers that have not passed CheckSupport() + untrustedHeaders := [untrusted_h] while true { for h in untrustedHeaders { @@ -320,7 +357,7 @@ func CanTrust(h1,h2,trustThreshold) error { th = h Store.Add(h) untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) - if th == h2 { return nil } + if th == untrusted_h { return nil } } if (err != ErrTooMuchChange) { return err } } @@ -348,26 +385,26 @@ func CanTrust(h1,h2,trustThreshold) error { ``` ```go -func CanTrustBisection(h1,h2,trustThreshold) error { - assume h1.Header.Height < h2.header.Height +func CanTrustBisection(trusted_h,untrusted_h,trustThreshold) error { + assume trusted_h.Header.Height < untrusted_h.header.Height - err = CheckSupport(h1,h2,trustThreshold) + err = CheckSupport(trusted_h,untrusted_h,trustThreshold) if err == nil { - Store.Add(h2) + Store.Add(untrusted_h) return nil } if err != ErrTooMuchChange return err - pivot := (h1.Header.height + h2.Header.height) / 2 + pivot := (trusted_h.Header.height + untrusted_h.Header.height) / 2 hp := Commit(pivot) if !verify(hp) return ErrInvalidHeader(hp) - err = CanTrustBisection(h1,hp,trustThreshold) + err = CanTrustBisection(trusted_h,hp,trustThreshold) if err == nil { Store.Add(hp) - err2 = CanTrustBisection(hp,h2,trustThreshold) + err2 = CanTrustBisection(hp,untrusted_h,trustThreshold) if err2 == nil { - Store.Add(h2) + Store.Add(untrusted_h) return nil } return err2 @@ -382,7 +419,7 @@ func CanTrustBisection(h1,h2,trustThreshold) error { *Correctness arguments (sketch)* Lite Client Accuracy: -- 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. - Thus we have a sequence of headers that all satisfied the CheckSupport - 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. -### 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. *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 -func Backwards(h1,h2) error { - assert (h2.Header.height < h1.Header.height) - if !isWithinTrustedPeriod(h1) return ErrHeaderNotTrusted(h1) +func Backwards(trusted_h,untrusted_h) error { + assert (untrusted_h.Header.height < trusted_h.Header.height) + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) - old := h1 - for i := h1.Header.height - 1; i > h2.Header.height; i-- { + old := trusted_h + for i := trusted_h.Header.height - 1; i > untrusted_h.Header.height; i-- { new := Commit(i) if (hash(new) != old.Header.hash) { return ErrInvalidAdjacentHeaders } old := new - if !isWithinTrustedPeriod(h1) return ErrHeaderNotTrusted(h1) + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) } - if hash(h2) != old.Header.hash return ErrInvalidAdjacentHeaders + if hash(untrusted_h) != old.Header.hash return ErrInvalidAdjacentHeaders return nil } ``` From 0adde9d415c398659c40cd9f22ef07d10c0ae5a3 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Thu, 26 Dec 2019 13:11:01 +0100 Subject: [PATCH 09/20] Separating algorithm from proofs --- spec/consensus/light-client.md | 591 +++++++++++++++++---------------- 1 file changed, 310 insertions(+), 281 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index e1bc8619..89ec5ec7 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -1,24 +1,16 @@ # Lite client -A lite client is a process that connects to Tendermint full nodes and then tries to verify application data using the Merkle proofs. - -## Context of this document - -In order to make sure that full nodes have the incentive to follow the protocol, we have to address the following three Issues - -1) The lite client needs a method to verify headers it obtains from a full node it connects to according to trust assumptions -- this document. - -2) The lite client must be able to connect to other full nodes to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document (see #4215). - -3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- a future document (see #3840). +A lite client is a process that connects to Tendermint full node(s) and then tries to verify application +data using the Merkle proofs. ## Problem statement +We assume that the lite client knows a (base) header *inithead* it trusts (by social consensus or because +the lite client has decided to trust the header before). The goal is to check whether another header +*newhead* can be trusted based on the data in *inithead*. -We assume that the lite client knows a (base) header *inithead* it trusts (by social consensus or because the lite client has decided to trust the header before). The goal is to check whether another header *newhead* can be trusted based on the data in *inithead*. - -The correctness of the protocol is based on the assumption that *inithead* was generated by an instance of Tendermint consensus. The term "trusting" above indicates that the correctness of the protocol depends on this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk of trusting a corrupted/forged *inithead* is negligible. - +The correctness of the protocol is based on the assumption that *inithead* was generated by an instance of +Tendermint consensus. ## Definitions @@ -26,290 +18,82 @@ 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. - * header fields - - *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. + ```go + type Header struct { + Height int64 + Time Time // the chain time when the header (block) was generated - * 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. + // hashes from the app output from the prev block + ValidatorsHash []byte // hash of the validators for the current block + NextValidatorsHash []byte // hash of the validators for the next block - * 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. + // hashes of block data + LastCommitHash []byte // hash of the commit from validators from the last block + ... + } - * Validator fields. We will write a validator as a tuple *(v,p)* such that - + *v* is the identifier (we assume identifiers are unique in each validator set) - + *p* is its voting power + type SignedHeader struct { + Header Header + Commit Commit // commit for the given header + } + + type ValidatorSet struct { + Validators []Validator + } + type Validator struct { + Address Address // validator address (we assume validator's addresses are unique) + VotingPower int64 // validator's voting power + } + ``` ### Functions For the purpose of this lite client specification, we assume that the Tendermint Full Node exposes the following functions over Tendermint RPC: ```go - // returns signed header: header (with the fields from above) with Commit - // that include signatures of validators that signed the header + // returns signed header: Header with Commit func Commit(height int64) (SignedHeader, error) // returns validator set for the given height func Validators(height int64) (ValidatorSet, error) - - type SignedHeader struct { - Header Header - Commit Commit - } - - type ValidatorSet struct { - Validators []Validator - } - - type Validator struct { - Address Address - VotingPower int64 - } ``` Furthermore, we assume the following auxiliary functions: ```go - // returns the validator set for the given validator hash func validators(validatorsHash []byte) ValidatorSet - // TODO: define precisely what this functions is supposed to be doing - func signers(commit) []Validator + // returns the set of validators from the given validator set that committed the block + func signers(commit Commit, validatorSet ValidatorSet) []Validator ``` - -### Definitions - -* *TRUSTED_PERIOD*: trusting period -* for realtime *t*, the predicate *correct(v,t)* is true if the validator *v* - follows the protocol until time *t* (we will see about recovery later). - - - - ### Tendermint Failure Model -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```. +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, -\[ -\sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + TRUSTED_PERIOD)} p > -2/3 \sum_{(v,p) \in h.Header.NextV} p -\] - -*Assumption*: "correct" is defined w.r.t. realtime (some Newtonian global notion of time, i.e., wall time), while *bfttime* corresponds to the reading of the local clock of a validator (how this time is computed may change when the Tendermint consensus is modified). In this note, we assume that all clocks are synchronized to realtime. We can make this more precise eventually (incorporating clock drift, accuracy, precision, etc.). Right now, we consider this assumption sufficient, as clock synchronization (under NTP) is in the order of milliseconds and *tp* is in the order of weeks. +*Assumption*: "correct" is defined w.r.t. realtime (some Newtonian global notion of time, i.e., wall time), +while `Header.Time` corresponds to the [BFT time](bft-time.md). In this note, we assume that clocks of correct processes +are synchronized (for example using NTP), and therefore there is bounded clock drift between clocks and +BFT time. We can make this more precise eventually (incorporating clock drift, accuracy, precision, etc.). Right now, +we consider this assumption sufficient, as clock synchronization (under NTP) is in the order of milliseconds and +`TRUSTED_PERIOD` is in the order of weeks. *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 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. +like `counter-factual signing`, `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 1/3 (or more) 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 - -The lite 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: - -- Lite Client Completeness: If header *h* was correctly generated by an instance of Tendermint consensus (and its age is less than the trusting period), then the lite client should eventually set *trust(h)* to true. - -- Lite Client Accuracy: If header *h* was *not generated* by an instance of Tendermint consensus, then the lite client should never set *trust(h)* to true. - -*Remark*: If in the course of the computation, the lite client obtains certainty that some headers were forged by adversaries (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. - -*Remark*: In Completeness we use "eventually", while in practice *trust(h)* should be set to true before *h.Header.bfttime + tp*. If not, the block cannot be trusted because it is too old. - -*Remark*: If a header *h* is marked with *trust(h)*, but it is too old (its bfttime is more than *tp* ago), then the lite client should set *trust(h)* to false again. - -*Assumption*: Initially, the lite client has a header *inithead* that it trusts correctly, that is, *inithead* was correctly generated by the Tendermint consensus. - -To reason about the correctness, we may prove the following invariant. - -*Verification Condition: Lite Client Invariant.* - For each lite client *l* and each header *h*: -if *l* has set *trust(h) = true*, - then validators that are correct until time *h.Header.bfttime + tp* have more than two thirds of the voting power in *h.Header.NextV*. - - Formally, - \[ - \sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > - 2/3 \sum_{(v,p) \in h.Header.NextV} p - \] - -*Remark.* To prove the invariant, we will have to prove that the lite client only trusts headers that were correctly generated by Tendermint consensus, then the formula above follows from the Tendermint failure model. - - -## High Level Solution - -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. - -1. **Uninterrupted sequence of proof.** If a block is appended to the chain, where the last block -is trusted (and properly committed by the old validator set in the next block), -and the new block contains a new validator set, the new block is trusted if the lite client knows all headers in the prefix. -Intuitively, a trusted validator set is assumed to only chose a new validator set that will obey the Tendermint Failure Model. - -2. **Trusting period.** Based on a trusted block *h*, and the lite client -invariant, which ensures the fault assumption during the trusting period, we can check whether at least one validator, that has been continuously correct from *h.Header.bfttime* until now, has signed *snh*. -If this is the case, similarly to above, the chosen validator set in *snh* does not violate the Tendermint Failure Model. - -3. **Bisection.** If a check according to the trusting period fails, the lite client can try to obtain a header *hp* whose height lies between *h* and *snh* in order to check whether *h* can be used to get trust for *hp*, and *hp* can be used to get trust for *snh*. If this is the case we can trust *snh*; if not, we may continue recursively. - -## How to use it - -We consider the following use case: - the lite client wants to verify a header for some given height *k*. Thus: - - it requests the signed header for height *k* from a full node - - it tries to verify this header with the methods described here. - -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*. - - 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 - -**Observation 1.** If *h.Header.bfttime + tp > now*, we trust the old -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 less than 1/3 of them are faulty (more precisely, the faulty ones have less than 1/3 of the total voting power). - +This is discussed in document on [Fork accountability](fork-accountability.md). ### Functions -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 -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 -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. - -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 -headers are detected). As detecting conflicting headers, its propagation through the network (by the gossip protocol) and execution of the fork accountability -protocol on the chain takes time, the lite client logic assumes conservative value for trusted period. More precisely, in the context of lite client we always -operate with a smaller trusted period that we call *lite client trusted period* (LITE_CLIENT_TRUSTED_PERIOD). If we assume that upper bound -for fork detection, propagation and processing on the chain is denoted with *fork procession period* (FORK_PROCESSING_PERIOD), then the following formula -holds: -```LITE_CLIENT_TRUSTED_PERIOD + FORK_PROCESSING_PERIOD < TRUSTED_PERIOD```, where TRUSTED_PERIOD comes from the Tendermint Failure Model. - - -*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: -- 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.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). - * 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 are talking to (as we haven't observed full node misbehavior in this case). - -**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. - -**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. -```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 - // return true if header is within its lite client trusted period; otherwise it returns false - func isWithinTrustedPeriod(h, now) bool { - 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 - // 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 votingPowerIn(signers(h.Commit),h.Header.V) > 2/3 * vp_all - } - - // Captures skipping condition. trusted_h and untrusted_h have already passed basic validation - // (function `verify`). - // Returns nil in case untrusted_h can be trusted based on trusted_h, otherwise returns error. - // ErrHeaderNotWithinTrustedPeriod is used when trusted_h 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(trusted_h,untrusted_h,trustThreshold) error { - assert trusted_h.Header.Height < untrusted_h.header.Height and - trusted_h.Header.bfttime < untrusted_h.Header.bfttime and - untrusted_h.Header.bfttime < now - - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) - - // Although while executing the rest of CheckSupport function, trusted_h can expire 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 trusted_h can be trusted. Furthermore, CheckSupport function - // is not doing expensive operation (neither rpc nor signature verification), so it - // should execute fast. - - // check for adjacent headers - if untrusted_h.Header.height == trusted_h.Header.height + 1 { - if trusted_h.Header.NextV == untrusted_h.Header.V - return nil - return ErrInvalidAdjacentHeaders - } - - // total sum of voting power of validators in trusted_h.NextV - vp_all := totalVotingPower(trusted_h.Header.NextV) - - // check for non-adjacent headers - if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { - return nil - } - return ErrTooMuchChange - } -``` - -*Correctness arguments* - -Towards Lite Client Accuracy: -- 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: -- 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 *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. - -**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. - +**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, trustThreshold) error { @@ -337,8 +121,21 @@ func VerifyHeader(height, trustThreshold) error { } return ErrHeaderNotTrusted(untrusted_h) } +``` +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 +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 +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. + + +```go // return nil in case we can trust header untrusted_h based on header trusted_h; otherwise return error // where error captures the nature of the error. // Note that untrusted_h must have been verified by the caller, i.e. verify(untrusted_h) was successful. @@ -413,34 +210,78 @@ func CanTrustBisection(trusted_h,untrusted_h,trustThreshold) error { } ``` +**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. +**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. +```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 + // return true if header is within its lite client trusted period; otherwise it returns false + func isWithinTrustedPeriod(h, now) bool { + return h.Header.bfttime + LITE_CLIENT_TRUSTED_PERIOD > now + } -*Correctness arguments (sketch)* + // 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 votingPowerIn(signers(h.Commit),h.Header.V) > 2/3 * vp_all + } -Lite Client Accuracy: -- 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. -- Thus we have a sequence of headers that all satisfied the CheckSupport -- again a contradiction + // Captures skipping condition. trusted_h and untrusted_h have already passed basic validation + // (function `verify`). + // Returns nil in case untrusted_h can be trusted based on trusted_h, otherwise returns error. + // ErrHeaderNotWithinTrustedPeriod is used when trusted_h 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(trusted_h,untrusted_h,trustThreshold) error { + assert trusted_h.Header.Height < untrusted_h.header.Height and + trusted_h.Header.bfttime < untrusted_h.Header.bfttime and + untrusted_h.Header.bfttime < now -Lite Client Completeness: + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) -This is only ensured if upon *Commit(pivot)* the lite client is always provided with a correctly generated header. + // Although while executing the rest of CheckSupport function, trusted_h can expire 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 trusted_h can be trusted. Furthermore, CheckSupport function + // is not doing expensive operation (neither rpc nor signature verification), so it + // should execute fast. -*Stalling* + // check for adjacent headers + if untrusted_h.Header.height == trusted_h.Header.height + 1 { + if trusted_h.Header.NextV == untrusted_h.Header.V + return nil + return ErrInvalidAdjacentHeaders + } -With CanTrustBisection, a faulty full node could stall a lite client by creating a long sequence of headers that are queried one-by-one by the lite client and look OK, before the lite client eventually detects a problem. There are several ways to address this: -* Each call to ```Commit``` could be issued to a different full node -* Instead of querying header by header, the lite client tells a full node which header it trusts, and the height of the header it needs. The full node responds with the header along with a proof consisting of intermediate headers that the light client can use to verify. Roughly, Bisection would then be executed at the full node. -* We may set a timeout how long bisection may take. + // total sum of voting power of validators in trusted_h.NextV + vp_all := totalVotingPower(trusted_h.Header.NextV) + + // check for non-adjacent headers + if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { + return nil + } + return ErrTooMuchChange + } +``` -### The case *untrusted_h.Header.height < trusted_h.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 func Backwards(trusted_h,untrusted_h) error { @@ -463,3 +304,191 @@ func Backwards(trusted_h,untrusted_h) error { +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 +headers are detected). As detecting conflicting headers, its propagation through the network (by the gossip protocol) and execution of the fork accountability +protocol on the chain takes time, the lite client logic assumes conservative value for trusted period. More precisely, in the context of lite client we always +operate with a smaller trusted period that we call *lite client trusted period* (LITE_CLIENT_TRUSTED_PERIOD). If we assume that upper bound +for fork detection, propagation and processing on the chain is denoted with *fork procession period* (FORK_PROCESSING_PERIOD), then the following formula +holds: +```LITE_CLIENT_TRUSTED_PERIOD + FORK_PROCESSING_PERIOD < TRUSTED_PERIOD```, where TRUSTED_PERIOD comes from the Tendermint Failure Model. + + +*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: +- 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.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). + * 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 are talking to (as we haven't observed full node misbehavior in this case). + + + +## Context of this document + +In order to make sure that full nodes have the incentive to follow the protocol, we have to address the +following three Issues + +1) The lite client needs a method to verify headers it obtains from a full node it connects to according to trust assumptions -- this document. + +2) The lite client must be able to connect to other full nodes to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document (see #4215). + +3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- a future document (see #3840). + +The term "trusting" above indicates that the correctness of the protocol depends on +this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk +of trusting a corrupted/forged *inithead* is negligible. + +* 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. + +* 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. + + + * Validator fields. We will write a validator as a tuple *(v,p)* such that + + *v* is the identifier (we assume identifiers are unique in each validator set) + + *p* is its voting power + +### Definitions + +* *TRUSTED_PERIOD*: trusting period +* for realtime *t*, the predicate *correct(v,t)* is true if the validator *v* + follows the protocol until time *t* (we will see about recovery later). + + +### Tendermint Failure Model + +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, +\[ +\sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + TRUSTED_PERIOD)} p > +2/3 \sum_{(v,p) \in h.Header.NextV} p +\] + + +## Lite Client Trusting Spec + +The lite 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: + +- Lite Client Completeness: If header *h* was correctly generated by an instance of Tendermint consensus (and its age is less than the trusting period), then the lite client should eventually set *trust(h)* to true. + +- Lite Client Accuracy: If header *h* was *not generated* by an instance of Tendermint consensus, then the lite client should never set *trust(h)* to true. + +*Remark*: If in the course of the computation, the lite client obtains certainty that some headers were forged by adversaries (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. + +*Remark*: In Completeness we use "eventually", while in practice *trust(h)* should be set to true before *h.Header.bfttime + tp*. If not, the block cannot be trusted because it is too old. + +*Remark*: If a header *h* is marked with *trust(h)*, but it is too old (its bfttime is more than *tp* ago), then the lite client should set *trust(h)* to false again. + +*Assumption*: Initially, the lite client has a header *inithead* that it trusts correctly, that is, *inithead* was correctly generated by the Tendermint consensus. + +To reason about the correctness, we may prove the following invariant. + +*Verification Condition: Lite Client Invariant.* + For each lite client *l* and each header *h*: +if *l* has set *trust(h) = true*, + then validators that are correct until time *h.Header.bfttime + tp* have more than two thirds of the voting power in *h.Header.NextV*. + + Formally, + \[ + \sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > + 2/3 \sum_{(v,p) \in h.Header.NextV} p + \] + +*Remark.* To prove the invariant, we will have to prove that the lite client only trusts headers that were correctly generated by Tendermint consensus, then the formula above follows from the Tendermint failure model. + + +## High Level Solution + +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. + +1. **Uninterrupted sequence of proof.** If a block is appended to the chain, where the last block +is trusted (and properly committed by the old validator set in the next block), +and the new block contains a new validator set, the new block is trusted if the lite client knows all headers in the prefix. +Intuitively, a trusted validator set is assumed to only chose a new validator set that will obey the Tendermint Failure Model. + +2. **Trusting period.** Based on a trusted block *h*, and the lite client +invariant, which ensures the fault assumption during the trusting period, we can check whether at least one validator, that has been continuously correct from *h.Header.bfttime* until now, has signed *snh*. +If this is the case, similarly to above, the chosen validator set in *snh* does not violate the Tendermint Failure Model. + +3. **Bisection.** If a check according to the trusting period fails, the lite client can try to obtain a header *hp* whose height lies between *h* and *snh* in order to check whether *h* can be used to get trust for *hp*, and *hp* can be used to get trust for *snh*. If this is the case we can trust *snh*; if not, we may continue recursively. + +## How to use it + +We consider the following use case: + the lite client wants to verify a header for some given height *k*. Thus: + - it requests the signed header for height *k* from a full node + - it tries to verify this header with the methods described here. + +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*. + - 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 + +**Observation 1.** If *h.Header.bfttime + tp > now*, we trust the old +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 less than 1/3 of them are faulty (more precisely, the faulty ones have less than 1/3 of the total voting power). + + + +*Correctness arguments* + +Towards Lite Client Accuracy: +- 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: +- 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 *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. + + + + +*Correctness arguments (sketch)* + +Lite Client Accuracy: +- 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. +- Thus we have a sequence of headers that all satisfied the CheckSupport +- again a contradiction + +Lite Client Completeness: + +This is only ensured if upon *Commit(pivot)* the lite client is always provided with a correctly generated header. + +*Stalling* + +With CanTrustBisection, a faulty full node could stall a lite client by creating a long sequence of headers that are queried one-by-one by the lite client and look OK, before the lite client eventually detects a problem. There are several ways to address this: +* Each call to ```Commit``` could be issued to a different full node +* Instead of querying header by header, the lite client tells a full node which header it trusts, and the height of the header it needs. The full node responds with the header along with a proof consisting of intermediate headers that the light client can use to verify. Roughly, Bisection would then be executed at the full node. +* We may set a timeout how long bisection may take. + + + + + From 4a9eb1f1acf99b73953005ad01e20e8a08b52219 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Tue, 31 Dec 2019 13:31:35 +0100 Subject: [PATCH 10/20] Intermediate commit (aligning spec with the code) --- spec/consensus/light-client.md | 280 +++++++++++++++++++++++++++------ 1 file changed, 230 insertions(+), 50 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 89ec5ec7..883ac009 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -29,7 +29,6 @@ In the following, only the details of the data structures needed for this specif // hashes of block data LastCommitHash []byte // hash of the commit from validators from the last block - ... } type SignedHeader struct { @@ -38,13 +37,19 @@ In the following, only the details of the data structures needed for this specif } type ValidatorSet struct { - Validators []Validator + Validators []Validator + TotalVotingPower int64 } type Validator struct { Address Address // validator address (we assume validator's addresses are unique) VotingPower int64 // validator's voting power } + + type TrustedState { + SignedHeader SignedHeader + ValidatorSet ValidatorSet + } ``` ### Functions @@ -66,6 +71,16 @@ Furthermore, we assume the following auxiliary functions: // returns the set of validators from the given validator set that committed the block func signers(commit Commit, validatorSet ValidatorSet) []Validator + // return the voting power the validators in v1 have according to their voting power in set V2 + func votingPowerIn(v1 []Validator, v2 ValidatorSet) int64 + + // add this state as trusted to the store + func add(store Store, trustedState TrustedState) error + + // retrieve the trusted state at given height if it exists (error = nil) + // return an error if there are no trusted state for the given height + // if height = 0, return the latest trusted state + func get(store Store, height int64) (TrustedState, error) ``` ### Tendermint Failure Model @@ -91,38 +106,185 @@ This is discussed in document on [Fork accountability](fork-accountability.md). ### Functions -**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. +**VerifyAndUpdateSingle.** The function `VerifyAndUpdateSingle` attempts to update +the (trusted) store with the given untrusted header and the corresponding validator sets. +It ensures that the last trusted header from the store hasn't expired yet (it is still within its trusted period), +and that the untrusted header can be verified using the latest trusted state from the store. +Note that this function is not making external (RPC) calls to the full node; the whole logic is +based on the local (given) state. This function is supposed to be used by the IBC handlers. ```go -func VerifyHeader(height, trustThreshold) error { - if untrusted_h, exists := Store.Get(height); exists { - if isWithinTrustedPeriod(untrusted_h) return nil - return ErrHeaderNotWithinTrustedPeriod(untrusted_h) +func VerifyAndUpdateSingle(untrustedSh SignedHeader, + untrustedVs ValidatorSet, + untrustedNextVs ValidatorSet, + trustThreshold TrustThreshold, + trustingPeriod Duration, + now Time, + store Store) error { + + // fetch the latest state and ensure it hasn't expired + trustedState, error = get(store, 0) + if error != nil return error + + trustedSh = trustedState.SignedHeader + if !isWithinTrustedPeriod(trustedSh.Header, trustingPeriod, now) { + return ErrHeaderNotWithinTrustedPeriod + } + + error = verifySingle( + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) + + if error != nil return error + + // the untrusted header is now trusted. update the store + newTrustedState = TrustedState(untrustedSh, untrustedNextVs) + return add(store, newTrustedState) +} + +``` + +**VerifyAndUpdateBisection.** The function `VerifyAndUpdateBisection` 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 VerifyAndUpdateBisection(height int64, + trustThreshold TrustThreshold, + trustingPeriod Duration, + now Time, + store Store) error { + + // fetch the latest state and ensure it hasn't expired + trustedState, error = get(store, 0) + if error != nil return error + + trustedSh = trustedState.SignedHeader + assert trustedSh.Header.Height < height + + if !isWithinTrustedPeriod(trustedSh.Header, trustingPeriod, now) { + return ErrHeaderNotWithinTrustedPeriod } - untrusted_h := Commit(height) - if !verify(untrusted_h) { return ErrInvalidHeader(untrusted_h) } - if !isWithinTrustedPeriod(untrusted_h) { return ErrHeaderNotWithinTrustedPeriod(untrusted_h) } + untrustedSh, error := Commit(height) + if error != nil return error - // get the highest trusted headers lower than untrusted_h - trusted_h = Store.HighestTrustedSmallerThan(height) - if trusted_h == nil { return ErrNoTrustedHeader } + untrustedH = untrustedSh.Header - err = CanTrust(trusted_h, untrusted_h, trustThreshold) // or CanTrustBisection((trusted_h, untrusted_h, trustThreshold) - if err != nil { return err } + untrustedVs, error := Validators(untrustedH.Height) + if error != nil return error - if isWithinTrustedPeriod(untrusted_h) { - Store.add(untrusted_h) - // we store only trusted headers, as we assume that only trusted headers - // are influencing end user business decisions. - return nil + untrustedNextVs, error := Validators(untrustedH.Height + 1) + if error != nil return error + + error = verifySingle( + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) + + if fatalCheckSupportError(error) return error + + if error == nil { + // the untrusted header is now trusted. update the store + newTrustedState = TrustedState(untrustedSh, untrustedNextVs) + return add(store, newTrustedState) } - return ErrHeaderNotTrusted(untrusted_h) + + // at this point in time we need to do bisection + pivotHeight := ceil((trustedSh.Header.Height + untrustedH.Height) / 2) + + error = VerifyAndUpdateBisection(pivotHeight, trustThreshold, trustingPeriod, now, store) + if error != nil return error + + error = VerifyAndUpdateBisection(untrustedH.Height, trustThreshold, trustingPeriod, now, store) + if error != nil return error + return nil } ``` +```go +func verifySingle(trustedState TrustedState, + untrustedSh SignedHeader, + untrustedVs ValidatorSet, + untrustedNextVs ValidatorSet, + trustThreshold TrustThreshold) error { + + // ensure the new height is higher + untrustedHeight = untrustedSh.Header.Height + trustedHeight = trustedState.SignedHeader.Header.Height + assert untrustedHeight > trustedHeight + + // validate the untrusted header against its commit, vals, and next_vals + untrustedHeader = untrustedSh.Header + untrustedCommit = untrustedSh.Commit + + error = validateHeaderAndVals(untrustedSh, untrustedVs, untrustedNextVs) + if error != nil return error + + trustedHeader = trustedState.SignedHeader.Header + trustedVs = trustedState.ValidatorSet + + // check for adjacent headers + if untrustedHeight == trustedHeight + 1 { + if trustedHeader.NextValidatorsHash != untrustedHeader.ValidatorsHash { + return ErrInvalidAdjacentHeaders + } + } else { + error = verifyCommitTrusting(trustedVs, untrustedCommit, trustThreshold) + if error != nil return error + } + + // verify the untrusted commit + return verifyCommitFull(untrustedVs, untrustedCommit) +} + +func validateHeaderAndVals(signedHeader SignedHeader, vs ValidatorSet, nextVs ValidatorSet) error { + if hash(nextVs) != signedHeader.Header.NextValidatorsHash or + hash(vs) != signedHeader.Header.ValidatorsHash or + !matchingCommit(signedHeader) { // commit corresponds to the header + return error + } +} + +func verifyCommitFull(vs ValidatorSet, commit Commit) error { + totalPower := vs.TotalVotingPower; + signed_power := votingPowerIn(signers(commit, vs), vs) + + // check the signers account for +2/3 of the voting power + if signed_power * 3 <= total_power * 2 return error + return nil +} + +func verifyCommitTrusting(vs ValidatorSet, commit Commit, trustLevel TrustThreshold) error { + totalPower := vs.TotalVotingPower; + signed_power := votingPowerIn(signers(commit, vs), vs) + + // check the signers account for more than max(1/3, trustLevel) of the voting power + if signed_power <= max(1/3, trustLevel) * totalPower return error + return nil +} + + +// return true if header is within its lite client trusted period; otherwise it returns false +func isWithinTrustedPeriod(header Header, + trustingPeriod Duration, + now Time) bool { + + return header.Time + trustedPeriod > now +} + +func fatalCheckSupportError(err) bool { + return err == ErrHeaderNotWithinTrustedPeriod or err == ErrInvalidAdjacentHeaders +} +``` + + + 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 @@ -140,10 +302,10 @@ i.e., it does not assume ensuring transitive trust relation between headers thro // where error captures the nature of the error. // Note that untrusted_h must have been verified by the caller, i.e. verify(untrusted_h) was successful. func CanTrust(trusted_h,untrusted_h,trustThreshold) error { - assume trusted_h.Header.Height < untrusted_h.header.Height + assert trusted_h.Header.Height < untrusted_h.header.Height th := trusted_h // th is trusted header - // untrustedHeader is a list of verified headers that have not passed CheckSupport() + // untrustedHeader is a list of (?) verified headers that have not passed CheckSupport() untrustedHeaders := [untrusted_h] while true { @@ -151,27 +313,27 @@ func CanTrust(trusted_h,untrusted_h,trustThreshold) error { // we assume here that iteration is done in the order of header heights err = CheckSupport(th,h,trustThreshold) if err == nil { + if !verify(h) { return ErrInvalidHeader(h) } th = h Store.Add(h) untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) if th == untrusted_h { return nil } } - if (err != ErrTooMuchChange) { return err } + if fatalCheckSupportError(err) { return err } } endHeight = min(untrustedHeaders) - foundPivot = false - while(!foundPivot) { - pivot := (th.Header.height + endHeight) / 2 + while true { + pivot := ceil((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 fatalCheckSupportError(err) return err if err == nil { + if !verify(hp) { return ErrInvalidHeader(hp) } th = hp - Store.Add(hp) - foundPivot = true + Store.Add(th) + break } untrustedHeaders.add(hp) endHeight = pivot @@ -179,6 +341,41 @@ func CanTrust(trusted_h,untrusted_h,trustThreshold) error { } return nil // this line should never be reached } + +func CheckSupport(trusted_h,untrusted_h,trustThreshold) error { + assert trusted_h.Header.Height < untrusted_h.header.Height and + trusted_h.Header.bfttime < untrusted_h.Header.bfttime and + untrusted_h.Header.bfttime < now + + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) + + // Although while executing the rest of CheckSupport function, trusted_h can expire 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 trusted_h can be trusted. Furthermore, CheckSupport function + // is not doing expensive operation (neither rpc nor signature verification), so it + // should execute fast. + + // check for adjacent headers + if untrusted_h.Header.height == trusted_h.Header.height + 1 { + if trusted_h.Header.NextV == untrusted_h.Header.V + return nil + return ErrInvalidAdjacentHeaders + } + + // total sum of voting power of validators in trusted_h.NextV + vp_all := totalVotingPower(trusted_h.Header.NextV) + + // check for non-adjacent headers + if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { + return nil + } + return ErrTooMuchChange +} + +func fatalCheckSupportError(err) bool { + return err == ErrHeaderNotWithinTrustedPeriod or err == ErrInvalidAdjacentHeaders ``` ```go @@ -210,29 +407,12 @@ func CanTrustBisection(trusted_h,untrusted_h,trustThreshold) error { } ``` -**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. - **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. ```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 - // return true if header is within its lite client trusted period; otherwise it returns false - func isWithinTrustedPeriod(h, now) bool { - 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 - // 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 votingPowerIn(signers(h.Commit),h.Header.V) > 2/3 * vp_all - } - // Captures skipping condition. trusted_h and untrusted_h have already passed basic validation // (function `verify`). // Returns nil in case untrusted_h can be trusted based on trusted_h, otherwise returns error. From 7130c2e68c125f49a3e705597739212dbd93146f Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Mon, 6 Jan 2020 18:30:59 +0100 Subject: [PATCH 11/20] Removing Store from API and providing end-to-end timing guarantees --- spec/consensus/light-client.md | 493 ++++++--------- spec/consensus/non-recursive-light-client.md | 612 +++++++++++++++++++ 2 files changed, 810 insertions(+), 295 deletions(-) create mode 100644 spec/consensus/non-recursive-light-client.md diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 883ac009..434c0b72 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -32,8 +32,8 @@ In the following, only the details of the data structures needed for this specif } type SignedHeader struct { - Header Header - Commit Commit // commit for the given header + Header Header + Commit Commit // commit for the given header } type ValidatorSet struct { @@ -42,8 +42,8 @@ In the following, only the details of the data structures needed for this specif } type Validator struct { - Address Address // validator address (we assume validator's addresses are unique) - VotingPower int64 // validator's voting power + Address Address // validator address (we assume validator's addresses are unique) + VotingPower int64 // validator's voting power } type TrustedState { @@ -56,7 +56,7 @@ In the following, only the details of the data structures needed for this specif For the purpose of this lite client specification, we assume that the Tendermint Full Node exposes the following functions over Tendermint RPC: ```go - // returns signed header: Header with Commit + // returns signed header: Header with Commit, for the given height func Commit(height int64) (SignedHeader, error) // returns validator set for the given height @@ -68,10 +68,15 @@ Furthermore, we assume the following auxiliary functions: // returns the validator set for the given validator hash func validators(validatorsHash []byte) ValidatorSet + // returns true if commit corresponds to the block data in the header; otherwise false + func matchingCommit(header Header, commit Commit) bool + // returns the set of validators from the given validator set that committed the block + // it does not assume signature verification func signers(commit Commit, validatorSet ValidatorSet) []Validator - // return the voting power the validators in v1 have according to their voting power in set V2 + // return the voting power the validators in v1 have according to their voting power in set v2 + // it assumes signature verification so it can be computationally expensive func votingPowerIn(v1 []Validator, v2 ValidatorSet) int64 // add this state as trusted to the store @@ -83,24 +88,26 @@ Furthermore, we assume the following auxiliary functions: func get(store Store, height int64) (TrustedState, error) ``` -### Tendermint Failure Model +### Failure Model -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`. +The lite client specification is defined with respect to the following failure model: 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`. *Assumption*: "correct" is defined w.r.t. realtime (some Newtonian global notion of time, i.e., wall time), while `Header.Time` corresponds to the [BFT time](bft-time.md). In this note, we assume that clocks of correct processes -are synchronized (for example using NTP), and therefore there is bounded clock drift between clocks and -BFT time. We can make this more precise eventually (incorporating clock drift, accuracy, precision, etc.). Right now, -we consider this assumption sufficient, as clock synchronization (under NTP) is in the order of milliseconds and -`TRUSTED_PERIOD` is in the order of weeks. +are synchronized (for example using NTP), and therefore there is bounded clock drift (CLOCK_DRIFT) between local clocks and +BFT time. More precisely, for every correct process p and every header (correctly generated by the Tendermint consensus) +time (BFT time) the following inequality holds: `Header.Time < now + CLOCK_DRIFT`. + +Furthermore, we assume that trust period is (several) order of magnitude bigger than clock drift (`TRUST_PERIOD >> CLOCK_DRIFT`), +as clock drift (using NTP) is in the order of milliseconds and `TRUSTED_PERIOD` is in the order of weeks. *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 the Tendermint Failure Model. Issues -like `counter-factual signing`, `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 1/3 (or more) faults, safety may be violated. +The specification in this document considers an implementation of the lite client under the Failure Model defined above. Issues +like `counter-factual slashing`, `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 1/3 (or more) faulty validators, 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 document on [Fork accountability](fork-accountability.md). @@ -114,98 +121,57 @@ Note that this function is not making external (RPC) calls to the full node; the based on the local (given) state. This function is supposed to be used by the IBC handlers. ```go -func VerifyAndUpdateSingle(untrustedSh SignedHeader, - untrustedVs ValidatorSet, - untrustedNextVs ValidatorSet, - trustThreshold TrustThreshold, - trustingPeriod Duration, - now Time, - store Store) error { +func VerifySingle(untrustedSh SignedHeader, + untrustedVs ValidatorSet, + untrustedNextVs ValidatorSet, + trustThreshold TrustThreshold, + trustingPeriod Duration, + clockDrift Duration, + now Time, + trustedState TrustedState) error { - // fetch the latest state and ensure it hasn't expired - trustedState, error = get(store, 0) - if error != nil return error + assert untrustedSh.Header.Time < now + clockDrift - trustedSh = trustedState.SignedHeader - if !isWithinTrustedPeriod(trustedSh.Header, trustingPeriod, now) { - return ErrHeaderNotWithinTrustedPeriod + trustedHeader = trustedState.SignedHeader.Header + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return (ErrHeaderNotWithinTrustedPeriod, nil) } + // we assume that time it takes to execute verifySingle function + // is several order of magnitudes smaller than trustingPeriod error = verifySingle( - trustedState, - untrustedSh, - untrustedVs, - untrustedNextVs, - trustThreshold) + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) - if error != nil return error + if error != nil return (error, nil) - // the untrusted header is now trusted. update the store + // the untrusted header is now trusted newTrustedState = TrustedState(untrustedSh, untrustedNextVs) - return add(store, newTrustedState) + return (nil, newTrustedState) } -``` - -**VerifyAndUpdateBisection.** The function `VerifyAndUpdateBisection` 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 VerifyAndUpdateBisection(height int64, - trustThreshold TrustThreshold, - trustingPeriod Duration, - now Time, - store Store) error { - - // fetch the latest state and ensure it hasn't expired - trustedState, error = get(store, 0) - if error != nil return error - - trustedSh = trustedState.SignedHeader - assert trustedSh.Header.Height < height - - if !isWithinTrustedPeriod(trustedSh.Header, trustingPeriod, now) { - return ErrHeaderNotWithinTrustedPeriod - } - - untrustedSh, error := Commit(height) - if error != nil return error - - untrustedH = untrustedSh.Header - - untrustedVs, error := Validators(untrustedH.Height) - if error != nil return error - - untrustedNextVs, error := Validators(untrustedH.Height + 1) - if error != nil return error - - error = verifySingle( - trustedState, - untrustedSh, - untrustedVs, - untrustedNextVs, - trustThreshold) - - if fatalCheckSupportError(error) return error +// return true if header is within its lite client trusted period; otherwise returns false +func isWithinTrustedPeriod(header Header, + trustingPeriod Duration, + now Time) bool { - if error == nil { - // the untrusted header is now trusted. update the store - newTrustedState = TrustedState(untrustedSh, untrustedNextVs) - return add(store, newTrustedState) - } + return header.Time + trustedPeriod > now +} +``` - // at this point in time we need to do bisection - pivotHeight := ceil((trustedSh.Header.Height + untrustedH.Height) / 2) +Note that in case `VerifyAndUpdateSingle` returns without an error (untrusted header +is successfully verified) then we have a guarantee that the transition of the trust +from `trustedState` to `newTrustedState` happened during the trusted period of +`trustedState.SignedHeader.Header`. - error = VerifyAndUpdateBisection(pivotHeight, trustThreshold, trustingPeriod, now, store) - if error != nil return error - error = VerifyAndUpdateBisection(untrustedH.Height, trustThreshold, trustingPeriod, now, store) - if error != nil return error - return nil -} -``` +**verifySingle.** The function `verifySingle` verifies a single untrusted header +against a given trusted state. It includes all validations and signature verification. +It is not publicly exposed since it does not check for header expiry (time constraints) +and hence it's possible to use it incorrectly. ```go func verifySingle(trustedState TrustedState, @@ -214,246 +180,185 @@ func verifySingle(trustedState TrustedState, untrustedNextVs ValidatorSet, trustThreshold TrustThreshold) error { - // ensure the new height is higher - untrustedHeight = untrustedSh.Header.Height - trustedHeight = trustedState.SignedHeader.Header.Height - assert untrustedHeight > trustedHeight - - // validate the untrusted header against its commit, vals, and next_vals - untrustedHeader = untrustedSh.Header - untrustedCommit = untrustedSh.Commit + untrustedHeader = untrustedSh.Header + untrustedCommit = untrustedSh.Commit - error = validateHeaderAndVals(untrustedSh, untrustedVs, untrustedNextVs) - if error != nil return error + trustedHeader = trustedState.SignedHeader.Header + trustedVs = trustedState.ValidatorSet - trustedHeader = trustedState.SignedHeader.Header - trustedVs = trustedState.ValidatorSet + assert trustedHeader.Height < untrustedHeader.Height AND + trustedHeader.Time < untrustedHeader.Time - // check for adjacent headers - if untrustedHeight == trustedHeight + 1 { - if trustedHeader.NextValidatorsHash != untrustedHeader.ValidatorsHash { - return ErrInvalidAdjacentHeaders - } - } else { - error = verifyCommitTrusting(trustedVs, untrustedCommit, trustThreshold) + // validate the untrusted header against its commit, vals, and next_vals + error = validateSignedHeaderAndVals(untrustedSh, untrustedVs, untrustedNextVs) if error != nil return error - } - // verify the untrusted commit - return verifyCommitFull(untrustedVs, untrustedCommit) -} + // check for adjacent headers + if untrustedHeader.Height == trustedHeader.Height + 1 { + if trustedHeader.NextValidatorsHash != untrustedHeader.ValidatorsHash { + return ErrInvalidAdjacentHeaders + } + } else { + error = verifyCommitTrusting(trustedVs, untrustedCommit, trustThreshold) + if error != nil return error + } -func validateHeaderAndVals(signedHeader SignedHeader, vs ValidatorSet, nextVs ValidatorSet) error { - if hash(nextVs) != signedHeader.Header.NextValidatorsHash or - hash(vs) != signedHeader.Header.ValidatorsHash or - !matchingCommit(signedHeader) { // commit corresponds to the header - return error - } + // verify the untrusted commit + return verifyCommitFull(untrustedVs, untrustedCommit) } -func verifyCommitFull(vs ValidatorSet, commit Commit) error { - totalPower := vs.TotalVotingPower; - signed_power := votingPowerIn(signers(commit, vs), vs) - - // check the signers account for +2/3 of the voting power - if signed_power * 3 <= total_power * 2 return error - return nil +// returns nil if header and validator sets are consistent; otherwise returns error +func validateSignedHeaderAndVals(signedHeader SignedHeader, vs ValidatorSet, nextVs ValidatorSet) error { + header = signedHeader.Header + if hash(nextVs) != header.NextValidatorsHash OR + hash(vs) != header.ValidatorsHash OR + !matchingCommit(header, signedHeader.Commit) { return error } + return nil } +// returns nil if at least single correst signer signed the commit; otherwise returns error func verifyCommitTrusting(vs ValidatorSet, commit Commit, trustLevel TrustThreshold) error { - totalPower := vs.TotalVotingPower; - signed_power := votingPowerIn(signers(commit, vs), vs) - // check the signers account for more than max(1/3, trustLevel) of the voting power - if signed_power <= max(1/3, trustLevel) * totalPower return error - return nil -} - - -// return true if header is within its lite client trusted period; otherwise it returns false -func isWithinTrustedPeriod(header Header, - trustingPeriod Duration, - now Time) bool { + totalPower := vs.TotalVotingPower + signedPower := votingPowerIn(signers(commit, vs), vs) - return header.Time + trustedPeriod > now + // check that the signers account for more than max(1/3, trustLevel) of the voting power + // this ensures that there is at least single correct validator in the set of signers + if signedPower < max(1/3, trustLevel) * totalPower return ErrInsufficientVotingPower + return nil } -func fatalCheckSupportError(err) bool { - return err == ErrHeaderNotWithinTrustedPeriod or err == ErrInvalidAdjacentHeaders +// returns nil if commit is signed by more than 2/3 of voting power of the given validator set +// return error otherwise +func verifyCommitFull(vs ValidatorSet, commit Commit) error { + totalPower := vs.TotalVotingPower; + signed_power := votingPowerIn(signers(commit, vs), vs) + + // check the signers account for +2/3 of the voting power + if signed_power * 3 <= total_power * 2 return ErrInvalidCommit + return nil } ``` +**VerifyHeaderAtHeight.** The function `VerifyHeaderAtHeight` captures high level +logic, i.e., application call to the lite client module to download and verify header +for some height. +```go +func VerifyHeaderAtHeight(untrustedHeight int64, + trustThreshold TrustThreshold, + trustingPeriod Duration, + clockDrift Duration, + trustedState TrustedState) (error, TrustedState)) { -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 -to understand but non-recursive version might be simpler to formally express and verify using TLA+/TLC. + trustedHeader := trustedState.SignedHeader.Header -Both implementations of `CanTrust` function are based on `CheckSupport` function that implements the skipping conditions under which we can trust a -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. + now := System.Time() + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return (ErrHeaderNotWithinTrustedPeriod, trustedState) + } + newTrustedState, err := VerifyAndUpdateBisection(trustedState, + untrustedHeight, + trustThreshold, + trustingPeriod, + clockDrift, + now) -```go -// return nil in case we can trust header untrusted_h based on header trusted_h; otherwise return error -// where error captures the nature of the error. -// Note that untrusted_h must have been verified by the caller, i.e. verify(untrusted_h) was successful. -func CanTrust(trusted_h,untrusted_h,trustThreshold) error { - assert trusted_h.Header.Height < untrusted_h.header.Height - - th := trusted_h // th is trusted header - // untrustedHeader is a list of (?) verified headers that have not passed CheckSupport() - untrustedHeaders := [untrusted_h] - - while true { - 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 { - if !verify(h) { return ErrInvalidHeader(h) } - th = h - Store.Add(h) - untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) - if th == untrusted_h { return nil } - } - if fatalCheckSupportError(err) { return err } - } + if err != nil return (err, trustedState) - endHeight = min(untrustedHeaders) - while true { - pivot := ceil((th.Header.height + endHeight) / 2) - hp := Commit(pivot) - // try to move trusted header forward to hp - err = CheckSupport(th,hp,trustThreshold) - if fatalCheckSupportError(err) return err - if err == nil { - if !verify(hp) { return ErrInvalidHeader(hp) } - th = hp - Store.Add(th) - break - } - untrustedHeaders.add(hp) - endHeight = pivot + now = System.Time() + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return (ErrHeaderNotWithinTrustedPeriod, trustedState) } - } - return nil // this line should never be reached -} - -func CheckSupport(trusted_h,untrusted_h,trustThreshold) error { - assert trusted_h.Header.Height < untrusted_h.header.Height and - trusted_h.Header.bfttime < untrusted_h.Header.bfttime and - untrusted_h.Header.bfttime < now - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) + return (nil, newTrustedState) +} +``` - // Although while executing the rest of CheckSupport function, trusted_h can expire 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 trusted_h can be trusted. Furthermore, CheckSupport function - // is not doing expensive operation (neither rpc nor signature verification), so it - // should execute fast. +Note that in case `VerifyAndUpdateSingle` returns without an error (untrusted header +is successfully verified) then we have a guarantee that the transition of the trust +from `trustedState` to `newTrustedState` happened during the trusted period of +`trustedState.SignedHeader.Header`. - // check for adjacent headers - if untrusted_h.Header.height == trusted_h.Header.height + 1 { - if trusted_h.Header.NextV == untrusted_h.Header.V - return nil - return ErrInvalidAdjacentHeaders - } +**VerifyAndUpdateBisection.** The function `VerifyAndUpdateBisection` implements +recursive logic for checking if it is possible building trust +relationship between trustedState and untrusted header at the given height over +finite set of (downloaded and verified) headers. - // total sum of voting power of validators in trusted_h.NextV - vp_all := totalVotingPower(trusted_h.Header.NextV) +```go +func VerifyAndUpdateBisection(trustedState TrustedState, + untrustedHeight int64, + trustThreshold TrustThreshold, + trustingPeriod Duration, + clockDrift Duration, + now Time) (error, TrustedState) { - // check for non-adjacent headers - if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { - return nil - } - return ErrTooMuchChange -} + trustedHeader = trustedState.SignedHeader.Header + assert trustedHeader.Height < untrustedHeight -func fatalCheckSupportError(err) bool { - return err == ErrHeaderNotWithinTrustedPeriod or err == ErrInvalidAdjacentHeaders -``` + untrustedSh, error := Commit(untrustedHeight) + if error != nil return (error, trustedState) -```go -func CanTrustBisection(trusted_h,untrusted_h,trustThreshold) error { - assume trusted_h.Header.Height < untrusted_h.header.Height + untrustedHeader = untrustedSh.Header + assert trustedHeader.Time < untrustedHeader.Time - err = CheckSupport(trusted_h,untrusted_h,trustThreshold) - if err == nil { - Store.Add(untrusted_h) - return nil - } - if err != ErrTooMuchChange return err - - pivot := (trusted_h.Header.height + untrusted_h.Header.height) / 2 - hp := Commit(pivot) - if !verify(hp) return ErrInvalidHeader(hp) - - err = CanTrustBisection(trusted_h,hp,trustThreshold) - if err == nil { - Store.Add(hp) - err2 = CanTrustBisection(hp,untrusted_h,trustThreshold) - if err2 == nil { - Store.Add(untrusted_h) - return nil - } - return err2 - } - return err -} -``` + // note that we pass now during the recursive calls. This is fine as + // all other untrusted headers we download during recursion will be + // for a smaller heights, and therefore should happen before. + assert untrustedHeader.Time < now + clockDrift -**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. -```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. + untrustedVs, error := Validators(untrustedHeight) + if error != nil return (error, trustedState) -```go - // Captures skipping condition. trusted_h and untrusted_h have already passed basic validation - // (function `verify`). - // Returns nil in case untrusted_h can be trusted based on trusted_h, otherwise returns error. - // ErrHeaderNotWithinTrustedPeriod is used when trusted_h 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(trusted_h,untrusted_h,trustThreshold) error { - assert trusted_h.Header.Height < untrusted_h.header.Height and - trusted_h.Header.bfttime < untrusted_h.Header.bfttime and - untrusted_h.Header.bfttime < now - - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) - - // Although while executing the rest of CheckSupport function, trusted_h can expire 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 trusted_h can be trusted. Furthermore, CheckSupport function - // is not doing expensive operation (neither rpc nor signature verification), so it - // should execute fast. + untrustedNextVs, error := Validators(untrustedHeight + 1) + if error != nil return (error, trustedState) - // check for adjacent headers - if untrusted_h.Header.height == trusted_h.Header.height + 1 { - if trusted_h.Header.NextV == untrusted_h.Header.V - return nil - return ErrInvalidAdjacentHeaders + error = verifySingle( + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) + + if fatalError(error) return (error, trustedState) + + if error == nil { + // the untrusted header is now trusted. + newTrustedState = TrustedState(untrustedSh, untrustedNextVs) + return (nil, newTrustedState) } - // total sum of voting power of validators in trusted_h.NextV - vp_all := totalVotingPower(trusted_h.Header.NextV) + // at this point in time we need to do bisection + pivotHeight := ceil((trustedHeader.Height + untrustedHeight) / 2) + + error, newTrustedState = VerifyAndUpdateBisection(trustedState, + pivotHeight, + trustThreshold, + trustingPeriod, + clockDrift, + now) + if error != nil return (error, trustedState) + + error, newTrustedState = verifyAndUpdateBisection(newTrustedState, + untrustedHeight, + trustThreshold, + trustingPeriod, + clockDrift, + now) + if error != nil return (error, trustedState) + return (nil, newTrustedState) +} - // check for non-adjacent headers - if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { - return nil - } - return ErrTooMuchChange - } +func fatalError(err) bool { + return err == ErrHeaderNotWithinTrustedPeriod OR + err == ErrInvalidAdjacentHeaders OR + err == ErrInvalidCommit +} ``` + ### 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 @@ -482,8 +387,6 @@ func Backwards(trusted_h,untrusted_h) error { } ``` - - 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 headers are detected). As detecting conflicting headers, its propagation through the network (by the gossip protocol) and execution of the fork accountability protocol on the chain takes time, the lite client logic assumes conservative value for trusted period. More precisely, in the context of lite client we always diff --git a/spec/consensus/non-recursive-light-client.md b/spec/consensus/non-recursive-light-client.md new file mode 100644 index 00000000..085f3f65 --- /dev/null +++ b/spec/consensus/non-recursive-light-client.md @@ -0,0 +1,612 @@ +# Lite client + +A lite client is a process that connects to Tendermint full node(s) and then tries to verify application +data using the Merkle proofs. + +## Problem statement + +We assume that the lite client knows a (base) header *inithead* it trusts (by social consensus or because +the lite client has decided to trust the header before). The goal is to check whether another header +*newhead* can be trusted based on the data in *inithead*. + +The correctness of the protocol is based on the assumption that *inithead* was generated by an instance of +Tendermint consensus. + +## Definitions + +### Data structures + +In the following, only the details of the data structures needed for this specification are given. + + ```go + type Header struct { + Height int64 + Time Time // the chain time when the header (block) was generated + + // hashes from the app output from the prev block + ValidatorsHash []byte // hash of the validators for the current block + NextValidatorsHash []byte // hash of the validators for the next block + + // hashes of block data + LastCommitHash []byte // hash of the commit from validators from the last block + } + + type SignedHeader struct { + Header Header + Commit Commit // commit for the given header + } + + type ValidatorSet struct { + Validators []Validator + TotalVotingPower int64 + } + + type Validator struct { + Address Address // validator address (we assume validator's addresses are unique) + VotingPower int64 // validator's voting power + } + + type TrustedState { + SignedHeader SignedHeader + ValidatorSet ValidatorSet + } + ``` + +### Functions + +For the purpose of this lite client specification, we assume that the Tendermint Full Node exposes the following functions over Tendermint RPC: +```go + // returns signed header: Header with Commit, for the given height + func Commit(height int64) (SignedHeader, error) + + // returns validator set for the given height + func Validators(height int64) (ValidatorSet, error) +``` + +Furthermore, we assume the following auxiliary functions: +```go + // returns the validator set for the given validator hash + func validators(validatorsHash []byte) ValidatorSet + + // returns true if commit corresponds to the block data in the header; otherwise false + func matchingCommit(header Header, commit Commit) bool + + // returns the set of validators from the given validator set that committed the block + // it does not assume signature verification + func signers(commit Commit, validatorSet ValidatorSet) []Validator + + // return the voting power the validators in v1 have according to their voting power in set v2 + // it assumes signature verification so it can be computationally expensive + func votingPowerIn(v1 []Validator, v2 ValidatorSet) int64 + + // add this state as trusted to the store + func add(store Store, trustedState TrustedState) error + + // retrieve the trusted state at given height if it exists (error = nil) + // return an error if there are no trusted state for the given height + // if height = 0, return the latest trusted state + func get(store Store, height int64) (TrustedState, error) +``` + + +**VerifyHeaderAtHeight.** TODO. +```go +func VerifyHeaderAtHeight(untrustedHeight int64, + trustThreshold TrustThreshold, + trustingPeriod Duration, + clockDrift Duration, + store Store) (error, (TrustedState, Time)) { + + now := System.Time() + initTrustedState, newTrustedState, err := VerifyAndUpdateNonRecursive(untrustedHeight, + trustThreshold, + trustingPeriod, + clockDrift, + now, + store Store) + + if err != nil return err + + now = System.Time() + if !isWithinTrustedPeriod(initTrustedState.SignedHeader.Header, trustingPeriod, now) { + return ErrHeaderNotWithinTrustedPeriod + } + return nil, (newTrustedState, now) +} +``` + +If we get some trustedState at time t (now = t), + + +**VerifyAndUpdateNonRecursive.** TODO. +```go +func VerifyAndUpdateNonRecursive(untrustedHeight int64, + trustThreshold TrustThreshold, + trustingPeriod Duration, + clockDrift Duration, + now Time, + store Store) error { + + // fetch the latest state and ensure it hasn't expired + trustedState, error = get(store, 0) + if error != nil return error + + trustedSh = trustedState.SignedHeader + trustedHeader = trustedSh.Header + assert trustedHeader.Height < untrustedHeight AND + trustedHeader.Time < now + + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return ErrHeaderNotWithinTrustedPeriod + } + + th := trustedHeader // th is trusted header + + untrustedSh, error := Commit(untrustedHeight) + if error != nil return error + + untrustedHeader = untrustedSh.Header + assert untrustedHeader.Time < now + clockDrift + + untrustedVs, error := Validators(untrustedHeight) + if error != nil return error + + untrustedNextVs, error := Validators(untrustedHeight + 1) + if error != nil return error + + // untrustedHeader is a list of headers that have not passed verifySingle + untrustedHeaders := [untrustedHeader] + + while true { + for h in untrustedHeaders { + // we assume here that iteration is done in the order of header heights + error = verifySingle( + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) + + if err == nil { + // the untrusted header is now trusted. update the store + trustedState = TrustedState(untrustedSh, untrustedNextVs) + add(store, trustedState) + + untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) + if trustedState.SignedHeader.Header == untrustedSh.Header { + return nil + } + } + if fatalError(err) { return err } + } + + endHeight = min(untrustedHeaders) + while true { + trustedSh = trustedState.SignedHeader + trustedHeader = trustedSh.Header + pivotHeight := ceil((trustedHeader.Height + endHeight) / 2) + + untrustedSh, error := Commit(pivotHeight) + if error != nil return error + + untrustedHeader = untrustedSh.Header + assert untrustedHeader.Time < now + clockDrift + + untrustedVs, error := Validators(untrustedHeight) + if error != nil return error + + untrustedNextVs, error := Validators(untrustedHeight + 1) + if error != nil return error + + error = verifySingle( + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) + + if fatalError(error) return error + + if err == nil { + trustedState = TrustedState(untrustedSh, untrustedNextVs) + add(store, trustedState) + break + } + + untrustedHeaders.add(untrustedHeader) + endHeight = pivot + } + } + return nil // this line should never be reached +} +``` + + + +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 +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 +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. + + +```go +// return nil in case we can trust header untrusted_h based on header trusted_h; otherwise return error +// where error captures the nature of the error. +// Note that untrusted_h must have been verified by the caller, i.e. verify(untrusted_h) was successful. +func CanTrust(trusted_h,untrusted_h,trustThreshold) error { + assert trusted_h.Header.Height < untrusted_h.header.Height + + th := trusted_h // th is trusted header + // untrustedHeader is a list of (?) verified headers that have not passed CheckSupport() + untrustedHeaders := [untrusted_h] + + while true { + 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 { + if !verify(h) { return ErrInvalidHeader(h) } + th = h + Store.Add(h) + untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) + if th == untrusted_h { return nil } + } + if fatalCheckSupportError(err) { return err } + } + + endHeight = min(untrustedHeaders) + while true { + pivot := ceil((th.Header.height + endHeight) / 2) + hp := Commit(pivot) + // try to move trusted header forward to hp + err = CheckSupport(th,hp,trustThreshold) + if fatalCheckSupportError(err) return err + if err == nil { + if !verify(hp) { return ErrInvalidHeader(hp) } + th = hp + Store.Add(th) + break + } + untrustedHeaders.add(hp) + endHeight = pivot + } + } + return nil // this line should never be reached +} + +func CheckSupport(trusted_h,untrusted_h,trustThreshold) error { + assert trusted_h.Header.Height < untrusted_h.header.Height and + trusted_h.Header.bfttime < untrusted_h.Header.bfttime and + untrusted_h.Header.bfttime < now + + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) + + // Although while executing the rest of CheckSupport function, trusted_h can expire 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 trusted_h can be trusted. Furthermore, CheckSupport function + // is not doing expensive operation (neither rpc nor signature verification), so it + // should execute fast. + + // check for adjacent headers + if untrusted_h.Header.height == trusted_h.Header.height + 1 { + if trusted_h.Header.NextV == untrusted_h.Header.V + return nil + return ErrInvalidAdjacentHeaders + } + + // total sum of voting power of validators in trusted_h.NextV + vp_all := totalVotingPower(trusted_h.Header.NextV) + + // check for non-adjacent headers + if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { + return nil + } + return ErrTooMuchChange +} + +func fatalCheckSupportError(err) bool { + return err == ErrHeaderNotWithinTrustedPeriod or err == ErrInvalidAdjacentHeaders +``` + +```go +func CanTrustBisection(trusted_h,untrusted_h,trustThreshold) error { + assume trusted_h.Header.Height < untrusted_h.header.Height + + err = CheckSupport(trusted_h,untrusted_h,trustThreshold) + if err == nil { + Store.Add(untrusted_h) + return nil + } + if err != ErrTooMuchChange return err + + pivot := (trusted_h.Header.height + untrusted_h.Header.height) / 2 + hp := Commit(pivot) + if !verify(hp) return ErrInvalidHeader(hp) + + err = CanTrustBisection(trusted_h,hp,trustThreshold) + if err == nil { + Store.Add(hp) + err2 = CanTrustBisection(hp,untrusted_h,trustThreshold) + if err2 == nil { + Store.Add(untrusted_h) + return nil + } + return err2 + } + return err +} +``` + +**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. +```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 + // Captures skipping condition. trusted_h and untrusted_h have already passed basic validation + // (function `verify`). + // Returns nil in case untrusted_h can be trusted based on trusted_h, otherwise returns error. + // ErrHeaderNotWithinTrustedPeriod is used when trusted_h 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(trusted_h,untrusted_h,trustThreshold) error { + assert trusted_h.Header.Height < untrusted_h.header.Height and + trusted_h.Header.bfttime < untrusted_h.Header.bfttime and + untrusted_h.Header.bfttime < now + + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) + + // Although while executing the rest of CheckSupport function, trusted_h can expire 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 trusted_h can be trusted. Furthermore, CheckSupport function + // is not doing expensive operation (neither rpc nor signature verification), so it + // should execute fast. + + // check for adjacent headers + if untrusted_h.Header.height == trusted_h.Header.height + 1 { + if trusted_h.Header.NextV == untrusted_h.Header.V + return nil + return ErrInvalidAdjacentHeaders + } + + // total sum of voting power of validators in trusted_h.NextV + vp_all := totalVotingPower(trusted_h.Header.NextV) + + // check for non-adjacent headers + if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { + return nil + } + return ErrTooMuchChange + } +``` + + +### 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. + +*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 +func Backwards(trusted_h,untrusted_h) error { + assert (untrusted_h.Header.height < trusted_h.Header.height) + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) + + old := trusted_h + for i := trusted_h.Header.height - 1; i > untrusted_h.Header.height; i-- { + new := Commit(i) + if (hash(new) != old.Header.hash) { + return ErrInvalidAdjacentHeaders + } + old := new + if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) + } + if hash(untrusted_h) != old.Header.hash return ErrInvalidAdjacentHeaders + return nil + } +``` + + + +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 +headers are detected). As detecting conflicting headers, its propagation through the network (by the gossip protocol) and execution of the fork accountability +protocol on the chain takes time, the lite client logic assumes conservative value for trusted period. More precisely, in the context of lite client we always +operate with a smaller trusted period that we call *lite client trusted period* (LITE_CLIENT_TRUSTED_PERIOD). If we assume that upper bound +for fork detection, propagation and processing on the chain is denoted with *fork procession period* (FORK_PROCESSING_PERIOD), then the following formula +holds: +```LITE_CLIENT_TRUSTED_PERIOD + FORK_PROCESSING_PERIOD < TRUSTED_PERIOD```, where TRUSTED_PERIOD comes from the Tendermint Failure Model. + + +*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: +- 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.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). + * 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 are talking to (as we haven't observed full node misbehavior in this case). + + + +## Context of this document + +In order to make sure that full nodes have the incentive to follow the protocol, we have to address the +following three Issues + +1) The lite client needs a method to verify headers it obtains from a full node it connects to according to trust assumptions -- this document. + +2) The lite client must be able to connect to other full nodes to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document (see #4215). + +3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- a future document (see #3840). + +The term "trusting" above indicates that the correctness of the protocol depends on +this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk +of trusting a corrupted/forged *inithead* is negligible. + +* 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. + +* 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. + + + * Validator fields. We will write a validator as a tuple *(v,p)* such that + + *v* is the identifier (we assume identifiers are unique in each validator set) + + *p* is its voting power + +### Definitions + +* *TRUSTED_PERIOD*: trusting period +* for realtime *t*, the predicate *correct(v,t)* is true if the validator *v* + follows the protocol until time *t* (we will see about recovery later). + + +### Tendermint Failure Model + +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, +\[ +\sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + TRUSTED_PERIOD)} p > +2/3 \sum_{(v,p) \in h.Header.NextV} p +\] + + +## Lite Client Trusting Spec + +The lite 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: + +- Lite Client Completeness: If header *h* was correctly generated by an instance of Tendermint consensus (and its age is less than the trusting period), then the lite client should eventually set *trust(h)* to true. + +- Lite Client Accuracy: If header *h* was *not generated* by an instance of Tendermint consensus, then the lite client should never set *trust(h)* to true. + +*Remark*: If in the course of the computation, the lite client obtains certainty that some headers were forged by adversaries (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. + +*Remark*: In Completeness we use "eventually", while in practice *trust(h)* should be set to true before *h.Header.bfttime + tp*. If not, the block cannot be trusted because it is too old. + +*Remark*: If a header *h* is marked with *trust(h)*, but it is too old (its bfttime is more than *tp* ago), then the lite client should set *trust(h)* to false again. + +*Assumption*: Initially, the lite client has a header *inithead* that it trusts correctly, that is, *inithead* was correctly generated by the Tendermint consensus. + +To reason about the correctness, we may prove the following invariant. + +*Verification Condition: Lite Client Invariant.* + For each lite client *l* and each header *h*: +if *l* has set *trust(h) = true*, + then validators that are correct until time *h.Header.bfttime + tp* have more than two thirds of the voting power in *h.Header.NextV*. + + Formally, + \[ + \sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > + 2/3 \sum_{(v,p) \in h.Header.NextV} p + \] + +*Remark.* To prove the invariant, we will have to prove that the lite client only trusts headers that were correctly generated by Tendermint consensus, then the formula above follows from the Tendermint failure model. + + +## High Level Solution + +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. + +1. **Uninterrupted sequence of proof.** If a block is appended to the chain, where the last block +is trusted (and properly committed by the old validator set in the next block), +and the new block contains a new validator set, the new block is trusted if the lite client knows all headers in the prefix. +Intuitively, a trusted validator set is assumed to only chose a new validator set that will obey the Tendermint Failure Model. + +2. **Trusting period.** Based on a trusted block *h*, and the lite client +invariant, which ensures the fault assumption during the trusting period, we can check whether at least one validator, that has been continuously correct from *h.Header.bfttime* until now, has signed *snh*. +If this is the case, similarly to above, the chosen validator set in *snh* does not violate the Tendermint Failure Model. + +3. **Bisection.** If a check according to the trusting period fails, the lite client can try to obtain a header *hp* whose height lies between *h* and *snh* in order to check whether *h* can be used to get trust for *hp*, and *hp* can be used to get trust for *snh*. If this is the case we can trust *snh*; if not, we may continue recursively. + +## How to use it + +We consider the following use case: + the lite client wants to verify a header for some given height *k*. Thus: + - it requests the signed header for height *k* from a full node + - it tries to verify this header with the methods described here. + +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*. + - 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 + +**Observation 1.** If *h.Header.bfttime + tp > now*, we trust the old +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 less than 1/3 of them are faulty (more precisely, the faulty ones have less than 1/3 of the total voting power). + + + +*Correctness arguments* + +Towards Lite Client Accuracy: +- 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: +- 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 *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. + + + + +*Correctness arguments (sketch)* + +Lite Client Accuracy: +- 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. +- Thus we have a sequence of headers that all satisfied the CheckSupport +- again a contradiction + +Lite Client Completeness: + +This is only ensured if upon *Commit(pivot)* the lite client is always provided with a correctly generated header. + +*Stalling* + +With CanTrustBisection, a faulty full node could stall a lite client by creating a long sequence of headers that are queried one-by-one by the lite client and look OK, before the lite client eventually detects a problem. There are several ways to address this: +* Each call to ```Commit``` could be issued to a different full node +* Instead of querying header by header, the lite client tells a full node which header it trusts, and the height of the header it needs. The full node responds with the header along with a proof consisting of intermediate headers that the light client can use to verify. Roughly, Bisection would then be executed at the full node. +* We may set a timeout how long bisection may take. + + + + + From 146e251892977df4ae5af57971a7e123caf55d10 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Wed, 8 Jan 2020 17:49:32 +0100 Subject: [PATCH 12/20] Address reviewer comment's. Intermediate commit --- spec/consensus/light-client.md | 240 +++++++++++++++++---------------- 1 file changed, 125 insertions(+), 115 deletions(-) diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index 434c0b72..8c07dfac 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -20,15 +20,10 @@ In the following, only the details of the data structures needed for this specif ```go type Header struct { - Height int64 - Time Time // the chain time when the header (block) was generated - - // hashes from the app output from the prev block - ValidatorsHash []byte // hash of the validators for the current block - NextValidatorsHash []byte // hash of the validators for the next block - - // hashes of block data - LastCommitHash []byte // hash of the commit from validators from the last block + Height int64 + Time Time // the chain time when the header (block) was generated + ValidatorsHash []byte // hash of the validators for the current block + NextValidatorsHash []byte // hash of the validators for the next block } type SignedHeader struct { @@ -65,58 +60,67 @@ For the purpose of this lite client specification, we assume that the Tendermint Furthermore, we assume the following auxiliary functions: ```go - // returns the validator set for the given validator hash - func validators(validatorsHash []byte) ValidatorSet - - // returns true if commit corresponds to the block data in the header; otherwise false + // returns true if the commit is for the header, ie. if it contains + // the correct hash of the header; otherwise false func matchingCommit(header Header, commit Commit) bool - // returns the set of validators from the given validator set that committed the block - // it does not assume signature verification + // returns the set of validators from the given validator set that + // committed the block (that correctly signed the block) + // it assumes signature verification so it can be computationally expensive func signers(commit Commit, validatorSet ValidatorSet) []Validator // return the voting power the validators in v1 have according to their voting power in set v2 - // it assumes signature verification so it can be computationally expensive + // it does not assume signature verification func votingPowerIn(v1 []Validator, v2 ValidatorSet) int64 - // add this state as trusted to the store - func add(store Store, trustedState TrustedState) error - - // retrieve the trusted state at given height if it exists (error = nil) - // return an error if there are no trusted state for the given height - // if height = 0, return the latest trusted state - func get(store Store, height int64) (TrustedState, error) + // returns hash of the given validator set + func hash(v2 ValidatorSet) []byte ``` ### Failure Model -The lite client specification is defined with respect to the following failure model: 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`. +For the purpose of model definitions we assume that there exists a function +`validators` that returns the corresponding validator set for the given hash. +The lite client specification is defined with respect to the following failure model: + +Given a known bound `TRUSTED_PERIOD`, and a block `b` with header `h` generated at time `Time` +(i.e. `h.Time = Time`), 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`. *Assumption*: "correct" is defined w.r.t. realtime (some Newtonian global notion of time, i.e., wall time), while `Header.Time` corresponds to the [BFT time](bft-time.md). In this note, we assume that clocks of correct processes -are synchronized (for example using NTP), and therefore there is bounded clock drift (CLOCK_DRIFT) between local clocks and -BFT time. More precisely, for every correct process p and every header (correctly generated by the Tendermint consensus) -time (BFT time) the following inequality holds: `Header.Time < now + CLOCK_DRIFT`. - -Furthermore, we assume that trust period is (several) order of magnitude bigger than clock drift (`TRUST_PERIOD >> CLOCK_DRIFT`), -as clock drift (using NTP) is in the order of milliseconds and `TRUSTED_PERIOD` is in the order of weeks. +are synchronized (for example using NTP), and therefore there is bounded clock drift (`CLOCK_DRIFT`) between local clocks and +BFT time. More precisely, for every correct lite client process and every `header.Time` (i.e. BFT Time, for a header correctly +generated by the Tendermint consensus), the following inequality holds: `Header.Time < now + CLOCK_DRIFT`, +where `now` corresponds to the system clock at the lite client process. + +Furthermore, we assume that `TRUSTED_PERIOD` is (several) order of magnitude bigger than `CLOCK_DRIFT` (`TRUSTED_PERIOD >> CLOCK_DRIFT`), +as `CLOCK_DRIFT` (using NTP) is in the order of milliseconds and `TRUSTED_PERIOD` is in the order of weeks. + +We expect a lite client process defined in this document to be used in the context in which there is some +larger period during which misbehaving validators can be detected and punished (we normally refer to it as `PUNISHMENT_PERIOD`). +Furthermore, we assume that `TRUSTED_PERIOD < PUNISHMENT_PERIOD` and that they are normally of the same order of magnitude, for example +`TRUSTED_PERIOD = PUNISHMENT_PERIOD / 2`. Note that `PUNISHMENT_PERIOD` is often referred to as an +unbonding period due to the "bonding" mechanism in modern proof of stake systems. + +The specification in this document considers an implementation of the lite client under the Failure Model defined above. +Mechanisms like `fork accountability` and `evidence submission` are defined in the context of `PUNISHMENT_PERIOD` and +they incentivize validators to follow the protocol specification defined in this document. If they don't, +and we have 1/3 (or more) faulty validators, 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 document on [Fork accountability](fork-accountability.md). *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 the Failure Model defined above. Issues -like `counter-factual slashing`, `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 1/3 (or more) faulty validators, 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 document on [Fork accountability](fork-accountability.md). - ### Functions -**VerifyAndUpdateSingle.** The function `VerifyAndUpdateSingle` attempts to update -the (trusted) store with the given untrusted header and the corresponding validator sets. -It ensures that the last trusted header from the store hasn't expired yet (it is still within its trusted period), -and that the untrusted header can be verified using the latest trusted state from the store. +In the functions below we will be using `trustThreshold` as a parameter. For simplicity +we assume that `trustThreshold` is a float between 1/3 and 2/3 and we will not be checking it +in the pseudo-code. + +**VerifySingle.** The function `VerifySingle` attempts to validate given untrusted header and the corresponding validator sets +based on a given trusted state. It ensures that the trusted state is still within its trusted period, +and that the untrusted header is within assume `clockDrift` bound of the passed time `now`. Note that this function is not making external (RPC) calls to the full node; the whole logic is based on the local (given) state. This function is supposed to be used by the IBC handlers. @@ -124,33 +128,35 @@ based on the local (given) state. This function is supposed to be used by the IB func VerifySingle(untrustedSh SignedHeader, untrustedVs ValidatorSet, untrustedNextVs ValidatorSet, - trustThreshold TrustThreshold, + trustedState TrustedState, + trustThreshold float, trustingPeriod Duration, clockDrift Duration, - now Time, - trustedState TrustedState) error { + now Time) (TrustedState, error) { - assert untrustedSh.Header.Time < now + clockDrift + if untrustedSh.Header.Time > now + clockDrift { + return (trustedState, ErrInvalidHeaderTime) + } trustedHeader = trustedState.SignedHeader.Header if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { - return (ErrHeaderNotWithinTrustedPeriod, nil) + return (state, ErrHeaderNotWithinTrustedPeriod) } // we assume that time it takes to execute verifySingle function // is several order of magnitudes smaller than trustingPeriod error = verifySingle( - trustedState, - untrustedSh, - untrustedVs, - untrustedNextVs, - trustThreshold) + trustedState, + untrustedSh, + untrustedVs, + untrustedNextVs, + trustThreshold) - if error != nil return (error, nil) + if error != nil return (state, error) // the untrusted header is now trusted newTrustedState = TrustedState(untrustedSh, untrustedNextVs) - return (nil, newTrustedState) + return (newTrustedState, nil) } // return true if header is within its lite client trusted period; otherwise returns false @@ -162,7 +168,7 @@ func isWithinTrustedPeriod(header Header, } ``` -Note that in case `VerifyAndUpdateSingle` returns without an error (untrusted header +Note that in case `VerifySingle` returns without an error (untrusted header is successfully verified) then we have a guarantee that the transition of the trust from `trustedState` to `newTrustedState` happened during the trusted period of `trustedState.SignedHeader.Header`. @@ -178,7 +184,7 @@ func verifySingle(trustedState TrustedState, untrustedSh SignedHeader, untrustedVs ValidatorSet, untrustedNextVs ValidatorSet, - trustThreshold TrustThreshold) error { + trustThreshold float) error { untrustedHeader = untrustedSh.Header untrustedCommit = untrustedSh.Commit @@ -186,8 +192,8 @@ func verifySingle(trustedState TrustedState, trustedHeader = trustedState.SignedHeader.Header trustedVs = trustedState.ValidatorSet - assert trustedHeader.Height < untrustedHeader.Height AND - trustedHeader.Time < untrustedHeader.Time + if trustedHeader.Height >= untrustedHeader.Height return ErrNonIncreasingHeight + if trustedHeader.Time >= untrustedHeader.Time return ErrNonIncreasingTime // validate the untrusted header against its commit, vals, and next_vals error = validateSignedHeaderAndVals(untrustedSh, untrustedVs, untrustedNextVs) @@ -199,7 +205,7 @@ func verifySingle(trustedState TrustedState, return ErrInvalidAdjacentHeaders } } else { - error = verifyCommitTrusting(trustedVs, untrustedCommit, trustThreshold) + error = verifyCommitTrusting(trustedVs, untrustedCommit, untrustedVs, trustThreshold) if error != nil return error } @@ -210,17 +216,20 @@ func verifySingle(trustedState TrustedState, // returns nil if header and validator sets are consistent; otherwise returns error func validateSignedHeaderAndVals(signedHeader SignedHeader, vs ValidatorSet, nextVs ValidatorSet) error { header = signedHeader.Header - if hash(nextVs) != header.NextValidatorsHash OR - hash(vs) != header.ValidatorsHash OR - !matchingCommit(header, signedHeader.Commit) { return error } + if hash(vs) != header.ValidatorsHash return ErrInvalidValidatorSet + if hash(nextVs) != header.NextValidatorsHash return ErrInvalidNextValidatorSet + if !matchingCommit(header, signedHeader.Commit) return ErrInvalidCommitValue return nil } // returns nil if at least single correst signer signed the commit; otherwise returns error -func verifyCommitTrusting(vs ValidatorSet, commit Commit, trustLevel TrustThreshold) error { +func verifyCommitTrusting(trustedVs ValidatorSet, + commit Commit, + untrustedVs ValidatorSet, + trustLevel float) error { - totalPower := vs.TotalVotingPower - signedPower := votingPowerIn(signers(commit, vs), vs) + totalPower := trustedVs.TotalVotingPower + signedPower := votingPowerIn(signers(commit, untrustedVs), trustedVs) // check that the signers account for more than max(1/3, trustLevel) of the voting power // this ensures that there is at least single correct validator in the set of signers @@ -232,10 +241,10 @@ func verifyCommitTrusting(vs ValidatorSet, commit Commit, trustLevel TrustThresh // return error otherwise func verifyCommitFull(vs ValidatorSet, commit Commit) error { totalPower := vs.TotalVotingPower; - signed_power := votingPowerIn(signers(commit, vs), vs) + signedPower := votingPowerIn(signers(commit, vs), vs) // check the signers account for +2/3 of the voting power - if signed_power * 3 <= total_power * 2 return ErrInvalidCommit + if signedPower * 3 <= totalPower * 2 return ErrInvalidCommit return nil } ``` @@ -246,73 +255,71 @@ for some height. ```go func VerifyHeaderAtHeight(untrustedHeight int64, - trustThreshold TrustThreshold, + trustedState TrustedState, + trustThreshold float, trustingPeriod Duration, - clockDrift Duration, - trustedState TrustedState) (error, TrustedState)) { + clockDrift Duration) (TrustedState, error)) { trustedHeader := trustedState.SignedHeader.Header now := System.Time() if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { - return (ErrHeaderNotWithinTrustedPeriod, trustedState) + return (trustedState, ErrHeaderNotWithinTrustedPeriod) } - newTrustedState, err := VerifyAndUpdateBisection(trustedState, - untrustedHeight, - trustThreshold, - trustingPeriod, - clockDrift, - now) + newTrustedState, err := VerifyBisection(untrustedHeight, + trustedState, + trustThreshold, + trustingPeriod, + clockDrift, + now) - if err != nil return (err, trustedState) + if err != nil return (trustedState, err) now = System.Time() if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { - return (ErrHeaderNotWithinTrustedPeriod, trustedState) + return (trustedState, ErrHeaderNotWithinTrustedPeriod) } - return (nil, newTrustedState) + return (newTrustedState, err) } ``` -Note that in case `VerifyAndUpdateSingle` returns without an error (untrusted header +Note that in case `VerifyHeaderAtHeight` returns without an error (untrusted header is successfully verified) then we have a guarantee that the transition of the trust from `trustedState` to `newTrustedState` happened during the trusted period of `trustedState.SignedHeader.Header`. -**VerifyAndUpdateBisection.** The function `VerifyAndUpdateBisection` implements +**VerifyBisection.** The function `VerifyBisection` implements recursive logic for checking if it is possible building trust -relationship between trustedState and untrusted header at the given height over +relationship between `trustedState` and untrusted header at the given height over finite set of (downloaded and verified) headers. ```go -func VerifyAndUpdateBisection(trustedState TrustedState, - untrustedHeight int64, - trustThreshold TrustThreshold, - trustingPeriod Duration, - clockDrift Duration, - now Time) (error, TrustedState) { - - trustedHeader = trustedState.SignedHeader.Header - assert trustedHeader.Height < untrustedHeight +func VerifyBisection(untrustedHeight int64, + trustedState TrustedState, + trustThreshold float, + trustingPeriod Duration, + clockDrift Duration, + now Time) (TrustedState, error) { untrustedSh, error := Commit(untrustedHeight) - if error != nil return (error, trustedState) + if error != nil return (trustedState, ErrRequestFailed) untrustedHeader = untrustedSh.Header - assert trustedHeader.Time < untrustedHeader.Time // note that we pass now during the recursive calls. This is fine as // all other untrusted headers we download during recursion will be // for a smaller heights, and therefore should happen before. - assert untrustedHeader.Time < now + clockDrift + if untrustedHeader.Time > now + clockDrift { + return (trustedState, ErrInvalidHeaderTime) + } untrustedVs, error := Validators(untrustedHeight) - if error != nil return (error, trustedState) + if error != nil return (trustedState, ErrRequestFailed) untrustedNextVs, error := Validators(untrustedHeight + 1) - if error != nil return (error, trustedState) + if error != nil return (trustedState, ErrRequestFailed) error = verifySingle( trustedState, @@ -321,38 +328,41 @@ func VerifyAndUpdateBisection(trustedState TrustedState, untrustedNextVs, trustThreshold) - if fatalError(error) return (error, trustedState) + if fatalError(error) return (trustedState, error) if error == nil { // the untrusted header is now trusted. newTrustedState = TrustedState(untrustedSh, untrustedNextVs) - return (nil, newTrustedState) + return (newTrustedState, nil) } // at this point in time we need to do bisection pivotHeight := ceil((trustedHeader.Height + untrustedHeight) / 2) - error, newTrustedState = VerifyAndUpdateBisection(trustedState, - pivotHeight, - trustThreshold, - trustingPeriod, - clockDrift, - now) - if error != nil return (error, trustedState) - - error, newTrustedState = verifyAndUpdateBisection(newTrustedState, - untrustedHeight, - trustThreshold, - trustingPeriod, - clockDrift, - now) - if error != nil return (error, trustedState) - return (nil, newTrustedState) + error, newTrustedState = VerifyBisection(pivotHeight, + trustedState, + trustThreshold, + trustingPeriod, + clockDrift, + now) + if error != nil return (newTrustedState, error) + + return VerifyBisection(untrustedHeight, + newTrustedState, + trustThreshold, + trustingPeriod, + clockDrift, + now) } func fatalError(err) bool { return err == ErrHeaderNotWithinTrustedPeriod OR err == ErrInvalidAdjacentHeaders OR + err == ErrNonIncreasingHeight OR + err == ErrNonIncreasingTime OR + err == ErrInvalidValidatorSet OR + err == ErrInvalidNextValidatorSet OR + err == ErrInvalidCommitValue OR err == ErrInvalidCommit } ``` From f26eb4ee89234cd5ad85bb316566f109463556bd Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 22 Jan 2020 12:55:31 -0800 Subject: [PATCH 13/20] light client dir and readmes --- spec/consensus/light/README.md | 49 +++++++++++++++++++ .../accountability.md} | 0 .../verification-non-recursive.md} | 0 .../verification.md} | 0 spec/consensus/readme.md | 24 +++++++++ 5 files changed, 73 insertions(+) create mode 100644 spec/consensus/light/README.md rename spec/consensus/{fork-accountability.md => light/accountability.md} (100%) rename spec/consensus/{non-recursive-light-client.md => light/verification-non-recursive.md} (100%) rename spec/consensus/{light-client.md => light/verification.md} (100%) diff --git a/spec/consensus/light/README.md b/spec/consensus/light/README.md new file mode 100644 index 00000000..c4587505 --- /dev/null +++ b/spec/consensus/light/README.md @@ -0,0 +1,49 @@ +# Tendermint Light Client Protocol + +## Contents + +- [Motivation](#motivation) +- [Structure](#structure) +- [Core Verification](./verification.md) +- [Fork Detection](./detection.md) +- [Fork Accountability](./accountability.md) + +## Motivation + +The Tendermint Light Client is motivated by the need for a light weight protocol +to sync with a Tendermint blockchain, with the least processing necessary to +securely verify a recent state. The protocol consists +primarily of checking hashes and verifying Tendermint commit signatures to +update trusted validator sets and committed block headers from the chain. + +Motivating use cases include: + +- Light Node: a daemon that syncs a blockchain to the latest committed header by making RPC requests to full nodes. +- State Sync: a reactor that syncs a blockchain to a recent committed state by making P2P requests to full nodes. +- IBC Client: an ABCI application library that syncs a blockchain to a recent committed header by receiving proof-carrying +transactions from "IBC relayers", who make RPC requests to full nodes on behalf of the IBC clients. + +## Structure + +The Tendermint Light Client consists of three primary components: + +- [Core Verification](./verification.md): verifying hashes, signatures, and validator set changes +- [Fork Detection](./detection.md): talking to multiple peers to detect byzantine behaviour +- [Fork Accountability](./accountability.md): analyzing byzantine behaviour to hold validators accountable. + +While every light client must perform core verification and fork detection +to achieve their prescribed security level, fork accountability is expected to +be done by full nodes and validators, and is thus more accurately a component of +the full node consensus protocol, though it is included here since it is +primarily concerned with providing security to light clients. + +Light clients are fundamentally synchronous protocols, +where security is grounded ultimately in the interval during which a validator can be punished +for byzantine behaviour. We assume here that such intervals have fixed and known minima +referred to commonly as a blockchain's Unbonding Period. + +A secure light client must guarantee that all three components - +core verification, fork detection, and fork accountability - +each with their own synchrony assumptions and fault model, can execute +sequentially and to completion within the given Unbonding Period. + diff --git a/spec/consensus/fork-accountability.md b/spec/consensus/light/accountability.md similarity index 100% rename from spec/consensus/fork-accountability.md rename to spec/consensus/light/accountability.md diff --git a/spec/consensus/non-recursive-light-client.md b/spec/consensus/light/verification-non-recursive.md similarity index 100% rename from spec/consensus/non-recursive-light-client.md rename to spec/consensus/light/verification-non-recursive.md diff --git a/spec/consensus/light-client.md b/spec/consensus/light/verification.md similarity index 100% rename from spec/consensus/light-client.md rename to spec/consensus/light/verification.md diff --git a/spec/consensus/readme.md b/spec/consensus/readme.md index 0ffc4b0b..32d5579b 100644 --- a/spec/consensus/readme.md +++ b/spec/consensus/readme.md @@ -3,3 +3,27 @@ cards: true --- # Consensus + +Specification of the Tendermint consensus protocol. + +## Contents + +- [Consensus Paper](./consensus-paper) - Latex paper on + [arxiv](https://arxiv.org/abs/1807.04938) describing the + core Tendermint consensus state machine with proofs of safety and termination. +- [BFT Time](./bft-time.md) - How the timestamp in a Tendermint + block header is computed in a Byzantine Fault Tolerant manner +- [Creating Proposal](./creating-proposal.md) - How a proposer + creates a block proposal for consensus +- [Light Client Protocol](./light) - A protocol for light weight consensus + verification and syncing to the latest state +- [Signing](./signing.md) - Rules for cryptographic signatures + produced by validators. +- [Write Ahead Log](./wal.md) - Write ahead log used by the + consensus state machine to recover from crashes. + +The protocol used to gossip consensus messages between peers, which is critical +for liveness, is described in the [reactors section](/spec/reactors/consensus). + +There is also a [stale markdown description](consensus.md) of the consensus state machine +(TODO update this). From eb9e1f961ce581d404f457d35a8df1f1d8f6e84f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 22 Jan 2020 13:06:49 -0800 Subject: [PATCH 14/20] titles --- spec/consensus/light/README.md | 3 +++ spec/consensus/light/accountability.md | 2 +- spec/consensus/light/verification.md | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/consensus/light/README.md b/spec/consensus/light/README.md index c4587505..7a4d8840 100644 --- a/spec/consensus/light/README.md +++ b/spec/consensus/light/README.md @@ -1,5 +1,8 @@ # Tendermint Light Client Protocol +NOTE: This specification is under heavy development and is not yet complete nor +accurate. + ## Contents - [Motivation](#motivation) diff --git a/spec/consensus/light/accountability.md b/spec/consensus/light/accountability.md index 2eb662ae..b605018d 100644 --- a/spec/consensus/light/accountability.md +++ b/spec/consensus/light/accountability.md @@ -1,4 +1,4 @@ -# Fork accountability -- Problem statement and attacks +# Fork accountability ## Problem Statement diff --git a/spec/consensus/light/verification.md b/spec/consensus/light/verification.md index 8c07dfac..66da23f1 100644 --- a/spec/consensus/light/verification.md +++ b/spec/consensus/light/verification.md @@ -1,4 +1,4 @@ -# Lite client +# Core Verification A lite client is a process that connects to Tendermint full node(s) and then tries to verify application data using the Merkle proofs. From e342c21336ad9e0dda0baabeff135c9bbfc362b0 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 22 Jan 2020 13:10:08 -0800 Subject: [PATCH 15/20] add redirects --- spec/consensus/fork-accountability.md | 3 +++ spec/consensus/light-client.md | 3 +++ spec/consensus/non-recursive-light-client.md | 3 +++ 3 files changed, 9 insertions(+) create mode 100644 spec/consensus/fork-accountability.md create mode 100644 spec/consensus/light-client.md create mode 100644 spec/consensus/non-recursive-light-client.md diff --git a/spec/consensus/fork-accountability.md b/spec/consensus/fork-accountability.md new file mode 100644 index 00000000..7509819d --- /dev/null +++ b/spec/consensus/fork-accountability.md @@ -0,0 +1,3 @@ +# Fork Accountability - MOVED! + +Fork Accountability has moved to [light](./light). diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md new file mode 100644 index 00000000..db20d49b --- /dev/null +++ b/spec/consensus/light-client.md @@ -0,0 +1,3 @@ +# Light Client - MOVED! + +Light Client has moved to [light](./light). diff --git a/spec/consensus/non-recursive-light-client.md b/spec/consensus/non-recursive-light-client.md new file mode 100644 index 00000000..b95815f4 --- /dev/null +++ b/spec/consensus/non-recursive-light-client.md @@ -0,0 +1,3 @@ +# Non Recursive Verification - MOVED! + +Non Recursive verification has moved to [light](./light). From 035838901e5031033a69c33d34aa5229f473d8b4 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 22 Jan 2020 13:17:16 -0800 Subject: [PATCH 16/20] add diagram --- spec/consensus/light/README.md | 16 +++++++++++++++- .../consensus/light/assets/light-node-image.png | Bin 0 -> 31450 bytes 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 spec/consensus/light/assets/light-node-image.png diff --git a/spec/consensus/light/README.md b/spec/consensus/light/README.md index 7a4d8840..79f04e9d 100644 --- a/spec/consensus/light/README.md +++ b/spec/consensus/light/README.md @@ -28,6 +28,8 @@ transactions from "IBC relayers", who make RPC requests to full nodes on behalf ## Structure +### Components + The Tendermint Light Client consists of three primary components: - [Core Verification](./verification.md): verifying hashes, signatures, and validator set changes @@ -40,8 +42,17 @@ be done by full nodes and validators, and is thus more accurately a component of the full node consensus protocol, though it is included here since it is primarily concerned with providing security to light clients. +A schematic of the core verification and fork detection components in +a Light Node are depicted below. The schematic is quite similar for other use cases. +Note fork accountability is not depicted, as it is the responsibility of the +full nodes. + +![Light Client Diagram](assets/light-node-image.png). + +### Synchrony + Light clients are fundamentally synchronous protocols, -where security is grounded ultimately in the interval during which a validator can be punished +where security is restricted by the interval during which a validator can be punished for byzantine behaviour. We assume here that such intervals have fixed and known minima referred to commonly as a blockchain's Unbonding Period. @@ -50,3 +61,6 @@ core verification, fork detection, and fork accountability - each with their own synchrony assumptions and fault model, can execute sequentially and to completion within the given Unbonding Period. +TODO: define all the synchrony parameters used in the protocol and their +relation to the Unbonding Period. + diff --git a/spec/consensus/light/assets/light-node-image.png b/spec/consensus/light/assets/light-node-image.png new file mode 100644 index 0000000000000000000000000000000000000000..24d80d71de3935459fba1cd1cff00601dbc41800 GIT binary patch literal 31450 zcmeFYXEfbW(>O{55l4yMIch{ZdMBI+q9sa1^xk{#{SbnKL@xDwTK|#ThmwTm(f`SeP{?eWR z11RT}G`GON$L3N>QYa|pG1xbT=s=&&SWZ<51;vXQ1tlN^1?37D3fM$JafP6uY#X4U zh$Nw)kUFH*zY_x{9vLafzCyVVi|KCxdRPu}TFxjaoD2_ts2)Eg-GD(%7kMQa%oPwN z3Fp(juWc(RD4qlIucXvHX7-vIhHe&`llUJ$4v(yM3Ubm3fGg*`Y;!~#AVGgrhsugR zDlA>{TsodE>Vj|?23waSdE=TQS27B&3tX40m6PE{SBjW;SNmpZunS^+DMC7Q(QG%A zU3}}^ay;1I&}buh=kxp22@@q?NXLZ~1*P=^gb}5ch>QTm;3)_T1%eKHjFR*S`Upia z5R8hF+X_cPQ8SQ331IzSu>bcJ5;XE93yI*ft2NVdBmBD06DVsS2l;Z9pqt$%n`Q0R z5$}LOMxeNwpony&t6l%GZxnP407NfS+>9D4B+a>ylFVU1gS6DQ;%S0~1`&#Ud`fX# z-x=dV(AwYz1oSkR;dMVHoAOCKMU>k+KEIY1dIG{)f7=Z;`7kcIz>bJjBBa6!3jntf zZC9Sp_jlGcGx@%u!Jc2kBn9xF)Vz0F8rnR(G}c^}6T$^y^}NL{x3F=axpX|#FF=$j z4e-um;tU3Y4P`r(+yuggIvPyvD=)ZR%I&=Lg4X=hiS{x-lYz0WgTRKQtDq!v-e#o` z`CM6#!~xkcnafkcbOtO2Lrn;yP3{Z7p@2&fyj*o9cw25@loc9}cJyjRte82wV76Rxkk zEX6&KxIJPfk%=v%BqR8(N~%Jr9jD{t?e0n$^S4i-i=k2{JY!~F$imiY$y|V zY_{>q+s~>;cH!>1cjapN+iVdb;%)FU3`c6kL@ukBMG+UjVx_C3_9vU-p398tqFo1o z4TU>ywk(oqD6hZ^P86SSxTl;26cH6pZPpM*_8)KR%SCrKE?qlF(TF2Us8}QdU+5Jy zB(&lHlZ-p-DjTWGl_MhNlfKwf{X@?v3ZKlOLfh$IQQj0MN#4F8mDM}bbIgjv#XB9x z69QC$KE5-Vm~>a|i*fb2ZZESwIL|;XNk!DxtA*c1{gqd1mMOOs<1QL3WO~)MVk!EK zcS!7Y7(7NSm5$&HAgs8Zi|svoMQ{)f?OtQNc1HtJ?17T~=#9#galM$GW z6ty`2a!oYm{H_#=?%W24gpF2fIFCX_ezl2082i8Jwv9xCw85A+uj;W;&w{~Af>CtE zADbEyn3@aIoe{Mx14lBmk%8w1au=UcvW>I{3+lg09HbHE3G!hbx5EEMdi5~b4XyMH z3LCy~NyN87fwprM5v__h-_Xm2>`Mn70*F#p+AK;?vE4Ma@C5Ay-AtoChk>JnKZIli z&CV5S@bev}MziHH`vQ7uOQ&0k#6M`jM3$GOn=aZ-QIJ1}v#yRKV?RJxQyo!%tZoA( zV@#eRrSFgjNOuoxQR5WYHJ#l4c1G&~_z4wp@UeQ6UIS%j@MczTPX=J5x7(t*JSk-f zUbkaH7!}pDgbAEjU4O%3D_^$*)|4B&GmJswk!^*cf+6iL)hYzQERX16)S33_w`&19 zWjwTyEP5egu_#kU3BpqIAd+6Vw)G&Rz)}Gw>5rd>d?7lSY<-~g#Fz3BL(122S5h>^ zN6_}i?Jawc6ets)!k~cS17oz6v*kR1t$Q4(a?3{@Mc-tl9Z(7|s5osGx<(H4y?I)R zXco{Qk>eeV+NvL`xrL~(VIf1Wnw7a($&S$gy-28DPsY#33O6w`v!g-VWkbw6x_X2f zOGMbwfgX=aDwxU2J_9aBxlcyGTze(9xz-J*wP=I?jYZyeb+vpuyU3EH1O%ibUb4@~ zKil}^n)nC=sFuY_vM2IwI&i%LaBSedbbua&@kE__X@#&T5OAm>N6c-wJncORQ16>A?~M=4gn4+}kHi$N-e1P`Kgu=3wx5 zt(+yNPg~zTnauiY4-Dii5~NLjl;V{IgaOuXF+=xk6*C8{HXn+TZXkD%|Ap?bmBr4c z>yIeesQY&K6DNWLK|VClmMB(G8V1x04|YOTwPTsnK$NS>O*iT;bnBO0O<^FIAP>no zaJaf%?^UhSZ-X~?Y@Jg`37)zC8*O?$Sb+VjJnui_$w5@tqQr%tuW?}~$?!JzMZ?M% zfRh;3DbqigWTW1YoSu-+#P_JM6B^JKR}lUYsqif10su~2ZA8f@kS?etk={LYjVTq* zL(7;LYQ%(kMW!e_i*DGPMn!fh8=EWd1e&Ak9<;$1RCl@KerWl)hg@7#%R`&@Yea^8 z^C*teS{}oKz)frQvXTQdFwpDkAqq1&&sV$AJN#s$+Ck!C*&^OOKKqxKR!B$va4i^n zvg(R>n!oSk!_k2P0J0k>MF)BCeRCK-SP4O)bw>n zf%cb(1UVvnMUO3}QTuKq6>OU6C301>+05*D!%%Mq6*X|g;d^jI6FeY#U} zJ-SF8shY~ZgqPT~!kY>93BrAOOa$I2&FcH}=1?T);@;am)#%b)R2g3TBY)lQ3R&gY zT$qUdSX42oS=|cvYK3R09*kifa>H~ixkELYQBN}UR!TE=wn zghCA5adUrbp|USa=9+IJXu?2F<=M53%JDC_Q9ig2ymSCccDXE`i+HD1u>Ew-X7Kq; z;v@2v`~bDk6vM=F7EyMeWS$LtNKW2vCE*DRbX@_5)qfs0;xp!X)nJjfrdiX`wq2U) z=u^aH`7K&HX*S)0h~e`rasmLuvsOs@^?X7G(%fE%4eSlq?Ch77Q%Q?BtZ{y-Bk z;$t+B)1S6ff;EAraUyJR$eYO!UQfxip(I@XAN{hnx;=>sVdf*FA(cqnFm<^^(HSDt!!ncigJc)@qI!j4?CX>Wa-zCQYNr>8oWaF=MNH!Vp;82H zn2f7twTFRIrDBW0V0vlG9Z^b{h(@zPgvluH2$N_suWg(Ye zL&tic;ip9a6)@)0h88etRXyNv1V%kMrlWC7}qlIRD-&2mrl_IjJYtBf% z1clFiDy|0vF_X)DwCznVTDl#51_W_n?5_(jN&){x0LimUamW;SQbr4W_=l!3zE*P~ z_yi)5oYLKj$?vHlpGm?^z;iPX&5 zU;+wAP7P7cbUadcnH?AFVd``py`AcHnFIQBNKOkQ=qmNK+i#l%2g+Sc=v7}-)Q8-B zm@jp!`Gbc%nNVb4*bSij=P%0w21pCArEipp4iow6qmcL?x5&rt^ zThrzSVdQJ#XPlvZp5(bH3LIrX9D4kEV1@=bjg}TzdCYWH*`DFVW&q|e`kV*9zR!?? z^*>#(ddwLU(`TP71F@zJ;B3*}rW8{qFFeuB&Ta&^SCEk&0UkXgjaWl<*P3 z55a@fKXxu=*Ep~cO}j(WS1toIMD1hYs(>zlGh8_tBT*BG^0rW+Ucdq|b8-04h1uk3 zAe6lNv)VzV5!<;yc@z!4g~j9P=fBD@1;ZYzEUk9f`_KqP{TpqO7t=E!1$Kjl)-T>u zto-qRn!(^UO`*%bsr`%M6puwGE`a41J;ZEgR-l+z56vR17(8l5G6KJq!%l)2O0t*B zTdsJ0f8BrVjjt%b0Ai3NnccnIpLaCEK%mm!y7;vkEhdk!jT+j{?o_u~yDz;N(1hz7 zR)NP07q16Gip_vq#6y5WW%68On==GMwL1Th3WVhxF0R`^pOf7U1SI|7;|eh1$UTXW z4+#5$^KfDjVJ}8?p9}*GX(8Wnh3-qIDX&_tJURiUWtNRobT4P5(8Yle+%X>U*LG<4 z3*)ReK&GRIZ??x6W)3tN4((aBgmuLJa@hUrL#uvW^7BDV)_7atkoc(@ED!WxFc2^5 ztit6o6YBD@9y&hKEkVf7_yhpHEY8JYuC-=W1A#OUZ-h)E6OhA+&GSjoHh=~`1Xz7K zG2wuGHRfHy2Y@%W2iV0aV*CB&L+ zOQo_sM0-3=KjhrA3v+Q)CD6tP2|*VRYQ(m5daId@^OIW^v#^m_dl!2iLB%Q7Pa z5fPej_OI)-qj}K}M9iu%v2lJYen!71@Q&B9i6QH9_5f>1uVY1VuDE~7RAA9@O)i9K zStAwTOL8i}RGPr=8RLjE4k0U-HGX|&{SlA*xTt8}O(IXMblS&HX#o(}Dnh|ON_|fX zdlPwa@Uj2S?pX~p(Te6+-%Iv&KT@H`un-MCtj+E=2)z&1%vm<}eM#46ImBQCNlzjw ze8RMU;u{I75EhVD)}m`O6uz(=a1QZ^ld>^%x+Zz^zROiaQ89za^SkgUkWUrmSRId= zr2pA2vR@T7u*N*JsaEh9=)`Q$kfHEZ%I-@%5H6Y^0s)G$sxMjKkG8pv;#vZ;O$Zbw ze&X{v)21>9DhF{rc#bzG4MPkaE2J{beCk0vz>cYqmpYrIS8YGFKi>@h0a0II|Ed{n z)XDXDYaBKFT(II>D3FazNs~U+TfW@N+oU`mu~!JeBn`i7|B@?D&n7TD}ZUmJ@I;#(-!13dYS zzsv#K2WU2I3|6!02Vg79}k(E?_I zLWME6{9O?TxkC{vpZgY+ZlEB-VQWRTcj9VF=~<$UolTqQ*(ukxQ5}3wA>{>_*Viw_ zac3W7HQ%`xa(73DZL6o##w7FmQeqzsQ7>#hTWE!sMJvO--6Btp8#`onf*0#y?H#%G zVnaRhxL0fjbO0;dirusPS@ox!5oT|k_>h~mI5n{BTiV6z+*VV-?7*}b&AT@P*_XnG zUf27LJT1}m4QTO;e&ZhBe`|;uNGU*wKv-;KQT}yy_tm7Ga^6;3vv|hVR_}6F?&AOc znP{nKmXQjLSL{6lvaQJco~NQ&Tcy4FB+FRKLcc_oYYr1C-V@KbAq$A4@1A#2(cGT5 z50?D_Bp!aNEXG5a6i{8wD&e}2)$ed6~6{FZ=9w$F@Xczs_E z`owta6|o*L#(5f+v3A=NsU5g0I%6o~0-*(A@v=(V=56OQtJ-&%9{}Dq=4geJFN3pM z*VAV=17MJ3`RLhv*Qkuk%uANx$eKhU_SG}iUuSOW2FfXWSY!n2aZMZ3vc(^)#rQ3& z0kcd}Iu)4d={a1+(ag-mdk_(I*|asL#rTnFO4B5bnE`~1faI{{*09khWPfxs`{){V z36G2*;VpWiBqeC;xL4=kaO z1j;a!6Hx=XXr&j8IH7_Luq753V}IsvytBR##+x9!T`Z$@>U!YBq9t-MR>2<=+K~V% zK2X6kM)D5dIp7%9+zznYW#!2=d;qc^2Y?vJ@V6d-fK-_jl7snB)p`9t&7|C|?$>a_ zaS*vXtujP0tG*nqzMQ2EzTbfbW8Lh&Lx;IMj1RjQ^DZ1q@XYhb09GC*^xuMc3p5nxj6!(7ip)AY?FdGB~III*teQE$?kRDS2 z#5YPx*aP5SoOSDA!cI^?SSO=JuOzfQ%7lQ*Lq>>R%2x=-ESgY9vl?XDfrD%mXl9JD zWBsBJWBUp0oCEAO7dKgl{Ns41t(MIU0Z{PZ=pHMjB=E?ArKzQ2H2`s#K)u0{NS3_94PG{BJbX>(=hEe zxI`=K8KG2k(^m@U6-wTeiV5Y%pAGnK04=UoxL#bWu|1H70`-<>6G|QPO*kOnvm2FF z7sM%Cw+jwsYXr!K8@xkO8+@!LHIT#mMEm*mvTSQrU26D|SB6pZi00_+QVn(-oC$Q%|hon}1r&>|`Mi#Awp z{wf51P1mC+1L!3z7vnLZ;uhiI8j*50aw^48qb}k~TRXQvq8O5fbWr z9%MP~2auOOZ6T>wSi<-Rr?MK;ATq4`MAl&GDe&n^JS*Sau>+U`NXUL+=V+$=2PMl0 za$1Oa@=yHm0Kb%5gVFD_4so|j%=T6S`cnXlm}w^daQQP0cW@d|jcD^@xqO6T@c93J zQ`N2x-Ph!tN_~?-=Ys{4EU9e$$d@WF z`6TXi^&vKVx_Zg#O4{ycM+^F8L}v!LSmC2Vv!x2^3j;Y4G;VAVR^PA9w^LykO7O~k z3ZTHghNMi@U~{AM=VhF*A_JT);V`HUZ!Hx}E^P{QNz|OY&ggxfF;dc`0MK1Tq;BpD zpF-N;Fu)0Ovj(f4Wf?$nmVnqxQL(Io^t1v@9%@eo;B}NW-Fjqr$Q4sy0%7cUTKcU= ziig}vi6;OWVPJ6V_XGo)0%R;dhQBtkw9z30+TiN|x2)v4Peq+fAvq%taE*D+^Pvu~ zkxuCM2KK)|^7Nx~u3wagoo~=Uju41izdV8N%S#@@6R;n8L?j=kzWOZ#bpfTvK$I*b z+%U?)HyD)q8i+ku)6#Uem}z*(Mu7?~`>WynGOhvXm0c@CW`I|EGDtHQc`z6UNa>^@ ztAl*lQ59wlKvn$16WCm8fEzY&B~S)W^P5aJjEie~7#>5M=K^xq{TC$S+nf$S_Azu{ zxigpKA=Ux=yI!u)-q-C**UI!7$Qc6!@Qsl3b>+@Ep-x9 zzI=na|6shKu1q4mve_%2G6#+Mo%{wYdMq5}M#h<$n`C1oOd9lT&u}6X-Db~mzj3vg4p$PQM$fRnf+u9#cZSiw=+pW$ zey%t(^YamDT5Qi#-SrAc)iReOU(lT~E~*fMe}$hvD%2=OE~K3qEZ5psvoF`~ti5w3 z?wo$>9ixwtNXJ@`yS++S?0wyrs?il8>?j7)K>=7+g7wqh7p0*5^k%hNpu^F^v}Jh} zFVTErI{9l@e#DoDt={kG?5;Gc@0cw{rANcTaR^>>w`e*=?L0nHxNil{p9v-xr$_7$ zO*ran?j6|O3hMUeE?@6g)d==QXt02w|27aa7rc@la`@o}Q`|eF@gDerJ;{-Z4&ZSC?=#KNmjPL2iaYy)3g6E4 zgL0P<68&L`88yPHQn%755V^pfg{ya;Bqf4k zW9Di4B(?fE`jjEEQB<9KJjkM>*Ltx_Lx~cBo3UMBAu&mk z16nze*q8A3ui#lNis*u41CFp_0bWJ1$8b<`G!56Sjl<>oWpOwSUz`1#P#a~o@9T4U z@oMj=3{n7oq5~bJGNJ{^Rz942KKW1%mm9tfl zk&b`16@K;~&T9+g$g%7@zJoUnu;}PJF<+00TE%EvUC#jEY5})w4$d&+R)__4<1&q! z(*xz#qqm>DZ}B;jXgoc9lUBvp5!Glzl*t5ZOt|fv+)r9rfUj`bFETA`zwpXun;0V2 zIrbAVzE`4M5Ig%Tf%yceJEQF2kay1qG||Fr{QY$6==bIvysuV$J*+Q`Vy_cjxz0pd z2iPGdL~QNv1aw-cjv~SK@vzSeJ8w?y^LZySe%_^KaS%s>Ndv&e=5^p>vtK16kvkyI zQpEefCEJr<*MEOczN&QVhucyoWHYyJA2F9YW-nEO^)s0VO6@1lGKY%6v!$kk<&@L$=4^fTAf^6Vd+ zFT!hd#UL2ZsFN?!-2OZ#pZa>gbtk9D7(Se=MF~`@N*uINV3_Q z+&zcBXUY^#V&lwy+ADb!O%;@TgeM23oW6Uocz=cUnKftqvz=7#Rq%uHcEHbvzfWl1K>KrznJ|>*fWggkJaQ1Vo!Q1!pdW;3xEC-2 zJ*|+Sym#=YW954+2Jlv-e?NM*u(Q_TF)1{m&2#bPf7AxDA(V zrc^bBM{%wqzuxQ@flaAj-Ky+m|De21J{t><+cy~9|E5kET2NR^d-Fl?%|ICXOT;?S z7vGTuK>aZf>gR-g5mZti*LbGW-Ud(n^L}whJM~oynQHUW3ry%!&sl{_Ur5fE!n6=w z;&;~gvN-wDZAJG!s=ZSs&OcJ-JPWNIn;!4A{ghAF(pbz=n8lqgA|9R8mp#+)y^g`bgSmuL4hB)$^2)iWq11XXFd=UQ8QIv z`LVA{1>lJy7Y!xbuAjzsyjyZ);p zlpVYlZ6RLr35}LA6K&0N1TFbONzJU!i@OsukmnO11|#`BF}^?O8mr6eS^8D_r`@Bk z{S(WFR_B51KYFZhEsj&?@Y#3F)+;lqnyM}%Wk}4m&WEl%i>~f~FC6{aRC2;ZxbbbI zSd-aGlUt#z#@bqZkDXTBLi6vhH>L^Qj&Y|NXF^I^g4m9>|Mek2e5c62`XV80IDz_` z^?w7t93{+6ivLE0mSd>(82^p#W?TYF{wMqs_dg!ks7@sR;;15sjE zk3GVGxHyUx8nj#6MiV>6&w06g)!%XvRUp?MLrl>v&-tF15Xy-HBuw?XPV+zB&c*#X zN)KZuT^ttyd48&XY}xQ`Z6xL?PQ1^dGv+@)6~YRpnc$4=JR0pRn`v?1vn~gPLZu`H&p=9^8?tX?_fQ23@&@tamt|wz2m$ zUfSOR3R)(Fbi9zCFNMtI&2h}b2}%8}b`#et!ogv2qo|a`^U6gV^Vi%_hClas?0#+I zo7dp;4cMn6;BBS3{2)8mLk#H>hwmO^5|H10foDX1kLptys=U7OG7Fja zdqK;Xd}9^c`JjQ4x0}dH8NTxppC6ns#Ut6>{4JxO#yH7t=>vWajjFh@t1(mmv@Zgk zHk;WvwM;zxDER`mF!RT{HfPVs!iG3)DsO%Q=>!a-qL*Jd>X11xlZ{eu3yeuv1}UZ6 z9c5>Qn};Y~abk=-+3`}%#SuH2Y?eD?lxjp=v#%PqMmQfR7W z=FxEeGUvC2_p8k^PNT8^q+*>>yuJ{v$8+yoFWqin&gFp?lR{@XJuaYmG03q!|NI^C zRIA3;2brQ{GaO|#ZMpiCgAKi8{q}`^dvjM3+Ti+aR*u}S)vK^MZ6N;|=l@GBtSd+6 zV;L^%??R~C(-ZXSGVOE*NTH)UzA zmR#fQo9nJ&aTna}@qOi~=EcRBRezPLe`JGQaJ6|>JvenMN%u2c-IABIO$zRfHmS~} z7DcJt{}z8TMAKdA(l&{BFeTlbm^W@JRe5Of)Nj&#o3oW_$?C%f_w(JzX`e6Jy}a*( z{xj=(`Puwi-_^fHcEv1r=`CWpm5!G{*LPTr3ZM_|3qYWm4um* z%AO^ApDo@j;U)epQ*Bus>DB?&>^bXopXloOJ731&%~@@poej4==o*)ydLY%0o)Tjh z>vC}ggRsi|q|7Kpm=xONxNwqGf~s0ge1AR~-}ANJck@K`pUEjZYgY+Oi|R@`k7d#w z>~#bo@1plBwBk8oUK2AW2PeU+6O%v9qlHSw_n>*!4*!;k9xGiWE5-A!=NuW-!o-Zd zrJh#?@$49F&XeR6G93FwRSaq&f=V$->b*Dl^8cl5E*T54Z1!6cnzShxc)0Y}#0=73 zEmPgNLEgwB?`=?B{idEOTMBKxjqWUZLT~23ZNmgz+jR9+<20#Mn$pm`It#Wo8Mj*K zf(Y@*C34!H)N!a~d|w^rsaa`RO-TA@me{XFx%5fp5HwCMhbNI2!zJi44Mwk2DLuJA z3t-|U${}{0Pd428SAvmt@8Vw?Aen$yI?uz>4LKbpb~0$*8~aka`?*X8<=BcwzToA8 zEo~jbO`K9sr?yBzezfj1Q~(nL4Z;uZ;{+x`Yv1X0YxwKMLC2uVv6J!>`h)Yv@=xWY z!DQmDoQ_u=aW$*6xrEbLQmai7R}@`vUZ7;mJ+mu6KQ7NK;fZKAg}%S@Tv#TTGHJmM zsw3^NkmNa?`&1$Ab7Yc?r**mE8F|V&vyI7Shy==OL$ypQabMhRsProW`J!L9q*7V? zy}_T}L$f>Tdb@V05UQ6Yq%6zt6%{;kkSkJag73L`5deDxv;G z|0>-WJZ-oyVbQQ4Of*IU^+5mpPUlBL2;-xMn(NPptWPJ%@*7IbNUA)nvdNyAOL{lh zANiW^s^(KLaIY#ta?T%a<`_&;Oa-|Q1aOAxhfMH_ep7su--&;MtXb^s91qFqw?%GPD&Yyg<#0mu|*MSB2 z^MKb7p~qeohMWMh^GpD(iku1sAMPOuI$T^wVed97n9}58< z0c(FNHPct6pvM8;;4&ux6!=~})R2{kKufcP(3<8`G7#466AR&4Lap>;lDSYd;EFdY zgwI>WXvTE_sAUU~5!A8mf{lSPv;aXF-9WU7i&-iGd-K zh4TT$^;E;UQ0g}LL&co8=(?VPZ1jN$OV*x^pwoICC9v#X&fG);I7!_Y_+d$Phs4lz zAoyWPT%8XS4qT9&2jYQA9C+uCpeX<}HL|Ywf11yQ4ZYir%pYjuJu)Lac)aotlSZem z)crru;@`kd|D}OOL4O`p-1tz;=N!hbBP_zRBq1Yk3xpxlTmP{~Y7st%^;@0?sO2}j zgsv*Tjd>tU3yg-Q{1bf_4JWn%xU=a1^ofUFb4K$v3ibf;=6Cp9@E6^Y2hz$&=l46D zA*W^JMTP+5pTbL0sGod@oC1X2?fk0YQlsX$MhR?II?_UF7FTyV7a+j$;mQ@0$1REv z1i})PGC#u|RyPDJ;4ElxcMrn*iw{6WPoGXTbZc+~NMiGJ4wOm9{cDO;2zIQxtpNOx zx|rF+%;f&3x%L|8;eKHL8?QG5RyH^auoS!D z=kkb$djUUyeqe|nC*k2J{A(F`a0f`FVp0wBV_4))b3u)xL6?Ua5+P^o$uy&cpBEZ? zy)=^Clp(D)54`tyU0=Gn_i9p+YE<6ium`T;SEV0~*2XW$mGT8Wo7bBdk-2^%JY6%6 zDvbMT%l7<5y@wIgH33St1aim-z&&bR|?&{=3 z==6Pl9V);Iy~<9qQmw{WQP10R)27hBT5;1Ej=`9)qT_ey-P20D?yV{KH8!iiAc8EV zKX|>FoHBO_v+ZL)mt-;u)}7nad_~5O*k**!H&1&)e|J>yyouUf_#iLL2S!h3jm`lO zjy}21_)v^U>IYjJ_Wr0qbLBqa>om&aa-E0EoEu=d<1`I?>%Ge6HPMg)*!iAolIen5 zDB;&gF(F-nS7S{pyYJl5V2ea}f6(jlleHwuYM?XXT{hMt3d@SCfR8>Mx>VCuCZ>q@ z*j1eG!fE65&Jwmo+AQXMNPP~xd_+4{WNSWB|((GPFG@0 zC0 zS>nEd|-pSvNEtZIYa>wXYwBKH!N|_!ClRpl`@dwzou7x8bpM2@q#@9H zMP1D9S1WTgABU$f>Jb>u+9TCv6;fqFC{*=k#B-kyefU!Hn{R%cB!VfJV^`2b zuBKJf2}cEPi4FtKB#W3~^2{hJl9G{H&eAgKUnKXeDANTuZfj05nX7 z#YDRi_k9@VGj9Ofvz^@>;ow*b=SW4CVrtHteR>I~T@L~Is# zLS2nBA)Lj`zwYFPTh80vpfJX1N!G!kdV=;O&6K?qmD>0GX?E912RMB=S|jndsaH2? zf_a7OUpGTeUl3u5Pq)aMXM*~0k}F7iKv`p9pPw!#k*W)xvF=Hsa!|F@4eTpa&cQVi zb>t=+g1-r7CF3*4qI~UBr(9@DF8l3@s^;LR&It2whJSGsUP*lKzD%od=f|O{2;W%O zaHB*ldkRM$Q_^o0kdL_-XO6Wf4h+$!&f;6vfRh`zV=hi;?$h3VK(k7PS17-fJCO*R z=2&y17@~R^(euAZ^mMazB?OlJ~vI%}t^9I4955Si!8Ov^bY!)Iml4 zfU61LUCM=AkT=q3zW9gptaY)#sv{cZxLFF$yXBW(y!>9}8HU6r^UY7?tu^(;_!lmw z+LTDfl#yl;c~&um%)Zth~ugA8r&irucINj%0#r-TW{g-#G#^$1AS=`{zbj* z7aLnTq)egFt$*WK#nr6e+1aiyegY;6?G0gSF1-vZU|LT^?X@7XY*=@|9~CJ^NLnHXe8J;6EyuC(J*MQd1;<)oxRTI zcf|?4MRHUih^LN39@~qq&n|Ul}dJ#jGbox%2wJ!x{d_CVDJ# zYANfCke;qUv&tJ$gL-b?R&)JSVcJ$Z+*Hd_4(fm|C67HX0v03hssMkbPaPC-ZKwRg zvvNl3c%Faee!3Z%A~@c%aC1>CWRq=-GsbHfu0B7tl)v=O?M<-r(McoD3`yDzPxI8l z0C76MrrwzAe2d|DqVD6{pRsppT5j#9A{7OYRaxM!sKoWO@zcZ#r$X?1QyFGTLxkd; zsz4-0OlC0H;cd6V8rr+3r`M^p`noz)JSMx>j}0B21glr6pb0Ch#eH=RY$Cg+EV}OG zlstVj_c#^og2jG47y__7AW=c zKK{-8RzXSe6x&Ek4E06dSl zL8gL@PxI;vrJSteBlWzp4XI7X9t&`sqv-*KS?dnj!J@y<0+ zkCYF2pL?L`avlO4S2rm+*XnaMCh0)BE?m3d2BmSCY#~QjzVpc_xt+U=Yq{aPb}4CT zy!-&o80)zVf@M5~*6MPa1ETg@8k2lX|Mfw5E5_q0A_J1+ajEWe;O(cF&ju2{oLKSK z1nL_ZjUYY$M88Pj_H$YasuzQOw8(1gjG>=Vx6{s>M~>Kw+KS&k)=nn{MJ?neD!g7B zXu@n0NK)XIne{vE*c;@kFUm_`jz=UwTuD=DT|?xr(*1jGb{+6o-u`H5#cN01 z_|q|px6{RT`c|bWZf^HA05N2I?!@QorW5m-XFJM4P-Q>Y>lLMe0=vvF({SR>j?TdK#G1Z%yftf?$?wy}?KgV^xD-cU_!kCs^g<$e*t(W4k_SI+C|g?61v$0+QLjm<)w|>`L5-^i2TojO2~(3+9nI zt+xvbP#*p9oTgq{c_ui_g-`A#&T;vJ%Xa3rq(xTrncKnL(Rv3CtAKx>QE%|r?qpf{ zc$%f){1nBckLa;j;e<~&my5d+{515^68W82n2e-OXaF8P)F&|vcS8FEJNL;axn#Jd zY0@Ln;;iTSwfHHowBEF%w56#|%*owO31;(=3HSQU9c(pNIiWu(OnN(htkXPCla*AP z)j(Ug&#+Rc>%0A4#czk(!$>ZZluoBzi;bc&j>a=uf_XxOckkF;*F&b~DN_QE^Acq$ zj%RPPT{KWcRvMo1H3HeQClmx5y`6o`(Eq0PDfwMLluG-HyHreIXiBZ#n z?%|h9y|dr_UcFLmz;yj3A8;1`pTwS6|x-iUN)F-kYO)rV#dH8M`qdq>l#r5S;=J2LJzx%0dA4!zOQ z6-dRW76WS{HGzXpP@ZZ-+!;y+yV+o*(yyby{cZ+kPbBd;4E!ui z>%bai{VBbRb>wjOQ0<^w-MCdKkL_Lqry|vJg@b#8JLa@%N*?Zmr~J5t+dn_F_;u+m zEutp7S-lr@>tId&qCFTSf|!TPatv=1gcS|fewoyk>4eCE5xR5(inUeV-Cw?OEB=}o3L`HwC6_1e~#=XH#1x6Tjwzf8Fe zMwhR?g^8Kb5{12u!QYcV^BqV+LhCcnuSU0YNw=40l62ixIuH39($hYfV?X}Gv=oH; z?lBm=)rWMBvtV3AQPXt^|Ji)~|@}%hGVB0sJ#cr;(+rFe&#w z1l>_BI?LHx`$NaHAr2g_MR5|gax&F=R@Q{v|@~LGO{48-AV7l{F1b7ZgF{8M8VK(#8 z#F9!kH~hGbVaZ0Dw(0L)H%ir3H%T^WhSLsY&#TrN{3@fnO}i=ts5yBIa9Ua%N1F~* z;*vQn`o5)N5Is74H;=#*y!RgdLZpPHzRJI$NZsPu&Wzr&TmLsrTlO;Sph(i|NJxps zYlg%8I~Ns%F|mU$OkCWo*Abor&y4$+QvPL`mD0iXf+1U%v>Yh(7ww5q*N_xRwMg9|89<eTmo@9!X)e0@B^#KOx{cD(1JE9rsm3?q6lD z+*EqxIsN>P5?q)+g>DyJ<_1<&*XrH&)a#OBNS^H#DO~3KvUvaX4)$}2H!>K51@vp} zFS#rK&DI)M^_l79a?=R6awK}W=E&OYecy1%`5{;O7ejY;ybq~%yNGjR2yacstJTc* zqXA(3i~eOd7D15KUb<3!ppV0iJmFf=d*x)7QbC7Gxo8f+Ib1Bp-?QZ2O<9t=^{6rx zh7GRSdr6u#5LMmHd6mn@Z>bV{NS)qd-J%MDrp3tnwZqfALtzO45kPQlaV zLFC)GI>NY8KRmh~2YFA>+9niU^ju$+U+??!=j+(DrYM^^>ZaL>7|FQ`mz~`39@*jE zWnKV3s&^E)1|KciMU54!NkvF#+<&=CPX{Ey(kb;NYpkJdcr&FE&+FCIXnYnvhbjx2 zuFR}h9wQ^BBF39$%1-5|HCxnNdm)pdtACBx&bZj6?YdZgd*`}gKE?GT<@>sgI?GmI z$F!uCljCgnkRJEJ&yAt&ZO1OS6{1<{x_-x;@2red4`C|o_6}Ef*G^bin6nSD6DIXX zs&PlINgEPtZvCr_^#8Q?mQhtkO}sdWfXE?4r0Y;hcSwkM5KsgJq`Re&R9cWj2^>Hg zq`SL8Iu6|_4bmYcc{lpL|GVy8>;Lh7xNF@{JZIQ@o*lF2*|TTnx352Ny_2y!%V6aD z<0M3M92(GWWnGvyZPH#kAZXaNDRU7Ooh?}06q-k?&WUc~IC&V)d~QUi4MsB`rE=3^ z-(@`QYe4wi=>Fv{#VO(uf$du7@h|RCFx0K66Gfk7j48MICH*O*hWD7Z3 zcVkk1)!MiU{$bY4cm-X~XmZylob}qwSJR^0B`L*Vt1xe^O%EI6LdJ}{e>kPEN_}u zsl(NdjV$aevmjGits-0un;*<}cSVD(u6@tdJ>hc)%45b8cXJvN%A%F;Uw=}MP;=x} zA3H2t4~NE!_~-Buy=xPTK))2Y8Jj~u>I`c+kCV*Jyc5z{hxZB?qAl31-g?L~iWoS) z6g2Ky3ox_(*oo08 znTM+TQ#!uA@ly=L0Kuk7+&h_bM*};fZ{GaAj1sc#ML4y|`x(ekT-{t3c+TfT>Xt=E z*jKAtkn?;6$vji|Vl^#Mg4293eyCSxpygdN6}6L^WY76>P>^q=@aFsF`h3YroH;+$ zZ?&-32n*9}VbiWmp*z0~vkl)z4U{ZcbLQe*xGr9|1eyeK@XGg1)|(D7jUHqscZ#{m>+rkoEEPU%l*C5K*q%4g&{o$_?2SUwoNztsb_8-ucp~c zeMkCwxWVtV=2x)FZj&b`!o1k{DU=(@8>CKBG>*a{ zj!1EE@@$==qO|6pC~mTpABq=~r0-nLeB;b0V}n zT5LNEgRGpRezMYe^#Px8GmDVXXN!}WN7Payg_sW0>;%)<(24&L9G2idc;+%}Tmz`-xUX2xEQg7RiYw@qxihijPRX}oYip=gA2v%oD z!3|H)8|Qzuz-4G1h*zJS?}R!Bkk0b2H0%lxe>n8Uzw)3DI5;h8NG(y`c{p3UN>4|; z3>Vs~Ou2VTI=)y9r4}%0BD9Yo6C*HrP*tw`l#l;rtwKn>qIQ4JsI{`PMrq24Z+ne& zd(3#R9!mXrycp9Q-?YVd-Gr?}Gg8ZU_wbUvcvU~90d-$$(^4%;1YJ z$#>W3*HG*}^7{GN6=L{3^3i*$p?;V83nS@Qv5&2_2o%C+XepfAmI61E@XU92fv3E! z6(iKu?w|(|s7P44vqU!2sZ3Ol&p($c$B-pQ+3T-y?~`#xZP6ZIdg;niHLe;S4%Ygj z#^kEpa|m#)+fwXewezjEa&Xd9z$0GeL|~#v=t&FSQ*C8Cy<>O=a>xyBRp|?JsX@Bs zB7TCLWa2ZykH7(lT1{nALr&9*4IFr#?w_l#?|A{Zv%2M%1e@*RpjN{HL%p5YTe$G; zqZCD>$o)%@9aH!*(#^%O_&e||OSxg^U1G&FfHZB7v&WEmDy(bZBNV8ABh(=BJ`}j$ z9yY?2uF?B-Ze5R;k9>e{$_>`WOJ=7*ly^zE-3ThQDe*v zgq5x?M{-uDr@p~l6zzFr6sr2-k#S`~OgCLg)EZ<=a|Jt^rmqbZ{qcws@Lz8UYTV7a zJgNS8exB>7b77bPnQ9=cIFBGx9*;7q=jSM2%xPiI3n(n-ZLckY86EPHbTra*~5)!i8*Cw}F931x+QtA*@j z2ea|^HuHyHe$8*nF*E~LwVFSCU;0a;#WXHitWjQ5QtVgSQKjMT2 za@+>(mXXG+WP9z*V(fJ>hiTBXtbu1OeKb@Gvjbf8v6T-2Bgqs7*KMKP)g-(zvH0eN zJvd=ebid#OPmiskm1X@cy~m(cc5?XYbKfbnjzqaXUqZKQ%ja>_Pv_>?s_-U;N#XQg z9PU7{WS%aM4{!_w{W;=F-%P7HrlXk0nR0d(Vtu{-iawa zY^^WcTCDDp8WF4!oob;@Q6*F<*0XEPmy>C3N=Y<_y&0MAo+nu9(N&xX^1sP&RiBqV zmq!?>XGSIeYW4vOYGM%`=cYqVt;&Rb|h)|+`VfA8FL$oT1I=>A$EqeSB(+>^W>I7mJ zwuZvxJjhV3ag8QJfM}tS-O1eCcl^Zv9KHkc2P>od>+{R+Y(LD#C5>?%rZ%a8P9=Qc>nMftQo&-eGBKF}}s&5b(%u zjmG@E2dLC|!}55z4*&!U?vG;>WGE*o0GtbHw(_mMM)5)g;F1frWSjdb%`Bim%h+?| z&OQl{cO{2!4wa za(HqZv4gq%dSO*X@b5=~*E+lX(NHTlU=fP8k%Q)kVgXp(sl?Q?{?c3wgw|BA%D|FX zBp-D7sais&hL*$RefXD%Vm|yb;Y;6O@CCsZRyNF=)Cc$h34XpCxxO#qAcxSFwi4aQ zYVzWA`@XBb6!_Pxt5D0Ha?O+*spuEZubvZ=5cH>qTvv*kS;Xs$y}nGso-;<31>7P$ zk1$EY)x*r3DP=tEYc9JPcpJ)X@kt2e=o_@Iw-3eChO$I?W3TrPn*w$Zy{G_dDd@nK zr}E68xCpxet7nfx^u2N~=pyeIk%G?;52{*q^h4^S@s9vprtC@Nd+SZpGO5s} z`}OZ`2y27T$v}?@9M1>?NE6JOhzowD>F$HTmG!q$9F$aGEyQR$=pm}~0 z8@88$v9Tv#s+AAuMl5mtKmt19usocLbL2ZKLEsS(C(}Wr92*ZVwvlV1amgpcx4r*i zB=4sld!nj8@%J}HD1ni1|N0{z?j7ov;$hFcp^LU;;&Q>&{0t-4l>MpFcwhv1=h4kx zXNpRVJ{cAR)6>f#{xi6%eEkvXOBWCcW7zG9t>~F4D*V-ouzQ=#6ZGLpH--3Se>w3z zvX?F(A_pyJ`IC3OMPE7b)rMqP{?Rh{f`_DO7(CRDJsIAwaBsqDjac{yI7^nzs?(S> zYRhLfyy%rhqUMFT0u+(7C*Bz?BM7si^N2n_g4%MBzuqFwOQz1xy|(?v)oM~BUy#3; z0D|TB_Ho`)zCF**@e3j(8U7Cqz)^fgDuVrHB_Q@BN^GpWq_im-K(h*ZX%>4(_kv;Q zu&r>%Xfv9&2209w)UGDae$bwKr;pZp=0R8a91zx@_Q{r=4YJ-4$dU`KeuR z#l>-OQcchwI*UE=VkWPkM6celHP?H7rZDFF>=jXpwN)41e{R+xYV zX4QVoJA2&>N&$aqiS%h}u=f#GcGH4X7;3ovdVa(|h8vT!146Va6-}e*R*O{^oR5q; z3Mu&sF|SYu!H5T!+wZYYq!IMGk2NxrxIjj zOg>Mn#5!qSn<7K`UdhSvm!>8!fWLo6saeUi+uKK3`z~JFau&>)#`y#GrLRquP}yhB zKE;j8fyzT#fGkw2Fx;H$+ooE~k1ygGF=f54L@##{VGCl`REA_zo_cgz)by^v@R zemC<7X#Hdh%b)>mk`FIy^YyW*HQE>n?T;1m0*p0N2I3oXy9)R!Us=|kFN-X8Rnwk$4S+VDw~{@w z|AUZ=kJdjet}O(b?<{ao#CJ`&2sJJC-&?@4|sZ=H^z~(IJsd4;-_IsY4AYcvZ z{mVy{Kg>f$Nyd5+Nd2?cT{9&s7t60e zQIl(g023Lj{L#7+R^20OZ2N-}Kb9%p*!Z1qL&V}@P<0JH&BevV;^`k;Xi z1F)k{CH{f&&d=$2dJ!>XgPJs6a_Y0*LbV4#viNp1Jn8Kcg*BJ44f}dcxCi4M!EPpZWf1mbKp&1yawqVV^*LPIJM*wb#{}+e8*91{#L$}Xf#?S%@ z{EICRSnXpua3922s^Ev4Sb(3ck@Pc)if{>YfQtg_`-xqt$gTR0oMf0>x^#!ZNLF4O z0FG3b3I|spZi~CXeys3WFx5aE0G`R`_+FDcDgmQ+;YYp+5`6{+WVC#Myx5aq8Ppx4 zk_Henu%UejBDzT;6#&>l98+V$!{_{kku&`<{+CG{Ca>G4K?Oqr)mrl-F!0cfv<`D_ z#<4AcwfCYo-7>Ex4DHwLvxIQeSeMg^v@_K0F3jbQ_Ec0WTM8y*18X#U)%MP@7xXIG zXoCl-Z&FFcnNEF=?40d!CR82!FRI6mp0uc*w3uG8{|={?3?86yV|>-bHGO;gSlwjl z$CC{>VR{~C4>pZ!-<$lQE7rfDyTSg&57eT$Q$pFF44A$eU3y{!`J?LR_Pgn_=_W(3 zR1VX0kdHI{DWM(Cb73s(xk%Hy3F4>KdeX4!DOcwl;jdv&62ysX^rU&7ILy<;ABf&t zga~z`!^p&kwbB$UA9()!_DBB4WAt+2^iENNIIxf<2ivG$WzOEn`H zX;|w;l902rmM@%BAUQa~3y^+P7};|+)yh)K;T?VdB#pC{6$!-DmY+nCr*yTmlC3fmNufDO z3&=6pf5{yD&&ryR)i+|VF96g46~&sh+}#)38YVSF7P)o)9i#AA zTMUpLQ$ahDsO1cGVzb{>5APs#pn&8&Tvz1X3+L@m-qMp!Tp`MQ-_%2)PV$aYm7KAK zRO=3W^p)je#aUi&jW1d@7?FpI5O!49W?P5bnrf_!eE(eiH0Oi<1pr_xxpL|I9Ps9_ z*PEw^K(lBS5%dZAfgpRurbW2s9Q!a-GRPJK7$@fRrM6s+^TzC~+x3El{JlOr)%PGd zq0=p4v^+iel0j;fX2=f}`V>gc zfXLw6Gd96T9@03F*r~+s^^NLsExSAMRPnz7wFx0P*fnw+$DOW5_JgCB9F*_`C6n&4 z7Dr<+<`JNi=&()N_#tf)F{(2q)pA@|Khy8<)2ZNKscurO`|#2BuW~PII$l4mAP55g z+o?ZJV;P+*wAAfipx{hrZ<%Kk&G|}QG=TT}JgR-FfQr17dDwyx0~YmS^pqJn9U2)b zpWc2Z9~nAt{POs`WiRRvsjz{P~ZfF|j*eI!EzBiHOO90Io_ zW1+U$f&B4*XgB2U?r!Bt>A%aebP}13!zgjnQlw8)CLe^kKG5&8a@&6M)N^-#ykqo= zL!)_Bx^FbbGzbTHFUNB93Jcz%@rR4!LfYf$aE3UfjEJQPNWckG{GZjj{%Pdf22GuR`%+}o*3iT(2CVWSGWUQReWw^oYKHMA`@yrf zbJO-3tLs&=QF2IVb3-j~aZ)N@OcmF~>pM(h>| zB+i5qd$9j$x#If^g*Dn%G(=dc#2854vfHq{<;rPv+S7&DAJpG=1D&zfdtgOO+( zviVK;N z-{1cEny1R{PaW z9pEzxu*lC!m`o2#Pu|Ua2|N@tcc_A##$l^D)Y$O^N?r4QJu!Qhd{@oSokS=uBZi5s zvJAJ3`e9#`rN`#AQ>tdA_oJF^0i8&YLyw$#$7~=t5EAF9&fRs zPt~|X|8>o+>H7PH8NG45le3RkYORHpWlJ~meRr2p!s~!VCyMc`UpflSD^?#DFScR3 zMWP|L^;C15sOA1yhIN|ts{8|_TDV{iPq#RNmnU$eEGY6$*C+NLaIZzW$wv@Os8fif zwxl+8eqgn!rlp#<4%J2SBL(-iRf#_cKdX1o=ucBe>!@jR5`1y|JZM-``H>l~NYxU@ zj?aem=^azsrzDDa2dka04Ke9-OjJgfoExjsdj+sBxS~o9AtuYH0uvrTl=m|#Q|ETk z`xopu)2S@CnY0v5$X#8cK?$8sY9%^?m9!j}oPsRONo2#@f}~9#b)dy)@bbh+Q%(=E zUm7N<-}-37w&gA2$!%omq$iJ-vgfs@Pa<3sb63Nb9klWA{xyVT*lD~I(VX|Qv~XVX z+As!Dc59z8aXuhf%e;s3{&eXoM`gJ;ufr0LrNW}+rZFLj%=LxMP`B9r9 zDCYZu65@Yl`KcCCxo19l1*y4u^_{HgD}H60B3IPvaD^Dp$rLr^am+{SPp9?vCpCWp zay$9aYgV_8LT^Zbu|s`=EisreFfe+;1;&{6;f0v-EhbM9 z>!~WkQ~wuDSP$X@Hrj10jDy*lY)4Jje zBp_fxaxls95EmBnX!$|e@p?QYrE*ebl}R|CJ~0Yb|1S-4rPfOGMB4@_`DlGc^C5IiFz6uB(bp)Ny$p^XXs? z7no#gdfORH9tSQ$`QJ8NpfWE0ev;@!m(q_oe&1!d-;9`G@U$?DO%(I_4Ho5~t(P)S z!A?52J%(7g2;MOfEULi8veq{|K3WIDq`QIf_U9oV%cU(wv=8*S_yk2}sN~~o-${+E zS=|}Dx6GXQS54k3F6si5`@_L^n~rC2kCn`f-hHyNI~qEQYwg*-{HLC#(Y{K#$12o5 z3HgDjD?LPsXJE2Zn0pDG2&6C}>Uc4Fc!{B|9Mn0pQcO+C)blgpt-n(q^49@l7!v$6fOn$o5TK^RqZ)EOj-uKQn z^Z})Tb7g&u+-Q%Hk)C`PdZ#?Lhw82vR!n6VrnXdI4O>i*cyb#F)djfh0O63z38c5=m`(2m zc`V4^C=GW8%*_bF7`G(J?L9>SvLXs>Q{M;r5ZcD3q`83J8pSw=vXov^m=}gIgCnm2 zBeFp6zB&db9vURa@2J!|B`WK|@?5eY*XwwjzJaN1I&LcuYe&KmBXTnciEvideP~ z`?GCEogJh>X)>EeSD;oEw$_8y5QN;mzA|2ZVE0cFn-N!?x$F;Irq9?F_xwBDf6A&c z;V^Z+Sqr*e=mD?xocb@@S(Qp3u_7KyzOip#n^<;Lgh_r1kl1Z#?T#6Ta9q9c3n@K; zOe>n4{#*XzCxp+)OW(Vl#u=~gsXI-PDdL7gRQfly|EW@aLtT!4LUut?YI&|rl?u6- zh{F~ptq=%tF~48&wzr2o{=x#}B;Sse`Rapb(*Q#&Wpv>Y%Q&En{iibqOm-^ zH(DWV)@X;cJfnB_uZoi0Hs~te$e@xHqrmgnhD%potOu`;`lo?@W0fD`KmEJms^+!R z>`}Tf818H7KWBn-2lP)1NcNpp1xGeOx5i|{ej~N&Jnu|=7nkYT24)>Pd{m;GJ)C@; zq>10+^a)oF4fm5=SB6th^XSd==>7c&-1qVaVf2n7KTeiWpK=?n%6^33(k?l!2ithN&$ViQ>ZvN5iU4 zI3!&ETJKQ>2QJI<$++dn;Skv}_{ZE@w7Srl&{pC}=^;cz(S*8xffejPRpiu3PLLiu zrui?UR+nee4vi{%g}!IvS7?A5^}uof93``^{_pt z_R$eop?6JfnMf3g`3u?ks(SegAx{63#=d92)JgR?L7e&rq82f+u!-{k!eMWaA`Gmr z{}?7MtIf0?kVlJo!_$l-a4>Y9f8Ga0@U?A_$a(iJEhU# ziya0yvNvy?D}1Xf^x~-x6|haiG;i&#waEx&Ji)&NC~HC`XHoCx$CN1v;&?w0=e89o z^GPPaIs=Hg?h-`&M*~d1`{LQ;1bu-2>o`nflHyrbFF(};u<6MDnU91-&vMqksY^1o z=i8Kl6O7x6+2d45C#~-aYttR$HLd2Yf`UFsV5efSS$3Rjn@ssX+GNk&{)RC68*VN;i`l>C! z^OeoPq5ErNn==%1zPiv^YiSv~wRCv^5sVl^$zNigDcws!pAa8tg%!z2(G z*0zE^ApT=oA@sMJGn3O3`)v!97E%Xb=_P4VSryh1#sf*#2jcVZe1_L+vW0c0pqCj| z#)=RsaLB>&5XedQdTn#3j=g`KJNolHDLDC2 z=1hM;ziCim!QNWk>s#Ak8Sm=+&T10uECD_)WJO{M$?(QrAMYlE`M2k}{Pioc+@#Cz z{YX-WhY&N23YYJivP-AnxFb0pbOf4SyB8W!Gun2AO05$ZyR z#lakrg(b$9AZe%bhNNS=_C^fomu#_{Ou|&5U!minO}Q68n%{BKle8_D z2Msg@a71q>i!oPE>H())?U`O?0o<*AD}3O*ac>m`+)0)kga>B3`tov39`5E@#%sjc zGr~o0c^Br*P87Sb*f@if8LPWahjL6T&X+-I_GD44KSRZdWpJ2ubZTWu?2ed$2TD(R zuw4aaLXGq1Om}j93g9VtgEI5e&@D|ezgG(L$WEC}fni5u4;*j7y!a}NXc@xJjgfVZ zXl9ZMwGc?`<4|463Cs5xy+w-*uWgp&lY6L&d?((_q5`*)!$Af|T?jkQ>ay%-x0ht4 z?{&ZStQ7=)y!_KyXY~HlhKsFtKSFu7K@5LqFZ{$;!b5qvkM`G3$I&j@Vt_4C8^fPY zU=%~Y$k3&U`3w?`8M@DIna*g~h3q6b^a#bUa;m9;uu|*BFw66cK2t> z*GQPhq2hT51?J74i4*IrpfxTPU``=_aF+7zMV_P$RT_i|E*_Q$T{T_ti)4l!!%o{} ztC??aopO&ls6Ua}2hK$Ba-&hashSX+917~NDD!T^F{ zco-e5M?CD6Mc2}&Geo(upfpeY(bdz|7#cgvx3`@>N+7GbAIh!4!?5q+$(Mf4^-;ZY z>Dow|VZv!O&;_9@@Q!=$@gU(`LflX|vAQbC-(kfc(5Bn*7G3%&%|hVl9+bXZTj36J z`Qo4049pOM8TFWF>{s*%fMEZ&4v`yU|E;uoD9^ihzb%;6i9z}qBFtbFoCgDX7OQfj}i;+6U_%${4JY=P@bsq8M77}z8p zXvM7G(Ez_`1fnKUd43@DXqAO&xeXTh-=o94V@FdzlEtGXgE*=G`>w_%v6MzZx%%HO z?|q1ag|YyUNdDh-zyAj>Hog&)9nlVKiDbwDcRe#Wyi{{A(swWxGPE-We^9tNxt_9d zakFuQrOGYD%P+*m$->Dg#K}paDOB~pRIsu!GBf$~|6joh!y6k^Kza32PNMLIp7;L% D#fwd) literal 0 HcmV?d00001 From d1bd98d5e0561d3453821e47f548cf7a6d173f02 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 22 Jan 2020 13:19:24 -0800 Subject: [PATCH 17/20] detection TODO --- spec/consensus/light/detection.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 spec/consensus/light/detection.md diff --git a/spec/consensus/light/detection.md b/spec/consensus/light/detection.md new file mode 100644 index 00000000..d0a0ad1d --- /dev/null +++ b/spec/consensus/light/detection.md @@ -0,0 +1,3 @@ +# Detection + +TODO From bd2f41bf798325a2dffa81b96fd10a3a34fc8138 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 22 Jan 2020 13:49:09 -0800 Subject: [PATCH 18/20] fix image --- .../light/assets/light-node-image.png | Bin 31450 -> 122270 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/spec/consensus/light/assets/light-node-image.png b/spec/consensus/light/assets/light-node-image.png index 24d80d71de3935459fba1cd1cff00601dbc41800..f0b93c6e415ebc39bc2313c885faacb35aa7af9a 100644 GIT binary patch literal 122270 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ɸa2^db4^;{u=6(Fa8oE-0a}M)8Mf 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

#_$y479`m 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 yG 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 NF6my(J#Y2ke*p{XTJ-<` literal 31450 zcmeFYXEfbW(>O{55l4yMIch{ZdMBI+q9sa1^xk{#{SbnKL@xDwTK|#ThmwTm(f`SeP{?eWR z11RT}G`GON$L3N>QYa|pG1xbT=s=&&SWZ<51;vXQ1tlN^1?37D3fM$JafP6uY#X4U zh$Nw)kUFH*zY_x{9vLafzCyVVi|KCxdRPu}TFxjaoD2_ts2)Eg-GD(%7kMQa%oPwN z3Fp(juWc(RD4qlIucXvHX7-vIhHe&`llUJ$4v(yM3Ubm3fGg*`Y;!~#AVGgrhsugR zDlA>{TsodE>Vj|?23waSdE=TQS27B&3tX40m6PE{SBjW;SNmpZunS^+DMC7Q(QG%A zU3}}^ay;1I&}buh=kxp22@@q?NXLZ~1*P=^gb}5ch>QTm;3)_T1%eKHjFR*S`Upia z5R8hF+X_cPQ8SQ331IzSu>bcJ5;XE93yI*ft2NVdBmBD06DVsS2l;Z9pqt$%n`Q0R z5$}LOMxeNwpony&t6l%GZxnP407NfS+>9D4B+a>ylFVU1gS6DQ;%S0~1`&#Ud`fX# z-x=dV(AwYz1oSkR;dMVHoAOCKMU>k+KEIY1dIG{)f7=Z;`7kcIz>bJjBBa6!3jntf zZC9Sp_jlGcGx@%u!Jc2kBn9xF)Vz0F8rnR(G}c^}6T$^y^}NL{x3F=axpX|#FF=$j z4e-um;tU3Y4P`r(+yuggIvPyvD=)ZR%I&=Lg4X=hiS{x-lYz0WgTRKQtDq!v-e#o` z`CM6#!~xkcnafkcbOtO2Lrn;yP3{Z7p@2&fyj*o9cw25@loc9}cJyjRte82wV76Rxkk zEX6&KxIJPfk%=v%BqR8(N~%Jr9jD{t?e0n$^S4i-i=k2{JY!~F$imiY$y|V zY_{>q+s~>;cH!>1cjapN+iVdb;%)FU3`c6kL@ukBMG+UjVx_C3_9vU-p398tqFo1o z4TU>ywk(oqD6hZ^P86SSxTl;26cH6pZPpM*_8)KR%SCrKE?qlF(TF2Us8}QdU+5Jy zB(&lHlZ-p-DjTWGl_MhNlfKwf{X@?v3ZKlOLfh$IQQj0MN#4F8mDM}bbIgjv#XB9x z69QC$KE5-Vm~>a|i*fb2ZZESwIL|;XNk!DxtA*c1{gqd1mMOOs<1QL3WO~)MVk!EK zcS!7Y7(7NSm5$&HAgs8Zi|svoMQ{)f?OtQNc1HtJ?17T~=#9#galM$GW z6ty`2a!oYm{H_#=?%W24gpF2fIFCX_ezl2082i8Jwv9xCw85A+uj;W;&w{~Af>CtE zADbEyn3@aIoe{Mx14lBmk%8w1au=UcvW>I{3+lg09HbHE3G!hbx5EEMdi5~b4XyMH z3LCy~NyN87fwprM5v__h-_Xm2>`Mn70*F#p+AK;?vE4Ma@C5Ay-AtoChk>JnKZIli z&CV5S@bev}MziHH`vQ7uOQ&0k#6M`jM3$GOn=aZ-QIJ1}v#yRKV?RJxQyo!%tZoA( zV@#eRrSFgjNOuoxQR5WYHJ#l4c1G&~_z4wp@UeQ6UIS%j@MczTPX=J5x7(t*JSk-f zUbkaH7!}pDgbAEjU4O%3D_^$*)|4B&GmJswk!^*cf+6iL)hYzQERX16)S33_w`&19 zWjwTyEP5egu_#kU3BpqIAd+6Vw)G&Rz)}Gw>5rd>d?7lSY<-~g#Fz3BL(122S5h>^ zN6_}i?Jawc6ets)!k~cS17oz6v*kR1t$Q4(a?3{@Mc-tl9Z(7|s5osGx<(H4y?I)R zXco{Qk>eeV+NvL`xrL~(VIf1Wnw7a($&S$gy-28DPsY#33O6w`v!g-VWkbw6x_X2f zOGMbwfgX=aDwxU2J_9aBxlcyGTze(9xz-J*wP=I?jYZyeb+vpuyU3EH1O%ibUb4@~ zKil}^n)nC=sFuY_vM2IwI&i%LaBSedbbua&@kE__X@#&T5OAm>N6c-wJncORQ16>A?~M=4gn4+}kHi$N-e1P`Kgu=3wx5 zt(+yNPg~zTnauiY4-Dii5~NLjl;V{IgaOuXF+=xk6*C8{HXn+TZXkD%|Ap?bmBr4c z>yIeesQY&K6DNWLK|VClmMB(G8V1x04|YOTwPTsnK$NS>O*iT;bnBO0O<^FIAP>no zaJaf%?^UhSZ-X~?Y@Jg`37)zC8*O?$Sb+VjJnui_$w5@tqQr%tuW?}~$?!JzMZ?M% zfRh;3DbqigWTW1YoSu-+#P_JM6B^JKR}lUYsqif10su~2ZA8f@kS?etk={LYjVTq* zL(7;LYQ%(kMW!e_i*DGPMn!fh8=EWd1e&Ak9<;$1RCl@KerWl)hg@7#%R`&@Yea^8 z^C*teS{}oKz)frQvXTQdFwpDkAqq1&&sV$AJN#s$+Ck!C*&^OOKKqxKR!B$va4i^n zvg(R>n!oSk!_k2P0J0k>MF)BCeRCK-SP4O)bw>n zf%cb(1UVvnMUO3}QTuKq6>OU6C301>+05*D!%%Mq6*X|g;d^jI6FeY#U} zJ-SF8shY~ZgqPT~!kY>93BrAOOa$I2&FcH}=1?T);@;am)#%b)R2g3TBY)lQ3R&gY zT$qUdSX42oS=|cvYK3R09*kifa>H~ixkELYQBN}UR!TE=wn zghCA5adUrbp|USa=9+IJXu?2F<=M53%JDC_Q9ig2ymSCccDXE`i+HD1u>Ew-X7Kq; z;v@2v`~bDk6vM=F7EyMeWS$LtNKW2vCE*DRbX@_5)qfs0;xp!X)nJjfrdiX`wq2U) z=u^aH`7K&HX*S)0h~e`rasmLuvsOs@^?X7G(%fE%4eSlq?Ch77Q%Q?BtZ{y-Bk z;$t+B)1S6ff;EAraUyJR$eYO!UQfxip(I@XAN{hnx;=>sVdf*FA(cqnFm<^^(HSDt!!ncigJc)@qI!j4?CX>Wa-zCQYNr>8oWaF=MNH!Vp;82H zn2f7twTFRIrDBW0V0vlG9Z^b{h(@zPgvluH2$N_suWg(Ye zL&tic;ip9a6)@)0h88etRXyNv1V%kMrlWC7}qlIRD-&2mrl_IjJYtBf% z1clFiDy|0vF_X)DwCznVTDl#51_W_n?5_(jN&){x0LimUamW;SQbr4W_=l!3zE*P~ z_yi)5oYLKj$?vHlpGm?^z;iPX&5 zU;+wAP7P7cbUadcnH?AFVd``py`AcHnFIQBNKOkQ=qmNK+i#l%2g+Sc=v7}-)Q8-B zm@jp!`Gbc%nNVb4*bSij=P%0w21pCArEipp4iow6qmcL?x5&rt^ zThrzSVdQJ#XPlvZp5(bH3LIrX9D4kEV1@=bjg}TzdCYWH*`DFVW&q|e`kV*9zR!?? z^*>#(ddwLU(`TP71F@zJ;B3*}rW8{qFFeuB&Ta&^SCEk&0UkXgjaWl<*P3 z55a@fKXxu=*Ep~cO}j(WS1toIMD1hYs(>zlGh8_tBT*BG^0rW+Ucdq|b8-04h1uk3 zAe6lNv)VzV5!<;yc@z!4g~j9P=fBD@1;ZYzEUk9f`_KqP{TpqO7t=E!1$Kjl)-T>u zto-qRn!(^UO`*%bsr`%M6puwGE`a41J;ZEgR-l+z56vR17(8l5G6KJq!%l)2O0t*B zTdsJ0f8BrVjjt%b0Ai3NnccnIpLaCEK%mm!y7;vkEhdk!jT+j{?o_u~yDz;N(1hz7 zR)NP07q16Gip_vq#6y5WW%68On==GMwL1Th3WVhxF0R`^pOf7U1SI|7;|eh1$UTXW z4+#5$^KfDjVJ}8?p9}*GX(8Wnh3-qIDX&_tJURiUWtNRobT4P5(8Yle+%X>U*LG<4 z3*)ReK&GRIZ??x6W)3tN4((aBgmuLJa@hUrL#uvW^7BDV)_7atkoc(@ED!WxFc2^5 ztit6o6YBD@9y&hKEkVf7_yhpHEY8JYuC-=W1A#OUZ-h)E6OhA+&GSjoHh=~`1Xz7K zG2wuGHRfHy2Y@%W2iV0aV*CB&L+ zOQo_sM0-3=KjhrA3v+Q)CD6tP2|*VRYQ(m5daId@^OIW^v#^m_dl!2iLB%Q7Pa z5fPej_OI)-qj}K}M9iu%v2lJYen!71@Q&B9i6QH9_5f>1uVY1VuDE~7RAA9@O)i9K zStAwTOL8i}RGPr=8RLjE4k0U-HGX|&{SlA*xTt8}O(IXMblS&HX#o(}Dnh|ON_|fX zdlPwa@Uj2S?pX~p(Te6+-%Iv&KT@H`un-MCtj+E=2)z&1%vm<}eM#46ImBQCNlzjw ze8RMU;u{I75EhVD)}m`O6uz(=a1QZ^ld>^%x+Zz^zROiaQ89za^SkgUkWUrmSRId= zr2pA2vR@T7u*N*JsaEh9=)`Q$kfHEZ%I-@%5H6Y^0s)G$sxMjKkG8pv;#vZ;O$Zbw ze&X{v)21>9DhF{rc#bzG4MPkaE2J{beCk0vz>cYqmpYrIS8YGFKi>@h0a0II|Ed{n z)XDXDYaBKFT(II>D3FazNs~U+TfW@N+oU`mu~!JeBn`i7|B@?D&n7TD}ZUmJ@I;#(-!13dYS zzsv#K2WU2I3|6!02Vg79}k(E?_I zLWME6{9O?TxkC{vpZgY+ZlEB-VQWRTcj9VF=~<$UolTqQ*(ukxQ5}3wA>{>_*Viw_ zac3W7HQ%`xa(73DZL6o##w7FmQeqzsQ7>#hTWE!sMJvO--6Btp8#`onf*0#y?H#%G zVnaRhxL0fjbO0;dirusPS@ox!5oT|k_>h~mI5n{BTiV6z+*VV-?7*}b&AT@P*_XnG zUf27LJT1}m4QTO;e&ZhBe`|;uNGU*wKv-;KQT}yy_tm7Ga^6;3vv|hVR_}6F?&AOc znP{nKmXQjLSL{6lvaQJco~NQ&Tcy4FB+FRKLcc_oYYr1C-V@KbAq$A4@1A#2(cGT5 z50?D_Bp!aNEXG5a6i{8wD&e}2)$ed6~6{FZ=9w$F@Xczs_E z`owta6|o*L#(5f+v3A=NsU5g0I%6o~0-*(A@v=(V=56OQtJ-&%9{}Dq=4geJFN3pM z*VAV=17MJ3`RLhv*Qkuk%uANx$eKhU_SG}iUuSOW2FfXWSY!n2aZMZ3vc(^)#rQ3& z0kcd}Iu)4d={a1+(ag-mdk_(I*|asL#rTnFO4B5bnE`~1faI{{*09khWPfxs`{){V z36G2*;VpWiBqeC;xL4=kaO z1j;a!6Hx=XXr&j8IH7_Luq753V}IsvytBR##+x9!T`Z$@>U!YBq9t-MR>2<=+K~V% zK2X6kM)D5dIp7%9+zznYW#!2=d;qc^2Y?vJ@V6d-fK-_jl7snB)p`9t&7|C|?$>a_ zaS*vXtujP0tG*nqzMQ2EzTbfbW8Lh&Lx;IMj1RjQ^DZ1q@XYhb09GC*^xuMc3p5nxj6!(7ip)AY?FdGB~III*teQE$?kRDS2 z#5YPx*aP5SoOSDA!cI^?SSO=JuOzfQ%7lQ*Lq>>R%2x=-ESgY9vl?XDfrD%mXl9JD zWBsBJWBUp0oCEAO7dKgl{Ns41t(MIU0Z{PZ=pHMjB=E?ArKzQ2H2`s#K)u0{NS3_94PG{BJbX>(=hEe zxI`=K8KG2k(^m@U6-wTeiV5Y%pAGnK04=UoxL#bWu|1H70`-<>6G|QPO*kOnvm2FF z7sM%Cw+jwsYXr!K8@xkO8+@!LHIT#mMEm*mvTSQrU26D|SB6pZi00_+QVn(-oC$Q%|hon}1r&>|`Mi#Awp z{wf51P1mC+1L!3z7vnLZ;uhiI8j*50aw^48qb}k~TRXQvq8O5fbWr z9%MP~2auOOZ6T>wSi<-Rr?MK;ATq4`MAl&GDe&n^JS*Sau>+U`NXUL+=V+$=2PMl0 za$1Oa@=yHm0Kb%5gVFD_4so|j%=T6S`cnXlm}w^daQQP0cW@d|jcD^@xqO6T@c93J zQ`N2x-Ph!tN_~?-=Ys{4EU9e$$d@WF z`6TXi^&vKVx_Zg#O4{ycM+^F8L}v!LSmC2Vv!x2^3j;Y4G;VAVR^PA9w^LykO7O~k z3ZTHghNMi@U~{AM=VhF*A_JT);V`HUZ!Hx}E^P{QNz|OY&ggxfF;dc`0MK1Tq;BpD zpF-N;Fu)0Ovj(f4Wf?$nmVnqxQL(Io^t1v@9%@eo;B}NW-Fjqr$Q4sy0%7cUTKcU= ziig}vi6;OWVPJ6V_XGo)0%R;dhQBtkw9z30+TiN|x2)v4Peq+fAvq%taE*D+^Pvu~ zkxuCM2KK)|^7Nx~u3wagoo~=Uju41izdV8N%S#@@6R;n8L?j=kzWOZ#bpfTvK$I*b z+%U?)HyD)q8i+ku)6#Uem}z*(Mu7?~`>WynGOhvXm0c@CW`I|EGDtHQc`z6UNa>^@ ztAl*lQ59wlKvn$16WCm8fEzY&B~S)W^P5aJjEie~7#>5M=K^xq{TC$S+nf$S_Azu{ zxigpKA=Ux=yI!u)-q-C**UI!7$Qc6!@Qsl3b>+@Ep-x9 zzI=na|6shKu1q4mve_%2G6#+Mo%{wYdMq5}M#h<$n`C1oOd9lT&u}6X-Db~mzj3vg4p$PQM$fRnf+u9#cZSiw=+pW$ zey%t(^YamDT5Qi#-SrAc)iReOU(lT~E~*fMe}$hvD%2=OE~K3qEZ5psvoF`~ti5w3 z?wo$>9ixwtNXJ@`yS++S?0wyrs?il8>?j7)K>=7+g7wqh7p0*5^k%hNpu^F^v}Jh} zFVTErI{9l@e#DoDt={kG?5;Gc@0cw{rANcTaR^>>w`e*=?L0nHxNil{p9v-xr$_7$ zO*ran?j6|O3hMUeE?@6g)d==QXt02w|27aa7rc@la`@o}Q`|eF@gDerJ;{-Z4&ZSC?=#KNmjPL2iaYy)3g6E4 zgL0P<68&L`88yPHQn%755V^pfg{ya;Bqf4k zW9Di4B(?fE`jjEEQB<9KJjkM>*Ltx_Lx~cBo3UMBAu&mk z16nze*q8A3ui#lNis*u41CFp_0bWJ1$8b<`G!56Sjl<>oWpOwSUz`1#P#a~o@9T4U z@oMj=3{n7oq5~bJGNJ{^Rz942KKW1%mm9tfl zk&b`16@K;~&T9+g$g%7@zJoUnu;}PJF<+00TE%EvUC#jEY5})w4$d&+R)__4<1&q! z(*xz#qqm>DZ}B;jXgoc9lUBvp5!Glzl*t5ZOt|fv+)r9rfUj`bFETA`zwpXun;0V2 zIrbAVzE`4M5Ig%Tf%yceJEQF2kay1qG||Fr{QY$6==bIvysuV$J*+Q`Vy_cjxz0pd z2iPGdL~QNv1aw-cjv~SK@vzSeJ8w?y^LZySe%_^KaS%s>Ndv&e=5^p>vtK16kvkyI zQpEefCEJr<*MEOczN&QVhucyoWHYyJA2F9YW-nEO^)s0VO6@1lGKY%6v!$kk<&@L$=4^fTAf^6Vd+ zFT!hd#UL2ZsFN?!-2OZ#pZa>gbtk9D7(Se=MF~`@N*uINV3_Q z+&zcBXUY^#V&lwy+ADb!O%;@TgeM23oW6Uocz=cUnKftqvz=7#Rq%uHcEHbvzfWl1K>KrznJ|>*fWggkJaQ1Vo!Q1!pdW;3xEC-2 zJ*|+Sym#=YW954+2Jlv-e?NM*u(Q_TF)1{m&2#bPf7AxDA(V zrc^bBM{%wqzuxQ@flaAj-Ky+m|De21J{t><+cy~9|E5kET2NR^d-Fl?%|ICXOT;?S z7vGTuK>aZf>gR-g5mZti*LbGW-Ud(n^L}whJM~oynQHUW3ry%!&sl{_Ur5fE!n6=w z;&;~gvN-wDZAJG!s=ZSs&OcJ-JPWNIn;!4A{ghAF(pbz=n8lqgA|9R8mp#+)y^g`bgSmuL4hB)$^2)iWq11XXFd=UQ8QIv z`LVA{1>lJy7Y!xbuAjzsyjyZ);p zlpVYlZ6RLr35}LA6K&0N1TFbONzJU!i@OsukmnO11|#`BF}^?O8mr6eS^8D_r`@Bk z{S(WFR_B51KYFZhEsj&?@Y#3F)+;lqnyM}%Wk}4m&WEl%i>~f~FC6{aRC2;ZxbbbI zSd-aGlUt#z#@bqZkDXTBLi6vhH>L^Qj&Y|NXF^I^g4m9>|Mek2e5c62`XV80IDz_` z^?w7t93{+6ivLE0mSd>(82^p#W?TYF{wMqs_dg!ks7@sR;;15sjE zk3GVGxHyUx8nj#6MiV>6&w06g)!%XvRUp?MLrl>v&-tF15Xy-HBuw?XPV+zB&c*#X zN)KZuT^ttyd48&XY}xQ`Z6xL?PQ1^dGv+@)6~YRpnc$4=JR0pRn`v?1vn~gPLZu`H&p=9^8?tX?_fQ23@&@tamt|wz2m$ zUfSOR3R)(Fbi9zCFNMtI&2h}b2}%8}b`#et!ogv2qo|a`^U6gV^Vi%_hClas?0#+I zo7dp;4cMn6;BBS3{2)8mLk#H>hwmO^5|H10foDX1kLptys=U7OG7Fja zdqK;Xd}9^c`JjQ4x0}dH8NTxppC6ns#Ut6>{4JxO#yH7t=>vWajjFh@t1(mmv@Zgk zHk;WvwM;zxDER`mF!RT{HfPVs!iG3)DsO%Q=>!a-qL*Jd>X11xlZ{eu3yeuv1}UZ6 z9c5>Qn};Y~abk=-+3`}%#SuH2Y?eD?lxjp=v#%PqMmQfR7W z=FxEeGUvC2_p8k^PNT8^q+*>>yuJ{v$8+yoFWqin&gFp?lR{@XJuaYmG03q!|NI^C zRIA3;2brQ{GaO|#ZMpiCgAKi8{q}`^dvjM3+Ti+aR*u}S)vK^MZ6N;|=l@GBtSd+6 zV;L^%??R~C(-ZXSGVOE*NTH)UzA zmR#fQo9nJ&aTna}@qOi~=EcRBRezPLe`JGQaJ6|>JvenMN%u2c-IABIO$zRfHmS~} z7DcJt{}z8TMAKdA(l&{BFeTlbm^W@JRe5Of)Nj&#o3oW_$?C%f_w(JzX`e6Jy}a*( z{xj=(`Puwi-_^fHcEv1r=`CWpm5!G{*LPTr3ZM_|3qYWm4um* z%AO^ApDo@j;U)epQ*Bus>DB?&>^bXopXloOJ731&%~@@poej4==o*)ydLY%0o)Tjh z>vC}ggRsi|q|7Kpm=xONxNwqGf~s0ge1AR~-}ANJck@K`pUEjZYgY+Oi|R@`k7d#w z>~#bo@1plBwBk8oUK2AW2PeU+6O%v9qlHSw_n>*!4*!;k9xGiWE5-A!=NuW-!o-Zd zrJh#?@$49F&XeR6G93FwRSaq&f=V$->b*Dl^8cl5E*T54Z1!6cnzShxc)0Y}#0=73 zEmPgNLEgwB?`=?B{idEOTMBKxjqWUZLT~23ZNmgz+jR9+<20#Mn$pm`It#Wo8Mj*K zf(Y@*C34!H)N!a~d|w^rsaa`RO-TA@me{XFx%5fp5HwCMhbNI2!zJi44Mwk2DLuJA z3t-|U${}{0Pd428SAvmt@8Vw?Aen$yI?uz>4LKbpb~0$*8~aka`?*X8<=BcwzToA8 zEo~jbO`K9sr?yBzezfj1Q~(nL4Z;uZ;{+x`Yv1X0YxwKMLC2uVv6J!>`h)Yv@=xWY z!DQmDoQ_u=aW$*6xrEbLQmai7R}@`vUZ7;mJ+mu6KQ7NK;fZKAg}%S@Tv#TTGHJmM zsw3^NkmNa?`&1$Ab7Yc?r**mE8F|V&vyI7Shy==OL$ypQabMhRsProW`J!L9q*7V? zy}_T}L$f>Tdb@V05UQ6Yq%6zt6%{;kkSkJag73L`5deDxv;G z|0>-WJZ-oyVbQQ4Of*IU^+5mpPUlBL2;-xMn(NPptWPJ%@*7IbNUA)nvdNyAOL{lh zANiW^s^(KLaIY#ta?T%a<`_&;Oa-|Q1aOAxhfMH_ep7su--&;MtXb^s91qFqw?%GPD&Yyg<#0mu|*MSB2 z^MKb7p~qeohMWMh^GpD(iku1sAMPOuI$T^wVed97n9}58< z0c(FNHPct6pvM8;;4&ux6!=~})R2{kKufcP(3<8`G7#466AR&4Lap>;lDSYd;EFdY zgwI>WXvTE_sAUU~5!A8mf{lSPv;aXF-9WU7i&-iGd-K zh4TT$^;E;UQ0g}LL&co8=(?VPZ1jN$OV*x^pwoICC9v#X&fG);I7!_Y_+d$Phs4lz zAoyWPT%8XS4qT9&2jYQA9C+uCpeX<}HL|Ywf11yQ4ZYir%pYjuJu)Lac)aotlSZem z)crru;@`kd|D}OOL4O`p-1tz;=N!hbBP_zRBq1Yk3xpxlTmP{~Y7st%^;@0?sO2}j zgsv*Tjd>tU3yg-Q{1bf_4JWn%xU=a1^ofUFb4K$v3ibf;=6Cp9@E6^Y2hz$&=l46D zA*W^JMTP+5pTbL0sGod@oC1X2?fk0YQlsX$MhR?II?_UF7FTyV7a+j$;mQ@0$1REv z1i})PGC#u|RyPDJ;4ElxcMrn*iw{6WPoGXTbZc+~NMiGJ4wOm9{cDO;2zIQxtpNOx zx|rF+%;f&3x%L|8;eKHL8?QG5RyH^auoS!D z=kkb$djUUyeqe|nC*k2J{A(F`a0f`FVp0wBV_4))b3u)xL6?Ua5+P^o$uy&cpBEZ? zy)=^Clp(D)54`tyU0=Gn_i9p+YE<6ium`T;SEV0~*2XW$mGT8Wo7bBdk-2^%JY6%6 zDvbMT%l7<5y@wIgH33St1aim-z&&bR|?&{=3 z==6Pl9V);Iy~<9qQmw{WQP10R)27hBT5;1Ej=`9)qT_ey-P20D?yV{KH8!iiAc8EV zKX|>FoHBO_v+ZL)mt-;u)}7nad_~5O*k**!H&1&)e|J>yyouUf_#iLL2S!h3jm`lO zjy}21_)v^U>IYjJ_Wr0qbLBqa>om&aa-E0EoEu=d<1`I?>%Ge6HPMg)*!iAolIen5 zDB;&gF(F-nS7S{pyYJl5V2ea}f6(jlleHwuYM?XXT{hMt3d@SCfR8>Mx>VCuCZ>q@ z*j1eG!fE65&Jwmo+AQXMNPP~xd_+4{WNSWB|((GPFG@0 zC0 zS>nEd|-pSvNEtZIYa>wXYwBKH!N|_!ClRpl`@dwzou7x8bpM2@q#@9H zMP1D9S1WTgABU$f>Jb>u+9TCv6;fqFC{*=k#B-kyefU!Hn{R%cB!VfJV^`2b zuBKJf2}cEPi4FtKB#W3~^2{hJl9G{H&eAgKUnKXeDANTuZfj05nX7 z#YDRi_k9@VGj9Ofvz^@>;ow*b=SW4CVrtHteR>I~T@L~Is# zLS2nBA)Lj`zwYFPTh80vpfJX1N!G!kdV=;O&6K?qmD>0GX?E912RMB=S|jndsaH2? zf_a7OUpGTeUl3u5Pq)aMXM*~0k}F7iKv`p9pPw!#k*W)xvF=Hsa!|F@4eTpa&cQVi zb>t=+g1-r7CF3*4qI~UBr(9@DF8l3@s^;LR&It2whJSGsUP*lKzD%od=f|O{2;W%O zaHB*ldkRM$Q_^o0kdL_-XO6Wf4h+$!&f;6vfRh`zV=hi;?$h3VK(k7PS17-fJCO*R z=2&y17@~R^(euAZ^mMazB?OlJ~vI%}t^9I4955Si!8Ov^bY!)Iml4 zfU61LUCM=AkT=q3zW9gptaY)#sv{cZxLFF$yXBW(y!>9}8HU6r^UY7?tu^(;_!lmw z+LTDfl#yl;c~&um%)Zth~ugA8r&irucINj%0#r-TW{g-#G#^$1AS=`{zbj* z7aLnTq)egFt$*WK#nr6e+1aiyegY;6?G0gSF1-vZU|LT^?X@7XY*=@|9~CJ^NLnHXe8J;6EyuC(J*MQd1;<)oxRTI zcf|?4MRHUih^LN39@~qq&n|Ul}dJ#jGbox%2wJ!x{d_CVDJ# zYANfCke;qUv&tJ$gL-b?R&)JSVcJ$Z+*Hd_4(fm|C67HX0v03hssMkbPaPC-ZKwRg zvvNl3c%Faee!3Z%A~@c%aC1>CWRq=-GsbHfu0B7tl)v=O?M<-r(McoD3`yDzPxI8l z0C76MrrwzAe2d|DqVD6{pRsppT5j#9A{7OYRaxM!sKoWO@zcZ#r$X?1QyFGTLxkd; zsz4-0OlC0H;cd6V8rr+3r`M^p`noz)JSMx>j}0B21glr6pb0Ch#eH=RY$Cg+EV}OG zlstVj_c#^og2jG47y__7AW=c zKK{-8RzXSe6x&Ek4E06dSl zL8gL@PxI;vrJSteBlWzp4XI7X9t&`sqv-*KS?dnj!J@y<0+ zkCYF2pL?L`avlO4S2rm+*XnaMCh0)BE?m3d2BmSCY#~QjzVpc_xt+U=Yq{aPb}4CT zy!-&o80)zVf@M5~*6MPa1ETg@8k2lX|Mfw5E5_q0A_J1+ajEWe;O(cF&ju2{oLKSK z1nL_ZjUYY$M88Pj_H$YasuzQOw8(1gjG>=Vx6{s>M~>Kw+KS&k)=nn{MJ?neD!g7B zXu@n0NK)XIne{vE*c;@kFUm_`jz=UwTuD=DT|?xr(*1jGb{+6o-u`H5#cN01 z_|q|px6{RT`c|bWZf^HA05N2I?!@QorW5m-XFJM4P-Q>Y>lLMe0=vvF({SR>j?TdK#G1Z%yftf?$?wy}?KgV^xD-cU_!kCs^g<$e*t(W4k_SI+C|g?61v$0+QLjm<)w|>`L5-^i2TojO2~(3+9nI zt+xvbP#*p9oTgq{c_ui_g-`A#&T;vJ%Xa3rq(xTrncKnL(Rv3CtAKx>QE%|r?qpf{ zc$%f){1nBckLa;j;e<~&my5d+{515^68W82n2e-OXaF8P)F&|vcS8FEJNL;axn#Jd zY0@Ln;;iTSwfHHowBEF%w56#|%*owO31;(=3HSQU9c(pNIiWu(OnN(htkXPCla*AP z)j(Ug&#+Rc>%0A4#czk(!$>ZZluoBzi;bc&j>a=uf_XxOckkF;*F&b~DN_QE^Acq$ zj%RPPT{KWcRvMo1H3HeQClmx5y`6o`(Eq0PDfwMLluG-HyHreIXiBZ#n z?%|h9y|dr_UcFLmz;yj3A8;1`pTwS6|x-iUN)F-kYO)rV#dH8M`qdq>l#r5S;=J2LJzx%0dA4!zOQ z6-dRW76WS{HGzXpP@ZZ-+!;y+yV+o*(yyby{cZ+kPbBd;4E!ui z>%bai{VBbRb>wjOQ0<^w-MCdKkL_Lqry|vJg@b#8JLa@%N*?Zmr~J5t+dn_F_;u+m zEutp7S-lr@>tId&qCFTSf|!TPatv=1gcS|fewoyk>4eCE5xR5(inUeV-Cw?OEB=}o3L`HwC6_1e~#=XH#1x6Tjwzf8Fe zMwhR?g^8Kb5{12u!QYcV^BqV+LhCcnuSU0YNw=40l62ixIuH39($hYfV?X}Gv=oH; z?lBm=)rWMBvtV3AQPXt^|Ji)~|@}%hGVB0sJ#cr;(+rFe&#w z1l>_BI?LHx`$NaHAr2g_MR5|gax&F=R@Q{v|@~LGO{48-AV7l{F1b7ZgF{8M8VK(#8 z#F9!kH~hGbVaZ0Dw(0L)H%ir3H%T^WhSLsY&#TrN{3@fnO}i=ts5yBIa9Ua%N1F~* z;*vQn`o5)N5Is74H;=#*y!RgdLZpPHzRJI$NZsPu&Wzr&TmLsrTlO;Sph(i|NJxps zYlg%8I~Ns%F|mU$OkCWo*Abor&y4$+QvPL`mD0iXf+1U%v>Yh(7ww5q*N_xRwMg9|89<eTmo@9!X)e0@B^#KOx{cD(1JE9rsm3?q6lD z+*EqxIsN>P5?q)+g>DyJ<_1<&*XrH&)a#OBNS^H#DO~3KvUvaX4)$}2H!>K51@vp} zFS#rK&DI)M^_l79a?=R6awK}W=E&OYecy1%`5{;O7ejY;ybq~%yNGjR2yacstJTc* zqXA(3i~eOd7D15KUb<3!ppV0iJmFf=d*x)7QbC7Gxo8f+Ib1Bp-?QZ2O<9t=^{6rx zh7GRSdr6u#5LMmHd6mn@Z>bV{NS)qd-J%MDrp3tnwZqfALtzO45kPQlaV zLFC)GI>NY8KRmh~2YFA>+9niU^ju$+U+??!=j+(DrYM^^>ZaL>7|FQ`mz~`39@*jE zWnKV3s&^E)1|KciMU54!NkvF#+<&=CPX{Ey(kb;NYpkJdcr&FE&+FCIXnYnvhbjx2 zuFR}h9wQ^BBF39$%1-5|HCxnNdm)pdtACBx&bZj6?YdZgd*`}gKE?GT<@>sgI?GmI z$F!uCljCgnkRJEJ&yAt&ZO1OS6{1<{x_-x;@2red4`C|o_6}Ef*G^bin6nSD6DIXX zs&PlINgEPtZvCr_^#8Q?mQhtkO}sdWfXE?4r0Y;hcSwkM5KsgJq`Re&R9cWj2^>Hg zq`SL8Iu6|_4bmYcc{lpL|GVy8>;Lh7xNF@{JZIQ@o*lF2*|TTnx352Ny_2y!%V6aD z<0M3M92(GWWnGvyZPH#kAZXaNDRU7Ooh?}06q-k?&WUc~IC&V)d~QUi4MsB`rE=3^ z-(@`QYe4wi=>Fv{#VO(uf$du7@h|RCFx0K66Gfk7j48MICH*O*hWD7Z3 zcVkk1)!MiU{$bY4cm-X~XmZylob}qwSJR^0B`L*Vt1xe^O%EI6LdJ}{e>kPEN_}u zsl(NdjV$aevmjGits-0un;*<}cSVD(u6@tdJ>hc)%45b8cXJvN%A%F;Uw=}MP;=x} zA3H2t4~NE!_~-Buy=xPTK))2Y8Jj~u>I`c+kCV*Jyc5z{hxZB?qAl31-g?L~iWoS) z6g2Ky3ox_(*oo08 znTM+TQ#!uA@ly=L0Kuk7+&h_bM*};fZ{GaAj1sc#ML4y|`x(ekT-{t3c+TfT>Xt=E z*jKAtkn?;6$vji|Vl^#Mg4293eyCSxpygdN6}6L^WY76>P>^q=@aFsF`h3YroH;+$ zZ?&-32n*9}VbiWmp*z0~vkl)z4U{ZcbLQe*xGr9|1eyeK@XGg1)|(D7jUHqscZ#{m>+rkoEEPU%l*C5K*q%4g&{o$_?2SUwoNztsb_8-ucp~c zeMkCwxWVtV=2x)FZj&b`!o1k{DU=(@8>CKBG>*a{ zj!1EE@@$==qO|6pC~mTpABq=~r0-nLeB;b0V}n zT5LNEgRGpRezMYe^#Px8GmDVXXN!}WN7Payg_sW0>;%)<(24&L9G2idc;+%}Tmz`-xUX2xEQg7RiYw@qxihijPRX}oYip=gA2v%oD z!3|H)8|Qzuz-4G1h*zJS?}R!Bkk0b2H0%lxe>n8Uzw)3DI5;h8NG(y`c{p3UN>4|; z3>Vs~Ou2VTI=)y9r4}%0BD9Yo6C*HrP*tw`l#l;rtwKn>qIQ4JsI{`PMrq24Z+ne& zd(3#R9!mXrycp9Q-?YVd-Gr?}Gg8ZU_wbUvcvU~90d-$$(^4%;1YJ z$#>W3*HG*}^7{GN6=L{3^3i*$p?;V83nS@Qv5&2_2o%C+XepfAmI61E@XU92fv3E! z6(iKu?w|(|s7P44vqU!2sZ3Ol&p($c$B-pQ+3T-y?~`#xZP6ZIdg;niHLe;S4%Ygj z#^kEpa|m#)+fwXewezjEa&Xd9z$0GeL|~#v=t&FSQ*C8Cy<>O=a>xyBRp|?JsX@Bs zB7TCLWa2ZykH7(lT1{nALr&9*4IFr#?w_l#?|A{Zv%2M%1e@*RpjN{HL%p5YTe$G; zqZCD>$o)%@9aH!*(#^%O_&e||OSxg^U1G&FfHZB7v&WEmDy(bZBNV8ABh(=BJ`}j$ z9yY?2uF?B-Ze5R;k9>e{$_>`WOJ=7*ly^zE-3ThQDe*v zgq5x?M{-uDr@p~l6zzFr6sr2-k#S`~OgCLg)EZ<=a|Jt^rmqbZ{qcws@Lz8UYTV7a zJgNS8exB>7b77bPnQ9=cIFBGx9*;7q=jSM2%xPiI3n(n-ZLckY86EPHbTra*~5)!i8*Cw}F931x+QtA*@j z2ea|^HuHyHe$8*nF*E~LwVFSCU;0a;#WXHitWjQ5QtVgSQKjMT2 za@+>(mXXG+WP9z*V(fJ>hiTBXtbu1OeKb@Gvjbf8v6T-2Bgqs7*KMKP)g-(zvH0eN zJvd=ebid#OPmiskm1X@cy~m(cc5?XYbKfbnjzqaXUqZKQ%ja>_Pv_>?s_-U;N#XQg z9PU7{WS%aM4{!_w{W;=F-%P7HrlXk0nR0d(Vtu{-iawa zY^^WcTCDDp8WF4!oob;@Q6*F<*0XEPmy>C3N=Y<_y&0MAo+nu9(N&xX^1sP&RiBqV zmq!?>XGSIeYW4vOYGM%`=cYqVt;&Rb|h)|+`VfA8FL$oT1I=>A$EqeSB(+>^W>I7mJ zwuZvxJjhV3ag8QJfM}tS-O1eCcl^Zv9KHkc2P>od>+{R+Y(LD#C5>?%rZ%a8P9=Qc>nMftQo&-eGBKF}}s&5b(%u zjmG@E2dLC|!}55z4*&!U?vG;>WGE*o0GtbHw(_mMM)5)g;F1frWSjdb%`Bim%h+?| z&OQl{cO{2!4wa za(HqZv4gq%dSO*X@b5=~*E+lX(NHTlU=fP8k%Q)kVgXp(sl?Q?{?c3wgw|BA%D|FX zBp-D7sais&hL*$RefXD%Vm|yb;Y;6O@CCsZRyNF=)Cc$h34XpCxxO#qAcxSFwi4aQ zYVzWA`@XBb6!_Pxt5D0Ha?O+*spuEZubvZ=5cH>qTvv*kS;Xs$y}nGso-;<31>7P$ zk1$EY)x*r3DP=tEYc9JPcpJ)X@kt2e=o_@Iw-3eChO$I?W3TrPn*w$Zy{G_dDd@nK zr}E68xCpxet7nfx^u2N~=pyeIk%G?;52{*q^h4^S@s9vprtC@Nd+SZpGO5s} z`}OZ`2y27T$v}?@9M1>?NE6JOhzowD>F$HTmG!q$9F$aGEyQR$=pm}~0 z8@88$v9Tv#s+AAuMl5mtKmt19usocLbL2ZKLEsS(C(}Wr92*ZVwvlV1amgpcx4r*i zB=4sld!nj8@%J}HD1ni1|N0{z?j7ov;$hFcp^LU;;&Q>&{0t-4l>MpFcwhv1=h4kx zXNpRVJ{cAR)6>f#{xi6%eEkvXOBWCcW7zG9t>~F4D*V-ouzQ=#6ZGLpH--3Se>w3z zvX?F(A_pyJ`IC3OMPE7b)rMqP{?Rh{f`_DO7(CRDJsIAwaBsqDjac{yI7^nzs?(S> zYRhLfyy%rhqUMFT0u+(7C*Bz?BM7si^N2n_g4%MBzuqFwOQz1xy|(?v)oM~BUy#3; z0D|TB_Ho`)zCF**@e3j(8U7Cqz)^fgDuVrHB_Q@BN^GpWq_im-K(h*ZX%>4(_kv;Q zu&r>%Xfv9&2209w)UGDae$bwKr;pZp=0R8a91zx@_Q{r=4YJ-4$dU`KeuR z#l>-OQcchwI*UE=VkWPkM6celHP?H7rZDFF>=jXpwN)41e{R+xYV zX4QVoJA2&>N&$aqiS%h}u=f#GcGH4X7;3ovdVa(|h8vT!146Va6-}e*R*O{^oR5q; z3Mu&sF|SYu!H5T!+wZYYq!IMGk2NxrxIjj zOg>Mn#5!qSn<7K`UdhSvm!>8!fWLo6saeUi+uKK3`z~JFau&>)#`y#GrLRquP}yhB zKE;j8fyzT#fGkw2Fx;H$+ooE~k1ygGF=f54L@##{VGCl`REA_zo_cgz)by^v@R zemC<7X#Hdh%b)>mk`FIy^YyW*HQE>n?T;1m0*p0N2I3oXy9)R!Us=|kFN-X8Rnwk$4S+VDw~{@w z|AUZ=kJdjet}O(b?<{ao#CJ`&2sJJC-&?@4|sZ=H^z~(IJsd4;-_IsY4AYcvZ z{mVy{Kg>f$Nyd5+Nd2?cT{9&s7t60e zQIl(g023Lj{L#7+R^20OZ2N-}Kb9%p*!Z1qL&V}@P<0JH&BevV;^`k;Xi z1F)k{CH{f&&d=$2dJ!>XgPJs6a_Y0*LbV4#viNp1Jn8Kcg*BJ44f}dcxCi4M!EPpZWf1mbKp&1yawqVV^*LPIJM*wb#{}+e8*91{#L$}Xf#?S%@ z{EICRSnXpua3922s^Ev4Sb(3ck@Pc)if{>YfQtg_`-xqt$gTR0oMf0>x^#!ZNLF4O z0FG3b3I|spZi~CXeys3WFx5aE0G`R`_+FDcDgmQ+;YYp+5`6{+WVC#Myx5aq8Ppx4 zk_Henu%UejBDzT;6#&>l98+V$!{_{kku&`<{+CG{Ca>G4K?Oqr)mrl-F!0cfv<`D_ z#<4AcwfCYo-7>Ex4DHwLvxIQeSeMg^v@_K0F3jbQ_Ec0WTM8y*18X#U)%MP@7xXIG zXoCl-Z&FFcnNEF=?40d!CR82!FRI6mp0uc*w3uG8{|={?3?86yV|>-bHGO;gSlwjl z$CC{>VR{~C4>pZ!-<$lQE7rfDyTSg&57eT$Q$pFF44A$eU3y{!`J?LR_Pgn_=_W(3 zR1VX0kdHI{DWM(Cb73s(xk%Hy3F4>KdeX4!DOcwl;jdv&62ysX^rU&7ILy<;ABf&t zga~z`!^p&kwbB$UA9()!_DBB4WAt+2^iENNIIxf<2ivG$WzOEn`H zX;|w;l902rmM@%BAUQa~3y^+P7};|+)yh)K;T?VdB#pC{6$!-DmY+nCr*yTmlC3fmNufDO z3&=6pf5{yD&&ryR)i+|VF96g46~&sh+}#)38YVSF7P)o)9i#AA zTMUpLQ$ahDsO1cGVzb{>5APs#pn&8&Tvz1X3+L@m-qMp!Tp`MQ-_%2)PV$aYm7KAK zRO=3W^p)je#aUi&jW1d@7?FpI5O!49W?P5bnrf_!eE(eiH0Oi<1pr_xxpL|I9Ps9_ z*PEw^K(lBS5%dZAfgpRurbW2s9Q!a-GRPJK7$@fRrM6s+^TzC~+x3El{JlOr)%PGd zq0=p4v^+iel0j;fX2=f}`V>gc zfXLw6Gd96T9@03F*r~+s^^NLsExSAMRPnz7wFx0P*fnw+$DOW5_JgCB9F*_`C6n&4 z7Dr<+<`JNi=&()N_#tf)F{(2q)pA@|Khy8<)2ZNKscurO`|#2BuW~PII$l4mAP55g z+o?ZJV;P+*wAAfipx{hrZ<%Kk&G|}QG=TT}JgR-FfQr17dDwyx0~YmS^pqJn9U2)b zpWc2Z9~nAt{POs`WiRRvsjz{P~ZfF|j*eI!EzBiHOO90Io_ zW1+U$f&B4*XgB2U?r!Bt>A%aebP}13!zgjnQlw8)CLe^kKG5&8a@&6M)N^-#ykqo= zL!)_Bx^FbbGzbTHFUNB93Jcz%@rR4!LfYf$aE3UfjEJQPNWckG{GZjj{%Pdf22GuR`%+}o*3iT(2CVWSGWUQReWw^oYKHMA`@yrf zbJO-3tLs&=QF2IVb3-j~aZ)N@OcmF~>pM(h>| zB+i5qd$9j$x#If^g*Dn%G(=dc#2854vfHq{<;rPv+S7&DAJpG=1D&zfdtgOO+( zviVK;N z-{1cEny1R{PaW z9pEzxu*lC!m`o2#Pu|Ua2|N@tcc_A##$l^D)Y$O^N?r4QJu!Qhd{@oSokS=uBZi5s zvJAJ3`e9#`rN`#AQ>tdA_oJF^0i8&YLyw$#$7~=t5EAF9&fRs zPt~|X|8>o+>H7PH8NG45le3RkYORHpWlJ~meRr2p!s~!VCyMc`UpflSD^?#DFScR3 zMWP|L^;C15sOA1yhIN|ts{8|_TDV{iPq#RNmnU$eEGY6$*C+NLaIZzW$wv@Os8fif zwxl+8eqgn!rlp#<4%J2SBL(-iRf#_cKdX1o=ucBe>!@jR5`1y|JZM-``H>l~NYxU@ zj?aem=^azsrzDDa2dka04Ke9-OjJgfoExjsdj+sBxS~o9AtuYH0uvrTl=m|#Q|ETk z`xopu)2S@CnY0v5$X#8cK?$8sY9%^?m9!j}oPsRONo2#@f}~9#b)dy)@bbh+Q%(=E zUm7N<-}-37w&gA2$!%omq$iJ-vgfs@Pa<3sb63Nb9klWA{xyVT*lD~I(VX|Qv~XVX z+As!Dc59z8aXuhf%e;s3{&eXoM`gJ;ufr0LrNW}+rZFLj%=LxMP`B9r9 zDCYZu65@Yl`KcCCxo19l1*y4u^_{HgD}H60B3IPvaD^Dp$rLr^am+{SPp9?vCpCWp zay$9aYgV_8LT^Zbu|s`=EisreFfe+;1;&{6;f0v-EhbM9 z>!~WkQ~wuDSP$X@Hrj10jDy*lY)4Jje zBp_fxaxls95EmBnX!$|e@p?QYrE*ebl}R|CJ~0Yb|1S-4rPfOGMB4@_`DlGc^C5IiFz6uB(bp)Ny$p^XXs? z7no#gdfORH9tSQ$`QJ8NpfWE0ev;@!m(q_oe&1!d-;9`G@U$?DO%(I_4Ho5~t(P)S z!A?52J%(7g2;MOfEULi8veq{|K3WIDq`QIf_U9oV%cU(wv=8*S_yk2}sN~~o-${+E zS=|}Dx6GXQS54k3F6si5`@_L^n~rC2kCn`f-hHyNI~qEQYwg*-{HLC#(Y{K#$12o5 z3HgDjD?LPsXJE2Zn0pDG2&6C}>Uc4Fc!{B|9Mn0pQcO+C)blgpt-n(q^49@l7!v$6fOn$o5TK^RqZ)EOj-uKQn z^Z})Tb7g&u+-Q%Hk)C`PdZ#?Lhw82vR!n6VrnXdI4O>i*cyb#F)djfh0O63z38c5=m`(2m zc`V4^C=GW8%*_bF7`G(J?L9>SvLXs>Q{M;r5ZcD3q`83J8pSw=vXov^m=}gIgCnm2 zBeFp6zB&db9vURa@2J!|B`WK|@?5eY*XwwjzJaN1I&LcuYe&KmBXTnciEvideP~ z`?GCEogJh>X)>EeSD;oEw$_8y5QN;mzA|2ZVE0cFn-N!?x$F;Irq9?F_xwBDf6A&c z;V^Z+Sqr*e=mD?xocb@@S(Qp3u_7KyzOip#n^<;Lgh_r1kl1Z#?T#6Ta9q9c3n@K; zOe>n4{#*XzCxp+)OW(Vl#u=~gsXI-PDdL7gRQfly|EW@aLtT!4LUut?YI&|rl?u6- zh{F~ptq=%tF~48&wzr2o{=x#}B;Sse`Rapb(*Q#&Wpv>Y%Q&En{iibqOm-^ zH(DWV)@X;cJfnB_uZoi0Hs~te$e@xHqrmgnhD%potOu`;`lo?@W0fD`KmEJms^+!R z>`}Tf818H7KWBn-2lP)1NcNpp1xGeOx5i|{ej~N&Jnu|=7nkYT24)>Pd{m;GJ)C@; zq>10+^a)oF4fm5=SB6th^XSd==>7c&-1qVaVf2n7KTeiWpK=?n%6^33(k?l!2ithN&$ViQ>ZvN5iU4 zI3!&ETJKQ>2QJI<$++dn;Skv}_{ZE@w7Srl&{pC}=^;cz(S*8xffejPRpiu3PLLiu zrui?UR+nee4vi{%g}!IvS7?A5^}uof93``^{_pt z_R$eop?6JfnMf3g`3u?ks(SegAx{63#=d92)JgR?L7e&rq82f+u!-{k!eMWaA`Gmr z{}?7MtIf0?kVlJo!_$l-a4>Y9f8Ga0@U?A_$a(iJEhU# ziya0yvNvy?D}1Xf^x~-x6|haiG;i&#waEx&Ji)&NC~HC`XHoCx$CN1v;&?w0=e89o z^GPPaIs=Hg?h-`&M*~d1`{LQ;1bu-2>o`nflHyrbFF(};u<6MDnU91-&vMqksY^1o z=i8Kl6O7x6+2d45C#~-aYttR$HLd2Yf`UFsV5efSS$3Rjn@ssX+GNk&{)RC68*VN;i`l>C! z^OeoPq5ErNn==%1zPiv^YiSv~wRCv^5sVl^$zNigDcws!pAa8tg%!z2(G z*0zE^ApT=oA@sMJGn3O3`)v!97E%Xb=_P4VSryh1#sf*#2jcVZe1_L+vW0c0pqCj| z#)=RsaLB>&5XedQdTn#3j=g`KJNolHDLDC2 z=1hM;ziCim!QNWk>s#Ak8Sm=+&T10uECD_)WJO{M$?(QrAMYlE`M2k}{Pioc+@#Cz z{YX-WhY&N23YYJivP-AnxFb0pbOf4SyB8W!Gun2AO05$ZyR z#lakrg(b$9AZe%bhNNS=_C^fomu#_{Ou|&5U!minO}Q68n%{BKle8_D z2Msg@a71q>i!oPE>H())?U`O?0o<*AD}3O*ac>m`+)0)kga>B3`tov39`5E@#%sjc zGr~o0c^Br*P87Sb*f@if8LPWahjL6T&X+-I_GD44KSRZdWpJ2ubZTWu?2ed$2TD(R zuw4aaLXGq1Om}j93g9VtgEI5e&@D|ezgG(L$WEC}fni5u4;*j7y!a}NXc@xJjgfVZ zXl9ZMwGc?`<4|463Cs5xy+w-*uWgp&lY6L&d?((_q5`*)!$Af|T?jkQ>ay%-x0ht4 z?{&ZStQ7=)y!_KyXY~HlhKsFtKSFu7K@5LqFZ{$;!b5qvkM`G3$I&j@Vt_4C8^fPY zU=%~Y$k3&U`3w?`8M@DIna*g~h3q6b^a#bUa;m9;uu|*BFw66cK2t> z*GQPhq2hT51?J74i4*IrpfxTPU``=_aF+7zMV_P$RT_i|E*_Q$T{T_ti)4l!!%o{} ztC??aopO&ls6Ua}2hK$Ba-&hashSX+917~NDD!T^F{ zco-e5M?CD6Mc2}&Geo(upfpeY(bdz|7#cgvx3`@>N+7GbAIh!4!?5q+$(Mf4^-;ZY z>Dow|VZv!O&;_9@@Q!=$@gU(`LflX|vAQbC-(kfc(5Bn*7G3%&%|hVl9+bXZTj36J z`Qo4049pOM8TFWF>{s*%fMEZ&4v`yU|E;uoD9^ihzb%;6i9z}qBFtbFoCgDX7OQfj}i;+6U_%${4JY=P@bsq8M77}z8p zXvM7G(Ez_`1fnKUd43@DXqAO&xeXTh-=o94V@FdzlEtGXgE*=G`>w_%v6MzZx%%HO z?|q1ag|YyUNdDh-zyAj>Hog&)9nlVKiDbwDcRe#Wyi{{A(swWxGPE-We^9tNxt_9d zakFuQrOGYD%P+*m$->Dg#K}paDOB~pRIsu!GBf$~|6joh!y6k^Kza32PNMLIp7;L% D#fwd) From c35d6e706f7a2affac0d7c6514f4bb49c4e163c7 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 22 Jan 2020 13:52:54 -0800 Subject: [PATCH 19/20] update readme --- spec/consensus/light/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/consensus/light/README.md b/spec/consensus/light/README.md index 79f04e9d..1747f9b6 100644 --- a/spec/consensus/light/README.md +++ b/spec/consensus/light/README.md @@ -47,7 +47,7 @@ a Light Node are depicted below. The schematic is quite similar for other use ca Note fork accountability is not depicted, as it is the responsibility of the full nodes. -![Light Client Diagram](assets/light-node-image.png). +![Light Client Diagram](./assets/light-node-image.png). ### Synchrony From 026fddee4fcdd5fdf5d5dababfcfd45bbd2dece2 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Thu, 23 Jan 2020 14:45:47 +0100 Subject: [PATCH 20/20] Aligh the correctness arguments with the pseudocode changes --- spec/consensus/fork-accountability.md | 2 +- spec/consensus/light-client.md | 2 +- .../{light => light-client}/README.md | 14 +- .../{light => light-client}/accountability.md | 0 .../assets/light-node-image.png | Bin .../{light => light-client}/detection.md | 0 .../{light => light-client}/verification.md | 378 ++++++----- .../light/verification-non-recursive.md | 612 ------------------ spec/consensus/non-recursive-light-client.md | 3 - 9 files changed, 194 insertions(+), 817 deletions(-) rename spec/consensus/{light => light-client}/README.md (83%) rename spec/consensus/{light => light-client}/accountability.md (100%) rename spec/consensus/{light => light-client}/assets/light-node-image.png (100%) rename spec/consensus/{light => light-client}/detection.md (100%) rename spec/consensus/{light => light-client}/verification.md (51%) delete mode 100644 spec/consensus/light/verification-non-recursive.md delete mode 100644 spec/consensus/non-recursive-light-client.md diff --git a/spec/consensus/fork-accountability.md b/spec/consensus/fork-accountability.md index 7509819d..ea77fe87 100644 --- a/spec/consensus/fork-accountability.md +++ b/spec/consensus/fork-accountability.md @@ -1,3 +1,3 @@ # Fork Accountability - MOVED! -Fork Accountability has moved to [light](./light). +Fork Accountability has moved to [light-client](./light-client/accountability). diff --git a/spec/consensus/light-client.md b/spec/consensus/light-client.md index db20d49b..c3da2f79 100644 --- a/spec/consensus/light-client.md +++ b/spec/consensus/light-client.md @@ -1,3 +1,3 @@ # Light Client - MOVED! -Light Client has moved to [light](./light). +Light Client has moved to [light-client](./light-client). diff --git a/spec/consensus/light/README.md b/spec/consensus/light-client/README.md similarity index 83% rename from spec/consensus/light/README.md rename to spec/consensus/light-client/README.md index 1747f9b6..fe42e497 100644 --- a/spec/consensus/light/README.md +++ b/spec/consensus/light-client/README.md @@ -15,9 +15,9 @@ accurate. The Tendermint Light Client is motivated by the need for a light weight protocol to sync with a Tendermint blockchain, with the least processing necessary to -securely verify a recent state. The protocol consists -primarily of checking hashes and verifying Tendermint commit signatures to -update trusted validator sets and committed block headers from the chain. +securely verify a recent state. The protocol consists of managing trusted validator +sets and trusted block headers, and is based primarily on checking hashes +and verifying Tendermint commit signatures. Motivating use cases include: @@ -33,8 +33,8 @@ transactions from "IBC relayers", who make RPC requests to full nodes on behalf The Tendermint Light Client consists of three primary components: - [Core Verification](./verification.md): verifying hashes, signatures, and validator set changes -- [Fork Detection](./detection.md): talking to multiple peers to detect byzantine behaviour -- [Fork Accountability](./accountability.md): analyzing byzantine behaviour to hold validators accountable. +- [Fork Detection](./detection.md): talking to multiple peers to detect Byzantine behaviour +- [Fork Accountability](./accountability.md): analyzing Byzantine behaviour to hold validators accountable. While every light client must perform core verification and fork detection to achieve their prescribed security level, fork accountability is expected to @@ -44,7 +44,7 @@ primarily concerned with providing security to light clients. A schematic of the core verification and fork detection components in a Light Node are depicted below. The schematic is quite similar for other use cases. -Note fork accountability is not depicted, as it is the responsibility of the +Note that fork accountability is not depicted, as it is the responsibility of the full nodes. ![Light Client Diagram](./assets/light-node-image.png). @@ -53,7 +53,7 @@ full nodes. Light clients are fundamentally synchronous protocols, where security is restricted by the interval during which a validator can be punished -for byzantine behaviour. We assume here that such intervals have fixed and known minima +for Byzantine behaviour. We assume here that such intervals have fixed and known minimal duration referred to commonly as a blockchain's Unbonding Period. A secure light client must guarantee that all three components - diff --git a/spec/consensus/light/accountability.md b/spec/consensus/light-client/accountability.md similarity index 100% rename from spec/consensus/light/accountability.md rename to spec/consensus/light-client/accountability.md diff --git a/spec/consensus/light/assets/light-node-image.png b/spec/consensus/light-client/assets/light-node-image.png similarity index 100% rename from spec/consensus/light/assets/light-node-image.png rename to spec/consensus/light-client/assets/light-node-image.png diff --git a/spec/consensus/light/detection.md b/spec/consensus/light-client/detection.md similarity index 100% rename from spec/consensus/light/detection.md rename to spec/consensus/light-client/detection.md diff --git a/spec/consensus/light/verification.md b/spec/consensus/light-client/verification.md similarity index 51% rename from spec/consensus/light/verification.md rename to spec/consensus/light-client/verification.md index 66da23f1..55e87cd7 100644 --- a/spec/consensus/light/verification.md +++ b/spec/consensus/light-client/verification.md @@ -1,17 +1,74 @@ # Core Verification -A lite client is a process that connects to Tendermint full node(s) and then tries to verify application -data using the Merkle proofs. - ## Problem statement -We assume that the lite client knows a (base) header *inithead* it trusts (by social consensus or because -the lite client has decided to trust the header before). The goal is to check whether another header -*newhead* can be trusted based on the data in *inithead*. +We assume that the light client knows a (base) header `inithead` it trusts (by social consensus or because +the light client has decided to trust the header before). The goal is to check whether another header +`newhead` can be trusted based on the data in `inithead`. -The correctness of the protocol is based on the assumption that *inithead* was generated by an instance of +The correctness of the protocol is based on the assumption that `inithead` was generated by an instance of Tendermint consensus. +### Failure Model + +For the purpose of the following definitions we assume that there exists a function +`validators` that returns the corresponding validator set for the given hash. + +The light client protocol is defined with respect to the following failure model: + +Given a known bound `TRUSTED_PERIOD`, and a block `b` with header `h` generated at time `Time` +(i.e. `h.Time = Time`), 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`. + +*Assumption*: "correct" is defined w.r.t. realtime (some Newtonian global notion of time, i.e., wall time), +while `Header.Time` corresponds to the [BFT time](./../bft-time.md). In this note, we assume that clocks of correct processes +are synchronized (for example using NTP), and therefore there is bounded clock drift (`CLOCK_DRIFT`) between local clocks and +BFT time. More precisely, for every correct light client process and every `header.Time` (i.e. BFT Time, for a header correctly +generated by the Tendermint consensus), the following inequality holds: `Header.Time < now + CLOCK_DRIFT`, +where `now` corresponds to the system clock at the light client process. + +Furthermore, we assume that `TRUSTED_PERIOD` is (several) order of magnitude bigger than `CLOCK_DRIFT` (`TRUSTED_PERIOD >> CLOCK_DRIFT`), +as `CLOCK_DRIFT` (using NTP) is in the order of milliseconds and `TRUSTED_PERIOD` is in the order of weeks. + +We expect a light client process defined in this document to be used in the context in which there is some +larger period during which misbehaving validators can be detected and punished (we normally refer to it as `UNBONDING_PERIOD` +due to the "bonding" mechanism in modern proof of stake systems). Furthermore, we assume that +`TRUSTED_PERIOD < UNBONDING_PERIOD` and that they are normally of the same order of magnitude, for example +`TRUSTED_PERIOD = UNBONDING_PERIOD / 2`. + +The specification in this document considers an implementation of the light client under the Failure Model defined above. +Mechanisms like `fork accountability` and `evidence submission` are defined in the context of `UNBONDING_PERIOD` and +they incentivize validators to follow the protocol specification defined in this document. If they don't, +and we have 1/3 (or more) faulty validators, 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 document on [Fork accountability](./accountability.md). + +The term "trusted" above indicates that the correctness of the protocol depends on +this assumption. It is in the responsibility of the user that runs the light client to make sure that the risk +of trusting a corrupted/forged `inithead` is negligible. + +*Remark*: This failure model might change to a hybrid version that takes heights into account in the future. + +### High Level Solution + +Upon initialization, the light client is given a header `inithead` it trusts (by +social consensus). When a light clients sees a new signed header `snh`, it has to decide whether to trust the new +header. Trust can be obtained by (possibly) the combination of three methods. + +1. **Uninterrupted sequence of headers.** Given a trusted header `h` and an untrusted header `h1`, +the light client trusts a header `h1` if it trusts all headers in between `h` and `h1`. + +2. **Trusted period.** Given a trusted header `h`, an untrusted header `h1 > h` and `TRUSTED_PERIOD` during which +the failure model holds, we can check whether at least one validator, that has been continuously correct +from `h.Time` until now, has signed `h1`. If this is the case, we can trust `h1`. + +3. **Bisection.** If a check according to 2. (trusted period) fails, the light client can try to +obtain a header `hp` whose height lies between `h` and `h1` in order to check whether `h` can be used to +get trust for `hp`, and `hp` can be used to get trust for `snh`. If this is the case we can trust `h1`; +if not, we continue recursively until either we found set of headers that can build (transitively) trust relation +between `h` and `h1`, or we failed as two consecutive headers don't verify against each other. + + ## Definitions ### Data structures @@ -22,6 +79,8 @@ In the following, only the details of the data structures needed for this specif type Header struct { Height int64 Time Time // the chain time when the header (block) was generated + + LastBlockID BlockID // prev block info ValidatorsHash []byte // hash of the validators for the current block NextValidatorsHash []byte // hash of the validators for the next block } @@ -49,7 +108,8 @@ In the following, only the details of the data structures needed for this specif ### Functions -For the purpose of this lite client specification, we assume that the Tendermint Full Node exposes the following functions over Tendermint RPC: +For the purpose of this light client specification, we assume that the Tendermint Full Node +exposes the following functions over Tendermint RPC: ```go // returns signed header: Header with Commit, for the given height func Commit(height int64) (SignedHeader, error) @@ -69,7 +129,7 @@ Furthermore, we assume the following auxiliary functions: // it assumes signature verification so it can be computationally expensive func signers(commit Commit, validatorSet ValidatorSet) []Validator - // return the voting power the validators in v1 have according to their voting power in set v2 + // returns the voting power the validators in v1 have according to their voting power in set v2 // it does not assume signature verification func votingPowerIn(v1 []Validator, v2 ValidatorSet) int64 @@ -77,50 +137,15 @@ Furthermore, we assume the following auxiliary functions: func hash(v2 ValidatorSet) []byte ``` -### Failure Model - -For the purpose of model definitions we assume that there exists a function -`validators` that returns the corresponding validator set for the given hash. -The lite client specification is defined with respect to the following failure model: - -Given a known bound `TRUSTED_PERIOD`, and a block `b` with header `h` generated at time `Time` -(i.e. `h.Time = Time`), 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`. - -*Assumption*: "correct" is defined w.r.t. realtime (some Newtonian global notion of time, i.e., wall time), -while `Header.Time` corresponds to the [BFT time](bft-time.md). In this note, we assume that clocks of correct processes -are synchronized (for example using NTP), and therefore there is bounded clock drift (`CLOCK_DRIFT`) between local clocks and -BFT time. More precisely, for every correct lite client process and every `header.Time` (i.e. BFT Time, for a header correctly -generated by the Tendermint consensus), the following inequality holds: `Header.Time < now + CLOCK_DRIFT`, -where `now` corresponds to the system clock at the lite client process. - -Furthermore, we assume that `TRUSTED_PERIOD` is (several) order of magnitude bigger than `CLOCK_DRIFT` (`TRUSTED_PERIOD >> CLOCK_DRIFT`), -as `CLOCK_DRIFT` (using NTP) is in the order of milliseconds and `TRUSTED_PERIOD` is in the order of weeks. - -We expect a lite client process defined in this document to be used in the context in which there is some -larger period during which misbehaving validators can be detected and punished (we normally refer to it as `PUNISHMENT_PERIOD`). -Furthermore, we assume that `TRUSTED_PERIOD < PUNISHMENT_PERIOD` and that they are normally of the same order of magnitude, for example -`TRUSTED_PERIOD = PUNISHMENT_PERIOD / 2`. Note that `PUNISHMENT_PERIOD` is often referred to as an -unbonding period due to the "bonding" mechanism in modern proof of stake systems. - -The specification in this document considers an implementation of the lite client under the Failure Model defined above. -Mechanisms like `fork accountability` and `evidence submission` are defined in the context of `PUNISHMENT_PERIOD` and -they incentivize validators to follow the protocol specification defined in this document. If they don't, -and we have 1/3 (or more) faulty validators, 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 document on [Fork accountability](fork-accountability.md). - -*Remark*: This failure model might change to a hybrid version that takes heights into account in the future. - ### Functions In the functions below we will be using `trustThreshold` as a parameter. For simplicity -we assume that `trustThreshold` is a float between 1/3 and 2/3 and we will not be checking it +we assume that `trustThreshold` is a float between `1/3` and `2/3` and we will not be checking it in the pseudo-code. **VerifySingle.** The function `VerifySingle` attempts to validate given untrusted header and the corresponding validator sets based on a given trusted state. It ensures that the trusted state is still within its trusted period, -and that the untrusted header is within assume `clockDrift` bound of the passed time `now`. +and that the untrusted header is within assumed `clockDrift` bound of the passed time `now`. Note that this function is not making external (RPC) calls to the full node; the whole logic is based on the local (given) state. This function is supposed to be used by the IBC handlers. @@ -159,7 +184,7 @@ func VerifySingle(untrustedSh SignedHeader, return (newTrustedState, nil) } -// return true if header is within its lite client trusted period; otherwise returns false +// return true if header is within its light client trusted period; otherwise returns false func isWithinTrustedPeriod(header Header, trustingPeriod Duration, now Time) bool { @@ -173,6 +198,7 @@ is successfully verified) then we have a guarantee that the transition of the tr from `trustedState` to `newTrustedState` happened during the trusted period of `trustedState.SignedHeader.Header`. +TODO: Explain what happens in case `VerifySingle` returns with an error. **verifySingle.** The function `verifySingle` verifies a single untrusted header against a given trusted state. It includes all validations and signature verification. @@ -250,7 +276,7 @@ func verifyCommitFull(vs ValidatorSet, commit Commit) error { ``` **VerifyHeaderAtHeight.** The function `VerifyHeaderAtHeight` captures high level -logic, i.e., application call to the lite client module to download and verify header +logic, i.e., application call to the light client module to download and verify header for some height. ```go @@ -290,6 +316,12 @@ is successfully verified) then we have a guarantee that the transition of the tr from `trustedState` to `newTrustedState` happened during the trusted period of `trustedState.SignedHeader.Header`. +In case `VerifyHeaderAtHeight` returns with an error, then either (i) the full node we are talking to is faulty +or (ii) the trusted header has expired (it is outside its trusted period). In case (i) the full node is faulty so +light client should disconnect and reinitialise with new peer. In the case (ii) as the trusted header has expired, +we need to reinitialise light client with a 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). + **VerifyBisection.** The function `VerifyBisection` implements recursive logic for checking if it is possible building trust relationship between `trustedState` and untrusted header at the given height over @@ -369,217 +401,177 @@ func fatalError(err) bool { -### The case `untrusted_h.Header.height < trusted_h.Header.height` +### The case `untrustedHeader.Height < trustedHeader.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 +In the use case where someone tells the light client that application data that is relevant for it +can be read in the block of height `k` and the light 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 +*Remark.* For the case were the light 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 -func Backwards(trusted_h,untrusted_h) error { - assert (untrusted_h.Header.height < trusted_h.Header.height) - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) - - old := trusted_h - for i := trusted_h.Header.height - 1; i > untrusted_h.Header.height; i-- { - new := Commit(i) - if (hash(new) != old.Header.hash) { +func VerifyHeaderBackwards(trustedHeader Header, + untrustedHeader Header, + trustingPeriod Duration, + clockDrift Duration) error { + + if untrustedHeader.Height >= trustedHeader.Height return ErrErrNonDecreasingHeight + if untrustedHeader.Time >= trustedHeader.Time return ErrNonDecreasingTime + + now := System.Time() + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return ErrHeaderNotWithinTrustedPeriod + } + + old := trustedHeader + for i := trustedHeader.Height - 1; i > untrustedHeader.Height; i-- { + untrustedSh, error := Commit(i) + if error != nil return ErrRequestFailed + + if (hash(untrustedSh.Header) != old.LastBlockID.Hash) { return ErrInvalidAdjacentHeaders } - old := new - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) + + old := untrustedSh.Header + } + + if hash(untrustedHeader) != old.LastBlockID.Hash { + return ErrInvalidAdjacentHeaders } - if hash(untrusted_h) != old.Header.hash return ErrInvalidAdjacentHeaders + + now := System.Time() + if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { + return ErrHeaderNotWithinTrustedPeriod + } + return nil } ``` -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 -headers are detected). As detecting conflicting headers, its propagation through the network (by the gossip protocol) and execution of the fork accountability -protocol on the chain takes time, the lite client logic assumes conservative value for trusted period. More precisely, in the context of lite client we always -operate with a smaller trusted period that we call *lite client trusted period* (LITE_CLIENT_TRUSTED_PERIOD). If we assume that upper bound -for fork detection, propagation and processing on the chain is denoted with *fork procession period* (FORK_PROCESSING_PERIOD), then the following formula -holds: -```LITE_CLIENT_TRUSTED_PERIOD + FORK_PROCESSING_PERIOD < TRUSTED_PERIOD```, where TRUSTED_PERIOD comes from the Tendermint Failure Model. - *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: -- 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 +- the light client communicates with one full node +- 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 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). - * 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 +- 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). + * In case of forged header, the full node is faulty so light client should disconnect and reinitialise with new peer. If the trusted header has expired, + we need to reinitialise light 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). - -## Context of this document - -In order to make sure that full nodes have the incentive to follow the protocol, we have to address the -following three Issues - -1) The lite client needs a method to verify headers it obtains from a full node it connects to according to trust assumptions -- this document. - -2) The lite client must be able to connect to other full nodes to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document (see #4215). - -3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- a future document (see #3840). - -The term "trusting" above indicates that the correctness of the protocol depends on -this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk -of trusting a corrupted/forged *inithead* is negligible. - -* 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. - -* 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. - - - * Validator fields. We will write a validator as a tuple *(v,p)* such that - + *v* is the identifier (we assume identifiers are unique in each validator set) - + *p* is its voting power +## Correctness of the Light Client Protocols ### Definitions -* *TRUSTED_PERIOD*: trusting period -* for realtime *t*, the predicate *correct(v,t)* is true if the validator *v* - follows the protocol until time *t* (we will see about recovery later). - +* `TRUSTED_PERIOD`: trusted period +* for realtime `t`, the predicate `correct(v,t)` is true if the validator `v` + follows the protocol until time `t` (we will see about recovery later). +* Validator fields. We will write a validator as a tuple `(v,p)` such that + + `v` is the identifier (i.e., validator address; we assume identifiers are unique in each validator set) + + `p` is its voting power +* For each header `h`, we write `trust(h) = true` if the light client trusts `h`. -### Tendermint Failure Model +### Failure Model -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```. +If a block `b` with a header `h` is generated at time `Time` (i.e. `h.Time = Time`), then a set of validators that +hold more than `2/3` of the voting power in `validators(h.NextValidatorsHash)` is correct until time +`h.Time + TRUSTED_PERIOD`. Formally, \[ -\sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + TRUSTED_PERIOD)} p > -2/3 \sum_{(v,p) \in h.Header.NextV} p +\sum_{(v,p) \in validators(h.NextValidatorsHash) \wedge correct(v,h.Time + TRUSTED_PERIOD)} p > +2/3 \sum_{(v,p) \in validators(h.NextValidatorsHash)} p \] -## Lite Client Trusting Spec - -The lite 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: +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: -- Lite Client Completeness: If header *h* was correctly generated by an instance of Tendermint consensus (and its age is less than the trusting period), then the lite client should eventually set *trust(h)* to true. +- *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), +then the light client should eventually set `trust(h)` to `true`. -- Lite Client Accuracy: If header *h* was *not generated* by an instance of Tendermint consensus, then the lite client should never set *trust(h)* to true. +- *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. -*Remark*: If in the course of the computation, the lite client obtains certainty that some headers were forged by adversaries (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. +*Remark*: If in the course of the computation, the light client obtains certainty that some headers were forged by adversaries +(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. -*Remark*: In Completeness we use "eventually", while in practice *trust(h)* should be set to true before *h.Header.bfttime + tp*. If not, the block cannot be trusted because it is too old. +*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 +cannot be trusted because it is too old. -*Remark*: If a header *h* is marked with *trust(h)*, but it is too old (its bfttime is more than *tp* ago), then the lite client should set *trust(h)* to false again. +*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`), +then the light client should set `trust(h)` to `false` again at time `now`. -*Assumption*: Initially, the lite client has a header *inithead* that it trusts correctly, that is, *inithead* was correctly generated by the Tendermint consensus. +*Assumption*: Initially, the light client has a header `inithead` that it trusts, that is, `inithead` was correctly generated by the Tendermint consensus. To reason about the correctness, we may prove the following invariant. -*Verification Condition: Lite Client Invariant.* - For each lite client *l* and each header *h*: -if *l* has set *trust(h) = true*, - then validators that are correct until time *h.Header.bfttime + tp* have more than two thirds of the voting power in *h.Header.NextV*. +*Verification Condition: light Client Invariant.* + For each light client `l` and each header `h`: +if `l` has set `trust(h) = true`, + then validators that are correct until time `h.Time + TRUSTED_PERIOD` have more than two thirds of the voting power in `validators(h.NextValidatorsHash)`. Formally, \[ - \sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > - 2/3 \sum_{(v,p) \in h.Header.NextV} p + \sum_{(v,p) \in validators(h.NextValidatorsHash) \wedge correct(v,h.Time + TRUSTED_PERIOD)} p > + 2/3 \sum_{(v,p) \in validators(h.NextValidatorsHash)} p \] -*Remark.* To prove the invariant, we will have to prove that the lite client only trusts headers that were correctly generated by Tendermint consensus, then the formula above follows from the Tendermint failure model. - - -## High Level Solution - -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. - -1. **Uninterrupted sequence of proof.** If a block is appended to the chain, where the last block -is trusted (and properly committed by the old validator set in the next block), -and the new block contains a new validator set, the new block is trusted if the lite client knows all headers in the prefix. -Intuitively, a trusted validator set is assumed to only chose a new validator set that will obey the Tendermint Failure Model. - -2. **Trusting period.** Based on a trusted block *h*, and the lite client -invariant, which ensures the fault assumption during the trusting period, we can check whether at least one validator, that has been continuously correct from *h.Header.bfttime* until now, has signed *snh*. -If this is the case, similarly to above, the chosen validator set in *snh* does not violate the Tendermint Failure Model. - -3. **Bisection.** If a check according to the trusting period fails, the lite client can try to obtain a header *hp* whose height lies between *h* and *snh* in order to check whether *h* can be used to get trust for *hp*, and *hp* can be used to get trust for *snh*. If this is the case we can trust *snh*; if not, we may continue recursively. - -## How to use it - -We consider the following use case: - the lite client wants to verify a header for some given height *k*. Thus: - - it requests the signed header for height *k* from a full node - - it tries to verify this header with the methods described here. - -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*. - - 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. - +*Remark.* To prove the invariant, we will have to prove that the light client only trusts headers that were correctly generated by Tendermint consensus. +Then the formula above follows from the failure model. ## Details -**Observation 1.** If *h.Header.bfttime + tp > now*, we trust the old -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 less than 1/3 of them are faulty (more precisely, the faulty ones have less than 1/3 of the total voting power). - - - -*Correctness arguments* +**Observation 1.** If `h.Time + TRUSTED_PERIOD > now`, we trust the validator set `validators(h.NextValidatorsHash)`. -Towards Lite Client Accuracy: -- 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. +When we say we trust `validators(h.NextValidatorsHash)` we do `not` trust that each individual validator in `validators(h.NextValidatorsHash)` +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). +*`VerifySingle` correctness arguments* -Towards Lite Client Completeness: -- 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 +Light Client Accuracy: +- Assume by contradiction that `untrustedHeader` was not generated correctly and the light client sets trust to true because `verifySingle` returns without error. +- `trustedState` is trusted and sufficiently new +- 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`. +- as `v` is correct up to now, it followed the Tendermint consensus protocol at least up to signing `untrustedHeader` => `untrustedHeader` was correctly generated. +We arrive at the required contradiction. -*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. +Light Client Completeness: +- The check is successful if sufficiently many validators of `trustedState` are still validators in the height `untrustedHeader.Height` and signed `untrustedHeader`. +- If `untrustedHeader.Height = trustedHeader.Height + 1`, and both headers were generated correctly, the test passes. +*Verification Condition:* We may need a Tendermint invariant stating that if `untrustedSignedHeader.Header.Height = trustedHeader.Height + 1` then +`signers(untrustedSignedHeader.Commit) \subseteq validators(trustedHeader.NextValidatorsHash)`. +*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 +`verifySingle` returns with an error for non-adjacent headers. -*Correctness arguments (sketch)* +* `VerifyBisection` correctness arguments (sketch)* -Lite Client Accuracy: -- 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. -- Thus we have a sequence of headers that all satisfied the CheckSupport +Light Client Accuracy: +- Assume by contradiction that the header at `untrustedHeight` obtained from the full node was not generated correctly and +the light client sets trust to true because `VerifyBisection` returns without an error. +- `VerifyBisection` returns without error only if all calls to `verifySingle` in the recursion return without error (return `nil`). +- Thus we have a sequence of headers that all satisfied the `verifySingle` - again a contradiction -Lite Client Completeness: +light Client Completeness: -This is only ensured if upon *Commit(pivot)* the lite client is always provided with a correctly generated header. +This is only ensured if upon `Commit(pivot)` the light client is always provided with a correctly generated header. *Stalling* -With CanTrustBisection, a faulty full node could stall a lite client by creating a long sequence of headers that are queried one-by-one by the lite client and look OK, before the lite client eventually detects a problem. There are several ways to address this: -* Each call to ```Commit``` could be issued to a different full node -* Instead of querying header by header, the lite client tells a full node which header it trusts, and the height of the header it needs. The full node responds with the header along with a proof consisting of intermediate headers that the light client can use to verify. Roughly, Bisection would then be executed at the full node. -* We may set a timeout how long bisection may take. +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, +before the light client eventually detects a problem. There are several ways to address this: +* Each call to `Commit` could be issued to a different full node +* 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 +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. +* We may set a timeout how long `VerifyBisection` may take. diff --git a/spec/consensus/light/verification-non-recursive.md b/spec/consensus/light/verification-non-recursive.md deleted file mode 100644 index 085f3f65..00000000 --- a/spec/consensus/light/verification-non-recursive.md +++ /dev/null @@ -1,612 +0,0 @@ -# Lite client - -A lite client is a process that connects to Tendermint full node(s) and then tries to verify application -data using the Merkle proofs. - -## Problem statement - -We assume that the lite client knows a (base) header *inithead* it trusts (by social consensus or because -the lite client has decided to trust the header before). The goal is to check whether another header -*newhead* can be trusted based on the data in *inithead*. - -The correctness of the protocol is based on the assumption that *inithead* was generated by an instance of -Tendermint consensus. - -## Definitions - -### Data structures - -In the following, only the details of the data structures needed for this specification are given. - - ```go - type Header struct { - Height int64 - Time Time // the chain time when the header (block) was generated - - // hashes from the app output from the prev block - ValidatorsHash []byte // hash of the validators for the current block - NextValidatorsHash []byte // hash of the validators for the next block - - // hashes of block data - LastCommitHash []byte // hash of the commit from validators from the last block - } - - type SignedHeader struct { - Header Header - Commit Commit // commit for the given header - } - - type ValidatorSet struct { - Validators []Validator - TotalVotingPower int64 - } - - type Validator struct { - Address Address // validator address (we assume validator's addresses are unique) - VotingPower int64 // validator's voting power - } - - type TrustedState { - SignedHeader SignedHeader - ValidatorSet ValidatorSet - } - ``` - -### Functions - -For the purpose of this lite client specification, we assume that the Tendermint Full Node exposes the following functions over Tendermint RPC: -```go - // returns signed header: Header with Commit, for the given height - func Commit(height int64) (SignedHeader, error) - - // returns validator set for the given height - func Validators(height int64) (ValidatorSet, error) -``` - -Furthermore, we assume the following auxiliary functions: -```go - // returns the validator set for the given validator hash - func validators(validatorsHash []byte) ValidatorSet - - // returns true if commit corresponds to the block data in the header; otherwise false - func matchingCommit(header Header, commit Commit) bool - - // returns the set of validators from the given validator set that committed the block - // it does not assume signature verification - func signers(commit Commit, validatorSet ValidatorSet) []Validator - - // return the voting power the validators in v1 have according to their voting power in set v2 - // it assumes signature verification so it can be computationally expensive - func votingPowerIn(v1 []Validator, v2 ValidatorSet) int64 - - // add this state as trusted to the store - func add(store Store, trustedState TrustedState) error - - // retrieve the trusted state at given height if it exists (error = nil) - // return an error if there are no trusted state for the given height - // if height = 0, return the latest trusted state - func get(store Store, height int64) (TrustedState, error) -``` - - -**VerifyHeaderAtHeight.** TODO. -```go -func VerifyHeaderAtHeight(untrustedHeight int64, - trustThreshold TrustThreshold, - trustingPeriod Duration, - clockDrift Duration, - store Store) (error, (TrustedState, Time)) { - - now := System.Time() - initTrustedState, newTrustedState, err := VerifyAndUpdateNonRecursive(untrustedHeight, - trustThreshold, - trustingPeriod, - clockDrift, - now, - store Store) - - if err != nil return err - - now = System.Time() - if !isWithinTrustedPeriod(initTrustedState.SignedHeader.Header, trustingPeriod, now) { - return ErrHeaderNotWithinTrustedPeriod - } - return nil, (newTrustedState, now) -} -``` - -If we get some trustedState at time t (now = t), - - -**VerifyAndUpdateNonRecursive.** TODO. -```go -func VerifyAndUpdateNonRecursive(untrustedHeight int64, - trustThreshold TrustThreshold, - trustingPeriod Duration, - clockDrift Duration, - now Time, - store Store) error { - - // fetch the latest state and ensure it hasn't expired - trustedState, error = get(store, 0) - if error != nil return error - - trustedSh = trustedState.SignedHeader - trustedHeader = trustedSh.Header - assert trustedHeader.Height < untrustedHeight AND - trustedHeader.Time < now - - if !isWithinTrustedPeriod(trustedHeader, trustingPeriod, now) { - return ErrHeaderNotWithinTrustedPeriod - } - - th := trustedHeader // th is trusted header - - untrustedSh, error := Commit(untrustedHeight) - if error != nil return error - - untrustedHeader = untrustedSh.Header - assert untrustedHeader.Time < now + clockDrift - - untrustedVs, error := Validators(untrustedHeight) - if error != nil return error - - untrustedNextVs, error := Validators(untrustedHeight + 1) - if error != nil return error - - // untrustedHeader is a list of headers that have not passed verifySingle - untrustedHeaders := [untrustedHeader] - - while true { - for h in untrustedHeaders { - // we assume here that iteration is done in the order of header heights - error = verifySingle( - trustedState, - untrustedSh, - untrustedVs, - untrustedNextVs, - trustThreshold) - - if err == nil { - // the untrusted header is now trusted. update the store - trustedState = TrustedState(untrustedSh, untrustedNextVs) - add(store, trustedState) - - untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) - if trustedState.SignedHeader.Header == untrustedSh.Header { - return nil - } - } - if fatalError(err) { return err } - } - - endHeight = min(untrustedHeaders) - while true { - trustedSh = trustedState.SignedHeader - trustedHeader = trustedSh.Header - pivotHeight := ceil((trustedHeader.Height + endHeight) / 2) - - untrustedSh, error := Commit(pivotHeight) - if error != nil return error - - untrustedHeader = untrustedSh.Header - assert untrustedHeader.Time < now + clockDrift - - untrustedVs, error := Validators(untrustedHeight) - if error != nil return error - - untrustedNextVs, error := Validators(untrustedHeight + 1) - if error != nil return error - - error = verifySingle( - trustedState, - untrustedSh, - untrustedVs, - untrustedNextVs, - trustThreshold) - - if fatalError(error) return error - - if err == nil { - trustedState = TrustedState(untrustedSh, untrustedNextVs) - add(store, trustedState) - break - } - - untrustedHeaders.add(untrustedHeader) - endHeight = pivot - } - } - return nil // this line should never be reached -} -``` - - - -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 -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 -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. - - -```go -// return nil in case we can trust header untrusted_h based on header trusted_h; otherwise return error -// where error captures the nature of the error. -// Note that untrusted_h must have been verified by the caller, i.e. verify(untrusted_h) was successful. -func CanTrust(trusted_h,untrusted_h,trustThreshold) error { - assert trusted_h.Header.Height < untrusted_h.header.Height - - th := trusted_h // th is trusted header - // untrustedHeader is a list of (?) verified headers that have not passed CheckSupport() - untrustedHeaders := [untrusted_h] - - while true { - 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 { - if !verify(h) { return ErrInvalidHeader(h) } - th = h - Store.Add(h) - untrustedHeaders.RemoveHeadersSmallerOrEqual(h.Header.Height) - if th == untrusted_h { return nil } - } - if fatalCheckSupportError(err) { return err } - } - - endHeight = min(untrustedHeaders) - while true { - pivot := ceil((th.Header.height + endHeight) / 2) - hp := Commit(pivot) - // try to move trusted header forward to hp - err = CheckSupport(th,hp,trustThreshold) - if fatalCheckSupportError(err) return err - if err == nil { - if !verify(hp) { return ErrInvalidHeader(hp) } - th = hp - Store.Add(th) - break - } - untrustedHeaders.add(hp) - endHeight = pivot - } - } - return nil // this line should never be reached -} - -func CheckSupport(trusted_h,untrusted_h,trustThreshold) error { - assert trusted_h.Header.Height < untrusted_h.header.Height and - trusted_h.Header.bfttime < untrusted_h.Header.bfttime and - untrusted_h.Header.bfttime < now - - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) - - // Although while executing the rest of CheckSupport function, trusted_h can expire 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 trusted_h can be trusted. Furthermore, CheckSupport function - // is not doing expensive operation (neither rpc nor signature verification), so it - // should execute fast. - - // check for adjacent headers - if untrusted_h.Header.height == trusted_h.Header.height + 1 { - if trusted_h.Header.NextV == untrusted_h.Header.V - return nil - return ErrInvalidAdjacentHeaders - } - - // total sum of voting power of validators in trusted_h.NextV - vp_all := totalVotingPower(trusted_h.Header.NextV) - - // check for non-adjacent headers - if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { - return nil - } - return ErrTooMuchChange -} - -func fatalCheckSupportError(err) bool { - return err == ErrHeaderNotWithinTrustedPeriod or err == ErrInvalidAdjacentHeaders -``` - -```go -func CanTrustBisection(trusted_h,untrusted_h,trustThreshold) error { - assume trusted_h.Header.Height < untrusted_h.header.Height - - err = CheckSupport(trusted_h,untrusted_h,trustThreshold) - if err == nil { - Store.Add(untrusted_h) - return nil - } - if err != ErrTooMuchChange return err - - pivot := (trusted_h.Header.height + untrusted_h.Header.height) / 2 - hp := Commit(pivot) - if !verify(hp) return ErrInvalidHeader(hp) - - err = CanTrustBisection(trusted_h,hp,trustThreshold) - if err == nil { - Store.Add(hp) - err2 = CanTrustBisection(hp,untrusted_h,trustThreshold) - if err2 == nil { - Store.Add(untrusted_h) - return nil - } - return err2 - } - return err -} -``` - -**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. -```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 - // Captures skipping condition. trusted_h and untrusted_h have already passed basic validation - // (function `verify`). - // Returns nil in case untrusted_h can be trusted based on trusted_h, otherwise returns error. - // ErrHeaderNotWithinTrustedPeriod is used when trusted_h 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(trusted_h,untrusted_h,trustThreshold) error { - assert trusted_h.Header.Height < untrusted_h.header.Height and - trusted_h.Header.bfttime < untrusted_h.Header.bfttime and - untrusted_h.Header.bfttime < now - - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotWithinTrustedPeriod(trusted_h) - - // Although while executing the rest of CheckSupport function, trusted_h can expire 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 trusted_h can be trusted. Furthermore, CheckSupport function - // is not doing expensive operation (neither rpc nor signature verification), so it - // should execute fast. - - // check for adjacent headers - if untrusted_h.Header.height == trusted_h.Header.height + 1 { - if trusted_h.Header.NextV == untrusted_h.Header.V - return nil - return ErrInvalidAdjacentHeaders - } - - // total sum of voting power of validators in trusted_h.NextV - vp_all := totalVotingPower(trusted_h.Header.NextV) - - // check for non-adjacent headers - if votingPowerIn(signers(untrusted_h.Commit),trusted_h.Header.NextV) > max(1/3,trustThreshold) * vp_all { - return nil - } - return ErrTooMuchChange - } -``` - - -### 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. - -*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 -func Backwards(trusted_h,untrusted_h) error { - assert (untrusted_h.Header.height < trusted_h.Header.height) - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) - - old := trusted_h - for i := trusted_h.Header.height - 1; i > untrusted_h.Header.height; i-- { - new := Commit(i) - if (hash(new) != old.Header.hash) { - return ErrInvalidAdjacentHeaders - } - old := new - if !isWithinTrustedPeriod(trusted_h) return ErrHeaderNotTrusted(trusted_h) - } - if hash(untrusted_h) != old.Header.hash return ErrInvalidAdjacentHeaders - return nil - } -``` - - - -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 -headers are detected). As detecting conflicting headers, its propagation through the network (by the gossip protocol) and execution of the fork accountability -protocol on the chain takes time, the lite client logic assumes conservative value for trusted period. More precisely, in the context of lite client we always -operate with a smaller trusted period that we call *lite client trusted period* (LITE_CLIENT_TRUSTED_PERIOD). If we assume that upper bound -for fork detection, propagation and processing on the chain is denoted with *fork procession period* (FORK_PROCESSING_PERIOD), then the following formula -holds: -```LITE_CLIENT_TRUSTED_PERIOD + FORK_PROCESSING_PERIOD < TRUSTED_PERIOD```, where TRUSTED_PERIOD comes from the Tendermint Failure Model. - - -*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: -- 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.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). - * 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 are talking to (as we haven't observed full node misbehavior in this case). - - - -## Context of this document - -In order to make sure that full nodes have the incentive to follow the protocol, we have to address the -following three Issues - -1) The lite client needs a method to verify headers it obtains from a full node it connects to according to trust assumptions -- this document. - -2) The lite client must be able to connect to other full nodes to detect and report on failures in the trust assumptions (i.e., conflicting headers) -- a future document (see #4215). - -3) In the event the trust assumption fails (i.e., a lite client is fooled by a conflicting header), the Tendermint fork accountability protocol must account for the evidence -- a future document (see #3840). - -The term "trusting" above indicates that the correctness of the protocol depends on -this assumption. It is in the responsibility of the user that runs the lite client to make sure that the risk -of trusting a corrupted/forged *inithead* is negligible. - -* 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. - -* 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. - - - * Validator fields. We will write a validator as a tuple *(v,p)* such that - + *v* is the identifier (we assume identifiers are unique in each validator set) - + *p* is its voting power - -### Definitions - -* *TRUSTED_PERIOD*: trusting period -* for realtime *t*, the predicate *correct(v,t)* is true if the validator *v* - follows the protocol until time *t* (we will see about recovery later). - - -### Tendermint Failure Model - -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, -\[ -\sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + TRUSTED_PERIOD)} p > -2/3 \sum_{(v,p) \in h.Header.NextV} p -\] - - -## Lite Client Trusting Spec - -The lite 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: - -- Lite Client Completeness: If header *h* was correctly generated by an instance of Tendermint consensus (and its age is less than the trusting period), then the lite client should eventually set *trust(h)* to true. - -- Lite Client Accuracy: If header *h* was *not generated* by an instance of Tendermint consensus, then the lite client should never set *trust(h)* to true. - -*Remark*: If in the course of the computation, the lite client obtains certainty that some headers were forged by adversaries (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. - -*Remark*: In Completeness we use "eventually", while in practice *trust(h)* should be set to true before *h.Header.bfttime + tp*. If not, the block cannot be trusted because it is too old. - -*Remark*: If a header *h* is marked with *trust(h)*, but it is too old (its bfttime is more than *tp* ago), then the lite client should set *trust(h)* to false again. - -*Assumption*: Initially, the lite client has a header *inithead* that it trusts correctly, that is, *inithead* was correctly generated by the Tendermint consensus. - -To reason about the correctness, we may prove the following invariant. - -*Verification Condition: Lite Client Invariant.* - For each lite client *l* and each header *h*: -if *l* has set *trust(h) = true*, - then validators that are correct until time *h.Header.bfttime + tp* have more than two thirds of the voting power in *h.Header.NextV*. - - Formally, - \[ - \sum_{(v,p) \in h.Header.NextV \wedge correct(v,h.Header.bfttime + tp)} p > - 2/3 \sum_{(v,p) \in h.Header.NextV} p - \] - -*Remark.* To prove the invariant, we will have to prove that the lite client only trusts headers that were correctly generated by Tendermint consensus, then the formula above follows from the Tendermint failure model. - - -## High Level Solution - -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. - -1. **Uninterrupted sequence of proof.** If a block is appended to the chain, where the last block -is trusted (and properly committed by the old validator set in the next block), -and the new block contains a new validator set, the new block is trusted if the lite client knows all headers in the prefix. -Intuitively, a trusted validator set is assumed to only chose a new validator set that will obey the Tendermint Failure Model. - -2. **Trusting period.** Based on a trusted block *h*, and the lite client -invariant, which ensures the fault assumption during the trusting period, we can check whether at least one validator, that has been continuously correct from *h.Header.bfttime* until now, has signed *snh*. -If this is the case, similarly to above, the chosen validator set in *snh* does not violate the Tendermint Failure Model. - -3. **Bisection.** If a check according to the trusting period fails, the lite client can try to obtain a header *hp* whose height lies between *h* and *snh* in order to check whether *h* can be used to get trust for *hp*, and *hp* can be used to get trust for *snh*. If this is the case we can trust *snh*; if not, we may continue recursively. - -## How to use it - -We consider the following use case: - the lite client wants to verify a header for some given height *k*. Thus: - - it requests the signed header for height *k* from a full node - - it tries to verify this header with the methods described here. - -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*. - - 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 - -**Observation 1.** If *h.Header.bfttime + tp > now*, we trust the old -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 less than 1/3 of them are faulty (more precisely, the faulty ones have less than 1/3 of the total voting power). - - - -*Correctness arguments* - -Towards Lite Client Accuracy: -- 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: -- 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 *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. - - - - -*Correctness arguments (sketch)* - -Lite Client Accuracy: -- 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. -- Thus we have a sequence of headers that all satisfied the CheckSupport -- again a contradiction - -Lite Client Completeness: - -This is only ensured if upon *Commit(pivot)* the lite client is always provided with a correctly generated header. - -*Stalling* - -With CanTrustBisection, a faulty full node could stall a lite client by creating a long sequence of headers that are queried one-by-one by the lite client and look OK, before the lite client eventually detects a problem. There are several ways to address this: -* Each call to ```Commit``` could be issued to a different full node -* Instead of querying header by header, the lite client tells a full node which header it trusts, and the height of the header it needs. The full node responds with the header along with a proof consisting of intermediate headers that the light client can use to verify. Roughly, Bisection would then be executed at the full node. -* We may set a timeout how long bisection may take. - - - - - diff --git a/spec/consensus/non-recursive-light-client.md b/spec/consensus/non-recursive-light-client.md deleted file mode 100644 index b95815f4..00000000 --- a/spec/consensus/non-recursive-light-client.md +++ /dev/null @@ -1,3 +0,0 @@ -# Non Recursive Verification - MOVED! - -Non Recursive verification has moved to [light](./light).