BUCHMAN tendermint论文分析(三):consensus

本章阐述Tendermint共识算法和用于原子广播( atomic broadcast)的相关区块链。拜占庭容错共识问题将被详细讨论,并且Tendermint共识的一个正式说明将以π-calculus的形式给出。Tendermint区块链已经被非正式地证明为满足原子广播。将来我们将以进程演进的方式来描述完整的区块链协议,并证明相关特性。
一.Tendermint综述
Tendermint是区块链范式中的一个安全的状态机复制算法。其算法形态为BFT-ABC,并且附加责任制,便于验证拜占庭节点的不诚实行为。

Tendermint算法给每个区块赋予一个增量索引或者高度(height),在某一高度中只存在一个有效的区块,区块链从高度为0的创世纪块开始,由一个验证者集合投票产生下一个区块,其中每一个验证者由各自的公钥标识。每一个验证者需要维护一份完整的复制状态的拷贝。在投票产生某一高度的区块的过程中,在正式提交(commit)某一高度的区块之前,至少需要经过一轮(round)投票(vote)来达成共识。每一轮都会通过round robin的方法产生一个提议者(proposer),该提议者在当轮以广播的形式提出一个提议(proposal),提议经过验证者的集体投票,来决定是否最终提交该区块或者进入下一轮。在提议的区块真正被提交(commit)之前,验证者们需要进行两轮投票(pre-vote & pre-commit), 通过一个简单的锁机制用来阻止少于总数1/3的拜占庭节点攻击。由于Tendermint网络的不同时性(asynchrony),当拜占庭节点超过总数的1/3,网络存在瘫痪的可能性。

注意到,tendermint的多轮投票机制的核心是共识算法。每一个区块包含一些元数据(metadata),称作区块头(header)。区块头里包含本区块的高度,提议时间,本区块所有交易的梅克尔根哈希值。
二.区块链结构
Tendermint使用如下的区块链结构保存区块链信息。区块包括三部分:Header(区块头),Data(区块中的交易信息)以及LastCommit(上一个区块的Commit信息)。
BUCHMAN tendermint论文分析(三):consensus_第1张图片
区块头中包括:
a)区块高度
b)上个区块ID(LastBlockID),也就是上一个区块Header的hash
c)Data Hash,所有区块交易信息hash
d)App State Hash,应用程序当前状态的hash,这个hash是通过ABCI接口的Commit的返回值。
e)Validator Hash,生成区块时所有validator信息的hash
f)LastCommit hash,上一个区块的Commit的hash。
三.共识
共识算法可以大致分为以下几部分:

• 提议(Proposals):在每一轮(round)中,新区块的提议者必须是有效的,并且告诉(gossiped)其他验证者。如果在一定时间内没有收到当轮提议(proposal),当前提议者将被后面的提议者接替。

• 投票(Votes):两阶段的投票基于优化的拜占庭容错。它们分别被称作预投票(pre-vote)和预提交(pre-commit)。对于同一个区块同一轮如果存在超过2/3的预提交(pre-commit)则对应产生一个提交(commit)。
• 锁(Locks):在拜占庭节点数少于节点总数的1/3的情况下,Tendermint中的锁机制可以确保没有两个验证者在同一高度提交(commit)了两个不同的区块。锁机制确保了在当前高度验证者的下一轮预投票或者预提交依赖于这一轮的预投票或者预提交。

BUCHMAN tendermint论文分析(三):consensus_第2张图片为了应对单个拜占庭故障节点,Tendermint网络至少需要包括4个验证者。每个验证者拥有一对非对称密钥,其中私钥用来进行数字签名,公钥用来标识自己的身份ID。验证者们从公共的初始状态开始,初始状态包含了一份验证者列表。所有的提议和投票都需要各自的私钥签名,便于其他验证者进行公钥验证。

共识开始于第0轮,第一个提议者(proposer)是区块链头里验证者列表里的第一个验证者。每一轮最终要么完成了一个提交(commit),要么直接进入当前高度的下一轮,每一轮都会产生一个新的提议者。

与其他选举(leader election )算法不同,Tendermint每一轮都会产生一个新的提议者(proposer),验证者投票决定是否进入下一轮,这与接受提议的流程类似。

