共识框架定义了每个共识插件都需要实现的接口:
-
consensus.Consenter
: 允许共识插件从网络上接收消息的接口 -
consensus.Stack
: 允许共识插件用来与栈交互的,这个接口可以分为两部分:-
consensus.Communicator
: 用来发送(广播或单播)消息到其他的验证 peer -
consensus.LedgerStack
: 这个接口使得执行框架像总账一样方便
-
就像下面描述的细节一样,consensus.LedgerStack
封装了其他接口,consensus.Executor
接口是共识框架的核心部分。换句话说,consensus.Executor
接口允许一个(批量)交易启动,执行,根据需要回滚,预览和提交。每一个共识插件都需要满足以所有验证 peer 上全序的方式把批量(块)交易(通过consensus.Executor.CommitTxBatch
)被提交到总账中(参看下面的consensus.Executor
接口获得详细细节)。
当前,共识框架由consensus
, controller
和helper
这三个包组成。使用controller
和helper
包的主要原因是防止Go语言的“循环引入”和当插件更新时的最小化代码变化。
-
controller
包规范了验证 peer 所使用的共识插件 -
helper
是围绕公式插件的垫片,它是用来与剩下的栈交互的,如为其他 peer 维护消息。
这里有2个共识插件提供:pbft
和noops
:
-
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
对象实现的。回想一下这个对象是:
- 在
helper.NewConsensusHandler
被调用时初始化的 - 当它们的插件构造了
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
, Ledger
和RemoteLedgers
这三个接口组成的。下面会描述它们。
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 中取出由start
和finish
标识的范围中的*pb.SyncBlocks
流。一般情况下,由于区块链必须是从结束到开始这样的顺序来验证的,所以start
是比finish
更高的块编号。由于慢速的结构,其它请求的返回可能出现在这个通道中,所以调用者必须验证返回的是期望的块。第二次以同样的peerID
来调用这个方法会导致第一次的通道关闭。
GetRemoteStateSnapshot(peerID uint64) (<-chan *pb.SyncStateSnapshot, error)
这个函数尝试从由peerID
指定的 peer 中取出*pb.SyncStateSnapshot
流。为了应用结果,首先需要通过WritableLedger
的EmptyState
调用来清空存在在状态,然后顺序应用包含在流中的变化量。
GetRemoteStateDeltas(peerID uint64, start, finish uint64) (<-chan *pb.SyncStateDeltas, error)
这个函数尝试从由peerID
指定的 peer 中取出由start
和finish
标识的范围中的*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
共识插件。(注意,它最终被改变为noops
或custom
。在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
}
共识中的上下文,我们只关注域coordinator
和consenter
。coordinator
就像名字隐含的那样,它被用来在 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
被设置为输入参数mhc
(helper.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
这个函数检查进来的Message
的Type
。有四种情况:
- 等于
pb.Message_CONSENSUS
:传递给处理器的consenter.RecvMsg
函数。 - 等于
pb.Message_CHAIN_TRANSACTION
(如:一个外部部署的请求): 一个响应请求首先被发送给用户,然后把消息传递给consenter.RecvMsg
函数 - 等于
pb.Message_CHAIN_QUERY
(如:查询): 传递给helper.doChainQuery
方法来在本地执行 - 其它: 传递给栈中下一个处理器的
HandleMessage
方法