术语

  • 网络由可选连接的_节点_组成。与某个特定节点直接连接的节点称为其_对等节点_。
  • 决定下一个区块的共识过程(在某个_高度_ H)由一个或多个_轮次_组成。
  • NewHeight、Propose、Prevote、Precommit 和 Commit 表示某一轮状态机的状态。(也称为 RoundStep,或简称“step”)
  • 可以说某个节点_处于_给定的高度、轮次和步骤,即处于 (H,R,S),或者省略步骤简写为 (H,R)。
  • 对某个内容执行 prevote 或 precommit,表示为该内容广播一条 prevote 或 precommit vote。
  • 处于 (H,R) 的 vote,是指其签名字节中包含 H 和 R 的 sign-bytes 的投票。
  • +2/3 是 “大于 2/3” 的简写。
  • 1/3+ 是 “1/3 或以上” 的简写。
  • 在 (H,R) 上,针对某个特定区块或 <nil> 的 +2/3 prevote 集合,称为 proof-of-lock-change,简称 PoLC。

状态机概览

在区块链的每个高度上,都会运行一个按轮次推进的协议来确定下一个区块。每一轮由三个_步骤_组成(Propose、Prevote 和 Precommit),以及两个特殊步骤 Commit 和 NewHeight。 在最理想的情况下,步骤顺序如下:
NewHeight -> (Propose -> Prevote -> Precommit)+ -> Commit -> NewHeight ->...
序列 (Propose -> Prevote -> Precommit) 被称为一_轮_。在某个给定高度上,提交一个区块可能需要不止一轮。可能需要更多轮次的原因包括:
  • 指定的 proposer 不在线。
  • 指定 proposer 提出的区块无效。
  • 指定 proposer 提出的区块未能及时传播。
  • 提出的区块是有效的,但在足够多的验证者节点到达 Precommit 步骤时,未能及时收到针对该提议区块的 +2/3 prevote。尽管推进到下一步需要 +2/3 prevote,但至少可能有一个验证者投了 <nil>,或恶意投给了其他内容。
  • 提出的区块是有效的,并且足够多的节点收到了 +2/3 prevote,但足够多的验证者节点没有收到针对该提议区块的 +2/3 precommit。
这些问题中的一部分会通过进入下一轮及下一个 proposer 来解决;另一部分则会通过在每一轮递增某些轮次超时参数来解决。

状态机示意图

                         +-------------------------------------+
                         v                                     |(等待直到 `CommmitTime+timeoutCommit`)
                   +-----------+                         +-----+-----+
      +----------> |  Propose  +--------------+          | NewHeight |
      |            +-----------+              |          +-----------+
      |                                       |                ^
      |(否则,在 timeoutPrecommit 之后)     v                |
+-----+-----+                           +-----------+          |
| Precommit |  <------------------------+  Prevote  |          |
+-----+-----+                           +-----------+          |
      |(当发现针对区块的 +2/3 Precommits 时)                 |
      v                                                        |
+--------------------------------------------------------------------+
|  Commit                                                            |
|                                                                    |
|  * 设置 CommitTime = now;                                          |
|  * 等待接收区块,然后暂存/保存/提交区块;                           |
+--------------------------------------------------------------------+

后台 Gossip

一个节点可能并没有对应的验证者私钥,但它依然通过向其对等节点中继相关元数据、提案、区块和投票,在共识过程中发挥积极作用。拥有活跃验证者私钥并参与签名投票的节点称为_验证者节点_。所有节点(不只是验证者节点)都具有关联状态(当前高度、轮次和步骤),并持续推动进展。 任意两个节点之间都存在一个 Connection,在该连接之上复用的是若干受到合理节流控制的信息 Channel。其中一些通道实现了流行病式的 gossip 协议,用于让对等节点及时同步到最新的共识状态。例如:
  • 节点会 gossip 当前轮 proposer 所提议区块的 PartSet 分片。这里使用了一种受 LibSwift 启发的算法,以便在 gossip 网络中快速广播区块。
  • 节点会 gossip prevote/precommit 投票。领先于 NODE_B 的节点 NODE_A 可以向 NODE_B 发送 NODE_B 当前轮(或未来轮)的 prevote 或 precommit,帮助它继续向前推进。
  • 如果提议中包含 PoLC(proof-of-lock-change)轮次,节点会 gossip 该提议 PoLC 轮次的 prevote。
  • 节点会向区块链高度落后的节点 gossip 较旧区块的 commits。
  • 节点会机会性地 gossip ReceivedVote 消息,以提示对等节点自己已经持有哪些投票。
  • 节点会向所有相邻对等节点广播自身当前状态。(但不会继续向外 gossip)
