对PBFT算法的理解

PBFT论文断断续续读了几遍,每次读或多或少都会有新的理解,结合最近的项目代码,对于共识的原理有了更清晰的认识。虽然之前写过一篇整理PBFT论文的博客,但是当时只是知道了怎么做,却不理解为什么。现在整理下思路,写一篇关于PBFT的理解。

1. 前提假定

1.1 同步模型

在分布式系统中谈论共识,首先需要明确系统同步模型是synchrony,asynchrony还是partial synchrony?

  • synchrony: 节点所发出的消息,在一个确定的时间内,肯定会到达目标节点;
  • asynchrony: 节点所发出的消息,不能确定一定会到达目标节点;
  • partial synchrony: 节点发出的消息,虽然会有延迟,但是最终会到达目标节点。

synchrony是十分理想的情况,如果假设分布式系统是一个同步系统,那么共识算法的设计可以简化很多,在同步系统中只要超时没收到消息就可以认为节点除了问题。asynchrony是更为贴近实际的模型,但是根据FLP Impossibility原理,在asynchrony假定下,共识算法不可能同时满足safetyliveness。为了设计能够符合实际场景的共识算法,目前的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。

系统模型

一组节点构成状态机复制系统,一个节点作为主节点(privary),其他节点作为备份节点(back-ups)。某个节点作为主节点时,这称为系统的一个view。当节点出了问题,就进行view更新,切换到下一个节点担任主节点。主节点更替不需要选举过程,而是采用round-robin方式。

\[ privary = view % N \]

在系统的主节点接收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

privary节点收到请求m时,会做两件事,首先需要讲这个请求m广播给其他节点;然后是给请求m分配一个序号n,并广播给其他节点。广播之后会将消息保存在本地log中。

pre-prepare阶段的消息格式\(<<PRE-PREPARE, v, n, d>_p, m>\),其中v表示当前view编号,n表示给m分配的序号,d为m的哈希,以及m的原文。

其他节点收到pre-prepare消息时,会依次做如下几步操作:

  1. 签名验证
  2. 消息是本本节点所在view的消息
  3. 本节点在v视图下,还没有收到序号n的其他消息
  4. 收到的消息序号n,在当前接收窗口内(h, H)
  5. 以上几部都通过,则接受该消息,并广播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)

  1. 在log中存在消息m
  2. 在log中存在m的pre-prepare消息,pre-prepare(m,v,n)
  3. 在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所对应的privary收到了2f个有效的view-change消息,它就会广播<NEW-VIEW, v+1, \(V\), \(O\)>消息;

+ \(V\): 是view-change消息集合

+ \(O\): pre-prepare消息的集合, \(O\)按照如下的过程计算:

- privary根据收到的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).

原文地址:https://www.cnblogs.com/gexin/p/10242161.html

时间: 2024-08-29 08:12:02

对PBFT算法的理解的相关文章

PBFT算法java实现

