由facebook发币谈谈PBFT共识算法

2023年 7月 22日 73.6k 0

引言

大家前段时间应该都看到了Facebook发布区块链Libra的消息。与大名鼎鼎的比特币相比,Libra有一个核心的特点就是修改了共识算法,从PoW换为了基于拜占庭将军问题演化而来的LibraBFT。今天要介绍的PBFT(Practical Byzantine Fault Tolerance)算法,是最经典的解决拜占庭将军问题的算法之一,也是LibraBFT很多核心思想的来源。
PBFT算法的全称叫实用拜占庭容错算法,是用来保证在分布式系统有节点可能背叛的情况下,对某个请求达成一致。本文会先介绍一些拜占庭将军问题的基本知识,然后再详细介绍PBFT算法的整个过程,希望能帮助初学者对拜占庭将军问题和PBFT算法有一个基本的认知。因为篇幅和水平的限制,省略了证明的过程,如果有错误和建议请随时提出。

大家之前可能对Paxos、Raft、Zab等传统分布式一致性算法有所了解,本文所说的共识算法与传统一致性算法的主要区别在于是否有背叛节点,传统一致性算法主要解决的是在机器故障、网络分区等情况下保证一致性,而本文中提到的共识算法不仅有传统的问题,还要防范背叛节点联合起来对系统进行有预谋的主动破坏。

起源-拜占庭将军问题

拜占庭将军问题简介

PBFT算法的提出,就是为了解决拜占庭将军问题,所以想要了解PBFT算法,就要从拜占庭将军问题讲起。拜占庭将军问题(Byzantine Generals Problem),是由莱斯利·兰波特在其同名论文中提出的分布式对等网络通信容错问题。
在分布式计算中,不同的计算机通过通讯交换信息达成共识而按照同一套协作策略行动。但有时候,系统中的成员计算机可能出错而发送错误的信息,用于传递信息的通讯网络也可能导致信息损坏,使得网络中不同的成员关于全体协作的策略得出不同结论,从而破坏系统一致性,这种问题被称为拜占庭将军问题。拜占庭将军问题被认为是容错性问题中最难的问题类型之一。
我们用更通俗的语言来描述一下,假设了这样一个场景,n 个将军被分隔在一个城市周围的不同地方,他们之间只能通过信使进行通信。n个将军中大部分是忠诚的将军,他们会按照约定的去做,但是有一些背叛的将军(又叫拜占庭节点),他们会做任意想做的事情。为了取得战斗的胜利,忠诚的将军必须达成一致,比如一起进攻或者一起撤退,否则会有灾难性的后果。所以他们需要有一个算法来保证无论叛徒做什么,所有忠诚的将军都能达成一致。

PBFT算法作为一致性算法的一种,主要考虑的是一致性,也就是说在有节点背叛的状态下,所有正常节点达成一致,具体达成的一致是什么(进攻或者撤退),算法并不关心。

经典的拜占庭将军问题解法

