Hyperledger Fabric从源码分析链码实例化过程

上篇文章——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
}

可以看到整体逻辑与链码安装几乎无异,差别在于两个点

  1. 在构造交易提案的时候不同,实例化最终走的是 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}}
    	}
    
  2. 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
}

链码实例化源码总结

  1. 根据用户执行实例化链码的命令启动全过程
  2. 初始化一个链码命令工厂,包含背书客户端,分发客户端,tls证书,签名者,广播客户端等成员信息
  3. 生成ChaincodeDeploymentSpec对象
  4. 执行链码的实例化,获取签名者 creator
  5. 执行 CreateDepolyProposalFromCDS()函数从 cds(即生成的 ChaincodeDeploymentSpec对象)中创建提案 Proposal
  6. Proposal进行签名,得到一个 SignedProposal对象signedProp
  7. signedProp通过EndorserClient.ProcessProposal方法发往指定的背书节点,即需要安装链码的节点,由背书节点的 EndorserServer进行处理
  8. 接收到由背书节点处理完成所返回的Response消息,生成一条交易,并将交易发送到排序节点
  9. 排序节点收到交易后进行排序,最终生成区块,并分发到所有记账节点
  10. 记账节点收到区块后验证有效性,最后更新账本数据

猜你喜欢

转载自blog.csdn.net/lvyibin890/article/details/106999546