前言
一份Peer节点启动的INFO级别日志如下,可以发现:
- 先注册了scc目录下的lscc, cscc, qscc,未注册chaincode目录下的lifecycle
- 然后又依次部署了上述scc。
本文的目的就是梳理出,系统链码的部署流程,这是peer节点提供背书、链码管理、配置、查询等功能的基础。
1 | 2019-09-09 07:52:09.409 UTC [gossip.gossip] start -> INFO 013 Gossip instance peer1.org1.example.com:8051 started |
宏观流程
提醒,本文使用SCC代指系统链码,使用scc代指core.scc模块。
在介绍源码之前,先给出总体流程,以便看源码的时候不会迷失。
部署SCC会涉及到4个模块:
- peer.node,它是peer的主程序,可以调用core.scc进行注册和部署SCC
- core.scc,它包含了lscc、qscc、cscc这3个scc,以及SCC的注册和部署
- core.chaincode,它是链码管理,普通链码和SCC都会走该模块,去部署和调用链码,和链码容器交互,并且它还提供了1个链码容器的工具shim
- core.container,它是实现链码容器,有2种链码容器,SCC使用的InprocVM,和普通链码使用的DockerVM
注册和部署的简要流程如下:
- peer运行启动程序
- 注册scc
- peer.node创建好lscc、cscc、qscc等scc实例,以及从配置文件读取的scc
- peer.node调用core.scc依次注册每一个scc实例
- core.scc调用core.container把scc实例信息注册到container
- 部署scc
- peer.node调用core.scc依次部署每一个注册的scc
- core.scc部署scc的流程复用普通链码部署流程,调用core.chaincode
- core.chaincode执行启动链码容器,scc也有链码容器是Inproc类型,不是Docker类型
- core.chaincode会调用core.container建立scc的Inproc容器实例
- core.container调用core.chaincode.shim启动容器内的程序,并负责和peer通信
- 启动完成后,core.chaincode向容器发送Init消息,让容器初始化,容器初始化完成会发送响应消息给core.chaincode,core.chaincode部署scc完成
总流程
列出源码的过程,会省略大量不相关代码,用
...
代替。
peer启动过程中,会调用node.serve
,其中包含了为系统链码注册SCC和部署SCC。之后,还会为应用通道部署SCC,说明每个通道有各自的SCC,这里省略掉这部分。
1 | func serve(args []string) error { |
注册SCC
注册SCC的流程:
peer.node -> core.scc -> core.container
peer.node
1 | // startChaincodeServer will finish chaincode related initialization, including: |
core.scc
注册某1个系统合约。
1 | // Provider implements sysccprovider.SystemChaincodeProvider |
core.container
1 | //Register registers system chaincode with given path. The deploy should be called to initialize |
部署SCC
部署SCC的流程:
peer.node -> core.scc -> core.chaincode -> core.container
peer.node
1 | func serve(args []string) error { |
core.scc
DeploySysCCs
会为chainID对应的channel,部署注册过程中收集的每一个SCC,它们在p.SysCCs
中。
部署链码实际是一笔交易,为了复用普通链码的部署流程,core.scc使用deploySysCC
封装部署链码需要的参数,链码是实际部署,走core.chaincode流程。
1 | //DeploySysCCs is the hook for system chaincodes where system chaincodes are registered with the fabric |
core.chaincode
CCProviderImpl
实现了ChaincodeProvider
接口,可以用来部署链码,ExecuteLegacyInit
会执行2项:
- 启动链码容器
- 执行链码Init函数,链码容器启动后,peer和链码容器通过消息通信,
ChaincodeMessage_INIT
是执行链码容器的Init函数
1 | // ExecuteLegacyInit executes a chaincode which is not in the LSCC table |
LaunchInit
是启动容器的一层检查,实际启动由Launcher.Launch
完成。启动链码容器是异步的,会创建单独的goroutine去执行。
core.chaincode使用Runtime
接口操控链码容器的启停。
1 | // LaunchInit bypasses getting the chaincode spec from the LSCC table |
ContainerRuntime
是core.chaincode封装出来和core.container交互的,在这里它会创建启动链码请求,交给container。
1 | // Start launches chaincode in a runtime environment. |
core.container
VMController
实现了Processor,它会按指定的类型建立虚拟机,明明就是容器,为啥内部又叫VM,VM有2种:
- InprocVM,意思是运行在单独进程中的虚拟机,但不是指操作系统的进程,而是指一个隔离的环境,SCC是这类。
- DockerVM,指利用Docker启动的容器,普通链码就是这类。
类型是存在ccci.ContainerType
中的,ccci
包含了部署链码所需要的信息,这个信息在core.chaincode很早就获取到了,可以往前翻。
Process
就是创建VM,然后利用VM处理请求的过程。
1 | // 根据请求对VM进行某种操作 |
虚拟机创建
1 | // 利用指定类型的vm provider创建vm |
创建VM需要使用NewVMController
,回过去找它的创建地方。
在注册SCC的过程中,调用registerChaincodeSupport
创建了chaincodeSupport
,其中一个字段为创建NewVMController
,就包含了2类Vm provider:
- ipRegistry,SCC的
- dockerProvider,普通链码的
1 | func registerChaincodeSupport( |
VM处理操作虚拟机的请求
core.container的请求,都实现了VMCReq
接口,StartContainerReq、StopContainerReq、WaitContainerReq是实现VMCReq的3类请求。
启动实际是启动虚拟机接口,处理请求。
1 | //VMCReq - all requests should implement this interface. |
DockerVM和InprocVM都实现了VM接口,本文只关注InprocVM类型,即SCC的。
InprocVM会得到一个容器实例ipc,用它来运行SCC。
1 | //Start starts a previously registered system codechain |
inprocContainer
开启2个goroutine:
- 第一个调用
shimStartInProc
,即利用core.chaincode.shim启动InProc类型的容器。 - 第二个调用
HandleChaincodeStream
,处理peer和Inproc容器间的通信数据,此处的stream是peer端的。
这里可以看到创建了2个通道peerRcvCCSend
和ccRcvPeerSend
,它们表明了peer和scc的链码容器是通过通道直接通信的。peer和docker链码容器之间是走gRPC通信的,这个到普通链码的时候再介绍。
1 | // 从进程启动链码 |
利用shim启动Inproc链码容器中的程序
shim是chaincode提供给容器,运行链码的工具,它运行在容器里。
利用shim启动InprocVM使用的函数是StartInProc
,提取一些运行链码需要的数据,比如又一个stream,此处的stream是容器端的。
1 | // 启动SCC的入口 |
chatWithPeer
是通用的,普通的链码也调用这个程序。它创建了一个handler,用来处理消息(发送和接收),以及操作(调用)链码。
这个过程,它会向peer发送REGISTER消息,和peer先“握手”,也会从peer读消息,消息的处理函数就是里面的for循环,这样链码容器就运行起来了。
1 | // 通用,SCC和CC都使用这个函数 |
具体的消息处理函数,先跳过,回过头来,关注scc容器和peer的通信。
SCC和Peer的通信通道
链码容器和Peer之间使用Stream进行通信,Stream有2种实现:
- 使用channel封装的Stream
- gRPC的Stream
链码容器和Peer通信的接口是:
1 | // PeerChaincodeStream interface for stream between Peer and chaincode instance. |
普通链码使用gRPC:
1 | type chaincodeSupportRegisterClient struct { |
系统链码直接使用通道通信,发送和接收消息都在下面了:
1 | // peer和chaincode之间通信的通道 |
Peer和链码容器的交互,完成链码容器启动
部署链码需要Peer和链码容器交互,不然Peer怎么知道链码容器已经启动。以下是一份peer的DEBUG日志,在下面标注了启动容器和链码Init过程中的消息:
1 | 2019-09-09 07:52:09.915 UTC [chaincode] LaunchConfig -> DEBU 098 launchConfig: executable:"chaincode",Args:[chaincode,-peer.address=peer0.org1.example.com:7052],Envs:[CORE_CHAINCODE_LOGGING_LEVEL=info,CORE_CHAINCODE_LOGGING_SHIM=warning,CORE_CHAINCODE_LOGGING_FORMAT=%{color}%{time:2006-01-02 15:04:05.000 MST} [%{module}] %{shortfunc} -> %{level:.4s} %{id:03x}%{color:reset} %{message},CORE_CHAINCODE_ID_NAME=lscc:1.4.3,CORE_PEER_TLS_ENABLED=true,CORE_TLS_CLIENT_KEY_PATH=/etc/hyperledger/fabric/client.key,CORE_TLS_CLIENT_CERT_PATH=/etc/hyperledger/fabric/client.crt,CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/peer.crt],Files:[/etc/hyperledger/fabric/client.crt /etc/hyperledger/fabric/client.key /etc/hyperledger/fabric/peer.crt] |
可以到REGISTER、READY、COMPLETED等消息,以及状态的改变:created、ready。
但前面还没有介绍Peer和链码容器之间的通信,所以不展示代码了,展示一下Peer和链码容器的消息交互图: