fabric源码解析18——SCC的注册和部署

来源:互联网 发布:吉林大学网络教育统考 编辑:程序博客网 时间:2024/06/06 04:13

fabric源码解析18——SCC的安装和部署

SCC的注册和部署由于集成于peer内部,直接与用户交互之处甚少,因此也最简单。在此将注册和部署合为一篇。首先要理解的是,SCC和ACC本质是一样的,但ACC是处理用户的数据并根据用户指定的操作进行执行的,而SCC是处理ACC自身的,也就是说,对于SCC来说,ACC就是它要处理的数据。具体负责ACC的安装,部署等,就算是ACC要查个数据,也要经过SCC,让SCC去查,查出来后由SCC给ACC。之所以区分SCC和ACC,按照官方文档的解释,就是这样做可以使项目源码的弹性十足。先讲SCC是最合适的,一是SCC相对于ACC比较简单,二是SCC的机制也是ACC的机制,SCC代码走的线路,基本也是ACC代码要走的线路,熟悉了SCC,再入手ACC要顺利得多。很自然的,由于ACC要与开发者和使用者进行交互,因此要更繁杂一些。

概述

  • SCC的Register相当于ACC的Install,SCC的Deploy相当于ACC的instantiate。这里和之后讲ACC,统一用Install和Deploy来描述两个动作。只有这两个动作执行完毕,一个chaincode才算真正可以使用。
  • SCC启动占用的是inproc容器,可以当作就是内存里概念上的容器,在core/container/inproccontroller下实现,ACC启动占用的是docker容器,在core/container/dockercontroller下实现。
  • chaincode接口是定义在core/chaincode/shim/interfaces.go中的Chaincode,只有两个接口:Init(stub)和Invoke(stub),统一用同文件中的ChaincodeStubInterface接口实例作为唯一的参数。ChaincodeStubInterface接口唯一的实现是在同目录下的chaincode.go中的ChaincodeStub。
  • 每一个chaincode都会起两个Handler,每一个Handler都是一个以状态机(FSM)驱动的通信机器,在驱动的过程中执行具体的状态事件,完成一个chaincode所需做的事情。一个可以称为服务端Handler(相当于服务端)实现在core/chaincode/handler.go中,另一个可以称为shim(shim本身有垫片的意思,可以理解为其是项目源码与开发者之间贴合的垫片)端Handler(相当于客户端),实现在core/chaincode/shim/handler.go中。后续文中统一以ServerHandlerShimHandler区分。对状态机,即第三方库github.com/looplab/fsm的概念和操作不太熟悉的,建议复习学习一下(可参考《fabirc源码解析7》),这是你能看懂这部分源码的前提。若粗略的分一下的话,core/chaincode下的为chaincode服务端的代码,主要用于处理chaincode的请求;core/chaincode/shim下的为chaincode客户端的代码,用于定义供开发者使用的接口和客户端提交申请。
  • 一个chaincode实质还是一个结构体对象,该结构体实现了Chaincode接口。SCC如core/scc/lscc/lscc.go中的LifeCycleSysCC,ACC如examples/chaincode/go/chaincode_example01/chaincode_example01.go中的SimpleChaincode。
  • SCC部署所涉及的图为SCC-Deploy-DataConstruct.PNG。所有的SCC对象和原始数据定义在importsysccs.go中,下文仅以lscc这个SCC作为例子进行分析,lscc的chaincode对象LifeCycleSysCC具体实现在core/scc/lscc中。因chaincode自身已经比较复杂,因此与chaincode相关却不属chaincode主题的对象,文中将简单略过,待在之后相关主题文章中详述。文章开头部分可能讲的尽量详细,后边与前文重叠的部分将不再详述,如两个Handler之间的数据流转,第一次涉及到会比较详细的讲,之后就将粗笔述之。