每轮的开始对同步有弱的依赖性。每一轮开始期间,存在一个用来计时的本地同步时钟,如果验证者在TimeoutPropose时间内没有收到提议,验证者将参与投票来决定是否跳过当前提交者。TimeoutPropose会随着轮数的增加而增加。

每轮收到提议以后,进入完全异步模式。之后验证者的每一个网络决定需要得到2/3验证者以上的同意。这样降低了对同步时钟的依赖或者网络的延迟。但是这也意味着如果得不到1/3以上验证者的响应,整个网络将瘫痪。

简言之,每轮,开始提议弱同步,之后投票完全异步。

为了增强Tendermint共识网络的安全性,引入了少量的锁定规则(locking rules)来迫使验证者自证其投票的合法性。尽管我们不需要实时广播他们的合法证明,但是我们确实期望验证者们保存相关数据。这样当网络被拜占庭故障节点瘫痪时,其可以存留为相关证据。这个问责机制确保在网络故障(例如PBFT)的时候Tendermint具有一个更健壮的担保(guarantees)。

验证者使用一组不同的消息(messages)来管理区块链,应用程序状态,p2p网络和共识。其中,核心的共识算法包含两类消息:

ProposalMsg: 对应某一高度及某一轮数的区块的提议(proposal),该提议已经由提议者签名

VoteMsg: 对某一提议的签名投票
四.锁机制(待完善)
多轮投票的安全问题是棘手的,必须避免同一高度不同轮数分别提交两个不同区块的情形。在Tendermint中,这个问题可以通过锁机制(locking mechanism)得到解决。锁机制的大致定位在波尔卡附近。本质上,预提交必须有一个波尔卡为其背书,验证者被锁定在其最近预提交(pre-commit)的区块上。

锁定规则:

· 预投票锁(Prevote-the-Lock):验证者只能预投票(pre-vote)他们被锁定的区块。这样就阻止验证者在上一轮中预提交(pre-commit)一个区块,之后又预投票了下一轮的另一个区块。

· 波尔卡解锁(Unlock-on-Polka ):验证者只有在看到更高一轮(相对于其当前被锁定区块的轮数)的波尔卡之后才能释放该锁。这样就允许验证者解锁,如果他们预提交了某个区块,但是这个区块网络的剩余节点不想提交,这样就保护了整个网络的运转,并且这样做并没有损害网络安全性。

简单来说,验证者可以被看作锁在任意高度-1轮的nil-block上,所以波尔卡解锁意味着验证者不能预提交一个新高度的区块直到他们看见一个波尔卡。

这些规则可以以例子的形式被更直观的理解。考虑4个验证者,A,B,C,D,假设有一个第R轮关于blockX的提议。现在假设blockX已经有一个波尔卡,但是A看不见它,预提交(pre-commit)为空,然而其他人对blockX进行了预提交。进一步假设只有D看见了所有的预提交,然而其他人并没有看见D的预提交(他们只看见他们的预提交和A的空预提交)。D现在将要提交(commit)这个区块,然而其他人进入到R+1轮。由于任何验证者都可能是新的提议者,如果他们提议并投票了一个新的区块blockY,他们可能提交这个区块。可是D已经提交了bockX,因此损害了系统的安全性。注意,这里并没有任何拜占庭行为,仅仅是不同时性。
五.tendermint子协议
上一章中对Tendermint共识的介绍省略了有关用于广播区块(blocks),投票(votes),交易(transactions)和其他节点信息的gossip协议的一些细节。之所以这样做是为了聚焦在共识协议本身,而不让工程实践的细节分散注意力。本章讲述一种特定的用来填充这些细节的方法,通过把这些组件实现为相对独立的反应器(reactors),在这些反应器中每个对等连接(peer connection)是复用的。
1.P2P网络
在启动时,每个Tendermint节点都会收到一份需要拨号的对等节点的初始列表(initial list)。对于每个对等节点,节点会保持一个持久的TCP连接(persistent TCP connection),在该连接上以速率受限的方式复用了多个子协议(subprotocols)。消息被序列化为紧凑的二进制表示,并且通过认证的加密协议对连接进行加密。

