Fabric源碼分析之Peer鏈碼安裝

environment:
fabric v1.4.2

1.概述

在Fabric中交易的處理過(guò)程欢搜,客戶端將提案首先發(fā)送到背書(shū)節(jié)點(diǎn),背書(shū)節(jié)點(diǎn)檢提案的合法性谴轮。如果合法的話炒瘟,背書(shū)節(jié)點(diǎn)將通過(guò)交易所屬的鏈碼臨時(shí)執(zhí)行一個(gè)交易,并執(zhí)行背書(shū)節(jié)點(diǎn)在本地持有的狀態(tài)副本第步。
Chaincode應(yīng)該僅僅被安裝于chaincode所有者的背書(shū)節(jié)點(diǎn)上疮装,鏈碼運(yùn)行在節(jié)點(diǎn)上的沙盒(Docker容器)中,并通過(guò)gRPC協(xié)議與相應(yīng)的Peer節(jié)點(diǎn)進(jìn)行交互粘都,以使該chaincode邏輯對(duì)整個(gè)網(wǎng)絡(luò)的其他成員保密廓推。
請(qǐng)務(wù)必在一條channel上每一個(gè)要運(yùn)行你chaincode的背書(shū)節(jié)點(diǎn)上安裝你的chaincode
其他沒(méi)有chaincode的成員將無(wú)權(quán)成為chaincode影響下的交易的認(rèn)證節(jié)點(diǎn)(endorser)。也就是說(shuō)翩隧,他們不能執(zhí)行chaincode樊展。不過(guò),他們?nèi)钥梢则?yàn)證交易并提交到賬本上。
ChainCode要在區(qū)塊鏈網(wǎng)絡(luò)中運(yùn)行专缠,需要經(jīng)過(guò)鏈碼安裝和鏈碼實(shí)例化兩個(gè)步驟雷酪。

鏈碼的安裝涉及到3個(gè)服務(wù),分別是client藤肢,peer背書(shū)節(jié)點(diǎn)和LSCC容器
主要流程:

  • 客戶端構(gòu)造提案信息并發(fā)送給背書(shū)節(jié)點(diǎn)
  • 背書(shū)節(jié)點(diǎn)檢提案的合法性
  • 背書(shū)節(jié)點(diǎn)調(diào)用lscc容器
  • lscc容器進(jìn)行鏈碼安裝
  • 提案背書(shū)返回

以下是在客戶端執(zhí)行"peer chaincode install ..."的業(yè)務(wù)流程圖:

peer chaincode install

2. 客戶端構(gòu)造提案信息并發(fā)送給背書(shū)節(jié)點(diǎn)

客戶端執(zhí)行鏈碼安裝命令:

#-n 指定mycc是由用戶定義的鏈碼名字太闺,-v 指定1.0是鏈碼的版本糯景,-p 是指定鏈碼的路徑
peer chaincode install -p chaincodedev/chaincode/sacc -n mycc -v 1.0

客戶端的整個(gè)流程切入點(diǎn)為fabric/peer/main.gomain函數(shù)

...
mainCmd.AddCommand(chaincode.Cmd(nil))  // chaincode命令入口
...

然后繼續(xù)找到peer/chaincode/chaincode.go

// Cmd returns the cobra command for Chaincode
func Cmd(cf *ChaincodeCmdFactory) *cobra.Command {
    addFlags(chaincodeCmd)

    chaincodeCmd.AddCommand(installCmd(cf))         // 執(zhí)行鏈碼的安裝
    chaincodeCmd.AddCommand(instantiateCmd(cf))     // 鏈碼的實(shí)例化
    chaincodeCmd.AddCommand(invokeCmd(cf))          // 鏈碼的調(diào)用嘁圈,具體調(diào)用什么方法要看鏈碼是怎么寫(xiě)的
    chaincodeCmd.AddCommand(packageCmd(cf, nil))    // 鏈碼的打包
    chaincodeCmd.AddCommand(queryCmd(cf))           // 對(duì)鏈碼數(shù)據(jù)進(jìn)行查詢,這個(gè)只是向指定的Peer節(jié)點(diǎn)請(qǐng)求查詢數(shù)據(jù)蟀淮,不會(huì)生成交易最后打包區(qū)塊的 
    chaincodeCmd.AddCommand(signpackageCmd(cf))     // 對(duì)已打包的鏈碼進(jìn)行簽名操作
    chaincodeCmd.AddCommand(upgradeCmd(cf))         // 更新鏈碼最住,之前提到過(guò) -v是指定鏈碼的版本,如果需要對(duì)鏈碼進(jìn)行更新的話怠惶,使用這條命令涨缚,比較常用
    chaincodeCmd.AddCommand(listCmd(cf))            // 如果已指定通道的話,則查詢已實(shí)例化的鏈碼策治,否則查詢當(dāng)前Peer節(jié)點(diǎn)已安裝的鏈碼

    return chaincodeCmd
}

繼續(xù)找到peer/chaincode/install.goinstallCmd函數(shù)脓魏,可以看出chaincodeInstall為主要的入口函數(shù)