安装

  1. 在peer/node/start.go的serve函数中,调用了registerChaincodeSupport,这其中执行了scc.RegisterSysCCs(),针对每一个SCC依次执行core/scc/sysccapi.go中实现的RegisterSysCC(syscc),进行注册(安装)。自然,也包括lscc。
  2. if !syscc.Enabled || !isWhitelisted(syscc),判断lscc是使能的且处于白名单之中,即lscc的Enabled要为true,且在配置文件core.yaml的chaincode.system项中,lscc要配置为enable(或yes/true)。如此,lscc的注册才能继续。
  3. inproccontroller.Register(lscc.Path,lscc.Chaincode),在core/container/inproccontroller定义,注册(即安装)lscc。上文说过,SCC启动占用的是inproc容器,这里Register所做的事情就是把lscc的Chaincode成员(lscc实际的chaincode对象LifeCycleSysCC)包装进一个inprocContainer容器,然后以lscc.Path为key放进typeRegistry这个map中,这就算注册(安装)完毕了。从这一点就可以体会,相较于ACC的安装启动一个docker容器,然后将ACC数据放入docker容器内指定的目录,inproc容器,只是一个叫法上的容器。typeRegistry这个map的作用也仅仅是存储注册的SCC了。

部署

  1. 在peer/node/start.go的serve函数中,调用了initSysCCs(),这其中执行了scc.DeploySysCCs(""),针对每一个SCC依次执行core/scc/sysccapi.go中实现的deploySysCC("",syscc),进行部署。这里需要留意第一个参数,是空”“,这个值是赋值给chainID的,chainID的值对之后对每一个chaincode的一系列操作有很大的影响,自然,也包括lscc。这里提前说一下,同chainID,之后会遇到channelID,channel,chain这样的字眼,其实指的都是同一个东西,都是的概念,一条链有一套属于自己的管理范围,工具,账本等,只有一个chaincode属于这条链,才能使用这条链的工具,账本,并受其管理,这个chaincode才能在链上起作用。SCC不属于任何一个链,它属于peer结点,也有权限,它只是处理ACC的请求,ACC在请求中提供chainID,SCC根据这个chainID从相应链上取出ACC所需要的工具或数据返回给ACC使用即可。
  2. if !syscc.Enabled || !isWhitelisted(syscc),如同安装一样,检测lscc是否使能且处于白名单之中。
  3. ctxt := context.Background()if chainID != "" {...},声明了一个Context,这也是一个之后一路都在使用的变量(对于Context不熟的需要自行学习了)ctxt。然后if判断chainID是否为空,若不是空的,if分支中所做的事情就是查看一下这条链上的账本和账本交易模拟器是否正常。换句话说,若chainID不为空,即指定了lscc部署到哪条链上,那肯定是之后部署lscc的时候会用到这条链的Ledger和Ledger的TxSimulator(否则不会为了不做无用功而提前检查)。这也是一点可说的,算是一个小技巧:在追溯比较冗长的代码过程中,前期函数中使用的if判断,尤其是函数中开头处的if判断,也有很好的间接提示作用,在后期的函数中可以做到相互映证,来判断自己追溯路线的是否正确。比如这里的if,若chainID不为空,后期追溯的过程中我们发现并没有使用Ledger和TxSimulator,那基本就可以判断你追溯错了。这一小技巧在后文还会提及。当然,这里我们还是遵循尽量实例化的原则,chainID为空,因此该if分支不会被执行。
  4. spec := &pb.ChaincodeSpec{...}chaincodeDeploymentSpec, err := buildSysCC(ctxt, spec)cccid := ccprov.GetCCContext(...),根据原始的lscc数据,一路封装最后得到lscc的部署包CDS,CCContext,并将这些数据连同ctxt传入下一步准备执行部署。
  5. ccprov.ExecuteWithErrorFilter(...)是chaincode执行交易的一个路线之一(另一个路线是core/chaincode/chaincodeexec.go中的ExecuteChaincode(),ACC走的是这条线),经ccprovider导航,执行调用到core/chaincode/exectransaction.go中的ExecuteWithErrorFilter(...),lscc的数据也跟着到此,进而直接调用通文件中的Execute(...),部署动作算是正式拉开序幕。需要说明的是,不单是lscc的部署,所有的交易,无论是ACC的安装部署,查询,Invoke等,最终都会到Execute(...)这里来开始,这里算是交易产生和最终结束的地方。同时也因此,要处理的情况更多,各种判断也会更多,而同样,遵循实例化原则,不会进入执行的if分支则不讨论。
  6. Execute(...)接收的还是在第4步中所封装的lscc数据,利用这些数据,主要执行了两步:theChaincodeSupport.Launch()theChaincodeSupport.Execute()。在Execute执行完后,会对交易返回的应答消息resp进行判断,若成功,将返回resp和resp中的“倒钩事件”(这个后文再提及)。下文将分别讲述Launch和Execute。

