PBFT(实用拜占庭容错算法)原理及代码分析(go语言)

PBFT原理及go语言代码实现

PBFT

前提

客户端

正常情况下的操作

预准备阶段

准备阶段

确认阶段

垃圾回收

视图切换

算法的安全性与活性

前提

在pbft算法中,用R表示副本节点集合,每个副本用{0,...,|R|-1}来表示。假设|R|=3f+1,f为出错节点的数量。
副本节点通过一系列称为“视图”(view)的配置来移动。在每一个视图中,包含一个primary(主节点),其他做为backups(备份节点)。算法的步骤如下:
step1:客户端向主节点发送请求以调用服务器操作。
step2:主节点向备份节点广播该请求。
step3:副本节点执行该请求并回复到客户端。
step4:客户端收到来自不同副本节点的f+1个相同的结果。
由于pbft算法是基于状态机复制的算法,因此副本节点必须满足以下两点:
(1)它们必须是确定性的。(在给定状态与给定参数集下,执行操作后产生的结果是相同的)
(2)它们必须以相同的状态开始。

客户端

在客户端执行的操作有两个:
客户端向主节点发送请求信息<REQUEST,o,t,c> 其中,o为请求状态复制机的操作,t为时间戳。
副本节点向客户端返回回复信息<REPLY,v,t,c,i,r> 其中,v为当前的视图编号,t为时间戳,i为副本节点编号,r为执行请求后的操作结果。

正常情况下的操作

“三阶段协议”
当主节点p收到客户端请求m,它将会启动三阶段协议自动广播请求信息给其他副本节点。三阶段协议分别是“pre-prepare预准备阶段”“prepare准备阶段”“commit确认阶段”

pre-prepare预准备阶段

预准备阶段主节点向所有的副本节点发送pre-prepare消息,<<PRE-PREPARE,v,n,d>,m>
但这个消息要满足以下四点,副本节点才会接收:
(1)请求消息m和pre-prepare消息的数字签名都是正确的,且d是消息m的摘要。
(2)它存在于当前的视图v中。
(3)该备份节点从未在当前视图v中接收过包含不同摘要的序号为n的预准备消息。
(4)预准备消息中的序号n位于消息量下限h和上限H之间。

prepare准备阶段

当备份节点i接收了预准备消息,它将进入准备阶段,将准备消息<PREPARE,v,n,d,i>发送给其他副本节点,并将预准备消息和准备消息都写入消息日志中。
同上,prepare消息满足以下三个要求,才会被其他副本节点接收:
(1)准备消息的签名是正确的。
(2)视图编号与副本节点当前的视图编号一致。
(3)准备消息的序列号位于消息量下限h和上限H之间。
准备阶段完成的标志,有以下内容插入到日志中就代表完成:
(1)请求消息m
(2)在视图v中,序列号为n的请求m的预准备消息
(3)2f个与预准备消息匹配的且来自不同备份节点的准备消息。(检查视图编号、序列号、消息摘要,如果都一致则代表匹配)

在PBFT算法中,pre-prepare和prepare阶段保证了非故障副本节点对在同一个视图中的请求排序达成一致。

commit确认阶段

准备阶段完成进入确认阶段,同上,副本节点接收确认信息<COMMIT,v,n,D(m),i>,确认消息也要满足以下条件:
(1)确认消息的签名是正确的。
(2)视图编号与副本节点当前的视图编号一致。
(3)确认消息的序列号位于消息量下限h和上限H之间。
PBFT算法流程图

垃圾回收

为了保证系统的安全性,副本节点在删除自己的消息日志前,需确保至少f+1个正常副本节点执行了消息所对应的请求,并且在视图变更时向其他副本节点证明。另外,如果某些副本节点错过了部分消息,且这些消息已经被非故障副本节点删除了,则需要通过转移全部或部分服务状态来更新,因此在执行每个阶段后,副本节点需要对状态的正确性进行证明。
因此在PBFT中定义了检查点协议checkpoint,如果已经进行证明了,则转换成stable checkout 。当一个副本节点i生成了"checkpoint",它向其他副本节点广播<CHECKPOINT,n,d,i>消息。每个副本节点在日志中收集checkpoint信息,直到收集到2f+1个来自不同副本节点的具有相同序列号n和摘要d的检查点消息,就代表已经进行证明了。
另外,checkpoint协议还可以用来更新水线的高低值h和H,即代表了可以被接收的消息量大小。

视图变更

视图变更机制保证了系统的活性。
当在视图v中,副本节点i计时器超时后,则会触发view-change,视图由v变成v+1,并且停止接收消息(检查点协议、视图变更、新消息视图除外),并向所有副本节点播报<VIEW-CHANGE,v+1,n,c,p,i>,当视图编号为v+1中的主节点从其他副本节点收到2f个有效的view-change消息后,则广播<NEW-VIEW,v+1,v,o>。其中v是有效的VIEW-CHANGE消息集合,o是主节点重新发起的未经完成的pre-prepare消息集合,主节点将o集合中的消息放入日志中。接下来就进入到视图v+1中,并开始o集合中的pre-prepare及后续处理流程。

算法的正确性

在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.

  • 3
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
拜占庭将军问题(Byzantine Generals Problem)是指在分布式系统中,存在多个节点(将军)之间需要达成一致的决策,但其中部分节点可能是不可信的(可能发送错误信息或者故意篡改信息)。该问题要求设计一种算法,使得系统能够在存在不可信节点的情况下仍然能够达成一致的决策。 拜占庭容错算法(Practical Byzantine Fault Tolerance, PBFT)是一种解决拜占庭将军问题的算法。简要流程如下: 1. 提案阶段:一个主节点(提议者)向其他节点发送提案消息,包含了要达成一致的决策内容。 2. 预备阶段:每个接收到提案的节点会将提案广播给其他节点,并等待其他节点的响应。在预备阶段,每个节点会收集到大多数节点的响应。 3. 收集阶段:在预备阶段后,每个节点会将收到的消息汇总,并向其他节点发送自己收集到的消息。 4. 决策阶段:每个节点根据收集到的消息进行判断,如果收集到的消息中大多数节点达成了一致的决策,则该节点也接受该决策。 5. 完成阶段:一旦节点接受了某个决策,它会向其他节点发送接受消息,以便通知其他节点它的决策。 6. 完成确认:节点接收到其他节点的完成消息后,如果收到了大多数节点的完成消息,则认为整个系统达成了一致的决策。 PBFT 算法通过多个阶段的消息交互和多数节点的确认来解决拜占庭将军问题,保证了节点之间的一致性。该算法适用于拜占庭容错要求较高的分布式系统,但也会带来一定的性能开销。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值