Fabric 1.4源码分析 - chaincode instantiate(2) peer端的调用流程

接下来具体执行env, err := instantiate(cmd, cf),这里可以看到入参是cobra.Command和刚刚初始化的ChaincodeCmdFactory,结果是提交给Orderer的Envelope messageprotos/common.Envelope.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//instantiate the command via Endorser
func instantiate(cmd *cobra.Command, cf *ChaincodeCmdFactory) (*protcommon.Envelope, error) {

spec, err := getChaincodeSpec(cmd)
cds, err := getChaincodeDeploymentSpec(spec, false)
creator, err := cf.Signer.Serialize()
prop, _, err := utils.CreateDeployProposalFromCDS(channelID, cds, creator, policyMarshalled, []byte(escc), []byte(vscc), collectionConfigBytes)

var signedProp *pb.SignedProposal
signedProp, err = utils.GetSignedProposal(prop, cf.Signer)

// instantiate is currently only supported for one peer
proposalResponse, err := cf.EndorserClients[0].ProcessProposal(context.Background(), signedProp)

if proposalResponse != nil {
// assemble a signed transaction (it's an Envelope message)
env, err := utils.CreateSignedTx(prop, cf.Signer, proposalResponse)
return env, nil
}

return nil, nil
}
  1. getChaincodeSpecgetChaincodeDeploymentSpec从提交的command里获取相应的参数,检查以及设置参数(例如是否指定escc(endorsement system chaincode), vscc(verification system chaincode), ConstructorJSON 构造参数),进而构造chaincode描述结构体pb.ChaincodeSpec和部署结构体pb.ChaincodeDeploymentSpec
    pb.ChaincodeSpec(cs)里指定了ChaincodeSpec_Type语言类型,默认为go,ChaincodeID,chaincode的标识(包括路径Path,名称Name,版本Version),以及Input(即ConstructorJSON 构造参数,初始化的值,命令行里-c参数值,例子里的’{“Args”:[“init”,”a”, “100”, “b”,”200”]}’)。
    pb.ChaincodeDeploymentSpec(cds)里包裹ChaincodeSpec,以及CodePackage,打包后用于安装部署的代码,ExecEnv,指定运行于同一系统中或者docker容器。getChaincodeDeploymentSpec入参区分是否当前是dev开发模式,而且是否需要构造CodePackage。构造打包是在install安装时进行的,后面分析install时详述。
  1. CreateDeployProposalFromCDS从上面构造的部署结构体得到peer.ChaincodeInvocationSpec(cis),进而得到peer.Proposal,然后使用初始化时候得到的signer对这个提案进行签名,这个步骤与初始化时候非常相似。

    Transaction的结构可以参看总结Hyperledger Fabric V1.0: Block Structure

    pb.ChaincodeInvokeSpec(cis)里指定Type为golang,ChaincodeID指定为lscc(lifecycle system chaincode),然后将上面构造的cds序列化在[]byte作为Input字段。
    CreateDeployProposalFromCDS这个方法从部署结构体cds出发,生成随机字符串NONCE,使用签名生成ProposalTxID; 指定类型TypeHeaderType_ENDORSER_TRANSACTION(此处多补充一句,初始化时获取配置的类型为HeaderType_CONFIG),指定channelIdchaincodeID(包括chaincode的version,path,name)。这里的chaincodeID是构造的peer.ChaincodeInvocationSpec的参数,指定了Name为lscc,lifecycle systme chaincode。此外,还有初始化过程MSP获取的creator(即序列化后的signer)表明身份以及创建时间Timestamp等。这些构成了transaction的header,连同序列化后的ChaincodeInvocationSpec对象作为payload,共同组成了peer.Proposal{Header: hdrBytes, Payload: ccPropPayloadBytes}.最后使用signer进行签名。

  2. rpc调用。初始化时候构建了endorser client组(实际上instantiate只允许有一个)。使用该client发送请求grpc.Invoke(ctx, "/protos.Endorser/ProcessProposal", in, out, c.cc, opts...),这里ctx传入了golang的context.Background(),后面的rpc调用派生自该顶层context。服务端的处理后面再详述。注意这里是同步调用。endorser rpc的数据结构SignedProposal如下

