Browse Source

Address reviewer comment's. Intermediate commit

pull/7804/head
Zarko Milosevic 5 years ago
parent
commit
146e251892
1 changed files with 125 additions and 115 deletions
  1. +125
    -115
      spec/consensus/light-client.md

+ 125
- 115
spec/consensus/light-client.md View File

@ -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
}
```


Loading…
Cancel
Save