Hyperledger Fabric从源码分析链码安装过程

用了 Fabric 也有一个多月了,写过使用过其中的链码,但是还没有搞清楚链码从安装到实例化,再到链码调用的整个具体流程是怎样的。接下来会写几篇文章,从源码角度来分析一下链码,本篇文章就先来分析一下链码的安装过程。

本文源码剖析都是在 Fabric1.4 版本中解析

如何找到链码相关的源码入口

在源码文件结构中,peer 目录是 peer 有关的命令的相关解析,它下面每一个目录都对应着一个子命令,例如 chaincode 目录对应的就是 peer chaincode相关的命令,当然下面还会有一些子命令,总之整体的项目结构是这样的,可以根据相关文件去找相关命令的入口。

链码的安装相关源码在 peer/chaincode/install.go

链码安装源码分析

Fabric 命令相关的库大部分使用的是 cobra这个命令库,它是一个功能很强大的命令库,并且经常与 viper配置库相结合使用。这个库具体的使用这里就不细说了,我们只挑挑重点的说

在 33 行 installCmd函数中,有一个 RunE字段,指定了 install 命令最终去执行的函数,它最终会去执行chaincodeInstall函数。

// installCmd returns the cobra command for Chaincode Deploy
func installCmd(cf *ChaincodeCmdFactory) *cobra.Command {
	chaincodeInstallCmd = &cobra.Command{
		Use:       "install",
		Short:     fmt.Sprint(installDesc),
		Long:      fmt.Sprint(installDesc),
		ValidArgs: []string{"1"},
		RunE: func(cmd *cobra.Command, args []string) error {
      // 定义链码文件,为命令的第一个参数
      // 官方举例的一条安装命令如下:
      // peer chaincode install -n mycc -v 1.0 -p github.com/chaincode/chaincode_example02/go/
      // 这种情况下args为0,-p后面跟的是指定的链码文件path
			var ccpackfile string
			if len(args) > 0 {
				ccpackfile = args[0]
			}
			return chaincodeInstall(cmd, ccpackfile, cf)
		},
	}
  // 执行可以在install命令中指定的相关flag
	flagList := []string{
		"lang",
		"ctor",
		"path",
		"name",
		"version",
		"peerAddresses",
		"tlsRootCertFiles",
		"connectionProfile",
	}
	attachFlags(chaincodeInstallCmd, flagList)

	return chaincodeInstallCmd
}

接下来就来看下chaincodeInstall的相关实现

// chaincodeInstall installs the chaincode. If remoteinstall, does it via a lscc call
func chaincodeInstall(cmd *cobra.Command, ccpackfile string, cf *ChaincodeCmdFactory) error {
	// Parsing of the command line is done so silence cmd usage
	cmd.SilenceUsage = true

	var err error
	if cf == nil {
    // 这里传进来的参数cf是nil,到这里就会初始化一个ChaincodeCmdFactory结构体,类似于一个链码命令工厂
		cf, err = InitCmdFactory(cmd.Name(), true, false)
        // ChaincodeCmdFactory holds the clients used by ChaincodeCmd
		//type ChaincodeCmdFactory struct {
		//	EndorserClients []pb.EndorserClient	 // 背书客户端,用于背书
		//	DeliverClients  []api.PeerDeliverClient // Deliver客户端,用于向peer的DeliverServer发送消息
		//	Certificate     tls.Certificate // tls证书相关信息
		//	Signer          msp.SigningIdentity // 用于消息的签名
		//	BroadcastClient common.BroadcastClient // 广播客户端,用于向orderer节点发送消息
		// }
		if err != nil {
			return err
		}
	}

	var ccpackmsg proto.Message
  // 这里ccpackfile可能为空也可能不为空,分为两种情况
  // ccpackfile为空,则链码可能是根据传入参数从本地链码源代码文件读取
  // ccpackfile不为空,根据注释,是根据package子命令或者signpackage打包的一个链码package,具体可以看两者的实现
  // 我们只分析ccpackfile为空的情况
	if ccpackfile == "" {
    // 从本地源代码出读取,-p参数指定chaincodePath,-v参数指定chaincodeVersion,-n参数指定chaincodeName,这也就是一条标准的chaincodeinstall命令必须要包含的参数,少一个都不行
		if chaincodePath == common.UndefinedParamValue || chaincodeVersion == common.UndefinedParamValue || chaincodeName == common.UndefinedParamValue {
			return fmt.Errorf("Must supply value for %s name, path and version parameters.", chainFuncName)
		}
		// 生成一个ChaincodeDeploymentSpec对象,下面看看这个函数的实现
		ccpackmsg, err = genChaincodeDeploymentSpec(cmd, chaincodeName, chaincodeVersion)
		if err != nil {
			return err
		}
	} else {
		//read in a package generated by the "package" sub-command (and perhaps signed
	// ...........
  }
  err = install(ccpackmsg, cf)

	return err
}

