PBFT

节点分类

  • Leader/Primary: 共识节点,负责将交易打包成区块和区块共识,每轮共识过程中有且仅有一个leader,为了防止leader伪造区块,每轮PBFT共识后,均会切换leader;
  • Replica: 副本节点,负责区块共识,每轮共识过程中有多个Replica节点,每个Replica节点的处理过程类似;
  • Observer: 观察者节点,负责从共识节点或副本节点获取最新区块,执行并验证区块执行结果后,将产生的区块上链。

节点ID

为了防止节点作恶,PBFT共识过程中每个共识节点均对其发送的消息进行签名,对收到的消息包进行验签名,因此每个节点均维护一份公私钥对,私钥用于对发送的消息进行签名,公钥作为节点ID,用于标识和验签。
共识节点签名公钥和共识节点唯一标识, 一般是64字节二进制串,其他节点使用消息包发送者的节点ID对消息包进行验签。

节点索引

每个共识节点ID在这个公共节点ID列表中的位置

视图

PBFT共识算法使用视图view记录每个节点的共识状态,相同视图节点维护相同的Leader和Replicas节点列表。当Leader出现故障,会发生视图切换,若视图切换成功(至少2f+1个节点达到相同视图),则根据新的视图选出新leader,新leader开始出块,否则继续进行视图切换,直至全网大部分节点(大于等于2f+1)达到一致视图。

核心流程

PBFT共识主要包括Pre-prepare、Prepare和Commit三个阶段:

1. Pre-prepare:负责执行区块,产生签名包,并将签名包广播给所有共识节点;

2. Prepare:负责收集签名包,某节点收集满2f+1的签名包后,表明自身达到可以提交区块的状态,开始广播Commit包;

3. Commit:负责收集Commit包,某节点收集满2f+1的Commit包后,直接将本地缓存的最新区块提交到数据库。
PBFT 学习记录

view change

假设 primary 是个作恶节点,它向 1/3 个正常节点发送指令A,向另外 1/3 发送指令B,其余的发送指令C,会有什么后果?肯定是正常节点永远都无法达成共识

所以 pBFT 引入了超时机制保证节点不会无期限的等待某个 request 达成共识,如果规定的时间内 request 无法达成共识的话,该节点会发起 view change。当副本收到请求时,就启动一个计时器,如果这个时候刚好有定时器在运行就重置(reset)定时器

checkpoint

pBFT 为了保证可靠性,三阶段中所有认可的消息都需要持久化到 log 中,log 会越来越大,那就需要裁减,checkpoint 就闪亮登场了。对系统状态定时生成 :

checkpoint<n, d, i>

n 是 sequence number,d 是摘要,生成规则自定义:比如每100个 request 生成一个 checkpoint,也就意味着会有很多 checkpoint,所以还需要对 checkpoint 进行清理

节点每生成一个 checkpoint 都会将该 checkpoint 广播出去,跟上述描述的类似,节点如果收到了同样的 2f+1 个 checkpoint 的话,说明这个 checkpoint 达成共识,即标记该 checkpoint 是一个 stable checkpoint,并且将之前的 checkpoint 和该 checkpoint 之前的 log 都删除(通过 sequence number 来区别 log 和 checkpoint 的先后顺序,所以笔者认为 sequence number 是全局有序的)。

view-change

view change request:<view-change, v+1, n, C, P, i>

i:replica 的下标

n:replica i 目前 stable checkpoint(暂称:s)的 sequence number

C:验证 checkpoint s 正确性的 2f+1 个checkpoint消息(checkpoint验证的时候接收到的消息)

P:一个集合,它的每个元素可以认为是一个对象,比如 Pm 对应就是 request m 相关的信息,Pm 中记录的是:一个 pre-prepare 消息、2f 个验证该 pre-prepare 的 prepare 消息。且只有 sequence number 大于 n 的 request 才能被记录在 P 中。换句话说:P 中记录的是 sequence number 大于 n 的且 prepare 阶段验证通过的 request。(这些 request 需要继续执行来决定是否可以被共识,思考一下:为啥满足接收到 2f 个 prepare 的消息才有资格放在 P 中?)

