hyperledger fabric 源码解析-peer(1) cli的套路

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:

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命令的工作流程:

  1. 根据vip和cobra获取配置信息和命令行信息
  2. 根据传入的参数和配置,实例化各个命令的grpc客户端
  3. 构造grpc消息,并调用rpc方法,发送请求,并获取消息响应
  4. 根据响应,构造命令的输出值

0x02 小结

本文简单介绍了fabric的客户端工具的代码流程,归纳总结了一下fabric中cli工具的大致工作流程,可以看到,通过rpc,服务端和客户端建立了一种非常松散的耦合关系,值得学习。

你可能感兴趣的:(hyperledger fabric 源码解析-peer(1) cli的套路)