先前分析程式著眼於細節分析,這樣沒有框架的概念,花了兩天時間分析整理了一下hyperledger fabric的架構設計,分析該程式沒有參照任何資料,如有錯誤歡迎指正,共同進步。
筆者在詳細分析程式前有以下疑問:
1)cli(命令列)客戶端如何傳送命令給peer節點
2)本peer節點如何接收其他節點的資料,接收到資料又如何處理,處理的方式和1又有什麼區別
3)資料是何時又是如何被送入consensus模組
4)consensus模組內部又是如何架構的 為什麼看起來helper executor pbft controller資料夾交至在一起,儲存各自控制代碼,相互呼叫
5)chaincode(鏈碼,簡稱cc)是如何接收到peer對其的操作、訪問的
6)chaincode是如何呼叫fabric api來查詢寫入資料的
7)在閱讀原始碼初始化過程中,peer節點會建立大量server,這些server後續過程我們是如何使用的
注:本人對於資料庫、docker相關知識不是很瞭解,儘量避免關於這兩個部分的介紹以免錯誤的引導讀者。
下面會慢慢的滲透以上涉及的問題。
server :
每個server作用:
adminserver:控制該節點的命運,可以刪除該節點所在的程序。(start stop getstatus )
eventhubserver:peer節點支援客戶端對指定事件進行監聽,例如rejection等。客戶端需要先註冊自己關心的events,當事件發生時trigger 監聽者。
openchainserver:對外提供ledger的訪問介面,涉及getblockchaininfo getblockbynumber等。
devopsserver:負責與cli client對接,外部進行cc操作的入口,deploy invoke query。
chaincodesupportserver:負責與shim/chaincode通訊,chaincode的所有呼叫接收傳送都要與該server資訊互動。
peerserver:該server是一個engine,engine關聯了內部訊息響應實現,同時為周圍peer節點建立client與之通訊。
restserver:該server沒有進行分析,應該是rest介面格式相關。
一級模組分類:
client: 之前建立伺服器與之對應的客戶端,可以理解成其他節點或者cli client等。
protos: 中間層,server與client端 api介面定義
serverprocess:服務響應處理函式,包括各型別的handlemessage。
consensus: 共識模組,目前採用的是pbft noops
chaincode shim:程式碼中shim和我理解的不一致,將chaincodesupport也應該算到shim,該模組的作用是連線peer節點與chaincode的媒介,用shim形容也可。
chaincode: 鏈碼,應用(例如智慧合約)。
db: 資料儲存。
library: 程式碼裡有一個叫做vendor的資料夾,該資料夾裡涉及的功能模組自成一體,例如grpcserver等
api: chaincode裡面會呼叫peer節點資訊。
crypto: 伴隨著資料加解密。
ledger: 賬本操作。
該程式碼使用handler觸發模式,在跟蹤程式碼程式時要注意handler物件賦值位置,否則容易找錯handlemessage,這些handler處理函式命名基本相同,容易操作混亂。
下面分析幾個讀者應該最關心的流程:
1)client透過cli執行一條invoke命令
2)某節點傳送給該節點viewchange命令
3)chaincode呼叫api putstatus
4)consensus流程
一、 client透過cli執行一條invoke命令
1)在peer節點初始化的時候 建立devopsserver
serverdevops := core.newdevopsserver(peerserver)
pb.registerdevopsserver(grpcserver, serverdevops)
2)devopsserver設定service規範,例如invoke message,呼叫_devops_invoke_handler函式
var _devops_servicedesc = grpc.servicedesc{
servicename: "protos.devops",
handlertype: (*devopsserver)(nil),
methods: []grpc.methoddesc{
{
methodname: "login",
handler: _devops_login_handler,
},
{
methodname: "build",
handler: _devops_build_handler,
},
{
methodname: "deploy",
handler: _devops_deploy_handler,
},
{
methodname: "invoke",
handler: _devops_invoke_handler,
},
{
methodname: "query",
handler: _devops_query_handler,
},
{
methodname: "exp_getapplicationtcert",
handler: _devops_exp_getapplicationtcert_handler,
},
{
methodname: "exp_preparefortx",
handler: _devops_exp_preparefortx_handler,
},
{
methodname: "exp_producesigma",
handler: _devops_exp_producesigma_handler,
},
{
methodname: "exp_executewithbinding",
handler: _devops_exp_executewithbinding_handler,
},
},
streams: []grpc.streamdesc{},
}
3)其中_devops_invoke_handler函式在protos模組,其負責將client接入的資訊傳遞到對應的server模組
func _devops_invoke_handler(srv interface{}, ctx context.context, dec func(interface{}) error) (interface{}, error) {
in := new(chaincodeinvocationspec)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(devopsserver).invoke(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
4)在函式在devops服務端程式碼中處理
func (d *devops) invoke(ctx context.context, chaincodeinvocationspec *pb.chaincodeinvocationspec) (*pb.response, error) {
return d.invokeorquery(ctx, chaincodeinvocationspec, chaincodeinvocationspec.chaincodespec.attributes, true)
}
5)精簡invokeorquery程式碼,d.coord 是peerserver物件,executetransaction 是對應engine的實現方法
func (d *devops) invokeorquery(ctx context.context, chaincodeinvocationspec *pb.chaincodeinvocationspec, attributes []string, invoke bool) (*pb.response, error) {
resp := d.coord.executetransaction(transaction)
}
6)本次請求被封裝成交易struct,該處理是在peerserver中。
func (p *impl) executetransaction(transaction *pb.transaction) (response *pb.response) {
if p.isvalidator {
response = p.sendtransactionstolocalengine(transaction)
} else {
peeraddresses := p.dischelper.getrandomnodes(1)
response = p.sendtransactionstopeer(peeraddresses[0], transaction)
}
return response
}
7)思考可知,最終這筆transaction是要交給到consensus進行處理,那麼如何傳遞的呢?就在下面p.engine.processtransactionmsg,其中"p"代指peerserver,engine是在建立peerserver的時候指定的engine,而這個engine的handler實現在consensus裡,在實現enginehandler過程中載入了pbft演算法。所以processtransactionmsg函式的實現在consensus模組engine程式碼裡。這樣解決了開始時提出的疑問3)。
func (p *impl) sendtransactionstolocalengine(transaction *pb.transaction) *pb.response {
peerlogger.debugf("marshalling transaction %s to send to local engine", transaction.type)
data, err := proto.marshal(transaction)
if err != nil {
return &pb.response{status: pb.response_failure, msg: []byte(fmt.sprintf("error sending transaction to local engine: %s", err))}
}
var response *pb.response
msg := &pb.message{type: pb.message_chain_transaction, payload: data, timestamp: util.createutctimestamp()}
peerlogger.debugf("sending message %s with timestamp %v to local engine", msg.type, msg.timestamp)
response = p.engine.processtransactionmsg(msg, transaction)
return response
}
8)從這裡開始進入了consensus內部處理,在這裡consensus模組是單獨分析。
func (eng *engineimpl) processtransactionmsg(msg *pb.message, tx *pb.transaction) (response *pb.response) {
err := eng.consenter.recvmsg(msg, eng.peerendpoint.id)
}
畫圖說明上述流程:
該圖中沒有體現的一點是在devops server建立的時候將peerserver物件作為構造引數傳入,而peerserver建立的過程就是建立engine的過程,也是載入engine-handler的過程,而engine-handler的實現在consensus模組。圖中直接從devops server 跳入consensus模組有些突兀。