Fabric 链码安装及实例化详解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/huchenguang/article/details/100532324

声明:本文的分析是基于fabric 1.4.0版本,其它版本实现类似,具体可以参考本文。

N久前,在一次误操作后,链码调用失败,返回信息如下:
Error: endorsement failure during invoke. response: status:500 message:"failed to execute transaction a8025cd90781b2d907749adcc558f358007b3ce0e796584928b18055369eaf95: [channel mychannel] failed to get chaincode container info for testcc:1.0: could not get chaincode code: chaincode fingerprint mismatch: data mismatch"
后来检查发现链码实现不一致,于是就有了一个概念:源码必须一致。而今天又有人贴出了官方文档,说同一个通道内同一个版本的链码,可以用不同的语言实现,只要语义一致即可。保持怀疑,决定梳理一下链码的安装及实例化流程。


链码安装

登录cli容器,设置环境变量:

export set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/fabricconfig/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051

执行peer命令行如下:

peer chaincode install -n ${ccName} -v ${ccVersion} -p ${ccPath} -l ${ccType}

其中:ccPath是相对$GOPATH/src的路径;ccType为链码的编程语言,默认为golang,且支持node,car,java,以下是不同语言对应的具体实现类:

var platformRegistry = platforms.NewRegistry(
	&golang.Platform{},
	&car.Platform{},
	&java.Platform{},
	&node.Platform{},
)

链码安装的过程发生了什么呢?首先要清楚一点,这个时候是有两个peer进程的,一个是在peer容器里,一个就是cli容器中执行链码安装命令启动的peer。
step 1. cli容器中的peer解析命令行参数,根据ccPath参数,打包链码源码,格式为tar.gz;

函数:func (goPlatform *Platform) GetDeploymentPayload(path string) ([]byte, error) {}
位置:core/chaincode/platforms/golang/platform.go: 248

step 2. cli容器中的peer,根据CORE_PEER_ADDRESS参数,找到需要安装该链码的peer,并调用系统链码lscc,参数为:"install"以及protobuf格式编码后protos.peer.ChaincodeDeploymentSpec对象。
step3. peer容器中的peer进程在收到链码安装调用后,进行相关校验,最终将protobuf格式编码后protos.peer.ChaincodeDeploymentSpec对象写到本地磁盘;

函数:func (lscc *LifeCycleSysCC) executeInstall(stub shim.ChaincodeStubInterface, ccbytes []byte) error {}
位置:core/scc/lscc/lscc.go: 547
函数:func (ccpack *CDSPackage) PutChaincodeToFS() error {}
位置:core/common/ccprovider/cdspackage.go: 265

step4. peer容器返回安装结果给cli容器的peer进程,链码安装结束。

结论:链码安装并未产生交易,因此不会影响通道内的其它peer节点,可以说链码安装是一个本地操作。

本地链码文件

peer的core.yaml中有一个配置项:peer.fileSystemPath,指定了peer保存数据的路径,如已安装链码,账本等数据。本地磁盘上的账本文件,其实是一个ChaincodeDeploymentSpec结构进行proto编码后的二进制文件。当前版本,这个文件可以直接拷贝到其它peer节点使用(拷贝后需要重启peer)。

链码实例化

同样是登录cli容器,设置环境变量,执行peer命令行,一般命令如下:

peer chaincode instantiate -o ${ordererAddr} -C ${channelName} -n ${ccName} -v ${ccVersion} -c ${InitArgs} -P ${EndorserPolicy}

重点:这里要求提供orderer的位置了,为什么呢?

链码实例化的过程又发生了什么呢?
step 1. cli容器中的peer同样是解析命令参数,调用peer容器中peer进程的系统链码lscc的实例化接口;
step 2. peer容器的peer进程在收到实例化请求后,根据链码名和版本从本地磁盘读取需要实例化的链码的相关信息,构造core.common.ccprovider.ChaincodeData对象,其中Data成员存储的是protobuf编码后的core.common.ccprovider.CDSData对象,存储tar.gz格式源码的hash,以及元数据(链码名+版本)的hash,最终把ChaincodeData对象进行protobuf编码后,以链码名为key存入世界状态;
step 3. peer容器调用新链码的Init函数,编译镜像,创建容器,完成调用;
step 4. peer容器返回结果给cli容器的peer进程;

