介绍
在 CometBFT 的预期行为 一节中, 我们介绍了最常见的行为,通常称为理想情况。 不过,同一节中给出的语法更加通用,覆盖了应用程序设计者需要考虑的更多场景。 本节将进一步说明这些可能出现的场景。我们重点关注 ABCI++ 引入的方法:PrepareProposal 和 ProcessProposal。更具体地说,我们关注下面这部分语法。
- 网络异步性;以及
- 提议者是拜占庭进程。
- 为区块 调用
PrepareProposal和/或ProcessProposal。 - 为区块 调用
PrepareProposal和/或ProcessProposal。 - 不调用
PrepareProposal和/或ProcessProposal。
PrepareProposal 的调用总是会紧跟一次
ProcessProposal 调用。原因在于该进程也会把提案广播给自己,而该提案会在本地被投递,
从而触发 ProcessProposal 调用。
ProcessProposal 处理的提案,与此前在相同高度和轮次上任意一次 PrepareProposal
返回的内容相同。
如果没有重启,那么此前这样的调用只有一次;但如果提议者发生重启,则每次重启都可能额外产生一次
针对同一高度和轮次的 PrepareProposal 调用。
由于共识算法在某次运行中究竟需要多少轮才能完成决定,这一点事先是未知的,因此应用程序需要能够处理任意数量的轮次,并且每一轮都可能表现为上述三种行为之一。请注意,应用程序并不了解共识内部机制,因此也感知不到这些轮次。
可能的场景
在遵循共识算法时,轮次数量未知,因此可能出现大量可预期的场景。 将它们全部列出并不可行。不过,这里会给出其中若干场景,并提炼主要结论。 具体来说,我们将说明在区块 被决定之前:- 在一个正确节点上,
PrepareProposal可能会被多次调用,并且对应不同区块(场景 1)。 - 在一个正确节点上,
ProcessProposal可能会被多次调用,并且对应不同区块(场景 2)。 - 在一个正确节点上,针对区块 的
PrepareProposal和ProcessProposal可能根本不会被调用(场景 3)。 - 在一个正确节点上,
PrepareProposal和ProcessProposal甚至可能完全不会被调用(场景 4)。
基本信息
每个场景都从某个进程 的视角进行描述。更准确地说,我们展示的是 Tendermint 共识算法 中每一轮的 会发生什么。 尽管在实际中,共识算法是基于验证者投票权来运作的,但为了简化表述,本文使用进程数量 (例如 、、)来说明。图例如下:第 X 轮
- 提案阶段: 描述当 时发生的情况。
- 预投票阶段: 描述当 时发生的情况。
- 预提交阶段: 描述当 时发生的情况。
场景 1
多次调用ProcessProposal,且每次对应的值都不同。
第 0 轮
- 提案阶段: 本轮的提议者是一个拜占庭进程,并且它选择不发送提案消息。因此, 的 超时,它会为 发送 ,并且不会调用
ProcessProposal。所有正确进程都会这样做。 - 预投票阶段: 最终会收到 条针对 的 消息,并启动 。当 超时后,它会为 发送 。
- 预提交阶段: 最终会收到 条针对 的 消息,并启动 。当其超时后,它会进入下一轮。
第 1 轮
- 提案阶段: 本轮的提议者是一个正确进程。它的 为 ,因此可以自由生成并提议一个新区块 。进程 及时收到该提案,对区块 调用
ProcessProposal,并为其广播一条 消息。 - 预投票阶段: 由于网络异步性,为该区块发送 的进程少于 个。因此, 在本轮不会更新 。
- 预提交阶段: 由于发送 的进程少于 个,因此不会有正确进程锁定该区块并发送 消息。结果就是, 不会对 作出决定。
第 2 轮
- 提案阶段: 与 第 1 轮 相同,只是这次由另一个正确进程担任提议者,并提议另一个值 。进程 及时收到该提案,对新区块 调用
ProcessProposal,并为其广播一条 消息。 - 预投票阶段: 与 第 1 轮 相同。
- 预提交阶段: 与 第 1 轮 相同。
ProcessProposal。
场景 2
多次调用PrepareProposal,且每次对应的值都不同。
第 0 轮
- 提案阶段: 进程 是本轮的提议者。它的 为 ,因此可以自由生成并提议新区块 。在提议之前,它会先针对 调用
PrepareProposal。之后,它会广播该提案,将其投递给自己,调用ProcessProposal,并为其广播 。 - 预投票阶段: 由于网络异步性,及时收到该提案并为其发送 的进程少于 个。因此, 在本轮不会更新 。
- 预提交阶段: 由于发送 的进程少于 个,因此不会有正确进程锁定该区块,也不会发送非 的 消息。结果就是, 不会对 作出决定。
PrepareProposal,但对应的是不同的值。当到达第 轮时,某个进程会提议区块 ;如果 收到 条 消息,它就会对该值作出决定。
场景 3
会针对多个值调用PrepareProposal 和 ProcessProposal,但最终决定的值却不是它调用过 PrepareProposal 或 ProcessProposal 的那个值。
在这个场景中,在第 轮之前的所有轮次里,都可能出现 场景 1 或 场景 2 中展示的任意轮次。需要注意的是:
-
没有任何提议者提议过区块 ,或者即使提议过,由于异步性,进程 也没有及时收到,因此它没有调用
ProcessProposal;并且 - 如果 是提议者,那么它提议的是另一个不等于 的值。
第 轮
- 提案阶段: 本轮的提议者是一个正确进程,并且它提议区块 。由于异步性,该提案消息在进程 的 超时并为 发送 之后才到达。因此,进程 不会针对区块 调用
ProcessProposal。不过,同一个提案会在其他进程的 超时之前送达,于是它们会为该提案发送 。 - 预投票阶段: 进程 收到 条针对提案 的 消息,相应地更新它的 和 ,并发送 消息。所有正确进程都会这样做。
- 预提交阶段: 最终,进程 收到 条 消息,并对区块 作出决定。
场景 4
可以将 场景 3 转换为另一种场景: 完全不调用PrepareProposal 和
ProcessProposal。要满足这一点,进程 必须在所有满足 的轮次中都不是提议者,
并且由于网络异步性或提议者是拜占庭进程,它在 超时之前始终收不到提案。
结果就是,在进入第 轮之前,它从未调用过 PrepareProposal 和 ProcessProposal;并且正如
场景 3 的第 轮所示,它会在这一轮完成决定。同样,整个过程中不会调用这两个方法。
Introduction
In the section CometBFT’s expected behaviour, we presented the most common behaviour, usually referred to as the good case. However, the grammar specified in the same section is more general and covers more scenarios that an Application designer needs to account for. In this section, we give more information about these possible scenarios. We focus on methods introduced by ABCI++:PrepareProposal and ProcessProposal. Specifically, we concentrate
on the part of the grammar presented below.
- network asynchrony, and
- a Byzantine process being the proposer.
- Call
PrepareProposaland/orProcessProposalfor block . - Call
PrepareProposaland/orProcessProposalfor block . - Does not call
PrepareProposaland/orProcessProposal.
PrepareProposal call is always followed by the
ProcessProposal call. The reason is that the process also broadcasts the proposal to itself, which is locally delivered and triggers the ProcessProposal call.
The proposal processed by ProcessProposal is the same as what was returned by any of the preceding PrepareProposal invoked for the same height and round.
While in the absence of restarts there is only one such preceding invocations, if the proposer restarts there could have been one extra invocation to PrepareProposal for each restart.
As the number of rounds the consensus algorithm needs to decide in a given run is a priori unknown, the
application needs to account for any number of rounds, where each round can exhibit any of these three
behaviours. Recall that the application is unaware of the internals of consensus and thus of the rounds.
Possible scenarios
The unknown number of rounds we can have when following the consensus algorithm yields a vast number of scenarios we can expect. Listing them all is unfeasible. However, here we give several of them and draw the main conclusions. Specifically, we will show that before block is decided:- On a correct node,
PrepareProposalmay be called multiple times and for different blocks (Scenario 1). - On a correct node,
ProcessProposalmay be called multiple times and for different blocks (Scenario 2). - On a correct node,
PrepareProposalandProcessProposalfor block may not be called (Scenario 3). - On a correct node,
PrepareProposalandProcessProposalmay not be called at all (Scenario 4).
Basic information
Each scenario is presented from the perspective of a process . More precisely, we show what happens in each round’s of the Tendermint consensus algorithm. While in practice the consensus algorithm works with respect to voting power of the validators, in this document we refer to number of processes (e.g., , , ) for simplicity. The legend is below:Round X
- Propose: Describes what happens while .
- Prevote: Describes what happens while .
- Precommit: Describes what happens while .
Scenario 1
callsProcessProposal many times with different values.
Round 0
- Propose: The proposer of this round is a Byzantine process, and it chooses not to send the proposal
message. Therefore, ‘s expires, it sends for , and it does not call
ProcessProposal. All correct processes do the same. - Prevote: eventually receives messages for and starts . When expires it sends for .
- Precommit: eventually receives messages for and starts . When it expires, it moves to the next round.
Round 1
- Propose: A correct process is the proposer in this round. Its is , and it is free
to generate and propose a new block . Process receives this proposal in time, calls
ProcessProposalfor block , and broadcasts a message for it. - Prevote: Due to network asynchrony less than processes send for this block. Therefore, does not update in this round.
- Precommit: Since less than processes send , no correct process will lock on this block and send message. As a consequence, does not decide on .
Round 2
- Propose: Same as in Round 1, just another correct process is the proposer, and it
proposes another value . Process receives the proposal on time, calls
ProcessProposalfor new block , and broadcasts a message for it. - Prevote: Same as in Round 1.
- Precommit: Same as in Round 1.
ProcessProposal
anymore for this height.
Scenario 2
callsPrepareProposal many times with different values.
Round 0
- Propose: Process is the proposer in this round. Its is , and it is free to
generate and propose new block . Before proposing, it calls
PrepareProposalfor . After that, it broadcasts the proposal, delivers it to itself, callsProcessProposaland broadcasts for it. - Prevote: Due to network asynchrony less than processes receive the proposal on time and send for it. Therefore, does not update in this round.
- Precommit: Since less than processes send , no correct process will lock on this block and send non- message. As a consequence, does not decide on .
PrepareProposal again but for a different value. When it reaches round
some process will propose block and if receives messages, it will decide on this
value.
Scenario 3
callsPrepareProposal and ProcessProposal for many values, but decides on a value for which it did
not call PrepareProposal or ProcessProposal.
In this scenario, in all rounds before we can have any round presented in Scenario 1 or
Scenario 2. What is important is that:
-
no proposer proposed block or if it did, process , due to asynchrony, did not receive it in time,
so it did not call
ProcessProposal, and - if was the proposer it proposed some other value .
Round
- Propose: A correct process is the proposer in this round, and it proposes block .
Due to asynchrony, the proposal message arrives to process after its
expires and it sends for . Consequently, process does not call
ProcessProposalfor block . However, the same proposal arrives at other processes before their expires, and they send for this proposal. - Prevote: Process receives messages for proposal , updates correspondingly its and and sends message. All correct processes do the same.
- Precommit: Finally, process receives messages, and decides on block .
Scenario 4
Scenario 3 can be translated into a scenario where does not callPrepareProposal and
ProcessProposal at all. For this, it is necessary that process is not the proposer in any of the
rounds and that due to network asynchrony or Byzantine proposer, it does not receive the
proposal before expires. As a result, it will enter round without calling
PrepareProposal and ProcessProposal before it, and as shown in Round of Scenario 3 it
will decide in this round. Again without calling any of these two calls.