From 4a9eb1f1acf99b73953005ad01e20e8a08b52219 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Tue, 31 Dec 2019 13:31:35 +0100 Subject: [PATCH] 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 89ec5ec7e..883ac0090 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.