0x00
在fabric中,peer是一個重要的二進位程式,其功能主要是提供peer
相關的操作,關於peer的概念,可以參考官方文檔1和 官方文檔2,peer
這個cli工具,作為一個用戶端,可以向區塊鏈網路(channel
)發起peer
相關從操作,這個命令包含很多的子命令,本文不會逐一介紹,這也不是本文的目的,本文主要是通過對peer
源碼的分析,介紹一下fabric
這個項目中,cli
工具與服務端通訊的”套路“
0x01 準備工作
- 一些
golang
的基本知識
- 對
rpc
通訊的基本原理或者概念有所瞭解(瞭解grpc
更好)
- 對
git
有基本的認識
下載代碼
fabric
的代碼目前在github
上有鏡像,通過:
git clone https://github.com/hyperledger/fabric.git
就可以將代碼下到本地
代碼結構如下:
$ tree -L 1.├── bccsp├── build├── CHANGELOG.md├── ci.properties├── cmd├── CODE_OF_CONDUCT.md├── common #公用工具源碼,例如configtxgen,cryptogen等├── CONTRIBUTING.md├── core # 主要代碼├── devenv├── discovery├── docker-env.mk├── docs├── events├── examples├── Gopkg.lock├── Gopkg.toml├── gossip├── gotools├── gotools.mk├── idemix # ibm idemix密碼├── images├── integration├── LICENSE├── Makefile├── msp├── orderer #orderer源碼├── peer # peer命令源碼├── protos # rpc源碼├── README.md├── release├── release_notes├── sampleconfig├── scripts├── settings.gradle├── si├── tox.ini├── unit-test└── vendor26 directories, 13 files
peer命令概覽
peer命令參數如下:
$ peer2018-06-18 09:39:18.382 UTC [msp] getMspConfig -> INFO 001 Loading NodeOUsUsage: peer [flags] peer [command]Available Commands: chaincode Operate a chaincode: install|instantiate|invoke|package|query|signpackage|upgrade|list. channel Operate a channel: create|fetch|join|list|update|signconfigtx|getinfo. logging Log levels: getlevel|setlevel|revertlevels. node Operate a peer node: start|status. version Print fabric peer version.Flags: -h, --help help for peer --logging-level string Default logging level and overrides, see core.yaml for full syntax -v, --version Display current version of fabric peer serverUse "peer [command] --help" for more information about a command.2018-06-18 09:39:18.415 UTC [main] main -> INFO 002 Exiting.....
想要使用peer
命令,可以直接通過 first network的 ./byfn.sh -m up
命令啟動一個樣本網路,然後通過docker exec -it peer0.org1.example.com bash
進入peer0的容器,然後執行 peer help
就能看到上面的列印了
可以看到,peer
一共有如下幾個子命令:
- chaincode
- channel
- node
- version
而當我們瀏覽peer
目錄下的代碼結構是,發現恰好存在這幾個目錄:
$ tree -L 1 .├── chaincode├── channel├── clilogging├── common├── gossip├── main.go├── main_test.go├── mocks├── node├── testdata└── version9 directories, 2 files
很明顯,每一個子命令對應了要給目錄,而總的入口,則是main.go
main.go
現在,我們來看 main.go
首先,來個call graph:
output.png
可以看到,整個main.go中,主要是對viper和cobra的一些API的調用,其中,下面的代碼通過AddCommand將各個子目錄的代碼,以子命令形式添加了進來:
// 首先import 各個子目錄的包import ( // other imports "github.com/hyperledger/fabric/peer/chaincode" "github.com/hyperledger/fabric/peer/channel" "github.com/hyperledger/fabric/peer/clilogging" "github.com/hyperledger/fabric/peer/common" "github.com/hyperledger/fabric/peer/node" "github.com/hyperledger/fabric/peer/version")// 一些準備工作func main() { // 環境變數初始化 mainCmd.AddCommand(version.Cmd()) mainCmd.AddCommand(node.Cmd()) mainCmd.AddCommand(chaincode.Cmd(nil)) mainCmd.AddCommand(clilogging.Cmd(nil)) mainCmd.AddCommand(channel.Cmd(nil)) // 其他的參數和配置解析 // 真正的命令執行 if mainCmd.Execute() != nil { os.Exit(1) }}
因此,要瞭解peer
命令,其實就是需要搞懂底下各個子命令的實現
chaincode包
在瞭解了fabric
命令的構造方式後(cobra+viper
初始化命令,然後掛載子命令),我們再來以chaincode
這個包作為一個例子,看看peer
是怎麼與服務端通訊的,首先,再次看到,chaincode
仍然有一系列的子命令:
- install
- instantiate
- invoke
- package
- query
- signpackage
- upgrade
- list
不過,這些命令是直接在chaincode包中實現的,例如install
命令,就對應了install.go
我們開啟這個源碼檔案,看到入口就是:
func installCmd(cf *ChaincodeCmdFactory) *cobra.Command { chaincodeInstallCmd = &cobra.Command{ Use: "install", Short: fmt.Sprint(installDesc), Long: fmt.Sprint(installDesc), ValidArgs: []string{"1"}, RunE: func(cmd *cobra.Command, args []string) error { var ccpackfile string if len(args) > 0 { ccpackfile = args[0] } return chaincodeInstall(cmd, ccpackfile, cf) }, }//...}
這裡,核心就是這個叫做RunE
的入口,這是cobra
的命令對象的入口函數,可以看到,這個屬性類型是一個函數,接受一個cobra.Command對象和參數字串,返回一個error對象,這個入口最終則調用了chaincodeInstall
這個函數。
於是,我們來看看chaincodeInstall
做了什麼:
func chaincodeInstall(cmd *cobra.Command, ccpackfile string, cf *ChaincodeCmdFactory) error { // 準備工作 var err error if cf == nil { //因此peer各個命令的cf參數都是nil,因此這裡是真正執行個體化cf的地方, //InitCmdFactory函數裡會根據cmd.Name()來填充一個grpc用戶端,每個命令的grpc用戶端都是不一樣的 cf, err = InitCmdFactory(cmd.Name(), true, false) if err != nil { return err } } // 準備工作 err = install(ccpackmsg, cf) return err}//install the depspec to "peer.address"func install(msg proto.Message, cf *ChaincodeCmdFactory) error { // 準備工作 proposalResponse, err := cf.EndorserClients[0].ProcessProposal(context.Background(), signedProp) if err != nil { return fmt.Errorf("Error endorsing %s: %s", chainFuncName, err) } if proposalResponse != nil { logger.Infof("Installed remotely %v", proposalResponse) } return nil}
可以看到,chaincodeInstall
最後調用了install
函數,而該函數最後則是調用了cf.EndorserClients[0].ProcessProposal
,這個函數的定義位於core/endorser/endorser.go
,這是一個grpc的服務端介面,服務定義在protos/peer/peer.proto
:
service Endorser { rpc ProcessProposal(SignedProposal) returns (ProposalResponse) {}}
入參訊息定義在protos/peer/proposal.proto
message SignedProposal { // The bytes of Proposal bytes proposal_bytes = 1; // Signaure over proposalBytes; this signature is to be verified against // the creator identity contained in the header of the Proposal message // marshaled as proposalBytes bytes signature = 2;}
返回訊息定義在protos/peer/proposal_response.proto
message ProposalResponse { // Version indicates message protocol version int32 version = 1; // Timestamp is the time that the message // was created as defined by the sender google.protobuf.Timestamp timestamp = 2; // A response message indicating whether the // endorsement of the action was successful Response response = 4; // The payload of response. It is the bytes of ProposalResponsePayload bytes payload = 5; // The endorsement of the proposal, basically // the endorser's signature over the payload Endorsement endorsement = 6;}
這樣一來,我們就大致理清了peer
命令的工作流程:
- 根據vip和cobra擷取配置資訊和命令列資訊
- 根據傳入的參數和配置,執行個體化各個命令的grpc用戶端
- 構造grpc訊息,並調用rpc方法,發送請求,並擷取訊息響應
- 根據響應,構造命令的輸出值
0x02 小結
本文簡單介紹了fabric
的用戶端工具的代碼流程,歸納總結一下fabric
中cli
的大致工作流程,可以看到,通過rpc通訊的方式,服務端和用戶端是一種非常鬆散的耦合關係,也可以為我們自己以後編寫相關cli程式提供一種思路。