1 容错类型
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。
2 三阶段消息
3阶段消息是:Pre-prepare、Prepare和Commit。每个消息都会包含数字签名,证明消息的发送者,以及消息类型,下文中会省略。
Pre-prepare消息由主节点发出,包含:
- 当前view:v
- 主节点分配给请求的序号n
- 请求的摘要d
- 请求本身m
- 务必记牢,m、v、n、d,后面会使用缩写。
Prepare是副本节点收到Pre-prepare消息后,做出的响应,发送给所有副本节点,包含:
Prepared状态:副本i有Pre-prepare消息,且收到2f个有效的Prepare消息。
副本i达到Prepared状态,可以发送Commit消息,Commit消息的内容和Prepare消息内容相同,但消息类型和数字签名是不同的,所以可以区分。
m可以使用d代替,所以Prepare和Commit消息使用d代替m,来节省通信量
3 为什么不能只有前2个阶段消息
这个问题的等价问题是:为什么Pre-prepare和Prepare消息,不能让非拜占庭节点达成一致?
Pre-prepare消息的目的是,主节点为请求m,分配了视图v和序号n,让至少f+1个非拜占庭节点对这个分配组合<m, v, n>达成一致,并且不存在<m’, v, n>,即不存在有2个消息使用同一个v和n的情况。
Prepared状态可以证明非拜占庭节点在只有请求m使用<v, n>上达成一致。主节点本身是认可<m, v, n>的,所以副本只需要收集2f个Prepare消息,而不是2f+1个Prepare消息,就可以计算出至少f个副本节点是非拜占庭节点,它们认可m使用<v, n>,并且没有另外1个消息可以使用<v, n>。
既然1个<v, n>只能对应1个请求m了,达到Prepared状态后,副本i执行请求m,不就达成一致了么?
并不能。Prepared是一个局部视角,不是全局一致,即副本i看到了非拜占庭节点认可了<m, v, n>,但整个系统包含3f+1个节点,异步的系统中,存在丢包、延时、拜占庭节点故意向部分节点发送Prepare等拜占庭行文,副本i无法确定,其他副本也达到Prepared状态。如果少于f个副本成为Prepared状态,然后执行了请求m,系统就出现了不一致。
所以,前2个阶段的消息,并不能让非拜占庭节点达成一致。
4 View Changes切换
View Changes的战略是:当副本节点怀疑主节点无法让请求达成一致时,发起视图切换,新的主节点收集当前视图中已经Prepared,但未Committed的请求,传递到下一个视图中,所有非拜占庭节点基于以上请求,会达到一个新的、一致的状态。然后,正常运行3阶段消息协议。
具体有4个路径:
- 正常阶段定时器超时,代表一定时间内无法完成Pre-prepare -> Prepare -> Commit
- View Changes阶段定时器超时,代表一定时间内无法完成正在进行的View Change
- 定时器未超时,但有效的view-change消息数量达到f+1个,代表当前已经有f+1个非拜占庭节点发起了新的视图切换,本节点为了不落后,不等待超时而进入视图切换
- new-view消息不合法,代表当前View Changes阶段的主节点为拜占庭节点
更换主节点
当主节点出现故障,就会触发ViewChange + 1 事件viewchange会有三个阶段,分别是
-
view-change , 从节点认为主节点有问题时,会向其它节点发送view-change 消息,当前存活的节点编号最小的节点将成为新的主节点。
-
view-change-ack ,新的主节点收到 2f 个view-change 消息,则证明 view-change有效,并广播new-view 消息。
-
new-view ,新主节点会继续执行上个视图未处理完的请求,其它节点验证new-view 消息通过后,就会处理执行pbft 过程并进入ViewChange + 1。
发生view转换时,需要的保证的是:如果视图转换之前的消息m被分配了序号n, 并且达到了prepared状态,那么在视图转换之后,该消息也必须被分配序号n(safety特性)。因为达到prepared状态以后,就有可能存在某个节点commit-local。要保证对于m的commit-local,在视图转换之后,其他节点的commit-local依然是一样的序号。
参考
https://lessisbetter.site/2020/03/22/why-pbft-needs-viewchange/
其他区块链技术请关注公众号(区块链技术空间)