// installCmd returns the cobra command for Chaincode Deploy
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]
            }
            // 入口函數(shù)
            return chaincodeInstall(cmd, ccpackfile, cf)
        },
    }
    flagList := []string{ // 在安裝鏈碼的命令中指定的相關(guān)參數(shù)
        "lang",
        "ctor",
        "path",
        "name",
        "version",
        "peerAddresses",
        "tlsRootCertFiles",
        "connectionProfile",
    }
    attachFlags(chaincodeInstallCmd, flagList)

    return chaincodeInstallCmd
}
// chaincodeInstall installs the chaincode. If remoteinstall, does it via a lscc call
func chaincodeInstall(cmd *cobra.Command, ccpackfile string, cf *ChaincodeCmdFactory) error {
    // Parsing of the command line is done so silence cmd usage
    cmd.SilenceUsage = true

    var err error
    if cf == nil {
        // 如果ChaincodeCmdFactory為空,則初始化一個(gè)
        cf, err = InitCmdFactory(cmd.Name(), true, false)
        if err != nil {
            return err
        }
    }

    var ccpackmsg proto.Message
    // 這個(gè)地方有兩種情況通惫,鏈碼可能是根據(jù)傳入?yún)?shù)從本地鏈碼源代碼文件讀取茂翔,也有可能是由其他節(jié)點(diǎn)簽名打包完成發(fā)送過(guò)來(lái)的
    if ccpackfile == "" {
        // 這里是從本地鏈碼源代碼文件讀取,一般從這里進(jìn)去
        if chaincodePath == common.UndefinedParamValue || chaincodeVersion == common.UndefinedParamValue || chaincodeName == common.UndefinedParamValue {
            return fmt.Errorf("Must supply value for %s name, path and version parameters.", chainFuncName)
        }
        //generate a raw ChaincodeDeploymentSpec
        // 生成ChaincodeDeploymentSpce
        ccpackmsg, err = genChaincodeDeploymentSpec(cmd, chaincodeName, chaincodeVersion)
        if err != nil {
            return err
        }
    } else {
        //read in a package generated by the "package" sub-command (and perhaps signed
        //by multiple owners with the "signpackage" sub-command)
        // 首先從ccpackfile中獲取數(shù)據(jù),主要就是從文件中讀取已定義的ChaincodeDeploymentSpec
        var cds *pb.ChaincodeDeploymentSpec
        ccpackmsg, cds, err = getPackageFromFile(ccpackfile)

        if err != nil {
            return err
        }

        //get the chaincode details from cds
        // 由于ccpackfile中已經(jīng)定義完成了以上的數(shù)據(jù)結(jié)構(gòu)履腋,所以這里就直接獲取了
        cName := cds.ChaincodeSpec.ChaincodeId.Name
        cVersion := cds.ChaincodeSpec.ChaincodeId.Version

        //if user provided chaincodeName, use it for validation
        if chaincodeName != "" && chaincodeName != cName {
            return fmt.Errorf("chaincode name %s does not match name %s in package", chaincodeName, cName)
        }

        //if user provided chaincodeVersion, use it for validation
        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
}

2.1 構(gòu)造ChaincodeCmdFactory結(jié)構(gòu)體

我們進(jìn)去看看InitCmdFactory做了什么珊燎,位置在peer/chaincode/common.go

// InitCmdFactory init the ChaincodeCmdFactory with default clients
func InitCmdFactory(cmdName string, isEndorserRequired, isOrdererRequired bool) (*ChaincodeCmdFactory, error) {
    var err error
    var endorserClients []pb.EndorserClient
    var deliverClients []api.PeerDeliverClient
    if isEndorserRequired {
        if err = validatePeerConnectionParameters(cmdName); err != nil {
            return nil, errors.WithMessage(err, "error validating peer connection parameters")
        }
        for i, address := range peerAddresses {
            var tlsRootCertFile string
            if tlsRootCertFiles != nil {
                tlsRootCertFile = tlsRootCertFiles[i]
            }
            endorserClient, err := common.GetEndorserClientFnc(address, tlsRootCertFile)
            if err != nil {
                return nil, errors.WithMessage(err, fmt.Sprintf("error getting endorser client for %s", cmdName))
            }
            endorserClients = append(endorserClients, endorserClient)
            deliverClient, err := common.GetPeerDeliverClientFnc(address, tlsRootCertFile)
            if err != nil {
                return nil, errors.WithMessage(err, fmt.Sprintf("error getting deliver client for %s", cmdName))
            }
            deliverClients = append(deliverClients, deliverClient)
        }
        if len(endorserClients) == 0 {
            return nil, errors.New("no endorser clients retrieved - this might indicate a bug")
        }
    }
    certificate, err := common.GetCertificateFnc()
    if err != nil {
        return nil, errors.WithMessage(err, "error getting client cerificate")
    }

    signer, err := common.GetDefaultSignerFnc()
    if err != nil {
        return nil, errors.WithMessage(err, "error getting default signer")
    }

    var broadcastClient common.BroadcastClient
    if isOrdererRequired {
        if len(common.OrderingEndpoint) == 0 {
            if len(endorserClients) == 0 {
                return nil, errors.New("orderer is required, but no ordering endpoint or endorser client supplied")
            }
            endorserClient := endorserClients[0]

            orderingEndpoints, err := common.GetOrdererEndpointOfChainFnc(channelID, signer, endorserClient)
            if err != nil {
                return nil, errors.WithMessage(err, fmt.Sprintf("error getting channel (%s) orderer endpoint", channelID))
            }
            if len(orderingEndpoints) == 0 {
                return nil, errors.Errorf("no orderer endpoints retrieved for channel %s", channelID)
            }
            logger.Infof("Retrieved channel (%s) orderer endpoint: %s", channelID, orderingEndpoints[0])
            // override viper env
            viper.Set("orderer.address", orderingEndpoints[0])
        }

        broadcastClient, err = common.GetBroadcastClientFnc()

        if err != nil {
            return nil, errors.WithMessage(err, "error getting broadcast client")
        }
    }
    return &ChaincodeCmdFactory{
        EndorserClients: endorserClients,
        DeliverClients:  deliverClients,
        Signer:          signer,
        BroadcastClient: broadcastClient,
        Certificate:     certificate,
    }, nil
}

返回了ChaincodeCmdFactory的結(jié)構(gòu)體挎峦,定義為:

// ChaincodeCmdFactory holds the clients used by ChaincodeCmd
type ChaincodeCmdFactory struct {
    EndorserClients []pb.EndorserClient         // 用于向背書(shū)節(jié)點(diǎn)發(fā)送消息
    DeliverClients  []api.PeerDeliverClient     // 用于與Order節(jié)點(diǎn)通信
    Certificate     tls.Certificate             // TLS證書(shū)相關(guān)
    Signer          msp.SigningIdentity         // 用于消息的簽名
    BroadcastClient common.BroadcastClient      // 用于廣播消息
}

2.2 構(gòu)造鏈碼部署標(biāo)準(zhǔn)數(shù)據(jù)結(jié)構(gòu)ChaincodeDeploymentSpec

找到定義genChaincodeDeploymentSpec

//genChaincodeDeploymentSpec creates ChaincodeDeploymentSpec as the package to install
func genChaincodeDeploymentSpec(cmd *cobra.Command, chaincodeName, chaincodeVersion string) (*pb.ChaincodeDeploymentSpec, error) {
    // 首先根據(jù)鏈碼名稱與鏈碼版本查找當(dāng)前鏈碼是否已經(jīng)安裝過(guò)炼杖,如果安裝過(guò)則返回鏈碼已存在的錯(cuò)誤
    if existed, _ := ccprovider.ChaincodePackageExists(chaincodeName, chaincodeVersion); existed {
        return nil, fmt.Errorf("chaincode %s:%s already exists", chaincodeName, chaincodeVersion)
    }
    // 獲取鏈碼標(biāo)準(zhǔn)數(shù)據(jù)結(jié)構(gòu)
    spec, err := getChaincodeSpec(cmd)
    if err != nil {
        return nil, err
    }
    // 獲取鏈碼部署標(biāo)準(zhǔn)數(shù)據(jù)結(jié)構(gòu)
    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

// getChaincodeSpec get chaincode spec from the cli cmd pramameters
func getChaincodeSpec(cmd *cobra.Command) (*pb.ChaincodeSpec, error) {
    spec := &pb.ChaincodeSpec{}
    // 檢查由用戶輸入的命令中的參數(shù)信息,比如格式桥言,是否有沒(méi)有定義過(guò)的參數(shù)等等
    if err := checkChaincodeCmdParams(cmd); err != nil {
        // unset usage silence because it's a command line usage error
        cmd.SilenceUsage = false
        return spec, err
    }

    // Build the spec
    // 定義一個(gè)鏈碼輸入?yún)?shù)結(jié)構(gòu)
    input := &pb.ChaincodeInput{}
    if err := json.Unmarshal([]byte(chaincodeCtorJSON), &input); err != nil {
        return spec, errors.Wrap(err, "chaincode argument error")
    }

    chaincodeLang = strings.ToUpper(chaincodeLang)
    // 最后封裝為ChaincodeSpec結(jié)構(gòu)體返回 
    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
}

封裝返回ChaincodeSpec結(jié)構(gòu)體

// Carries the chaincode specification. This is the actual metadata required for
// defining a chaincode.
type ChaincodeSpec struct {
    Type                 ChaincodeSpec_Type // 鏈碼的編寫(xiě)語(yǔ)言延旧,GOLANG谋国、JAVA
    ChaincodeId          *ChaincodeID       // ChaincodeId,鏈碼路徑迁沫、鏈碼名稱烹卒、鏈碼版本
    Input                *ChaincodeInput    // 鏈碼的具體執(zhí)行參數(shù)信息
    Timeout              int32              
}

剛才生成的ChaincodeSpec作為getChaincodeDeploymentSpec函數(shù)的輸入?yún)?shù),返回ChaincodeDeploymentSpec結(jié)構(gòu)體

