|
Byzantine Consensus Algorithm
|
|
=============================
|
|
|
|
Terms
|
|
-----
|
|
|
|
- The network is composed of optionally connected *nodes*. Nodes
|
|
directly connected to a particular node are called *peers*.
|
|
- The consensus process in deciding the next block (at some *height*
|
|
``H``) is composed of one or many *rounds*.
|
|
- ``NewHeight``, ``Propose``, ``Prevote``, ``Precommit``, and
|
|
``Commit`` represent state machine states of a round. (aka
|
|
``RoundStep`` or just "step").
|
|
- A node is said to be *at* a given height, round, and step, or at
|
|
``(H,R,S)``, or at ``(H,R)`` in short to omit the step.
|
|
- To *prevote* or *precommit* something means to broadcast a `prevote
|
|
vote <https://godoc.org/github.com/tendermint/tendermint/types#Vote>`__
|
|
or `first precommit
|
|
vote <https://godoc.org/github.com/tendermint/tendermint/types#FirstPrecommit>`__
|
|
for something.
|
|
- A vote *at* ``(H,R)`` is a vote signed with the bytes for ``H`` and
|
|
``R`` included in its
|
|
`sign-bytes <block-structure.html#vote-sign-bytes>`__.
|
|
- *+2/3* is short for "more than 2/3"
|
|
- *1/3+* is short for "1/3 or more"
|
|
- A set of +2/3 of prevotes for a particular block or ``<nil>`` at
|
|
``(H,R)`` is called a *proof-of-lock-change* or *PoLC* for short.
|
|
|
|
State Machine Overview
|
|
----------------------
|
|
|
|
At each height of the blockchain a round-based protocol is run to
|
|
determine the next block. Each round is composed of three *steps*
|
|
(``Propose``, ``Prevote``, and ``Precommit``), along with two special
|
|
steps ``Commit`` and ``NewHeight``.
|
|
|
|
In the optimal scenario, the order of steps is:
|
|
|
|
::
|
|
|
|
NewHeight -> (Propose -> Prevote -> Precommit)+ -> Commit -> NewHeight ->...
|
|
|
|
The sequence ``(Propose -> Prevote -> Precommit)`` is called a *round*.
|
|
There may be more than one round required to commit a block at a given
|
|
height. Examples for why more rounds may be required include:
|
|
|
|
- The designated proposer was not online.
|
|
- The block proposed by the designated proposer was not valid.
|
|
- The block proposed by the designated proposer did not propagate in
|
|
time.
|
|
- The block proposed was valid, but +2/3 of prevotes for the proposed
|
|
block were not received in time for enough validator nodes by the
|
|
time they reached the ``Precommit`` step. Even though +2/3 of
|
|
prevotes are necessary to progress to the next step, at least one
|
|
validator may have voted ``<nil>`` or maliciously voted for something
|
|
else.
|
|
- The block proposed was valid, and +2/3 of prevotes were received for
|
|
enough nodes, but +2/3 of precommits for the proposed block were not
|
|
received for enough validator nodes.
|
|
|
|
Some of these problems are resolved by moving onto the next round &
|
|
proposer. Others are resolved by increasing certain round timeout
|
|
parameters over each successive round.
|
|
|
|
State Machine Diagram
|
|
---------------------
|
|
|
|
::
|
|
|
|
+-------------------------------------+
|
|
v |(Wait til `CommmitTime+timeoutCommit`)
|
|
+-----------+ +-----+-----+
|
|
+----------> | Propose +--------------+ | NewHeight |
|
|
| +-----------+ | +-----------+
|
|
| | ^
|
|
|(Else, after timeoutPrecommit) v |
|
|
+-----+-----+ +-----------+ |
|
|
| Precommit | <------------------------+ Prevote | |
|
|
+-----+-----+ +-----------+ |
|
|
|(When +2/3 Precommits for block found) |
|
|
v |
|
|
+--------------------------------------------------------------------+
|
|
| Commit |
|
|
| |
|
|
| * Set CommitTime = now; |
|
|
| * Wait for block, then stage/save/commit block; |
|
|
+--------------------------------------------------------------------+
|
|
|
|
Background Gossip
|
|
-----------------
|
|
|
|
A node may not have a corresponding validator private key, but it
|
|
nevertheless plays an active role in the consensus process by relaying
|
|
relevant meta-data, proposals, blocks, and votes to its peers. A node
|
|
that has the private keys of an active validator and is engaged in
|
|
signing votes is called a *validator-node*. All nodes (not just
|
|
validator-nodes) have an associated state (the current height, round,
|
|
and step) and work to make progress.
|
|
|
|
Between two nodes there exists a ``Connection``, and multiplexed on top
|
|
of this connection are fairly throttled ``Channel``\ s of information.
|
|
An epidemic gossip protocol is implemented among some of these channels
|
|
to bring peers up to speed on the most recent state of consensus. For
|
|
example,
|
|
|
|
- Nodes gossip ``PartSet`` parts of the current round's proposer's
|
|
proposed block. A LibSwift inspired algorithm is used to quickly
|
|
broadcast blocks across the gossip network.
|
|
- Nodes gossip prevote/precommit votes. A node NODE\_A that is ahead of
|
|
NODE\_B can send NODE\_B prevotes or precommits for NODE\_B's current
|
|
(or future) round to enable it to progress forward.
|
|
- Nodes gossip prevotes for the proposed PoLC (proof-of-lock-change)
|
|
round if one is proposed.
|
|
- Nodes gossip to nodes lagging in blockchain height with block
|
|
`commits <https://godoc.org/github.com/tendermint/tendermint/types#Commit>`__
|
|
for older blocks.
|
|
- Nodes opportunistically gossip ``HasVote`` messages to hint peers
|
|
what votes it already has.
|
|
- Nodes broadcast their current state to all neighboring peers. (but is
|
|
not gossiped further)
|
|
|
|
There's more, but let's not get ahead of ourselves here.
|
|
|
|
Proposals
|
|
---------
|
|
|
|
A proposal is signed and published by the designated proposer at each
|
|
round. The proposer is chosen by a deterministic and non-choking round
|
|
robin selection algorithm that selects proposers in proportion to their
|
|
voting power. (see
|
|
`implementation <https://github.com/tendermint/tendermint/blob/develop/types/validator_set.go>`__)
|
|
|
|
A proposal at ``(H,R)`` is composed of a block and an optional latest
|
|
``PoLC-Round < R`` which is included iff the proposer knows of one. This
|
|
hints the network to allow nodes to unlock (when safe) to ensure the
|
|
liveness property.
|
|
|
|
State Machine Spec
|
|
------------------
|
|
|
|
Propose Step (height:H,round:R)
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Upon entering ``Propose``: - The designated proposer proposes a block at
|
|
``(H,R)``.
|
|
|
|
The ``Propose`` step ends: - After ``timeoutProposeR`` after entering
|
|
``Propose``. --> goto ``Prevote(H,R)`` - After receiving proposal block
|
|
and all prevotes at ``PoLC-Round``. --> goto ``Prevote(H,R)`` - After
|
|
`common exit conditions <#common-exit-conditions>`__
|
|
|
|
Prevote Step (height:H,round:R)
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Upon entering ``Prevote``, each validator broadcasts its prevote vote.
|
|
|
|
- First, if the validator is locked on a block since ``LastLockRound``
|
|
but now has a PoLC for something else at round ``PoLC-Round`` where
|
|
``LastLockRound < PoLC-Round < R``, then it unlocks.
|
|
- If the validator is still locked on a block, it prevotes that.
|
|
- Else, if the proposed block from ``Propose(H,R)`` is good, it
|
|
prevotes that.
|
|
- Else, if the proposal is invalid or wasn't received on time, it
|
|
prevotes ``<nil>``.
|
|
|
|
The ``Prevote`` step ends: - After +2/3 prevotes for a particular block
|
|
or ``<nil>``. --> goto ``Precommit(H,R)`` - After ``timeoutPrevote``
|
|
after receiving any +2/3 prevotes. --> goto ``Precommit(H,R)`` - After
|
|
`common exit conditions <#common-exit-conditions>`__
|
|
|
|
Precommit Step (height:H,round:R)
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Upon entering ``Precommit``, each validator broadcasts its precommit
|
|
vote. - If the validator has a PoLC at ``(H,R)`` for a particular block
|
|
``B``, it (re)locks (or changes lock to) and precommits ``B`` and sets
|
|
``LastLockRound = R``. - Else, if the validator has a PoLC at ``(H,R)``
|
|
for ``<nil>``, it unlocks and precommits ``<nil>``. - Else, it keeps the
|
|
lock unchanged and precommits ``<nil>``.
|
|
|
|
A precommit for ``<nil>`` means "I didn’t see a PoLC for this round, but
|
|
I did get +2/3 prevotes and waited a bit".
|
|
|
|
The Precommit step ends: - After +2/3 precommits for ``<nil>``. --> goto
|
|
``Propose(H,R+1)`` - After ``timeoutPrecommit`` after receiving any +2/3
|
|
precommits. --> goto ``Propose(H,R+1)`` - After `common exit
|
|
conditions <#common-exit-conditions>`__
|
|
|
|
common exit conditions
|
|
^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
- After +2/3 precommits for a particular block. --> goto ``Commit(H)``
|
|
- After any +2/3 prevotes received at ``(H,R+x)``. --> goto
|
|
``Prevote(H,R+x)``
|
|
- After any +2/3 precommits received at ``(H,R+x)``. --> goto
|
|
``Precommit(H,R+x)``
|
|
|
|
Commit Step (height:H)
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
- Set ``CommitTime = now()``
|
|
- Wait until block is received. --> goto ``NewHeight(H+1)``
|
|
|
|
NewHeight Step (height:H)
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
- Move ``Precommits`` to ``LastCommit`` and increment height.
|
|
- Set ``StartTime = CommitTime+timeoutCommit``
|
|
- Wait until ``StartTime`` to receive straggler commits. --> goto
|
|
``Propose(H,0)``
|
|
|
|
Proofs
|
|
------
|
|
|
|
Proof of Safety
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Assume that at most -1/3 of the voting power of validators is byzantine.
|
|
If a validator commits block ``B`` at round ``R``, it's because it saw
|
|
+2/3 of precommits at round ``R``. This implies that 1/3+ of honest
|
|
nodes are still locked at round ``R' > R``. These locked validators will
|
|
remain locked until they see a PoLC at ``R' > R``, but this won't happen
|
|
because 1/3+ are locked and honest, so at most -2/3 are available to
|
|
vote for anything other than ``B``.
|
|
|
|
Proof of Liveness
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
If 1/3+ honest validators are locked on two different blocks from
|
|
different rounds, a proposers' ``PoLC-Round`` will eventually cause
|
|
nodes locked from the earlier round to unlock. Eventually, the
|
|
designated proposer will be one that is aware of a PoLC at the later
|
|
round. Also, ``timeoutProposalR`` increments with round ``R``, while the
|
|
size of a proposal are capped, so eventually the network is able to
|
|
"fully gossip" the whole proposal (e.g. the block & PoLC).
|
|
|
|
Proof of Fork Accountability
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Define the JSet (justification-vote-set) at height ``H`` of a validator
|
|
``V1`` to be all the votes signed by the validator at ``H`` along with
|
|
justification PoLC prevotes for each lock change. For example, if ``V1``
|
|
signed the following precommits: ``Precommit(B1 @ round 0)``,
|
|
``Precommit(<nil> @ round 1)``, ``Precommit(B2 @ round 4)`` (note that
|
|
no precommits were signed for rounds 2 and 3, and that's ok),
|
|
``Precommit(B1 @ round 0)`` must be justified by a PoLC at round 0, and
|
|
``Precommit(B2 @ round 4)`` must be justified by a PoLC at round 4; but
|
|
the precommit for ``<nil>`` at round 1 is not a lock-change by
|
|
definition so the JSet for ``V1`` need not include any prevotes at round
|
|
1, 2, or 3 (unless ``V1`` happened to have prevoted for those rounds).
|
|
|
|
Further, define the JSet at height ``H`` of a set of validators ``VSet``
|
|
to be the union of the JSets for each validator in ``VSet``. For a given
|
|
commit by honest validators at round ``R`` for block ``B`` we can
|
|
construct a JSet to justify the commit for ``B`` at ``R``. We say that a
|
|
JSet *justifies* a commit at ``(H,R)`` if all the committers (validators
|
|
in the commit-set) are each justified in the JSet with no duplicitous
|
|
vote signatures (by the committers).
|
|
|
|
- **Lemma**: When a fork is detected by the existence of two
|
|
conflicting `commits <./validators.html#commiting-a-block>`__,
|
|
the union of the JSets for both commits (if they can be compiled)
|
|
must include double-signing by at least 1/3+ of the validator set.
|
|
**Proof**: The commit cannot be at the same round, because that would
|
|
immediately imply double-signing by 1/3+. Take the union of the JSets
|
|
of both commits. If there is no double-signing by at least 1/3+ of
|
|
the validator set in the union, then no honest validator could have
|
|
precommitted any different block after the first commit. Yet, +2/3
|
|
did. Reductio ad absurdum.
|
|
|
|
As a corollary, when there is a fork, an external process can determine
|
|
the blame by requiring each validator to justify all of its round votes.
|
|
Either we will find 1/3+ who cannot justify at least one of their votes,
|
|
and/or, we will find 1/3+ who had double-signed.
|
|
|
|
Alternative algorithm
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Alternatively, we can take the JSet of a commit to be the "full commit".
|
|
That is, if light clients and validators do not consider a block to be
|
|
committed unless the JSet of the commit is also known, then we get the
|
|
desirable property that if there ever is a fork (e.g. there are two
|
|
conflicting "full commits"), then 1/3+ of the validators are immediately
|
|
punishable for double-signing.
|
|
|
|
There are many ways to ensure that the gossip network efficiently share
|
|
the JSet of a commit. One solution is to add a new message type that
|
|
tells peers that this node has (or does not have) a +2/3 majority for B
|
|
(or ) at (H,R), and a bitarray of which votes contributed towards that
|
|
majority. Peers can react by responding with appropriate votes.
|
|
|
|
We will implement such an algorithm for the next iteration of the
|
|
Tendermint consensus protocol.
|
|
|
|
Other potential improvements include adding more data in votes such as
|
|
the last known PoLC round that caused a lock change, and the last voted
|
|
round/step (or, we may require that validators not skip any votes). This
|
|
may make JSet verification/gossip logic easier to implement.
|
|
|
|
Censorship Attacks
|
|
~~~~~~~~~~~~~~~~~~
|
|
|
|
Due to the definition of a block
|
|
`commit <validators.html#commiting-a-block>`__, any 1/3+
|
|
coalition of validators can halt the blockchain by not broadcasting
|
|
their votes. Such a coalition can also censor particular transactions by
|
|
rejecting blocks that include these transactions, though this would
|
|
result in a significant proportion of block proposals to be rejected,
|
|
which would slow down the rate of block commits of the blockchain,
|
|
reducing its utility and value. The malicious coalition might also
|
|
broadcast votes in a trickle so as to grind blockchain block commits to
|
|
a near halt, or engage in any combination of these attacks.
|
|
|
|
If a global active adversary were also involved, it can partition the
|
|
network in such a way that it may appear that the wrong subset of
|
|
validators were responsible for the slowdown. This is not just a
|
|
limitation of Tendermint, but rather a limitation of all consensus
|
|
protocols whose network is potentially controlled by an active
|
|
adversary.
|
|
|
|
Overcoming Forks and Censorship Attacks
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
For these types of attacks, a subset of the validators through external
|
|
means should coordinate to sign a reorg-proposal that chooses a fork
|
|
(and any evidence thereof) and the initial subset of validators with
|
|
their signatures. Validators who sign such a reorg-proposal forego its
|
|
collateral on all other forks. Clients should verify the signatures on
|
|
the reorg-proposal, verify any evidence, and make a judgement or prompt
|
|
the end-user for a decision. For example, a phone wallet app may prompt
|
|
the user with a security warning, while a refrigerator may accept any
|
|
reorg-proposal signed by +½ of the original validators.
|
|
|
|
No non-synchronous Byzantine fault-tolerant algorithm can come to
|
|
consensus when ⅓+ of validators are dishonest, yet a fork assumes that
|
|
⅓+ of validators have already been dishonest by double-signing or
|
|
lock-changing without justification. So, signing the reorg-proposal is a
|
|
coordination problem that cannot be solved by any non-synchronous
|
|
protocol (i.e. automatically, and without making assumptions about the
|
|
reliability of the underlying network). It must be provided by means
|
|
external to the weakly-synchronous Tendermint consensus algorithm. For
|
|
now, we leave the problem of reorg-proposal coordination to human
|
|
coordination via internet media. Validators must take care to ensure
|
|
that there are no significant network partitions, to avoid situations
|
|
where two conflicting reorg-proposals are signed.
|
|
|
|
Assuming that the external coordination medium and protocol is robust,
|
|
it follows that forks are less of a concern than `censorship
|
|
attacks <#censorship-attacks>`__.
|