2018-07-11 15:45:10 -04:00
|
|
|
|
# Byzantine Consensus Algorithm
|
2018-06-15 22:56:26 -07:00
|
|
|
|
|
2018-07-11 15:45:10 -04:00
|
|
|
|
## Terms
|
2018-06-15 22:56:26 -07:00
|
|
|
|
|
2018-08-27 15:33:46 +08:00
|
|
|
|
- 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.
|
2018-07-11 15:45:10 -04:00
|
|
|
|
|
|
|
|
|
## State Machine Overview
|
|
|
|
|
|
|
|
|
|
At each height of the blockchain a round-based protocol is run to
|
2018-08-27 15:33:46 +08:00
|
|
|
|
determine the next block. Each round is composed of three _steps_
|
2018-07-11 15:45:10 -04:00
|
|
|
|
(`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 ->...
|
|
|
|
|
```
|
|
|
|
|
|
2018-08-27 15:33:46 +08:00
|
|
|
|
The sequence `(Propose -> Prevote -> Precommit)` is called a _round_.
|
2018-07-11 15:45:10 -04:00
|
|
|
|
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:
|
|
|
|
|
|
2018-08-27 15:33:46 +08:00
|
|
|
|
- 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.
|
2018-07-11 15:45:10 -04:00
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
+--------------------------------------------------------------------+
|
|
|
|
|
```
|
|
|
|
|
|
2018-08-27 15:33:46 +08:00
|
|
|
|
# Background Gossip
|
2018-07-11 15:45:10 -04:00
|
|
|
|
|
|
|
|
|
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
|
2018-08-27 15:33:46 +08:00
|
|
|
|
signing votes is called a _validator-node_. All nodes (not just
|
2018-07-11 15:45:10 -04:00
|
|
|
|
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,
|
|
|
|
|
|
2018-08-27 15:33:46 +08:00
|
|
|
|
- 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)
|
2018-07-11 15:45:10 -04:00
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
2018-08-27 15:33:46 +08:00
|
|
|
|
- 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>`.
|
2018-07-11 15:45:10 -04:00
|
|
|
|
|
|
|
|
|
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.
|
2018-08-27 15:33:46 +08:00
|
|
|
|
|
2018-07-11 15:45:10 -04:00
|
|
|
|
- If the validator has a PoLC at `(H,R)` for a particular block `B`, it
|
2018-08-27 15:33:46 +08:00
|
|
|
|
(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>`.
|
2018-07-11 15:45:10 -04:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2018-08-27 15:33:46 +08:00
|
|
|
|
- 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)`
|
2018-07-11 15:45:10 -04:00
|
|
|
|
|
|
|
|
|
### Commit Step (height:H)
|
|
|
|
|
|
2018-08-27 15:33:46 +08:00
|
|
|
|
- Set `CommitTime = now()`
|
|
|
|
|
- Wait until block is received. --> goto `NewHeight(H+1)`
|
2018-07-11 15:45:10 -04:00
|
|
|
|
|
|
|
|
|
### NewHeight Step (height:H)
|
|
|
|
|
|
2018-08-27 15:33:46 +08:00
|
|
|
|
- Move `Precommits` to `LastCommit` and increment height.
|
|
|
|
|
- Set `StartTime = CommitTime+timeoutCommit`
|
|
|
|
|
- Wait until `StartTime` to receive straggler commits. --> goto
|
|
|
|
|
`Propose(H,0)`
|
2018-07-11 15:45:10 -04:00
|
|
|
|
|
|
|
|
|
## 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
|
2018-08-27 15:33:46 +08:00
|
|
|
|
_justifies_ a commit at `(H,R)` if all the committers (validators in the
|
2018-07-11 15:45:10 -04:00
|
|
|
|
commit-set) are each justified in the JSet with no duplicitous vote
|
|
|
|
|
signatures (by the committers).
|
|
|
|
|
|
2018-08-27 15:33:46 +08:00
|
|
|
|
- **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.
|
2018-07-11 15:45:10 -04:00
|
|
|
|
|
|
|
|
|
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](../../tendermint-core/validator.md#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 +1/2 of the original validators.
|
|
|
|
|
|
|
|
|
|
No non-synchronous Byzantine fault-tolerant algorithm can come to
|
|
|
|
|
consensus when 1/3+ of validators are dishonest, yet a fork assumes that
|
|
|
|
|
1/3+ 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).
|