genChaincodeDeploymentSpec()方法在第99行:

// ChaincodePackageExists returns whether the chaincode package exists in the file system
func ChaincodePackageExists(ccname string, ccversion string) (bool, error) {
  // chaincodeInstallPath是一个全局变量,在peer节点启动时创建chaincode server时设置了,是peer容器内部的一个链码路径
  // 默认路径是 /var/hyperledger/production/chaincodes
	path := filepath.Join(chaincodeInstallPath, ccname+"."+ccversion)
	_, err := os.Stat(path)
	if err == nil {
		// chaincodepackage already exists
		return true, nil
	}
	return false, err
}

//genChaincodeDeploymentSpec creates ChaincodeDeploymentSpec as the package to install
func genChaincodeDeploymentSpec(cmd *cobra.Command, chaincodeName, chaincodeVersion string) (*pb.ChaincodeDeploymentSpec, error) {
  // 根据链码名称与链码版本号,查找chaincode package是否已经被安装了
  // 该函数实现在上面
	if existed, _ := ccprovider.ChaincodePackageExists(chaincodeName, chaincodeVersion); existed {
		return nil, fmt.Errorf("chaincode %s:%s already exists", chaincodeName, chaincodeVersion)
	}
	// 走到这都是链码还未安装
  // 获取一个 ChaincodeSpec 数据结构,声明链码的标准信息
	spec, err := getChaincodeSpec(cmd)
	if err != nil {
		return nil, err
	}
	
  // 获取一个 ChaincodeDeploymentSpec 数据结构,指定链码的部署信息
	cds, err := getChaincodeDeploymentSpec(spec, true)
	if err != nil {
		return nil, fmt.Errorf("error getting chaincode code %s: %s", chaincodeName, err)
	}
	
  // 最终返回得到的ChaincodeDeploymentSpec数据结构
	return cds, nil
}

上面函数主要有两个关键的函数,getChaincodeSpec()getChaincodeDeploymentSpec(),先来看看getChaincodeSpec()函数,代码实现在 peer/chaincode/common.go69行

// Carries the chaincode specification. This is the actual metadata required for
// defining a chaincode.
type ChaincodeSpec struct {
	Type                 ChaincodeSpec_Type 
	ChaincodeId          *ChaincodeID       
	Input                *ChaincodeInput    
	Timeout              int32              
	XXX_NoUnkeyedLiteral struct{}          
	XXX_unrecognized     []byte            
	XXX_sizecache        int32              
}

// Carries the chaincode function and its arguments.
// UnmarshalJSON in transaction.go converts the string-based REST/JSON input to
// the []byte-based current ChaincodeInput structure.
type ChaincodeInput struct {
	Args                 [][]byte          
	Decorations          map[string][]byte 
	XXX_NoUnkeyedLiteral struct{}         
	XXX_unrecognized     []byte            
	XXX_sizecache        int32            
}

// ChaincodeID contains the path as specified by the deploy transaction
// that created it as well as the hashCode that is generated by the
// system for the path. From the user level (ie, CLI, REST API and so on)
// deploy transaction is expected to provide the path and other requests
// are expected to provide the hashCode. The other value will be ignored.
// Internally, the structure could contain both values. For instance, the
// hashCode will be set when first generated using the path
type ChaincodeID struct {
	Path string 
	Name string 
	Version              string   
	XXX_NoUnkeyedLiteral struct{} 
	XXX_unrecognized     []byte  
	XXX_sizecache        int32    
}