Launch

  1. 根据lscc封装的数据,cds不为空,cID = cds.ChaincodeSpec.ChaincodeIdcMsg = cds.ChaincodeSpec.InputcanName := cccid.GetCanonicalName(),从封装的数据中抽取出目前需要的数据,然后首先调用chaincodeSupport.chaincodeHasBeenLaunched(canName)进行if判断查看lscc是否已经被Launch过。这个if判断已经可以看出Launch这个函数最终要做的事情了:将lscc以canName(就是chaincode名字+’:’+版本号,即lscc:1.0.0,这算是chaincode内部认可的权威名字)为key,将lscc的对应的ServerHandler放入这个map中。这一点还是上文所说的技巧之一。当然,这里lscc是第一次部署,其数据不会存在于chaincodeMap中,所返回的chrte也自然为空。
  2. if cds == nil { ... },该分支不会进入,但是在ACC安装部署时会进入,这里不讨论。
  3. if (!....userRunsCC || cds.ExecEnv == ..) && (...) { ... },对照lscc封装的数据会发现会,该分支可以进入。分支中,判断if cds.CodePackage == nil会进入,但是if !(....userRunsCC || cds.ExecEnv == ....SYSTEM)不会进入。直接走到了builder := func() ...,定义了建立Docker容器的对象builder,并将Builder作为参数之一传入chaincodeSupport.launchAndWaitForRegister(...)真正开始Launch的工作。注意的是,这个builder对于SCC来说,在之后不会使用,对于ACC来说才会用到。这里罗列一下进入launchAndWaitForRegister的参数的值:context,cccid,cds仍是上文第3,4步封装的数据(参看组装图,仍是进入Execute时的数据),builder是此步产生的,但之后不会再用。
  4. launchAndWaitForRegister(...)从名字可以看出,是一个等待函数,即一直要等到Register完毕之后才会返回。这里的Register是指的lscc服务端的Handler的状态,即要等到lscc的ServerHandler处于REGISTER状态才会返回,对应该函数结尾处的select-case等待,顺理成章。在函数的开头,再次调用了chaincodeSupport.chaincodeHasBeenLaunched(canName)查看lscc是否已经被Launch过。然后开始准备数据,形成ipCtxt,vmtype,sir(包含CCID,Env,Args等数据),传入container.VMCProcess()(core/container/controller.go中定义),在其执行成功的情况下进入等待。这里需要注意几点要记清,后文要对应提及:(1)被包装进sir中的preLaunchFunc函数,负责将lscc的ServerHandler生成并放入chaincodeMap中。(2)**notfy是ServerHandler进入REGISTER状态的通知通道,在preLaunchFunc中被赋值,所赋的即是lscc的ServerHandler实例中的readyNotify通道。而select-case等待的就是这个notfy通道的通知,也即lscc的ServerHandler实例中的readyNotify通道的通知。(3)**ipcCtxt依然出自上文第3步创建的ctxt变量,一路跟着函数传递到这一步,这里又往里新加了ChaincodeSupport这个对象。
  5. VMCProcess()中,首先获取了lscc使用的inproc类型容器对象v,然后新起了一个goroutine,非阻塞的在锁保护下将v传入StartImageReq(即上一步生成传入到这里的sir)的do函数,然后使用select-case等待do函数的执行结束。
  6. do函数所做的,就是将StartImageReq自身已经初始化的每一个成员字段作为参数(第4步),依旧还有ctxt,传入lscc对应的inproc虚拟机对象v的Start()函数。这个Start()定义在core/container/inproccontroller/inproccontroller.go中,v的原型为InprocVM(Docker容器的虚拟机对象原型为DockerVM,在dockercontroller.go中)。
  7. Start()函数中,ipctemplate := typeRegistry[path]ipc, err := vm.getInstance(...),所做的是先从上文lscc注册(安装)进typeRegistry的map(对看上文安装一节第3步)中取出lscc的chaincode实例,作为新生成的inprocContainer容器的成员,然后将这个属于lscc的容器放入instRegistry这个map中。if ipc.running则检查lscc对应的容器ipc是否已经处于运行状态,若重复部署lscc且能运行到这里,那这里就会返回错误。ccSupport, ok := ctxt.Value(...)则是从ctxt中取出之前放进去的ChaincodeSupport(对看第4步(3)),接着执行了prelaunchFunc()(对看第4步(1),(2)),最后ipc.running = true,置lscc的inprocContainer为运行状态,至此,lscc就算是部署到位了,之后再对lscc进行chaincodeHasBeenLaunched(canName)检查返回都会是true。但是虽然lscc已经部署到了容器中,但还是有一些关于lscc的两个Handler的初始化工作要做,Start()函数最后的最后,新起的goroutine中去执行了ipc.launchInProc,即是为此。新起了这个goroutine之后,Start()返回,一路返回,结束了第5步中VMCProcess()的等待,使得第4步的launchAndWaitForRegister(...)继续向下执行进入等待。标记一下,执行到这里,Launch的旅途中只剩下这一个select-case等待处。
  8. launchInProc()函数层次分明,三段式,起了两个goroutine,一个去启动lscc的ShimHandler,一个去启动lscc的ServerHandler,最后使用select-case等待两个Handler初始化完成。从select-case等待结束的条件上看,两个goroutine任何一个结束(无论成功与否),都会导致函数结束返回。这里需要说明的是,由于SCC是运行在inproc容器中的,其实就是运行在内存中的,所以SCC的ServerHandler与ShimHandler之间直接使用的是go中的chan进行交互,即peerRcvCCSend和ccRcvPeerSend,并在core/container/inproccontroller/inprocstream.go中定义了inProcStream来模拟grpc流(只是模拟),实现了Send()Recv(),这两个函数使用上述两个chan为两端的Handler收发消息(ServerHandler用peerRcvCCSend收,用ccRcvPeerSend发,ShimHandler则相反)。在此罗列一下传入用于启动ShimHandler的函数shim.StartInProc()的参数:env,args来自于第4步包含在sir中的数据,ipc.chaincode则为第7步lscc所部署的自身的chaincode对象实例。
  9. 第一个goroutine,调用了shim.StartInProc(...),在/core/chaincode/shim/chaincode.go中定义。在这个函数中,for _, v := range env首先从env中获取lscc的权威名字赋于chaincodename(即lscc:1.0.0),stream := newInProcStream(recv, send)再利用第8步提到的两个chan生成模拟收发流,最后调用chatWithPeer(...)传入chaincodename,stream,chaincode(即lscc的chaincode实例),运行ShimHandler。在chatWithPeer(...)中:(1)handler := newChaincodeHandler(stream, cc)使用stream和chaincode创建了一个ShimHandler实例handler。(2)handler.serialSend(...)使用handler向ServerHandler发送一个ChaincodeMessage_REGISTER类型的ChaincodeMessage,即要服务端进入REGISTER状态的消息。注意,由于第8步是连起两个goroutine去运行两个Handler,且工作量接近,因此两个Handler运行起来的时间不会差太多,由于是通过chan发送的,即便此时ServerHandler没有运行起来,ShimHandler也会进行阻塞等待。(3)发送完毕之后,新启一个goroutine在for循环中持续接收来自ServerHandler和自身状态机的消息,然后chatWithPeer利用<-waitc等待该goroutine结束。在这个for循环中,接收三个路径来的消息:一个是stream接收来自ServerHandler的消息后转发给msgAvail的消息,一个是自身状态机handler.nextState的状态消息,一个是stream发送消息失败的errc的消息。除errc通道接收到失败消息直接返回外,另外两个路径来的消息都会交予ShimHandler处理,即handler.handleMessage(in)。倘若是handler.nextState来的状态消息,还要再向ServerHandler发送,即else if nsInfo != nil && nsInfo.sendToCC分支中的内容。每次接收处理完一次消息后,for循环将重置一些变量,再次进入等待接收三路消息的状态。
  10. 第二个goroutine,newInProcStream(...)先使用第8步提到的两个chan生成了模拟收发流,再使用这个流调用ccSupport.HandleChaincodeStream(...),进而调用HandleChaincodeStream(...)(core/chaincode/handler.go中定义)创建了一个ServerHandler实例(如果你够细心,或者跟着步骤对看至此仍没有糊涂,你就会产生一个问题,即在前文的第7步,所执行的prelaunchFunc()函数,已经针对lscc在chaincodeMap中注册了一个Handler(也是ServerHandler),那为何这里又创建一个ServerHandler实例呢?这点下文会提及)。随即,执行了handler.processStream()运行ServerHandlerprocessStream()函数的结构与第9步(3)所述ShimHandler的for循环结构十分类似,不再累述。
  11. 从步骤中抽出来闲述一笔。两端的Handler的初始状态都为createdstate(创建状态)。ServerHandler端的代码是这么安排的:接收和处理(回复)消息都在core/chaincode/handler.go中进行,接收用handler.processStream(),处理(回复)用同文件下的各个ServerHandler的各个方法函数。ShimHandler端的代码则是这么安排的:接收消息在core/chaincode/shim/chaincode.go中进行,处理(回复)消息则在同目录下的handler.go中进行。接收用chaincode.go中的chatWithPeer,处理(回复)用handler.go中ShimHandler的各个方法函数。
  12. 继续,第9步ShimHandler在启动前就向ServerHandler发送了一条ChaincodeMessage_REGISTER类型的ChaincodeMessage消息,ServerHandler在启动后,于processStream()中接收到该条消息,交由handler.HandleMessage(in)处理,一看这条消息是REGISTER类型,直接就触发ServerHandler的状态机状态变为establishedstate(建立状态),同时触发了beforeRegisterEvententerEstablishedState两个状态机的事件函数,且前者一定先执行完毕后才会执行后者。(1)beforeRegisterEvent主要做了两件事:第一,handler.chaincodeSupport.registerHandler(handler)注册lscc当前的这个ServerHandler。第二,handler.serialSend(...)向ShimHandler回复ChaincodeMessage_REGISTERED类型消息,注意这里加了ED,即表示注册过的意思。第二件事很好理解,而第一件事,就是第4步所提及的等待Register完毕所要等的事情,也对应第10步所留下的疑问。该函数所做的就是先将之前processStream()预注册到chaincodeMap中的Handler的readyNotify通道赋值给当前的ServerHandler的readyNotify通道,然后用当前的ServerHandler替换这个预注册的Handler,之后将ServerHandler的registered置为true,给txCtxs,txidMap分配内存,这就是Register所要做的事情。至于为什么用替换的方法,应该是为了在第4步留下预注册的Handler的readyNotify通道,使得第4步的launchAndWaitForRegister可以接收到ServerHandler中的通知(目前只能想到这个原因,至于是否还有更深层的原因,还请大神指点)。(2)enterEstablishedState主要做的通知的事情(此时经过(1)的执行,ServerHandlerd的readyNotify不可能为空),就是调用handler.notifyDuringStartup(true)向ServerHandler的readyNotify通道发送一个true值。由于(1)中所做的替换的事情,发送后,步骤7中的标记的还在select-case等待的launchAndWaitForRegister(...)会收到这个true值,立即结束等待并返回值为空的err,即表明ServerHandler已经Register成功,也即表明ServerHandler现在已经处于可以正常运行,收发处理消息的状态之中。
  13. ShimHandler收到第12步(1)发送来的ChaincodeMessage_REGISTERED类型消息,交由handler.handleMessage(in)处理,ShimHandler的状态机状态变为established,同时触发了beforeRegistered状态事件。beforeRegistered函数没有继续做任何事情。
  14. 第12步(2)中enterEstablishedState执行之后,第4步launchAndWaitForRegister(...)成功返回,Launch()将于第1,2,3,4步后继续执行,调用了chaincodeSupport.sendReady(...),目的在于由ServerHandler发起让自身和ShimHandler的状态机都进入ready状态,没有做多余的事情。在sendReady(...)中:(1)if chrte, ok = chaincodeSupport.chaincodeHasBeenLaunched(...),判断lscc于此时是否被Launch过,注意这里用的是!ok,即此时lscc必须已经在chaincodeMap中被Launch了,否则将错误返回。(2)notfy, err = chrte.handler.ready(...)让lscc的ServerHandler发起进入ready状态的动作。(3)select-case等待notfy的通知,即等待两端Handler都进入ready状态的通知。
  15. 在第14步(2)的handler.ready(...)函数中,txctx, funcErr := handler.createTxContext(...)创建了一个交易上下文对象transactionContext,将这个对象存储在lscc的ServerHandler的txCtxs这个map中,以txid(交易ID)为key。这里岔开说一下txid,之前步骤中一直没说。txid是部署之初生成的txid,部署也是一个交易,而每个交易都会有唯一的txid,整个交易的过程也都使用这个txid,以此来避免交易的重复和区分不同交易。这里所创造的以txid为key的transactionContext,会在ServerHandler进入ready状态之后从txCtxs中被删除。继续,生成了一个ChaincodeMessage_READY类型的ChaincodeMessage消息,然后调用handler.triggerNextStateSync(ccMsg),把消息发送到handler.nextStatehandler.ready(...)立即将txid对应的transactionContext的responseNotifier通道返回,赋值给第14步(2)中的notfy,并进入(3)中的等待。
  16. processStream()handler.nextState中接收到ChaincodeMessage_READY类型的消息,做了两件事:(1)交由handler.HandleMessage(in)处理,这个lscc的ServerHandler一看消息类型,状态机状态立刻由establishedstate变为readystate,同时触发enterReadyState状态事件函数,该事件函数只是通过notify(msg),将消息转发给取出的之前第15步存放在txCtxs中txid对应的transactionContext的responseNotifier通道(这个通道经过第15步已经赋值给了第14步(2)中的notfy),而现在第14步(3)中等待的notfy收到这条消息后,结束等待,然后调用handler.deleteTxContext(cccid.TxID)将txid对应的transactionContext从txCtxs中删除后,第14步开始的chaincodeSupport.sendReady(...)结束返回,继而Launch()结束。至此,lscc的ServerHandler终于全部Launch结束。(2)由于handler.nextState路径接收到的消息,因此最后的if nsInfo != nil && nsInfo.sendToCC分支可以进入,也就可以将ChaincodeMessage_READY消息异步发给了ShimHandler。
  17. ShimHandler的chatWithPeer()收到来自ServerHandler的ChaincodeMessage_READY消息,交由handler.handleMessage(in)处理,ShimHandler的状态机简单的将状态从established变为ready后,不会触发其他动作。至此,ShimHandler端的工作在Launch阶段也彻底结束。
  18. 题外话:步骤的线条已经尽量的细了,不过还是有很多省略之处,这还仅仅是开头的一个Launch。经过这么一遭,各位可能对chaincode的交易的整个过程和冗杂程度稍有了了解,ACC的部署过程只会比这个更复杂。步骤都是在函数间跳来跳去,比较关键:第一,还是要清楚函数传入的数据是什么,函数返回的数据是什么。第二,熟悉一个消息在两个Handler之间是怎么流转和触发状态事件的。

