fabric源码解析19——ACC的安装

fabric源码解析19——ACC的安装

概述

  • peer chaincode install命令执行安装命令,命令定义在peer/chaincode/install.go中,这也是安装的起点。另外需要注意的一点是,这个命令是在peer node start,peer channel create,peer channel join命令依次执行完毕之后所执行的,即执行install之时,peer结点的基本的模块(包括SCC)都已初始化完毕,channel也已经建立。
  • 根据实例化的原则,从install_test.go中提取了一句实际的install命令:peer chaincode install -n example02 -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02 -v anotherversion,本篇将以此句命令为例子。安装的ACC是example02,存在于-p指定的路径下,版本是anotherversion。-l没有给,自然取默认的值,为go语言。
  • install安装使用的chaincode数据有两种形式,一种是CDS,一种是chaincode package/signpackage命令形成的ccpackfile包。因为我们没有涉及过这两个命令,因此这里我们只以前一种CDS数据包为例,叙述安装过程。
  • ACC的安装涉及的图为ACC-Install-DataConstuct.PNG。下文中提及“图中”字眼,均指此图中。
  • install能够识别的flag有-l,-c,-p,-n,-v。其中-c不常用,其指定的是ACC所要执行的函数和函数的参数,一般在具体执行的时候,如查询或转账的时候再给定。
  • install最终所要做的事情,就是将example02的源码包放入docker容器的安装目录中

生成签名申请包

以peer/chaincode/install.go的chaincodeInstall(...)为起点(设定ccpackfile==""),根据ACC-Install-DataConstuct.PNG,从左上角最原始的命令行数据开始,一路组装数据,至形成下面中路的SignedProposal,然后将SignedProposal通过cf.EndorserClient.ProcessProposal(...)提交至Endorser服务端。这个冗长的过程,不再详述。只提以下几点:

  • chainID在SignedProposal中为空,即安装所要做的仅仅是把example02的压缩包放入容器的指定目录中而已,只有部署的时候才需要指定部署到哪条链上。
  • CDS的CodePackage是example02的代码压缩包,包括源码和依赖的第三方库。因为SCC的一切都在项目编译时编进peer中了,所以SCC的CDS的这个字段就是空的,而ACC需要安装源码,所以这个字段在正常的情况下肯定不是空的。这个压缩包最终是在core/chaincode/platforms/golang/platform.go中的GetDeploymentPayload()打包的。该程序的复杂之处在于除了要打包example02源码,还要打包example02直接依赖的但go标准库未提供的第三方库,还要打包这些第三方库直接依赖但go标准库未提供的第三方库(即example02间接依赖但go标准库未提供第三方库)。这么做的目的就是让一个chaincode无论放到哪个容器里,不会因为缺少某个第三方库而编译失败。同时这个打包的过程也就要求我们在执行example02安装的时候,要实现把其依赖的第三方库事先放到GOPATH/src下。
  • txid是在/protos/utils/proputils.go中的CreateChaincodeProposalWithTransient(...)中计算出来的,是哈希(peer结点的MSPID+证书元数据+随机数nonce)的值,可以就把它当作一个唯一的字符串,不用太过深究。

处理安装申请

  1. Endorser服务端在core/endorser/endorser.go中的ProcessProposal(...)处,接收到来自Endorser客户端发送的SignedProposal和一个之后一路都会用到的Context上下文ctxt
  2. ProcessProposal(...)中,从开始至var txsim ledger.TxSimulator处,之上的代码全部是一边解压抽取SignedProposal中的数据,一边验证这些数据。至于抽取验证了哪些数据,根据代码对照图回溯,在此不做详述。
  3. var txsim ledger.TxSimulatorvar historyQueryExecutor ...,一个是交易模拟工具,一个是历史查询执行工具。由于是Install,且chainID为空,这两个值在之后都一直为空。if chainID != ""的分支也不会进入(当部署example02时,chainID不为空,则会进入此分支,根据chainID获取这两个工具,供之后部署使用)。
  4. ProcessProposal(...)所做的主要的两件事就是:(1)e.simulateProposal(...),模拟执行申请。(2)e.endorseProposal(...),背书申请执行的结果。但是由于chainID为空,所以install命令不会执行此步(同样,部署时会用到),而是直接以ProposalResponse的形式返回(1)中执行的结果。下文将对(1)展开详述。
  5. 在此撇开一笔说一下chaincode的交易使用的结果。该结果定义在core/chaincode/shim/response.go中,目前定义的还相当的简单(也为以后升级留了空间),只定义了三个:OK,ERRORTHRESHOLD,ERROR。其中ERRORTHRESHOLD算是错误标志线,值为400,即小于它的值,表示成功或者还能勉强接受且无伤大雅的异常,而大于等于它的值,则表示是不能接受的错误。