// getChaincodeSpec get chaincode spec from the cli cmd pramameters
func getChaincodeSpec(cmd *cobra.Command) (*pb.ChaincodeSpec, error) {
  // 定义一个链码标准数据结构
	spec := &pb.ChaincodeSpec{}
  // 检查用户输入的命令中的参数信息,如果出错则返回命令使用的错误
	if err := checkChaincodeCmdParams(cmd); err != nil {
		// unset usage silence because it's a command line usage error
		cmd.SilenceUsage = false
		return spec, err
	}

	// Build the spec
  // 定义一个链码输入参数结构,该结构体用于保存链码中定义的功能以及参数等信息,install命令一般不指定这个命令,在执行invoke命令时指定的-c参数就是指定了chaincodeCtorJSON,一般会这么指定
  // -c '{"Args":["a","100"]}'
	input := &pb.ChaincodeInput{}
	if err := json.Unmarshal([]byte(chaincodeCtorJSON), &input); err != nil {
		return spec, errors.Wrap(err, "chaincode argument error")
	}
	
  // chaincodeLang默认是golang,将其转换为答谢字母
	chaincodeLang = strings.ToUpper(chaincodeLang)
  // 最后封装成一个完整的 ChaincodeSpec 对象返回
	spec = &pb.ChaincodeSpec{
		Type:        pb.ChaincodeSpec_Type(pb.ChaincodeSpec_Type_value[chaincodeLang]),	// 该字段最终是一个GOLANG类型对应的ChaincodeSpec_Type_value类型,是一个int32类型,GOLANG对应的是1
		ChaincodeId: &pb.ChaincodeID{Path: chaincodePath, Name: chaincodeName, Version: chaincodeVersion},	// 该字段是一个ChaincodeID类型的数据,包括chaincodePath,chaincodeName以及chaincodeVersion
		Input:       input,	// 刚刚设置的 Input
	}
	return spec, nil
}

调用getChaincodeSpec()获得了一个链码标准数据结构以后,就可以用它作为参数传给getChaincodeDeploymentSpec(spec, true)获取一个 ChaincodeDeploymentSpec 数据结构,该方法在peer/chaincode/common.go的50行:

type ChaincodeDeploymentSpec struct {
	ChaincodeSpec        *ChaincodeSpec                               
	CodePackage          []byte                                       
	ExecEnv              ChaincodeDeploymentSpec_ExecutionEnvironment // 是一个int32类型
	XXX_NoUnkeyedLiteral struct{}                                     
	XXX_unrecognized     []byte                                       
	XXX_sizecache        int32                                        
}

// getChaincodeDeploymentSpec get chaincode deployment spec given the chaincode spec
func getChaincodeDeploymentSpec(spec *pb.ChaincodeSpec, crtPkg bool) (*pb.ChaincodeDeploymentSpec, error) {
	var codePackageBytes []byte
  // 判断当前是否为开发者模式,不是的话进入这里
	if chaincode.IsDevMode() == false && crtPkg {
		var err error
    // 检查传进来的ChaincodeSpec对象,检查是否为空,链码类型等信息
		if err = checkSpec(spec); err != nil {
			return nil, err
		}
		
   // 获取链码的payload,也就是获取链码源代码,以及一些环境变量等等信息,最终返回的是一个[]byte信息
		codePackageBytes, err = container.GetChaincodePackageBytes(platformRegistry, spec)
		if err != nil {
			err = errors.WithMessage(err, "error getting chaincode package bytes")
			return nil, err
		}
	}
  // 最终返回一个ChaincodeDeploymentSpec对象,这里如果Fabric出于开发者模式,那么codePackageBytes就是空
	chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes}
	return chaincodeDeploymentSpec, nil
}

返回到最初的 chaincodeInstall()函数,如果是本地安装的话,接下来一步就是要安装链码了,这里提一下如果 ccpackfile 不会空的那个分支,不同的点是该分支是从打包好的链码文件中获取链码相关信息,最终得到的也是一个ChaincodeDeploymentSpec 对象