还有更多内容,但这里先不展开。

提案

每一轮中,提案由指定的 proposer 签名并发布。proposer 通过一种确定性的、不会饥饿的轮询选择算法来选出,并按其投票权重进行比例分配(见实现)。 位于 (H,R) 的提案由一个区块以及一个可选的最新 PoLC-Round < R 组成;只有当 proposer 知道该值时才会包含它。这样做是为了提示网络在安全的情况下允许节点解锁,从而保证活性属性。

状态机规范

Propose 步骤(height:H,round:R)

进入 Propose 时:
  • 指定的 proposer 在 (H,R) 提议一个区块。
Propose 步骤结束于:
  • 进入 Propose 后经过 timeoutProposeR。—> 转到 Prevote(H,R)
  • 收到提案区块以及 PoLC-Round 上的所有 prevote 后。—> 转到 Prevote(H,R)
  • 满足通用退出条件后

Prevote 步骤(height:H,round:R)

进入 Prevote 时,每个验证者都会广播自己的 prevote 投票。
  • 首先,如果验证者自 LastLockRound 起锁定在某个区块上,但现在在轮次 PoLC-Round 上看到了针对其他内容的 PoLC,且满足 LastLockRound < PoLC-Round < R,那么它会解锁。
  • 如果验证者仍然锁定在某个区块上,它就对该区块投 prevote。
  • 否则,如果来自 Propose(H,R) 的提议区块是有效的,它就对其投 prevote。
  • 否则,如果提案无效或未能及时收到,它就投 prevote <nil>。
Prevote 步骤结束于:
  • 收到针对某个特定区块或 <nil> 的 +2/3 prevote 后。—>; 转到 Precommit(H,R)
  • 在收到任意 +2/3 prevote 后,再经过 timeoutPrevote。—> 转到 Precommit(H,R)
  • 满足通用退出条件后

Precommit 步骤(height:H,round:R)

进入 Precommit 时,每个验证者都会广播自己的 precommit 投票。
  • 如果验证者在 (H,R) 上针对某个特定区块 B 拥有 PoLC,它会(重新)加锁(或把锁切换到)B,并对 B 投 precommit,同时设置 LastLockRound = R。
  • 否则,如果验证者在 (H,R) 上针对 <nil> 拥有 PoLC,它会解锁,并对 <nil> 投 precommit。
  • 否则,它保持锁不变,并对 <nil> 投 precommit。
对 <nil> 的 precommit 表示:“我没有看到这一轮的 PoLC,但我确实收到了 +2/3 prevote,并且又等待了一段时间”。 Precommit 步骤结束于:
  • 收到针对 <nil> 的 +2/3 precommit 后。—> 转到 Propose(H,R+1)
  • 在收到任意 +2/3 precommit 后,再经过 timeoutPrecommit。—> 转到 Propose(H,R+1)
  • 满足通用退出条件后

通用退出条件

  • 收到针对某个特定区块的 +2/3 precommit 后。—> 转到 Commit(H)
  • 在 (H,R+x) 收到任意 +2/3 prevote 后。—> 转到 Prevote(H,R+x)
  • 在 (H,R+x) 收到任意 +2/3 precommit 后。—> 转到 Precommit(H,R+x)

