0x00在fabric中,peer是一个重要的二进制程序,其功能主要是提供相关的操作,关于peer的概念,可以参考官方文档1和 官方文档2,这个cli工具,作为一个客户端,可以向区块链网络()发起相关从操作,这个命令包含很多的子命令,本文不会逐一介绍,这也不是本文的目的,本文主要是通过对源码的分析,介绍一下这个项目中,工具与服务端通信的”套路“
0x01 准备工作- 一些的基本知识
- 对通信的基本原理或者概念有所了解(了解更好)
- 对有基本的认识
[h1]下载代码[/h1]的代码目前在上有镜像,通过:- 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
复制代码 [h1]peer命令概览[/h1]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.....
复制代码 想要使用命令,可以直接通过 first network的命令启动一个示例网络,然后通过- docker exec -it peer0.org1.example.com bash
复制代码 进入peer0的容器,然后执行就能看到上面的打印了
可以看到,一共有如下几个子命令:
- chaincode
- channel
- node
- version
而当我们浏览目录下的代码结构是,发现恰好存在这几个目录:- $ tree -L 1 .├── chaincode├── channel├── clilogging├── common├── gossip├── main.go├── main_test.go├── mocks├── node├── testdata└── version9 directories, 2 files
复制代码 很明显,每一个子命令对应了要给目录,而总的入口,则是[h2]main.go[/h2]现在,我们来看 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) }}
复制代码 因此,要了解命令,其实就是需要搞懂底下各个子命令的实现
[h2]chaincode包[/h2]在了解了命令的构造方式后(初始化命令,然后挂载子命令),我们再来以这个包作为一个例子,看看是怎么与服务端通信的,首先,再次看到,仍然有一系列的子命令:
- install
- instantiate
- invoke
- package
- query
- signpackage
- upgrade
- list
不过,这些命令是直接在chaincode包中实现的,例如命令,就对应了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) }, }//...}
复制代码 这里,核心就是这个叫做的入口,这是的命令对象的入口函数,可以看到,这个属性类型是一个函数,接受一个cobra.Command对象和参数字符串,返回一个error对象,这个入口最终则调用了这个函数。
于是,我们来看看做了什么:- 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}
复制代码 可以看到,最后调用了函数,而该函数最后则是调用了- cf.EndorserClients[0].ProcessProposal
复制代码 ,这个函数的定义位于- core/endorser/endorser.go
复制代码 ,这是一个grpc的服务端接口,服务定义在:- 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;}
复制代码 这样一来,我们就大致理清了命令的工作流程:
- 根据vip和cobra获取配置信息和命令行信息
- 根据传入的参数和配置,实例化各个命令的grpc客户端
- 构造grpc消息,并调用rpc方法,发送请求,并获取消息响应
- 根据响应,构造命令的输出值
0x02 小结本文简单介绍了的客户端工具的代码流程,归纳总结了一下中cli工具的大致工作流程,可以看到,通过rpc,服务端和客户端建立了一种非常松散的耦合关系,值得学习。
|
|