兰波特在论文中在不同的约束条件下,提出了数种解决方案。这里我们详细介绍其中一种,这种解法基于以下一些约束条件:

  • 背叛节点不足三分之一。背叛节点数如何得出会在后续进行分析。
  • 忠诚节点间的通信不会出现问题。在有界时间t内,消息一定能达到。
  • 消息可以确认来源。节点收到的消息可以确认来源,比如将军间通信的信使互相都是认识的,可以确认来源,但是消息本身没有签名,无法确认消息的正确性。
  • 有了上述约束条件,我们先从只有四个节点(将军),其中一个为主节点的简单场景入手,看一看如何设计一个算法来保证进攻或者防守命令的一致性。

    分布式系统中有一个经典定理叫FLP不可能定义,其核心是如果通信不畅通的情况下,分布式系统永远无法达成一致。因此我们的算法都假设在超时时间之内通信都是畅通,如果通信不通,就把对方归为拜占庭节点。
    通信可以确认来源和防止伪造是解决拜占庭问题的必要条件之一,在分布式系统中一般通过签名和摘要等密码学来保证,本文讲的经典解法不需要密码学保证,只需要消息可以确认来源。
    

    这种经典算法可以保证一致,一共分为以下三步:

  • 主节点把进攻消息通知到所有从节点。
  • 从节点把自己收到的消息发送给其他从节点。
  • 每节点都收到了所有的命令,通过约定好的majority算法(总结点数是3f+1的情况下,除了自己收到2f个节点的消息就执行命令),就可以得到结果。
  • 如果主节点是拜占庭节点

    拜占庭节点的定义是随机行为,为了便于分析算法是否可靠,我们假设拜占庭节点都是叛徒节点,故意来破坏算法的一致性。
    我们先来分析如果主节点是拜占庭节点,看看他可以进行哪些破坏行为,这些行为是否会影响一致性:

  • 主节点不发出命令,或发出命令但自己不执行。此时除了主节点外的所有节点收到的命令是一致的,所以一致性不会被影响。
  • 主节点给不同的人发送不同的命令,或者只给一部分人发送命令。因为节点间通信有超时时间,超过超时时间没有收到请求就默认是失败,所以发送不同的命令和不发送命令本质上是一样的。我们假设主节点A是拜占庭节点的情况下执行下算法,看看是否会影响一致性。
  • 算法步骤1,主节点A向从节点B发送命令0,向从节点CD发送命令1

    算法步骤2,从节点BCD收到命令后,把收到的A命令同步给其他两个从节点

    算法步骤3,各个节点按照majority算法计算各自的请求结果。如图所示,除了主节点,其余节点都收到了两个1和一个0,majority算法是大于两(2f)个1结果就是1,忠诚的三个从节点达成了一致。

    如果从节点是拜占庭节点

    如果从节点是拜占庭节点的情况下,那么他可以做的破坏行为有一下几种:

  • 正常接收命令,但是自己不执行
    这种情况下只有自己与其他人不一致,忠诚节点之间是一致的,不会影响一致性。
  • 接收主节点的消息后,拜占庭从节点不发送消息或者给不同的节点发送不同的消息
    我们按照拜占庭从节点给不同节点发送不同消息来执行一下算法,来看一看是否会破坏一致性。拜占庭从节点不发送消息等同于宕机的情况较为简单,大家可以自己套用算法执行看看。
    算法步骤1,主节点A向从节点BCD发送命令1
  • 算法步骤2,从节点BCD收到命令后,把收到的A命令同步给其他两个从节点,其中D为拜占庭从节点,分别给B和C发送了1和0两条不同的消息

    算法步骤3,各个节点按照majority算法计算出请求的结果。如图所示,AB节点收到的都是1,所以算法执行结果一定是1,C节点收到两个1,majority算法的结果也是1,ABC三个忠诚节点之间达成了一致。

    算法的容错能力

    大部分拜占庭问题解法容错能力都是在总节点数为3f+1时,最多能在有f个拜占庭节点时达成一致性,如果拜占庭节点超过f个就可能无法达成一致,上述经典拜占庭算法也不例外。当总结点数是一个是单机问题,总结点是两个节点时,有一个拜占庭节点只剩一个忠诚节点,讨论一致性也没有意义。所以我们用反证法证明总结点数为三时,如果有一个拜占庭节点一定无法达成一致。如果主节点是拜占庭节点,我们来执行以下算法的流程:

  • 主节点A向从节点B发送命令1,向节点C发送命令0
  • 2. 从节点BC互相同步收到的命令

    3. 从节点BC各自收到一个1一个0,此时收到的结果没有一方占据多数,且各自从主节点收到的命令也不相同,此时无论如何设计majority算法都无法达成一致。
    本文只讨论了基础情况下,经典拜占庭问题算法的执行过程,容错能力的证明较为简单,主要是通过反证法来进行。假设3f+1个节点中,主节点加f个节点是拜占庭节点,一定可以破坏一致性。而在拜占庭节点小于等于f个时,可以达成一致性的证明主要是依靠数学归纳法来实现,此处就不具体讨论,有兴趣的可以看一下。

    进阶-从拜占庭容错到实用拜占庭容错

    上述经典拜占庭问题的解法可以保证在有拜占庭节点情况下的一致性,但是缺少了很多部分,导致不实用,PBFT算法就在经典算法的基础上完善了下列部分,让算法变得更实用:

  • 增加了选主和切主的策略,防范主节点作恶的同时,保证切主过程中一致性得到保持。
  • 增加了密码学验证和一些检测机制,可以及时的发现拜占庭节点。
  • 不仅仅考虑了系统的一致性,还考虑了客户端到分布式集群之间的一致性保证。
  • PBFT(Practical Byzantine Fault Tolerance)算法由Miguel Castro 和Barbara Liskov在1999年提出来的,是一个基于状态机复制的算法,设计用来解决拜占庭将军问题。算法在异步通信条件下,可以保证高可用(liveness)和安全性(safety)。

    PBFT中的一些基础概念

    我们以一个分布式文件系统为例,介绍一下实用拜占庭容错的过程。

    • client c,分布式文件系统之外的独立客户端,可以发起请求
    • request m,客户端发起的请求,包含一个操作
    • fault f,拜占庭节点的个数
    • replica i,编号为i的节点
    • n,总结点数,又是也写为3f+1
    • view v,视图编号,用于表示当前主节点。主节点编号i = v mod n,v在切主过程中单调递增
    • timestamp t,时间戳,有过期时间delay(t)用于防止重放
    • operation o,请求中的操作,例如记录一条日志
    • checkpoint,检查点用于垃圾回收,在视图切换时会详细说明
    • 高低水位,用于平衡最快节点和最慢节点之间的执行差距

    PBFT算法的基本流程

    简单来说,PBFT算法可以分为以下四步,其中步骤2和步骤3之间的部分就使用了经典拜占庭容错算法

  • 客户端向主服务器发送服务操作o的请求
  • 主服务器将请求广播到备份
  • 节点执行请求并向客户端发送回复
  • 客户端等待来自不同节点的f + 1个回复,结果相同就认为请求成功。
  • PBFT算法请求的五个阶段

    主节点不是拜占庭节点的情况下,一次正常客户端的请求分为以下五个阶段,如下图所示,通过这五个阶段可以保证在从节点是拜占庭节点时,不会影响一致性。

  • Request
    Client c向主节点发送消息m 发起一次请求。(如果请求没有被快速执行,客户端就向全部节点广播)
  • Pre-Prepare
    主节点接收到c的请求,校验合法后,为当前请求分配一个自增序号n,然后主节点向所有从节点广播,其中v是当前视图编号,m是请求本身。此处等同于经典算法的步骤1。
  • Prepare
    当从节点收到消息后会做以下校验:
    a. request和pre-prepare消息签名正确(验证来源)
    b. pre-prepare消息与自身存储的视图编号相等(保证没切主)
    c. 该节点没有接收过v、v相同,但是摘要不同的消息(验证完整性)
    d. n在高低水位之间(防止主节点破坏)
    如果校验通过,会就会进入prepare阶段。进入prepare阶段后,节点会向其他所有节点广播消息,此阶段基本等同于经典算法的步骤2。
  • Commit
    当任意节点收到2f(包括自己,不包括主节点)个经过确认的prepare消息后,节点拥有prepared certificate,进入commit阶段,进入commit阶段后,节点会执行o,然后广播。任意一个正常节点进入commit状态就可以认为该请求已经执行成功,之后无论发生什么都不应该丢失。此处基本等同于经典算法的步骤3。
  • Reply
    当节点收到2f+1条commit消息后,向客户端发送消息,客户端c收到f+1条reply消息后,即认为本次请求成功 。(有一条正常节点的commit其实就可以认为成功,为了防止f个拜占庭节点所以要等待f+1个reply消息)
  • PBFT算法的垃圾回收

    一个节点进入commit阶段执行完操作,对于其自身来说,已经达到了一致性的终点,但是此后还有reply阶段和意外切主的恢复,所以说整个请求流程的日志记录都必须保留。
    所以需要一种机制来清理数据,这里使用的就是checkpoint的方式。当连续执行了k条请求之后,在第k条请求执行完,进行全网广播说自身的checkpoint到达了k条。大家同时在k条广播,如果一个节点收到了2f+1条在k的checkpoint,就会认为之前的数据都已经不重要了,可以把之前的数据清理掉了。
    checkpoint还有一个作用就是在高低水位,高低水位是节点可以执行的操作序号的上下限。低水位就是stable checkpoint所在的位置,高水位是stable checkpoint所在位置加2k(几倍于k可配置),这样可以保证分布式系统中,执行最快的一批节点和执行最慢的一批节点之间的之间操作差不大于2k。

    PBFT算法的视图更换

    PBFT算法视图更换是为了保证系统能从主节点故障中恢复。
    先来说说视图更换的触发,这部分主要由从节点的超时机制触发。超时机制具体分为以下几个部分:

  • 客户端请求时,如果超时未收到结果就会认为主节点可能有问题,所以向所有节点广播。
  • 从节点接收到客户端请求后,会向主节点转发请求,同时启动一个定时器。如果超过设定的超时时间,还没有收到主节点发来的该请求的pre-prepare请求,就会认为主节点可能成为了拜占庭节点,所以自己就会开始进入切主流程。
  • 通过上述超时机制,可以防止拜占庭节主节点带来的影响。再来发现主节点是拜占庭节点之后的切主的流程,切主的流程分为以下几个步骤:

  • 触发超时机制的从节点会认为主节点是拜占庭节点,此时他还认可主节点但是会提议更换主节点,具体操作是向其他所有节点发起视图更换请求。其中v+1表示切换主节点会顺序后延下一个节点,n是stable checkpoint序号,C是stable checkpoint证明,P是本机存储的stable checkpoint后所有commit消息的集合(本机消息的快照,每条消息都包含签名所以可以自证)。
  • 当原主节点顺延的下一个节点v+1收到2f+1个view-change消息时,启动视图更换,广播消息。其中V是新主节点收到的2f+1个视图更换消息中P的集合,O则包含min和max,min是稳定检查点序号,max是V中最大的prepare消息序号。如果是V中存在的序号,则为此序号操作重新发送一条消息,如果V中不存在该序号对应的消息,则提交一条空消息,以此来保证视图切换过程中不丢消息。从节点在视图更换时,是可能收到重复的请求m的,所以从节点执行请求前会比较摘要,防止重复执行。
    通过new-view、view-change和重放pre-prepare三个操作,PBFT算法就可以保证在视图切换中保证一致性。
  • PBFT算法的缺点

  • 视图切换成本太大,有文章称是O(n^4)
  • 达成一致用的五个阶段复杂度是O(n^2),一般常用的节点数是4个或者7个节点,节点数较少。
  • 总结

    与区块链PoW算法的对比

    通过上边文章,我们已经基本了解了PBFT算法。最后我们再与比特币的PoW算法做个对比,让大家认识到PBFT类共识算法与PoW算法的核心区别:

  • PBFT算法参与的节点数有限,需要预先选出一批节点,在这些节点中达成共识,参与共识的节点数少。PoW算法则是每个参与份子都是节点,依靠算力在所有参与者中达成共识,参与共识的节点数多。
  • PBFT算法共识达成的速度很快,吞吐高,经过多项式次通信即可达成共识。PoW算法需要等待数十分钟,交易所在的记录彻底进入主链才可以达成共识。
  • PBFT算法达成共识只需要有限次通信,消耗小。PoW算法达成一致性需要大量算力,浪费严重不环保。
  • 最后的最后

    从经典BFT算法演进到PBFT算法,完善了许多的工程细节。根据PBFT算法,我们已经可以实现一个支持拜占庭容错的分布式文件系统。不过最近几年随着区块链的火热和密码学的发展,解决拜占庭问题的算法也在不断发展,比如facebook使用的hotstuff算法就是基于门限签名来实现的。不过万变不离其宗,了解了拜占庭问题最经典的算法,相信你在看其他拜占庭容错算法一定会更加如鱼得水。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论