func chaincodeInstall(cmd *cobra.Command, ccpackfile string, cf *ChaincodeCmdFactory) error {
	// ...........
  var ccpackmsg proto.Message
	if ccpackfile == "" {
    // ...........
    // 刚刚看到这里
		ccpackmsg, err = genChaincodeDeploymentSpec(cmd, chaincodeName, chaincodeVersion)
		if err != nil {
			return err
		}
	} else {
		var cds *pb.ChaincodeDeploymentSpec
    // 从文件中读取已定义的ChaincodeDeploymentSpec
		ccpackmsg, cds, err = getPackageFromFile(ccpackfile)

		if err != nil {
			return err
		}
		
    // 下面其实都是一些链码信息的检查之类的
		//get the chaincode details from cds
		cName := cds.ChaincodeSpec.ChaincodeId.Name
		cVersion := cds.ChaincodeSpec.ChaincodeId.Version

		//if user provided chaincodeName, use it for validation
		if chaincodeName != "" && chaincodeName != cName {
			return fmt.Errorf("chaincode name %s does not match name %s in package", chaincodeName, cName)
		}

		//if user provided chaincodeVersion, use it for validation
		if chaincodeVersion != "" && chaincodeVersion != cVersion {
			return fmt.Errorf("chaincode version %s does not match version %s in packages", chaincodeVersion, cVersion)
		}
  }
  err = install(ccpackmsg, cf)

	return err
}

看下 install()函数,在peer/chaincode/install.go63 行:

//install the depspec to "peer.address"
func install(msg proto.Message, cf *ChaincodeCmdFactory) error {
  // 从 ChaincodeCmdFactory 中获取一个用于发起提案与签名的creator
	creator, err := cf.Signer.Serialize()
	if err != nil {
		return fmt.Errorf("Error serializing identity for %s: %s", cf.Signer.GetIdentifier(), err)
	}
	// 从ChaincodeDeploymentSpec中创建一个用于安装链码的提案
	prop, _, err := utils.CreateInstallProposalFromCDS(msg, creator)
	if err != nil {
		return fmt.Errorf("Error creating proposal  %s: %s", chainFuncName, err)
	}

	// ...........
}

来看下CreateInstallProposalFromCDS()这个函数,在protos/utils/proputils.go的498行与536行:

// CreateInstallProposalFromCDS returns a install proposal given a serialized
// identity and a ChaincodeDeploymentSpec
func CreateInstallProposalFromCDS(ccpack proto.Message, creator []byte) (*peer.Proposal, string, error) {
	return createProposalFromCDS("", ccpack, creator, "install")
}

// createProposalFromCDS returns a deploy or upgrade proposal given a
// serialized identity and a ChaincodeDeploymentSpec
func createProposalFromCDS(chainID string, msg proto.Message, creator []byte, propType string, args ...[]byte) (*peer.Proposal, string, error) {
	// in the new mode, cds will be nil, "deploy" and "upgrade" are instantiates.
  // chainID为空,msg,creator由上层传入,propType为install,args为空
	var ccinp *peer.ChaincodeInput
	var b []byte
	var err error
	if msg != nil {
    // 将msg转化为[]byte
		b, err = proto.Marshal(msg)
		if err != nil {
			return nil, "", err
		}
	}
  // 根据 propType 选择不同的case,实例化走depoly,升级走upgrade
	switch propType {
	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":
    // 走这个case
		ccinp = &peer.ChaincodeInput{Args: [][]byte{[]byte(propType), b}}
	}

	// wrap the deployment in an invocation spec to lscc...
  // 安装链码是用系统链码lscc调用的,所以创建了一个链码调用标准数据结构,其中ChaincodeSpec字段,ChaincodeID的name字段复制的是lscc,它的参数就是刚刚case中赋值的ccinp,由此可以推断它最终执行的是lscc的install函数
	lsccSpec := &peer.ChaincodeInvocationSpec{
		ChaincodeSpec: &peer.ChaincodeSpec{
			Type:        peer.ChaincodeSpec_GOLANG,
			ChaincodeId: &peer.ChaincodeID{Name: "lscc"},
			Input:       ccinp,
		},
	}

	// ...and get the proposal for it
  // 根据ChaincodeInvocationSpec创建Proposal
	return CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, chainID, lsccSpec, creator)
}

下面来看看CreateProposalFromCIS()函数,在protos/utils/proputils.go464行

这里备注一下一些简称,方便源代码的阅读与理解。

CDS(Chaincode Deployment Spec),链码部署标准数据结构

CIS(Chaincode Invocation Spec),链码调用标准数据结构

// CreateProposalFromCIS returns a proposal given a serialized identity and a
// ChaincodeInvocationSpec
func CreateProposalFromCIS(typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, creator []byte) (*peer.Proposal, string, error) {
	return CreateChaincodeProposal(typ, chainID, cis, creator)
}