执行申请

  1. 在此罗列一下传入e.simulateProposal(...)的参数:ctx为上文所述的上下文ctxt(至此未有更新);chainID为空;txid为交易ID;signedProp是客户端发送来的原数据;prop是从signedProp中抽取出来的Proposal;hdrExt.ChaincodeId也是抽取出来的数据,只包含一个值为lscc的Name字段;txsim为空。
  2. e.simulateProposal(...)中,前期又做了些简单的抽取和检查的事情:cis, err := putils.GetChaincodeInvocationSpec(prop)从prop中抽取出CIS。if err = e.disableJavaCCInst(cid, cis); err != nil通过判断example02的CDS.CS.Type来断定要安装的是否为Java源码,关于这点注释说的很清楚,当前版本不支持Java写的chaincode,但这部分在将来会被移除。if e.checkEsccAndVscc(prop); err != nil,escc和vscc对prop的检查,但是当前版本未作什么实际的检查,以后的版本可能会加入。if !syscc.IsSysCC(cid.Name){...}else{ version = util.GetSysCCVersion() },由于cid.Name就是lscc,因此只会进入else分支,得到的为lscc的版本值为1.0.0。
  3. 最后一步调用了e.callChaincode(...),执行CIS指定的动作。上文所述的另一个函数endorseProposal()最后也是调用这个函数开始执行申请的任务的。也就是说,e.callChaincode(...)能实现什么效果,做什么事情,完全是传入的参数决定的,这个函数可以算是实际开始执行申请的起点。在此罗列一下进入e.callChaincode(...)的参数:ctx依旧为ctxt;chainID=空;version=1.0.0;txid为交易ID;signedProp/prop/cid/txsim不变;cis是第2步新抽取出的CIS。这些参数均在图中可以找到对应数据。
  4. callChaincode()函数做了三件事:(1)cccid := ccprovider.NewCCContext(...),根据传入的参数,创建一个CCContext对象供执行申请所用。(2)chaincode.ExecuteChaincode(...),执行申请。(3)if cid.Name == "lscc" && len(cis.ChaincodeSpec.Input.Args) >= 3 && ...,这一步只有部署,升级的交易才会进入此分支,还记得上文的txsim和概述中提到的-c么,这里的分支就是执行这些对象所承载的任务的,在此不做讨论。下文将对(2)展开详述。
  5. ExecuteChaincode(...)函数,在core/chaincode/chaincodeexec.go中定义,这里可以对看《fabric源码分析18》的部署章节第5步所述的线路问题。spec, err = createCIS(cccid.Name, args)又根据传入的参数新建了一个CIS,不过仔细看一下createCIS就可以知道,其新生成的CIS和第2步从prop中抽取出的CIS在内容上是完全一致的,因此我们完全可以将这个spec看作是图中的那个CIS。接着就调用了Execute(ctxt, cccid, spec),对应到《fabric源码分析18》的部署章节的第6步,殊途同归,ACC也就此进入了类似于SCC部署所述的机制和道路来进行example02的安装。只不过,传入数据所承载的任务不同,执行的方向也会稍微有所不同。由于执行过程在《fabric源码分析18》详述过,因此下文将以粗线条叙述,重点在于提及不同之处。在此罗列一下进入Execute(...)的参数:ctxt依旧未有更新;cccid是第4步生成的,对应图中的CCContext;spec可以将其当作图中的CIS。
  6. Execute(ctxt, cccid, spec)仍依次执行theChaincodeSupport.Launch(...)theChaincodeSupport.Execute(...),但这里spec是第5步生成的CIS,因此cctyp的值为ChaincodeMessage_TRANSACTION,进而生成的供theChaincodeSupport.Execute(...)(下文若非特指,凡提到的Execute函数均指此函数)使用的ccMsg是ChaincodeMessage_TRANSACTION类型的消息(对应图中的ChaincodeMessage)。依旧,分开讲解两个函数。

Launch

  1. Launch(...)函数在ACC安装的情况下执行不了太久,canName := cccid.GetCanonicalName()等到的canName=lscc:1.0.0,此值会是程序进入if chrte, ok = chaincodeSupport.chaincodeHasBeenLaunched(canName); ok分支,进而进入if chrte.handler.isRunning()分支而返回。因为负责处理安装example02的lscc已经Launch过了,所以这样安排顺理成章。

