看了看客戶端安裝鏈碼的部分,感受仍是比較簡單的,因此在這裏記錄一下。
仍是先給出安裝鏈碼所使用的命令好了,這裏就使用官方的安裝鏈碼的一個例子: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
節點進行處理