对PBFT算法的理解
PBFT论文断断续续读了几遍,每次读或多或少都会有新的理解,结合最近的项目代码,对于共识的原理有了更清晰的认识。虽然之前写过一篇整理PBFT论文的博客,但是当时只是知道了怎么做,却不理解为什么。现在整理下思路,写一篇关于PBFT的理解。
1. 前提假定
1.1 同步模型
在分布式系统中谈论共识,首先需要明确系统同步模型是synchrony,asynchrony还是partial synchrony?
- synchrony: 节点所发出的消息,在一个确定的时间内,肯定会到达目标节点;
- asynchrony: 节点所发出的消息,不能确定一定会到达目标节点;
- partial synchrony: 节点发出的消息,虽然会有延迟,但是最终会到达目标节点。
synchrony是十分理想的情况,如果假设分布式系统是一个同步系统,那么共识算法的设计可以简化很多,在同步系统中只要超时没收到消息就可以认为节点除了问题。asynchrony是更为贴近实际的模型,但是根据FLP Impossibility原理,在asynchrony假定下,共识算法不可能同时满足safety和liveness。为了设计能够符合实际场景的共识算法,目前的BFT类共识算法多是基于partial synchrony假定,这在PBFT论文中被称为"weak synchrony"。
PBFT假设系统是异步的,节点通过网络连接,消息会被延迟,但是不会被无限延迟。
1.2 容错类型
PBFT假定错误可以是拜占庭类型的,也就是说可以使任意类型的错误,比如节点作恶、说谎等。这有别于crash-down类型的错误,raft、paxos这类共识算法只能允许crash-down类型错误,节点只能crash而不能产生假消息。
错误类型 | 总节点数 |
---|---|
Byzantine fault | \(3f+1\) |
Crash-down fault | \(2f+1\) |
对于拜占庭类错误,总节点数为n,假设系统可能存在f个拜占庭节点,假如需要根据节点发送过来的消息做判断。为了共识正常进行,在收到n-f个消息时,就应该进行处理,因为可能有f个节点根本不发送消息。现在我们根据收到的n-f个消息做判断,判断的原则至少f+1个相同结果。但是,在收到的n-f个消息中,不能确定其中没有错误节点过来的消息,其中也可能存在f个假消息。应该保证n-f-f > f,即n>3f。
系统模型
一组节点构成状态机复制系统,一个节点作为主节点(primary),其他节点作为备份节点(back-ups)。某个节点作为主节点时,这称为系统的一个view。当节点出了问题,就进行view更新,切换到下一个节点担任主节点。主节点更替不需要选举过程,而是采用round-robin方式。
\]
在系统的主节点接收client发来的请求,并产生pre-prepare消息,进入共识流程。
我们需要系统满足如下两个条件
+deterministic: 在一个给定状态上的操作,产生一样的执行结果
+ 每个节点都有一样的起始状态
要保证non-fault节点对于执行请求的全局顺序达成一致。
1.3 safety & liveness
- safety: 坏的事情不会发生,即共识系统不能产生错误的结果,比如一部分节点说yes,另一部分说no。在区块链的语义下,指的是不会分叉。
- liveness: 好的事情一定会发生,即系统一直有回应,在区块链的语义下,指的是共识会持续进行,不会卡住。假如一个区块链系统的共识卡在了某个高度,那么新的交易是没有回应的,也就是不满足liveness。
2. Normal process
正常状态下的共识流程可以用论文中的配图清晰表示,如下所示。
共识过程由三个阶段构成,pre-prepare阶段和prepare阶段确保了在同一个view下,正常节点对于消息m达成了全局一致的顺序,用\(Order<v, m, n>\)表示,在view = v下,正常节点都会对消息m,确认一个序号n。接下来的commit投票,再配合上viewchange的设计,实现了即使view切换,也可以保证对于m的全局一致顺序,即\(Order<v+1, m, n>\),视图切换到v+1, 依然会对消息m,确认序号n。
pre-prepare
primary节点收到请求m时,会做两件事,首先需要讲这个请求m广播给其他节点;然后是给请求m分配一个序号n,并广播给其他节点。广播之后会将消息保存在本地log中。
pre-prepare阶段的消息格式\(<<PRE-PREPARE, v, n, d>_p, m>\),其中v表示当前view编号,n表示给m分配的序号,d为m的哈希,以及m的原文。
其他节点收到pre-prepare消息时,会依次做如下几步操作:
- 签名验证
- 消息是本本节点所在view的消息
- 本节点在v视图下,还没有收到序号n的其他消息
- 收到的消息序号n,在当前接收窗口内(h, H)
- 以上几部都通过,则接受该消息,并广播prepare消息进入prepare阶段
一旦节点接受\(\langle \langle PRE-PREPARE, v, n, d \rangle_p, m \rangle\),则该节点进入到prepare阶段,然后节点广播prepare消息\(\langle PREPARE, v, n, d, i \rangle_i\)。之后,节点将消息加入到本地的log中。
prepare
节点收到prepare消息时,会验签并检查是否是当前view的消息,同时检查消息序号n在当前的接收窗口内,验证通过则接受该消息,保存到本地log中。
当节点达成以下3点时,则表明节点达成了prepared状态,记为prepared(m,v,n,i)。
- 在log中存在消息m
- 在log中存在m的pre-prepare消息,pre-prepare(m,v,n)
- 在log中存在2f个来自其他节点的prepare消息,prepare(m,v,n,i)
至此,可以确保在view不发生切换的情况下,对于消息m有全局一致的顺序。
也就是说,在view不变的情况的下:
- (1) 一个正常节点i,不能对两个及以上的不同消息,达成相同序号n的prepared状态。即不能同时存在prepared(m,v,n,i)和prepared(m',v,n,i)
- (2) 两个正常节点i、j,必须对相同的消息m,达成相同序号n的prepared状态。prepared(m,v,n,i) && prepared(m,v,n,j)
简要的证明:
(1) 假如正常节点i, 对于消息m达成了prepared(m,v,n,i),同时存在一个m',也达成了prepared(m',v,n,i)。
首先对于prepared(m,v,n,i),肯定有2m+1个节点发出了<prepare,m,v,n>消息。
对于prepared(m',v,n,i),肯定也有2m+1个节点发出了<prepare,m',v,n>。
2*(2f+1) - (3f+1) = f+1
所以至少有f+1个节点,既发出了<prepare,m,v,n>,又发出了<prepare,m',v,n>,这明显是拜占庭行为。也就是说,至少有f+1个拜占庭节点,而这与容错条件相矛盾。
(2) 假如两个正常节点i、j,分别对不同的消息m、m',达成序号n的prepared状态,prepared(m,v,n,i)和prepared(m',v,n,j)
首先对于prepared(m,v,n,i),肯定有2m+1个节点发出了<prepare,m,v,n>消息。
对于prepared(m',v,n,j),肯定也有2m+1个节点发出了<prepare,m',v,n>。
2*(2f+1) - (3f+1) = f+1
所以至少有f+1个节点,既发出了<prepare,m,v,n>,又发出了<prepare,m',v,n>,这明显是拜占庭行为。也就是说,至少有f+1个拜占庭节点,而这与容错条件相矛盾。
prepared状态是十分重要的,当涉及到view转换时,为了保证view切换前后的safety特性,需要将上一轮view的信息传递到新的view,而在pbft中就是将prepared状态信息传递到新的view。可以这么理解,新的view中需要在上一轮view的prepared信息基础上,继续进行共识。
在tendermin共识算法中,同样是采用与pbft类似的三个阶段(两轮投票),但是在round切换时,并没有传递prepared状态信息。为了保证safety特性,tendermint中新的轮次中,根据本地节点是否有锁定的信息来进行,而锁定的信息就是prepared状态。所以,tendermint也是在本节点上一轮prepared信息的基础上继续进行共识。
达成prepared状态以后,节点会广播commit消息\(\langle COMMIT, v, n, d, i \rangle_i\).
commit
节点接收commit消息后,会像收到prepare消息一样进行几步验证已确定是否接受该消息。
当节点i,达成了prepared(m,v,n,i)状态,并且收到了\(2f+1\)个commit(v,n,d,i)消息,则该节点达成了commit-local(m,v,n,i)状态。
达成commit-local之后,节点对于消息m就有了一个全局一致的顺序,可以执行该消息并 reply to 客户端了。
commit-local状态说明有2f+1个节点达成了prepared状态.
3. garbage collect
由于实际的消息log不可能无限大,因此需要设定checkpoint,以实现过时消息的清除。
直观的做法就是,每隔一段时间,在序号(n%100 == 0)时,确认每个节点都已经执行完第n个消息了。这样就可以清除掉比n还要早的消息了。
在pbft论文中,这也是通过投票实现的,当一个节点执行完第n个消息后,就广播\(\langle CHECKPOINT, n, d, i \rangle\) 消息。节点收集到\(2f+1\) checkpoint消息后,就产生一个本地的checkpoint,然后清除掉比n小的消息。然后将接收消息的窗口调整为(n, n+100).
4. viewchange
个人认为,viewchange是pbft中最为关键的设计,viewchange的设计保证了共识系统的safety和liveness特性。
当节点检测到超时时,会发送viewchange消息,进入viewchange流程,viewchange消息包含如下内容:
- <VIEWCHANGE, n, C, P, i>
- n: 消息序号,本节点最近的一个check-point所确定的序号
- C: 对应于n的check-point 2f+1个CHECKPOINT消息集合
- P: 一个\(P_m\)组成的集合,m表示消息,m的序号是大于n的,\(P_m\)表示序号为m的达成prepared状态的消息集合。\(P_m\)内容包含关于m的\(1\)个pre-prepare消息和\(2f\)条prepare消息集合。
- i: 节点ID
由消息结构可以看出,当节点发出viewchange消息时,节点将本地的prepared状态信息打包到了消息中,传递给后续的view。
当view+1所对应的primary收到了2f个有效的view-change消息,它就会广播<NEW-VIEW, v+1, \(V\), \(O\)>消息;
+ \(V\): 是view-change消息集合
+ \(O\): pre-prepare消息的集合, \(O\)按照如下的过程计算:
- primary根据收到的view-change消息判断,最低的check-point min-s和最高的check-point max-s
- 对介于 min-s和max-s之间的每个序号n创建pre-prepare消息。这分两种情况:(1) 在P集合存在一个\(P_m\)其中序号为n; (2) 没有这样的集合\(P_m\). 对于第一种情况,创建一个pre-prepare消息,<PRE-PREPARE, v+1, n, d>。对于第二种情况,创建新的<PRE-PREPARE, v+1, n, d_null>。
可以这样理解,在新的view中,节点是在上一轮view中各个节点的prepared状态基础上进行共识流程的。
发生view转换时,需要的保证的是:如果视图转换之前的消息m被分配了序号n, 并且达到了prepared状态,那么在视图转换之后,该消息也必须被分配序号n(safety特性)。因为达到prepared状态以后,就有可能存在某个节点commit-local。要保证对于m的commit-local,在视图转换之后,其他节点的commit-local依然是一样的序号。
5. 思考
- 经过两轮投票的BFT共识协议,比如PBFT、tendermint等,轮次切换时,都是在previous轮次中的第一轮投票结果基础上继续共识流程。
- BFT类共识需要保证safty和liveness,safety可以在asynchrony假设下达成,liveness需要弱同步假设
- pbft的核心设计是viewchange,巧妙的在viewchange消息添加prepared信息,实现将previous视图信息传递到下一轮。但是,这样存在的问题是,消息太大了,有些冗余。
6. Reference
[1] Castro, Miguel, and Barbara Liskov. "Practical Byzantine fault tolerance." OSDI. Vol. 99. 1999.
[2] Kwon, Jae. "Tendermint: Consensus without mining." Draft v. 0.6, fall (2014).
对PBFT算法的理解的更多相关文章
- PBFT算法java实现
PBFT 算法的java实现(上) 在这篇博客中,我会通过Java 去实现PBFT中结点的加入,以及认证.其中使用socket实现网络信息传输. 关于PBFT算法的一些介绍,大家可以去看一看网上的博客 ...
- PBFT 算法 java实现(下)
PBFT 算法的java实现(下) 在上一篇博客中(如果没有看上一篇博客建议去看上一篇博客),我们介绍了使用Java实现PBFT算法中节点的加入,view的同步等操作.在这篇博客中,我将介绍PBFT算 ...
- openCV中直方图均衡化算法的理解
直方图均衡化就是调整灰度直方图的分布,即将原图中的灰度值映射为一个新的值.映射的结果直观表现是灰度图的分布变得均匀,从0到255都有分布,不像原图那样集中.图像上的表现就是对比度变大,亮的更亮,暗的更 ...
- SDUT OJ 数据结构实验之串一:KMP简单应用 && 浅谈对看毛片算法的理解
数据结构实验之串一:KMP简单应用 Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Discuss Problem Descr ...
- 一文读懂实用拜占庭容错(PBFT)算法
在区块链中有一个著名的问题,就是拜占庭将军问题,对于拜占庭将军问题,网上的文章已经多得不要不要了,今天和大家分享的是其相关的实用拜占庭容错算法,一起来看看吧. 实用拜占庭容错算法(Practi ...
- POJ1523(割点所确定的连用分量数目,tarjan算法原理理解)
SPF Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 7406 Accepted: 3363 Description C ...
- 【转】浅谈对主成分分析(PCA)算法的理解
以前对PCA算法有过一段时间的研究,但没整理成文章,最近项目又打算用到PCA算法,故趁热打铁整理下PCA算法的知识.本文观点旨在抛砖引玉,不是权威,更不能尽信,只是本人的一点体会. 主成分分析(PCA ...
- Vue中diff算法的理解
Vue中diff算法的理解 diff算法用来计算出Virtual DOM中改变的部分,然后针对该部分进行DOM操作,而不用重新渲染整个页面,渲染整个DOM结构的过程中开销是很大的,需要浏览器对DOM结 ...
- 关于KMP算法中,获取next数组算法的理解
参考:KMP入门级别算法详解--终于解决了(next数组详解) https://blog.csdn.net/lee18254290736/article/details/77278769 在这里讨论的 ...
随机推荐
- yii 执行流程
应用执行流程: 浏览器向服务器发送 Http Request | 控制器(protected/controllers) | |---> Action | 创建模型 (Model) | 检查$_P ...
- JS应该放在什么位置?
(1)放在底部,虽然放在底部照样会阻塞所有呈现,但不会阻塞资源下载 (2)如果嵌入JS放在head中,请把嵌入JS放在CSS头部 (3)使用defer(只支持IE) (4)不要在嵌入的JS中调用运行时 ...
- JavaScript-闭包函数(理解)
JavaScript-闭包函数(理解) var foo = function (a) { return function inner () { console.log(a) } } var faa = ...
- CentOS7.3安装mysql数据库
Mysql数据库安装 1.环境 操作系统:CentOS 7.3 软件:MySQL 5.7 下载链接:https://cdn.mysql.com//Downloads/MySQL-5.7/mysql-5 ...
- 利用phar实行php反序列化命令执行(测试环境复现)
测试环境的过程大概是:构成出来的phar文件,并修改为任意后缀上传至服务器.通过index.php中存在的文件操作函数参数可控,把参数设置为 phar://上传文件名 即可导致命令执行. index. ...
- 架构相关:组件化/模块化/工程化/性能优化/开发规范与团队协作/组件间调用与通信(flex/redux)/调试与测试
https://github.com/fouber/blog http://teropa.info/blog/2015/09/10/full-stack-redux-tutorial.html htt ...
- JavaWeb基础—Servlet
一.Servlet是什么 是服务器上运行的Java小应用程序,并被称为JavaWeb三大组件之一 通常我们把实现了Servlet的类,称之为Servlet Servlet作用主要是 1.接收请求数据 ...
- [agc011E]Increasing Numbers-[思考题]
Description 传送门 Solution 依题得所有不下降数(设为a)可以拆为若干个全1数的和(如:1558=1111+111+111+111+111+1+1+1) 并且任意a所能拆出的全一数 ...
- springboot之jpa多数据源
1.随着业务复杂程度的增加,我们在单一数据源上面的使用越来越不满足具体的业务逻辑以及实现了. 2.那么多数据源,比如多库多数据库等,我们在使用一个工程的时候多数据源的连接还是很有必要的,这里做一下记录 ...
- Quartz动态添加,修改,删除任务(暂停,任务状态,恢复,最近触发时间)
首页 博客 学院 下载 图文课 论坛 APP 问答 商城 VIP会员 活动 招聘 ITeye GitChat 写博客 小程序 消息 登录注册 关闭 quartz_Cron表达式一分钟教程 09-05 ...