本章的其余部分的每一小节分别描述了一个独立的反应器,该反应器在每一个对等链接进行了复用。有一个额外的对等节点交换反应器(peer exchange reactor),该反应器允许节点彼此请求其他对等节点地址,并跟踪它们先前连接到的对等节点,以便保持连接其他对等点的最小数量。
2.共识Gossip
共识反应器(consensus reactor)封装了共识状态机(consensus state machine),用来确保当每个节点的状态发生变化时都向其所有对等节点广播其当前状态。以这种方式,每个节点跟踪所有对等节点的共识状态,只发送对等节点此刻需要的信息,或者对等节点没有的信息,以此来优化消息的gossip过程。对于每个对等节点,本地节点维护来两个例程(routine)来不断检查发送给对其等节点的新信息,即提议(proposals)和投票(votes)。信息应该以“第一稀有”(ratest first)的原则来最大限度地提高gossip的效率,并尽量减少某些信息变得不可用的概率。
3.区块数据
在上一章节中,假设提议信息是包含区块的。然而由于区块是从一个验证节点发出的,并且可以相当大,对于块提议节点来说把数据上传到所有其他节点的方式压力过大;如果把区块分成若干块后再广播出去会快很多。
一个常用来保证数据被安全广播的方法,就是利用Merkle树,此方法也被各种P2P协议所采用,具体方法就是允许每一段数据伴随一个简短的证明(原数据的对数级大小),用来证明该片段是整体的一部分。为了使用这种方法,根据区块大小和验证者的数量,区块被序列化并分割成适当大小的块(chunks),块(chunks)被散列成Merkle树。被签名的提议(proposals)不再包含整个区块,而是只包含Merkle的根哈希(root hash),允许网络通过协作来广播被分割后的块(chunks)。节点每次收到块(chunks)时都会通知它的对等节点,来避免把同一个块对一个节点传输多次,以此来节省带宽。
一旦接收到所有块,就对块进行反序列化和验证,以确保它正确地指向前一个块,并且其各种校验和(用Merkle树实现)是正确的。虽然先前假定验证者在接收到提案(包括块)之前不预先投票,但是通过允许验证者在收到提案之后,在接收到完整的块数据之前进行投票,可以提升性能。这意味着预先投票到最后证明是无效的块也是可以的。不管怎么样,对一个无效区块进行预提交(pre-commit)被认为是拜占庭行为。
节点通过接受块(chunks)的方式同步到最新的高度,以一次一个区块的方式进行同步(progress one block at a time)。
4.投票
在共识状态机中,一个块被提议以后,节点就会开始等待投票(或者本地计时器过期)以往前推进。如果一个节点刚好进入了下一个区块高度的状态下收到一个对上一个块的pre-commits投票,而这个节点如果同时是这轮区块的提议者,它也许会把pre-commits投票当作下一个块的中的LastCommit值存入区块。如果一个对等节点(peer)已经预投票,但尚未预先提交,或者已经预先提交,但尚未进入下一轮,则其被分别发送预投票(pre-votes)或预提交(pre-commits)。如果一个节点是在同步区块的情况下,它将会收到在它当前的高度下预提交(pre-commits)。
5.内存池
交易独立地在内存缓存中被管理,这个缓存沿用比特币的叫法就是内存池。交易被业务逻辑验证合法后加入内存池,并用有序组播算法发送给相邻节点。一个节点会为每个相邻节点维持一个例程(routine),用来确保发送交易的顺序是和自己处理的顺序是一致的。
块提议者会把内存池的有序交易列表中的交易打包进新的区块。一旦一个区块被确认提交,区块中的所有交易都会被从内存池中删除,而留下来的交易会被业务逻辑重新验证,因为账户中的其他交易被确认提交,导致它们的有效性也许会改变。
6.区块同步
共识反应器(consensus reactor)提供一种相对较慢的区块同步方法,这是为了实时共识而设计的,也就是说节点在处理下一个块之前,会等待接受到所有的信息来确认当下的一个块。为了适应一个节点远落后与当前高度的情况,有一个区块链反应器(blockchain reactor),这个反应器允许节点同时并行地下载多个区块,使节点可以百倍于共识反应器的速度来同步区块数据。
当一个节点链接一个新的对等节点时,这个对等节点需要把它当前区块高度发送给对方。节点并行的从所有比自己高度高的对等节点中循序的请求区块,并下载区块加入到自己的区块池(block pool)。另一个例程不断尝试从池中删除块并通过验证和执行将它们添加到区块链中,一次两个块,相对于区块链的最新状态。必须每次同时处理两个区块,因为一个区块的提交确认被包含在下一个区块的LastCommit字段中。
节点持续的查询它所有对等节点的当前高度,同时不断的并行请求区块,直到同步到其所有对等节点的最高高度,一旦同步到最高高度就会停止请求区块,并开启共识反应器。
六.Tendermint 套接字协议
Tendermint套接字协议(TMSP)定义了共识引擎和应用程序状态机之间通讯的核心接口。这个接口定义包括一系列的消息类型,使用Google’s Protocol Buffers (一个长度作为前缀通过套接字传输的协议),一系列的消息类型,他们的参数,返回值和用途都在图5.1中,整体的结构和消息流程也在图5.2中。
BUCHMAN tendermint论文分析(三):consensus_第3张图片
Golang定义的TMSP Application interface,TMSP消息被定义为使用Google’s Protocal Buffers,序列化格式是通过TMSP套接字传输之前将长度作为前缀,返回值包括一个代码,和HTTP的状态码类似,代表了任意的错误信息,0表示没有错误,消息在客户端缓存直到一个flush消息被发出,在这个点所有的缓存消息都会被发出。服务端设计的是异步化的,消息返回必须是和请求相匹配的正确的顺序。
BUCHMAN tendermint论文分析(三):consensus_第4张图片
共识逻辑与应用程序逻辑进行通信是通过TMSP(套接字协议)。维护两个sockets(套接字),一个用于mempool检查新交易的有效性,另一个用于执行共识的新提交的块。

