美文网首页
Fabric 插拔式共识框架(协议规范)

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

作者: 吴伟彬 | 来源:发表于2018-12-24 15:50 被阅读0次

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

    • 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 插拔式共识框架(协议规范)

          本文链接:https://www.haomeiwen.com/subject/ityzkqtx.html