fabric源码解析17——chaincode的元数据

fabric源码解析17——peer的chaincode之元数据

gossip初始化后,在start.go的serve函数中,执行了initSysCCs()peer.Initialize(...)系统链码(system chaincode)进行了初始化和部署。其实用户自定义的chaincode与系统链码只是人为上的角色的区分,在原理,实现,部署等方面并无二致。借此,展开对chaincode的讨论。之前的文章《fabric源码分析7》,《fabric源码分析8》都有涉及peer的chaincode,但是都不够深入,在此再次探讨,重点在于理清。由于chaincode是fabric项目中的中心概念之一,十分庞杂,因此分篇讨论。该篇文章旨在于认识和了解,主要讨论chaincode所涉及的各种承载chaincode数据的结构体和元工具,这些结构体和工具统称为chaincode的元数据。

在此和之后的文章,用户实现应用的chaincode(application chaincode)用ACC表示,系统chaincode(system chaincode)用SCC表示。对于chaincode的元数据描述过程中,可能会涉及到一些chaincode在具体操作时的结构上的概念,如chaincode的状态机,shim端等,这些暂不用在意,与之后的几篇同同讲chaincode的文章对看即可。同样,按照惯例,若读者找不到所述对象,文件或函数,请自行在chaincode所涉及的目录中利用grep命令等进行搜索。

chaincode涉及的主要目录

  • core/common
  • core/scc
  • core/chaincode
  • peer/chaincode
  • examples/chaincode/go
  • protos/peer/chaincode相关的原型