// CreateChaincodeProposal creates a proposal from given input.
// It returns the proposal and the transaction id associated to the proposal
func CreateChaincodeProposal(typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, creator []byte) (*peer.Proposal, string, error) {
	return CreateChaincodeProposalWithTransient(typ, chainID, cis, creator, nil)
}

// CreateChaincodeProposalWithTransient creates a proposal from given input
// It returns the proposal and the transaction id associated to the proposal
func CreateChaincodeProposalWithTransient(typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, creator []byte, transientMap map[string][]byte) (*peer.Proposal, string, error) {
  // 生成一个nonce,并根据nonce和creator计算出一个txid
	// generate a random nonce
	nonce, err := crypto.GetRandomNonce()
	if err != nil {
		return nil, "", err
	}

	// compute txid
	txid, err := ComputeTxID(nonce, creator)
	if err != nil {
		return nil, "", err
	}

	return CreateChaincodeProposalWithTxIDNonceAndTransient(txid, typ, chainID, cis, nonce, creator, transientMap)
}

// CreateChaincodeProposalWithTxIDNonceAndTransient creates a proposal from
// given input
func CreateChaincodeProposalWithTxIDNonceAndTransient(txid string, typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, nonce, creator []byte, transientMap map[string][]byte) (*peer.Proposal, string, error) {
  // txid是上个函数计算出来的,typ是最初指定的交易Header类型HeaderType_ENDORSER_TRANSACTION
  // chainID为空,cis是传进来的ChaincodeInvocationSpec对象,nonce是上一个函数传进来的
  // creator是最初的方法传进来,transientMap为nil
  
  // 构造一个ChaincodeHeaderExtension对象
	ccHdrExt := &peer.ChaincodeHeaderExtension{ChaincodeId: cis.ChaincodeSpec.ChaincodeId}
  // 序列化ccHdrExt
	ccHdrExtBytes, err := proto.Marshal(ccHdrExt)
	if err != nil {
		return nil, "", errors.Wrap(err, "error marshaling ChaincodeHeaderExtension")
	}
	
  // 序列化cis
	cisBytes, err := proto.Marshal(cis)
	if err != nil {
		return nil, "", errors.Wrap(err, "error marshaling ChaincodeInvocationSpec")
	}
	// 构造一个ChaincodeProposalPayload对象,链码提案payload,包含input,transientMap
	ccPropPayload := &peer.ChaincodeProposalPayload{Input: cisBytes, TransientMap: transientMap}
  // 序列化 ccPropPayload
	 ccPropPayloadBytes, err := proto.Marshal(ccPropPayload)
	if err != nil {
		return nil, "", errors.Wrap(err, "error marshaling ChaincodeProposalPayload")
	}

	// TODO: epoch is now set to zero. This must be changed once we
	// get a more appropriate mechanism to handle it in.
	var epoch uint64

	timestamp := util.CreateUtcTimestamp()
	
  // 构造Header结构体,包含ChannelHeader和SignatureHeader
	hdr := &common.Header{
		ChannelHeader: MarshalOrPanic(
			&common.ChannelHeader{
				Type:      int32(typ),
				TxId:      txid,
				Timestamp: timestamp,
				ChannelId: chainID,
				Extension: ccHdrExtBytes,
				Epoch:     epoch,
			},
		),
		SignatureHeader: MarshalOrPanic(
			&common.SignatureHeader{
				Nonce:   nonce,
				Creator: creator,
			},
		),
	}
	
  // 序列化header
	hdrBytes, err := proto.Marshal(hdr)
	if err != nil {
		return nil, "", err
	}
	
  // 构造Proposal提案结构体并返回
	prop := &peer.Proposal{
		Header:  hdrBytes,
		Payload: ccPropPayloadBytes,
	}
	return prop, txid, nil
}

返回到 install()函数中:

// 刚刚看到这里
prop, _, err := utils.CreateInstallProposalFromCDS(msg, creator)
	if err != nil {
		return fmt.Errorf("Error creating proposal  %s: %s", chainFuncName, err)
	}

var signedProp *pb.SignedProposal
// 对创建的提案进行签名,具体细节不展开了
	signedProp, err = utils.GetSignedProposal(prop, cf.Signer)
	if err != nil {
		return fmt.Errorf("Error creating signed proposal  %s: %s", chainFuncName, err)
	}