P是已经Prepared消息的信息集合:消息既然已经Prepared,说明至少2f+1的节点拥有了消息,并且认可<view, n, d>,即为消息分配的view和序号,只是还差一步commit阶段就可以完成一致性确认。P中包含的就是已经达到Prepared的消息的摘要d,无需包含完整的请求消息。新的view中,这些请求会使用老的序号n,而无需分配新的序号Q是已经Pre-prepared消息的信息集合,主节点已经发送Pre-prepare或副本节点i为请求已经发送Prepare消息,证明该节点认可<n, d, v>

当新的 primary 收到了 2f 个 v+1 的 view change request 时,它会广播一个<new-view, v+1, V, O>消息,

V:这个是大写,它包含的是:刚才说到的 (2f 个 view change request) + (它自己的 view change request),也就是 2f+1 个 request

O:O 也比较讲究,首先新的 primary 先从刚才 2f 个 view change requests 中的 checkpoint 中挑出来最小的 sequence number --> min-s,然后再从 view change requests 中的 P 集合中挑出来最大的 sequence number --> max-s。**然后 primary 给每一个在 min-s 和 max-s 之间的 request,在 v+1 的视图下创建一个新的 pre-prepare,**但是 P 集合在 min-s 和 max-s 之间的不一定所有高度都会有 request,此时又有两种情况:1. 对于存在于这两个高度之间的 request,选取 v 值最大的 request(因为可同一个 sequence number 可能存在多个 request),直接对其创建<pre-prepare, v+1, n, d>消息。2. 对于不存在的,直接创建一个<pre-prepare, v+1, n, d-null>消息。

注:有点绕,看不懂的多看看论文,疑问:为什么一定要将 min-s 和 max-s 之间的 request 用 null 补全,不补全会有什么后果?

确定stable checkpoint之前的消息在视图切换的过程中不会丢,但是当前检查点之后,下一个检查点之前的已经PREPARE可能会被丢弃,在视图切换到v+1后,Pbft会把旧视图中已经PREPARE的消息变为PRE-PREPARE然后新广播。

replicas 收到 primary 的广播后会用同样的方式验证一下这个 request 的正确性,更新视图至 v+1,然后为 min-s 和 max-s 之间的 request 广播 prepare 消息,这样确保在 v+1 视图下,之前未处理完成的 request 能够被正常处理(因为算法规定 v+1 不应该处理小于该视图的任何 request)。同样 replica 通过保留的信息判断该 request 是否已经处理过,如果已经 replica 已经将该 request 的 reply 回复给 client 的话,replica 会直接跳过这个 request。

上述流程相对比较复杂,论文中给出了推导过程并证明了正确性,后续还介绍了一些优化的细节,并且采用 pBFT 算法实现了一个文件系统,测试表明和传统的文件系统相比性能几乎没有下降。本文略过了很多论文的细节,想要对 pBFT 有更深入了解的话还是建议参看论文。

4种条件下触发View Changes

View Change的核心因素只有一个:怀疑当前的主节点在有限的时间内,无法达成一致。

具体有4个路径:

  1. 正常阶段定时器超时,代表一定时间内无法完成Pre-prepare -> Prepare -> Commit View
  2. Changes阶段定时器超时,代表一定时间内无法完成正在进行的View Change
  3. 定时器未超时,但有效的view-change消息数量达到f+1个,代表当前已经有f+1个非拜占庭节点发起了新的视图切换,本节点为了不落后,不等待超时而进入视图切换
  4. new-view消息不合法,代表当前View Changes阶段的主节点为拜占庭节点

参考:
奔跑的番茄酱
清源的区块链实验室
豋链社区
大彬

相关文章: