peer chaincode install -n example02 -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02 -v anotherversion
,本篇将以此句命令为例子。安装的ACC是example02,存在于-p指定的路径下,版本是anotherversion。-l没有给,自然取默认的值,为go语言。以peer/chaincode/install.go的chaincodeInstall(...)
为起点(设定ccpackfile==""
),根据ACC-Install-DataConstuct.PNG,从左上角最原始的命令行数据开始,一路组装数据,至形成下面中路的SignedProposal,然后将SignedProposal通过cf.EndorserClient.ProcessProposal(...)
提交至Endorser服务端。这个冗长的过程,不再详述。只提以下几点:
GetDeploymentPayload()
打包的。该程序的复杂之处在于除了要打包example02源码,还要打包example02直接依赖的但go标准库未提供的第三方库,还要打包这些第三方库直接依赖但go标准库未提供的第三方库(即example02间接依赖但go标准库未提供第三方库)。这么做的目的就是让一个chaincode无论放到哪个容器里,不会因为缺少某个第三方库而编译失败。同时这个打包的过程也就要求我们在执行example02安装的时候,要实现把其依赖的第三方库事先放到GOPATH/src下。CreateChaincodeProposalWithTransient(...)
中计算出来的,是哈希(peer结点的MSPID+证书元数据+随机数nonce)的值,可以就把它当作一个唯一的字符串,不用太过深究。ProcessProposal(...)
处,接收到来自Endorser客户端发送的SignedProposal和一个之后一路都会用到的Context上下文ctxt。ProcessProposal(...)
中,从开始至var txsim ledger.TxSimulator
处,之上的代码全部是一边解压抽取SignedProposal中的数据,一边验证这些数据。至于抽取验证了哪些数据,根据代码对照图回溯,在此不做详述。var txsim ledger.TxSimulator
,var historyQueryExecutor ...
,一个是交易模拟工具,一个是历史查询执行工具。由于是Install,且chainID为空,这两个值在之后都一直为空。if chainID != ""
的分支也不会进入(当部署example02时,chainID不为空,则会进入此分支,根据chainID获取这两个工具,供之后部署使用)。ProcessProposal(...)
所做的主要的两件事就是:(1)e.simulateProposal(...)
,模拟执行申请。(2)e.endorseProposal(...)
,背书申请执行的结果。但是由于chainID为空,所以install命令不会执行此步(同样,部署时会用到),而是直接以ProposalResponse的形式返回(1)中执行的结果。下文将对(1)展开详述。e.simulateProposal(...)
的参数:ctx为上文所述的上下文ctxt(至此未有更新);chainID为空;txid为交易ID;signedProp是客户端发送来的原数据;prop是从signedProp中抽取出来的Proposal;hdrExt.ChaincodeId也是抽取出来的数据,只包含一个值为lscc的Name字段;txsim为空。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。e.callChaincode(...)
,执行CIS指定的动作。上文所述的另一个函数endorseProposal()
最后也是调用这个函数开始执行申请的任务的。也就是说,e.callChaincode(...)
能实现什么效果,做什么事情,完全是传入的参数决定的,这个函数可以算是实际开始执行申请的起点。在此罗列一下进入e.callChaincode(...)
的参数:ctx依旧为ctxt;chainID=空;version=1.0.0;txid为交易ID;signedProp/prop/cid/txsim不变;cis是第2步新抽取出的CIS。这些参数均在图中可以找到对应数据。callChaincode()
函数做了三件事:(1)cccid := ccprovider.NewCCContext(...)
,根据传入的参数,创建一个CCContext对象供执行申请所用。(2)chaincode.ExecuteChaincode(...)
,执行申请。(3)if cid.Name == "lscc" && len(cis.ChaincodeSpec.Input.Args) >= 3 && ...
,这一步只有部署,升级的交易才会进入此分支,还记得上文的txsim和概述中提到的-c么,这里的分支就是执行这些对象所承载的任务的,在此不做讨论。下文将对(2)展开详述。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。Execute(ctxt, cccid, spec)
仍依次执行theChaincodeSupport.Launch(...)
,theChaincodeSupport.Execute(...)
,但这里spec是第5步生成的CIS,因此cctyp的值为ChaincodeMessage_TRANSACTION,进而生成的供theChaincodeSupport.Execute(...)
(下文若非特指,凡提到的Execute函数均指此函数)使用的ccMsg是ChaincodeMessage_TRANSACTION类型的消息(对应图中的ChaincodeMessage)。依旧,分开讲解两个函数。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(...)
函数的参数:ctxt依旧位更新;cccid为图中的CCContext;ccMsg是图中的ChaincodeMessage;executetimeout为超时时间。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。sendExecuteMessage(...)
中,通过调用handler.triggerNextState(msg, true)
,ServerHandler将msg发给自身的handler.nextState
通道以触发ServerHandler的状态机进入下一个状态。processStream()
收到来自handler.nextState
通道的msg,先交给handler.HandleMessage(in)
处理,ServerHandler状态机无任何变化,然后handler.serialSendAsync(in, errc)
给lscc的ShimHandler发送msg。chatWithPeer()
收到msg,交由handler.handleMessage(in)
处理,触发beforeTransaction
事件函数,该事件函数主要调用同文件中的handleTransaction(...)
函数。handleTransaction(...)
函数主要做的就是根据ShimHandler收到的msg生成并初始化一个ChaincodeStub,对应图中的ChaincodeStub,然后handler.cc.Invoke(stub)
调用lscc的Invoke()
方法对example02进行安装。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:
的分支。case INSTALL:
分支中,首先,lscc.policyChecker.CheckPolicyNoChannel(...)
专门使用了检测未指定Channel的chaincode的函数来检查要安装的example02,这也侧面映证了上文生成签名申请包章节中对chainID的描述。接着,depSpec := args[1]
取出来的就是example02的CDS,不过此时的CDS仍是被Marshal过的。最后,lscc.executeInstall(stub, depSpec)
,调用lscc的函数,依据stub和example02的CDS,执行安装。executeInstall(...)
中,首先,ccpack,err := ccprovider.GetCCPackage(ccbytes)
,根据example02被Marshal过的CDS创建一个CDSPackage(core/common/ccprovider/cdspackage.go中定义),对应图中的CDSPackage。其次,简单的验证了example02的Name和Version。最后,调用ccpack.PutChaincodeToFS()
将example02源码写入文件系统。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的安装申请执行完毕。由此,开始一路返回。handleTransaction(...)
中,handler.cc.Invoke(stub)
返回,继续向下执行,将nextStateMsg赋值为ChaincodeMessage_COMPLETED类型的消息,并执行defer中的handler.triggerNextState(nextStateMsg, send)
将该消息发送给自己的状态机。ShimHandler只将ChaincodeMessage_COMPLETED消息发送给ServerHandler之后就再无其他动作或变化。Execute(...)
函数,然后等待结束,Execute(...)
函数成功返回。Execute(...)
函数结束之后,就此一路返回,一直返回到core/endorser/endorser.go中的callChaincode(...)
,chaincode.ExecuteChaincode(...)
执行完毕,对应到上文执行申请章节的第4步的(2),由于这一步的(3)install申请不会执行,因此callChaincode(...)
也就此结束。simulateProposal
,继续的代码中if txsim != nil
分支不会进入,因此也是直接返回至ProcessProposal()
。ProcessProposal()
,将进入if res != nil
分支,但无法进入if res.Status >= shim.ERROR
分支。因此继续向下走,进入if chainID == ""
分支对要返回给Endorser客户端的应答消息pResp赋值,最后返回pResp给Endorser客户端。chaincodeInstall(...)
,所调用的install(...)
中的cf.EndorserClient.ProcessProposal()
,即是Endorser客户端,收到服务端发来的消息,返回后install(...)
随之结束,进而chaincodeInstall(...)
结束。至此,整个example02的安装全部结束。