1. Tendermint简介
Tendermint被设计用于构建各种分布式应用,易于理解和使用,并且高效。
Tendermint基于状态机副本复制技术,适用于区块链的账本存储。它是BFT(拜占庭容错)的,能够容许不超过1/3 的拜占庭节点的存在,也就是说,在这个前提下,系统能够保证所有正常节点拥有相同的交易列表,并按相同的顺序执行交易,最终得到相同的状态。
Tendermint 包含两个主要的模块:
(1)共识引擎称作Tendermint Core, 用于保证各节点按相同的顺序记录相同的交易列表。
(2)应用接口称作Application BlockChain Interface,简称 ABCI 。ABCI允许开发者使用任意的编程语言和开发环境编写应用逻辑,例如交易的处理逻辑等。
通过将共识引擎与应用逻辑解耦,Tendermint 可以用于构建各种分布式应用,包括各种区块链等。例如
Cosmos、 Ethermint 和 Hyperledger Burrow 等都基于 Tendermint 共识引擎而构建。
2. Tendermint共识算法
本部分详细介绍Tendermint共识算法。 Tendermint共识算法属于拜占庭容错类的共识算法,在拜占庭节点数不超过 1/3的情况下,能够保证共识的安全性(safety)。严格来说,由于系统中 validator节点的投票权(voting power)可能不一样,因此,容错性更准确地说,是指拜占庭节点的投票权不超过 1/3。
2.1. Tendermint共识算法
2.1.1. 系统模型
系统中,节点分为两种类型:validator和非validator节点。
validator节点参与共识,也就是对区块(包含一批交易)进行共识,包括propose区块,对提议的区块进行投票。而非validator节点不参与共识,但会帮助传播区块和投票消息,以及相互同步状态等。
节点之间不一定两两相连,和一个节点直接相连的那些节点称作peers。
无论是validator节点,还是非validator节点,都包含与共识过程相关的一些状态,如区块链当前高度height,round,以及step等。
节点之间运行gossip协议,相互同步共识状态和区块链状态信息。
2.1.2. 状态机概览
2.1.2.1. 算法主体流程
Tendermint算法主流程如下所示:
上图大致描述了共识的过程:从Propose区块开始,进行Prevote和Precommit两个阶段的投票。如果投票达成共识,则依次进入Commit 和 NewHeight阶段,完成共识,区块链高度增加。整个过程称为一个round。不管投票是否达成共识,系统将推进到下一个round,进行新一轮的共识。这其中的区别是:
(1)如果是完成NewHeight 阶段,则下一个round 用于共识下一个高度的区块
(2)而如果当前round投票没有达成共识,则下一个round仍将共识当前高度的区块。
注意到上图右下角有一幅跳舞的图片,这是一种捷克民间舞蹈,叫波尔卡,英文名为polka。Tendermint算法中,各validator节点的共识过程有点类似于跳波尔卡舞蹈。事实上,在第一阶段 Prevote投票中,如果节点收到了+2/3的Prevote投票,这些投票整体上就称为一个polka,也叫做PoLC。
2.1.2.2. 算法相关概念
从上述过程可以看出,Tendermint共识过程基于round进行。基于区块链的当前高度,每个新区块的共识需要一个或多个round。
关于round
一个round包括3个阶段(step):Propose,Prevote和Precommit。整个Tendermint共识过程就是由一个或多个round,再加上两个特殊的阶段:Commit和NewHeight所组成,如下所示:
NewHeight -> (Propose -> Prevote -> Precommit)+ -> Commit -> NewHeight ->...
其中,阶段序列:
(Propose -> Prevote -> Precommit)
称为一个round。
一个共识过程可能需要多个round的原因可能有:
(1)指定的proposer节点不在线
(2)proposal block无效
(3)节点没有及时收到proposal block
(4)虽然proposal block有效,但是没有足够多的节点在Precommit 阶段及时收到对应的 +2/3 的prevotes
(5)虽然proposal block有效,也有足够多的节点接收到了+2/3 的prevotes,但是没有足够多的节点收到+2/3 的 precommits
类似上述情况的解决方法有:
(1)系统变更到下一个round,重新指定proposer节点
(2)round变更时,增加各阶段的超时时间
2.1.2.3. 算法详细流程
下面详细介绍算法的流程。
1、Propose 阶段(height:H,round:R)
在这一阶段,指定的proposer节点组装并广播proposal(提议内容)。
说明:
(1)proposer节点指定方式为:依据当前的round和各validator的投票权,采取确定性的、非阻塞的roundrobin选择算法来选取。
(2)proposal包含一个提议的区块,以及一个可选的、最新的PoLC-round(小于当前的round值R)。PoLC-round的含义是,针对该round,有一个PoLC,即有+2/3(多于2/3)的节点的prevote投票。
仅在当前proposer节点知晓一个最新的PoLC-round时,才会将其包含在proposal中。 这里PoLC-round的作用是,必要时让节点从更旧的round 解除锁定,保持系统的liveness(活性)。
Propose 阶段结束条件为:
(1)超时时间内收到proposal block及PoLC-Round的所有prevote 投票(如果有的话),则立即转到下一阶段:Prevote(H,R);
(2)否则,等到timeoutProposeR超时, 转到下一阶段: Prevote(H,R)。这里可以看到,节点在Propose 阶段会等待一小段时间:timeoutProposeR,来接收proposal。这是Tendermint所基于的一个弱同步假设。除此之外,算法的其余步骤为异步的。
(3)公共退出条件
上述公共退出条件具体指的是:
收到针对特定block的 +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)
上述公共退出条件在接下来将介绍到的 Prevote 和Precommit 阶段也都会起作用。
2、Prevote 阶段 (height:H,round:R)
每个validator节点进入到Prevote 阶段后,会投prevote 投票并向其他节点广播其投票。
首先,如果节点自LastLockRound起lock在某一block,此时却有PoLC-Round的、另一个block或nil的PoLC,则节点unlock。这里 LastLockRound < PoLC-Round < R;
否则,如果节点还lock在某一block,则针对该block 进行prevote(投票并广播,之后所有类似表述都可以类比理解);
否则,如果proposed block有效,则对其prevote;
否则,如果proposed block无效,或者 没有及时收到,则prevote nil。
Prevote 阶段的结束条件为:
(1)收到任何+2/3 的prevote 投票后等待一段时间:timeoutPrevote。
如果该时间内收到针对一个特定block或nil的+2/3 的 prevote 投票,则立即转到下一阶段:Precommit(H,R);
如果该时间内没有收到上述PoLC,则转到下一阶段:Precommit(H,R)。
(2)公共退出条件
3、Precommit 阶段 (height:H,round:R)
每个validator节点进入到Precommit 阶段后,会投 precommit 投票并广播其投票。
首先,在(H,R),如果节点有针对一个特定block的PoLC,则节点lock在该block,并且置LastLockRound =R,同时precommit 该block;
否则,在(H,R),如果节点有针对 nil 的一个PoLC,则节点unlock,同时precommit nil;
否则,节点将保持其目前的lock状态不变(即如果已经lock了,则继续lock,否则,继续保持unlock的状
态) ,并且 precommit nil。
Precommit 阶段的结束条件为:
收到任何+2/3 的 precommit 投票后等待一段时间:timeoutPrecommit,如果该时间内收到针对nil的+2/3的precommit 投票,则相当于没有共识出有效区块,立即转到Propose(H,R+1);
否则,如果超时后也没有收到针对特定 block 的+2/3 的 precommit 投票,则相当于也没有共识出
有效区块,转到Propose(H,R+1)。
公共退出条件
4、Commit 阶段 (height:H)
这一个阶段中,节点尝试对区块进行commit:
首先设置CommitTime = now()
然后,节点等待接收完整的、将要 commit 的区块,然后转到 NewHeight(H+1) 阶段。
5、NewHeight 阶段 (height:H)
在这一阶段,节点最终完成整个共识过程的最后环节,使链的高度增加一个区块,具体步骤为:
置LastCommit=Precommits(即+2/3的Precommit 投票的集合);
增加区块高度,将共识出的新区块添加到链上;
置StartTime = CommitTime+timeoutCommit;
等待直到StartTime,以便接收延迟的commits.
最后,转到 Propose(H,0),开始新的高度上的区块的共识。注意,此时round的编号又从0开始。
以上即为Tendermint共识算法的主要流程,其状态机概览如下:
3. Background Gossip
前面已经提到,系统中节点分为参与共识的validator节点和普通的非validator节点。后者虽然不参与共识,但是通过gossip协议,这些非validator节点会帮助转发相关的元数据、提议的区块信息、其他区块的信息,以及投票信息等。
所有的节点都保存相应的共识状态信息,如当前的 height, round 和 step 等,系统据此进行工作。
节点之间经由Connection进行连接,同时,Connection由多个channel构成。节点之间就是基于其中的一些channel来运行gossip协议,使得各节点同步到当前最新的共识状态。
以下为Tendermint系统中gossip协议的一些特性:
节点相互gossip proposed block的PartSet包含的不同数据包,并且基于LibSwift 优化数据传输,提升速
度;
节点相互gossip prevote/precommit 投票;
节点相互gossip其知晓的PoLC的prevote投票;
节点向高度落后的节点gossip落后的区块的commits;
节点时不时地gossip HasVote消息到其连接的 peer 节点以告知其已收到的投票信息;
节点向邻近节点广播其当前的state
本文参考资料
[1] https://tendermint.readthedocs.io/en/master/
重点章节:
Tendermint 101/Introduction
Tendermint 201/Specification/Byzantine Consensus Algorithm,Genesis, Validators