Hyperledger fabric 1.0 代码解析 之 chaincode instantiate or upgrade

2017-08-05

1.sdk或client发起chaincode instantiate/upgrade 请求,然后开始构建一个Proposal包体。 主要过程是通过createProposalFromCDS,设置为chainID,将ChaincodeID的Name指定为lscc,然后通过CreateProposalFromCIS以及GetSignedProposal 继续打包,主要是得到一个transcation id(txid)加上各种包头等。

// createProposalFromCDS returns a deploy or upgrade proposal given a serialized identity and a ChaincodeDeploymentSpec
func createProposalFromCDS(chainID string, msg proto.Message, creator []byte, policy []byte, escc []byte, vscc []byte, propType string) (*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 {
    case "deploy":
        fallthrough
    case "upgrade":
        cds, ok := msg.(*peer.ChaincodeDeploymentSpec)
        if !ok || cds == nil {
            return nil, "", fmt.Errorf("invalid message for creating lifecycle chaincode proposal from")
        }
        //这里在deploy和upgrade的时候policy,escc,vscc都来自调用时的参数
        ccinp = &peer.ChaincodeInput{Args: [][]byte{[]byte(propType), []byte(chainID), b, policy, escc, vscc}}
    case "install":
        ccinp = &peer.ChaincodeInput{Args: [][]byte{[]byte(propType), b}}
    }

    //wrap the deployment in an invocation spec to lscc...
    lsccSpec := &peer.ChaincodeInvocationSpec{
        ChaincodeSpec: &peer.ChaincodeSpec{
            Type:        peer.ChaincodeSpec_GOLANG,
            ChaincodeId: &peer.ChaincodeID{Name: "lscc"},
            Input:       ccinp}}

    //...and get the proposal for it
    return CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, chainID, lsccSpec, creator)
}

2.通过EndorserClient发起请求,调用endorser(通常是某个或某些peer)的ProcessProposal函数,处理第一步得到的Proposal包体。从Hyperledger fabric 1.0 代码解析 之 ProcessProposal 最开始的流程图,可以看出,chaincode instantiate首先需要调用simulateProposalendorseProposal来完成整个流程。

3.simulateProposal过程中会最终通过ExecuteChaincode,执行到lscc端中的Invoke函数,处理deploy的情况。首先检查签名,然后在deploy的case中获取到policy,escc,vscc相关信息,然后传递给executeDeploy or executeUpgrade来进行chaincode的实际deploy或upgrade操作.

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:
        /*另一篇中已有分析,这里忽略*/
    case DEPLOY:
            if len(args) < 3 || len(args) > 6 {
            return shim.Error(InvalidArgsLenErr(len(args)).Error())
        }
        chainname := string(args[1])

        if !lscc.isValidChainName(chainname) {
            return shim.Error(InvalidChainNameErr(chainname).Error())
        }

        depSpec := args[2]

        // optional arguments here (they can each be nil and may or may not be present)
        // args[3] is a marshalled SignaturePolicyEnvelope representing the endorsement policy
        // args[4] is the name of escc
        // args[5] is the name of vscc
        var policy []byte
        if len(args) > 3 && len(args[3]) > 0 {
            policy = args[3]
        } else {
            //默认的policy是channel中任何一个成员背书即可
            p := cauthdsl.SignedByAnyMember(peer.GetMSPIDs(chainname))
            policy, err = utils.Marshal(p)
            if err != nil {
                return shim.Error(err.Error())
            }
        }

        var escc []byte
        if len(args) > 4 && args[4] != nil {
            escc = args[4]
        } else {
            escc = []byte("escc")
        }

        var vscc []byte
        if len(args) > 5 && args[5] != nil {
            vscc = args[5]
        } else {
            vscc = []byte("vscc")
        }

        cd, err := lscc.executeDeploy(stub, chainname, depSpec, policy, escc, vscc)
        if err != nil {
            return shim.Error(err.Error())
        }
        cdbytes, err := proto.Marshal(cd)
        if err != nil {
            return shim.Error(err.Error())
        }
        return shim.Success(cdbytes)
    case UPGRADE:
            /*暂时忽略相应情况内容,待之后相应部分分析*/
    case GETCCINFO, GETDEPSPEC, GETCCDATA:
            /*暂时忽略相应情况内容,待之后相应部分分析*/
    case GETCHAINCODES:
            /*暂时忽略相应情况内容,待之后相应部分分析*/
    case GETINSTALLEDCHAINCODES:
           /*暂时忽略相应情况内容,待之后相应部分分析*/
    return shim.Error(InvalidFunctionErr(function).Error())
}

4.executeDeploy首先进行各项检查,然后从文件系统中获取chaincode,并对是否具有Instantiation权限进行检查。最后调用createChaincode。createChaincode主要执行的是将chaincode put state到lscc.