// getChaincodeDeploymentSpec get chaincode deployment spec given the chaincode spec
func getChaincodeDeploymentSpec(spec *pb.ChaincodeSpec, crtPkg bool) (*pb.ChaincodeDeploymentSpec, error) {
    var codePackageBytes []byte
    // 首先判斷是否當(dāng)前Fabric網(wǎng)絡(luò)處于開(kāi)發(fā)模式弯洗,如果不是的話進(jìn)入這里
    if chaincode.IsDevMode() == false && crtPkg {
        var err error
        // 然后對(duì)之前創(chuàng)建的鏈碼標(biāo)準(zhǔn)數(shù)據(jù)結(jié)構(gòu)進(jìn)行驗(yàn)證旅急,驗(yàn)證是否為空,鏈碼類型路徑等信息
        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網(wǎng)絡(luò)處于開(kāi)發(fā)模式下藐吮,codePackageBytes為空
    chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes}
    return chaincodeDeploymentSpec, nil
}
// Specify the deployment of a chaincode.
// TODO: Define `codePackage`.
type ChaincodeDeploymentSpec struct {
    ChaincodeSpec        *ChaincodeSpec     // ChaincodeSpec消息
    CodePackage          []byte             // 鏈碼文件打包
    ExecEnv              ChaincodeDeploymentSpec_ExecutionEnvironment  // 鏈碼執(zhí)行環(huán)境,DOCKER或SYSTEM
}

2.3 創(chuàng)建提案結(jié)構(gòu)、簽名和發(fā)送提案

//install the depspec to "peer.address"
func install(msg proto.Message, cf *ChaincodeCmdFactory) error {
    // 首先獲取一個(gè)用于發(fā)起提案與簽名的creator
    creator, err := cf.Signer.Serialize()
    if err != nil {
        return fmt.Errorf("Error serializing identity for %s: %s", cf.Signer.GetIdentifier(), err)
    }
    // 從ChaincodeDeploymentSpec中創(chuàng)建一個(gè)用于安裝鏈碼的Proposal
    prop, _, err := utils.CreateInstallProposalFromCDS(msg, creator)
    if err != nil {
        return fmt.Errorf("Error creating proposal  %s: %s", chainFuncName, err)
    }

    var signedProp *pb.SignedProposal
    // 對(duì)創(chuàng)建的Proposal進(jìn)行簽名
    signedProp, err = utils.GetSignedProposal(prop, cf.Signer)
    if err != nil {
        return fmt.Errorf("Error creating signed proposal  %s: %s", chainFuncName, err)
    }

    // install is currently only supported for one peer
    // 這里安裝鏈碼只在指定的Peer節(jié)點(diǎn)谣辞,而不是所有Peer節(jié)點(diǎn)迫摔,依舊是調(diào)用了主要的方法ProcessProposal
    proposalResponse, err := cf.EndorserClients[0].ProcessProposal(context.Background(), signedProp)
    // 到這里,Peer節(jié)點(diǎn)對(duì)提案處理完成之后泥从,整個(gè)鏈碼安裝的過(guò)程就結(jié)束了
    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
}

2.3.1 創(chuàng)建提案結(jié)構(gòu)

CreateInstallProposalFromCDS位于protos/utils/proutils.go

// CreateInstallProposalFromCDS returns a install proposal given a serialized
// identity and a ChaincodeDeploymentSpec
func CreateInstallProposalFromCDS(ccpack proto.Message, creator []byte) (*peer.Proposal, string, error) {
    return createProposalFromCDS("", ccpack, creator, "install")
}

調(diào)用createProposalFromCDS

