PBFT
n>3f的由来:
- 系统中存在n个节点,其中f个为拜占庭节点
- 由于f个节点呈现任意行为,包括同时宕机,系统需要保证在收集到n-f个意见时可做出决策
- 但收集到的n-f条消息并无法识别哪些为拜占庭节点,根据多数原则,收集到的正常节点意见数应大于拜占庭节点意见数,所以推出公式:(n-f)-f > f,即:n>3f
4 算法整体流程
4.1 客户端
算法流程大致如下:
- 客户端请求主节点执行一些操作(发送请求)
- 主节点将请求多播到所有备份节点
- 各节点执请求,响应客户端
- 客户端等待f+1个不同节点响应的相同结果
4.2 共识的三个阶段
- <m>σi表示i节点对消息m进行签名
pre-prepare 预准备阶段
主节点接收到客户端请求后,为请求分配一个序列号 n
,将预准备消息
(pre-prepare message)多播给所有备份节点,并将其追加到日志中。
预准备消息
:<<PRE-PREPARE,v,n,d>σp,m>
- v:视图号
- d:m的摘要
- n:视图中的消息序列号
- m:客户端的请求消息
预准备消息
被备份节点接收需满足:
- 请求的签名以及
预准备消息
正确,d为m的摘要 - 视图v为当前视图号
- 在视图v中还没有接收到过序列号为n包含不同摘要d的
预准备消息
- 序列号在低水线h(low water mark)和高水线H(high water mark)之间
准备消息、提交消息的接收有类同的条件。
prepare 准备阶段
如果节点i接收到经校验无误的预准备消息
,就将准备消息
多播给所有节点进入准备阶段,并将预准备消息
和准备消息
追加到日志中。否则不作任何处理。
准备消息
:<PREPARE,v,n,d,i>σi
谓词(predicate) prepared(m,v,n,i)
为真,当且仅当节点i在日志中插入了:
- 请求消息m
- m的
预准备消息
<<PRE-PREPARE,v,n,d>σp,m> - 与
预准备消息
匹配的2f个节点(包括自己)的准备消息
预准备与准备阶段保证了所有正常节点在特定视图中请求的排序一致
commit 提交阶段
当谓词 prepared
为真时,节点将提交消息
多播给其他节点
提交消息
:<COMMIT,v,n,d,i>σi
谓词 committed(m,v,n)
为真,当且仅当某f+1个节点中的谓词 prepared
为真
谓词 committed-local(m,v,n,i)
为真,当且仅当谓词prepared
为真,且收到了来自不同节点(通常包括自己)2f+1个与m的预准备消息
匹配的提交消息
。即 v, n, d相同
在谓词 committed-local(m,v,n,i)
为真之后,节点执行m请求,i的状态反映了较小序列号请求线性执行的结果。
算法流程示例
3个正常节点,1个离线节点
- A节点接收到请求后,将<<PRE-PREPARE,v,n,d>σp,m>(省略为[P-P])多播给所有备份节点
- 各备份节点校验正确后,进入准备阶段,将<PREPARE,v,n,d,i>σi(省略为[P])多播给所有节点
- 各节点谓词
prepared
为真后,将<COMMIT,v,n,d,i>σi(省略为[C])多播给所有节点。谓词prepared
的意义在于节点可借此判断自己是否收与到了正确的信息,与网络中的信息一致。但并不能判断其他节点是否也正确收到了消息(因为可能存在拜占庭节点向不同的节点发送不同的消息)。 - 各节点谓词
committed-local
为真后,执行请求,并向客户端响应执行结果。谓词committed-local
的意义在于,可以确认 2f+1个(即n-f,除拜占庭节点外的所有节点)已经收到了正确的消息,因此可以执行请求内容。
需要注意的是
- 第一阶段只有主节点发送消息,第二阶段主节点不发送消息
- 节点会持有自己向其他节点多播的消息,例如第二阶段后B节点只收到了一条(2f-1)[P],加上自己持有的[P]即为两条(2f)
3个正常节点,主节点为拜占庭节点
主节点A向BC发送了<<PRE-PREPARE,v,n,d>σp,m>(省略为[P-P],相应的PREPARE消息为[P]),向D发送了<<PRE-PREPARE,v,n,d>σp,m'>(省略为[P-P]',相应的PREPARE消息为[P]')
在prepare阶段后,BC节点的谓词prepared
为真,多播提交消息
,D节点的谓词prepared
为假,不进行多播。在commit阶段后,所有的节点谓词commit-local
都为假,不对请求进行执行。
4.3 垃圾回收
从安全性角度来考虑,在至少f+1个正常节点已经执行了相关请求,且能够向其他节点在视图更换中证明之前,相关消息都应该保留。另外,如果一些节点缺失的消息被所有节点都丢弃了,它需要向其他节点获取部分或全部状态数据进行更新。因此,节点需要证明状态的正确性。
检查点
在每次操作后做一次状态证明将会占用较多的算力。解决方案是当序列号能够整除一些常数(如100)的请求被执行后。我们称这些特殊请求被执行后产生的状态称为检查点(checkpoint),同时称有正确性证明的检查点为稳定检查点(stable checkpoint)。
稳定检查点的正确性证明的收集过程如下。当节点i产生了一个检查点,它向其余节点多播检查点消息。每个节点将接收到的检查点消息存进日志,直到拥有2f+1个来自不同节点的相同检查点消息(包括自己)。这2f+1条检查点消息则是检查点的正确性证明。
检查点消息:<CHECKPOINT,n,d,i>σi
- n: 最新的被执行的请求的序列号
- d: 检查点状态的摘要
检查点协议用于优化高低水线机制。低水线h即最新的稳定检查点序列号。高水线 H=h+k,k值能够保证节点不至于由于等待检查点稳定性证明而停止即可。例如:若每100个请求建立一个检查点,则k可以为200。
消息日志清理
当节点具有一个稳定检查点时,则可以将所有序列号小于检查点序列号的预准备、准备以及提交消息清除掉;同时它将清除所有以前的检查点以及检查点消息。
4.4 视图更换
视图更换协议保了证系统的活性,即使主节点异常也能够继续推进。通过超时机制触发的视图更换避免了备份节点的无止尽等待。
备份节点收到一个未执行的请求则保持等待主节点多播请求的状态。在收到一个请求后启动计时器(若计时器未运行)。在节点不再等待执行请求时停止计时器,在重新等待其他请求时启动计时器。
备份节点等待超时,请求视图更换
当处于视图v中的备份节点i的计时器超时,他将开始视图更换进入到v+1视图。他将仅接收 checkpoint, view-chagne, new-view 类型的消息,并将视图更换消息
VIEW-CHANGE多播给所有节点。
视图更换消息
:<VIEW-CHANGE,v+1,n,C,P,i>σi
- n: 最新的稳定检查点s的序列号
- C: 2f+1个证明稳定检查点s合法性的checkpoint消息
- P: Pm的集合,Pm包含了m的合法
预准备消息
,2f个合法的准备消息
,另外m是在节点i视图v中序列号大于n的谓词prepared
为真的请求消息。
主节点通知备份节点视图更换
当新视图v+1的主节点p收到了2f个合法的视图更换消息
,则向备份节点多播新视图消息
新视图消息
: <NEW-VIEW,v+1,V,O>σp
- V: 包含了主节点收到的和多播的(或即将多播的)合法
视图更换消息
- O: 不带请求消息m本身的
预准备消息
主节点按如下方式处理O集合中的预准备消息
:
- 主节点决定序列号min-s和max-s,min-s为V中最近的稳定检查点的序列号,max-s为V中所有
准备消息
里面最大的序列号 - 主节点为所有min-s与max-s之间的序列号创造一个
预准备消息
。有两种情况:(1) V中一些视图更换消息
的P中至少有一个集合Pm序列号为n;(2)没有1中描述的集合。对于第一种情况,主节点创建一个新的预准备消息
<PRE-PREPARE,v+1,n,d>σp,这些消息具有最大的视图号;对于第二种情况,主节点创建一个新的空预准备消息
<PRE-PREPARE,v+1,n,dnull>σp
视图更换消息
的P中包括了所有准备了但还未提交的请求,新视图消息
的O中包含了需要主节点多播的预准备消息
状态重做
主节点
主节点将O中的消息添加到日志中。如果min-s比主节点最新的稳定检查点大,则主节点将此min-s对应检查点的稳定性证明插入到日志中,并进行垃圾回收。然后切换到视图v+1,并可接收在视图v+1中的请求消息。
备份节点
备份节点收到新视图消息
在进入视图v+1前,它需要验证:
- 消息有合法签名
- 它包含的
视图更换消息
是合法的 - 集合O正确。对集合O的验证方式是通过和主节点做一样的计算产生集合O
验证通过后,他将如主节点那样将新消息追加到日志中,为所有O中的消息新建一条准备消息
,插入日志并多播。之后便可切换到v+1视图
状态滞后的处理
可能存在丢失一些请求消息或稳定检查点。它可以从其他节点获取缺失信息。如缺失检查点s的节点i可以从其他节点处获取,只要其正确性可以从V集合中验证即可。
可以通过将状态切分,并用序列号打上标记的方式避免发送整个检查点。要将一个副本状态更新,将过期的状态部分更新即可。
4.5 正确性
4.5.1 一致性 Safety
同一视图中的一致性
如前文讨论过的,因为所有的正常节点在本地提交(commit locally)的请求序列号上达成共识,在同一视图中算法可以保证一致性。
4.2中我们展示了对于正常节点, prepared(m,v,n,i)
为真,则 prepared(m',v,n,i)
为假。即保证了同一个序列号不会分配给两个不同的请求。
不同视图中的一致性
视图转换协议保证了正常节点同样能在不同视图上提交的请求序列号达成共识。
请求m在某正常节点本地被提交需要谓词 commited(m,v,n)
为真,这意味着存在一个集合R1有至少f+1个正常节点谓词 prepared(m,v,n,i)
为真。
正常节点不会接收大于当前视图v的预准备消息
p-p(v'),但任意正确的新视图消息
(v')包含了2f+1个正常节点(视作集合R2)的视图转换消息。
因为系统中共3f+1个节点,所以有R1, R2至少存在一个正常节点k的非空交集。k的视图转换消息保证了上一个视图中准备就绪(prepared
)的请求m传递到了本视图中,除非新视图消息
包含了带有稳定检查点,且检查点的序列号s大于m的序列号n。对于前一种情况,算法将在新视图中将三个阶段重做。这避免了在以前视图中已用的序列号上提交一个不同的请求。
4.5.2 活性 Liveness
为了提供活性,当无法正确执行请求的时候,节点必须切换到新的视图。但当有至少2f+1个正常节点在相同的视图中时,需要尽量避免视图更换,并且确保视图持续时间指数增长知道某些请求操作被执行。我们通过三个途径来达到目的:
第一,为了避免视图更换太快,多播消息切换到视图v+1的节点需要等待2f+1个相同的视图更换消息
,然后启动一个时长T之后超时的计时器。如果在收到v+1的新视图消息
或在新视图中执行一个新的请求消息之前计时器超时,它则会开始尝试更换到v+2,但将会将等待时间加倍(2T)。
第二,如果一个节点收到了比当前视图号v大的f+1个合法的视图更换消息
,它将取其中最小的视图号发送一个视图更换消息
第三,恶意节点并不能通过频繁的视图切换阻碍共识进程。因为需要至少f+1个节点发送了视图更换消息
,视图更换才可能发生。
文献
Miguel Castro and Barbara Liskov. “Practical Byzantine fault tolerance” Operating Systems Design and Implementation (1999).
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。