quorum中的BFT

这块内容主要是一个拜占庭的过程。本文掐头去尾,只讲一下在 quorum 中是如何实现拜占庭的,也就是共识接口 Seal() 下向拜占庭发送了一个区块的请求事件开始。请求事件如下:

// post block into Istanbul engine
go sb.EventMux().Post(istanbul.RequestEvent{
	Proposal: block,
})

前言

在说正文之前,先讲四个变量:sequence round state Code,因为这四个变量覆盖了 BFT 的整个流程,可以说 BFT 玩的就是这几个变量。

// 视图包括round和sequence两个数字
//sequence是我们即将提交的区块号
//每个round都有一个数字,而且包含三步:preprepare, prepare and commit

// 如果给定一个区块没有被验证者接收,round将会改变,并且验证者将使用 round+1 这个新的round开始新的验证周期
type View struct {
	Round    *big.Int
	Sequence *big.Int
}

state 表述了每个 round 周期中,进行到了哪一步,是每个 round 周期的状态。

type State uint64

const (
	StateAcceptRequest State = iota
	StatePreprepared
	StatePrepared
	StateCommitted
)

标明发送消息的类型

const (
	msgPreprepare uint64 = iota
	msgPrepare
	msgCommit
	msgRoundChange
	msgAll   // 未使用
)

type message struct {
	Code          uint64  // 消息的类型
	Msg           []byte  // 消息的内容
	Address       common.Address // 发送消息的人
	Signature     []byte  // 消息的签名
	CommittedSeal []byte  // 提交消息到区块额外字段时用到
}

BFT正文

从这里开始正式对 quorumistanbul 共识的 BFT 流程进行说明,开始的文件是 consensus/istanbul/core/handler.go

启动BFT

在启动挖矿的时候会启动 BFT,此时开始一个新的 BFT 轮次,然后订阅事件并启动对事件的监听及处理。

func (c *core) Start() error {
	// Start a new round from last sequence + 1
	// 开始一个新的轮次
	c.startNewRound(common.Big0)

	// Tests will handle events itself, so we have to make subscribeEvents()
	// be able to call in test.
	// 订阅事件,事件包括:
	// |-- 普通拜占庭事件
	// 	 |-- 区块提案请求事件
	// 	 |-- 消息事件,又分为四种:
	// 	 	|--preprepare
	// 	 	|--prepare
	// 	 	|--commit
	// 	 	|--roundChange
	//	 |-- 积压消息事件
	// |-- round周期超时事件
	// |--区块头提交事件
	c.subscribeEvents()
	// 启动一个协程来处理不同的事件
	go c.handleEvents()

	return nil
}

处理不同的事件

要处理的事件可分为三大类:普通拜占庭事件、round周期超时事件、区块头提交事件普通拜占庭事件监听的是 BFT 一个轮次的流程。round周期超时事件 则是监听每个 BFT 轮次的时间,只要超时就会结束本次 BFT 流程,然后开始一个新的 BFT 流程。区块头提交事件 则是只要矿工收到区块头,就结束本次 BFT,然后开始一个新块的 BFT

