Fabric 1.4源码分析 - chaincode instantiate(1)peer端的初始化过程

推荐参考两个博客:

选择peer chaincode instantiate作为开篇,是因为笔者学习官网过程中没有完全理解install与instantiate的区别,所以选择深入源码学习来加深理解

官网Building Your First Network例子中,命令为

1
peer chaincode instantiate -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a", "100", "b","200"]}' -P "AND ('Org1MSP.peer','Org2MSP.peer')"

此前,需要先设置环境变量

1
2
3
4
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
CORE_PEER_ADDRESS=peer0.org2.example.com:7051
CORE_PEER_LOCALMSPID="Org2MSP"
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt

客户端调用peer chaincode instatiate命令部署chaincode,代码在‘peer/chaincode/instantiate.go’这个文件下,命令关联的方法是chaincodeDeploy(使用了流行的第三方命令行cobra库,具体可自行学习)。

1
2
3
4
5
6
7
8
9
10
func chaincodeDeploy(cmd *cobra.Command, args []string, cf *ChaincodeCmdFactory) error {
if cf == nil {
cf, err = InitCmdFactory(cmd.Name(), true, true)
}
defer cf.BroadcastClient.Close()

env, err := instantiate(cmd, cf)

err = cf.BroadcastClient.Send(env)
}

