区块链作为一个去中心化的分布式记账本,最近几年取得了飞速发展。究其原因,去中心化让其更加可靠,去信任化让其更加安全。从根本而言,区块链是个大型的分布式数据库系统,在这个分布式系统中有着众多的参与节点,我们现在探究的就是如何让众多参与节点达成一个一致性结果。
共识算法可以分为两类,一类是Byzantine Fault Tolerance,也就是我们俗称的拜占庭容错算法,比较有代表性的有PBFT,PoW,PoS等。另一类是Fail-Stop Failure,也就是非拜占庭容错的共识算法,比较有代表性的有Paxos,Raft等。
今天我们着重介绍的是基于拜占庭容错的一个十分经典的共识算法----Tendermint[^1]
NewHeigth->Propose->Prevote->Precommit->Commit
算法规定三个共识阶段是一个Round,一个块的提交可能要经过多个Round,从图中我们也可以看出,其分为多个阶段。
参与节点分为Validator节点和Non-Validator节点,Validator节点是负责参与共识的节点。
作为一种拜占庭容错算法,算法才用了2/3这个条件去限制整个过程,这种方式与PBFT类似。
A set of +2/3 of prevotes for a particular block or at (H,R) is called a proof-of-lock-change or PoLC for short.
from:https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm
PoLC(Proof of Lock Change)表示的是在某个高度轮数(height,Round),对某个块的Prevote投票集合超过2/3,也就是Prevote的投票集。正是有了它的存在让Validator节点永远不可能在同一高度提交冲突块。详细的解释将会在第三部分中的Prevote阶段介绍。
首先,创世区块高度(Height=0),通过Round Robin[^2]随机选取一个Validator节点作为Proposal一个Block的节点,然后剩下的Validator节点对其进行投票。
下面分NewHeigth->Propose->Prevote->Precommit->Commit阶段阐述整个共识过程。
这里谈一下我自己的一点儿理解
Prevote和Precommit都是broadcast过程,但是Prevote是节点内部,Precommit是节点间。本质上都是vote过程,在Wiki中,其过程都被称为Prevote Vote和Precommit Vote。(我不知道这里能否说清楚这个过程)
表示要开始新一轮的Proposal,开始新一轮的Proposal的原因有2,1:之前Proposal解锁(与PoLC有关,Prevote阶段会提到)2:通过所有阶段共识阶段(>2/3)
需要注意的是,为了避免选出重复的Proposer(Proposal的提议者),系统内设置了Total Voting Power,当Proposer成功将自己的Proposal Commit完成之后,会用Voting Power-Total Voting Power,让其成为负值。原理就是让上一轮参与Proposal过程的Validator节点不会参与到下一轮的Proposal过程。
- Upon entering Propose:
1.The designated proposer proposes a block at (H,R).- The Propose step ends:
1.After timeoutProposeR after entering Propose. --> goto Prevote(H,R)
2.After receiving proposal block and all prevotes at PoLC-Round. --> goto Prevote(H,R)
3.After common exit conditions
from:https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm
Proposal包含了block和可供选择的最近的PoLC-Round,包含PoLC-Round的原因是保证某些锁定的节点可以让其释放掉,以保证系统的liveness,因为在Proposer成功propose一个block之前,可能存在从上一个PoLC到这一个PoLC的过程,在这个过程中可能伴随着vote失败,那么这个时候就需要将vote到错误block被锁的节点进行释放,以保证系统的活跃度。(提一嘴Istanbul-BFT,它没有这一步过程,所以很容易造成liveness无法保证的情况,下一篇博文会对其分析)
Proposer通过Gossip协议发送Proposal到剩余的每个Validator节点,如果该Proposer被Lock到上一轮的block中,那么该Proposer会直接propose那个block,同时含有证明的Proof of Lock(证明该Proposer被Lock在之前Round中的事实)。
对于如何解锁(这里是不太严谨的说法)将在下面说到。
- Upon entering Prevote, each validator broadcasts its prevote vote.
1.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.
2.If the validator is still locked on a block, it prevotes that.
3.Else, if the proposed block from Propose(H,R) is good, it prevotes that.
4.Else, if the proposal is invalid or wasn’t received on time, it prevotes. - The Prevote step ends:
1.After +2/3 prevotes for a particular block or. --> goto Precommit(H,R)
2.After timeoutPrevote after receiving any +2/3 prevotes. --> goto Precommit(H,R)
3.After common exit conditions
from:https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm
当Validator节点收到Propose的block之后,会判断自身节点是否被锁在之前的block中,如果锁在之前的block中,会继续为之前的block投票,否则就投当前的block。(这个投票过程类似于PBFT中的Prepare的全网广播,目的是让其它节点同意区块的存在)
为了保证活跃度,假设自身是个Lock-Block,这个时候如果收到一个针对新block的PoLC(投票完成的标志),LatestLockRound Precommit是真实Lock的地方 Prevote超时或者收到Prevote(or nil)+2/3的时候,进入Precommit阶段,如果此时节点收到+2/3的Prevote的投票,广播一条Precommit投票,同时将自己锁在这个block上(将之前的block释放掉)。一个节点依次只能锁在一个块上。 当一个节点锁在一个block上的时候(有PoLC),它将LastLockRound设置为当前Round,并且对这个投Precommit票。 如果有针对 nil 票的 PoLC,则解锁并且对 nil 投 Precommit 票;否则的话保持 Lock-Block 不变,并投 nil 。 如果在 timeout 期间内,没有收到对某个块的足够的 +2/3 投票(prevote 或者 nil 都行),那么就什么也不干。 最终,如果一个节点收到了 +2/3 的 precommit 投票,就进入 Commit 阶段。否则,继续进入下一轮的 Propose 阶段。 要想真正commit,必须要满足两个条件: 假定有最多小于总结点 1/3 的拜占庭节点。如果一个节点在第 R 轮提交一个块,则表明此节点在第 R 轮收到大于 2/3 的针对此块的 Precommit 投票。这也就意味有大于1/3 的诚实节点在第 R’ (R’ > R)轮仍然锁定在这个块上(因为大于 2/3 的 Precommit 投票必定包含大于 1/3 诚实节点的 Precommit 投票)。只有当遇到针对另一个块的 PoLC 时才会解锁,但是在 R’ 轮是不可能有针对某个块的 PoLC,因为已经有大于 1/3 的诚实节点已经锁定在这个块上,所以就不可能有对另外一个块大于 2/3的 Prevote 投票。 假设多于 1/3 的节点分别 Lock 在不同的块上,则在 Prevote 阶段的条件保证最终Round 较小的会 unlock,而且 Proposal 的超时时间会随着轮数的提高而提高。 Tendermint共识算法作为一种拜占庭容错的类PBFT的共识算法,结合了锁(Lock)的概念,并且创新性的提出了PoLC的概念,让Liveness得到了充分的保障。下篇博客中将针对另外一种类PBFT的共识算法Istanbul-BFT进行解析,并且对其Liveness进行分析,发现其问题(reference4中)。最后感谢前人blog,为我的文章提供了参考,希望大家有问题能多多交流。 [1]: https://github.com/tendermint/tendermint/wiki/Byzantine-Consensus-Algorithm a3.4 Precommit
1.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.
2.Else, if the validator has a PoLC at (H,R) for
3.Else, it keeps the lock unchanged and precommits
A precommit for
1.After +2/3 precommits for . --> goto Propose(H,R+1)
2.After timeoutPrecommit after receiving any +2/3 precommits. --> goto Propose(H,R+1)
3.After common exit conditions
4.common exit conditions
4.1After +2/3 precommits for a particular block. --> goto Commit(H)
4.2After any +2/3 prevotes received at (H,R+x). --> goto Prevote(H,R+x)
4.3After any +2/3 precommits received at (H,R+x). --> goto Precommit(H,R+x)3.5 Commit
4 Algorithm Analysis
4.1 Safety
4.2 Liveness
5 Summary
6 Reference
[2]:https://baike.baidu.com/item/Round Robin/1980131?fr=aladdin
[3]: https://www.jianshu.com/p/ac82ec874be0
[4]:https://github.com/jpmorganchase/quorum/issues/305