step2-4的相关函数:
总入口函数:func (e *Endorser) callChaincode(txParams *ccprovider.TransactionParams, version string, input *pb.ChaincodeInput, cid *pb.ChaincodeID) (*pb.Response, *pb.ChaincodeEvent, error) {}
位置:core/endorser/endorser.go: 133
先调用lscc的Invoke,再调用实例化链码的Init
函数:func (lscc *LifeCycleSysCC) executeDeployOrUpgrade(...) (*ccprovider.ChaincodeData, error) {}
位置:core/scc/lscc.lscc.go: 610
Hash计算的函数:func (ccpack *CDSPackage) getCDSData(cds *pb.ChaincodeDeploymentSpec) ([]byte, []byte, *CDSData, error) {}
位置:core/common/ccprovider/cdspackage.go: 121
链码信息存入世界状态的相关函数:func (lscc *LifeCycleSysCC) executeDeploy(...) (*ccprovider.ChaincodeData, error) {}
位置:core/scc/lscc/lscc.go: 649
函数:func (lscc *LifeCycleSysCC) putChaincodeData(stub shim.ChaincodeStubInterface, cd *ccprovider.ChaincodeData) error {}
位置:core/scc/lscc/lscc.go: 206
执行实例化链码的Init:func (s *SupportImpl) ExecuteLegacyInit(txParams *ccprovider.TransactionParams, cid, name, version, txid string, signedProp *pb.SignedProposal, prop *pb.Proposal, cds *pb.ChaincodeDeploymentSpec) (*pb.Response, *pb.ChaincodeEvent, error) {}
位置:core/endorser/support.go: 125

step 5. cli容器的peer根据链码实例化返回的结果,生成交易,提交到orderer;
step 6. orderer生成区块,分发给通道内的所有peer节点;
step 7. peer节点验证交易之后,把读写集更新到世界状态,并把区块提交到本地账本。

哈,其实链码实例化,就是一笔特殊的交易,链码在某个节点实例化后,实例化的信息就被广播到通道内的所有节点了。如果其它节点再次实例化,自然就会冲突。
注意:通道内的其它peer节点,只是同步了世界状态,并不需要调用被实例化链码的Init,也就不会创建镜像,并启动链码容器了。

本文之前的问题,是如何发生的?

背书过程中,会尝试获取链码对应的handler对象,如果获取失败,意味着链码容器未启动(链码实例化ok的前提下),尝试编译链码镜像并创建容器。下面列出相关函数:

函数:func (cs *ChaincodeSupport) Invoke(txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, input *pb.ChaincodeInput) (*pb.ChaincodeMessage, error) {}
位置:core/chaincode/chaincode_support.go: 284
函数:func (cs *ChaincodeSupport) Launch(chainID, chaincodeName, chaincodeVersion string, qe ledger.QueryExecutor) (*Handler, error) {}
位置:core/chaincode/chaincode_support.go: 136

编译链码镜像并创建容器,会先校验本地磁盘上存储的链码信息是否和实例化的链码信息一致。从世界状态中读取正确的链码信息,从本地磁盘读取本地安装的链码信息,进行校验。校验内容:
1. 版本是否一致,名称是否一致
2. tar.gz格式源码的hash是否一致,元数据(链码名+版本)的hash是否一致
下面列出相关函数:

在Launch函数中被调用,函数:func (lscc *LifeCycleSysCC) ChaincodeContainerInfo(chaincodeName string, qe ledger.QueryExecutor) (*ccprovider.ChaincodeContainerInfo, error) {}
位置:core/scc/lscc/lscc.go: 165
函数:func (lscc *LifeCycleSysCC) getCCCode(ccname string, cdbytes []byte) (*pb.ChaincodeDeploymentSpec, []byte, error) {}
位置:core/scc/lscc/lscc.go: 395
从本地磁盘读取链码相关信息的函数:func (*CCInfoFSImpl) GetChaincodeFromPath(ccname string, ccversion string, path string) (CCPackage, error) {}
位置:core/common/ccprovider/ccprovider.go: 144
函数:func (ccpack *CDSPackage) ValidateCC(ccdata *ChaincodeData) error {}
位置:core/common/ccprovider/cdspackage.go: 176
函数:func (ccpack *SignedCDSPackage) ValidateCC(ccdata *ChaincodeData) error {}
位置:core/common/ccprovider/sigcdspackage.go: 217

注意:源码的hash计算有个关键点,因为链码安装是把源码压缩成tar.gz格式,且包含了代码路径的。相同源码,如果安装时路径不一致,这个源码hash也是不一致的。
tar.gz格式数据,解压之后,路径是src/${ccPath}/**,golang类型的链码是这样的,其它语言没有src这一级目录,但应该都是包含链码安装时的那个-p参数的内容。

回到本文最初的问题,同一个通道内的同一个版本的链码,能不能用不同的语言实现?答案显而易见的是不能!

文章创建于: 2019-09-04 09:19:23
展开阅读全文

没有更多推荐了,返回首页