func (c *core) handleEvents() {
	// Clear state
	defer func() {
		c.current = nil
		c.handlerWg.Done()
	}()

	c.handlerWg.Add(1)

	for {
		select {
		case event, ok := <-c.events.Chan():
			if !ok {
				return
			}
			// A real event arrived, process interesting content
			// 一个真正人事件到达,处理有趣的内容
			switch ev := event.Data.(type) {
			case istanbul.RequestEvent: // 普通拜占庭事件
				r := &istanbul.Request{ // 区块提案请求事件
					Proposal: ev.Proposal,
				}
				// 处理区块提案请求
				err := c.handleRequest(r)
				// 如果是未来的请求则保存
				if err == errFutureMessage {
					c.storeRequestMsg(r)
				}
			case istanbul.MessageEvent: // 各自消息事件
				if err := c.handleMsg(ev.Payload); err == nil {
					// 如果没错误,通过gossip协议发送给其它验证器(不包括自己)
					c.backend.Gossip(c.valSet, ev.Payload)
				}
			case backlogEvent: // 积压消息事件
				// No need to check signature for internal messages
				// 没必要检查内部消息的签名,因为消息事件中已检查过了
				if err := c.handleCheckedMsg(ev.msg, ev.src); err == nil {
					p, err := ev.msg.Payload()
					if err != nil {
						c.logger.Warn("Get message payload failed", "err", err)
						continue
					}
					c.backend.Gossip(c.valSet, p)
				}
			}
		case _, ok := <-c.timeoutSub.Chan():// 拜占庭round周期超时事件
			if !ok {
				return
			}
			c.handleTimeoutMsg()
		case event, ok := <-c.finalCommittedSub.Chan(): //区块头提交事件
			if !ok {
				return
			}
			switch event.Data.(type) {
			case istanbul.FinalCommittedEvent:
				c.handleFinalCommitted()
			}
		}
	}
}

后两个比较简单,这里贴一下代码和注释。
round周期超时事件
// round 超时后处理方式
func (c *core) handleTimeoutMsg() {
	// If we're not waiting for round change yet, we can try to catch up
	// the max round with F+1 round change message. We only need to catch up
	// if the max round is larger than current round.
	// 如果不需要等待round值修改
	if !c.waitingForRoundChange {
		//消息数量大于等于 f+1 的round中, 取round值最大的那个round值,
		maxRound := c.roundChangeSet.MaxRound(c.valSet.F() + 1)
		if maxRound != nil && maxRound.Cmp(c.current.Round()) > 0 {
			//修改 round 轮次
			c.sendRoundChange(maxRound)
			return
		}
	}

	// 拿到最后一次申请的提案
	lastProposal, _ := c.backend.LastProposal()
	// 如果提案不是空的,而且提案不小于当前的区块号,则开始生产新块的轮次
	if lastProposal != nil && lastProposal.Number().Cmp(c.current.Sequence()) >= 0 {
		c.logger.Trace("round change timeout, catch up latest sequence", "number", lastProposal.Number().Uint64())
		// 开始一个全新round轮次
		c.startNewRound(common.Big0)
	} else {
		// 否则,进入下一个round轮次,也就是round+1
		c.sendNextRoundChange()
	}
}

区块头提交事件

func (c *core) handleFinalCommitted() error {
	logger := c.logger.New("state", c.state)
	logger.Trace("Received a final committed proposal")
	// 开始一个新的轮次
	c.startNewRound(common.Big0)
	return nil
}

然后开始重头戏,BFT的正常流程。

先简单介绍一下BFT的正常流程:

——开始一个新的 round 轮次,round=0sequence=当前区块链高度+1state状态置为StateAcceptRequest,启动 round 的计时器。

——区块提案申请人提交提案。

——收到请求后判断,如果是未来消息,则保存该请求到待处理请求的优先队列中。否则发送 preprepare 消息。

——把当前 View(round sequence) 和请求提案(区块)包装成 preprepare 消息,广播出去,广播前会对消息进行签名处理。

——收到preprepare消息后处理消息,如果验证区块提案时发现这是一个未来区块,则把它放到积压消息的优先队列中,到期处理。如果 区块Hash 被锁定且一致,则发送一个 commit消息。如果是正常流程,则切换 state状态StatePreparepared 并发送 prepare消息

——把 View(round sequence)Hash 包装成 prepare消息,广播出去,广播前会对消息进行签名处理。

——收到 prepare消息 后处理消息,检查 round、sequence 来确保消息的有效性,验证消息签名的正确性,当收到 2f+1 条消息后,锁定 Hash、切换 state 状态为 StatePrepared 并发送 commit消息

——把 View(round sequence)Hash 包装成 commit消息,广播出去,广播前会对消息进行签名处理。

——收到 commit消息 后处理消息,检查 round、sequence 来确保消息的有效性,验证消息签名的正确性,当收到 2f+1 条消息后,锁定 <

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值