mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-05 13:05:09 +00:00
Address reviewer comment's. Intermediate commit
This commit is contained in:
@@ -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`.
|
||||
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 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.
|
||||
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 = VerifyBisection(pivotHeight,
|
||||
trustedState,
|
||||
trustThreshold,
|
||||
trustingPeriod,
|
||||
clockDrift,
|
||||
now)
|
||||
if error != nil return (newTrustedState, error)
|
||||
|
||||
error, newTrustedState = verifyAndUpdateBisection(newTrustedState,
|
||||
untrustedHeight,
|
||||
trustThreshold,
|
||||
trustingPeriod,
|
||||
clockDrift,
|
||||
now)
|
||||
if error != nil return (error, trustedState)
|
||||
return (nil, newTrustedState)
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user