Commit 步骤(height:H)

  • 设置 CommitTime = now()
  • 等待直到收到区块。—> 转到 NewHeight(H+1)

NewHeight 步骤(height:H)

  • 将 Precommits 移到 LastCommit,并增加高度。
  • 设置 StartTime = CommitTime+timeoutCommit
  • 等待到 StartTime 以接收迟到的 commit。—> 转到 Propose(H,0)

证明

安全性证明

假设验证者中至多只有 -1/3 的投票权是拜占庭的。如果某个验证者在轮次 R 提交了区块 B,那是因为它看到了轮次 R 上的 +2/3 precommit。这意味着仍有 1/3+ 的诚实节点在某个 R' > R 的轮次上保持锁定。这些被锁定的验证者会一直保持锁定,直到它们在某个 R' > R 上看到一个 PoLC;但这不会发生,因为有 1/3+ 的节点被锁定且是诚实的,因此最多只有 -2/3 的投票权可用于对 B 之外的任何内容投票。

活性证明

如果有 1/3+ 的诚实验证者分别锁定在来自不同轮次的两个不同区块上,proposer 的 PoLC-Round 最终会使那些在更早轮次上锁定的节点解锁。最终,指定的 proposer 会变成一个知晓较晚轮次上存在 PoLC 的节点。此外,timeoutProposalR 会随着轮次 R 增长,而提案的大小是有上限的,因此网络最终能够对整个提案进行“完整 gossip”(例如区块和 PoLC)。

分叉问责证明

定义验证者 V1 在高度 H 上的 JSet(justification-vote-set)为:该验证者在 H 上签署的所有投票,以及每次锁变更所对应的 justification PoLC prevote。举例来说,如果 V1 签署了如下 precommit:Precommit(B1 @ round 0)、Precommit(<nil> @ round 1)、Precommit(B2 @ round 4)(注意,轮次 2 和 3 没有签署 precommit,这没有问题),那么 Precommit(B1 @ round 0) 必须由第 0 轮的一个 PoLC 来证明,而 Precommit(B2 @ round 4) 必须由第 4 轮的一个 PoLC 来证明;但第 1 轮针对 <nil> 的 precommit 按定义不属于锁变更,因此 V1 的 JSet 不需要包含第 1、2 或 3 轮的任何 prevote(除非 V1 恰好在这些轮次中投过 prevote)。 进一步地,定义一组验证者 VSet 在高度 H 上的 JSet,为 VSet 中每个验证者 JSet 的并集。对于诚实验证者在轮次 R 上针对区块 B 的一个给定 commit,我们可以构造一个 JSet,用来证明在 R 上对 B 的提交是正当的。如果某个 JSet 使得 (H,R) 上的一个 commit 得到_证明_,是指该 commit 中的所有提交者(即 commit-set 中的验证者)都能在该 JSet 中得到各自的证明,并且不存在这些提交者的重复签名投票。
  • 引理:当由于存在两个相互冲突的 commits 而检测到分叉时,这两个 commit 的 JSet 并集(如果能够汇编出来)必然包含至少 1/3+ 验证者集合的双重签名。证明:这两个 commit 不可能发生在同一轮次,因为那会立即意味着存在 1/3+ 的双重签名。取这两个 commit 的 JSet 并集。如果该并集中不存在至少 1/3+ 验证者集合的双重签名,那么在第一次 commit 之后,任何诚实验证者都不可能再对不同的区块投 precommit。然而,实际却有 +2/3 的验证者这么做了。反证法得证。
作为推论,当发生分叉时,一个外部过程可以通过要求每个验证者为其所有轮次投票提供证明来判定责任归属。最终我们要么会发现有 1/3+ 的验证者无法为其至少一个投票提供证明,要么会发现有 1/3+ 的验证者进行了双重签名。

替代算法

