hyperledger fabric PBFT算法简要解析

hyperledger的PBFT算法基于拜占庭将军问题,解决方法不同于ripple,但有着相同的理论基础,先理解一个公式:3f+1 <=N,
f为容许出错的节点1,N为总节点数。废话不多说,转载网上一篇文章,对照代码(比较适合笔者)

fabric的 hyperledger共识算法代码全部都在consensus文件夹里,consensus文件夹里主要分为controller,executor,helper,noops,pbft,util文件模块。 
其中consensus.Go 主要包含了算法插件内部对外部暴露的接口和hyperledger外部对算法内部暴露的接口。

  • controller:共识算法模块是可插拔的,在controller里面可以选择具体使用哪种共识算法。目前hyperledger它提供了一个pbft算法和一个比较简单的noops算法。
  • executor:executor和helper是两个相互依赖的模块,主要提供了共识算法和外部衔接的一块代码。主要负责事件处理的转接。
  • helper:这里面主要包含了对外部接口的一个调用,比如执行处理transaction,stateupdate,持久化一些对象等。
  • noops: noops也是一种比较简单共识算法
  • pbft: pbft算法,下面会简单的介绍一下pbft算法的调用流程。
  • util: 一些交互需要的工具包,最主要的一个实现的功能就是它的消息机制。

下面简要介绍两点,一点pbft算法代码内部从头到尾的一个调用流程,一点是pbft算法内部的事件机制和timeout代码的一个简要解析。

内部调用流程

在engine.go里面有获取一个共识算法plugin