PBFT 算法的java实现(上) 在这篇博客中,我会通过Java 去实现PBFT中结点的加入,以及认证.其中使用socket实现网络信息传输. 关于PBFT算法的一些介绍,大家可以去看一看网上的博客,也可以参考我的上上一篇博客,关于怎么构建P2P网络可以参考我的上一篇博客. 该项目的地址:GitHub 使用前的准备 使用maven构建项目,当然,也可以不使用,这个就看自己的想法吧. 需要使用到的Java包: t-io:使用t-io进行网络socket通信,emm,这个框架的文档需要收费(699

POJ1523(求连用分量数目,tarjan算法原理理解)

SPF Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 7406   Accepted: 3363 Description Consider the two networks shown below. Assuming that data moves around these networks only between directly connected nodes on a peer-to-peer basis, a

01背包算法的理解

01背包问题: 有N件物品和一个最大重量限制为V的背包.第i件物品的重量是c[i],价值是w[i].求解将哪些物品装入背包可使这些物品的重量总和不超过V,且价值总和最大.每个物品只有1份,且不可分割 看了01背包算法,言简意赅,但理解起来头昏脑胀,不得要领.尝试解释下对该算法的理解,加深记忆. 假设最优解已经存在,怎么判断一个物品i是否在背包里?  简单,只要知道, 1.c[i]是否大于V, 2.F[i-1][V-c[i]],即没有i物品的情况下,最大重量限制为V-c[i]的最优解. 3.F[i

【转】浅谈对主成分分析(PCA)算法的理解

以前对PCA算法有过一段时间的研究,但没整理成文章,最近项目又打算用到PCA算法,故趁热打铁整理下PCA算法的知识.本文观点旨在抛砖引玉,不是权威,更不能尽信,只是本人的一点体会. 主成分分析(PCA)是多元统计分析中用来分析数据的一种方法,它是用一种较少数量的特征对样本进行描述以达到降低特征空间维数的方法,它的本质实际上是K-L变换.PCA方法最著名的应用应该是在人脸识别中特征提取及数据维,我们知道输入200*200大小的人脸图像,单单提取它的灰度值作为原始特征,则这个原始特征将达到40000

KPM算法初步理解

一个字符串"FBCABCDABABCDABCDABYW"中是否包含另外一个字符串"ABCDABY"? 上面这道题目是一个经典的字符串匹配的题目,对于字符串匹配,比较好的算法里很容易想到KPM算法,那KPM算法是干什么的?为什么说KPM比较优秀? 给定一个字符串O和F,长度分别是m.n,判断F是否在O中出现,如果出现则返回出现的位置.常规方法是遍历O的每一个字符,与F的每一个字符进行比较,但是这种方法的时间复杂度是T(m*n),但是KPM算法使得时间复杂度为T(m+n

对动态规划算法的理解及相关题目分析

1.对动态规划算法的理解 (1)基本思想: 动态规划算法的基本思想与分治法类似:将待求解的问题分解成若干个子问题,先求解子问题,然后从这些子问题的解中得到原问题的解.但是,与分治法不同的是,为了避免重复多次计算子问题,动态规划算法用一个表记录所有已解决的子问题的答案,不管该子问题以后是否被利用,只要它被计算过,就将其结果填入表中. (2)设计动态规划算法的步骤: ①找出最优解的性质,并刻画其结构特征 ②递归地定义最优值 ③以自底向上的方式计算最优值 ④根据计算最优值时得到的信息构造最优解 (3)

KMP算法详细理解

KMP算法详细理解 从昨天开始看KMP算法到今天凌晨..... 把一些知识点进行总结,其实KMP还是挺简单的(HHHHHH) 博客新地址:https://miraitowa2.top/ 1:BF(暴力匹配)算法 假设现在我们面临这样一个问题:有一个文本串S,和一个模式串P,现在要查找P在S中的位置,怎么查找呢? 如果用暴力匹配的思路,并假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置,则有: 如果当前字符匹配成功(即S[i] == P[j]),则i++,j++,继续匹配下一个字符: 如

对回溯算法的理解

一.对回溯算法的理解 应用回溯算法的三个步骤: 1.首先得构造解空间树:子集树和排列树: 2.以深度优先的方式搜索解空间:递归或迭代: 3.设计剪枝函数避免无效搜索:使用约束函数,剪去不满足约束条件的路径或使用限界函数,剪去不能得到最优解的路径. 回溯法解问题的一个显著特征是,解空间树是虚拟的,在任何时候,只需保存从根节点到当前扩展结点的路径. 在回溯问题中,若要求问题的所有解,就要回溯到根. 二.请说明“子集和”问题的解空间结构和约束函数 子集和问题: 设集合S={x1,x2,…,xn}是一个

二分算法 再次理解

二分算法 再次理解 详解二分查找算法 这篇博客很详细介绍了二分算法的一些细节问题 寻找一个数,也是最基本的二分搜索 //代码示例如下 int bsearch(int []nums, int target) { int left=0, right=nums.length-1;//这里的数组长度用法可以是其他的形式 while(left<=right) { int mid = left + (right - left) / 2; if(num[mid] == target) return mid;