看了看客户端安装链码的部分,感受仍是比较简单的,因此在这里记录一下。
仍是先给出安装链码所使用的命令好了,这里就使用官方的安装链码的一个例子:html
#-n 指定mycc是由用户定义的链码名字,-v 指定1.0是链码的版本,-p ...是指定链码的路径 peer chaincode install -n mycc -v 1.0 -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02
整个流程的切入点依旧是fabric/peer/main.go
文件中,在main()
方法中第47行:git
mainCmd.AddCommand(chaincode.Cmd(nil))
这里就包含了Peer节点关于操做链码的全部相关命令,点进行看一下,转到了peer/chaincode/chaincode.go
文件中第49行:github
func Cmd(cf *ChaincodeCmdFactory) *cobra.Command { #这里的命令应该是比较熟悉的 addFlags(chaincodeCmd) chaincodeCmd.AddCommand(installCmd(cf)) #这一个就是执行链码的安装 chaincodeCmd.AddCommand(instantiateCmd(cf)) #链码的实例化 chaincodeCmd.AddCommand(invokeCmd(cf)) #链码的调用,具体调用什么方法要看链码是怎么写的 chaincodeCmd.AddCommand(packageCmd(cf, nil)) #链码的打包,暂时尚未使用过 chaincodeCmd.AddCommand(queryCmd(cf)) #对链码数据进行查询,这个只是向指定的Peer节点请求查询数据,不会生成交易最后打包区块的 chaincodeCmd.AddCommand(signpackageCmd(cf)) #对已打包的链码进行签名操做 chaincodeCmd.AddCommand(upgradeCmd(cf)) #更新链码,以前提到过 -v是指定链码的版本,若是须要对链码进行更新的话,使用这条命令,比较经常使用 chaincodeCmd.AddCommand(listCmd(cf)) #若是已指定通道的话,则查询已实例化的链码,不然查询当前Peer节点已安装的链码 return chaincodeCmd }
咱们这里只对链码的安装部分进行相关的说明,其余的之后再说了,点进去安装链码的那条命令,转到了peer/chaincode/install.go
文件中的第33行:json
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) }, } #这个就是能够在安装链码的命令中指定的相关参数 flagList := []string{ "lang", "ctor", "path", "name", "version", "peerAddresses", "tlsRootCertFiles", "connectionProfile", } attachFlags(chaincodeInstallCmd, flagList) return chaincodeInstallCmd }
看一下chaincodeInstall()
方法:api
func chaincodeInstall(cmd *cobra.Command, ccpackfile string, cf *ChaincodeCmdFactory) error { cmd.SilenceUsage = true var err error if cf == nil { #若是ChaincodeCmdFactory为空,则初始化一个, cf, err = InitCmdFactory(cmd.Name(), true, false) =================ChaincodeCmdFactory================== #ChaincodeCmdFactory结构体 type ChaincodeCmdFactory struct { EndorserClients []pb.EndorserClient #用于向背书节点发送消息 DeliverClients []api.PeerDeliverClient #用于与Order节点通讯 Certificate tls.Certificate #TLS证书相关 Signer msp.SigningIdentity #用于消息的签名 BroadcastClient common.BroadcastClient #用于广播消息 } =================ChaincodeCmdFactory================== if err != nil { return err } } var ccpackmsg proto.Message #这个地方有两种状况,链码多是根据传入参数从本地链码源代码文件读取,也有多是由其余节点签名打包完成发送过来的,这种方式尚未使用过 if ccpackfile == "" { #这里是从本地链码源代码文件读取 if chaincodePath == common.UndefinedParamValue || chaincodeVersion == common.UndefinedParamValue || chaincodeName == common.UndefinedParamValue { return fmt.Errorf("Must supply value for %s name, path and version parameters.", chainFuncName) } #看一下这个方法,生成ChaincodeDeploymentSpce ccpackmsg, err = genChaincodeDeploymentSpec(cmd, chaincodeName, chaincodeVersion) if err != nil { return err } }
genChaincodeDeploymentSpec()
这个方法在99行:网络
func genChaincodeDeploymentSpec(cmd *cobra.Command, chaincodeName, chaincodeVersion string) (*pb.ChaincodeDeploymentSpec, error) { #首先根据链码名称与链码版本查找当前链码是否已经安装过,若是安装过则返回链码已存在的错误 if existed, _ := ccprovider.ChaincodePackageExists(chaincodeName, chaincodeVersion); existed { return nil, fmt.Errorf("chaincode %s:%s already exists", chaincodeName, chaincodeVersion) } #获取链码标准数据结构 spec, err := getChaincodeSpec(cmd) if err != nil { return nil, err } #获取链码部署标准数据结构 cds, err := getChaincodeDeploymentSpec(spec, true) if err != nil { return nil, fmt.Errorf("error getting chaincode code %s: %s", chaincodeName, err) } return cds, nil }
看一下getChaincodeSpec()
方法,在peer/chaincode/common.go
文件中第69行:数据结构
func getChaincodeSpec(cmd *cobra.Command) (*pb.ChaincodeSpec, error) { #首先定义了个链码标准数据结构 ===========================ChaincodeSpec=========================== type ChaincodeSpec struct { Type ChaincodeSpec_Type `protobuf:"varint,1,opt,name=type,proto3,enum=protos.ChaincodeSpec_Type" json:"type,omitempty"` ChaincodeId *ChaincodeID `protobuf:"bytes,2,opt,name=chaincode_id,json=chaincodeId,proto3" json:"chaincode_id,omitempty"` Input *ChaincodeInput `protobuf:"bytes,3,opt,name=input,proto3" json:"input,omitempty"` Timeout int32 `protobuf:"varint,4,opt,name=timeout,proto3" json:"timeout,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } ===========================ChaincodeSpec=========================== spec := &pb.ChaincodeSpec{} #检查由用户输入的命令中的参数信息,好比格式,是否有没有定义过的参数等等 if err := checkChaincodeCmdParams(cmd); err != nil { cmd.SilenceUsage = false return spec, err } #定义一个链码输入参数结构 input := &pb.ChaincodeInput{} =======================ChaincodeInput======================================= #该结构体主要保存用户链码中定义的功能以及参数等信息 type ChaincodeInput struct { Args [][]byte `protobuf:"bytes,1,rep,name=args,proto3" json:"args,omitempty"` Decorations map[string][]byte `protobuf:"bytes,2,rep,name=decorations,proto3" json:"decorations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } ========================ChaincodeInput====================================== if err := json.Unmarshal([]byte(chaincodeCtorJSON), &input); err != nil { return spec, errors.Wrap(err, "chaincode argument error") } chaincodeLang = strings.ToUpper(chaincodeLang) #最后封装为ChaincodeSpec结构体返回 spec = &pb.ChaincodeSpec{ Type: pb.ChaincodeSpec_Type(pb.ChaincodeSpec_Type_value[chaincodeLang]), ChaincodeId: &pb.ChaincodeID{Path: chaincodePath, Name: chaincodeName, Version: chaincodeVersion}, Input: input, } return spec, nil }
得到了链码标准数据结构以后,到了getChaincodeDeploymentSpec
这个方法,点进去看一下,在peer/chaincode/common.go
文件中第50行:app
func getChaincodeDeploymentSpec(spec *pb.ChaincodeSpec, crtPkg bool) (*pb.ChaincodeDeploymentSpec, error) { var codePackageBytes []byte #首先判断是否当前Fabric网络处于开发模式,若是不是的话进入这里 if chaincode.IsDevMode() == false && crtPkg { var err error #而后对以前建立的链码标准数据结构进行验证,验证是否为空,链码类型路径等信息 if err = checkSpec(spec); err != nil { return nil, err } #获取链码信息的有效载荷 codePackageBytes, err = container.GetChaincodePackageBytes(platformRegistry, spec) if err != nil { err = errors.WithMessage(err, "error getting chaincode package bytes") return nil, err } } #最后封装为ChaincodeDeploymentSpec,这里若是Fabric网络处于开发模式下,codePackageBytes为空 chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes} return chaincodeDeploymentSpec, nil } ==============================ChaincodeDeploymentSpec===================== type ChaincodeDeploymentSpec struct { ChaincodeSpec *ChaincodeSpec `protobuf:"bytes,1,opt,name=chaincode_spec,json=chaincodeSpec,proto3" json:"chaincode_spec,omitempty"` CodePackage []byte `protobuf:"bytes,3,opt,name=code_package,json=codePackage,proto3" json:"code_package,omitempty"` ExecEnv ChaincodeDeploymentSpec_ExecutionEnvironment `protobuf:"varint,4,opt,name=exec_env,json=execEnv,proto3,enum=protos.ChaincodeDeploymentSpec_ExecutionEnvironment" json:"exec_env,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } ==============================ChaincodeDeploymentSpec=====================
返回到最初的方法chaincodeInstall()
,若是是本地安装的话,下一步就是链码的安装了,在此以前,咱们看一下若是ccpackfile
不为空的那个分支:ide
func chaincodeInstall(cmd *cobra.Command, ccpackfile string, cf *ChaincodeCmdFactory) error { ... if ccpackfile == "" { ... ccpackmsg, err = genChaincodeDeploymentSpec(cmd, chaincodeName, chaincodeVersion) ... } else { var cds *pb.ChaincodeDeploymentSpec #首先从ccpackfile中获取数据,这个方法就不看了,主要就是从文件中读取已定义的ChaincodeDeploymentSpec ccpackmsg, cds, err = getPackageFromFile(ccpackfile) if err != nil { return err } #因为ccpackfile中已经定义完成了以上的数据结构,因此这里就直接获取了 cName := cds.ChaincodeSpec.ChaincodeId.Name cVersion := cds.ChaincodeSpec.ChaincodeId.Version ... if chaincodeName != "" && chaincodeName != cName { return fmt.Errorf("chaincode name %s does not match name %s in package", chaincodeName, cName) } ... if chaincodeVersion != "" && chaincodeVersion != cVersion { return fmt.Errorf("chaincode version %s does not match version %s in packages", chaincodeVersion, cVersion) } } #到了安装链码的地方了,咱们看一下 err = install(ccpackmsg, cf) return err }
接下来咱们看一下链码的安装过程:
install()
方法在peer/chaincode/install.go
文件中第63行:code
func install(msg proto.Message, cf *ChaincodeCmdFactory) error { #和以前分析的文章同样,首先获取一个用于发起提案与签名的creator creator, err := cf.Signer.Serialize() if err != nil { return fmt.Errorf("Error serializing identity for %s: %s", cf.Signer.GetIdentifier(), err) } #从ChaincodeDeploymentSpec中建立一个用于安装链码的Proposal prop, _, err := utils.CreateInstallProposalFromCDS(msg, creator) if err != nil { return fmt.Errorf("Error creating proposal %s: %s", chainFuncName, err) } ... }
咱们主要看一下CreateInstallProposalFromCDS()
方法,点进去一直到protos/utils/proutils.go
文件中第538行:
func createProposalFromCDS(chainID string, msg proto.Message, creator []byte, propType string, args ...[]byte) (*peer.Proposal, string, error) { #传入的参数说明一下:chainID为空,msg,creator由以前的方法传入,propType为install,args为空 var ccinp *peer.ChaincodeInput var b []byte var err error if msg != nil { b, err = proto.Marshal(msg) if err != nil { return nil, "", err } } switch propType { #这里就判断propTypre类型,若是是deploy,或者是upgrade须要链码已经实例化完成 case "deploy": fallthrough #若是是deploy不跳出代码块,继续执行upgrade中的代码 case "upgrade": cds, ok := msg.(*peer.ChaincodeDeploymentSpec) if !ok || cds == nil { return nil, "", errors.New("invalid message for creating lifecycle chaincode proposal") } Args := [][]byte{[]byte(propType), []byte(chainID), b} Args = append(Args, args...) #与安装链码相同,都须要定义一个ChaincodeInput结构体,该结构体保存链码的基本信息 ccinp = &peer.ChaincodeInput{Args: Args} case "install": ccinp = &peer.ChaincodeInput{Args: [][]byte{[]byte(propType), b}} } #安装链码须要使用到生命周期系统链码,因此这里定义了一个lsccSpce,注意这里的ChaincodeInvocationSpec在下面使用到 lsccSpec := &peer.ChaincodeInvocationSpec{ ChaincodeSpec: &peer.ChaincodeSpec{ Type: peer.ChaincodeSpec_GOLANG, ChaincodeId: &peer.ChaincodeID{Name: "lscc"}, Input: ccinp, }, } #到这个方法了,根据ChaincodeInvocationSpec建立Proposal return CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, chainID, lsccSpec, creator) }
CreateProposalFromCIS()
这个方法在以前Fabric1.4源码解析:Peer节点加入通道这篇文章中讲过,具体能够看这里.
返回到install()
方法中,继续往下:
prop, _, err := utils.CreateInstallProposalFromCDS(msg, creator) if err != nil { return fmt.Errorf("Error creating proposal %s: %s", chainFuncName, err) } var signedProp *pb.SignedProposal #,到这里了,对建立的Proposal进行签名,该方法也在上面那篇文章中说过,再也不说明 signedProp, err = utils.GetSignedProposal(prop, cf.Signer) if err != nil { return fmt.Errorf("Error creating signed proposal %s: %s", chainFuncName, err) } #这个地方与以前分析的不一样,这里安装链码只在指定的Peer节点,而不是全部Peer节点,依旧是调用了主要的方法ProcessProposal proposalResponse, err := cf.EndorserClients[0].ProcessProposal(context.Background(), signedProp) #到这里,Peer节点对提案处理完成以后,整个链码安装的过程就结束了 if err != nil { return fmt.Errorf("Error endorsing %s: %s", chainFuncName, err) } if proposalResponse != nil { if proposalResponse.Response.Status != int32(pcommon.Status_SUCCESS) { return errors.Errorf("Bad response: %d - %s", proposalResponse.Response.Status, proposalResponse.Response.Message) } logger.Infof("Installed remotely %v", proposalResponse) } else { return errors.New("Error during install: received nil proposal response") } return nil
ProcessProposal()
以前在另外一篇文章Fabric1.4源码解析:Peer节点背书提案过程中都有说过,这里就再也不说了,不过该方法很是很是重要,算是Fabric中使用频率很高的一个方法。
最后总结一下:
ChaincodeInput
数据结构,保存链码的功能以及参数信息ChaincodeDeploymentSpec
ChaincodeDeploymentSpec
相关信息ChaincodeDeploymentSpec
中建立Proposal
Proposal
进行签名Proposal
由Peer
节点进行处理