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 的解析,之后也会专门写一篇文章

  • 1
    点赞
  • 2
    收藏
  • 打赏
    打赏
  • 0
    评论
Hyperledger Fabric区块链分布式账本-学习参考资料合集,共37份。 一、Hyperledger开发资料 0. 票据背书详细介绍 1. 设置组织结构 2. 网络环境 3. 使用Fabric SDK Go 4.0. 链码开发_数据结构 4.1. 链码开发_票据相关请求处理 4.2. 链码开发_背书相关请求处理 4.3. 链码安装及实例化 5. 链码调用 6. 在网络应用程序中进行设置 GoWeb编程 二、Hyperledger技术资料 超级账本Hyperledger白皮书(中文版) 快速带你上手Hyperledger Fabric环境搭建+开发测试 性能基准测试和优化Hyperledger Fabric 区块链平台 An Introduction to Hyperledger Architecture of the Hyperledger Blockchain Fabric Cryptography and Protocols in Hyperledger Fabric FabNet- an Automatic Hyperledger Fabric Network Wizard Hyperledger 白皮书 Hyperledger 分析Fabric - 整体结构 Hyperledger Blockchain Performance Metrics Hyperledger Fabric - A Distributed Operating System for Permissioned Blockchains Hyperledger Fabric 实验指导 HyperLedger Fabric 在携程区块链平台中的应用实战 Hyperledger Fabric Access Control System for Internet of Things Layer in Blockchain-Based Applications Hyperledger Fabric Blockchain:Secure and Efficient Solution for Electronic Health Records Hyperledger fabric- towards scalable blockchain for business Hyperledger Fabric架构概览 hyperledger-fabricdocs Documentation-release-2.0 HyperLedgerFabric智能合约速成 Integrating Blockchain for Data Sharing and Collaboration Support in Scientific Ecosystem Platform Performance Analysis of Hyperledger Fabric Platforms Performance Benchmarking & Optimizing Hyperledger Fabric Blockchain Platform Performance Modeling & Analysis of Hyperledger Fabric (Permissioned Blockchain Network)-177页 Principle Foundations of Hyperledger Fabric Supporting Private Data on Hyperledger Fabric with Secure Multiparty Computation The privacy protection mechanism of Hyperledger Fabric and its application in supply chain finance

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:猿与汪的秘密 设计师:我叫白小胖 返回首页
评论

打赏作者

TLpigff

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值