这个方法主要分为3个步骤(源码只列出关键步骤,省略判断逻辑及错误处理等,下同)。

  1. 入参cf *ChaincodeCmdFactory为nil,未初始化。(可回溯到peer启动peer/main.go#main时看到mainCmd.AddCommand(chaincode.Cmd(nil)))。初始化chaincode命令工厂,包含客户端及辅助类。这个方法在所有的chaincode命令前都先执行,入参有所不同。
    1
    2
    3
    4
    5
    6
    7
    8
    // ChaincodeCmdFactory holds the clients used by ChaincodeCmd
    type ChaincodeCmdFactory struct {
    EndorserClients []pb.EndorserClient // 与endorser交互的客户端
    DeliverClients []api.PeerDeliverClient //
    Certificate tls.Certificate //
    Signer msp.SigningIdentity
    BroadcastClient common.BroadcastClient // 与orderer交互的客户端
    }
    下面关注下初始化的过程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    // peer/chaincode/common.go
    func InitCmdFactory(cmdName string, isEndorserRequired, isOrdererRequired bool) (*ChaincodeCmdFactory, error) {
    if isEndorserRequired {
    if err = validatePeerConnectionParameters(cmdName); err != nil {}

    for i, address := range peerAddresses {
    endorserClient, err := common.GetEndorserClientFnc(address, tlsRootCertFile)
    endorserClients = append(endorserClients, endorserClient)

    deliverClient, err := common.GetPeerDeliverClientFnc(address, tlsRootCertFile)
    deliverClients = append(deliverClients, deliverClient)
    }
    }
    certificate, err := common.GetCertificateFnc()

    signer, err := common.GetDefaultSignerFnc()

    var broadcastClient common.BroadcastClient
    if isOrdererRequired {
    endorserClient := endorserClients[0]
    orderingEndpoints, err := common.GetOrdererEndpointOfChainFnc(channelID, signer, endorserClient)

    // override viper env
    viper.Set("orderer.address", orderingEndpoints[0])

    broadcastClient, err = common.GetBroadcastClientFnc()
    }

    return &ChaincodeCmdFactory{
    EndorserClients: endorserClients,
    DeliverClients: deliverClients,
    Signer: signer,
    BroadcastClient: broadcastClient,
    Certificate: certificate,
    }, nil
    }
  2. 入参为cmdName(命令名) string(命令名), isEndorserRequired(是否需要初始化endorser client), isOrdererRequired(是否需要初始化orderer broadcast client) bool。这个是根据命令传参,例如instantiate部署chaincode既需要endorser背书,也需要orderer排序打包并且扩散全网,而install安装则只需要背书。
  3. instantiate需要背书,isEndorserRequired=truevalidatePeerConnectionParameters校验命令参数。例如,校验peerAddresses个数及相应的tlsRootCertFiles匹配。注意,这里有// currently only support multiple peer addresses for invoke注释,对非invoke方法只能传入一个peerAddress,如install,instantiate。这也说明,这些操作只能作用于单个节点,而且下面也可以看到只需要单个节点的背书。
  4. GetEndorserClientFncGetPeerDeliverClientFnc方法非常类似。都是构造以下这个client(主要是grpc client, hyperledger fabric使用grpc作为rpc方案),数据结构完全一致,只是后续的用途有所不同。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // GetEndorserClient returns a new endorser client. If the both the address and
    // tlsRootCertFile are not provided, the target values for the client are taken
    // from the configuration settings for "peer.address" and
    // "peer.tls.rootcert.file"
    func GetEndorserClient(address, tlsRootCertFile string) (pb.EndorserClient, error) {
    if address != "" {
    // 从命令行传入peer address, --peerAddresses, --tlsRootCertFiles
    peerClient, err = NewPeerClientForAddress(address, tlsRootCertFile)
    } else {
    // 从环境变量读取peer address
    peerClient, err = NewPeerClientFromEnv()
    }
    return peerClient.Endorser()
    }

    &PeerClient{
    commonClient: commonClient{
    GRPCClient: gClient, // grpc client. 工程使用grpc作为节点间的远程调用方案
    address: address, // “peer.address”
    sn: override // "peer.tls.serverhostoverride"

    在上面的例子中,命令里并没有提供peerAddresstlsRootCertFile这两个参数,因此从环境变量里读取。在源码中,使用了流行的第三方库viper来进行配置加载和管理。也就是我们要先设置的变量CORE_PEER_ADDRESSCORE_PEER_TLS_ROOTCERT_FILE

  5. 生成客户端X509证书GetCertificateFnc。客户端通信时是否需要校验X509证书是由环境变量peer.tls.enabledpeer.tls.clientAuthRequired这两个参数决定的。在该tutorial例子中并没有设置。如果需要校验,则另外需要提供两个参数peer.tls.clientKey.filepeer.tls.clientCert.file,分别对应节点的public/private key文件。需要说明的是,在前面构造的endorseClient和peerDeliverClient过程中,也已经读取这两个参数和文件,生成X509 key pair,可以进行tls通信。这里的方法也是构建peerClient,获取该key pair。(TODO:以后专题分析加密)

  6. GetDefaultSignerFnc调用本地MSPmspmgmt.GetLocalMSP().GetDefaultSigningIdentity()获取signer,后续用于发送的proposal进行签名,构造signedProposal。(TODO:以后专题分析msp成员管理服务)

  7. isOrdererRequired这个参数初始化时候传入,根据后续操作是否需要与orderer交互乃至全网同步而是否选择初始化。chaincode instantiate需要全网同步,因此需要初始化。命令行-o参数指定orderer地址,如果没有指定,则通过GetOrdererEndpointOfChainFnc获取orderer地址。这个是通过调用cscc(configuration system chaincode,配置系统链码)的GetConfigBlock方法(参数为channelId)实现的。流程与一般的chaincode调用相似。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    func GetOrdererEndpointOfChain(chainID string, signer msp.SigningIdentity, endorserClient pb.EndorserClient) ([]string, error) {
    // query cscc for chain config block
    invocation := &pb.ChaincodeInvocationSpec{
    ChaincodeSpec: &pb.ChaincodeSpec{
    Type: pb.ChaincodeSpec_Type(pb.ChaincodeSpec_Type_value["GOLANG"]),
    ChaincodeId: &pb.ChaincodeID{Name: "cscc"},
    Input: &pb.ChaincodeInput{Args: [][]byte{[]byte(cscc.GetConfigBlock), []byte(chainID)}},
    },
    }

    creator, err := signer.Serialize()
    prop, _, err := putils.CreateProposalFromCIS(pcommon.HeaderType_CONFIG, "", invocation, creator)
    signedProp, err := putils.GetSignedProposal(prop, signer)

    proposalResp, err := endorserClient.ProcessProposal(context.Background(), signedProp)

    // parse config block
    block, err := putils.GetBlockFromBlockBytes(proposalResp.Response.Payload)
    envelopeConfig, err := putils.ExtractEnvelope(block, 0)
    bundle, err := channelconfig.NewBundleFromEnvelope(envelopeConfig)
    return bundle.ChannelConfig().OrdererAddresses(), nil
    }
  • 构造ChaincodeInvocationSpeccis描述该chaincode请求,包含请求的语言(GOLAND),chaincodeId(“cscc”),具体请求Input(cscc.GetConfigBlock)等。这个请求体格式通用于所有的系统及普通chaincode。然后,构造proplsal,并通过上面MSP获取的signer进行签名,在被调用方需要依次判断该节点是否有权限调用chaincode。
  • 接着,通过上面构建等endorserClient进行grpc调用grpc.Invoke(ctx, "/protos.Endorser/ProcessProposal", in, out, c.cc, opts...),简单介绍下参数(ctx context.Context,in *SignedProposal, opts …grpc.CallOption, out ProposalResponse, c.cc endorser.grpc.ClientConn)。此处grpc调用的方法是/protos.Endorser/ProcessProposal,endorser端校验,调用cscc,得到结果ProposalResponse返回。这个后面详述。
  • 最后,从rpc调用的结果bytes依次反序列化成block,获取并反序列化block中第一个envelope,把envelope的payload反序列化成configtx,并且获取相应的信息channelConfig,最后从channelConfig中获取到OrdererAddresses。(这里的结构处理是根据cscc的GetConfigBlock调用结果)选择第一个orderer的地址重写为系统变量orderer.address。(TODO:这里是否会把所有的请求都集中到第一个orderer有待研究cscc的方法)
  1. GetBroadcastClientFnc与上面的GetEndorserClient主要流程非常相似,主要区别有两点。第一,GetEndorserClient构建的client,使用的是peer的设置,而GetBroadcastClientFnc获取的是orderer的设置,从环境变量里获取构造参数,configFromEnv("orderer"),这里orderer.address这个参数正是上面的方法从chaincode config block里获取并设置为环境变量的。第二,调用了orderClient.Broadcast(),这里grpc.NewClientStream(ctx, &_AtomicBroadcast_serviceDesc.Streams[0], c.cc, "/orderer.AtomicBroadcast/Broadcast", opts...)直接指明了grpc的远程调用方法为"/orderer.AtomicBroadcast/Broadcast".

至此,完成了chaincode调用的初始化构造过程。后续的chaincode调用就可以直接使用此过程中构建的client及signer等执行不同的操作。