hyperledger fabric 源码解析-peer(1)

作者: 飞起一砣子 | 来源:发表于2018-06-18 23:09 被阅读24次

    0x00

    在fabric中,peer是一个重要的二进制程序,其功能主要是提供peer相关的操作,关于peer的概念,可以参考官方文档1官方文档2peer这个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中,主要是对vipercobra的一些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)

        本文链接:https://www.haomeiwen.com/subject/jhlvjftx.html