// createProposalFromCDS returns a deploy or upgrade proposal given a
// serialized identity and a ChaincodeDeploymentSpec
// 傳入的參數(shù)說(shuō)明一下:chainID為空句占,msg,creator由之前的方法傳入躯嫉,propType為install纱烘,args為空
func createProposalFromCDS(chainID string, msg proto.Message, creator []byte, propType string, args ...[]byte) (*peer.Proposal, string, error) {
    // in the new mode, cds will be nil, "deploy" and "upgrade" are instantiates.
    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需要鏈碼已經(jīng)實(shí)例化完成
    case "deploy":
        fallthrough
        // 如果是deploy不跳出代碼塊祈餐,繼續(xù)執(zhí)行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...)
        // 與安裝鏈碼相同擂啥,都需要定義一個(gè)ChaincodeInput結(jié)構(gòu)體,該結(jié)構(gòu)體保存鏈碼的基本信息
        ccinp = &peer.ChaincodeInput{Args: Args}
    case "install":
        ccinp = &peer.ChaincodeInput{Args: [][]byte{[]byte(propType), b}}
    }

    // wrap the deployment in an invocation spec to lscc...
    // 安裝鏈碼需要使用到生命周期系統(tǒng)鏈碼帆阳,所以這里定義了一個(gè)lsccSpce哺壶,注意這里的ChaincodeInvocationSpec在下面使用到
    lsccSpec := &peer.ChaincodeInvocationSpec{
        ChaincodeSpec: &peer.ChaincodeSpec{
            Type:        peer.ChaincodeSpec_GOLANG,
            ChaincodeId: &peer.ChaincodeID{Name: "lscc"},
            Input:       ccinp,
        },
    }

    // ...and get the proposal for it
    // 根據(jù)ChaincodeInvocationSpec創(chuàng)建Proposal
    return CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, chainID, lsccSpec, creator)
}

從結(jié)構(gòu)體ChaincodeInvocationSpec可以看到用戶鏈碼安裝需要調(diào)用到系統(tǒng)鏈碼lscc
通過(guò)CreateProposalFromCIS=>CreateChaincodeProposal=>CreateChaincodeProposalWithTransient

// CreateChaincodeProposalWithTransient creates a proposal from given input
// It returns the proposal and the transaction id associated to the proposal
func CreateChaincodeProposalWithTransient(typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, creator []byte, transientMap map[string][]byte) (*peer.Proposal, string, error) {
    // generate a random nonce
    // 生成一個(gè)隨機(jī)數(shù)
    nonce, err := crypto.GetRandomNonce()
    if err != nil {
        return nil, "", err
    }

    // compute txid
    // 計(jì)算出一個(gè)TxID,具體是根據(jù)HASH算法生成的
    txid, err := ComputeTxID(nonce, creator)
    if err != nil {
        return nil, "", err
    }
    // 用了這個(gè)方法,將之前生成的數(shù)據(jù)傳入進(jìn)去
    return CreateChaincodeProposalWithTxIDNonceAndTransient(txid, typ, chainID, cis, nonce, creator, transientMap)
}

再看CreateChaincodeProposalWithTxIDNonceAndTransient函數(shù)

/ CreateChaincodeProposalWithTxIDNonceAndTransient creates a proposal from
// given input
func CreateChaincodeProposalWithTxIDNonceAndTransient(txid string, typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, nonce, creator []byte, transientMap map[string][]byte) (*peer.Proposal, string, error) {
    // 首先是構(gòu)造一個(gè)ChaincodeHeaderExtension結(jié)構(gòu)體
    ccHdrExt := &peer.ChaincodeHeaderExtension{ChaincodeId: cis.ChaincodeSpec.ChaincodeId}
    // 將該結(jié)構(gòu)體序列化
    ccHdrExtBytes, err := proto.Marshal(ccHdrExt)
    if err != nil {
        return nil, "", errors.Wrap(err, "error marshaling ChaincodeHeaderExtension")
    }
    // 將ChaincodeInvocationSpec結(jié)構(gòu)體序列化
    cisBytes, err := proto.Marshal(cis)
    if err != nil {
        return nil, "", errors.Wrap(err, "error marshaling ChaincodeInvocationSpec")
    }
    // ChaincodeProposalPayload結(jié)構(gòu)體
    ccPropPayload := &peer.ChaincodeProposalPayload{Input: cisBytes, TransientMap: transientMap}
    // 序列化
    ccPropPayloadBytes, err := proto.Marshal(ccPropPayload)
    if err != nil {
        return nil, "", errors.Wrap(err, "error marshaling ChaincodeProposalPayload")
    }

    // TODO: epoch is now set to zero. This must be changed once we
    // get a more appropriate mechanism to handle it in.
    var epoch uint64
    // 創(chuàng)建一個(gè)時(shí)間戳
    timestamp := util.CreateUtcTimestamp()
    // 構(gòu)造Header結(jié)構(gòu)體蜒谤,包含兩部分ChannelHeader和SignatureHeader
    hdr := &common.Header{
        ChannelHeader: MarshalOrPanic(
            &common.ChannelHeader{
                Type:      int32(typ),
                TxId:      txid,
                Timestamp: timestamp,
                ChannelId: chainID,
                Extension: ccHdrExtBytes,
                Epoch:     epoch,
            },
        ),
        SignatureHeader: MarshalOrPanic(
            &common.SignatureHeader{
                Nonce:   nonce,
                Creator: creator,
            },
        ),
    }
    // 序列化
    hdrBytes, err := proto.Marshal(hdr)
    if err != nil {
        return nil, "", err
    }
    // 最后構(gòu)造成一個(gè)Proposal
    prop := &peer.Proposal{
        Header:  hdrBytes,
        Payload: ccPropPayloadBytes,
    }
    return prop, txid, nil
}

最后返回Proposal結(jié)構(gòu)體山宾,定義見(jiàn)protos\peer\proposal.pb.go

