Java 多线程安全问题简单切入详细解析
PBFT && RBFT算法流程以及其实现(上)
这篇文章主要是讲一下RBFT中共识算法流程以及节点的加入的流程。在下一篇博客中,将使用Java实现该算法。
传统的PBFT算法无法动态的添加和删除结点,高鲁棒拜占庭容错算法RBFT(Robust Byzantine Tolerance)算法实现了该功能。
在RBFT算法中,有几个变量我们需要知道:f,N,quorum
- N ; 代表结点的数量。
- f :代表PBFT中最多能容忍的错误的结点$ f = \lfloor\frac{N-1} { 3} \rfloor$
- quorum:达到共识需要的结点数量 $quorum = \lceil \frac {N + f +1 }{2 }\rceil $
因此在PBFT算法中,为了能够容忍f个错误,需要的结点数量是$3f+1$
在RBFT算法中,有一个主节点和多个从结点,其中主节点是通过选举产生的,负责对客户端发来的交易进行打包处理,而从节点很简单,进行共识认证以及主结点的选取。
RBFT && PBFT的常规流程
下面是来自hyperchain的关于RBFT流程的一些介绍:
RBFT常规流程
RBFT的常规流程保证了区块链各共识节点以相同的顺序处理来自客户端的交易。RBFT同PBFT的容错能力相同,需要至少3f+1个节点才能容忍f个拜占庭错误。下图为最少集群节点数下的共识流程,其N=4,f=1。图中的Primary1为共识节点动态选举出来的主节点,负责对客户端发来的交易进行排序打包,Replica2,3,4为从节点。所有节点执行交易的逻辑相同并能够在主节点失效时参与新主节点的选举。
常规流程
RBFT共识保留了PBFT原有的三阶段处理流程(PrePrepare、Prepare、Commit)的同时增加了重要的交易验证(validate)环节,在保证对交易执行顺序达成共识的同时也保证了对区块验证结果的共识。
RBFT常规流程在原生的PBFT算法中穿插了交易验证环节,主节点将交易打包成块后先行验证,并将验证结果包含到PrePrepare消息中进行全网广播,这样PrePrepare消息中既包含了排好序的交易信息也包含了区块验证结果。从节点在收到主节点的PrePrepare消息后先检查消息的合法性,检查通过后广播Prepare消息表明本节点同意主节点的排序结果;在收到(quorum-1)个Prepare消息后从节点才会开始验证区块,并将验证结果与主节点的验证结果进行比对,比对结果一致则广播Commit表明本节点同意主节点的验证结果,否则直接发起ViewChange表明本节点认为主节点有异常行为。RBFT常规流程具体分为如下几个步骤:
- 交易转发阶段: 客户端将交易发送到区块链中的任意节点(包括共识节点与记账节点),其中记账节点在收到交易后会主动转发给与其相连的共识节点;而共识节点在收到客户端的交易后将其广播给其他共识节点,这样所有共识节点的交易池中都会维护一份完整的交易列表;
- PrePrepare阶段: 主节点按照如下策略进行打包:用户可以根据需求自定义打包的超时时间(batch timeout)与打包的最大区块大小(batch size),主节点在超时时间内收集到了足够多(超过最大区块大小个数)的交易或者超时时间到达后仍未收集到足够多的交易都会触发主节点的打包事件。主节点将交易按照接收的时间顺序打包成块,并进行验证,计算执行结果,最后将定序好的交易信息连同验证结果等写入PrePrepare消息中广播给所有共识节点,开始三阶段处理流程;
- Prepare阶段: 从节点在收到主节点的PrePrepare消息后,首先进行消息合法性检查,检查当前的视图与区块号等信息,检查通过后向共识节点广播Prepare消息;
- Commit阶段: 从节点在收到(quorum-1)个Prepare消息以及相应的PrePrepare消息后进行验证,并将验证结果与主节点写入PrePrepare消息中的验证结果进行比对,比对结果一致则广播Commit表明本节点同意主节点的验证结果,否则直接发起ViewChange表明本节点认为主节点存在异常行为,需要切换主节点;
- 写入账本: 所有共识节点在收到quorum个Commit消息后将执行结果写入本地账本。
以上的过程还是很简单的,就是主节点发送交易信息,如果大部分的从结点(也就是quorum个结点)同意,则这个交易信息会被写入所有结点的区块(少数服从多数)。
在前面我们可以发现,从节点是可以怀疑主节点的,也就是说从节点可以发起请求进行重新选举,得到一个新的主节点(这个在主节点被攻击或者宕机是非常有效的)。
主节点的生成以及变迁
在PBFT以及RBFT中,都有视图(View),这个值从零开始只增不减。那么我们如何得到主节点呢?或者说重新选举,选择谁为主节点。
设:结点数为N,当前视图为view,则主结点的id为:
$$primaryId = (view +1) mod N$$
下面的引用还是来自hyperchain,毕竟有图能够理解的更好。其中:
- nullRequest 消息的目的是查看主节点是不是在线
视图变更流程
上图中,Primary 1为拜占庭节点,需要进行ViewChange。在RBFT中的ViewChange流程如下:
- 从节点在检测到主节点有异常情况(没有按时收到nullRequest消息)或者接收到来自其他f+1个节点的ViewChange消息之后会向全网广播ViewChange消息,自身view从v更改为v+1;
- 新视图中主节点收到N-f 个ViewChange消息后,根据收到的ViewChange消息计算出新视图中主节点开始执行的checkpoint和接下来要处理的交易包,封装进NewView消息并广播,发起VcReset;
- 从节点接收到NewView消息之后进行消息的验证和对比,如果通过验证,进行VcReset,如果不通过,发送ViewChange消息,进行又一轮ViewChange;
- 所有节点完成VcReset之后向全网广播FinishVcReset;
- 每个节点在收到N-f个FinishVcReset消息之后,开始处理确定的checkpoint后的交易,完成整个ViewChange流程。
由于共识模块与执行模块之间是异步通信的,而ViewChange之后执行模块可能存在一些无用的validate缓存,因此共识模块需要在ViewChange完成之前通知执行模块清除无用的缓存,RBFT通过VcReset事件主动通知执行模块清除缓存,并在清理完成之后才能完成ViewChange。
RBFT中结点动态的增添和删除
在前面我们知道,PBFT算法是无法实现结点动态的增删的,而新的算法RBFT实现了该功能。
结点的增加和删除当然会遵守共识原则,下面还是来自hyperchain的介绍。毕竟别人介绍的比我好多了,我就不多介绍了。
简单点来说,就是新加入的结点会向区块链中的已经存在的结点申请加入,如果存在的结点同意的话,则就加入成功,然后新加入的结点会发送recovery消息(关于recovery可以看hyperchain的介绍),目的是为了让自己与区块链中结点的内容保持一致。然后新加入的结点会要求进行重新选举主节点(因为N已经发生改变),然后完成主节点的更改。
新增节点流程
上图中,Replica 5为待新增的节点。RBFT节点的动态新增节点流程如下:
- 新增节点Replica 5通过读取配置文件信息,主动向现有节点发起连接,确认所有节点连接成功后更新自身的路由表,并发起recovery;
- 现有节点接收到Replica 5的连接请求后确认同意该节点加入,然后向全网广播AddNode消息,表明自己同意该新节点加入整个共识网络;
- 当现有节点收到N条(N为现有区块链共识网络中节点总数)AddNode消息后,更新自身的路由表,随后开始回应新增节点的共识消息请求(在此之前,新增节点的所有共识消息是不予处理的);
- Replica 5完成recovery之后,向全网现有节点广播ReadyForN请求;
- 现有节点在收到ReadyForN请求后,重新计算新增节点加入之后的N,view等信息,随后将其与PQC消息封装到AgreeUpdateN消息中,进行全网广播;
- Replica 5加入后的共识网络会产生一个新的主节点,该主节点在收到N-f个AgreeUpdateN消息后,以新的主节点的身份发送UpdateN消息;
- 全网所有节点在收到UpdateN消息之后确认消息的正确性,进行VCReset;
- 每个节点完成VCReset后,全网广播FinishUpdate消息;
- 节点在收到N-f个FinishUpdate消息后,处理后续请求,完成新增节点流程。
这篇文章基本上就是从hyperchain上面copy上来的,在这里仅仅是做一个笔记用。如果想了解更多,建议参考官方文档。这一篇主要是为了弄清楚PBFT or RBFT的流程,这几天在看《区块链底层设计 Java实战》一直没弄得太懂,然后代码也写的没头绪。这里不得不感谢牛冬(这本书的作者)大佬,很热心的回答我的问题(真没想到加作者的微信竟然加成功了O(∩_∩)O~~)。
哦,还得掉头发,想一想怎么实现这些算法……