上篇文章——Hyperledger Fabric从源码分析链码安装过程,分析了链码安装的大致过程,这篇文章分析一下链码实例化的过程,其中有一部分的代码非常相似,类似的函数解析我就不再展开了,可以直接看链码安装文章的相关介绍。
好了下面就开始吧。
相关源码入口在peer/chaincode/instantiate.go
中
先给一个官方实例化的例子吧
peer chaincode instantiate -o orderer.example.com:7050 --tls true --cafile $ORDERER_CA -C mychannel -n mycc -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P "OR ('Org1MSP.member','Org2MSP.member')"
来看下命令函数instantiateCmd
// instantiateCmd returns the cobra command for Chaincode Deploy
func instantiateCmd(cf *ChaincodeCmdFactory) *cobra.Command {
chaincodeInstantiateCmd = &cobra.Command{
Use: instantiateCmdName,
Short: fmt.Sprint(instantiateDesc),
Long: fmt.Sprint(instantiateDesc),
ValidArgs: []string{"1"},
RunE: func(cmd *cobra.Command, args []string) error {
// 链码最终执行的函数chaincodeDeploy
return chaincodeDeploy(cmd, args, cf)
},
}
// instantiate命令的可用参数,以官方实例化的例子为例
flagList := []string{
"lang",
"ctor",
"name",
"channelID",
"version",
"policy",
"escc",
"vscc",
"collections-config",
"peerAddresses",
"tlsRootCertFiles",
"connectionProfile",
}
attachFlags(chaincodeInstantiateCmd, flagList)
return chaincodeInstantiateCmd
}
下面来看下链码最终执行的函数chaincodeDeploy()
,在peer/chaincode/instantiate.go
的106行
func chaincodeDeploy(cmd *cobra.Command, args []string, cf *ChaincodeCmdFactory) error {
// 如果没有指定channelID,则直接返回错误,该变量由-C参数指定
if channelID == "" {
return errors.New("The required parameter 'channelID' is empty. Rerun the command with -C flag")
}
// Parsing of the command line is done so silence cmd usage
cmd.SilenceUsage = true
var err error
if cf == nil {
// 和install一样,创建一个CmdFactory
cf, err = InitCmdFactory(cmd.Name(), true, true)
if err != nil {
return err
}
}
// 延迟调用,实例化完了以后关闭广播客户端
defer cf.BroadcastClient.Close()
// 执行instantiate函数,完成实例化过程
env, err := instantiate(cmd, cf)
if err != nil {
return err
}
// 向排序节点发送交易
if env != nil {
err = cf.BroadcastClient.Send(env)
}
return err
}
接下来看看instantiate()
函数,在peer/chaincode/instantiate.go
的57行:
//instantiate the command via Endorser
func instantiate(cmd *cobra.Command, cf *ChaincodeCmdFactory) (*protcommon.Envelope, error) {
// 与install一样,获取一个ChaincodeSpec链码标准数据结构
spec, err := getChaincodeSpec(cmd)
if err != nil {
return nil, err
}
// 与install一样,获取一个ChaincodeDeploymentSpec链码部署标准数据结构
cds, err := getChaincodeDeploymentSpec(spec, false)
if err != nil {
return nil, fmt.Errorf("error getting chaincode code %s: %s", chaincodeName, err)
}
// 与install一样,获取一个签名者creator
creator, err := cf.Signer.Serialize()
if err != nil {
return nil, fmt.Errorf("error serializing identity for %s: %s", cf.Signer.GetIdentifier(), err)
}
// 注意这个函数与install不一样了,创建一个交易提案
// 这里是CreateDeployProposalFromCDS
// install是CreateInstallProposalFromCDS
// 不过他们最终调用的都是createProposalFromCDS函数,只是传入的参数不同,instantiate走的是deploy分支,会继续走到upgrade分支中去,而install走的是install分支,具体可以参考上篇文章中关于createProposalFromCDS函数的解析
prop, _, err := utils.CreateDeployProposalFromCDS(channelID, cds, creator, policyMarshalled, []byte(escc), []byte(vscc), collectionConfigBytes)
if err != nil {
return nil, fmt.Errorf("error creating proposal %s: %s", chainFuncName, err)
}
// 与install一样,对上面生成的提案进行签名
var signedProp *pb.SignedProposal
signedProp, err = utils.GetSignedProposal(prop, cf.Signer)
if err != nil {
return nil, fmt.Errorf("error creating signed proposal %s: %s", chainFuncName, err)
}
// instantiate is currently only supported for one peer
// 与install一样,向背书节点发送交易提案
proposalResponse, err := cf.EndorserClients[0].ProcessProposal(context.Background(), signedProp)
if err != nil {
return nil, fmt.Errorf("error endorsing %s: %s", chainFuncName, err)
}
// 如果提案相应不为nil,就生成一个Envelope对象,即交易对象,
if proposalResponse != nil {
// assemble a signed transaction (it's an Envelope message)
// 创建一个签名的交易,这个方法稍后会讲到
env, err := utils.CreateSignedTx(prop, cf.Signer, proposalResponse)
if err != nil {
return nil, fmt.Errorf("could not assemble transaction, err %s", err)
}
return env, nil
}
return nil, nil
}
可以看到整体逻辑与链码安装几乎无异,差别在于两个点
在构造交易提案的时候不同,实例化最终走的是 upgrade 的 case,而 install走的是 install 的 case,最终调用的都是 lscc 的相关函数,实例化调用的是 deploy 方法,而 install 调用的是 install 方法
switch propType {
case "deploy":
// 实例化走这里,fallthrough到upgrade
fallthrough
case "upgrade":
cds, ok := msg.(*peer.ChaincodeDeploymentSpec)
if !ok || cds == nil {
return nil, "", errors.New("invalid message for creating lifecycle chaincode proposal")
}
// Args的第一个参数,[]byte(propType),最终会被解释为lscc的某个方法,[]byte(chainID)即channelID
Args := [][]byte{[]byte(propType), []byte(chainID), b}
Args = append(Args, args...)
ccinp = &peer.ChaincodeInput{Args: Args}
case "install":
// install走这里
ccinp = &peer.ChaincodeInput{Args: [][]byte{[]byte(propType), b}}
}
对 ProcessProposal()
方法的响应处理,install 只是判断返回是否有 err,或是返回是 status 是否错误;而实例化对返回响应有跟进一步的措施,它需要重新生成一个交易,并其发送给排序节点,最终记录到账本中。
type BroadcastClient interface {
//Send data to orderer
Send(env *cb.Envelope) error
Close() error
}
// 执行instantiate函数,完成实例化过程
env, err := instantiate(cmd, cf)
if err != nil {
return err
}
// 向排序节点发送交易
if env != nil {
err = cf.BroadcastClient.Send(env)
}
还有一个陌生的方法还没讲到,CreateSignedTx()
,用于生成一个Envelope
交易对象,下面来看看这个方法,在protos/utils/txutils.go
的117行:
// 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
// 注释已经很清楚了,这个函数从给入的参数,proposal,signer,endorsements中生成一个交易,这个函数在客户端收集到足够多的背书节点的响应之后被调用,用于生成一个交易并将它提交到排序节点
func CreateSignedTx(proposal *peer.Proposal, signer msp.SigningIdentity, resps ...*peer.ProposalResponse) (*common.Envelope, error) {
if len(resps) == 0 {
return nil, errors.New("at least one proposal response is required")
}
// the original header
// 获取提案的 Header
hdr, err := GetHeader(proposal.Header)
if err != nil {
return nil, err
}
// the original payload
// 获取提案的payload
pPayl, err := GetChaincodeProposalPayload(proposal.Payload)
if err != nil {
return nil, err
}
// check that the signer is the same that is referenced in the header
signerBytes, err := signer.Serialize()
if err != nil {
return nil, err
}
shdr, err := GetSignatureHeader(hdr.SignatureHeader)
if err != nil {
return nil, err
}
// 比较signer是否和Header中的SignatureHeader的creator字段一样
if bytes.Compare(signerBytes, shdr.Creator) != 0 {
return nil, errors.New("signer must be the same as the one referenced in the header")
}
// get header extensions so we have the visibility field
// 获取头部扩展部分
hdrExt, err := GetChaincodeHeaderExtension(hdr)
if err != nil {
return nil, err
}
// ensure that all actions are bitwise equal and that they are successful
var a1 []byte
// 比较每个提案相应是否相等,有不相等的就返回错误
for n, r := range resps {
if n == 0 {
a1 = r.Payload
if r.Response.Status < 200 || r.Response.Status >= 400 {
return nil, errors.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, errors.New("ProposalResponsePayloads do not match")
}
}
// fill endorsements
endorsements := make([]*peer.Endorsement, len(resps))
for n, r := range resps {
endorsements[n] = r.Endorsement
}
// create ChaincodeEndorsedAction
// 创建 ChaincodeEndorsedAction 对象
cea := &peer.ChaincodeEndorsedAction{ProposalResponsePayload: resps[0].Payload, Endorsements: endorsements}
// obtain the bytes of the proposal payload that will go to the transaction
// 获取 propPayloadBytes
propPayloadBytes, err := GetBytesProposalPayloadForTx(pPayl, hdrExt.PayloadVisibility)
if err != nil {
return nil, err
}
// serialize the chaincode action payload
// 构造 ChaincodeActionPayload 对象,并序列化
cap := &peer.ChaincodeActionPayload{ChaincodeProposalPayload: propPayloadBytes, Action: cea}
capBytes, err := GetBytesChaincodeActionPayload(cap)
if err != nil {
return nil, err
}
// create a transaction
// 构造一个 TransactionAction 对象,并序列化
taa := &peer.TransactionAction{Header: hdr.SignatureHeader, Payload: capBytes}
taas := make([]*peer.TransactionAction, 1)
taas[0] = taa
tx := &peer.Transaction{Actions: taas}
// serialize the tx
txBytes, err := GetBytesTransaction(tx)
if err != nil {
return nil, err
}
// create the payload
// 创建 Envelope payload
payl := &common.Payload{Header: hdr, Data: txBytes}
paylBytes, err := GetBytesPayload(payl)
if err != nil {
return nil, err
}
// sign the payload
// 获取签名
sig, err := signer.Sign(paylBytes)
if err != nil {
return nil, err
}
// here's the envelope
return &common.Envelope{Payload: paylBytes, Signature: sig}, nil
}
ChaincodeDeploymentSpec
对象CreateDepolyProposalFromCDS()
函数从 cds(即生成的 ChaincodeDeploymentSpec
对象)中创建提案 Proposal
Proposal
进行签名,得到一个 SignedProposal
对象signedProp
signedProp
通过EndorserClient.ProcessProposal
方法发往指定的背书节点,即需要安装链码的节点,由背书节点的 EndorserServer
进行处理Response
消息,生成一条交易,并将交易发送到排序节点