chaincode的元数据

  1. 命令行Flag - 在peer/chaincode/chaincode.go中,在init()中调用resetFlags()初始化了一众Flag,并把Flag的值存储在文件中定义的变量中,如chaincodeLang,chaincodeCtorJSON,chaincodePath,chaincodeName等,并在chaincode的每个子命令初始化时调用attachFlags添加子命令想要的Flag。
  2. policy - 较复杂的Flag之一,chaincode中用到策略之一。chaincode部署时需要签名,该策略指定了对chaincode签名的时候都要有谁的签名才能生效。策略原型字符串是如"OR(AND('A.member', 'B.member'), OR('C.admin', 'D.member'))"这样的嵌套结构OR(X,Y)表示X,Y二者取其一,AND(X,Y)表示X,Y二者都取,是组成嵌套结构的基础单位。X,Y是如A.memberC.admin这样的ORG.ROLE格式的组织成员,ORG表示一个组织MSP的ID,ROLE表示该MSP管理的成员角色(admin或memeber)。最终由common/cauthdsl/policyparser.go中的FromString()将策略原型字符串解析(主要使用了第三方库govaluate)存储在一个SignaturePolicyEnvelope(在protos/common/policies.pb.go中定义)对象中,该对象中的成员SignaturePolicy是一个递归结构,对应就可以存储嵌套结构的策略原型字符串:SignaturePolicy的存储SignaturePolicy_SignedBy和SignaturePolicy_NOutOf_两种类型的对象,SignaturePolicy_NOutOf_是递归结构的,有成员SignaturePolicy和成员N,成员SignaturePolicy就是用于嵌套下一层策略的,成员N表示当前嵌套层的策略是AND还是OR,1表示OR,2表示AND。最终policy会在调用protos/utils/commonutils.go中的proto.Marshal()之后存储在chaincode.go中的变量policyMarhsalled中。
  3. chaincodeCtorJSON - 较复杂的Flag之一,指定chaincode要运行的函数和函数的参数,原型为JSON格式的字符串,如{ "Function":"func", "Args":["param"] }。可以直接调用json.Unmarshal将原型字符串存入一个ChaincodeInput对象中,作为ChaincodeSpec的Input。
  4. chaincode说明书 - ChaincodeSpec,在protos/peer/chaincode.pb.go中定义,描述说明chaincode的结构体,简称为CS。成员有:Type指定chaincode的语言类型,当前版本的Fabric只支持go语言的chaincode;ChaincodeId指定了chaincode的路径,名称,版本;Input存储指定的chaincode的运行的函数和函数的参数。这些数据都来自于Flag(参照上述1-3点)。
  5. chaincode部署说明书 - ChaincodeDeploymentSpec,在protos/peer/chaincode.pb.go中定义,描述说明一个chaincode该如何部署,简称为CDS。成员有:ChaincodeSpec指定了第4点所说的chaincode说明书;EffectiveDate记录了chaincode何时有效的时间戳,即在chaincode开始运行时记录下时间点;CodePackage存储了一个.tar.gz压缩包的二进制数据,这个压缩包包含chaincode源码以及源码所依赖第三方库;ExecEnv标识chaincode的运行的环境,表明是运行在docker容器中还是操作系统之中。其中CodePackage经core/container/vm.go中的GetChaincodePackageBytes(),选择go平台的goPlatform对象后,最终调用core/chaincode/platforms/golang/platform.go中的GetDeploymentPayload,根据chaincode说明书中记录的chaincode信息,生成压缩包。这个过程的复杂之处主要在于收集chaincode源码所依赖的第三方库(这些依赖的第三方库应该放在GOPATH/src下),在搜集过程中,排除掉了GOROOT和fabric项目已经提供的库,也排除掉了chaincode源码目录路径中所有的vendor目录中的库,剩下的所依赖的第三方库都被重新映射到chaincode源码目录下的vendor目录。最终,将源码文件和所依赖的库都打进一个.tar.gz压缩包中返回。如chaincode源码的路径是GOPATH/src/hyperledger/fabric/examples/chaincode/go/chaincode_example02/,假设该chaincode源码依赖第三方库github.com/jmoiron/sqlx(放在GOPATH/src/下,实际上不依赖),则生成的压缩包中的路径为src/hyperledger/fabric/examples/chaincode/go/chaincode_example02/,chaincode_example02目录下包含chaincode所有源码和一个vendor目录,vendor目录中包含路径github.com/jmoiron/sqlx/,sqlx目录中包含sqlx库全部的文件。
  6. ccpackfile包文件 - 一种由chaincode命令的package或signpackage子命令生成的chaincode二进制部署包。前者是由CDS对象生成,后者是由Envelope对象(包含签名过的CDS)生成。将这两者形成的ccpackfile使用ioutil.ReadFile被读入一个buf中后,可以使用CDSPackage对象或SignedCDSPackage对象的InitFromBuffer先将buf中的数据转入对象,然后调用对象的GetPackageObject从对象中抽取部署数据将其尝试转化为CDS或Envelope供部署时使用。这里和下文,我们将CDS和用于部署的Envelope都称作部署数据
  7. CDSPackage/SignedCDSPackage对象 - 在core/common/ccprovider/下定义,也是一种存储chaincode部署说明书数据的对象,但同时也提供了一系列接口供各种情况下调用,所以更像是扮演了一种存储CDS数据的中间角色,以供把CDS数据转化为其他所需的格式。
  8. ChaincodeInvocationSpec - chaincode调用说明书,简称为CIS,主要存储了一份chaincode说明书,只不过这份说明书可能是关于某一个系统链码的说明书,Input中存储的函数变为install/upgrade等对ACC进行操作的函数,而用户的部署数据(CDS或Envelope)则变为该函数的参数。比如用户要安装一个自己的chaincode,生成的调用说明书中,会使用到一份系统链lscc的说明书,而对于lscc说明书中的输入Input来说,操作的函数为install,函数的参数为用户chaincode的部署数据。
  9. Proposal/SignedProposal - Proposal封装了chaincode的数据,如部署包等,作为一个申请消息,让结点签名后,会同签名数据一同放入SignedProposal,而SignedProposal就是chaincode可以通过grpc与结点进行通信的数据包结构,即ACC向结点发送执行交易的消息,都是以SignedProposal消息的形式发送出去的。
  10. CCContext - 字面意思就是chaincode context,即链码内容。也是描述chaincode自身信息的一种载体,记录了chaincode的一些关键信息,如成员ChainID指定了所代表的chaincode所在的链的ID,成员Name指定了所代表的chaincode的名字,成员Syscc指定了所代表的chaincode是否是SCC。
  11. transactionContext - 交易上下文,在交易中产生,以交易id为key记录在Handler的一个map中,随用随删。比如一个部署交易中,在Handler处理这个交易期间,会产生一个这样的交易上下文,存储关于这个交易的一些信息,供处理交易的主体使用。待该次部署交易完成,则会将其删除。
  12. Chaincode消息 - ChaincodeMessage,链对象的服务端和shim端进行消息交互的主要的消息载体,在protos/peer/chaincode_shim.pb.go中定义。成员Type指定了消息的类型,有ChaincodeMessage_REGISTER,ChaincodeMessage_INIT等。成员Txid指定了该消息所在的交易编号(每个chaincode执行的交易都会有一个编号,每个交易会使用到多个类型的ChaincodeMessage在chaincode的服务端和shim端进行交互)。成员Timestamp自然是时间戳。成员Payload,Proposal是消息承载的chaincode数据,如源码包,执行的参数等等(只要是[]byte格式的,那其承载的数据就自由的多),可能有,也可能是空。成员ChaincodeEvent是chaincode在Init或Invoke时给回的所要触发的事件,比如,chaincode部署的时候,部署完毕后,shim可能给回服务端一个Event让服务端去触发,做一些必要的其他事情,同样,可能有,也可能是空。
  13. CCPackage/SignedCDSPackage - 分别定义在core/common/ccprovider/下的cdspackage.go和sigcdspackage.go中,两者都实现ccprovider.go中定义的CCPackage接口,分别可以从一个Marshal过的CDS或Envelope抽取出其中所承载的关于ACC的数据来初始化自身,以方便SCC进行检查ACC的数据是否符合要求,最终也是以这种两类结构写入文件系统以保存ACC的。
  14. ChaincodeData - 也是承载ACC数据的,用于ACC在系统中部署时,最终向账本提交安装的数据形式。
  15. StartImageReq - 容器相关的数据结构,在core/container/controller.go中定义。包含了部署一个chaincode时启动一个容器所需要知道的如环境变量,网络ID,结点ID,部署包,建立函数,执行函数等等数据。相应的,有StopImageReq,DestroyImageReq两个结构体。
  16. ChaincodeStub - 相当重要的一个结构体,接口定义在core/chaincode/shim/interfaces.go,实现在chaincode.go中。但是这个可以自己实现的,参见core/chaincode/shim/mockstub.go和mockstub_test.go。这个结构体是链码执行InitInvoke两个接口时直接使用的数据,也即一个交易到底要做什么的信息都会封装到这个结构里。

