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
└── vendor
26 directories, 13 files
peer命令概览
peer命令参数如下:
$ peer
2018-06-18 09:39:18.382 UTC [msp] getMspConfig -> INFO 001 Loading NodeOUs
Usage:
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 server
Use "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
└── version
9 directories, 2 files
很明显,每一个子命令对应了要给目录,而总的入口,则是main.go
main.go
现在,我们来看 main.go
首先,来个call graph:
可以看到,整个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,服务端和客户端建立了一种非常松散的耦合关系,值得学习。