Execute

将目光重新定位到core/chaincode/exectransaction.go中的Execute(...)函数,重新查看进入Execute(...)的数据图SCC-Deploy-DataConstruct.PNG。在执行完毕theChaincodeSupport.Launch(...)后,继续。ccMsg, err = createCCMessage(...)根据Launch返回的CDS.CS.ChaincodeInput(实际为空)和之前的cctyp生成一个ChaincodeMessage_INIT类型的ChaincodeMessage传入theChaincodeSupport.Execute(...)(下文若非特指,凡提到的Execute函数均指此函数)。在此罗列一下进入Execute的参数:ctxt和cccid仍是lscc部署之初生成的,ccMsg是当前生成的,executetimeout是ChaincodeSupport设置的超时时间。

  1. if chrte, ok := chaincodeSupport.chaincodeHasBeenLaunched(canName),再次检查lscc:1.0.0是否被Launch过,同时若已被Launch也返回了lscc对应的ServerHandler。到这一步,lscc:1.0.0必定已被Launch。
  2. notfy, err = chrte.handler.sendExecuteMessage(...),利用lscc的ServerHandler,目的在于运行ChainMessage中所携带的数据指向的动作,并返回通知通道给notfy。当顺利返回后,Execute(...)函数将进入常规的select-case等待(由此可知sendExecuteMessage中的具体的任务依旧会是异步执行)。
  3. sendExecuteMessage函数中,handler.createTxContext(...),依然用txid为key创建了一个交易上下文对象transactionContext(这个对象会在Execute结束前调用chrte.handler.deleteTxContext(msg.Txid)被删除),然后调用handler.triggerNextState(msg, true)将ChaincodeMessage_INIT类型的消息发送到handler.nextState通道,然后将交易上下文对象的responseNotifier通道返回给Execute函数的notfy,并让Execute函数进入等待。
  4. ServerHandler对于handler.nextState通道来的ChaincodeMessage_INIT类型消息不会触发任何状态的改变和事件函数。只会把该消息原封不动的发给ShimHandler。ShimHandler收到此消息后,将触发beforeInit事件函数。beforeInit事件函数中实际执行任务的又是handleInit(msg)函数。
  5. handleInit(msg)函数也是一个比较典型的处理流程(handleTransaction函数也是这种流程,ACC中会讲到)。函数整体只新起一个goroutine,在goroutine中,在defer中执行最后要发送的消息nextStateMsg(可能是正常的消息,可能是错误消息),然后定义了一个errFun函数,一旦检测有错,则返回一个ChaincodeMessage_ERROR类型的ChaincodeMessage消息(即错误消息)给nextStateMsg,随即return,也就触发了defer。若一路都没有发生错误,则将一个ChaincodeMessage_COMPLETED类型的ChaincodeMessage赋值给nextStateMsg,函数结束,也会触发defer。handleInit(msg)函数主要做了三件事:(1)stub := new(ChaincodeStub)stub.init(...)组装ChaincodeStub(对于SCC来说,这个数据没用,但ACC会用到)。(2)handler.cc.Init(stub),调用lscc的chaincode实例的Init接口初始化这个chaincode,也即ChaincodeMessage_INIT类型的消息就是指示lscc做这个动作的。**(3)**defer发送ChaincodeMessage_COMPLETED类型消息,当然这是在之前的步骤都成功的情况下。注意这里返回的ChaincodeMessage中给ChaincodeEvent成员赋值为stub.chaincodeEvent,这就是部署之初所提到的“倒钩事件”,这个“倒钩事件”会随着ChaincodeMessage一路返回到peer结点最核心的地方。但是目前SCC和ACC都没有明显的使用这一点,也算是留下的一个可升级的地方(看大神的代码就是精妙啊)。
  6. 第5步(2)处调用的是lscc的chaincode实例的Init接口,定义在core/scc/lscc/lscc.go中,所做的事情相当简单(ACC会稍微麻烦点儿),就是给lscc的两个成员赋值,一个是系统链提供者实例,一个是策略检查器。然后就返回成功标识shim.Success(nil)
  7. 第6步执行完毕后,第5步的handleInit(msg)也会随之结束触发defer而再向ServerHandler发送ChaincodeMessage_COMPLETED类型消息,ServerHandler接收到此消息后状态机也会无动于衷,只是在handler.HandleMessage(in)中会通过handler.notify(msg)将消息再转发给txid对应的transactionContext的responseNotifier通道。对看第2,3步,由此,第2步Execute(...)函数的等待结束,将收到的ChaincodeMessage_COMPLETED类型消息resp返回。