type Proposal struct {
    // The header of the proposal. It is the bytes of the Header
    Header []byte `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
    // The payload of the proposal as defined by the type in the proposal
    // header.
    Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"`
    // Optional extensions to the proposal. Its content depends on the Header's
    // type field.  For the type CHAINCODE, it might be the bytes of a
    // ChaincodeAction message.
    Extension            []byte   `protobuf:"bytes,3,opt,name=extension,proto3" json:"extension,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

到這里install調(diào)用的CreateInstallProposalFromCDS完畢,返回Proposal結(jié)構(gòu)體
關(guān)系有點(diǎn)復(fù)雜鳍徽,給出一個(gè)類圖能看得清晰點(diǎn)

SignedProposal struct

2.3.2 簽名

回到install资锰,看GetSignedProposal對(duì)剛創(chuàng)建的提案結(jié)構(gòu)進(jìn)行簽名
函數(shù)位于protos/utils/txutils.go

// GetSignedProposal returns a signed proposal given a Proposal message and a
// signing identity
func GetSignedProposal(prop *peer.Proposal, signer msp.SigningIdentity) (*peer.SignedProposal, error) {
    // check for nil argument
    if prop == nil || signer == nil {
        return nil, errors.New("nil arguments")
    }
    // 獲取提案信息的字節(jié)數(shù)組
    propBytes, err := GetBytesProposal(prop)
    if err != nil {
        return nil, err
    }
    // 對(duì)字節(jié)數(shù)組進(jìn)行簽名
    signature, err := signer.Sign(propBytes)
    if err != nil {
        return nil, err
    }
    // 返回SignedProposal結(jié)構(gòu)體
    return &peer.SignedProposal{ProposalBytes: propBytes, Signature: signature}, nil
}

返回SignedProposal結(jié)構(gòu)體,定義位于protos/peer/proposal.pb.go

type SignedProposal struct {
    // The bytes of Proposal
    ProposalBytes []byte `protobuf:"bytes,1,opt,name=proposal_bytes,json=proposalBytes,proto3" json:"proposal_bytes,omitempty"`
    // Signaure over proposalBytes; this signature is to be verified against
    // the creator identity contained in the header of the Proposal message
    // marshaled as proposalBytes
    Signature            []byte   `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

2.3.3 發(fā)送提案

提案簽名完后install調(diào)用ProcessProposal發(fā)送提案到peer節(jié)點(diǎn)進(jìn)行處理,參數(shù)帶了SignedProposal結(jié)構(gòu)體
接下來(lái)client端就等到peer的proposalResponse

3. 背書(shū)節(jié)點(diǎn)檢提案的合法性

當(dāng)client調(diào)用了ProposalResponse消息就發(fā)送到peer背書(shū)節(jié)點(diǎn),也就是走peer節(jié)點(diǎn)背書(shū)提案流程.
要看安裝鏈碼前做了什么旬盯,直接看peer節(jié)點(diǎn)背書(shū)提案流程就好台妆。

4. 背書(shū)節(jié)點(diǎn)調(diào)用lscc容器

我們從core/endorser/endorser.gocallChaincode=>Execute函數(shù)開(kāi)始講

// call specified chaincode (system or user)
func (e *Endorser) callChaincode(txParams *ccprovider.TransactionParams, version string, input *pb.ChaincodeInput, cid *pb.ChaincodeID) (*pb.Response, *pb.ChaincodeEvent, error) {
    ... ...
    // is this a system chaincode
    // 執(zhí)行鏈碼,如果是用戶鏈碼具體怎么執(zhí)行的要看用戶寫(xiě)的鏈碼邏輯胖翰,執(zhí)行完畢后返回響應(yīng)信息與鏈碼事件
    res, ccevent, err = e.s.Execute(txParams, txParams.ChannelID, cid.Name, version, txParams.TxID, txParams.SignedProp, txParams.Proposal, input)
    if err != nil {
        return nil, nil, err
    }
    ... ...

core/chaincode/chaincode_support.go找到Execute

// Execute invokes chaincode and returns the original response.
func (cs *ChaincodeSupport) Execute(txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, input *pb.ChaincodeInput) (*pb.Response, *pb.ChaincodeEvent, error) {
    // 主要是啟動(dòng)鏈碼容器接剩,調(diào)用鏈碼
    resp, err := cs.Invoke(txParams, cccid, input)
    // 對(duì)鏈碼執(zhí)行結(jié)果進(jìn)行處理
    return processChaincodeExecutionResult(txParams.TxID, cccid.Name, resp, err)
}

主要看Invoke:

// Invoke will invoke chaincode and return the message containing the response.
// The chaincode will be launched if it is not already running.
func (cs *ChaincodeSupport) Invoke(txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, input *pb.ChaincodeInput) (*pb.ChaincodeMessage, error) {
    // 啟動(dòng)鏈碼容器
    h, err := cs.Launch(txParams.ChannelID, cccid.Name, cccid.Version, txParams.TXSimulator)
    if err != nil {
        return nil, err
    }

    // TODO add Init exactly once semantics here once new lifecycle
    // is available.  Enforced if the target channel is using the new lifecycle
    //
    // First, the function name of the chaincode to invoke should be checked.  If it is
    // "init", then consider this invocation to be of type pb.ChaincodeMessage_INIT,
    // otherwise consider it to be of type pb.ChaincodeMessage_TRANSACTION,
    //
    // Secondly, A check should be made whether the chaincode has been
    // inited, then, if true, only allow cctyp pb.ChaincodeMessage_TRANSACTION,
    // otherwise, only allow cctype pb.ChaincodeMessage_INIT,
    cctype := pb.ChaincodeMessage_TRANSACTION
    // 給鏈碼發(fā)送消息
    return cs.execute(cctype, txParams, cccid, input, h)
}
// Launch starts executing chaincode if it is not already running. This method
// blocks until the peer side handler gets into ready state or encounters a fatal
// error. If the chaincode is already running, it simply returns.
func (cs *ChaincodeSupport) Launch(chainID, chaincodeName, chaincodeVersion string, qe ledger.QueryExecutor) (*Handler, error) {
    cname := chaincodeName + ":" + chaincodeVersion
    // 如果是系統(tǒng)鏈碼,在peer啟動(dòng)的時(shí)候已經(jīng)初始化了萨咳,所以如果是鏈碼安裝在下面語(yǔ)句直接返回了
    if h := cs.HandlerRegistry.Handler(cname); h != nil {
        return h, nil
    }

    // 此處到得容器相關(guān)的信息懊缺,包括生產(chǎn)容器的具體類型是系統(tǒng)鏈碼容器還是用戶鏈碼容器
    // 在后面會(huì)說(shuō)明,系統(tǒng)鏈碼啟動(dòng)的容器是:inprocVM---inproContainer,用戶鏈碼啟動(dòng)的容器是DockerVM---DockerContainer
    ccci, err := cs.Lifecycle.ChaincodeContainerInfo(chaincodeName, qe)
    if err != nil {
        // TODO: There has to be a better way to do this...
        if cs.UserRunsCC {
            chaincodeLogger.Error(
                "You are attempting to perform an action other than Deploy on Chaincode that is not ready and you are in developer mode. Did you forget to Deploy your chaincode?",
            )
        }

        return nil, errors.Wrapf(err, "[channel %s] failed to get chaincode container info for %s", chainID, cname)
    }

    // 啟動(dòng)Runtime中的Launch
    if err := cs.Launcher.Launch(ccci); err != nil {
        return nil, errors.Wrapf(err, "[channel %s] could not launch chaincode %s", chainID, cname)
    }

    h := cs.HandlerRegistry.Handler(cname)
    if h == nil {
        return nil, errors.Wrapf(err, "[channel %s] claimed to start chaincode container for %s but could not find handler", chainID, cname)
    }

    return h, nil
}

根據(jù)之前的信息培他,我們調(diào)用的是lscc來(lái)安裝鏈碼鹃两,所以在peer啟動(dòng)的時(shí)候已經(jīng)初始化lscc鏈碼容器了,所以回直接返回handler對(duì)象舀凛,后面的語(yǔ)句就不說(shuō)了俊扳,在啟動(dòng)鏈碼容器的章節(jié)再詳細(xì)研究。

接著我們看execute函數(shù)猛遍,調(diào)用createCCMessage創(chuàng)建一個(gè)ChaincodeMessage結(jié)構(gòu)體消息.Execute負(fù)責(zé)把消息發(fā)送出去

// execute executes a transaction and waits for it to complete until a timeout value.
func (cs *ChaincodeSupport) execute(cctyp pb.ChaincodeMessage_Type, txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, input *pb.ChaincodeInput, h *Handler) (*pb.ChaincodeMessage, error) {
    input.Decorations = txParams.ProposalDecorations
    // 創(chuàng)建一個(gè)ChaincodeMessage結(jié)構(gòu)體消息
    ccMsg, err := createCCMessage(cctyp, txParams.ChannelID, txParams.TxID, input)
    if err != nil {
        return nil, errors.WithMessage(err, "failed to create chaincode message")
    }
    
    ccresp, err := h.Execute(txParams, cccid, ccMsg, cs.ExecuteTimeout)
    if err != nil {
        return nil, errors.WithMessage(err, fmt.Sprintf("error sending"))
    }

    return ccresp, nil
}

core/chaincode/handler.go找到Execute

func (h *Handler) Execute(txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, msg *pb.ChaincodeMessage, timeout time.Duration) (*pb.ChaincodeMessage, error) {
    chaincodeLogger.Debugf("Entry")
    defer chaincodeLogger.Debugf("Exit")

    txParams.CollectionStore = h.getCollectionStore(msg.ChannelId)
    txParams.IsInitTransaction = (msg.Type == pb.ChaincodeMessage_INIT)

    txctx, err := h.TXContexts.Create(txParams)
    if err != nil {
        return nil, err
    }
    defer h.TXContexts.Delete(msg.ChannelId, msg.Txid)

    if err := h.setChaincodeProposal(txParams.SignedProp, txParams.Proposal, msg); err != nil {
        return nil, err
    }
    // 異步發(fā)送grpc消息
    h.serialSendAsync(msg)

    var ccresp *pb.ChaincodeMessage
    select {
    case ccresp = <-txctx.ResponseNotifier:
        // response is sent to user or calling chaincode. ChaincodeMessage_ERROR
        // are typically treated as error
    case <-time.After(timeout):
        err = errors.New("timeout expired while executing transaction")
        ccName := cccid.Name + ":" + cccid.Version
        h.Metrics.ExecuteTimeouts.With(
            "chaincode", ccName,
        ).Add(1)
    }

    return ccresp, err
}

這里關(guān)鍵是h.serialSendAsync(msg)語(yǔ)句馋记,功能是把包裝好的信息以grpc協(xié)議發(fā)送出去号坡,直接就等返回結(jié)果了。
至此Execute調(diào)用的Invoke就在等返回結(jié)果梯醒,結(jié)果返回就調(diào)用processChaincodeExecutionResult對(duì)鏈碼結(jié)果進(jìn)行處理

5. lscc容器進(jìn)行鏈碼安裝

5.1 容器信息接收以及處理

peer發(fā)送的信息哪去了呢宽堆?
我們定位到code/chaincode/shim/chaincode.go,我們看到兩個(gè)入口函數(shù)StartStartInProcStart為用戶鏈碼的入口函數(shù)茸习,而StartInProc是系統(tǒng)鏈碼的入口函數(shù)畜隶,他們同時(shí)都調(diào)用了chatWithPeer,因?yàn)槲覀冋{(diào)用的是lscc,就看StartInProc

// StartInProc is an entry point for system chaincodes bootstrap. It is not an
// API for chaincodes.
func StartInProc(env []string, args []string, cc Chaincode, recv <-chan *pb.ChaincodeMessage, send chan<- *pb.ChaincodeMessage) error {
    chaincodeLogger.Debugf("in proc %v", args)

    var chaincodename string
    for _, v := range env {
        if strings.Index(v, "CORE_CHAINCODE_ID_NAME=") == 0 {
            p := strings.SplitAfter(v, "CORE_CHAINCODE_ID_NAME=")
            chaincodename = p[1]
            break
        }
    }
    if chaincodename == "" {
        return errors.New("error chaincode id not provided")
    }

    stream := newInProcStream(recv, send)
    chaincodeLogger.Debugf("starting chat with peer using name=%s", chaincodename)
    err := chatWithPeer(chaincodename, stream, cc)
    return err
}

chatWithPeer就是開(kāi)啟grpc的接收模式在等到節(jié)點(diǎn)發(fā)來(lái)信息号胚,接收到信息后就調(diào)用handleMessage處理信息籽慢。

func chatWithPeer(chaincodename string, stream PeerChaincodeStream, cc Chaincode) error {
    // Create the shim handler responsible for all control logic
    handler := newChaincodeHandler(stream, cc)
    defer stream.CloseSend()

    // Send the ChaincodeID during register.
    chaincodeID := &pb.ChaincodeID{Name: chaincodename}
    payload, err := proto.Marshal(chaincodeID)
    if err != nil {
        return errors.Wrap(err, "error marshalling chaincodeID during chaincode registration")
    }

    // Register on the stream
    chaincodeLogger.Debugf("Registering.. sending %s", pb.ChaincodeMessage_REGISTER)
    if err = handler.serialSend(&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_REGISTER, Payload: payload}); err != nil {
        return errors.WithMessage(err, "error sending chaincode REGISTER")
    }

    // holds return values from gRPC Recv below
    type recvMsg struct {
        msg *pb.ChaincodeMessage
        err error
    }
    msgAvail := make(chan *recvMsg, 1)
    errc := make(chan error)

    receiveMessage := func() {
        in, err := stream.Recv()
        msgAvail <- &recvMsg{in, err}
    }
    // 開(kāi)啟線程等信息
    go receiveMessage()
    for {
        select {
        case rmsg := <-msgAvail:
            switch {
            case rmsg.err == io.EOF:
                err = errors.Wrapf(rmsg.err, "received EOF, ending chaincode stream")
                chaincodeLogger.Debugf("%+v", err)
                return err
            case rmsg.err != nil:
                err := errors.Wrap(rmsg.err, "receive failed")
                chaincodeLogger.Errorf("Received error from server, ending chaincode stream: %+v", err)
                return err
            case rmsg.msg == nil:
                err := errors.New("received nil message, ending chaincode stream")
                chaincodeLogger.Debugf("%+v", err)
                return err
            default:
                chaincodeLogger.Debugf("[%s]Received message %s from peer", shorttxid(rmsg.msg.Txid), rmsg.msg.Type)
                // 處理接收到的信息
                err := handler.handleMessage(rmsg.msg, errc)
                if err != nil {
                    err = errors.WithMessage(err, "error handling message")
                    return err
                }

                go receiveMessage()
            }

        case sendErr := <-errc:
            if sendErr != nil {
                err := errors.Wrap(sendErr, "error sending")
                return err
            }
        }
    }
}

因?yàn)槲覀冃畔㈩愋蜑?code>ChaincodeMessage_TRANSACTION,所以我們?cè)?code>core/chaincode/shim/handler.go順著handleMessage=>handleReady扎到handleTransaction

