Fabric1.4源碼解析:客戶端安裝鏈碼

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

      最後總結一下:

  1. 首先由用戶執行安裝鏈碼的命令啓動了整個流程。
  2. 判斷鏈碼是從本地鏈碼源代碼文件讀取仍是由接收到其餘節點打包的鏈碼進行安裝
    1. 若是是從本地鏈碼源代碼文件讀取,首先根據傳入的鏈碼名稱和版本判斷該鏈碼是否已經存在。
      1. 若是存在的話直接返回錯誤信息。
      2. 若是不存在的話,首先對安裝鏈碼的命令中的參數進行驗證。
      3. 定義一個ChaincodeInput數據結構,保存鏈碼的功能以及參數信息
      4. 最後定義了個鏈碼標準數據結構
      5. 判斷是否處於開發環境下,定義了ChaincodeDeploymentSpec
    2. 若是不是本地安裝,則從文件中直接讀取已定義的ChaincodeDeploymentSpec相關信息
  3. 執行鏈碼的安裝,獲取簽名者
  4. ChaincodeDeploymentSpec中建立Proposal
  5. Proposal進行簽名
  6. 將已簽名的ProposalPeer節點進行處理
  7. 安裝鏈碼過程結束
相關文章
相關標籤/搜索