pbft 总结

本贴最后更新于 2339 天前,其中的信息可能已经事过景迁

pbft 的启动及配置

当节点是 vp 时,server 方法 -> GetEngine 设置 engine.consenter 为 obcBatch,obcBatch 有一个成员 externalEventReceiver 实现了 RecvMsg
方法。
id:由 getValidatorID 可得 (vpX..id 就为 X)

当收到消息时(和 noops 消息类型一样),调用 externalEventReceiver.RecvMsg:

// RecvMsg is called by the stack when a new message is received
func (eer *externalEventReceiver) RecvMsg(ocMsg *pb.Message, senderHandle *pb.PeerID) error {
	eer.manager.Queue() <- batchMessageEvent{
		msg:    ocMsg,
		sender: senderHandle,
	}
	return nil
}

由于 externalEventReceiver 设置的 Receiver 是 obcbatch,所以外面所有事件交由 obcbatch.ProcessEvent 处理,obcbatch.ProcessEvent 转
发给 obcbatch.processMessage,然后调用 op.submitToLeader(req)

submitToLeader 存下该请求(storeOutstanding(req)),并广播(防止自己处于错误的 view),然后如果自己是主节点的话,执行 leaderProcReq,它的策略主要是
在请求数达到 op.batchSize 的时候才返回 events.Event,否则返回空。当返回不为空时,processMessage 会返回一个非空事件,然后传递到 ProcessEvent
进入 default 分支,交给 pbft-core 的 ProcessEvent 模块处理(RequestBatch 事件)

pbft 协议过程

主节点收到 RequestBatch 过程

  1. 存入 <hash(req), req> 到 reqBatchStore,outstandingReqBatches,并持久化( instance.consumer.StoreState(k,v) )
  2. 打开一个与 hash(req)相关的计时器
  3. 判断当前节点是不是主节点,如果是 sendPrePrepare

主节点发送 PrePrepare 过程

  1. 先得到序列号 n(instance.seqNo + 1)
  2. 如果发现已经收到其他摘要相同 view 相同而序列号 n 不同的,则返回
  3. 当 n 不在 watermarks 间,或 n > h + L/2 返回
  4. 当 n 大于 viewChangeSeqNo,因为将要 view-change 主节点,返回
func (instance *pbftCore) updateViewChangeSeqNo() {
	if instance.viewChangePeriod <= 0 {
		return
	}
	// Ensure the view change always occurs at a checkpoint boundary
	instance.viewChangeSeqNo = instance.seqNo + instance.viewChangePeriod*instance.K - instance.seqNo%instance.K
	logger.Debugf("Replica %d updating view change sequence number to %d", instance.id, instance.viewChangeSeqNo)
}

如果 viewchangeperiod 配置为 0 则不会改变 viewChangeSeqNo

  1. 以上条件都不满足。构造 pre-prepare 消息
	preprep := &PrePrepare{
		View:           instance.view, // 当前view
		SequenceNumber: n,             // 消息号
		BatchDigest:    digest,
		RequestBatch:   reqBatch,
		ReplicaId:      instance.id, //主节点id
	}

将 prePrepare, digest 存入 cert 里,并持久化 qset(?)
6. 广播 pre-Prepare 消息
7. maybeSendCommit (目前不是特别明白)

recvPrePrepare 处理流程

  1. 如果正在 view-change,忽略这个 Pre-Prepare 消息

  2. 如果收到的 Pre-Prepare 发送 id 不是当前 view 的主节点,忽略这个 Pre-Prepare 消息(view 一定,主节点一定 pbftCore.primary 函数)

  3. 当收到的 view 与 pbft-core 的 view 不一致,或 n 不在 watermarks 间的话,丢弃这个消息

  4. 当 n 大于 viewChangeSeqNo,发送 viewChange 消息,返回

  5. 当收到相同序列号的消息时,如果消息体不同,进行 view-change
    否则,将收到的 prePrepare, digest 存入 cert 里

  6. 当 reqBatchStore 不存在当前 PrePrepare 消息的摘要时,首先计算摘要,如果摘要与收到的摘要一致,将摘要记录
    到 reqBatchStore 与 outstandingReqBatches 中,并持久化该 reqBatch

7.当前节点不是主节点并且这个 Pre-Prepare 消息之前没有发送 Prepare 消息的话,构造 prepare 消息:

prep := &Prepare{
	View:           preprep.View,
	SequenceNumber: preprep.SequenceNumber,
	BatchDigest:    preprep.BatchDigest,
	ReplicaId:      instance.id,
}

自己调用 recvPrepare(相当于自己收到了 Prepare 消息)
然后持久化 qset(?)
调用 instance.innerBroadcast(&Message{Payload: &Message_Prepare{Prepare: prep}})广播消息

节点收到 prepare 消息

  1. 如果收到的是主节点的消息,丢弃(prepare 不可能由主节点发送)
  2. 当收到的 view 与 pbft-core 的 view 不一致,或 n 不在 watermarks 间的话,丢弃这个消息
  3. 获取 cert,如果收到同一节点同一 view 的同一序号消息,则退出
  4. 将 prepare 放入 cert,并持久化 pset
  5. maybeSendCommit(目前不是特别明白)

节点收到 commit 消息 recvCommit

  1. 当收到的 view 与 pbft-core 的 view 不一致,或 n 不在 watermarks 间的话,丢弃这个消息
  2. 获取 cert,如果收到同一节点同一 view 的同一序号存在该 commit 消息,丢弃这个消息
  3. 添加 commit 到 cert 里
  4. 看该 msg 是否满足 commited 函数,若满足,将先前放入的 outstandingReqBatches 删除
    执行未完成的 batch,执行完后,如果序列号等于 viewChangeSeqNo,进行 view-change

执行未完成的 batch executeOutstanding()

大概思路是每来一个 commit,执行所有没执行完的,当中任意一个执行成功则返回,否则继续执行

  1. 如果 instance.currentExec 当前正在执行,退出
  2. 执行所有没执行完的,执行成功一次就返回
for idx := range instance.certStore {
	if instance.executeOne(idx) {
		break
	}
}

执行某一个 commit executeOne(n)

  1. 如果 n 不等于 lastExec+1,返回 false(为了保证按序执行)
    这里的 lastExec 初始化由 getLastSeqNo 获得
  2. 如果设置了 skipInProgress,则返回 false
    skipInProgress : 在发现了一个落后的情况到我们选择一个新的起点间设置
  3. 如果该 req 不满足 commited 函数,则返回 false
  4. 如果是空请求 instance.execDoneSync()
    否则 instance.consumer.execute(idx.n, reqBatch)进入执行状态

obcbatch execute

  1. 把即将执行的交易放入 txs
  2. 存入最近执行交易的节点和时间戳
  3. 调用 op.stack.Execute(meta, txs) (后台执行,执行完会收到 executedEvent)

广播消息:

消息 函数
Message_PrePrepare sendPrePrepare
Message_Prepare recvPrePrepare
Message_Commit maybeSendCommit
Message_Checkpoint Checkpoint
Message_FetchRequestBatch fetchRequestBatches
Message_ViewChange sendViewChange
Message_NewView sendNewView
Message_Prepare processNewView2
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    492 引用 • 1383 回帖 • 375 关注
  • hyperledger
    3 引用

相关帖子

回帖

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...