// handleTransaction Handles request to execute a transaction.
func (handler *Handler) handleTransaction(msg *pb.ChaincodeMessage, errc chan error) {
    // The defer followed by triggering a go routine dance is needed to ensure that the previous state transition
    // is completed before the next one is triggered. The previous state transition is deemed complete only when
    // the beforeInit function is exited. Interesting bug fix!!
    go func() {
        //better not be nil
        var nextStateMsg *pb.ChaincodeMessage

        defer func() {
            handler.triggerNextState(nextStateMsg, errc)
        }()

        errFunc := func(err error, ce *pb.ChaincodeEvent, errStr string, args ...interface{}) *pb.ChaincodeMessage {
            if err != nil {
                payload := []byte(err.Error())
                chaincodeLogger.Errorf(errStr, args...)
                return &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR, Payload: payload, Txid: msg.Txid, ChaincodeEvent: ce, ChannelId: msg.ChannelId}
            }
            return nil
        }

        // Get the function and args from Payload
        input := &pb.ChaincodeInput{}
        unmarshalErr := proto.Unmarshal(msg.Payload, input)
        if nextStateMsg = errFunc(unmarshalErr, nil, "[%s] Incorrect payload format. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_ERROR.String()); nextStateMsg != nil {
            return
        }

        // Call chaincode's Run
        // Create the ChaincodeStub which the chaincode can use to callback
        stub := new(ChaincodeStub)
        err := stub.init(handler, msg.ChannelId, msg.Txid, input, msg.Proposal)
        if nextStateMsg = errFunc(err, stub.chaincodeEvent, "[%s] Transaction execution failed. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_ERROR.String()); nextStateMsg != nil {
            return
        }
        res := handler.cc.Invoke(stub)

        // Endorser will handle error contained in Response.
        resBytes, err := proto.Marshal(&res)
        if nextStateMsg = errFunc(err, stub.chaincodeEvent, "[%s] Transaction execution failed. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_ERROR.String()); nextStateMsg != nil {
            return
        }

        // Send COMPLETED message to chaincode support and change state
        chaincodeLogger.Debugf("[%s] Transaction completed. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_COMPLETED)
        nextStateMsg = &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: resBytes, Txid: msg.Txid, ChaincodeEvent: stub.chaincodeEvent, ChannelId: stub.ChannelId}
    }()
}