// peer.SignedProposal:
{
    "ProposalBytes": "${propBytes}", 
    "Signature": "${signature}" // signature, err := signer.Sign(propBytes)
}

// peer.Proposal : 
{
    "Header": "${hdrBytes}", 
    "Payload": "${ccPropPayloadBytes}"
}

// common.Header(hdrBytes) :
{
    "ChannelHeader" : { // common.ChannelHeader
        "Type" : "HeaderType_ENDORSER_TRANSACTION", // common.HeaderType
        "TxId" : "#ComputeProposalTxID(nonce, creator)",
        "Timestamp": "timestamp",   // util.CreateUtcTimestamp()
        "ChannelId": "${chainID}", 
        "Extension": "${ccHdrExtBytes}",
        "Epoch":     "epoch" // always 0
    },
    "SignatureHeader" : {   // common.SignatureHeader
        "Nonce": "nonce",   // nonce, err := crypto.GetRandomNonce()
        "Creator": "creator"  // creator, err := cf.Signer.Serialize()
    }
}
// peer.ChaincodeProposalPayload(ccPropPayload)
{
    "Input": "cisBytes",
    "TransientMap": nil
}

// peer.ChaincodeHeaderExtension(ccHdrExtBytes) :
{
    "ChaincodeId" : "${cis.ChaincodeSpec.ChaincodeId}"
}

// peer.ChaincodeInvokeSpec(cis)
{
    "ChaincodeInvokeSpec" : {
        "ChaincodeSpec" : { // pb.ChaincodeSpec
            "Type" : "ChaincodeSpec_GOLANG",
            "ChaincodeId" : {
                "Name" : "lscc"
            },
            "Input" : {
                "Args" : ["deploy", "${channelId}", "${chaincodDeploySpec}"]
            }    
        }
    }
}

// pb.chaincodDeploySpec(cds)
{
    "ChaincodeSpec": {
        "Type" : "ChaincodeSpec_GOLANG",
        "ChaincodeId" : {
            "Path" : "${chaincodePath}",
            "Name" : "${chaincodeName}",
            "Version" : "${chaincodeVersion}"
        },
        "Input" : {
            "Args" : ["init","a", "100", "b","200"]
        }   
    },
    "CodePackage": nil
}
  1. 收集endorser的response,然后构建签名后的transaction。下面的方法是普适的。
// CreateSignedTx assembles an Envelope message from proposal, endorsements, and a signer.
// This function should be called by a client when it has collected enough endorsements
// for a proposal to create a transaction and submit it to peers for ordering
func CreateSignedTx(proposal *peer.Proposal, signer msp.SigningIdentity, resps ...*peer.ProposalResponse) (*common.Envelope, error) {
    // the original header
    hdr, err := GetHeader(proposal.Header)
    // the original payload
    pPayl, err := GetChaincodeProposalPayload(proposal.Payload)
    shdr, err := GetSignatureHeader(hdr.SignatureHeader)
    // get header extensions so we have the visibility field
    hdrExt, err := GetChaincodeHeaderExtension(hdr)

    // ensure that all actions are bitwise equal and that they are successful 
    // 比较所有的response返回的结果是一致的
    var a1 []byte
    for n, r := range resps {
        if n == 0 {
            a1 = r.Payload
            if r.Response.Status != 200 {
                return nil, fmt.Errorf("Proposal response was not successful, error code %d, msg %s", r.Response.Status, r.Response.Message)
            }
            continue
        }

        if bytes.Compare(a1, r.Payload) != 0 {
            return nil, fmt.Errorf("ProposalResponsePayloads do not match")
        }
    }

    // fill endorsements
    endorsements := make([]*peer.Endorsement, len(resps))
    for n, r := range resps {
        endorsements[n] = r.Endorsement
    }

    // 构造各级数据结构并序列化,包含原始的proposal的payload,header等信息,
    // 以及repsponse的payload,各个resp相应的endorsement,
    // 构造了新的peer.Transaction,然后使用signer进行签名。
    // 最后将序列化后的transaction和签名组装成common.Envelope并返回。 
}

最后,初始化时候得到的cf.BroadcastClient.Send(env).至此,完成了chaincode instantiate在peer端的所有操作。