共識框架定義了每個(gè)共識插件都需要實(shí)現(xiàn)的接口:
-
consensus.Consenter
: 允許共識插件從網(wǎng)絡(luò)上接收消息的接口 -
consensus.Stack
: 允許共識插件用來與棧交互的凡傅,這個(gè)接口可以分為兩部分:-
consensus.Communicator
: 用來發(fā)送(廣播或單播)消息到其他的驗(yàn)證 peer -
consensus.LedgerStack
: 這個(gè)接口使得執(zhí)行框架像總賬一樣方便
-
就像下面描述的細(xì)節(jié)一樣氛谜,consensus.LedgerStack
封裝了其他接口托慨,consensus.Executor
接口是共識框架的核心部分。換句話說望薄,consensus.Executor
接口允許一個(gè)(批量)交易啟動(dòng),執(zhí)行喜每,根據(jù)需要回滾间护,預(yù)覽和提交。每一個(gè)共識插件都需要滿足以所有驗(yàn)證 peer 上全序的方式把批量(塊)交易(通過consensus.Executor.CommitTxBatch
)被提交到總賬中(參看下面的consensus.Executor
接口獲得詳細(xì)細(xì)節(jié))白筹。
當(dāng)前智末,共識框架由consensus
, controller
和helper
這三個(gè)包組成。使用controller
和helper
包的主要原因是防止Go語言的“循環(huán)引入”和當(dāng)插件更新時(shí)的最小化代碼變化徒河。
-
controller
包規(guī)范了驗(yàn)證 peer 所使用的共識插件 -
helper
是圍繞公式插件的墊片系馆,它是用來與剩下的棧交互的,如為其他 peer 維護(hù)消息顽照。
這里有2個(gè)共識插件提供:pbft
和noops
:
-
obcpbft
包包含實(shí)現(xiàn) PBFT [1] 和 Sieve 共識協(xié)議的共識插件由蘑。參看上一篇文章介紹。 -
noops
是一個(gè)為開發(fā)和測試提供的''假的''共識插件. 它處理所有共識消息但不提供共識功能代兵,它也是一個(gè)好的學(xué)習(xí)如何開發(fā)一個(gè)共識插件的簡單例子尼酿。
3.4.1 Consenter
接口
定義:
type Consenter interface {
RecvMsg(msg *pb.Message) error
ExecutionConsumer
}
Consenter
接口是插件對(外部的)客戶端請求的入口,當(dāng)處理共識時(shí)植影,共識消息在內(nèi)部(如從共識模塊)產(chǎn)生裳擎。NewConsenter創(chuàng)建
Consenter插件。
RecvMsg`以到達(dá)共識的順序來處理進(jìn)來的交易思币。
閱讀下面的helper.HandleMessage
來理解 peer 是如何和這個(gè)接口來交互的鹿响。
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
對象實(shí)現(xiàn)的谷饿』涛遥回想一下這個(gè)對象是:
- 在
helper.NewConsensusHandler
被調(diào)用時(shí)初始化的 - 當(dāng)它們的插件構(gòu)造了
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)
}
這個(gè)接口是consensus.CPI
接口的一部分博投。它是用來獲取網(wǎng)絡(luò)中驗(yàn)證 peer 的(GetNetworkHandles
)句柄绸贡,以及那些驗(yàn)證 peer 的明細(xì)(GetNetworkInfo
):
注意peers由pb.PeerID
對象確定。這是一個(gè)protobuf消息,當(dāng)前定義為(注意這個(gè)定義很可能會被修改):
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
}
這個(gè)接口是consensus.CPI
接口的一部分恃轩。它是用來與網(wǎng)絡(luò)上其它 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
}
這個(gè)接口是consensus.CPI
接口的一部分结洼。它用來處理消息簽名(Sign
)的加密操作和驗(yàn)證簽名(Verify
)
3.4.6 LedgerStack
接口
定義:
type LedgerStack interface {
Executor
Ledger
RemoteLedgers
}
CPI
接口的主要成員,LedgerStack
組與fabric的其它部分與共識相互作用叉跛,如執(zhí)行交易松忍,查詢和更新總賬。這個(gè)接口支持對本地區(qū)塊鏈和狀體的查詢筷厘,更新本地區(qū)塊鏈和狀態(tài)鸣峭,查詢共識網(wǎng)絡(luò)上其它節(jié)點(diǎn)的區(qū)塊鏈和狀態(tài)。它是由Executor
, Ledger
和RemoteLedgers
這三個(gè)接口組成的酥艳。下面會描述它們摊溶。
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
接口最常使用的部分,且是共識網(wǎng)絡(luò)工作的必要部分充石。接口允許交易啟動(dòng)莫换,執(zhí)行,根據(jù)需要回滾骤铃,預(yù)覽和提交拉岁。這個(gè)接口由下面這些方法組成。
3.4.7.1 開始批量交易
BeginTxBatch(id interface{}) error
這個(gè)調(diào)用接受任意的惰爬,故意含糊的id
喊暖,來使得共識插件可以保證與這個(gè)具體的批量相關(guān)的交易才會被執(zhí)行。例如:在pbft實(shí)現(xiàn)中撕瞧,這個(gè)id
是被執(zhí)行交易的編碼過的哈希陵叽。
3.4.7.2 執(zhí)行交易
ExecTXs(id interface{}, txs []*pb.Transaction) ([]byte, []error)
這個(gè)調(diào)用根據(jù)總賬當(dāng)前的狀態(tài)接受一組交易,并返回帶有對應(yīng)著交易組的錯(cuò)誤信息組的當(dāng)前狀態(tài)的哈希丛版。注意一個(gè)交易所產(chǎn)生的錯(cuò)誤不影響批量交易的安全提交巩掺。當(dāng)遇到失敗所采用的策略取決與共識插件的實(shí)現(xiàn)。這個(gè)接口調(diào)用多次是安全的页畦。
3.4.7.3 提交與回滾交易
RollbackTxBatch(id interface{}) error
這個(gè)調(diào)用中止了批量執(zhí)行锌半。這會廢棄掉對當(dāng)前狀態(tài)的操作,并把總賬狀態(tài)回歸到之前的狀態(tài)寇漫。批量是從BeginBatchTx
開始的,如果需要開始一個(gè)新的就需要在執(zhí)行任意交易之前重新創(chuàng)建一個(gè)殉摔。
PreviewCommitTxBatchBlock(id interface{}, transactions []*pb.Transaction, metadata []byte) (*pb.Block, error)
這個(gè)調(diào)用是共識插件對非確定性交易執(zhí)行的測試時(shí)最有用的方法州胳。區(qū)塊返回的哈希表部分會保證,當(dāng)CommitTxBatch
被立即調(diào)用時(shí)的區(qū)塊是同一個(gè)逸月。這個(gè)保證會被任意新的交易的執(zhí)行所打破栓撞。
CommitTxBatch(id interface{}, transactions []*pb.Transaction, transactionsResults []*pb.TransactionResult, metadata []byte) error
這個(gè)調(diào)用提交區(qū)塊到區(qū)塊鏈中。區(qū)塊必須以全序提交到區(qū)塊鏈中,CommitTxBatch
結(jié)束批量交易瓤湘,在執(zhí)行或提交任意的交易之前必須先調(diào)用BeginTxBatch
瓢颅。
3.4.8 Ledger
接口
定義:
type Ledger interface {
ReadOnlyLedger
UtilLedger
WritableLedger
}
Ledger
接口是為了允許共識插件詢問或可能改變區(qū)塊鏈當(dāng)前狀態(tài)。它是由下面描述的三個(gè)接口組成的
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
接口是為了查詢總賬的本地備份弛说,而不會修改它挽懦。它是由下面這些函數(shù)組成的。
GetBlockchainSize() (uint64, error)
這個(gè)函數(shù)返回區(qū)塊鏈總賬的長度木人。一般來說信柿,這個(gè)函數(shù)永遠(yuǎn)不會失敗,在這種不太可能發(fā)生情況下醒第,錯(cuò)誤被傳遞給調(diào)用者渔嚷,由它確定是否需要恢復(fù)。具有最大區(qū)塊值的區(qū)塊的值為GetBlockchainSize()-1
注意在區(qū)塊鏈總賬的本地副本是腐壞或不完整的情況下稠曼,這個(gè)調(diào)用會返回鏈中最大的區(qū)塊值+1形病。這允許節(jié)點(diǎn)在舊的塊是腐壞或丟失的情況下能繼續(xù)操作當(dāng)前狀態(tài)/塊。
GetBlock(id uint64) (block *pb.Block, err error)
這個(gè)調(diào)用返回區(qū)塊鏈中塊的數(shù)值id
霞幅。一般來說這個(gè)調(diào)用是不會失敗的漠吻,除非請求的區(qū)塊超出當(dāng)前區(qū)塊鏈的長度,或者底層的區(qū)塊鏈被腐壞了蝗岖。GetBlock
的失敗可能可以通過狀態(tài)轉(zhuǎn)換機(jī)制來取回它侥猩。
GetCurrentStateHash() (stateHash []byte, err error)
這個(gè)調(diào)用返回總賬的當(dāng)前狀態(tài)的哈希。一般來說抵赢,這個(gè)函數(shù)永遠(yuǎn)不會失敗欺劳,在這種不太可能發(fā)生情況下,錯(cuò)誤被傳遞給調(diào)用者铅鲤,由它確定是否需要恢復(fù)划提。
3.4.8.2 UtilLedger
接口
定義:
type UtilLedger interface {
HashBlock(block *pb.Block) ([]byte, error)
VerifyBlockchain(start, finish uint64) (uint64, error)
}
UtilLedger
接口定義了一些由本地總賬提供的有用的功能。使用mock接口來重載這些功能在測試時(shí)非常有用邢享。這個(gè)接口由兩個(gè)函數(shù)構(gòu)成鹏往。
會會
HashBlock(block *pb.Block) ([]byte, error)
盡管*pb.Block
定義了GetHash
方法,為了mock測試骇塘,重載這個(gè)方法會非常有用伊履。因此,建議GetHash
方法不直接調(diào)用款违,而是通過UtilLedger.HashBlock
接口來調(diào)用這個(gè)方法唐瀑。一般來說,這個(gè)函數(shù)永遠(yuǎn)不會失敗插爹,但是錯(cuò)誤還是會傳遞給調(diào)用者哄辣,讓它決定是否使用適當(dāng)?shù)幕謴?fù)请梢。
VerifyBlockchain(start, finish uint64) (uint64, error)
這個(gè)方法是用來校驗(yàn)區(qū)塊鏈中的大的區(qū)域。它會從高的塊start
到低的塊finish
力穗,返回第一個(gè)塊的PreviousBlockHash
與塊的前一個(gè)塊的哈希不相符的塊編號以及錯(cuò)誤信息毅弧。注意,它一般會標(biāo)識最后一個(gè)好的塊的編號当窗,而不是第一個(gè)壞的塊的編號够坐。
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
接口允許調(diào)用者更新區(qū)塊鏈。注意這NOT 不是共識插件的通常用法超全。當(dāng)前的狀態(tài)需要通過Executor
接口執(zhí)行交易來修改咆霜,新的區(qū)塊在交易提交時(shí)生成。相反的嘶朱,這個(gè)接口主要是用來狀態(tài)改變和腐化恢復(fù)蛾坯。特別的,這個(gè)接口下的函數(shù)永遠(yuǎn)不能直接暴露給共識消息疏遏,這樣會導(dǎo)致打破區(qū)塊鏈所承諾的不可修改這一概念脉课。這個(gè)結(jié)構(gòu)包含下面這些函數(shù)。
? PutBlock(blockNumber uint64, block *pb.Block) error
? 這個(gè)函數(shù)根據(jù)給定的區(qū)塊編號把底層區(qū)塊插入到區(qū)塊鏈中财异。注意這是一個(gè)不安全的接口倘零,所以它不會有錯(cuò)誤返回或返回。插入一個(gè)比當(dāng)前區(qū)塊高度更高的區(qū)塊是被允許的戳寸,同樣呈驶,重寫一個(gè)已經(jīng)提交的區(qū)塊也是被允許的。記住疫鹊,由于哈希技術(shù)使得創(chuàng)建一個(gè)鏈上的更早的塊是不可行的袖瞻,所以這并不影響鏈的可審計(jì)性和不可變性。任何嘗試重寫區(qū)塊鏈的歷史的操作都能很容易的被偵測到拆吆。這個(gè)函數(shù)一般只用于狀態(tài)轉(zhuǎn)移API聋迎。
? ApplyStateDelta(id interface{}, delta *statemgmt.StateDelta) error
這個(gè)函數(shù)接收狀態(tài)變化,并把它應(yīng)用到當(dāng)前的狀態(tài)枣耀。變化量的應(yīng)用會使得狀態(tài)向前或向后轉(zhuǎn)變霉晕,這取決于狀態(tài)變化量的構(gòu)造,與Executor
方法一樣捞奕,ApplyStateDelta
接受一個(gè)同樣會被傳遞給CommitStateDelta
or RollbackStateDelta
不透明的接口id
? CommitStateDelta(id interface{}) error
這個(gè)方法提交在ApplyStateDelta
中應(yīng)用的狀態(tài)變化牺堰。這通常是在調(diào)用者調(diào)用ApplyStateDelta
后通過校驗(yàn)由GetCurrentStateHash()
獲得的狀態(tài)哈希之后調(diào)用的。這個(gè)函數(shù)接受與傳遞給ApplyStateDelta
一樣的id
颅围。
? RollbackStateDelta(id interface{}) error
這個(gè)函數(shù)撤銷在ApplyStateDelta
中應(yīng)用的狀態(tài)變化量萌焰。這通常是在調(diào)用者調(diào)用ApplyStateDelta
后與由GetCurrentStateHash()
獲得的狀態(tài)哈希校驗(yàn)失敗后調(diào)用的。這個(gè)函數(shù)接受與傳遞給ApplyStateDelta
一樣的id
谷浅。
EmptyState() error
這個(gè)函數(shù)將會刪除整個(gè)當(dāng)前狀態(tài),得到原始的空狀態(tài)。這通常是通過變化量加載整個(gè)新的狀態(tài)時(shí)調(diào)用的一疯。這一般只對狀態(tài)轉(zhuǎn)移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
接口的存在主要是為了啟用狀態(tài)轉(zhuǎn)移,和向其它副本詢問區(qū)塊鏈的狀態(tài)墩邀。和WritableLedger
接口一樣掌猛,這不是給正常的操作使用,而是為追趕眉睹,錯(cuò)誤恢復(fù)等操作而設(shè)計(jì)的荔茬。這個(gè)接口中的所有函數(shù)調(diào)用這都有責(zé)任來處理超時(shí)。這個(gè)接口包含下面這些函數(shù):
GetRemoteBlocks(peerID uint64, start, finish uint64) (<-chan *pb.SyncBlocks, error)
這個(gè)函數(shù)嘗試從由peerID
指定的 peer 中取出由start
和finish
標(biāo)識的范圍中的*pb.SyncBlocks
流竹海。一般情況下慕蔚,由于區(qū)塊鏈必須是從結(jié)束到開始這樣的順序來驗(yàn)證的,所以start
是比finish
更高的塊編號斋配。由于慢速的結(jié)構(gòu)孔飒,其它請求的返回可能出現(xiàn)在這個(gè)通道中,所以調(diào)用者必須驗(yàn)證返回的是期望的塊艰争。第二次以同樣的peerID
來調(diào)用這個(gè)方法會導(dǎo)致第一次的通道關(guān)閉坏瞄。
GetRemoteStateSnapshot(peerID uint64) (<-chan *pb.SyncStateSnapshot, error)
這個(gè)函數(shù)嘗試從由peerID
指定的 peer 中取出*pb.SyncStateSnapshot
流。為了應(yīng)用結(jié)果甩卓,首先需要通過WritableLedger
的EmptyState
調(diào)用來清空存在在狀態(tài)鸠匀,然后順序應(yīng)用包含在流中的變化量。
GetRemoteStateDeltas(peerID uint64, start, finish uint64) (<-chan *pb.SyncStateDeltas, error)
這個(gè)函數(shù)嘗試從由peerID
指定的 peer 中取出由start
和finish
標(biāo)識的范圍中的*pb.SyncStateDeltas
流逾柿。由于慢速的結(jié)構(gòu)缀棍,其它請求的返回可能出現(xiàn)在這個(gè)通道中,所以調(diào)用者必須驗(yàn)證返回的是期望的塊變化量鹿寻。第二次以同樣的peerID
來調(diào)用這個(gè)方法會導(dǎo)致第一次的通道關(guān)閉睦柴。
3.4.10 controller
包
3.4.10.1 controller.NewConsenter
簽名:
func NewConsenter(cpi consensus.CPI) (consenter consensus.Consenter)
這個(gè)函數(shù)讀取為peer
過程指定的core.yaml
配置文件中的peer.validator.consensus
的值。鍵peer.validator.consensus
的有效值指定運(yùn)行noops
還是pbft
共識插件毡熏。(注意坦敌,它最終被改變?yōu)?code>noops或custom
。在custom
情況下痢法,驗(yàn)證 peer 將會運(yùn)行由consensus/config.yaml
中定義的共識插件)
插件的作者需要編輯函數(shù)體狱窘,來保證路由到它們包中正確的構(gòu)造函數(shù)。例如财搁,對于pbft
我們指向pbft.GetPlugin
構(gòu)造器蘸炸。
這個(gè)函數(shù)是當(dāng)設(shè)置返回信息處理器的consenter
域時(shí),被helper.NewConsensusHandler
調(diào)用的尖奔。輸入?yún)?shù)cpi
是由helper.NewHelper
構(gòu)造器輸出的搭儒,并實(shí)現(xiàn)了consensus.CPI
接口
3.4.11 helper
包
3.4.11.1 高層次概述
驗(yàn)證 peer 通過helper.NewConsesusHandler
函數(shù)(一個(gè)處理器工廠)穷当,為每個(gè)連接的 peer 建立消息處理器(helper.ConsensusHandler
)。每個(gè)進(jìn)來的消息都會檢查它的類型(helper.HandleMessage
)淹禾;如果這是為了共識必須到達(dá)的消息馁菜,它會傳遞到 peer 的共識對象(consensus.Consenter
)。其它的信息會傳遞到棧中的下一個(gè)信息處理器铃岔。
3.4.11.2 helper.ConsensusHandler
定義:
type ConsensusHandler struct {
chatStream peer.ChatStream
consenter consensus.Consenter
coordinator peer.MessageHandlerCoordinator
done chan struct{}
peerHandler peer.MessageHandler
}
共識中的上下文汪疮,我們只關(guān)注域coordinator
和consenter
。coordinator
就像名字隱含的那樣毁习,它被用來在 peer 的信息處理器之間做協(xié)調(diào)智嚷。例如,當(dāng) peer 希望Broadcast
時(shí)纺且,對象被訪問盏道。共識需要到達(dá)的共識者會接收到消息并處理它們。
注意隆檀,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)
創(chuàng)建一個(gè)helper.ConsensusHandler
對象恐仑。為每個(gè)coordinator
設(shè)置同樣的消息處理器泉坐。同時(shí)把consenter
設(shè)置為controller.NewConsenter(NewHelper(coord))
3.4.11.4 helper.Helper
定義:
type Helper struct {
coordinator peer.MessageHandlerCoordinator
}
包含驗(yàn)證peer的coordinator
的引用。對象是否為peer實(shí)現(xiàn)了consensus.CPI
接口裳仆。
3.4.11.5 helper.NewHelper
簽名:
func NewHelper(mhc peer.MessageHandlerCoordinator) consensus.CPI
返回coordinator
被設(shè)置為輸入?yún)?shù)mhc
(helper.ConsensusHandler
消息處理器的coordinator
域)的helper.Helper
對象腕让。這個(gè)對象實(shí)現(xiàn)了consensus.CPI
接口,從而允許插件與棧進(jìn)行交互歧斟。
3.4.11.6 helper.HandleMessage
回憶一下纯丸,helper.NewConsensusHandler
返回的helper.ConsesusHandler
對象實(shí)現(xiàn)了 peer.MessageHandler
接口:
type MessageHandler interface {
RemoteLedger
HandleMessage(msg *pb.Message) error
SendMessage(msg *pb.Message) error
To() (pb.PeerEndpoint, error)
Stop() error
}
在共識的上下文中,我們只關(guān)心HandleMessage
方法静袖。簽名:
func (handler *ConsensusHandler) HandleMessage(msg *pb.Message) error
這個(gè)函數(shù)檢查進(jìn)來的Message
的Type
觉鼻。有四種情況:
- 等于
pb.Message_CONSENSUS
:傳遞給處理器的consenter.RecvMsg
函數(shù)。 - 等于
pb.Message_CHAIN_TRANSACTION
(如:一個(gè)外部部署的請求): 一個(gè)響應(yīng)請求首先被發(fā)送給用戶队橙,然后把消息傳遞給consenter.RecvMsg
函數(shù) - 等于
pb.Message_CHAIN_QUERY
(如:查詢): 傳遞給helper.doChainQuery
方法來在本地執(zhí)行 - 其它: 傳遞給棧中下一個(gè)處理器的
HandleMessage
方法