Fabric 插拔式共识框架(协议规范)

共识框架定义了每个共识插件都需要实现的接口:

  • consensus.Consenter: 允许共识插件从网络上接收消息的接口
  • consensus.Stack: 允许共识插件用来与栈交互的,这个接口可以分为两部分:
    • consensus.Communicator: 用来发送(广播或单播)消息到其他的验证 peer
    • consensus.LedgerStack: 这个接口使得执行框架像总账一样方便

就像下面描述的细节一样,consensus.LedgerStack封装了其他接口,consensus.Executor接口是共识框架的核心部分。换句话说,consensus.Executor接口允许一个(批量)交易启动,执行,根据需要回滚,预览和提交。每一个共识插件都需要满足以所有验证 peer 上全序的方式把批量(块)交易(通过consensus.Executor.CommitTxBatch)被提交到总账中(参看下面的consensus.Executor接口获得详细细节)。

当前,共识框架由consensus, controllerhelper这三个包组成。使用controllerhelper包的主要原因是防止Go语言的“循环引入”和当插件更新时的最小化代码变化。

  • controller 包规范了验证 peer 所使用的共识插件
  • helper 是围绕公式插件的垫片,它是用来与剩下的栈交互的,如为其他 peer 维护消息。

这里有2个共识插件提供:pbftnoops

  • obcpbft包包含实现 PBFT [1] 和 Sieve 共识协议的共识插件。参看上一篇文章介绍。
  • noops 是一个为开发和测试提供的''假的''共识插件. 它处理所有共识消息但不提供共识功能,它也是一个好的学习如何开发一个共识插件的简单例子。

3.4.1 Consenter 接口

定义:

type Consenter interface {
    RecvMsg(msg *pb.Message) error
    ExecutionConsumer
}