Execute

  1. 继续,在此罗列一下传入Execute(...)函数的参数:ctxt依旧位更新;cccid为图中的CCContext;ccMsg是图中的ChaincodeMessage;executetimeout为超时时间。
  2. Execute(...)函数中,canName := cccid.GetCanonicalName()再次得到lscc:1.0.0,然后通过chrte, ok := chaincodeSupport.chaincodeHasBeenLaunched(canName),获取了lscc在《fabric源码分析18》中已部署过的ServerHandler,再chrte.handler.sendExecuteMessage(...)开始使用该ServerHandler以触发状态机进入运行,最后进入select-case等待。在此罗列一下传入sendExecuteMessage(...)的参数:ctxt依旧未更新;cccid.ChainID为空;msg为图中的ChaincodeMessage;cccid.SignedProposal为图中的SignedProposal;cccid.Proposal为图中的Proposal。
  3. sendExecuteMessage(...)中,通过调用handler.triggerNextState(msg, true),ServerHandler将msg发给自身的handler.nextState通道以触发ServerHandler的状态机进入下一个状态。
  4. ServerHandler的processStream()收到来自handler.nextState通道的msg,先交给handler.HandleMessage(in)处理,ServerHandler状态机无任何变化,然后handler.serialSendAsync(in, errc)给lscc的ShimHandler发送msg。
  5. lscc的ShimHandler的chatWithPeer()收到msg,交由handler.handleMessage(in)处理,触发beforeTransaction事件函数,该事件函数主要调用同文件中的handleTransaction(...)函数。
  6. handleTransaction(...)函数主要做的就是根据ShimHandler收到的msg生成并初始化一个ChaincodeStub,对应图中的ChaincodeStub,然后handler.cc.Invoke(stub)调用lscc的Invoke()方法对example02进行安装。
  7. 在lscc的Invoke()中,args := stub.GetArgs()获取到的是CIS.CS.Input.Args。这个数组的值来自于protos/utils/proputils.go中createProposalFromCDS()中的case "install":中的ccinp。因此Invoke()中,function := string(args[0])得到function的值是"install"switch function会进入case INSTALL:的分支。
  8. case INSTALL:分支中,首先,lscc.policyChecker.CheckPolicyNoChannel(...)专门使用了检测未指定Channel的chaincode的函数来检查要安装的example02,这也侧面映证了上文生成签名申请包章节中对chainID的描述。接着,depSpec := args[1]取出来的就是example02的CDS,不过此时的CDS仍是被Marshal过的。最后,lscc.executeInstall(stub, depSpec),调用lscc的函数,依据stub和example02的CDS,执行安装。
  9. executeInstall(...)中,首先,ccpack,err := ccprovider.GetCCPackage(ccbytes),根据example02被Marshal过的CDS创建一个CDSPackage(core/common/ccprovider/cdspackage.go中定义),对应图中的CDSPackage。其次,简单的验证了example02的Name和Version。最后,调用ccpack.PutChaincodeToFS()将example02源码写入文件系统。
  10. PutChaincodeToFS(...)中,一系列if检查之后,先path := fmt.Sprintf("%s/%s.%s", chaincodeInstallPath, ccname, ccversion)整合出要写入的路径,即在chaincodeInstallPath目录下放入名为example02.anotherversion的文件。然后os.Stat(path)先查看这个文件名是否可用。最后ioutil.WriteFile(path, ccpack.buf, 0644)将CDSPackage中成员buf写入path指定的地方。这个buf,从图中就可知道,就是example02源码压缩包。至此,example02的安装申请执行完毕。由此,开始一路返回。
  11. 一路返回至第6步ShimHandler的事件函数handleTransaction(...)中,handler.cc.Invoke(stub)返回,继续向下执行,将nextStateMsg赋值为ChaincodeMessage_COMPLETED类型的消息,并执行defer中的handler.triggerNextState(nextStateMsg, send)将该消息发送给自己的状态机。ShimHandler只将ChaincodeMessage_COMPLETED消息发送给ServerHandler之后就再无其他动作或变化。
  12. ServerHandler收到ChaincodeMessage_COMPLETED消息,通知仍处于等待之中的Execute(...)函数,然后等待结束,Execute(...)函数成功返回。

一路返回

  1. Execute(...)函数结束之后,就此一路返回,一直返回到core/endorser/endorser.go中的callChaincode(...)chaincode.ExecuteChaincode(...)执行完毕,对应到上文执行申请章节的第4步的(2),由于这一步的(3)install申请不会执行,因此callChaincode(...)也就此结束。
  2. 继续返回到simulateProposal,继续的代码中if txsim != nil分支不会进入,因此也是直接返回至ProcessProposal()
  3. 继续ProcessProposal(),将进入if res != nil分支,但无法进入if res.Status >= shim.ERROR分支。因此继续向下走,进入if chainID == ""分支对要返回给Endorser客户端的应答消息pResp赋值,最后返回pResp给Endorser客户端。
  4. example02安装申请的起点,peer/chaincode/install.go中的chaincodeInstall(...),所调用的install(...)中的cf.EndorserClient.ProcessProposal(),即是Endorser客户端,收到服务端发来的消息,返回后install(...)随之结束,进而chaincodeInstall(...)结束。至此,整个example02的安装全部结束

安装后的状态

  1. peer结点的chaincodeInstallPath目录下,会有一个名为example02.anotherversion的文件,该文件即为example02源码压缩包。

你可能感兴趣的:(Fabric)