func GetEngine(coord peer.MessageHandlerCoordinator) (peer.Engine, error) {
    var err error
    engineOnce.Do(func() {
        engine = new(EngineImpl)
        engine.helper = NewHelper(coord)
        engine.consenter = controller.NewConsenter(engine.helper)
        engine.helper.setConsenter(engine.consenter)
        engine.peerEndpoint, err = coord.GetPeerEndpoint()
        engine.consensusFan = util.NewMessageFan()

        go func() {
            logger.Debug("Starting up message thread for consenter")

            // The channel never closes, so this should never break
            for msg := range engine.consensusFan.GetOutChannel() {
                engine.consenter.RecvMsg(msg.Msg, msg.Sender)
            }
        }()
    })
    return engine, err
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

它初始化一个consenter和一个helper,并互相把一个句柄赋值给了对方。这样做的目的,就是为了可以让外部调用内部,内部可以调用外部。

首先看一下它是如何初始化一个共识模块的:

 1. 调用controller获取一个plugin,当选择是pbft算法时,它会调用pbft.go 里的 GetPlugin(c consensus.Stack)方法,在pbft.go里面把所有的外部参数读进算法内部。
func New(stack consensus.Stack) consensus.Consenter {
    handle, _, _ := stack.GetNetworkHandles()
    id, _ := getValidatorID(handle)

    switch strings.ToLower(config.GetString("general.mode")) {
    case "batch":
        return newObcBatch(id, config, stack)
    default:
        panic(fmt.Errorf("Invalid PBFT mode: %s", config.GetString("general.mode")))
    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
 2. 用方法newObcBatch(id uint64, config *viper.Viper, stack consensus.Stack)初始化一个obcbatch对象。这个batch对象的作用就是用来做request缓存,提高transaction的执行效率,如果每来一个请求就去做一次共识,那代价会很高。缓存存储在batchStore里。
 3. 在newobcbatch时,会初始化得到一个pbftcore的一个实例,这个是算法的核心模块。并此时会启动一个batchTimer(这个batchTimer是一个计时器,当batchTimer timeout后会触发一个sendbatch操作,这个只有primary节点才会去做)。当然此时会创建一个事件处理机制,这个事件处理机制是各个模块沟通的一个bridge。
 4. 在初始化pbftcore时,在把所用配置读进的同时,创建了三个timer     
instance.newViewTimer = etf.CreateTimer()
instance.vcResendTimer = etf.CreateTimer()
instance.nullRequestTimer = etf.CreateTimer()
   
   
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • newViewTimer对应于viewChangeTimerEvent{},当这个timer在一定时间没有close时,就会触发一个viewchange事件。
  • vcResendTimer对应viewChangeResendTimerEvent,发出viewchange过时时会触发一个将viewchange从新发送。
  • nullRequestTimer对应nullRequestEvent,如果主节点长期没有发送preprepare消息,也就是分配了seq的reqBatch。它timeout就认为主节点挂掉了然后发送viewchange消息。

当然理解以上东西需要了解pbft算法的各个具体流程。以上是pbft算法插件的初始化流程, 并讲解了部分初始化我认为比较重要的信息。具体初始化需要看代码了。

算法内部的事件机制

为什么要把事件机制单独提出来,因为要想完全看懂它的调用流程,就必须理解它的事件流。说起来它的事件流真是复杂,我觉得可以不必要这么复杂。

它有两个事件流,一个是在helper里,一个是在batch里。

首先分析一下它的事件流工具,它整个代码都在util包里的events.go,我认为这个设计还是棒棒哒,在我自己的共识算法测试环境里,对它改装了一下,感觉还是不错的。

首先看一下它提供的接口

type Manager interface {
    Inject(Event)   // A temporary interface to allow the event manager thread to skip the queue
    Queue() chan<- Event // Get a write-only reference to the queue, to submit events
    SetReceiver(Receiver) // Set the target to route events to
    Start()              // Starts the Manager thread TODO, these thread management things should probably go away
    Halt()                // Stops the Manager thread
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

SetReceiver(Receiver)是一个很重要的接口,决定了这个事件机制的灵活性。考验我表达能力的时候到了~~,一个事件机制必定有一个输入和一个输出,这个SetReceiver(Receiver) interface 方法就决定了事件流的去向。下面是receiver的interface,凡是事件的接受者都必须实现ProcessEvent(e Event) Event方法。batch里面实现了此方法。

type Receiver interface {
    // ProcessEvent delivers an event to the Receiver, if it returns non-nil, the return is the next processed event
    ProcessEvent(e Event) Event
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

那对应的输出,Queue() chan<- Event ,它返回一个event channel,你所有的消息尽管往里面发。接收者取决于receiver。

func SendEvent(receiver Receiver, event Event) {
    next := event
    for {
        // If an event returns something non-nil, then process it as a new event
        next = receiver.ProcessEvent(next)
        if next == nil {
            break
        }
    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这段代码是把事件传给receiver处理。举个batch事件流机制的例子。

在external.go里面实现了接收外边request请求的接口。在obcbatch初始化会对其创建并且把event manager复制给externalEventReceiver。因此所有接收到这个manager的消息都会进入到batch里面。

// 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
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

当接收到一个request时,将batchMessageEvent放到事件流,之后

func (em *managerImpl) eventLoop() {
    for {
        select {
        case next := <-em.events:
            em.Inject(next)
        case <-em.exit:
            logger.Debug("eventLoop told to exit")
            return
        }
    }
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

这个死循环接收到的event 进行了em.Inject(next),并执行

func SendEvent(receiver Receiver, event Event) {
    next := event
    for {
        // If an event returns something non-nil, then process it as a new event
        next = receiver.ProcessEvent(next)
        if next == nil {
            break
        }
    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

之后在obcbatch ProcessEvent里执行了这个操作

case batchMessageEvent:
        ocMsg := et
        return op.processMessage(ocMsg.msg, ocMsg.sender)
   
   
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

这是消息往里抛的过程,同理,消息往外抛,就是算法内部把event抛给外部executor的event manager。

Timer机制

timer机制和event机制有很大关联,time out后,会把事先创建的event塞到eventmanager里的事件流里。

type Timer interface {
    SoftReset(duration time.Duration, event Event) // start a new countdown, only if one is not already started
    Reset(duration time.Duration, event Event)     // start a new countdown, clear any pending events
    Stop()                                         // stop the countdown, clear any pending events
    Halt()                                         // Stops the Timer thread
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

设置time out主要是SoftReset和reset方法。因此在初始化,会把Manager传给Timer。

但这样的事件机制在大数据处理时,可能会出现问题。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是Hyperledger FabricPBFT算法Go语言实现代码: ```go package pbft import ( "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "sync" ) const ( // PbftPhaseNotStarted represents the state where PBFT is not started PbftPhaseNotStarted = iota // PbftPhasePrePrepare represents the state where PBFT is in the pre-prepare phase PbftPhasePrePrepare // PbftPhasePrepare represents the state where PBFT is in the prepare phase PbftPhasePrepare // PbftPhaseCommit represents the state where PBFT is in the commit phase PbftPhaseCommit ) // PbftMessage represents a message in the PBFT algorithm type PbftMessage struct { Phase int SequenceNum int View int Digest string Block []byte ReplicaID int } // Pbft represents a PBFT instance type Pbft struct { mux sync.Mutex replicaID int view int sequenceNum int state int digest string block []byte messages map[string]PbftMessage } // NewPbft creates a new PBFT instance with the specified replica ID func NewPbft(replicaID int) *Pbft { return &Pbft{ replicaID: replicaID, view: 0, state: PbftPhaseNotStarted, messages: make(map[string]PbftMessage), } } // Start starts the PBFT algorithm with the specified block func (p *Pbft) Start(block []byte) { p.mux.Lock() defer p.mux.Unlock() // Set the initial state p.view = 0 p.sequenceNum = 1 p.state = PbftPhasePrePrepare p.block = block // Compute the digest of the block digest := sha256.Sum256(block) p.digest = hex.EncodeToString(digest[:]) // Create and broadcast the pre-prepare message prePrepareMsg := PbftMessage{ Phase: PbftPhasePrePrepare, SequenceNum: p.sequenceNum, View: p.view, Digest: p.digest, Block: p.block, ReplicaID: p.replicaID, } p.broadcast(prePrepareMsg) } // HandleMessage handles an incoming PBFT message func (p *Pbft) HandleMessage(msg []byte) { p.mux.Lock() defer p.mux.Unlock() // Parse the message var pbftMsg PbftMessage err := json.Unmarshal(msg, &pbftMsg) if err != nil { fmt.Printf("Failed to parse PBFT message: %s\n", err) return } // Check if we have already seen this message key := p.getMessageKey(pbftMsg) if _, ok := p.messages[key]; ok { return } // Add the message to our list of seen messages p.messages[key] = pbftMsg switch p.state { case PbftPhasePrePrepare: p.handlePrePrepare(pbftMsg) case PbftPhasePrepare: p.handlePrepare(pbftMsg) case PbftPhaseCommit: p.handleCommit(pbftMsg) } } // broadcast broadcasts a PBFT message to all other replicas func (p *Pbft) broadcast(msg PbftMessage) { // TODO: implement broadcast } // handlePrePrepare handles a pre-prepare message func (p *Pbft) handlePrePrepare(msg PbftMessage) { if msg.View != p.view || msg.SequenceNum != p.sequenceNum || msg.Digest != p.digest { return } // Create and broadcast the prepare message prepareMsg := PbftMessage{ Phase: PbftPhasePrepare, SequenceNum: p.sequenceNum, View: p.view, Digest: p.digest, Block: p.block, ReplicaID: p.replicaID, } p.broadcast(prepareMsg) // Update state p.state = PbftPhasePrepare } // handlePrepare handles a prepare message func (p *Pbft) handlePrepare(msg PbftMessage) { if msg.View != p.view || msg.SequenceNum != p.sequenceNum || msg.Digest != p.digest { return } // Update state p.state = PbftPhaseCommit // Create and broadcast the commit message commitMsg := PbftMessage{ Phase: PbftPhaseCommit, SequenceNum: p.sequenceNum, View: p.view, Digest: p.digest, Block: p.block, ReplicaID: p.replicaID, } p.broadcast(commitMsg) } // handleCommit handles a commit message func (p *Pbft) handleCommit(msg PbftMessage) { if msg.View != p.view || msg.SequenceNum != p.sequenceNum || msg.Digest != p.digest { return } // TODO: validate the commit message } // getMessageKey returns a unique key for a PBFT message func (p *Pbft) getMessageKey(msg PbftMessage) string { return fmt.Sprintf("%d:%d:%s:%d", msg.View, msg.SequenceNum, msg.Digest, msg.ReplicaID) } ``` 注意,上述代码只是一个简单的示例,需要根据实际情况进行修改和扩展。另外,这里的广播机制还没有实现,需要根据实际情况选择适当的广播方式。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值