另一种做法是,将一次提交的 JSet 视为“完整提交”。 也就是说,如果轻客户端和验证者在不知道该提交的 JSet 时,不认为某个区块已经被 提交,那么我们就能获得一个理想性质:如果真的发生分叉(例如存在两个相互冲突的 “完整提交”),那么将立即有 1/3+ 的验证者因双重签名而受到惩罚。 有很多方法可以确保 gossip 网络高效地共享一次提交的 JSet。一个方案是新增一种消息类型, 用于告知对等节点:该节点在 (H,R) 处对 B 是否拥有 +2/3 多数,以及一个位数组,说明哪些投票构成了该多数。 对等节点随后可以通过返回相应的投票来响应。 我们将在下一版共识协议中实现这样的算法。 其他潜在改进包括:在投票中加入更多数据,例如导致锁变更的最近已知 PoLC 轮次,以及最近一次投票的轮次/步骤(或者,我们可以要求验证者不得跳过任何投票)。 这可能会让 JSet 的验证与 gossip 逻辑更容易实现。

审查攻击

由于区块提交的定义,任何由 1/3+ 验证者组成的联盟都可以通过不广播其投票来使区块链停摆。这种联盟还可以通过拒绝包含特定交易的区块来审查这些交易,不过这会导致相当比例的区块提案被拒绝,从而降低区块链的区块提交速度,削弱其实用性和价值。恶意联盟还可能以零星、缓慢的方式广播投票,使区块链的区块提交速度几乎降至停滞,或者组合使用上述任意攻击方式。 如果再有一个全局主动型对手参与,它可以以某种方式分割网络,使人看起来像是错误的那部分验证者应对减速负责。这不仅是 Tendermint 的局限,更是所有在网络可能被主动型对手控制时的共识协议的共同局限。

克服分叉与审查攻击

对于这类攻击,部分验证者应通过外部手段进行协调,签署一份重组提案,其中指定要选择的分叉(以及相关证据)和最初那部分验证者及其签名。签署此类重组提案的验证者,将放弃其在所有其他分叉上的抵押担保。客户端应验证重组提案上的签名、验证任何证据,并自行作出判断,或提示终端用户作出决定。例如,手机钱包应用可能会向用户显示安全警告,而冰箱则可能接受任何由原始验证者中 +1/2 签署的重组提案。 当 1/3+ 的验证者不诚实时,任何非同步拜占庭容错算法都无法达成共识;而分叉则意味着已有 1/3+ 的验证者通过双重签名或无正当理由地更改锁而作恶。因此,签署重组提案本质上是一个协调问题,任何非同步协议都无法解决它(即无法在不对底层网络可靠性作出假设的情况下自动解决)。它必须通过弱同步 Tendermint 共识算法之外的手段来提供。目前,我们将重组提案协调的问题留给人们通过互联网媒介进行协作。验证者必须谨慎确保不存在显著的网络分区,以避免出现两个相互冲突的重组提案都被签署的情况。 如果假设外部协调媒介和协议是健壮的,那么分叉相比于审查攻击就不那么值得担忧。

规范提交与主观提交

我们区分“规范提交”和“主观提交”。主观提交是每个验证者在决定提交某个区块时,在本地看到的内容。规范提交则是下一个区块的提议者写入该区块 LastCommit 字段中的内容。正是这一点使它成为规范提交,并确保每个验证者都对该规范提交达成一致,即使它与某个验证者所看到、并促使其提交相应区块的 +2/3 投票不同。每个区块都包含前一个区块的规范 +2/3 提交。

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 or precommit vote for something.
  • A vote at (H,R) is a vote signed with the bytes for H and R included in its 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 Channels 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 for older blocks.
  • Nodes opportunistically gossip ReceivedVote 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). 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

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

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

  • 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, 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 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, 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.

Canonical vs subjective commit

We distinguish between “canonical” and “subjective” commits. A subjective commit is what each validator sees locally when they decide to commit a block. The canonical commit is what is included by the proposer of the next block in the LastCommit field of the block. This is what makes it canonical and ensures every validator agrees on the canonical commit, even if it is different from the +2/3 votes a validator has seen, which caused the validator to commit the respective block. Each block contains a canonical +2/3 commit for the previous block.