chaincode的元工具

  1. Signer - 签名者,由peer/chaincode/common.go中的InitCmdFactory初始化,使用的是一个结点的本地MSP服务对象bccspmsp中的signer,即一个SigningIdentity,可参看《fabric源码分析12》。签名者的作用就是调用CreateInstallProposalFromCDSGetSignedProposal对chaincode的部署数据签名(CDS或Envelope),形成一个已签名的申请SignedProposal。
  2. EndorserClient - 背书客户端,由peer/chaincode/common.go中的InitCmdFactory初始化。在《fabric源码分析11》中所提及的Endorser服务,所述的即为背书的服务端,该服务端是会随着一个结点的start.go的serve函数运行起来的。背书客户端的作用是调用ProcessProposal将签名者生成的SignedProposal发送给背书服务端执行交易后背书,获得一个申请应答ProposalResponse。ACC的交易,都是通过它来发起的。
  3. BroadcastClient - 一个连接orderer服务的广播客户端。chaincode用其来发送交易的结果数据给orderer结点,供其进一步处理。
  4. ccprovider - chaincode提供者,接口在core/common/ccprovider/ccprovider.go中的ChaincodeProvider,唯一的实现是core/chaincode/ccproviderimpl.go中的ccProviderImpl(ccprovider.go中初始化的也是这个实例,参看ccproviderimpl.go的init())。这个工具相当于一个封装层,置于chaincode与各种结构的chaincode数据之间。比较明显的就是这个接口的方法中,很多参数都是interface{}类型。也即,各种不同目的各种形式的chaincode数据,要经过它,去往各个适合其该去的地方去做事情。
  5. ChaincodeSupport - chaincode支持者,是一个全局单例,在core/chaincode/chaincode_support.go中实现和定义。为整个chaincode的框架提供支持,更精确的说,是提供chaincode整个服务端的框架支持。相当于一个大杂烩,关于chaincode用到的,需要的,涉及到的,都可能在这里存储并引导执行chaincode的动作。但他也仅仅是这样,并不真正掺和chaincode的执行,也就是说,对于ChaincodeSupport来说,你chaincode需要的,我尽力给你,比如一个账本对象,但给你之后你怎么用你自己处理或账本对象自身如何处理交易,我就不管了,再比如,你要部署,我把你交给属于你的Handler之后我就坐等结果了,也不管。再比如,你需要何种类型的容器,我根据你的需要返回给你inproc容器或Docker容器对象,然后让容器对象自己跟你接触和交互,我也不管。
  6. container - 容器,即chaincode最终要寄居的地方。有两种,inproc容器,docker容器。
  7. Handler - 分为服务端的Handler和客户端的Handler,各自封装了一个FSM状态机,chaincode的交易就是由这两个Handler的状态机驱动的,相当于chaincode交易的控制器和发动机。
  8. txsimulator - 交易模拟器,模拟交易发生的事情,其实就是在内存(典型的就是map)中记录交易产生的数据。在一个chaincode交易进行后,会将数据由交易模拟器记录一份,然后再把数据交给orderer服务处理(最终提交至账本)。类似的还有historyQueryExecutor这类的查询工具。

前奏

下一篇正式地开讲chaincode,由于chaincode比较庞杂,若文章混乱,模糊,错误之处,还请见谅。未定位的函数,源码文件,请参照本文罗列的chaincode相关目录自行使用greplocate命令搜索,定位的函数或源码文件,均以这些目录为基础。

从上面就可以看出(上文罗列的只是些较关键重要的对象),承载chaincode数据的结构变化相当多,SCC还稍微好些,ACC确实相当复杂,而且某些字段会影响到逻辑判断,需要十分耐心。比较好的方法是尽量用具体的例子和数据填充,即实例化原则,这样有助于具象化,因此笔者在研究时自己画了数据组装图,分享在 http://pan.baidu.com/s/1bpu91uf ,密码s51j,根据这些数据组装的过程走,边看代码,不容易产生混乱,后文讲解过程中也会以依照这些图和图中的数据。图本身比较杂乱,不过在这种情况下也很难做到美观了,可以自行下载,文中就不再贴图。

你可能感兴趣的:(Fabric)