Consenter接口是插件对(外部的)客户端请求的入口,当处理共识时,共识消息在内部(如从共识模块)产生。NewConsenter创建Consenter插件。RecvMsg`以到达共识的顺序来处理进来的交易。

阅读下面的helper.HandleMessage来理解 peer 是如何和这个接口来交互的。

3.4.2 CPI接口

定义:

type Stack interface {
    NetworkStack
    SecurityUtils
    Executor
    LegacyExecutor
    LedgerManager
    ReadOnlyLedger
    StatePersistor
}
type CPI interface {
    Inquirer
    Communicator
    SecurityUtils
    Executor
    Ledger
    RemoteLedgers
}

CPI 允许插件和栈交互。它是由helper.Helper对象实现的。回想一下这个对象是:

  1. helper.NewConsensusHandler被调用时初始化的
  2. 当它们的插件构造了consensus.Consenter对象,那么它对插件的作者是可访问的

3.4.3 Inquirer接口

定义:

type Inquirer interface {
        GetNetworkInfo() (self *pb.PeerEndpoint, network []*pb.PeerEndpoint, err error)
        GetNetworkHandles() (self *pb.PeerID, network []*pb.PeerID, err error)
}

这个接口是consensus.CPI接口的一部分。它是用来获取网络中验证 peer 的(GetNetworkHandles)句柄,以及那些验证 peer 的明细(GetNetworkInfo):

注意peers由pb.PeerID对象确定。这是一个protobuf消息,当前定义为(注意这个定义很可能会被修改):

message PeerID {
    string name = 1;
}

3.4.4 Communicator接口

定义:

type Communicator interface {
    Broadcast(msg *pb.Message) error
    Unicast(msg *pb.Message, receiverHandle *pb.PeerID) error
}

这个接口是consensus.CPI接口的一部分。它是用来与网络上其它 peer 通信的(helper.Broadcast, helper.Unicast):

3.4.5 SecurityUtils接口

定义:

type SecurityUtils interface {
        Sign(msg []byte) ([]byte, error)
        Verify(peerID *pb.PeerID, signature []byte, message []byte) error
}

这个接口是consensus.CPI接口的一部分。它用来处理消息签名(Sign)的加密操作和验证签名(Verify)

3.4.6 LedgerStack 接口

定义:

type LedgerStack interface {
    Executor
    Ledger
    RemoteLedgers
}

CPI接口的主要成员,LedgerStack 组与fabric的其它部分与共识相互作用,如执行交易,查询和更新总账。这个接口支持对本地区块链和状体的查询,更新本地区块链和状态,查询共识网络上其它节点的区块链和状态。它是由Executor, LedgerRemoteLedgers这三个接口组成的。下面会描述它们。

3.4.7 Executor 接口

定义:

type Executor interface {
    BeginTxBatch(id interface{}) error
    ExecTXs(id interface{}, txs []*pb.Transaction) ([]byte, []error)
    CommitTxBatch(id interface{}, transactions []*pb.Transaction, transactionsResults []*pb.TransactionResult, metadata []byte) error
    RollbackTxBatch(id interface{}) error
    PreviewCommitTxBatchBlock(id interface{}, transactions []*pb.Transaction, metadata []byte) (*pb.Block, error)
}

executor接口是LedgerStack接口最常使用的部分,且是共识网络工作的必要部分。接口允许交易启动,执行,根据需要回滚,预览和提交。这个接口由下面这些方法组成。

3.4.7.1 开始批量交易

BeginTxBatch(id interface{}) error

这个调用接受任意的,故意含糊的id,来使得共识插件可以保证与这个具体的批量相关的交易才会被执行。例如:在pbft实现中,这个id是被执行交易的编码过的哈希。

3.4.7.2 执行交易

ExecTXs(id interface{}, txs []*pb.Transaction) ([]byte, []error)

这个调用根据总账当前的状态接受一组交易,并返回带有对应着交易组的错误信息组的当前状态的哈希。注意一个交易所产生的错误不影响批量交易的安全提交。当遇到失败所采用的策略取决与共识插件的实现。这个接口调用多次是安全的。

3.4.7.3 提交与回滚交易

RollbackTxBatch(id interface{}) error

这个调用中止了批量执行。这会废弃掉对当前状态的操作,并把总账状态回归到之前的状态。批量是从BeginBatchTx开始的,如果需要开始一个新的就需要在执行任意交易之前重新创建一个。

PreviewCommitTxBatchBlock(id interface{}, transactions []*pb.Transaction, metadata []byte) (*pb.Block, error)

这个调用是共识插件对非确定性交易执行的测试时最有用的方法。区块返回的哈希表部分会保证,当CommitTxBatch被立即调用时的区块是同一个。这个保证会被任意新的交易的执行所打破。

CommitTxBatch(id interface{}, transactions []*pb.Transaction, transactionsResults []*pb.TransactionResult, metadata []byte) error

这个调用提交区块到区块链中。区块必须以全序提交到区块链中,CommitTxBatch结束批量交易,在执行或提交任意的交易之前必须先调用BeginTxBatch

3.4.8 Ledger 接口

定义:

type Ledger interface {
    ReadOnlyLedger
    UtilLedger
    WritableLedger
}

Ledger 接口是为了允许共识插件询问或可能改变区块链当前状态。它是由下面描述的三个接口组成的

3.4.8.1 ReadOnlyLedger 接口

定义:

type ReadOnlyLedger interface {
    GetBlock(id uint64) (block *pb.Block, err error)
    GetCurrentStateHash() (stateHash []byte, err error)
    GetBlockchainSize() (uint64, error)
}

ReadOnlyLedger 接口是为了查询总账的本地备份,而不会修改它。它是由下面这些函数组成的。

GetBlockchainSize() (uint64, error)

这个函数返回区块链总账的长度。一般来说,这个函数永远不会失败,在这种不太可能发生情况下,错误被传递给调用者,由它确定是否需要恢复。具有最大区块值的区块的值为GetBlockchainSize()-1

注意在区块链总账的本地副本是腐坏或不完整的情况下,这个调用会返回链中最大的区块值+1。这允许节点在旧的块是腐坏或丢失的情况下能继续操作当前状态/块。

GetBlock(id uint64) (block *pb.Block, err error)

这个调用返回区块链中块的数值id。一般来说这个调用是不会失败的,除非请求的区块超出当前区块链的长度,或者底层的区块链被腐坏了。GetBlock的失败可能可以通过状态转换机制来取回它。

GetCurrentStateHash() (stateHash []byte, err error)

这个调用返回总账的当前状态的哈希。一般来说,这个函数永远不会失败,在这种不太可能发生情况下,错误被传递给调用者,由它确定是否需要恢复。

3.4.8.2 UtilLedger 接口

定义:

type UtilLedger interface {
    HashBlock(block *pb.Block) ([]byte, error)
    VerifyBlockchain(start, finish uint64) (uint64, error)
}

UtilLedger 接口定义了一些由本地总账提供的有用的功能。使用mock接口来重载这些功能在测试时非常有用。这个接口由两个函数构成。
会会

HashBlock(block *pb.Block) ([]byte, error)

尽管*pb.Block定义了GetHash方法,为了mock测试,重载这个方法会非常有用。因此,建议GetHash方法不直接调用,而是通过UtilLedger.HashBlock接口来调用这个方法。一般来说,这个函数永远不会失败,但是错误还是会传递给调用者,让它决定是否使用适当的恢复。

VerifyBlockchain(start, finish uint64) (uint64, error)

这个方法是用来校验区块链中的大的区域。它会从高的块start到低的块finish,返回第一个块的PreviousBlockHash与块的前一个块的哈希不相符的块编号以及错误信息。注意,它一般会标识最后一个好的块的编号,而不是第一个坏的块的编号。

3.4.8.3 WritableLedger 接口

定义:

type WritableLedger interface {
    PutBlock(blockNumber uint64, block *pb.Block) error
    ApplyStateDelta(id interface{}, delta *statemgmt.StateDelta) error
    CommitStateDelta(id interface{}) error
    RollbackStateDelta(id interface{}) error
    EmptyState() error
}

WritableLedger 接口允许调用者更新区块链。注意这NOT 不是共识插件的通常用法。当前的状态需要通过Executor接口执行交易来修改,新的区块在交易提交时生成。相反的,这个接口主要是用来状态改变和腐化恢复。特别的,这个接口下的函数永远不能直接暴露给共识消息,这样会导致打破区块链所承诺的不可修改这一概念。这个结构包含下面这些函数。

​   PutBlock(blockNumber uint64, block *pb.Block) error

​ 这个函数根据给定的区块编号把底层区块插入到区块链中。注意这是一个不安全的接口,所以它不会有错误返回或返回。插入一个比当前区块高度更高的区块是被允许的,同样,重写一个已经提交的区块也是被允许的。记住,由于哈希技术使得创建一个链上的更早的块是不可行的,所以这并不影响链的可审计性和不可变性。任何尝试重写区块链的历史的操作都能很容易的被侦测到。这个函数一般只用于状态转移API。

​   ApplyStateDelta(id interface{}, delta *statemgmt.StateDelta) error

这个函数接收状态变化,并把它应用到当前的状态。变化量的应用会使得状态向前或向后转变,这取决于状态变化量的构造,与Executor方法一样,ApplyStateDelta接受一个同样会被传递给CommitStateDelta or RollbackStateDelta不透明的接口id

​   CommitStateDelta(id interface{}) error

这个方法提交在ApplyStateDelta中应用的状态变化。这通常是在调用者调用ApplyStateDelta后通过校验由GetCurrentStateHash()获得的状态哈希之后调用的。这个函数接受与传递给ApplyStateDelta一样的id

​   RollbackStateDelta(id interface{}) error

这个函数撤销在ApplyStateDelta中应用的状态变化量。这通常是在调用者调用ApplyStateDelta后与由GetCurrentStateHash()获得的状态哈希校验失败后调用的。这个函数接受与传递给ApplyStateDelta一样的id

    EmptyState() error

这个函数将会删除整个当前状态,得到原始的空状态。这通常是通过变化量加载整个新的状态时调用的。这一般只对状态转移API有用。

3.4.9 RemoteLedgers 接口

定义:

type RemoteLedgers interface {
    GetRemoteBlocks(peerID uint64, start, finish uint64) (<-chan *pb.SyncBlocks, error)
    GetRemoteStateSnapshot(peerID uint64) (<-chan *pb.SyncStateSnapshot, error)
    GetRemoteStateDeltas(peerID uint64, start, finish uint64) (<-chan *pb.SyncStateDeltas, error)
}

RemoteLedgers 接口的存在主要是为了启用状态转移,和向其它副本询问区块链的状态。和WritableLedger接口一样,这不是给正常的操作使用,而是为追赶,错误恢复等操作而设计的。这个接口中的所有函数调用这都有责任来处理超时。这个接口包含下面这些函数:

    GetRemoteBlocks(peerID uint64, start, finish uint64) (<-chan *pb.SyncBlocks, error)

这个函数尝试从由peerID指定的 peer 中取出由startfinish标识的范围中的*pb.SyncBlocks流。一般情况下,由于区块链必须是从结束到开始这样的顺序来验证的,所以start是比finish更高的块编号。由于慢速的结构,其它请求的返回可能出现在这个通道中,所以调用者必须验证返回的是期望的块。第二次以同样的peerID来调用这个方法会导致第一次的通道关闭。

    GetRemoteStateSnapshot(peerID uint64) (<-chan *pb.SyncStateSnapshot, error)

这个函数尝试从由peerID指定的 peer 中取出*pb.SyncStateSnapshot流。为了应用结果,首先需要通过WritableLedgerEmptyState调用来清空存在在状态,然后顺序应用包含在流中的变化量。

    GetRemoteStateDeltas(peerID uint64, start, finish uint64) (<-chan *pb.SyncStateDeltas, error)

这个函数尝试从由peerID指定的 peer 中取出由startfinish标识的范围中的*pb.SyncStateDeltas流。由于慢速的结构,其它请求的返回可能出现在这个通道中,所以调用者必须验证返回的是期望的块变化量。第二次以同样的peerID来调用这个方法会导致第一次的通道关闭。

3.4.10 controller

3.4.10.1 controller.NewConsenter

签名:

func NewConsenter(cpi consensus.CPI) (consenter consensus.Consenter)

这个函数读取为peer过程指定的core.yaml配置文件中的peer.validator.consensus的值。键peer.validator.consensus的有效值指定运行noops还是pbft共识插件。(注意,它最终被改变为noopscustom。在custom情况下,验证 peer 将会运行由consensus/config.yaml中定义的共识插件)

插件的作者需要编辑函数体,来保证路由到它们包中正确的构造函数。例如,对于pbft 我们指向pbft.GetPlugin构造器。

这个函数是当设置返回信息处理器的consenter域时,被helper.NewConsensusHandler调用的。输入参数cpi是由helper.NewHelper构造器输出的,并实现了consensus.CPI接口

3.4.11 helper

3.4.11.1 高层次概述

验证 peer 通过helper.NewConsesusHandler函数(一个处理器工厂),为每个连接的 peer 建立消息处理器(helper.ConsensusHandler)。每个进来的消息都会检查它的类型(helper.HandleMessage);如果这是为了共识必须到达的消息,它会传递到 peer 的共识对象(consensus.Consenter)。其它的信息会传递到栈中的下一个信息处理器。

3.4.11.2 helper.ConsensusHandler

定义:

type ConsensusHandler struct {
    chatStream  peer.ChatStream
    consenter   consensus.Consenter
    coordinator peer.MessageHandlerCoordinator
    done        chan struct{}
    peerHandler peer.MessageHandler
}

共识中的上下文,我们只关注域coordinatorconsentercoordinator就像名字隐含的那样,它被用来在 peer 的信息处理器之间做协调。例如,当 peer 希望Broadcast时,对象被访问。共识需要到达的共识者会接收到消息并处理它们。

注意,fabric/peer/peer.go定义了peer.MessageHandler (接口),和peer.MessageHandlerCoordinator(接口)类型。

3.4.11.3 helper.NewConsensusHandler

签名:

func NewConsensusHandler(coord peer.MessageHandlerCoordinator, stream peer.ChatStream, initiatedStream bool, next peer.MessageHandler) (peer.MessageHandler, error)

创建一个helper.ConsensusHandler对象。为每个coordinator设置同样的消息处理器。同时把consenter设置为controller.NewConsenter(NewHelper(coord))

3.4.11.4 helper.Helper

定义:

type Helper struct {
    coordinator peer.MessageHandlerCoordinator
}

包含验证peer的coordinator的引用。对象是否为peer实现了consensus.CPI接口。

3.4.11.5 helper.NewHelper

签名:

func NewHelper(mhc peer.MessageHandlerCoordinator) consensus.CPI

返回coordinator被设置为输入参数mhchelper.ConsensusHandler消息处理器的coordinator域)的helper.Helper对象。这个对象实现了consensus.CPI接口,从而允许插件与栈进行交互。

3.4.11.6 helper.HandleMessage

回忆一下,helper.NewConsensusHandler返回的helper.ConsesusHandler对象实现了 peer.MessageHandler 接口:

type MessageHandler interface {
    RemoteLedger
    HandleMessage(msg *pb.Message) error
    SendMessage(msg *pb.Message) error
    To() (pb.PeerEndpoint, error)
    Stop() error
}

在共识的上下文中,我们只关心HandleMessage方法。签名:

func (handler *ConsensusHandler) HandleMessage(msg *pb.Message) error

这个函数检查进来的MessageType。有四种情况:

  1. 等于pb.Message_CONSENSUS:传递给处理器的consenter.RecvMsg函数。
  2. 等于pb.Message_CHAIN_TRANSACTION (如:一个外部部署的请求): 一个响应请求首先被发送给用户,然后把消息传递给consenter.RecvMsg函数
  3. 等于pb.Message_CHAIN_QUERY (如:查询): 传递给helper.doChainQuery方法来在本地执行
  4. 其它: 传递给栈中下一个处理器的HandleMessage方法

你可能感兴趣的:(Fabric 插拔式共识框架(协议规范))