将目光重新定位到core/chaincode/exectransaction.go中的Execute(...)函数,当theChaincodeSupport.Execute(...)执行完毕返回ChaincodeMessage_COMPLETED消息,经过一些列的if判断,会进入if resp.Type == pb.ChaincodeMessage_COMPLETED分支并成功返回。至此Execute(...)函数执行完毕,转回同文件中的ExecuteWithErrorFilter(...),亦是成功返回。再一路回溯,一直回到最初的core/scc/sysccapi.go中的deploySysCC(...),也是就此成功返回。就此,整个lscc的部署过程结束

部署后的状态

  1. theChaincodeSupport中的runningChaincodes.chaincodeMap中存在这以lscc:1.0.0为key的lscc的ServerHandler实例。
  2. lscc的ServerHandler和ShimHandler均处于ready状态,且接收发送消息的goroutine都在运行当中。
  3. 其他的SCC均与lscc状态相同。

遗留问题

  • 版本数据问题:关于版本数据问题,笔者还未搞清楚,但无论对于SCC还是ACC来说,这都是个比较关键的数据,交易的一路都在使用。SCC所用到的版本数据是在deploySysCC中组装CCContext时,调用version := util.GetSysCCVersion()得到的,这个版本数据来自于common/metadata/metadata.go中的Version变量。然而这个变量整个项目中没有找到给其赋值的地方,按照注释的说法是来自于fabric的Makefile文件中的GO_LDFLAGS,查看Makefile文件确有版本号相关的数据,如BASE_VERSION = 1.0.0。但是笔者对Makefile不算精通,不清楚如何在编译项目时是如何将Makefile文件中的版本数据赋值给metadata.go中的Version变量的。还请精通Makefile文件的大神若也在研究fabric,予以指点。
原创粉丝点击