PBFT原理及go语言代码实现
在pbft算法中,用R表示副本节点集合,每个副本用{0,...,|R|-1}来表示。假设|R|=3f+1,f为出错节点的数量。
副本节点通过一系列称为“视图”(view)的配置来移动。在每一个视图中,包含一个primary(主节点),其他做为backups(备份节点)。算法的步骤如下:
step1:客户端向主节点发送请求以调用服务器操作。
step2:主节点向备份节点广播该请求。
step3:副本节点执行该请求并回复到客户端。
step4:客户端收到来自不同副本节点的f+1个相同的结果。
由于pbft算法是基于状态机复制的算法,因此副本节点必须满足以下两点:
(1)它们必须是确定性的。(在给定状态与给定参数集下,执行操作后产生的结果是相同的)
(2)它们必须以相同的状态开始。
在客户端执行的操作有两个:
客户端向主节点发送请求信息
副本节点向客户端返回回复信息
“三阶段协议”
当主节点p收到客户端请求m,它将会启动三阶段协议自动广播请求信息给其他副本节点。三阶段协议分别是“pre-prepare预准备阶段”,“prepare准备阶段”,“commit确认阶段”。
预准备阶段主节点向所有的副本节点发送pre-prepare消息,<
但这个消息要满足以下四点,副本节点才会接收:
(1)请求消息m和pre-prepare消息的数字签名都是正确的,且d是消息m的摘要。
(2)它存在于当前的视图v中。
(3)该备份节点从未在当前视图v中接收过包含不同摘要的序号为n的预准备消息。
(4)预准备消息中的序号n位于消息量下限h和上限H之间。
当备份节点i接收了预准备消息,它将进入准备阶段,将准备消息
同上,prepare消息满足以下三个要求,才会被其他副本节点接收:
(1)准备消息的签名是正确的。
(2)视图编号与副本节点当前的视图编号一致。
(3)准备消息的序列号位于消息量下限h和上限H之间。
准备阶段完成的标志,有以下内容插入到日志中就代表完成:
(1)请求消息m
(2)在视图v中,序列号为n的请求m的预准备消息
(3)2f个与预准备消息匹配的且来自不同备份节点的准备消息。(检查视图编号、序列号、消息摘要,如果都一致则代表匹配)
在PBFT算法中,pre-prepare和prepare阶段保证了非故障副本节点对在同一个视图中的请求排序达成一致。
准备阶段完成进入确认阶段,同上,副本节点接收确认信息
(1)确认消息的签名是正确的。
(2)视图编号与副本节点当前的视图编号一致。
(3)确认消息的序列号位于消息量下限h和上限H之间。
为了保证系统的安全性,副本节点在删除自己的消息日志前,需确保至少f+1个正常副本节点执行了消息所对应的请求,并且在视图变更时向其他副本节点证明。另外,如果某些副本节点错过了部分消息,且这些消息已经被非故障副本节点删除了,则需要通过转移全部或部分服务状态来更新,因此在执行每个阶段后,副本节点需要对状态的正确性进行证明。
因此在PBFT中定义了检查点协议checkpoint,如果已经进行证明了,则转换成stable checkout 。当一个副本节点i生成了"checkpoint",它向其他副本节点广播
另外,checkpoint协议还可以用来更新水线的高低值h和H,即代表了可以被接收的消息量大小。
视图变更机制保证了系统的活性。
当在视图v中,副本节点i计时器超时后,则会触发view-change,视图由v变成v+1,并且停止接收消息(检查点协议、视图变更、新消息视图除外),并向所有副本节点播报
在PBFT中,视图变更和垃圾回收机制保证了算法的安全性safety和活性liveness。
首先定义消息类型结构体
//请求消息结构体
type RequestMsg struct{
TimeStamp int64 'json:"timestamp"'
ClientID string 'json:"clientID"'
Operation string 'json:"operation"'
SequenceID int64 'json:"sequenceID"'
}
//回复消息结构体
type ReplyMsg struct{
ViewID int64 'json:"viewID"'
TimeStamp int64 'json:"timestamp"'
ClientID string 'json:"clientID"'
NodeID string 'json:"nodeID"'
Result string 'json:"result"'
}
//预准备消息结构体
type PrePrepareMsg struct{
ViewID int64 'json:"viewID"'
SequenceID int64 'json:"sequenceID"'
Digest string 'json:"digest"'
RequestMsg *RequestMsg 'json:"requestMsg"'
}
//投票消息结构体
type PrePrepareMsg struct{
ViewID int64 'json:"viewID"'
SequenceID int64 'json:"sequenceID"'
Digest string 'json:"digest"'
NodeID int64 'json:"nodeID"'
MsgType
}
//常量
type MsgType int
const{
PrepareMsg MsgType=iota
CommitMsg
}
所用到的接口
type PBFT interface {
startConsensus(request *RequestMsg)(*PrePrepareMsg,error)
PrePrepare(prePrepareMsg *PrePrepareMsg)(*VoteMsg,error)
Prepare(prepareMsg *VoteMsg)(*VoteMsg,error)
Commit(commitMsg *VoteMsg)(*ReplyMsg,*RequestMsg,error)
}
引入的包包括:encoding/json、errors、time、fmt
以下是算法实现的具体代码:
func (state *State) StartConsensus(request *RequestMsg)(*PrePrepareMsg,error){
//主节点选取
//生成唯一标识id
sequenceID:=time.Now().UnixNano()
if state.LastSequenceID!=-1{
for state.LastSequenceID>=sequenceID{
sequenceID+=1
}
}
request.SequenceID=sequenceID
//将ReqMsg保存到日志中
state.MsgLogs.ReqMsg=request
//请求消息的摘要
digest,err:=digest(request)
if err!=nil{
fm.Println(err)
return nil,err
}
//进入预准备阶段
state.CurrentStage=PrePrepared
return &PrePrepareMsg{
ViewID:state.ViewID,
SequenceID:sequenceID,
Digest:digest,
RequestMsg:request,
},nil
}
//预准备阶段
func (state *State) PrePrepare(prePrepareMsg *PrePrepareMsg)(*VoteMsg,error){
//将预准备消息保存到日志中
state.MsgLogs.ReqMsg=prePrepareMsg.RequestMsg
//验证v,n,d是否正确
if !state.verifyMsg(prePrepareMsg.ViewID,prePrepareMsg.SequenceID,prePrepareMsg.Digest){
return nil.errors.New("pre-prepare messages is corrupted")
}
//进入预准备阶段
state.CurrentStage=PrePrepared
return &VoteMsg{
ViewID: state.ViewID,
SequenceID: prePrepareMsg.SequenceID,
Digest: prePrepareMsg.Digest,
MsgType: PrepareMsg,
}, nil
}
//准备阶段
func (state *State) Prepare(prepareMsg *VoteMsg) (*VoteMsg, error){
if !state.verifyMsg(prepareMsg.ViewID, prepareMsg.SequenceID, prepareMsg.Digest) {
return nil, errors.New("prepare message is corrupted")
}
// 将准备消息添加到日志中
state.MsgLogs.PrepareMsgs[prepareMsg.NodeID] = prepareMsg
//输出当前投票状态
fmt.Printf("[Prepare-Vote]: %d\n", len(state.MsgLogs.PrepareMsgs))
if state.prepared() {
// 进入准备阶段
state.CurrentStage = Prepared
return &VoteMsg{
ViewID: state.ViewID,
SequenceID: prepareMsg.SequenceID,
Digest: prepareMsg.Digest,
MsgType: CommitMsg,
}, nil
}
return nil, nil
}
//确认阶段
func (state *State) Commit(commitMsg *VoteMsg) (*ReplyMsg, *RequestMsg, error) {
if !state.verifyMsg(commitMsg.ViewID, commitMsg.SequenceID, commitMsg.Digest) {
return nil, nil, errors.New("commit message is corrupted")
}
// 将确认信息保存到日志中
state.MsgLogs.CommitMsgs[commitMsg.NodeID] = commitMsg
// 输出当前状态
fmt.Printf("[Commit-Vote]: %d\n", len(state.MsgLogs.CommitMsgs))
if state.committed() {
result := "Executed"
//进入确认阶段
state.CurrentStage = Committed
return &ReplyMsg{
ViewID: state.ViewID,
Timestamp: state.MsgLogs.ReqMsg.Timestamp,
ClientID: state.MsgLogs.ReqMsg.ClientID,
Result: result,
}, state.MsgLogs.ReqMsg, nil
}
return nil, nil, nil
}
//视图切换
func (state *State) verifyMsg(viewID int64, sequenceID int64, digestGot string) bool {
// 视图错误
if state.ViewID != viewID {
return false
}
// 主节点是否发生故障
if state.LastSequenceID != -1 {
if state.LastSequenceID >= sequenceID {
return false
}
}
digest, err := digest(state.MsgLogs.ReqMsg)
if err != nil {
fmt.Println(err)
return false
}
if digestGot != digest {
return false
}
return true
}
func (state *State) prepared() bool {
if state.MsgLogs.ReqMsg == nil {
return false
}
if len(state.MsgLogs.PrepareMsgs) < 2*f {
return false
}
return true
}
func (state *State) committed() bool {
if !state.prepared() {
return false
}
if len(state.MsgLogs.CommitMsgs) < 2*f {
return false
}
return true
}
func digest(object interface{
}) (string, error) {
msg, err := json.Marshal(object)
if err != nil {
return "", err
}
return Hash(msg), nil
}
PBFT原文链接: PBFT.