其中關(guān)鍵語(yǔ)句res := handler.cc.Invoke(stub),這語(yǔ)句是調(diào)用相應(yīng)鏈碼的Invoke函數(shù),所以我們找到core/scc/lscc/lscc.go下的Invoke函數(shù)

5.2 鏈碼安裝

進(jìn)去core/scc/lscc/lscc.goInvoke函數(shù)可以看到涕刚,這里有"INSTALL", "DEPLOY", "UPGRADE"等操作嗡综,我們只看INSTALL部分乙帮。
關(guān)鍵調(diào)用函數(shù)是executeInstall

// Invoke implements lifecycle functions "deploy", "start", "stop", "upgrade".
// Deploy's arguments -  {[]byte("deploy"), []byte(<chainname>), <unmarshalled pb.ChaincodeDeploymentSpec>}
//
// Invoke also implements some query-like functions
// Get chaincode arguments -  {[]byte("getid"), []byte(<chainname>), []byte(<chaincodename>)}
func (lscc *LifeCycleSysCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    args := stub.GetArgs()
    if len(args) < 1 {
        return shim.Error(InvalidArgsLenErr(len(args)).Error())
    }

    function := string(args[0])

    // Handle ACL:
    // 1. get the signed proposal
    // 獲取簽名提案
    sp, err := stub.GetSignedProposal()
    if err != nil {
        return shim.Error(fmt.Sprintf("Failed retrieving signed proposal on executing %s with error %s", function, err))
    }

    switch function {
    // 鏈碼安裝
    case INSTALL:
        if len(args) < 2 {
            return shim.Error(InvalidArgsLenErr(len(args)).Error())
        }

        // 2. check local MSP Admins policy
        // 檢查 local MSP Admins 策略
        if err = lscc.PolicyChecker.CheckPolicyNoChannel(mgmt.Admins, sp); err != nil {
            return shim.Error(fmt.Sprintf("access denied for [%s]: %s", function, err))
        }

        depSpec := args[1]

        err := lscc.executeInstall(stub, depSpec)
        if err != nil {
            return shim.Error(err.Error())
        }
        return shim.Success([]byte("OK"))
    
    ... ...

接著看executeInstall

// executeInstall implements the "install" Invoke transaction
func (lscc *LifeCycleSysCC) executeInstall(stub shim.ChaincodeStubInterface, ccbytes []byte) error {
    // 還原鏈碼結(jié)構(gòu)CDSPackage
    ccpack, err := ccprovider.GetCCPackage(ccbytes)
    if err != nil {
        return err
    }
    // 獲取ChaincodeDeploymentSpec結(jié)構(gòu)數(shù)據(jù)
    cds := ccpack.GetDepSpec()

    if cds == nil {
        return fmt.Errorf("nil deployment spec from from the CC package")
    }
    // 鏈碼名字是否合法
    if err = lscc.isValidChaincodeName(cds.ChaincodeSpec.ChaincodeId.Name); err != nil {
        return err
    }
    // 鏈碼版本是否合法
    if err = lscc.isValidChaincodeVersion(cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version); err != nil {
        return err
    }

    // 系統(tǒng)鏈碼不給安裝
    if lscc.SCCProvider.IsSysCC(cds.ChaincodeSpec.ChaincodeId.Name) {
        return errors.Errorf("cannot install: %s is the name of a system chaincode", cds.ChaincodeSpec.ChaincodeId.Name)
    }

    // Get any statedb artifacts from the chaincode package, e.g. couchdb index definitions
    // 解壓狀態(tài)db數(shù)據(jù)
    statedbArtifactsTar, err := ccprovider.ExtractStatedbArtifactsFromCCPackage(ccpack, lscc.PlatformRegistry)
    if err != nil {
        return err
    }

    if err = isValidStatedbArtifactsTar(statedbArtifactsTar); err != nil {
        return InvalidStatedbArtifactsErr(err.Error())
    }

    chaincodeDefinition := &cceventmgmt.ChaincodeDefinition{
        Name:    ccpack.GetChaincodeData().Name,
        Version: ccpack.GetChaincodeData().Version,
        Hash:    ccpack.GetId()} // Note - The chaincode 'id' is the hash of chaincode's (CodeHash || MetaDataHash), aka fingerprint

    // HandleChaincodeInstall will apply any statedb artifacts (e.g. couchdb indexes) to
    // any channel's statedb where the chaincode is already instantiated
    // Note - this step is done prior to PutChaincodeToLocalStorage() since this step is idempotent and harmless until endorsements start,
    // that is, if there are errors deploying the indexes the chaincode install can safely be re-attempted later.
    // 處理安裝杜漠,含有db數(shù)據(jù)
    err = cceventmgmt.GetMgr().HandleChaincodeInstall(chaincodeDefinition, statedbArtifactsTar)
    defer func() {
        cceventmgmt.GetMgr().ChaincodeInstallDone(err == nil)
    }()
    if err != nil {
        return err
    }

    // Finally, if everything is good above, install the chaincode to local peer file system so that endorsements can start
    // 最后把文件寫(xiě)到指定文件路徑
    if err = lscc.Support.PutChaincodeToLocalStorage(ccpack); err != nil {
        return err
    }

    logger.Infof("Installed Chaincode [%s] Version [%s] to peer", ccpack.GetChaincodeData().Name, ccpack.GetChaincodeData().Version)

    return nil
}

HandleChaincodeInstall為處理statedb,而PutChaincodeToLocalStorage是把鏈碼文件安裝到本地文件目錄
鏈碼安裝到peer的默認(rèn)路徑/var/hyperledger/production/chaincodes
到此鏈碼的安裝完畢

6 提案背書(shū)返回

lscc鏈碼安裝完畢后察净,返回信息給peer節(jié)點(diǎn)驾茴,peer節(jié)點(diǎn)就給提案背書(shū)返回給client服務(wù)端,至此鏈碼安裝完畢氢卡。

github

參考:
5-ChainCode生命周期锈至、分類及安裝、實(shí)例化命令解析
fabric源碼解讀【peer chaincode】:安裝鏈碼
Fabric1.4源碼解析:客戶端安裝鏈碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末译秦,一起剝皮案震驚了整個(gè)濱河市峡捡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌筑悴,老刑警劉巖们拙,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異阁吝,居然都是意外死亡砚婆,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門突勇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)装盯,“玉大人,你說(shuō)我怎么就攤上這事甲馋」∧危” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵定躏,是天一觀的道長(zhǎng)账磺。 經(jīng)常有香客問(wèn)我海蔽,道長(zhǎng),這世上最難降的妖魔是什么绑谣? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任党窜,我火速辦了婚禮,結(jié)果婚禮上借宵,老公的妹妹穿的比我還像新娘幌衣。我一直安慰自己,他們只是感情好壤玫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布豁护。 她就那樣靜靜地躺著,像睡著了一般欲间。 火紅的嫁衣襯著肌膚如雪楚里。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天猎贴,我揣著相機(jī)與錄音班缎,去河邊找鬼。 笑死她渴,一個(gè)胖子當(dāng)著我的面吹牛达址,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播趁耗,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼沉唠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了苛败?” 一聲冷哼從身側(cè)響起满葛,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎罢屈,沒(méi)想到半個(gè)月后嘀韧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡儡遮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年乳蛾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鄙币。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肃叶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出十嘿,到底是詐尸還是另有隱情因惭,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布绩衷,位于F島的核電站蹦魔,受9級(jí)特大地震影響激率,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜勿决,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一乒躺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧低缩,春花似錦嘉冒、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至玩般,卻和暖如春银觅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坏为。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工究驴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人久脯。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓纳胧,卻偏偏與公主長(zhǎng)得像镰吆,于是被迫代替她去往敵國(guó)和親帘撰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354