本文为万向区块链技术中心研究组撰写,介绍了PBFT算法的正常流程。算法
本部分介绍PBFT算法运行的系统模型。安全
PBFT工做在异步的分布式系统中,系统中各个节点彼此经过网络链接。 系统运行时,消息的传递容许出现下列情形:
不能正确发送
延迟
重复
乱序网络
系统容许错误节点也就是拜占庭节点表现出任意行为,可是须要附加一个限定条件: 节点失效彼此应相互独立,从而大部分或所有节点不会同时失效。
在有恶意攻击存在的状况下,能够采起相似于下列措施来保证这个限制的成立:
各节点运行的服务程序和操做系统的版本尽量多样化
各节点的管理员账号和密码不一样异步
防止身份欺骗、重播攻击
监测错误消息分布式
公钥签名:用于验证消息发送者身份,PBFT中,实际上只用于view-change和new-view消息,以及出现错误的状况。其余消息都采用下面将会提到的MAC(消息认证码)进行认证。这是算法设计中提出的一种优化措施,用于提高算法性能。
MAC:即消息认证码,用于算法正常操做流程中的消息认证
消息摘要:用于检测错误消息性能
算法限定敌手(adversary)能够:
串通拜占庭节点
延迟通讯或正常节点
同时,敌手不能够:
无限延迟正常节点的通讯
伪造正常节点签名
从消息摘要反推原始消息
让不一样消息产生相同摘要区块链
本部分介绍运行PBFT算法的系统的服务属性。优化
PBFT算法可用于实现肯定性的副本复制服务(Replicated service)。 副本复制服务拥有状态(state)和操做(operation)。
客户端(client)向服务发起请求,以执行操做,并等待响应。
服务由 n个节点组成。操做能够执行任何计算,只要这些计算始终产生肯定性的结果。
节点和客户端若是遵循算法的预约步骤执行操做,则被称为正常节点或客户端。加密
只要系统中失效节点的个数不超过容错数 ,系统就能提供safety和liveness。spa
Safety的提供,是系统能保证客户端请求的线性一致性(linearizability),即请求按顺序一次一条地被执行。
PBFT相对于以前的算法如Rampart等的一个显著的不一样在于,其Safety不依赖于同步假设。
算法不需限定客户端必定是正常的,容许其发送不合法的请求,缘由是各正常节点能够一致性地监测客户端请求的各类操做。而且算法能够经过权限控制的方式对客户端进行限制。
因为算法不依赖于同步提供 Safety,所以必须经过同步假设来提供 Liveness。
这里的同步假设是,客户端的请求最终总能在有限的时间内被系统接收到。
客户端可能会经过屡次重传的方式,发送请求到服务,确保其被服务接收到。
PBFT所依赖的同步假设实际上是比较弱的假设,缘由是在真实的系统中,网络错误最终总能够修复。
PBFT的算法弹性(resiliency)是最优的:假定系统中失效节点最大个数为f,则系统最少只须要 3f+1 个节点就能够保证Safety和Liveness。
简单证实:
考虑到最不理想的状况,系统有最大数量的失效节点,即f个。(总节点数为n) 客户端此时能够接收到的回复个数最坏状况是 n-f,由于失效节点可能都不会回复。 可是,因为网络等缘由,客户端接收到的 n-f 个请求中,实际上有可能包含有失效节点的回复(有多是错误的),而另一些正常节点的回复还未及时收到。 这其中,最坏的状况是,n-f个结果中,有f个是失效节点发送的。按照PBFT算法的定义,客户端须要收到 f+1 个相同的回复,才被看成是正确的结果。所以 n-f 个结果中,出去f个失效节点的结果,即 n-f-f = n-2f 至少要是f+1, 即 n-2f = f+1,也就是说 n=3f+1是最少须要的节点数量。
通常状况下,为确保服务的高效性,不能提供容错的信息保密性。
可能可使用secret sharing scheme来得到操做参数和部分对操做来讲透明的状态的保密性。
本部分介绍运行PBFT算法的主流程,即正常操做流程。
算法是状态机副本复制技术的一种形式:服务被建模为状态机,其状态在分布式系统中的不一样副本节点上被复制。每一个状态机副本节点保存维护着服务状态,并实现服务的各类操做。
假设全部副本节点个数为n,算法中,每一个节点依次编号为 0, 1, ..., n-1
方便起见,假设系统中的副本节点总数为 3f+1。能够有更多数量的节点,可是这不会使算法的弹性更优,只会使系统的性能下降。
系统在称为视图(view)的配置下工做。视图以整数编号,从0开始。在一个具体的视图 v 中,经过 p =v mod n,决定出主节点(primary),而其他节点成为副本节点(backup)。当主节点失效时,进行视图变动(view change)。视图的编号是连续递增的。
算法主流程可简要描述以下:
1. 客户端经过向主节点发送请求,以调用服务的操做;
2. 主节点向其余全部副本节点广播该请求;
3. 各节点执行客户请求,同时将回复发送到客户端;
4. 客户端收到 f+1 个来自不一样节点的相同的回复后,此回复即为本次请求的结果。
由于算法基于状态机副本复制技术,因此节点需知足两个条件:
必须是肯定性的,即对于给定的状态,以及给定的参数集合,调用相同的操做后,将始终获得相同的结
果。
各节点拥有相同的初始状态。
在知足上述两个条件的状况下,算法能够保证系统的Safety属性:即便存在失效的节点,各正常副本节点仍能够就不一样的请求的执行顺序达成整体的一致。
接下来详细描述算法主流程。为方便起见,这里省略讨论如下细节:
节点因空间不足致使错误,以及如何恢复;
相似网络的缘由等致使的客户端消息的重传。
另外,假设消息使用公钥签名进行认证,而不是更高效的 MAC 的方式。算法流程的启动从客户端发送请求开始。
客户端操做流程示意图以下:
客户端向其认为的Primary节点发送请求:<REQUEST, o, t, c >。其中,o是请求的操做,t是时间戳,c表明客户端信息。这里省略了消息的签名,包括下文提到的全部消息都应该有发送方的签名,为了讨论方便,做了省略。
相关的几点说明:
请求中的时间戳用于保证请求只被执行一次的语义:全部的时间戳都严格排序,即后发送的请求比先发送的请求拥有更高的时间戳。
每一个副本节点向客户端发送回复时,都会包含当前的视图编号。客户端能够经过该视图编号来肯定当前的主节点,而且只向其认为的主节点发送请求。
每一个副本节点执行完请求后,各自单独地向客户端发送回复,其格式为: <REPLY, v, t, c, i, r > 。v是当前的视图编号,t是请求发送时对应的时间戳,i是副本节点的编号,r是请求的操做的执行结果。
客户端等待来自 f+1 个不一样副本节点的相同回复,即t和r要相同。若是客户端等到了 f+1 个相同回复,r即为请求的结果。 之因此该结果是有效的,是由于错误节点的个数最多为f个,所以必然至少有一个正常节点回复了正确结果,此结果就是 r。
若是客户端没有及时收到回复,则会将请求广播给全部副本节点。副本节点收到请求后,若是发现已经执行过该请求,就只是将结果从新发送至客户端;不然,它会把请求转发到主节点。若是主节点没有把请求广播给其余节点,则最终会被足够多的副本节点认定为错误节点,从而触发视图变动。
在接下的流程讨论中,假定客户端等待一个请求完成后,才发送下一个请求。可是,算法容许客户端异步地发送请求,而且能够保证不一样请求按顺序执行。
主节点收到客户端请求后,将启动三阶段协议,也就是算法接下来的流程。
这三阶段是pre-prepare,prepare和commit。前两阶段,即 pre-prepare 和 prepare 用于保证当前视图中请求
被排好序,然后两阶段 prepare 和 commit 保证请求在视图变动后,仍旧是排好序的。
3.2.2.1 pre-prepare阶段
pre-prepare 阶段流程示意图以下:
pre-prepare阶段中,主节点组装预准备消息,同时把客户端请求附加在其后:<<PRE-PREPARE, v, n, d>, m>,其中,v 指示当前消息当前在哪一个视图中被编号和发送,m 是客户端的请求消息,n是主节点给 m 分配的一个序号(sequence number), d 是 m 的摘要。
这里须要注意的是,请求 m 并无放在预准备消息中,这样作可使预准备消息变得更简短。这样作有两个好处:
一、下降通讯的负载:在视图变动时,因为各节点收到的预准备消息会被用来证实一个特定的请求确实在特定的视图中被赋予了一个序号,较简短的预准备消息将使数据传输量更少。
二、有助于传输优化:算法运行中,一方面须要向各节点发送客户端请求,另外一方面须要传输协议消息以实现对客户请求的排序。经过对这二者解耦,能够实现对较小的协议消息的传输以及对应于较大的请求的大消息的传输分别进行优化。
每一个副本节点接收到预准备消息后,会进行以下校验,若是条件都知足的话,就接受该消息;不然就什么也不作:
客户端请求和预准备消息的签名都正确;d 和 m的消息摘要一致;
当前节点的当前视图是v;
当前节点不曾接受另一条预准备消息,其包含的视图编号和消息序号都和本条消息相同,但对应的是不一样的客户端请求;
预准备消息中的序号 n 位于低水线 h 和高水线 H 之间。这是为了防止有可能出错的主节点随意地选择序号来耗尽序号空间,例如故意选择一个很是大的序号。
若是副本节点接受预准备消息,接下来就进入prepare 阶段,以下节所示。
3.2.2.2 prepare阶段
prepare 阶段流程示意图以下:
在 prepare 阶段,节点会组装并广播准备消息给其余全部副本节点,同时把预准备和准备消息写入到本地消息日志中。
准备消息格式以下: <PREPARE, v, n, d, i>。其中,i 为节点编号,其他参数和预准备消息中的含义相同。
对于副本节点(包括主节点)来讲,当其收到其余节点发送过来的准备消息时,会对这些消息进行校验,若是这些消息知足下列条件:
签名正确
其视图编号和节点的当前视图编号相同
消息中的序号在 h 和 H 之间
则节点会接受准备消息,并写入消息日志中。
对于一个副本节点 i 来讲,若是其消息日志中包含以下消息:
客户端请求 m
在视图 v 中将 m 分配序号 n 的预准备消息
2f个由不一样的副本节点发送的、和预准备消息相匹配的准备消息;这里匹配的含义是,有相同的视图编号、请求序号,以及消息摘要。
咱们就称prepared(m, v, n, i)为true。
算法的预准备和准备阶段用于保证全部的正常副本节点就同一视图中的全部请求的顺序达成一致。具体来讲,这两阶段能确保如下不变式:
若是prepared(m, v, n, i)为true,则对任意一个正常的副本节点j(包含i)来讲,prepared(m', v, n, j)确定为false,这里m'是不一样于m的一个请求。
简单证实以下:
由于prepared(m, v, n, i)为true,而错误节点最多为f个,因此至少有f个正常节点发送了准备消息,再加上主节点,这样至少有f+1个节点已经就m在视图v中被编号为n达成了一致。所以,若是prepared(m', v, n, j)为true,意味着上述f+1个节点中至少有一个节点发送了两个相互矛盾的预准备或准备消息,也就是说,这些消息拥有相同的视图编号和序号,可是对应着不一样的请求消息。但这是不可能的,由于该节点是正常节点,所以prepared(m', v, n, j)必定为false。
对于副本节点i来讲,prepared(m, v, n, i)变为true,则其将进入commit阶段,以下节所示。
3.2.2.3 commit阶段
commit 阶段流程示意图以下:
节点进入 commit 阶段时,副本节点i将向其余全部副本节点广播确认消息:
<COMMIT, v, n, D(m), i>,
对于副本节点来讲,当其收到其余节点发来的确认消息的时候,会判断其是否知足下列条件:
签名正确;
消息中的视图编号等于当前节点的视图编号;
消息中的请求序号在h和H之间。
若是以上条件均知足,则节点则会接受确认消息并写入本地的日志消息中。
对于副本节点i来讲,若是:
prepared(m, v, n, i)为true
而且已经接受了 2f+1 个来自不一样节点的、和 m 对应的预准备消息相匹配的确认消息(可能包含它本身的)
咱们称 committed-local(m, v, n, i) 为 true。这里确认消息和预准备消息匹配的含义是,它们有相同的视图编号、消息序号,以及消息摘要。
另外,若是至少存在 f+1 个节点,对于其中每个节点i来讲,若是 prepared(m, v, n, i) 为true,咱们则称committed(m, v, n) 为 true。
commit 阶段能保证如下不变式:
若是对某个副本节点来讲,committed-local(m, v, n, i) 为 true,则 committed(m, v, n) s也为true。
上述不变式和视图变动协议一块儿可以保证:
全部正常节点可以就全部本地确认的请求的序号达成一致,即便这些请求是在不一样的视图中确认的。对应的证实将在另一篇文档中给出。
另外,该不变式也能保证:任何一个请求若是在一个副本节点被确认,那么它最终也会被至少 f+1 个副本节点确认。
对于任何一个副本节点i来讲,若是:
committed-local(m, v, n, i) 为 true
i 的状态反映了全部序号小于 n 的请求顺序执行的结果
此时,它就能够执行 m 所请求的操做。这就保证了全部的正常节点,按相同的顺序执行请求,从而保证算法的安全性。
在执行了请求的操做后,每一个节点单独地给客户端发送回复。对于一样内容的请求,节点会忽略那些时间戳更早的,以保证请求只被执行一次。
此外,算法并不要求消息按顺序投递,所以,节点能够乱序确认请求。这样作没有问题,由于算法只在一个请求对应的预准备、准备和确认消息都收集彻底时才会执行该请求。
如下是 f=1,即失效节点数为1个,总共节点数为 4个时,PBFT 算法的运行示意图:
关注我,后续将更新PBFT的补充算法流程。