我们知道分布式一致性算法一般可以分为两类:拜占庭容错和非拜占庭容错。非拜占庭容错算法如 Paxos, Raft 等在当前的分布式系统中已经广泛使用,而拜占庭容错算法的实际应用范围相对来说小很多(特别是在区块链问世之前)。Tendermint 属于拜占庭容错算法,它针对传统的PBFT算法做了优化,只需要有两轮投票即可达成共识,目前 Tendermint 算法主要应用在区块链系统中。
Round-based协议
首先我们先说一下Round-based协议。在Tendermint中一共有三种类型的投票:prevote,precommit和commit。当一个block被全网络commit的话,意味着这个block已经被全网超过2/3的Validator签名并广播了。
vote数据结构如下:
在链达到一个新的 Height 时候,系统会运行一个 round-based 协议来决定下一个block。round-based 协议由以下三个步骤构成:proposal,prevote,precommit。以及两个特殊步骤:commit,NewHeight。其中 propose,prevote 和 precommit 会分别占用整个 round 1/3时间。每一 round 的时间会比上一 round 的时间长一点,这是为了让网络在部分同步的情况下最终达成一致性共识。
round-based 协议运行过程如下:
round-based 协议是一个状态机,主要有 NewHeigh -> Propose -> Prevote ->Precommit -> Commit 一共 5 个状态。上述每个状态都被称为一个 Step。首尾的 NewHeigh 和 Commit ,这两个 Steps 被称为特殊的 Step。而中间循环三个 Steps 则被称为一个 Round,是共识阶段,也是算法的核心原理所在。一个块的最终提交(Commit)可能需要多个 Round 过程,这是因为有许多原因可能会导致当前 Round 不成功(比如出块节点 Offline,提出的块是无效块,收到的 Prevote 或者 Precommit 票数不够 +2/3 等等)。出现这些情况的话,解决方案就是移步到下一轮,或者增加 timeout 时间。当区块链达到一个新的高度时进入 NewHeight 阶段。接下来 Propose 阶段会提交一个 proposal ,prevote 阶段会对收到的 proposal 进行 prevote 投票。在 precommit 阶段收集到+⅔ prevote 投票后,对 block 进行 precommit 投票。如果收集到+⅔ precommit 投票后则进入 commit 阶段,如果没有收集到+⅔ precommit 投票,会再次进入 propose 阶段。在共识阶段期间如果收到+⅔ commit 投票那么直接进入 commit 阶段。以上就是算法运行的整体过程,接下来分阶段来阐述各个阶段。
Proposal
在每一轮开始前会通过 round-robin 方式选出一个 proposer,选出的 proposal 会提交这一轮的 proposal。proposer 的选择规则请查看之前的一篇文章《出块节点选择》proposal 的数据结构如下:
Prevote
在 Prevote 开始阶段,每个 Validator 会判断自己是否锁定在上一轮的 proposed 区块上,如果锁定在之前的 proposal 区块中,那么在本轮中继续为之前锁定的 proposal 区块签名并广播 prevote 投票。否则为当前轮中接收到的 proposal 区块签名并广播prevote 投票。如果由于某些原因当前 Validator 并没有收到任何 proposal 区块,那么签名并广播一个空的 prevote 投票。Precommit在 precommit 开始阶段,每个 Validator 会判断,如果收集到了超过 2/3 prevote 投票,那么为这个区块签名并广播 precommit 投票,并且当前 Validator 会锁定在这个区块上,同时释放之前锁定的区块。。一个 Validator 一次只能锁定在一个区块上。如果一个 Validator 收集到超过2/3空区块(nil)的 prevote 投票,那么释放之前锁定的区块。处于锁定状态的 Validator 会为锁定的区块收集 prevote 投票,并把这些投票打成包放入 proof-of-lock 中,proof-of-lock 会在之后的propose阶段用到。如果一个 Validator 没有收集到超过2/3的 prevote 投票,那么它不会锁定在任何区块上。这里,介绍一个重要概念:PoLC,全称为 Proof of Lock Change,表示在某个特定的高度和轮数(height, round),对某个块或 nil (空块)超过总结点 2/3 的 Prevote 投票集合。简单来说 PoLC 就是 Prevote 的投票集。在 precommit 阶段后期,如果 Validator 收集到超过2/3的 precommit 投票,那么 Validator 进入到 commit 阶段。否则进入下一轮的 propose 阶段。
Commit
commit 阶段分为两个并行的步骤:
- Validator 收到了被全网 commit 的区块,Validator 会为这个区块广播一个commit 投票。
- Validator 需要为被全网络precommit的区块,收集到超过 ⅔ commit投票。
一旦两个条件全部满足了,节点会将 commitTime 设置到当前时间上,并且会进入NewHeight 阶段。在整个共识过程的任何阶段,一旦节点收到超过⅔ commit 投票,那么它会立刻进入到commit 阶段。
为什么不会分叉?
如果小于1/3节点是拜占庭节点(如果大于等于1/3,那么共识就没法达成了)。当validator commit 了区块 B,那么表示有大于2/3的节点在R轮投了 precommit,这表示至少有大于1/3节点(大于1/3节点哪儿来的呢?就是大于2/3减去小于⅓。为什么是这么算呢?有人说不是有大于2/3的节点投了 precommit ,那么这些人不都是诚实的节点吗?当然不是了,拜占庭节点的意思它工作随性,有时候正确有时候失败。假设这个时候所有的拜占庭节点正确的工作了,所以都算在在+2/3节点内,所以这么算了)被 lock在了 R‘>R。如果这个时候有针对同一区块高度的投票,那么由于这+1/3节点被 lock在了 R’ 轮,所以不会有+2/3的节点投 prevote,也就不会在同一高度达成一个新的共识区块,所以就不会分叉。所以 Tendermint 不分叉是基于它是 BFT 共识,然后加上 PoLC 共同完成。
今天我们带大家一起深入学习了,Tendermint 中的共识算法部分。重点介绍了 round-based 协议运行过程,以及在运行过程中 Propose 、 Prevote 、Precommit 和 Commit 环节的具体实现步骤。相信通过这两期文章,大家应该对 Tendermint 技术有了更深入的了解。
夏洛的克 发布了13 篇原创文章 · 获赞 6 · 访问量 697 私信 关注