TMSP协议被实现为一个有序的异步的服务,消息类型请求和返回是成对的,当一个特殊的消息类型出现的时候,清空并通过连接推送缓存的所有消息然后等待消息。

在TMSP协议有两种消息是核心,AppendTx 和Commit,一旦块由共识产生,引擎就会在块上调用AppendTx处理每个块中的交易,将其传递给应用程序状态机处理。如果交易有效,则会导致应用程序中的状态转换。

一旦所有AppendTx调用返回,共识引擎调用Commit,使得应用程序提交到最新状态,并将其永久存储到磁盘。

分离协议和执行

使用TMSP可以使我们明确区分共识,或者关于交易顺序的协议,以及它们在状态机中的实际执行情况。特别是,我们首先就顺序达成共识,并且然后按顺序执行交易。这种分离实际上有所提升系统的容错,但是仍然需要总数3f + 1个节点去容忍f个拜占庭式节点的失败,只需要2f + 1个节点执行。也就是说,虽然我们仍需要三分之二的多数来排序,我们只需要一半的多数来执行。

另一方面,事实上交易的执行放在排序之后会导致可能是无效的交易,这可能会浪费系统资源。这种情况使用额外的TMSP消息CheckTx解决的,由mempool调用,允许它检查交易是否会对最新的状态有效。但请注意,事实上块中的一次性提交会引起CheckTx处理消息的复杂性。特别是,应用程序希望维护第二个状态机只执行主状态机相关的交易有效性的规则。第二个状态机由CheckTx消息更新状态并在每次提交后重置为最新的已提交状态。在本质上,第二个状态机描述了交易池的过滤规则。

在某种程度上,CheckTx可以用作`乐观的执行`返回并将结果发送给交易发送者,但结果可能是错误的,比如一个带有冲突交易的块在利息交易之前提交。这种`乐观的执行`是扩展BFT系统的方法的关注焦点,可以很好地适用于交易之间冲突很少的应用程序。与此同时,它增加了客户端的额外复杂性,因为可能需要处理无效的结果。

你可能感兴趣的:(BUCHMAN tendermint论文分析(三):consensus)