mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-26 09:54:19 +00:00
Merge remote-tracking branch 'origin' into jasmina/8219-blocksync-spec
This commit is contained in:
@@ -6,47 +6,49 @@ title: Application Requirements
|
||||
# Application Requirements
|
||||
|
||||
This section specifies what Tendermint expects from the Application. It is structured as a set
|
||||
of formal requirement that can be used for testing and verification of the Application's logic.
|
||||
of formal requirements that can be used for testing and verification of the Application's logic.
|
||||
|
||||
Let $p$ and $q$ be two different correct proposers in rounds $r_p$ and $r_q$ respectively, in height $h$.
|
||||
Let $s_{p,h-1}$ be $p$'s Application's state committed for height $h-1$.
|
||||
Let $v_p$ (resp. $v_q$) be the block that $p$'s (resp. $q$'s) Tendermint passes on to the Application
|
||||
via `RequestPrepareProposal` as proposer of round $r_p$ (resp $r_q$), height $h$, also known as the
|
||||
raw proposal.
|
||||
Let $v'_p$ (resp. $v'_q$) the possibly modified block $p$'s (resp. $q$'s) Application returns via
|
||||
`ResponsePrepareProposal` to Tendermint, also known as the prepared proposal.
|
||||
Let *p* and *q* be two different correct proposers in rounds *r<sub>p</sub>* and *r<sub>q</sub>*
|
||||
respectively, in height *h*.
|
||||
Let *s<sub>p,h-1</sub>* be *p*'s Application's state committed for height *h-1*.
|
||||
Let *v<sub>p</sub>* (resp. *v<sub>q</sub>*) be the block that *p*'s (resp. *q*'s) Tendermint passes
|
||||
on to the Application
|
||||
via `RequestPrepareProposal` as proposer of round *r<sub>p</sub>* (resp *r<sub>q</sub>*), height *h*,
|
||||
also known as the raw proposal.
|
||||
Let *v'<sub>p</sub>* (resp. *v'<sub>q</sub>*) the possibly modified block *p*'s (resp. *q*'s) Application
|
||||
returns via `ResponsePrepareProposal` to Tendermint, also known as the prepared proposal.
|
||||
|
||||
Process $p$'s prepared proposal can differ in two different rounds where $p$ is the proposer.
|
||||
Process *p*'s prepared proposal can differ in two different rounds where *p* is the proposer.
|
||||
|
||||
* Requirement 1 [`PrepareProposal`, header-changes] When the blockchain is in same-block execution mode,
|
||||
$p$'s Application provides values for the following parameters in `ResponsePrepareProposal`:
|
||||
_AppHash_, _TxResults_, _ConsensusParams_, _ValidatorUpdates_. Provided values for
|
||||
_ConsensusParams_ and _ValidatorUpdates_ MAY be empty to denote that the Application
|
||||
* Requirement 1 [`PrepareProposal`, header-changes]: When the blockchain is in same-block execution mode,
|
||||
*p*'s Application provides values for the following parameters in `ResponsePrepareProposal`:
|
||||
`AppHash`, `TxResults`, `ConsensusParams`, `ValidatorUpdates`. Provided values for
|
||||
`ConsensusParams` and `ValidatorUpdates` MAY be empty to denote that the Application
|
||||
wishes to keep the current values.
|
||||
|
||||
Parameters _AppHash_, _TxResults_, _ConsensusParams_, and _ValidatorUpdates_ are used by Tendermint to
|
||||
Parameters `AppHash`, `TxResults`, `ConsensusParams`, and `ValidatorUpdates` are used by Tendermint to
|
||||
compute various hashes in the block header that will finally be part of the proposal.
|
||||
|
||||
* Requirement 2 [`PrepareProposal`, no-header-changes] When the blockchain is in next-block execution
|
||||
mode, $p$'s Application does not provide values for the following parameters in `ResponsePrepareProposal`:
|
||||
_AppHash_, _TxResults_, _ConsensusParams_, _ValidatorUpdates_.
|
||||
* Requirement 2 [`PrepareProposal`, no-header-changes]: When the blockchain is in next-block execution
|
||||
mode, *p*'s Application does not provide values for the following parameters in `ResponsePrepareProposal`:
|
||||
`AppHash`, `TxResults`, `ConsensusParams`, `ValidatorUpdates`.
|
||||
|
||||
In practical terms, Requirements 1 and 2 imply that Tendermint will (a) panic if the Application is in
|
||||
same-block execution mode and _does_ _not_ provide values for
|
||||
_AppHash_, _TxResults_, _ConsensusParams_, and _ValidatorUpdates_, or
|
||||
(b) log an error if the Application is in next-block execution mode and _does_ provide values for
|
||||
_AppHash_, _TxResults_, _ConsensusParams_, or _ValidatorUpdates_ (the values provided will be ignored).
|
||||
same-block execution mode and *does not* provide values for
|
||||
`AppHash`, `TxResults`, `ConsensusParams`, and `ValidatorUpdates`, or
|
||||
(b) log an error if the Application is in next-block execution mode and *does* provide values for
|
||||
`AppHash`, `TxResults`, `ConsensusParams`, or `ValidatorUpdates` (the values provided will be ignored).
|
||||
|
||||
* Requirement 3 [`PrepareProposal`, timeliness] If $p$'s Application fully executes prepared blocks in
|
||||
`PrepareProposal` and the network is in a synchronous period while processes $p$ and $q$ are in $r_p$, then
|
||||
the value of *TimeoutPropose* at $q$ must be such that $q$'s propose timer does not time out
|
||||
(which would result in $q$ prevoting *nil* in $r_p$).
|
||||
* Requirement 3 [`PrepareProposal`, timeliness]: If *p*'s Application fully executes prepared blocks in
|
||||
`PrepareProposal` and the network is in a synchronous period while processes *p* and *q* are in *r<sub>p</sub>*,
|
||||
then the value of *TimeoutPropose* at *q* must be such that *q*'s propose timer does not time out
|
||||
(which would result in *q* prevoting `nil` in *r<sub>p</sub>*).
|
||||
|
||||
Full execution of blocks at `PrepareProposal` time stands on Tendermint's critical path. Thus,
|
||||
Requirement 3 ensures the Application will set a value for _TimeoutPropose_ such that the time it takes
|
||||
Requirement 3 ensures the Application will set a value for `TimeoutPropose` such that the time it takes
|
||||
to fully execute blocks in `PrepareProposal` does not interfere with Tendermint's propose timer.
|
||||
|
||||
* Requirement 4 [`PrepareProposal`, tx-size] When $p$'s Application calls `ResponsePrepareProposal`, the
|
||||
* Requirement 4 [`PrepareProposal`, tx-size]: When *p*'s Application calls `ResponsePrepareProposal`, the
|
||||
total size in bytes of the transactions returned does not exceed `RequestPrepareProposal.max_tx_bytes`.
|
||||
|
||||
Busy blockchains might seek to maximize the amount of transactions included in each block. Under those conditions,
|
||||
@@ -54,29 +56,31 @@ Tendermint might choose to increase the transactions passed to the Application v
|
||||
beyond the `RequestPrepareProposal.max_tx_bytes` limit. The idea is that, if the Application drops some of
|
||||
those transactions, it can still return a transaction list whose byte size is as close to
|
||||
`RequestPrepareProposal.max_tx_bytes` as possible. Thus, Requirement 4 ensures that the size in bytes of the
|
||||
transaction list returned by the application will never cause the resulting block to go beyond its byte limit.
|
||||
transaction list returned by the application will never cause the resulting block to go beyond its byte size
|
||||
limit.
|
||||
|
||||
* Requirement 5 [`PrepareProposal`, `ProcessProposal`, coherence]: For any two correct processes $p$ and $q$,
|
||||
if $q$'s Tendermint calls `RequestProcessProposal` on $v'_p$,
|
||||
$q$'s Application returns Accept in `ResponseProcessProposal`.
|
||||
* Requirement 5 [`PrepareProposal`, `ProcessProposal`, coherence]: For any two correct processes *p* and *q*,
|
||||
if *q*'s Tendermint calls `RequestProcessProposal` on *v'<sub>p</sub>*,
|
||||
*q*'s Application returns Accept in `ResponseProcessProposal`.
|
||||
|
||||
Requirement 5 makes sure that blocks proposed by correct processes _always_ pass the correct receiving process's
|
||||
Requirement 5 makes sure that blocks proposed by correct processes *always* pass the correct receiving process's
|
||||
`ProcessProposal` check.
|
||||
On the other hand, if there is a deterministic bug in `PrepareProposal` or `ProcessProposal` (or in both),
|
||||
strictly speaking, this makes all processes that hit the bug byzantine. This is a problem in practice,
|
||||
as very often validators are running the Application from the same codebase, so potentially _all_ would
|
||||
as very often validators are running the Application from the same codebase, so potentially *all* would
|
||||
likely hit the bug at the same time. This would result in most (or all) processes prevoting `nil`, with the
|
||||
serious consequences on Tendermint's liveness that this entails. Due to its criticality, Requirement 5 is a
|
||||
target for extensive testing and automated verification.
|
||||
|
||||
* Requirement 6 [`ProcessProposal`, determinism-1]: `ProcessProposal` is a (deterministic) function of the current
|
||||
state and the block that is about to be applied. In other words, for any correct process $p$, and any arbitrary block $v'$,
|
||||
if $p$'s Tendermint calls `RequestProcessProposal` on $v'$ at height $h$,
|
||||
then $p$'s Application's acceptance or rejection **exclusively** depends on $v'$ and $s_{p,h-1}$.
|
||||
state and the block that is about to be applied. In other words, for any correct process *p*, and any arbitrary block *v'*,
|
||||
if *p*'s Tendermint calls `RequestProcessProposal` on *v'* at height *h*,
|
||||
then *p*'s Application's acceptance or rejection **exclusively** depends on *v'* and *s<sub>p,h-1</sub>*.
|
||||
|
||||
* Requirement 7 [`ProcessProposal`, determinism-2]: For any two correct processes $p$ and $q$, and any arbitrary block $v'$,
|
||||
if $p$'s (resp. $q$'s) Tendermint calls `RequestProcessProposal` on $v'$ at height $h$,
|
||||
then $p$'s Application accepts $v'$ if and only if $q$'s Application accepts $v'$.
|
||||
* Requirement 7 [`ProcessProposal`, determinism-2]: For any two correct processes *p* and *q*, and any arbitrary
|
||||
block *v'*,
|
||||
if *p*'s (resp. *q*'s) Tendermint calls `RequestProcessProposal` on *v'* at height *h*,
|
||||
then *p*'s Application accepts *v'* if and only if *q*'s Application accepts *v'*.
|
||||
Note that this requirement follows from Requirement 6 and the Agreement property of consensus.
|
||||
|
||||
Requirements 6 and 7 ensure that all correct processes will react in the same way to a proposed block, even
|
||||
@@ -87,20 +91,26 @@ In such a scenario, Tendermint's liveness cannot be guaranteed.
|
||||
Again, this is a problem in practice if most validators are running the same software, as they are likely
|
||||
to hit the bug at the same point. There is currently no clear solution to help with this situation, so
|
||||
the Application designers/implementors must proceed very carefully with the logic/implementation
|
||||
of `ProcessProposal`. As a general rule `ProcessProposal` _should_ always accept the block.
|
||||
of `ProcessProposal`. As a general rule `ProcessProposal` SHOULD always accept the block.
|
||||
|
||||
According to the Tendermint algorithm, a correct process can broadcast at most one precommit message in round $r$, height $h$.
|
||||
Since, as stated in the [Description](#description) section, `ResponseExtendVote` is only called when Tendermint
|
||||
is about to broadcast a non-`nil` precommit message, a correct process can only produce one vote extension in round $r$, height $h$.
|
||||
Let $e^r_p$ be the vote extension that the Application of a correct process $p$ returns via `ResponseExtendVote` in round $r$, height $h$.
|
||||
Let $w^r_p$ be the proposed block that $p$'s Tendermint passes to the Application via `RequestExtendVote` in round $r$, height $h$.
|
||||
According to the Tendermint algorithm, a correct process can broadcast at most one precommit
|
||||
message in round *r*, height *h*.
|
||||
Since, as stated in the [Methods](./abci++_methods_002_draft.md#extendvote) section, `ResponseExtendVote`
|
||||
is only called when Tendermint
|
||||
is about to broadcast a non-`nil` precommit message, a correct process can only produce one vote extension
|
||||
in round *r*, height *h*.
|
||||
Let *e<sup>r</sup><sub>p</sub>* be the vote extension that the Application of a correct process *p* returns via
|
||||
`ResponseExtendVote` in round *r*, height *h*.
|
||||
Let *w<sup>r</sup><sub>p</sub>* be the proposed block that *p*'s Tendermint passes to the Application via `RequestExtendVote`
|
||||
in round *r*, height *h*.
|
||||
|
||||
* Requirement 8 [`ExtendVote`, `VerifyVoteExtension`, coherence]: For any two correct processes $p$ and $q$, if $q$ receives $e^r_p$
|
||||
from $p$ in height $h$, $q$'s Application returns Accept in `ResponseVerifyVoteExtension`.
|
||||
* Requirement 8 [`ExtendVote`, `VerifyVoteExtension`, coherence]: For any two correct processes *p* and *q*, if *q*
|
||||
receives *e<sup>r</sup><sub>p</sub>*
|
||||
from *p* in height *h*, *q*'s Application returns Accept in `ResponseVerifyVoteExtension`.
|
||||
|
||||
Requirement 8 constrains the creation and handling of vote extensions in a similar way as Requirement 5
|
||||
contrains the creation and handling of proposed blocks.
|
||||
Requirement 8 ensures that extensions created by correct processes _always_ pass the `VerifyVoteExtension`
|
||||
constrains the creation and handling of proposed blocks.
|
||||
Requirement 8 ensures that extensions created by correct processes *always* pass the `VerifyVoteExtension`
|
||||
checks performed by correct processes receiving those extensions.
|
||||
However, if there is a (deterministic) bug in `ExtendVote` or `VerifyVoteExtension` (or in both),
|
||||
we will face the same liveness issues as described for Requirement 5, as Precommit messages with invalid vote
|
||||
@@ -108,58 +118,62 @@ extensions will be discarded.
|
||||
|
||||
* Requirement 9 [`VerifyVoteExtension`, determinism-1]: `VerifyVoteExtension` is a (deterministic) function of
|
||||
the current state, the vote extension received, and the prepared proposal that the extension refers to.
|
||||
In other words, for any correct process $p$, and any arbitrary vote extension $e$, and any arbitrary
|
||||
block $w$, if $p$'s (resp. $q$'s) Tendermint calls `RequestVerifyVoteExtension` on $e$ and $w$ at height $h$,
|
||||
then $p$'s Application's acceptance or rejection **exclusively** depends on $e$, $w$ and $s_{p,h-1}$.
|
||||
In other words, for any correct process *p*, and any arbitrary vote extension *e*, and any arbitrary
|
||||
block *w*, if *p*'s (resp. *q*'s) Tendermint calls `RequestVerifyVoteExtension` on *e* and *w* at height *h*,
|
||||
then *p*'s Application's acceptance or rejection **exclusively** depends on *e*, *w* and *s<sub>p,h-1</sub>*.
|
||||
|
||||
* Requirement 10 [`VerifyVoteExtension`, determinism-2]: For any two correct processes $p$ and $q$,
|
||||
and any arbitrary vote extension $e$, and any arbitrary block $w$,
|
||||
if $p$'s (resp. $q$'s) Tendermint calls `RequestVerifyVoteExtension` on $e$ and $w$ at height $h$,
|
||||
then $p$'s Application accepts $e$ if and only if $q$'s Application accepts $e$.
|
||||
* Requirement 10 [`VerifyVoteExtension`, determinism-2]: For any two correct processes *p* and *q*,
|
||||
and any arbitrary vote extension *e*, and any arbitrary block *w*,
|
||||
if *p*'s (resp. *q*'s) Tendermint calls `RequestVerifyVoteExtension` on *e* and *w* at height *h*,
|
||||
then *p*'s Application accepts *e* if and only if *q*'s Application accepts *e*.
|
||||
Note that this requirement follows from Requirement 9 and the Agreement property of consensus.
|
||||
|
||||
Requirements 9 and 10 ensure that the validation of vote extensions will be deterministic at all
|
||||
correct processes.
|
||||
Requirements 9 and 10 protect against arbitrary vote extension data from Byzantine processes
|
||||
similarly to Requirements 6 and 7 and proposed blocks.
|
||||
Requirements 9 and 10 protect against arbitrary vote extension data from Byzantine processes,
|
||||
in a similar way as Requirements 6 and 7 protect against arbitrary proposed blocks.
|
||||
Requirements 9 and 10 can be violated by a bug inducing non-determinism in
|
||||
`VerifyVoteExtension`. In this case liveness can be compromised.
|
||||
Extra care should be put in the implementation of `ExtendVote` and `VerifyVoteExtension` and,
|
||||
as a general rule, `VerifyVoteExtension` _should_ always accept the vote extension.
|
||||
Extra care should be put in the implementation of `ExtendVote` and `VerifyVoteExtension`.
|
||||
As a general rule, `VerifyVoteExtension` SHOULD always accept the vote extension.
|
||||
|
||||
* Requirement 11 [_all_, no-side-effects]: $p$'s calls to `RequestPrepareProposal`,
|
||||
`RequestProcessProposal`, `RequestExtendVote`, and `RequestVerifyVoteExtension` at height $h$ do
|
||||
not modify $s_{p,h-1}$.
|
||||
* Requirement 11 [*all*, no-side-effects]: *p*'s calls to `RequestPrepareProposal`,
|
||||
`RequestProcessProposal`, `RequestExtendVote`, and `RequestVerifyVoteExtension` at height *h* do
|
||||
not modify *s<sub>p,h-1</sub>*.
|
||||
|
||||
* Requirement 12 [`ExtendVote`, `FinalizeBlock`, non-dependency]: for any correct process $p$,
|
||||
and any vote extension $e$ that $p$ received at height $h$, the computation of
|
||||
$s_{p,h}$ does not depend on $e$.
|
||||
* Requirement 12 [`ExtendVote`, `FinalizeBlock`, non-dependency]: for any correct process *p*,
|
||||
and any vote extension *e* that *p* received at height *h*, the computation of
|
||||
*s<sub>p,h</sub>* does not depend on *e*.
|
||||
|
||||
The call to correct process $p$'s `RequestFinalizeBlock` at height $h$, with block $v_{p,h}$
|
||||
passed as parameter, creates state $s_{p,h}$.
|
||||
The call to correct process *p*'s `RequestFinalizeBlock` at height *h*, with block *v<sub>p,h</sub>*
|
||||
passed as parameter, creates state *s<sub>p,h</sub>*.
|
||||
Additionally,
|
||||
|
||||
* in next-block execution mode, $p$'s `FinalizeBlock` creates a set of transaction results $T_{p,h}$,
|
||||
* in same-block execution mode, $p$'s `PrepareProposal` creates a set of transaction results $T_{p,h}$
|
||||
if $p$ was the proposer of $v_{p,h}$, otherwise `FinalizeBlock` creates $T_{p,h}$.
|
||||
* in next-block execution mode, *p*'s `FinalizeBlock` creates a set of transaction results *T<sub>p,h</sub>*,
|
||||
* in same-block execution mode, *p*'s `PrepareProposal` creates a set of transaction results *T<sub>p,h</sub>*
|
||||
if *p* was the proposer of *v<sub>p,h</sub>*. If *p* was not the proposer of *v<sub>p,h</sub>*,
|
||||
`ProcessProposal` creates *T<sub>p,h</sub>*. `FinalizeBlock` MAY re-create *T<sub>p,h</sub>* if it was
|
||||
removed from memory during the execution of height *h*.
|
||||
|
||||
* Requirement 13 [`FinalizeBlock`, determinism-1]: For any correct process $p$,
|
||||
$s_{p,h}$ exclusively depends on $s_{p,h-1}$ and $v_{p,h}$.
|
||||
* Requirement 13 [`FinalizeBlock`, determinism-1]: For any correct process *p*,
|
||||
*s<sub>p,h</sub>* exclusively depends on *s<sub>p,h-1</sub>* and *v<sub>p,h</sub>*.
|
||||
|
||||
* Requirement 14 [`FinalizeBlock`, determinism-2]: For any correct process $p$,
|
||||
the contents of $T_{p,h}$ exclusively depend on $s_{p,h-1}$ and $v_{p,h}$.
|
||||
* Requirement 14 [`FinalizeBlock`, determinism-2]: For any correct process *p*,
|
||||
the contents of *T<sub>p,h</sub>* exclusively depend on *s<sub>p,h-1</sub>* and *v<sub>p,h</sub>*.
|
||||
|
||||
Note that Requirements 13 and 14, combined with Agreement property of consensus ensure
|
||||
the Application state evolves consistently at all correct processes.
|
||||
state machine replication, i.e., the Application state evolves consistently at all correct processes.
|
||||
|
||||
Finally, notice that neither `PrepareProposal` nor `ExtendVote` have determinism-related
|
||||
requirements associated.
|
||||
Indeed, `PrepareProposal` is not required to be deterministic:
|
||||
|
||||
* $v'_p$ may depend on $v_p$ and $s_{p,h-1}$, but may also depend on other values or operations.
|
||||
* $v_p = v_q \nRightarrow v'_p = v'_q$.
|
||||
* *v'<sub>p</sub>* may depend on *v<sub>p</sub>* and *s<sub>p,h-1</sub>*, but may also depend on other values or operations.
|
||||
* *v<sub>p</sub> = v<sub>q</sub> ⇏ v'<sub>p</sub> = v'<sub>q</sub>*.
|
||||
|
||||
Likewise, `ExtendVote` can also be non-deterministic:
|
||||
|
||||
* $e^r_p$ may depend on $w^r_p$ and $s_{p,h-1}$, but may also depend on other values or operations.
|
||||
* $w^r_p = w^r_q \nRightarrow e^r_p = e^r_q$
|
||||
* *e<sup>r</sup><sub>p</sub>* may depend on *w<sup>r</sup><sub>p</sub>* and *s<sub>p,h-1</sub>*,
|
||||
but may also depend on other values or operations.
|
||||
* *w<sup>r</sup><sub>p</sub> = w<sup>r</sup><sub>q</sub> ⇏
|
||||
e<sup>r</sup><sub>p</sub> = e<sup>r</sup><sub>q</sub>*
|
||||
|
||||
@@ -1,16 +1,31 @@
|
||||
# PBTS: System Model and Properties
|
||||
|
||||
## Outline
|
||||
|
||||
- [System model](#system-model)
|
||||
- [Synchronized clocks](#synchronized-clocks)
|
||||
- [Message delays](#message-delays)
|
||||
- [Problem Statement](#problem-statement)
|
||||
- [Protocol Analysis - Timely Proposals](#protocol-analysis---timely-proposals)
|
||||
- [Timely Proof-of-Locks](#timely-proof-of-locks)
|
||||
- [Derived Proof-of-Locks](#derived-proof-of-locks)
|
||||
- [Temporal Analysis](#temporal-analysis)
|
||||
- [Safety](#safety)
|
||||
- [Liveness](#liveness)
|
||||
|
||||
## System Model
|
||||
|
||||
#### **[PBTS-CLOCK-NEWTON.0]**
|
||||
|
||||
There is a reference Newtonian real-time `t` (UTC).
|
||||
There is a reference Newtonian real-time `t`.
|
||||
|
||||
No process has direct access to this reference time, used only for specification purposes.
|
||||
The reference real-time is assumed to be aligned with the Coordinated Universal Time (UTC).
|
||||
|
||||
### Synchronized clocks
|
||||
|
||||
Processes are assumed to be equipped with synchronized clocks.
|
||||
Processes are assumed to be equipped with synchronized clocks,
|
||||
aligned with the Coordinated Universal Time (UTC).
|
||||
|
||||
This requires processes to periodically synchronize their local clocks with an
|
||||
external and trusted source of the time (e.g. NTP servers).
|
||||
@@ -27,43 +42,35 @@ and drifts of local clocks from real time.
|
||||
#### **[PBTS-CLOCK-PRECISION.0]**
|
||||
|
||||
There exists a system parameter `PRECISION`, such that
|
||||
for any two processes `p` and `q`, with local clocks `C_p` and `C_q`,
|
||||
that read their local clocks at the same real-time `t`, we have:
|
||||
for any two processes `p` and `q`, with local clocks `C_p` and `C_q`:
|
||||
|
||||
- If `p` and `q` are equipped with synchronized clocks, then `|C_p(t) - C_q(t)| < PRECISION`
|
||||
- If `p` and `q` are equipped with synchronized clocks,
|
||||
then for any real-time `t` we have `|C_p(t) - C_q(t)| <= PRECISION`.
|
||||
|
||||
`PRECISION` thus bounds the difference on the times simultaneously read by processes
|
||||
from their local clocks, so that their clocks can be considered synchronized.
|
||||
|
||||
#### Accuracy
|
||||
|
||||
The [first draft][sysmodel_v1] of this specification included a second clock-related parameter, `ACCURACY`,
|
||||
that relates the values read by processes from their synchronized clocks with real time:
|
||||
A second relevant clock parameter is accuracy, which binds the values read by
|
||||
processes from their clocks to real time.
|
||||
|
||||
- If `p` is a process is equipped with a synchronized clock, then at real time
|
||||
`t` it reads from its clock time `C_p(t)` with `|C_p(t) - t| < ACCURACY`
|
||||
##### **[PBTS-CLOCK-ACCURACY.0]**
|
||||
|
||||
The adoption of `ACCURACY` as the upper bound on the difference between clock
|
||||
readings and real time, however, renders the `PRECISION` parameter redundant.
|
||||
In fact, if we assume that clocks readings are at most `ACCURACY` from real
|
||||
time, we would therefore be assuming that they cannot be more than `2 * ACCURACY`
|
||||
apart from each other, thus establishing a worst-case upper bound for `PRECISION`.
|
||||
|
||||
The approach we take is to assume that processes clocks are periodically
|
||||
synchronized with an external source of time, thus improving their accuracy.
|
||||
This allows us to adopt a relaxed version of the above `ACCURACY` definition:
|
||||
|
||||
##### **[PBTS-CLOCK-FAIR.0]**
|
||||
For the sake of completeness, we define a parameter `ACCURACY` such that:
|
||||
|
||||
- At real time `t` there is at least one correct process `p` which clock marks
|
||||
`C_p(t)` with `|C_p(t) - t| < ACCURACY`
|
||||
`C_p(t)` with `|C_p(t) - t| <= ACCURACY`.
|
||||
|
||||
Then, through [PBTS-CLOCK-PRECISION] we can extend this relation of clock times
|
||||
with real time to every correct process, which will have a clock with accuracy
|
||||
bound by `ACCURACY + PRECISION`.
|
||||
But, for the sake of simpler specification we can assume that the `PRECISION`,
|
||||
which is a worst-case parameter that applies to all correct processes,
|
||||
includes the best `ACCURACY` achieved by any of them.
|
||||
As a consequence, applying the definition of `PRECISION`, we have:
|
||||
|
||||
- At real time `t` the synchronized clock of any correct process `p` marks
|
||||
`C_p(t)` with `|C_p(t) - t| <= ACCURACY + PRECISION`.
|
||||
|
||||
The reason for not adopting `ACCURACY` as a system parameter is the assumption
|
||||
that `PRECISION >> ACCURACY`.
|
||||
This allows us to consider, for practical purposes, that the `PRECISION` system
|
||||
parameter embodies the `ACCURACY` model parameter.
|
||||
|
||||
### Message Delays
|
||||
|
||||
@@ -79,172 +86,264 @@ defining a lower bound, a *minimum time* that a correct process assigns to propo
|
||||
While *minimum delay* for delivering a proposal to a destination allows defining
|
||||
an upper bound, the *maximum time* assigned to a proposal.
|
||||
|
||||
#### **[PBTS-MSG-D.0]**
|
||||
#### **[PBTS-MSG-DELAY.0]**
|
||||
|
||||
There exists a system parameter `MSGDELAY` for end-to-end delays of messages carrying proposals,
|
||||
such for any two correct processes `p` and `q`, and any real time `t`:
|
||||
There exists a system parameter `MSGDELAY` for end-to-end delays of proposal messages,
|
||||
such for any two correct processes `p` and `q`:
|
||||
|
||||
- If `p` sends a message `m` carrying a proposal at time `ts`,
|
||||
then if `q` receives the message and learns the proposal,
|
||||
`q` does that at time `t` such that `ts <= t <= ts + MSGDELAY`.
|
||||
- If `p` sends a proposal message `m` at real time `t` and `q` receives `m` at
|
||||
real time `t'`, then `t <= t' <= t + MSGDELAY`.
|
||||
|
||||
While we don't want to impose particular restrictions regarding the format of `m`,
|
||||
we need to assume that their size is upper bounded.
|
||||
In practice, using messages with a fixed-size to carry proposals allows
|
||||
for a more accurate estimation of `MSGDELAY`, and therefore is advised.
|
||||
Notice that, as a system parameter, `MSGDELAY` should be observed for any
|
||||
proposal message broadcast by correct processes: it is a *worst-case* parameter.
|
||||
As message delays depends on the message size, the above requirement implicitly
|
||||
indicates that the size of proposal messages is either fixed or upper bounded.
|
||||
|
||||
## Problem Statement
|
||||
|
||||
In this section we define the properties of Tendermint consensus
|
||||
(cf. the [arXiv paper][arXiv]) in this new system model.
|
||||
(cf. the [arXiv paper][arXiv]) in this system model.
|
||||
|
||||
#### **[PBTS-PROPOSE.0]**
|
||||
### **[PBTS-PROPOSE.0]**
|
||||
|
||||
A proposer proposes a consensus value `v` with an associated proposal time `v.time`.
|
||||
A proposer proposes a consensus value `v` that includes a proposal time
|
||||
`v.time`.
|
||||
|
||||
> We then restrict the allowed decisions along the following lines:
|
||||
|
||||
#### **[PBTS-INV-AGREEMENT.0]**
|
||||
|
||||
[Agreement] No two correct processes decide on different values `v`. (This implies that no two correct processes decide on different proposal times `v.time`.)
|
||||
- [Agreement] No two correct processes decide on different values `v`.
|
||||
|
||||
This implies that no two correct processes decide on different proposal times
|
||||
`v.time`.
|
||||
|
||||
#### **[PBTS-INV-VALID.0]**
|
||||
|
||||
[Validity] If a correct process decides on value `v`,
|
||||
then `v` satisfies a predefined `valid` predicate.
|
||||
- [Validity] If a correct process decides on value `v`, then `v` satisfies a
|
||||
predefined `valid` predicate.
|
||||
|
||||
With respect to PBTS, the `valid` predicate requires proposal times to be
|
||||
[monotonic](./pbts-algorithm_002_draft.md#time-monotonicity) over heights of
|
||||
consensus:
|
||||
|
||||
##### **[PBTS-INV-MONOTONICITY.0]**
|
||||
|
||||
- If a correct process decides on value `v` at the height `h` of consensus,
|
||||
thus setting `decision[h] = v`, then `v.time > decision[h'].time` for all
|
||||
previous heights `h' < h`.
|
||||
|
||||
The monotonicity of proposal times, and external validity in general,
|
||||
implicitly assumes that heights of consensus are executed in order.
|
||||
|
||||
#### **[PBTS-INV-TIMELY.0]**
|
||||
|
||||
[Time-Validity] If a correct process decides on value `v`,
|
||||
then the associated proposal time `v.time` satisfies a predefined `timely` predicate.
|
||||
- [Time-Validity] If a correct process decides on value `v`, then the proposal
|
||||
time `v.time` was considered `timely` by at least one correct process.
|
||||
|
||||
> Both [Validity] and [Time-Validity] must be observed even if up to `2f` validators are faulty.
|
||||
PBTS introduces a `timely` predicate that restricts the allowed decisions based
|
||||
on the proposal time `v.time` associated with a proposed value `v`.
|
||||
As a synchronous predicate, the time at which it is evaluated impacts on
|
||||
whether a process accepts or reject a proposal time.
|
||||
For this reason, the Time-Validity property refers to the previous evaluation
|
||||
of the `timely` predicate, detailed in the following section.
|
||||
|
||||
### Timely proposals
|
||||
## Protocol Analysis - Timely proposals
|
||||
|
||||
For PBTS, a `proposal` is a tuple `(v, v.time, v.round)`, where:
|
||||
|
||||
- `v` is the proposed value;
|
||||
- `v.time` is the associated proposal time;
|
||||
- `v.round` is the round at which `v` was first proposed.
|
||||
|
||||
We include the proposal round `v.round` in the proposal definition because a
|
||||
value `v` and its associated proposal time `v.time` can be proposed in multiple
|
||||
rounds, but the evaluation of the `timely` predicate is only relevant at round
|
||||
`v.round`.
|
||||
|
||||
> Considering the algorithm in the [arXiv paper][arXiv], a new proposal is
|
||||
> produced by the `getValue()` method, invoked by the proposer `p` of round
|
||||
> `round_p` when starting its proposing round with a nil `validValue_p`.
|
||||
> The first round at which a value `v` is proposed is then the round at which
|
||||
> the proposal for `v` was produced, and broadcast in a `PROPOSAL` message with
|
||||
> `vr = -1`.
|
||||
|
||||
#### **[PBTS-PROPOSAL-RECEPTION.0]**
|
||||
|
||||
The `timely` predicate is evaluated when a process receives a proposal.
|
||||
Let `now_p` be time a process `p` reads from its local clock when `p` receives a proposal.
|
||||
Let `v` be the proposed value and `v.time` the proposal time.
|
||||
The proposal is considered `timely` by `p` if:
|
||||
More precisely, let `p` be a correct process:
|
||||
|
||||
#### **[PBTS-RECEPTION-STEP.1]**
|
||||
- `proposalReceptionTime(p,r)` is the time `p` reads from its local clock when
|
||||
`p` is at round `r` and receives the proposal of round `r`.
|
||||
|
||||
1. `now_p >= v.time - PRECISION` and
|
||||
1. `now_p <= v.time + MSGDELAY + PRECISION`
|
||||
#### **[PBTS-TIMELY.0]**
|
||||
|
||||
The proposal `(v, v.time, v.round)` is considered `timely` by a correct process
|
||||
`p` if:
|
||||
|
||||
1. `proposalReceptionTime(p,v.round)` is set, and
|
||||
1. `proposalReceptionTime(p,v.round) >= v.time - PRECISION`, and
|
||||
1. `proposalReceptionTime(p,v.round) <= v.time + MSGDELAY + PRECISION`.
|
||||
|
||||
A correct process at round `v.round` only sends a `PREVOTE` for `v` if the
|
||||
associated proposal time `v.time` is considered `timely`.
|
||||
|
||||
> Considering the algorithm in the [arXiv paper][arXiv], the `timely` predicate
|
||||
> is evaluated by a process `p` when it receives a valid `PROPOSAL` message
|
||||
> from the proposer of the current round `round_p` with `vr = -1`.
|
||||
|
||||
### Timely Proof-of-Locks
|
||||
|
||||
We denote by `POL(v,r)` a *Proof-of-Lock* of value `v` at the round `r` of consensus.
|
||||
`POL(v,r)` consists of a set of `PREVOTE` messages of round `r` for the value `v`
|
||||
from processes whose cumulative voting power is at least `2f + 1`.
|
||||
A *Proof-of-Lock* is a set of `PREVOTE` messages of round of consensus for the
|
||||
same value from processes whose cumulative voting power is at least `2f + 1`.
|
||||
We denote as `POL(v,r)` a proof-of-lock of value `v` at round `r`.
|
||||
|
||||
#### **[PBTS-TIMELY-POL.1]**
|
||||
For PBTS, we are particularly interested in the `POL(v,v.round)` produced in
|
||||
the round `v.round` at which a value `v` was first proposed.
|
||||
We call it a *timely* proof-of-lock for `v` because it can only be observed
|
||||
if at least one correct process considered it `timely`:
|
||||
|
||||
#### **[PBTS-TIMELY-POL.0]**
|
||||
|
||||
If
|
||||
|
||||
- there is a valid `POL(v,r*)` for height `h`, and
|
||||
- `r*` is the lowest-numbered round `r` of height `h` for which there is a valid `POL(v,r)`, and
|
||||
- `POL(v,r*)` contains a `PREVOTE` message from at least one correct process,
|
||||
- there is a valid `POL(v,r)` with `r = v.round`, and
|
||||
- `POL(v,v.round)` contains a `PREVOTE` message from at least one correct process,
|
||||
|
||||
Then, where `p` is a such correct process:
|
||||
Then, let `p` is a such correct process:
|
||||
|
||||
- `p` received a `PROPOSE` message of round `r*` and height `h`, and
|
||||
- the `PROPOSE` message contained a proposal for value `v` with proposal time `v.time`, and
|
||||
- a correct process `p` considered the proposal `timely`.
|
||||
- `p` received a `PROPOSAL` message of round `v.round`, and
|
||||
- the `PROPOSAL` message contained a proposal `(v, v.time, v.round)`, and
|
||||
- `p` was in round `v.round` and evaluated the proposal time `v.time` as `timely`.
|
||||
|
||||
The round `r*` above defined will be, in most cases,
|
||||
the round in which `v` was originally proposed, and when `v.time` was assigned,
|
||||
using a `PROPOSE` message with `POLRound = -1`.
|
||||
In any case, at least one correct process must consider the proposal `timely` at round `r*`
|
||||
to enable a valid `POL(v,r*)` to be observed.
|
||||
The existence of a such correct process `p` is guaranteed provided that the
|
||||
voting power of Byzantine processes is bounded by `2f`.
|
||||
|
||||
### Derived Proof-of-Locks
|
||||
|
||||
#### **[PBTS-DERIVED-POL.1]**
|
||||
The existence of `POL(v,r)` is a requirement for the decision of `v` at round
|
||||
`r` of consensus.
|
||||
|
||||
At the same time, the Time-Validity property establishes that if `v` is decided
|
||||
then a timely proof-of-lock `POL(v,v.round)` must have been produced.
|
||||
|
||||
So, we need to demonstrate here that any valid `POL(v,r)` is either a timely
|
||||
proof-of-lock or it is derived from a timely proof-of-lock:
|
||||
|
||||
#### **[PBTS-DERIVED-POL.0]**
|
||||
|
||||
If
|
||||
|
||||
- there is a valid `POL(v,r)` for height `h`, and
|
||||
- there is a valid `POL(v,r)`, and
|
||||
- `POL(v,r)` contains a `PREVOTE` message from at least one correct process,
|
||||
|
||||
Then
|
||||
|
||||
- there is a valid `POL(v,r*)` for height `h`, with `r* <= r`, and
|
||||
- `POL(v,r*)` contains a `PREVOTE` message from at least one correct process, and
|
||||
- a correct process considered the proposal for `v` `timely` at round `r*`.
|
||||
- there is a valid `POL(v,v.round)` with `v.round <= r` which is a timely proof-of-lock.
|
||||
|
||||
The above relation derives from a recursion on the round number `r`.
|
||||
It is trivially observed when `r = r*`, the base of the recursion,
|
||||
when a timely `POL(v,r*)` is obtained.
|
||||
We need to ensure that, once a timely `POL(v,r*)` is obtained,
|
||||
it is possible to obtain a valid `POL(v,r)` with `r > r*`,
|
||||
without the need of satisfying the `timely` predicate (again) in round `r`.
|
||||
In fact, since rounds are started in order, it is not likely that
|
||||
a proposal time `v.time`, assigned at round `r*`,
|
||||
will still be considered `timely` when the round `r > r*` is in progress.
|
||||
The above relation is trivially observed when `r = v.round`, as `POL(v,r)` must
|
||||
be a timely proof-of-lock.
|
||||
Notice that we cannot have `r < v.round`, as `v.round` is defined as the first
|
||||
round at which `v` was proposed.
|
||||
|
||||
In other words, the algorithm should ensure that once a `POL(v,r*)` attests
|
||||
that the proposal for `v` is `timely`,
|
||||
further valid `POL(v,r)` with `r > r*` can be obtained,
|
||||
even though processes do not consider the proposal for `v` `timely` any longer.
|
||||
For `r > v.round` we need to demonstrate that if there is a valid `POL(v,r)`,
|
||||
then a timely `POL(v,v.round)` was previously obtained.
|
||||
We observe that a condition for observing a `POL(v,r)` is that the proposer of
|
||||
round `r` has broadcast a `PROPOSAL` message for `v`.
|
||||
As `r > v.round`, we can affirm that `v` was not produced in round `r`.
|
||||
Instead, by the protocol operation, `v` was a *valid value* for the proposer of
|
||||
round `r`, which means that if the proposer has observed a `POL(v,vr)` with `vr
|
||||
< r`.
|
||||
The above operation considers a *correct* proposer, but since a `POL(v,r)` was
|
||||
produced (by hypothesis) we can affirm that at least one correct process (also)
|
||||
observed a `POL(v,vr)`.
|
||||
|
||||
> This can be achieved if the proposer of round `r' > r*` proposes `v` in a `PROPOSE` message
|
||||
with `POLRound = r*`, and at least one correct processes is aware of a `POL(v,r*)`.
|
||||
> From this point, if a valid `POL(v,r')` is achieved, it can replace the adopted `POL(v,r*)`.
|
||||
> Considering the algorithm in the [arXiv paper][arXiv], `v` was proposed by
|
||||
> the proposer `p` of round `round_p` because its `validValue_p` variable was
|
||||
> set to `v`.
|
||||
> The `PROPOSAL` message broadcast by the proposer, in this case, had `vr > -1`,
|
||||
> and it could only be accepted by processes that also observed a `POL(v,vr)`.
|
||||
|
||||
### SAFETY
|
||||
Thus, if there is a `POL(v,r)` with `r > v.round`, then there is a valid
|
||||
`POL(v,vr)` with `v.round <= vr < r`.
|
||||
If `vr = v.round` then `POL(vr,v)` is a timely proof-of-lock and we are done.
|
||||
Otherwise, there is another valid `POL(v,vr')` with `v.round <= vr' < vr`,
|
||||
and the above reasoning can be recursively applied until we get `vr' = v.round`
|
||||
and observe a timely proof-of-lock.
|
||||
|
||||
The safety of the algorithm requires a *timely* proof-of-lock for a decided value,
|
||||
either directly evaluated by a correct process,
|
||||
or indirectly received through a derived proof-of-lock.
|
||||
## Temporal analysis
|
||||
|
||||
#### **[PBTS-CONSENSUS-TIME-VALID.0]**
|
||||
In this section we present invariants that need be observed for ensuring that
|
||||
PBTS is both safe and live.
|
||||
|
||||
In addition to the variables and system parameters already defined, we use
|
||||
`beginRound(p,r)` as the value of process `p`'s local clock
|
||||
when it starts round `r` of consensus.
|
||||
|
||||
### Safety
|
||||
|
||||
The safety of PBTS requires that if a value `v` is decided, then at least one
|
||||
correct process `p` considered the associated proposal time `v.time` timely.
|
||||
Following the definition of [timely proposals](#pbts-timely0) and
|
||||
proof-of-locks, we require this condition to be asserted at a specific round of
|
||||
consensus, defined as `v.round`:
|
||||
|
||||
#### **[PBTS-SAFETY.0]**
|
||||
|
||||
If
|
||||
|
||||
- there is a valid commit `C` for height `k` and round `r`, and
|
||||
- there is a valid commit `C` for a value `v`
|
||||
- `C` contains a `PRECOMMIT` message from at least one correct process
|
||||
|
||||
Then, where `p` is one such correct process:
|
||||
then there is a correct process `p` (not necessarily the same above considered) such that:
|
||||
|
||||
- since `p` is correct, `p` received a valid `POL(v,r)`, and
|
||||
- `POL(v,r)` contains a `PREVOTE` message from at least one correct process, and
|
||||
- `POL(v,r)` is derived from a timely `POL(v,r*)` with `r* <= r`, and
|
||||
- `POL(v,r*)` contains a `PREVOTE` message from at least one correct process, and
|
||||
- a correct process considered a proposal for `v` `timely` at round `r*`.
|
||||
- `beginRound(p,v.round) <= proposalReceptionTime(p,v.round) <= beginRound(p,v.round+1)` and
|
||||
- `proposalReceptionTime (p,v.round) - MSGDELAY - PRECISION <= v.time <= proposalReceptionTime(p,v.round) + PRECISION`
|
||||
|
||||
### LIVENESS
|
||||
That is, a correct process `p` started round `v.round` and, while still at
|
||||
round `v.round`, received a `PROPOSAL` message from round `v.round` proposing
|
||||
`v`.
|
||||
Moreover, the reception time of the original proposal for `v`, according with
|
||||
`p`'s local clock, enabled `p` to consider the proposal time `v.time` as
|
||||
`timely`.
|
||||
This is the requirement established by PBTS for issuing a `PREVOTE` for the
|
||||
proposal `(v, v.time, v.round)`, so for the eventual decision of `v`.
|
||||
|
||||
In terms of liveness, we need to ensure that a proposal broadcast by a correct process
|
||||
will be considered `timely` by any correct process that is ready to accept that proposal.
|
||||
So, if:
|
||||
### Liveness
|
||||
|
||||
- the proposer `p` of a round `r` is correct,
|
||||
- there is no `POL(v',r')` for any value `v'` and any round `r' < r`,
|
||||
- `p` proposes a valid value `v` and sets `v.time` to the time it reads from its local clock,
|
||||
The liveness of PBTS relies on correct processes accepting proposal times
|
||||
assigned by correct proposers.
|
||||
We thus present a set of conditions for assigning a proposal time `v.time` so
|
||||
that every correct process should be able to issue a `PREVOTE` for `v`.
|
||||
|
||||
Then let `q` be a correct process that receives `p`'s proposal, we have:
|
||||
#### **[PBTS-LIVENESS.0]**
|
||||
|
||||
- `q` receives `p`'s proposal after its clock reads `v.time - PRECISION`, and
|
||||
- if `q` is at or joins round `r` while `p`'s proposal is being transmitted,
|
||||
then `q` receives `p`'s proposal before its clock reads `v.time + MSGDELAY + PRECISION`
|
||||
If
|
||||
|
||||
> Note that, before `GST`, we cannot ensure that every correct process receives `p`'s proposals, nor that it does it while ready to accept a round `r` proposal.
|
||||
- the proposer of a round `r` of consensus is correct
|
||||
- and it proposes a value `v` for the first time, with associated proposal time `v.time`
|
||||
|
||||
A correct process `q` as above defined must then consider `p`'s proposal `timely`.
|
||||
It will then broadcast a `PREVOTE` message for `v` at round `r`,
|
||||
thus enabling, from the Time-Validity point of view, `v` to be eventually decided.
|
||||
then the proposal `(v, v.time, r)` is accepted by every correct process provided that:
|
||||
|
||||
#### Under-estimated `MSGDELAY`s
|
||||
- `min{p is correct : beginRound(p,r)} <= v.time <= max{p is correct : beginRound(p,r)}` and
|
||||
- `max{p is correct : beginRound(p,r)} <= v.time + MSGDELAY + PRECISION <= min{p is correct : beginRound(p,r+1)}`
|
||||
|
||||
The liveness assumptions of PBTS are conditioned by a conservative and clever
|
||||
choice of the timing parameters, specially of `MSGDELAY`.
|
||||
In fact, if the transmission delay for a message carrying a proposal is wrongly
|
||||
estimated, correct processes may never consider a valid proposal as `timely`.
|
||||
The first condition establishes a range of safe proposal times `v.time` for round `r`.
|
||||
This condition is trivially observed if a correct proposer `p` sets `v.time` to the time it
|
||||
reads from its clock when starting round `r` and proposing `v`.
|
||||
A `PROPOSAL` message sent by `p` at local time `v.time` should not be received
|
||||
by any correct process before its local clock reads `v.time - PRECISION`, so
|
||||
that condition 2 of [PBTS-TIMELY.0] is observed.
|
||||
|
||||
To circumvent this liveness issue, which could result from a misconfiguration,
|
||||
we assume that the `MSGDELAY` parameter can be increased as rounds do not
|
||||
succeed on deciding a value, possibly because no proposal is considered
|
||||
`timely` by enough processes.
|
||||
The precise behavior for this workaround is under [discussion](https://github.com/tendermint/spec/issues/371).
|
||||
The second condition establishes that every correct process should start round
|
||||
`v.round` at a local time that allows `v.time` to still be considered timely,
|
||||
according to condition 3. of [PBTS-TIMELY.0].
|
||||
In addition, it requires correct processes to stay long enough in round
|
||||
`v.round` so that they can receive the `PROPOSAL` message of round `v.round`.
|
||||
It assumed here that the proposer of `v` broadcasts a `PROPOSAL` message at
|
||||
time `v.time`, according to its local clock, so that every correct process
|
||||
should receive this message by time `v.time + MSGDELAY + PRECISION`, according
|
||||
to their local clocks.
|
||||
|
||||
Back to [main document][main].
|
||||
|
||||
|
||||
Reference in New Issue
Block a user