// executeDeploy implements the "instantiate" Invoke transaction
func (lscc *LifeCycleSysCC) executeDeploy(stub shim.ChaincodeStubInterface, chainname string, depSpec []byte, policy []byte, escc []byte, vscc []byte) (*ccprovider.ChaincodeData, error) {
    cds, err := utils.GetChaincodeDeploymentSpec(depSpec)

    /*各种检查,忽略*/

    //从文件系统中获取chaincode
    ccpack, err := ccprovider.GetChaincodeFromFS(cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version)
    if err != nil {
        return nil, fmt.Errorf("cannot get package for the chaincode to be instantiated (%s:%s)-%s", cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version, err)
    }

    //this is guarantees to be not nil
    cd := ccpack.GetChaincodeData()

    //retain chaincode specific data and fill channel specific ones
    cd.Escc = string(escc)
    cd.Vscc = string(vscc)
    cd.Policy = policy

    // retrieve and evaluate instantiation policy
    cd.InstantiationPolicy, err = lscc.getInstantiationPolicy(chainname, ccpack)
    if err != nil {
        return nil, err
    }
    err = lscc.checkInstantiationPolicy(stub, chainname, cd.InstantiationPolicy)
    if err != nil {
        return nil, err
    }

    err = lscc.createChaincode(stub, cd)

    return cd, err
}

5.第4步lscc的executeDeploy执行完毕后,simulateProposal的callChaincode中的第一个executeChaincode完成。对于deploy这种比较特殊的情况,在callChaincode中还需要调用chaincode.Execute来完成chaincode的真正部署(启动了对应的docker 容器,完成了chaincode的Init操作)。其中启动相应docker 容器部分,参考Hyperledger fabric 1.0 代码解析 之 chaincode Launch

6.endorseProposal对整个结果进行背书。整个过程依然是一个callChaincode的过程,最终调用到escc的Invoke函数。Invoke函数中主要是对各个参数进行检查,并使用peer的身份进行签名。

core/scc/escc/endorser_onevalidsignature.go。

func (e *EndorserOneValidSignature) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    args := stub.GetArgs()
    if len(args) < 6 {
        return shim.Error(fmt.Sprintf("Incorrect number of arguments (expected a minimum of 5, provided %d)", len(args)))
    } else if len(args) > 8 {
        return shim.Error(fmt.Sprintf("Incorrect number of arguments (expected a maximum of 7, provided %d)", len(args)))
    }

    logger.Debugf("ESCC starts: %d args", len(args))

    // handle the header
    var hdr []byte
    if args[1] == nil {
        return shim.Error("serialized Header object is null")
    }

    hdr = args[1]

    // handle the proposal payload
    var payl []byte
    if args[2] == nil {
        return shim.Error("serialized ChaincodeProposalPayload object is null")
    }

    payl = args[2]

    // handle ChaincodeID
    if args[3] == nil {
        return shim.Error("ChaincodeID is null")
    }

    ccid, err := putils.UnmarshalChaincodeID(args[3])
    if err != nil {
        return shim.Error(err.Error())
    }

    // handle executing chaincode result
    // Status code < shim.ERRORTHRESHOLD can be endorsed
    if args[4] == nil {
        return shim.Error("Response of chaincode executing is null")
    }

    response, err := putils.GetResponse(args[4])
    if err != nil {
        return shim.Error(fmt.Sprintf("Failed to get Response of executing chaincode: %s", err.Error()))
    }

    if response.Status >= shim.ERRORTHRESHOLD {
        return shim.Error(fmt.Sprintf("Status code less than %d will be endorsed, received status code: %d", shim.ERRORTHRESHOLD, response.Status))
    }

    // handle simulation results
    var results []byte
    if args[5] == nil {
        return shim.Error("simulation results are null")
    }

    results = args[5]

    // Handle serialized events if they have been provided
    // they might be nil in case there's no events but there
    // is a visibility field specified as the next arg
    events := []byte("")
    if len(args) > 6 && args[6] != nil {
        events = args[6]
    }

    var visibility []byte
    if len(args) > 7 {
        visibility = args[7]
    }

    // obtain the default signing identity for this peer; it will be used to sign this proposal response
    localMsp := mspmgmt.GetLocalMSP()
    if localMsp == nil {
        return shim.Error("Nil local MSP manager")
    }

    signingEndorser, err := localMsp.GetDefaultSigningIdentity()
    if err != nil {
        return shim.Error(fmt.Sprintf("Could not obtain the default signing identity, err %s", err))
    }

    // obtain a proposal response
    presp, err := utils.CreateProposalResponse(hdr, payl, response, results, events, ccid, visibility, signingEndorser)
    if err != nil {
        return shim.Error(err.Error())
    }

    // marshall the proposal response so that we return its bytes
    prBytes, err := utils.GetBytesProposalResponse(presp)
    if err != nil {
        return shim.Error(fmt.Sprintf("Could not marshal ProposalResponse: err %s", err))
    }

    pResp, err := utils.GetProposalResponse(prBytes)
    if err != nil {
        return shim.Error(err.Error())
    }
    if pResp.Response == nil {
        fmt.Println("GetProposalResponse get empty Response")
    }

    logger.Debugf("ESCC exits successfully")
    return shim.Success(prBytes)
}

7.sdk或者client拿到结果后,还会与orderer交互,最后写block等,这部分回头再补充。