Fabric1.4源码解析:客户端安装链码
看了看客户端安装链码的部分,感觉还是比较简单的,所以在这里记录一下。
还是先给出安装链码所使用的命令好了,这里就使用官方的安装链码的一个例子:
#-n 指定mycc是由用户定义的链码名字,-v 指定1.0是链码的版本,-p ...是指定链码的路径
peer chaincode install -n mycc -v 1.0 -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02
整个流程的切入点依旧是fabric/peer/main.go
文件中,在main()
方法中第47行:
mainCmd.AddCommand(chaincode.Cmd(nil))
这里就包含了Peer节点关于操作链码的所有相关命令,点进行看一下,转到了peer/chaincode/chaincode.go
文件中第49行:
func Cmd(cf *ChaincodeCmdFactory) *cobra.Command {
#这里的命令应该是比较熟悉的
addFlags(chaincodeCmd)
chaincodeCmd.AddCommand(installCmd(cf)) #这一个就是执行链码的安装
chaincodeCmd.AddCommand(instantiateCmd(cf)) #链码的实例化
chaincodeCmd.AddCommand(invokeCmd(cf)) #链码的调用,具体调用什么方法要看链码是怎么写的
chaincodeCmd.AddCommand(packageCmd(cf, nil)) #链码的打包,暂时还没有使用过
chaincodeCmd.AddCommand(queryCmd(cf)) #对链码数据进行查询,这个只是向指定的Peer节点请求查询数据,不会生成交易最后打包区块的
chaincodeCmd.AddCommand(signpackageCmd(cf)) #对已打包的链码进行签名操作
chaincodeCmd.AddCommand(upgradeCmd(cf)) #更新链码,之前提到过 -v是指定链码的版本,如果需要对链码进行更新的话,使用这条命令,比较常用
chaincodeCmd.AddCommand(listCmd(cf)) #如果已指定通道的话,则查询已实例化的链码,否则查询当前Peer节点已安装的链码
return chaincodeCmd
}
我们这里只对链码的安装部分进行相关的说明,其他的以后再说了,点进去安装链码的那条命令,转到了peer/chaincode/install.go
文件中的第33行:
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 {
#定义链码文件
var ccpackfile string
if len(args) > 0 {
ccpackfile = args[0]
}
#这里我们主要关注的就是这行代码
return chaincodeInstall(cmd, ccpackfile, cf)
},
}
#这个就是可以在安装链码的命令中指定的相关参数
flagList := []string{
"lang",
"ctor",
"path",
"name",
"version",
"peerAddresses",
"tlsRootCertFiles",
"connectionProfile",
}
attachFlags(chaincodeInstallCmd, flagList)
return chaincodeInstallCmd
}
看一下chaincodeInstall()
方法:
func chaincodeInstall(cmd *cobra.Command, ccpackfile string, cf *ChaincodeCmdFactory) error {
cmd.SilenceUsage = true
var err error
if cf == nil {
#如果ChaincodeCmdFactory为空,则初始化一个,
cf, err = InitCmdFactory(cmd.Name(), true, false)
=================ChaincodeCmdFactory==================
#ChaincodeCmdFactory结构体
type ChaincodeCmdFactory struct {
EndorserClients []pb.EndorserClient #用于向背书节点发送消息
DeliverClients []api.PeerDeliverClient #用于与Order节点通信
Certificate tls.Certificate #TLS证书相关
Signer msp.SigningIdentity #用于消息的签名
BroadcastClient common.BroadcastClient #用于广播消息
}
=================ChaincodeCmdFactory==================
if err != nil {
return err
}
}
var ccpackmsg proto.Message
#这个地方有两种情况,链码可能是根据传入参数从本地链码源代码文件读取,也有可能是由其他节点签名打包完成发送过来的,这种方式还没有使用过
if ccpackfile == "" {
#这里是从本地链码源代码文件读取
if chaincodePath == common.UndefinedParamValue || chaincodeVersion == common.UndefinedParamValue || chaincodeName == common.UndefinedParamValue {
return fmt.Errorf("Must supply value for %s name, path and version parameters.", chainFuncName)
}
#看一下这个方法,生成ChaincodeDeploymentSpce
ccpackmsg, err = genChaincodeDeploymentSpec(cmd, chaincodeName, chaincodeVersion)
if err != nil {
return err
}
}
genChaincodeDeploymentSpec()
这个方法在99行:
func genChaincodeDeploymentSpec(cmd *cobra.Command, chaincodeName, chaincodeVersion string) (*pb.ChaincodeDeploymentSpec, error) {
#首先根据链码名称与链码版本查找当前链码是否已经安装过,如果安装过则返回链码已存在的错误
if existed, _ := ccprovider.ChaincodePackageExists(chaincodeName, chaincodeVersion); existed {
return nil, fmt.Errorf("chaincode %s:%s already exists", chaincodeName, chaincodeVersion)
}
#获取链码标准数据结构
spec, err := getChaincodeSpec(cmd)
if err != nil {
return nil, err
}
#获取链码部署标准数据结构
cds, err := getChaincodeDeploymentSpec(spec, true)
if err != nil {
return nil, fmt.Errorf("error getting chaincode code %s: %s", chaincodeName, err)
}
return cds, nil
}
看一下getChaincodeSpec()
方法,在peer/chaincode/common.go
文件中第69行:
func getChaincodeSpec(cmd *cobra.Command) (*pb.ChaincodeSpec, error) {
#首先定义了个链码标准数据结构
===========================ChaincodeSpec===========================
type ChaincodeSpec struct {
Type ChaincodeSpec_Type `protobuf:"varint,1,opt,name=type,proto3,enum=protos.ChaincodeSpec_Type" json:"type,omitempty"`
ChaincodeId *ChaincodeID `protobuf:"bytes,2,opt,name=chaincode_id,json=chaincodeId,proto3" json:"chaincode_id,omitempty"`
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===========================
spec := &pb.ChaincodeSpec{}
#检查由用户输入的命令中的参数信息,比如格式,是否有没有定义过的参数等等
if err := checkChaincodeCmdParams(cmd); err != nil {
cmd.SilenceUsage = false
return spec, err
}
#定义一个链码输入参数结构
input := &pb.ChaincodeInput{}
=======================ChaincodeInput=======================================
#该结构体主要保存用户链码中定义的功能以及参数等信息
type ChaincodeInput struct {
Args [][]byte `protobuf:"bytes,1,rep,name=args,proto3" json:"args,omitempty"`
Decorations map[string][]byte `protobuf:"bytes,2,rep,name=decorations,proto3" json:"decorations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
========================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
}
获得了链码标准数据结构之后,到了getChaincodeDeploymentSpec
这个方法,点进去看一下,在peer/chaincode/common.go
文件中第50行:
func getChaincodeDeploymentSpec(spec *pb.ChaincodeSpec, crtPkg bool) (*pb.ChaincodeDeploymentSpec, error) {
var codePackageBytes []byte
#首先判断是否当前Fabric网络处于开发模式,如果不是的话进入这里
if chaincode.IsDevMode() == false && crtPkg {
var err error
#然后对之前创建的链码标准数据结构进行验证,验证是否为空,链码类型路径等信息
if err = checkSpec(spec); err != nil {
return nil, err
}
#获取链码信息的有效载荷
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
}
==============================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"`
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=====================
返回到最初的方法chaincodeInstall()
,如果是本地安装的话,下一步就是链码的安装了,在此之前,我们看一下如果ccpackfile
不为空的那个分支:
func chaincodeInstall(cmd *cobra.Command, ccpackfile string, cf *ChaincodeCmdFactory) error {
...
if ccpackfile == "" {
...
ccpackmsg, err = genChaincodeDeploymentSpec(cmd, chaincodeName, chaincodeVersion)
...
} else {
var cds *pb.ChaincodeDeploymentSpec
#首先从ccpackfile中获取数据,这个方法就不看了,主要就是从文件中读取已定义的ChaincodeDeploymentSpec
ccpackmsg, cds, err = getPackageFromFile(ccpackfile)
if err != nil {
return err
}
#由于ccpackfile中已经定义完成了以上的数据结构,所以这里就直接获取了
cName := cds.ChaincodeSpec.ChaincodeId.Name
cVersion := cds.ChaincodeSpec.ChaincodeId.Version
...
if chaincodeName != "" && chaincodeName != cName {
return fmt.Errorf("chaincode name %s does not match name %s in package", chaincodeName, cName)
}
...
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.go
文件中第63行:
func install(msg proto.Message, cf *ChaincodeCmdFactory) error {
#和之前分析的文章一样,首先获取一个用于发起提案与签名的creator
creator, err := cf.Signer.Serialize()
if err != nil {
return fmt.Errorf("Error serializing identity for %s: %s", cf.Signer.GetIdentifier(), err)
}
#从ChaincodeDeploymentSpec中创建一个用于安装链码的Proposal
prop, _, err := utils.CreateInstallProposalFromCDS(msg, creator)
if err != nil {
return fmt.Errorf("Error creating proposal %s: %s", chainFuncName, err)
}
...
}
我们主要看一下CreateInstallProposalFromCDS()
方法,点进去一直到protos/utils/proutils.go
文件中第538行:
func createProposalFromCDS(chainID string, msg proto.Message, creator []byte, propType string, args ...[]byte) (*peer.Proposal, string, error) {
#传入的参数说明一下:chainID为空,msg,creator由之前的方法传入,propType为install,args为空
var ccinp *peer.ChaincodeInput
var b []byte
var err error
if msg != nil {
b, err = proto.Marshal(msg)
if err != nil {
return nil, "", err
}
}
switch propType {
#这里就判断propTypre类型,如果是deploy,或者是upgrade需要链码已经实例化完成
case "deploy":
fallthrough
#如果是deploy不跳出代码块,继续执行upgrade中的代码
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...)
#与安装链码相同,都需要定义一个ChaincodeInput结构体,该结构体保存链码的基本信息
ccinp = &peer.ChaincodeInput{Args: Args}
case "install":
ccinp = &peer.ChaincodeInput{Args: [][]byte{[]byte(propType), b}}
}
#安装链码需要使用到生命周期系统链码,所以这里定义了一个lsccSpce,注意这里的ChaincodeInvocationSpec在下面使用到
lsccSpec := &peer.ChaincodeInvocationSpec{
ChaincodeSpec: &peer.ChaincodeSpec{
Type: peer.ChaincodeSpec_GOLANG,
ChaincodeId: &peer.ChaincodeID{Name: "lscc"},
Input: ccinp,
},
}
#到这个方法了,根据ChaincodeInvocationSpec创建Proposal
return CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, chainID, lsccSpec, creator)
}
CreateProposalFromCIS()
这个方法在之前Fabric1.4源码解析:Peer节点加入通道这篇文章中讲过,具体可以看这里.
返回到install()
方法中,继续往下:
prop, _, err := utils.CreateInstallProposalFromCDS(msg, creator)
if err != nil {
return fmt.Errorf("Error creating proposal %s: %s", chainFuncName, err)
}
var signedProp *pb.SignedProposal
#,到这里了,对创建的Proposal进行签名,该方法也在上面那篇文章中说过,不再说明
signedProp, err = utils.GetSignedProposal(prop, cf.Signer)
if err != nil {
return fmt.Errorf("Error creating signed proposal %s: %s", chainFuncName, err)
}
#这个地方与之前分析的不同,这里安装链码只在指定的Peer节点,而不是所有Peer节点,依旧是调用了主要的方法ProcessProposal
proposalResponse, err := cf.EndorserClients[0].ProcessProposal(context.Background(), signedProp)
#到这里,Peer节点对提案处理完成之后,整个链码安装的过程就结束了
if err != nil {
return fmt.Errorf("Error endorsing %s: %s", chainFuncName, err)
}
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
ProcessProposal()
之前在另一篇文章Fabric1.4源码解析:Peer节点背书提案过程中都有说过,这里就不再说了,不过该方法非常非常重要,算是Fabric中使用频率很高的一个方法。
最后总结一下:
- 首先由用户执行安装链码的命令启动了整个流程。
- 判断链码是从本地链码源代码文件读取还是由接收到其他节点打包的链码进行安装
- 如果是从本地链码源代码文件读取,首先根据传入的链码名称和版本判断该链码是否已经存在。
- 如果存在的话直接返回错误信息。
- 如果不存在的话,首先对安装链码的命令中的参数进行验证。
- 定义一个
ChaincodeInput
数据结构,保存链码的功能以及参数信息 - 最后定义了个链码标准数据结构
- 判断是否处于开发环境下,定义了
ChaincodeDeploymentSpec
- 如果不是本地安装,则从文件中直接读取已定义的
ChaincodeDeploymentSpec
相关信息
- 如果是从本地链码源代码文件读取,首先根据传入的链码名称和版本判断该链码是否已经存在。
- 执行链码的安装,获取签名者
- 从
ChaincodeDeploymentSpec
中创建Proposal
- 对
Proposal
进行签名 - 将已签名的
Proposal
由Peer
节点进行处理 - 安装链码过程结束