• Fabric1.4源码解析:链码实例化过程


    之前说完了链码的安装过程,接下来说一下链码的实例化过程好了,再然后是链码的调用过程。其实这几个过程内容已经很相似了,都是涉及到Proposal,不过整体流程还是要说一下的。
    同样,切入点仍然是fabric/peer/main.go文件中的main()方法:

    #这一句定义了关于通过Peer节点操作链码的命令
    mainCmd.AddCommand(chaincode.Cmd(nil))
    

    然后是fabric/peer/chaincode/chaincode.go文件中的Cmd()方法,这里则是具体的操作链码的命令,其中就有对链码进行实例化的命令:

    chaincodeCmd.AddCommand(instantiateCmd(cf))
    

    最后调用到了fabric/peer/chaincode/instantiate.go文件中的第57行的instantiate()方法。也就是说,当我们在Peer节点执行以下命令时,最终会到这个方法:

    #以官方的实例化链码的方法为例
    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')"
    

    接下来就看一下instantiate()方法:

    #首先获取要实例化的链码的信息
    spec, err := getChaincodeSpec(cmd)
    if err != nil {
    	return nil, err
    }
    

    getChaincodeSpec()方法在peer/chaincode/common.go文件中第69行:

        #将用户实例化链码所执行的命令作为参数传入进去
    func getChaincodeSpec(cmd *cobra.Command) (*pb.ChaincodeSpec, error) {
        #定义一个ChaincodeSpec结构体
    	spec := &pb.ChaincodeSpec{}
    ====================ChaincodeSpec===========================
    type ChaincodeSpec struct {
        #Type表示链码的类型 有GOLANG,NODE,CAR,JAVA,UNDEFINED五种类型
    	Type                 ChaincodeSpec_Type `protobuf:"varint,1,opt,name=type,proto3,enum=protos.ChaincodeSpec_Type" json:"type,omitempty"`
        #ChaincodeId也是一个结构体,定义了链码的路径信息,链码的名称以及版本信息
    	ChaincodeId          *ChaincodeID       `protobuf:"bytes,2,opt,name=chaincode_id,json=chaincodeId,proto3" json:"chaincode_id,omitempty"`
        #ChaincodeInput结构体中定义链码的功能以及函数参数信息
    	Input                *ChaincodeInput    `protobuf:"bytes,3,opt,name=input,proto3" json:"input,omitempty"`
    	Timeout              int32              `protobuf:"varint,4,opt,name=timeout,proto3" json:"timeout,omitempty"`
    	XXX_NoUnkeyedLiteral struct{}           `json:"-"`
    	XXX_unrecognized     []byte             `json:"-"`
    	XXX_sizecache        int32              `json:"-"`
    }
    ====================ChaincodeSpec===========================
        #对用户输入的命令进行检查
    	if err := checkChaincodeCmdParams(cmd); err != nil {
    		// unset usage silence because it's a command line usage error
    		cmd.SilenceUsage = false
    		return spec, err
    	}
    
    	#定义ChaincodeInput结构体,就是上面说过的那个
    	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结构体返回 
    	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
    }
    

    看一下checkChaincodeCmdParams()方法做了哪些工作,在219行:

    func checkChaincodeCmdParams(cmd *cobra.Command) error {
    	#检查用户输入的链码名称是否为空字符串
    	if chaincodeName == common.UndefinedParamValue {
    		return errors.Errorf("must supply value for %s name parameter", chainFuncName)
    	}
        #调用的方法是否为instantiate,install,upgrade,package其中的一个
    	if cmd.Name() == instantiateCmdName || cmd.Name() == installCmdName ||
    		cmd.Name() == upgradeCmdName || cmd.Name() == packageCmdName {
    		if chaincodeVersion == common.UndefinedParamValue {
    			return errors.Errorf("chaincode version is not provided for %s", cmd.Name())
    		}
    
    		if escc != common.UndefinedParamValue {
    			logger.Infof("Using escc %s", escc)
    		} else {
    			logger.Info("Using default escc")
    			escc = "escc"
    		}
    
    		if vscc != common.UndefinedParamValue {
    			logger.Infof("Using vscc %s", vscc)
    		} else {
    			logger.Info("Using default vscc")
    			vscc = "vscc"
    		}
    
    		if policy != common.UndefinedParamValue {
                #获取定义的策略,就比如   OR ('Org1MSP.member','Org2MSP.member')这条信息是否有误
    			p, err := cauthdsl.FromString(policy)
    			if err != nil {
    				return errors.Errorf("invalid policy %s", policy)
    			}
    			policyMarshalled = putils.MarshalOrPanic(p)
    		}
            #如果定义了配置文件,则从配置文件中读取配置信息
    		if collectionsConfigFile != common.UndefinedParamValue {
    			var err error
    			collectionConfigBytes, err = getCollectionConfigFromFile(collectionsConfigFile)
    			if err != nil {
    				return errors.WithMessage(err, fmt.Sprintf("invalid collection configuration in file %s", collectionsConfigFile))
    			}
    		}
    	}
        #对用户传入的实例化参数比如:-c '{"Args":["init","a","100","b","200"]}'
    	if chaincodeCtorJSON != "{}" {
    		...
    	}
    
    	return nil
    }
    

    回到instantiate()方法:

    cds, err := getChaincodeDeploymentSpec(spec, false)
    if err != nil {
    	return nil, fmt.Errorf("error getting chaincode code %s: %s", chaincodeName, err)
    }
    

    获取ChaincodeDeploymentSpec这个结构体:

    type ChaincodeDeploymentSpec struct {
        #这个是之前获取到的结构体
    	ChaincodeSpec        *ChaincodeSpec                               `protobuf:"bytes,1,opt,name=chaincode_spec,json=chaincodeSpec,proto3" json:"chaincode_spec,omitempty"`
        #链码数据 
    	CodePackage          []byte                                       `protobuf:"bytes,3,opt,name=code_package,json=codePackage,proto3" json:"code_package,omitempty"`
        #链码的运行环境,有两种,Docker容器或者直接在系统中运行
    	ExecEnv              ChaincodeDeploymentSpec_ExecutionEnvironment `protobuf:"varint,4,opt,name=exec_env,json=execEnv,proto3,enum=protos.ChaincodeDeploymentSpec_ExecutionEnvironment" json:"exec_env,omitempty"`
    	XXX_NoUnkeyedLiteral struct{}                                     `json:"-"`
    	XXX_unrecognized     []byte                                       `json:"-"`
    	XXX_sizecache        int32                                        `json:"-"`
    }
    

    看一下如何获取ChaincodeDeploymentSpec结构体:

    #定义了ChaincodeDeploymentSpec中的CodePackage
    var codePackageBytes []byte
    #判断是否为开发模式
    if chaincode.IsDevMode() == false && crtPkg {
    	var err error
        #如果不是则检查链码是否为空,以及路径是否正确
    	if err = checkSpec(spec); err != nil {
    		return nil, err
    	}
          #将链码转换为Byte数据
    	codePackageBytes, err = container.GetChaincodePackageBytes(platformRegistry, spec)
    	...
    }
    #构造chaincodeDeploymentSpec并返回 
    chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes}
    return chaincodeDeploymentSpec, nil
    

    回到instantiate()方法:

    #获取一全个签名者,需要对创建实例化链码的Proposal进行签名
    creator, err := cf.Signer.Serialize()
    if err != nil {
    		return nil, fmt.Errorf("error serializing identity for %s: %s", cf.Signer.GetIdentifier(), err)
    }
    #要创建用于实例化链码的Proposal了
    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)
    }
    

    看一下CreateDeployProposalFromCDS()方法,看名字了解到是根据chaincodeDeploymentSpec创建用于部署链码的Proposal

    func CreateDeployProposalFromCDS(
        #通道Id
    	chainID string,
    	cds *peer.ChaincodeDeploymentSpec,
        #签名者
    	creator []byte,
        #具体的策略
    	policy []byte,
        #endorser system chaincode
    	escc []byte,
        #Verification System ChainCode
    	vscc []byte,
    	collectionConfig []byte) (*peer.Proposal, string, error) {
        #下面的两个方法调用的是同一个,只是传入的参数不同,点进去
    	if collectionConfig == nil {
    		return createProposalFromCDS(chainID, cds, creator, "deploy", policy, escc, vscc)
    	}
    	return createProposalFromCDS(chainID, cds, creator, "deploy", policy, escc, vscc, collectionConfig)
    }
    

    该方法在538行,接下来的部分与客户端安装链码所执行的流程基本是相同的,只有下面的一部分不同:

    #对于实例化链码来说,执行的是deploy与upgrade这两部分,而安装链码则是install这部分,差异就在于ChaincodeInput结构体内的参数不同
    case "deploy":
    		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{[]byte(propType), []byte(chainID), b}
    		Args = append(Args, args...)
    
    		ccinp = &peer.ChaincodeInput{Args: Args}
    	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,
    		},
    	}
    

    剩下的部分就不再重复看了,可以参考Fabric1.4源码解析:客户端安装链码这篇文章。
    总的来说,整个流程共有以下几部分:

    1. 根据用户执行实例化链码的命令启动全过程
    2. 获取需要实例化链码的基本信息
    3. 创建ChaincodeDeploymentSpec结构体.
    4. 获取用于对Proposal进行签名的Creator
    5. 创建ProposalProposalHeader定义为ENDORSER_TRANSACTION,表示是一个需要背书的交易。
    6. 由之前获取的Creator进行签名操作。
    7. Peer节点调用ProcessProposal()方法进行处理,该方法的解析在这里。这是一个很重要的方法。
    8. 接收到由Peer节点处理完成所返回的Response消息后发送到Orderer节点。
    9. Orderer节点接收到消息后进行排序操作,如果是SOLO模式则由Orderer节点生成区块,最后将区块广播至Peer节点,
    10. Peer节点接收到区块消息后验证有效性,最后更新账本数据。

    最后附上参考链接:1.传送门
    2.传送门

  • 相关阅读:
    RabbitMQ消息队列
    集群概念
    重新学习CSS,认识CSS3中的属性
    计算机网络中,路由器和交换机的区别
    微信小程序中,如何点击链接跳转到外部网页
    微信小程序中,如何实现显示,隐藏密码的功能
    Vue 中引入echarts
    解决Ubuntu(linux)系统中PHP的curl函数无法使用的问题
    数据通信的基本知识
    计算机网络的性能
  • 原文地址:https://www.cnblogs.com/cbkj-xd/p/11149791.html
Copyright © 2020-2023  润新知