// install is currently only supported for one peer
// 安装链码只能一个一个安装,只在制定的peer节点安装,调用背书客户端的ProcessProposal方法,发送交易提案
	proposalResponse, err := cf.EndorserClients[0].ProcessProposal(context.Background(), signedProp)
	if err != nil {
		return fmt.Errorf("Error endorsing %s: %s", chainFuncName, err)
	}
	
// 接下来都是一些处理提案响应的操作,判断提案status等等
	if proposalResponse != nil {
		if proposalResponse.Response.Status != int32(pcommon.Status_SUCCESS) {
			return errors.Errorf("Bad response: %d - %s", proposalResponse.Response.Status, proposalResponse.Response.Message)
		}
		logger.Infof("Installed remotely %v", proposalResponse)
	} else {
		return errors.New("Error during install: received nil proposal response")
	}

	return nil
}

这里提一下EndorserClient.ProcessProposal()这个方法,这是一个很常用的方法,用于向背书节点发送交易提案,即 EndoserClientEndoserServer发送 GRPC 请求,由EndoserServer处理请求。EndoserServer在 peer 节点启动时启动,具体细节可以看protos/peer/peer.pb.goprotos/peer/peer.proto两个文件,我这里仅贴出部分代码,关于 peer 节点是如何处理交易提案的,这个之后单独会写一篇文章来介绍。

protos/peer/peer.pb.go

type EndorserClient interface {
	ProcessProposal(ctx context.Context, in *SignedProposal, opts ...grpc.CallOption) (*ProposalResponse, error)
}

type endorserClient struct {
	cc *grpc.ClientConn
}

func NewEndorserClient(cc *grpc.ClientConn) EndorserClient {
	return &endorserClient{cc}
}

// 最终执行的是这个ProcessProposal方法,调用GRPC客户端的invoke函数
func (c *endorserClient) ProcessProposal(ctx context.Context, in *SignedProposal, opts ...grpc.CallOption) (*ProposalResponse, error) {
	out := new(ProposalResponse)
	err := c.cc.Invoke(ctx, "/protos.Endorser/ProcessProposal", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

protos/peer/peer.proto

syntax = "proto3";

option java_package = "org.hyperledger.fabric.protos.peer";
option go_package = "github.com/hyperledger/fabric/protos/peer";

package protos;

import "peer/proposal.proto";
import "peer/proposal_response.proto";

message PeerID {
    string name = 1;
}

message PeerEndpoint {
    PeerID id = 1;
    string address = 2;
}

// 服务端包含ProcessProposal这个方法
service Endorser {
	rpc ProcessProposal(SignedProposal) returns (ProposalResponse) {}
}

链码安装源码总结

下面总结一下链码安装

  1. 用户执行安装链码的命令,以此命令为例:peer chaincode install -n mycc -v 1.0 -p github.com/chaincode/chaincode_example02/go/
  2. 初始化一个链码命令工厂,包含背书客户端,分发客户端,tls证书,签名者,广播客户端等成员信息
  3. 判断 ccpackfile 是否为空
    1. 如果为空,则从本地链码源代码文件读取
      1. 判断链码是否已经存在,如果存在直接返回错误信息,如果不存在接着往下走
      2. 验证用户给入的命令参数
      3. 构造ChaincodeInput对象,即命令中的-c选项,install命令一般没有
      4. 根据ChaincodeInput对象构造ChaincodeSpec对象,即链码标准数据结构
      5. 根据生成的ChaincodeSpec对象 spec 构造ChaincodeDeploymentSpec对象cds,主要有两个字段,一个字段是 spec,一个字段是从链码源代码中获取 payload 信息,可以理解为读取链码源代码,将其转化为 []byte 类型存储
      6. 最终返回生成的ChaincodeDeploymentSpec对象
    2. 如果不为空,则从给定的package中直接读取已定义的ChaincodeDeploymentSpec信息
  4. 执行链码的安装,获取签名者 creator
  5. 执行 CreateInstallProposalFromCDS()函数从 cds(即生成的 ChaincodeDeploymentSpec对象)中创建提案 Proposal
  6. Proposal进行签名,得到一个 SignedProposal对象signedProp
  7. signedProp通过EndorserClient.ProcessProposal方法发往指定的背书节点,即需要安装链码的节点,由背书节点的 EndorserServer进行处理

顺带说一句,链码安装具体最终执行的是 lscc 生命周期链码的 install 函数,关于 lscc 的解析,之后也会专门写一篇文章

猜你喜欢

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