本系列目录:超级账本源码(V1.3)解析目录
本篇博客讲解Fabric如何启动链码容器并与之交互的。
启动链码容器
-
在peer启动的时候,
peer/node/start.go
的serve
函数中调用了startChaincodeServer
函数来启动链码服务器,该服务器负责与链码容器进行交互,完成交易。 -
startChaincodeServer
中调用了registerChaincodeSupport
函数,其中初始化了ChaincodeSupport
结构体:chaincodeSupport := chaincode.NewChaincodeSupport( chaincode.GlobalConfig(), ccEndpoint, userRunsCC, ca.CertBytes(), authenticator, packageProvider, lsccInst, aclProvider, container.NewVMController(map[string]container.VMProvider{ dockercontroller.ContainerType: dockercontroller.NewProvider( // "DOCKER" viper.GetString("peer.id"), viper.GetString("peer.networkId"), ), inproccontroller.ContainerType: ipRegistry, // "SYSTEM }), sccp, pr, peer.DefaultSupport, )
-
其中最关键的部分是
NewVMController
,该函数构造了一个VMController
实例(实现了Processor
的Process
接口,该接口负责处理操作容器的message),该实例包含了一个VMProvider
的map映射,这里使用了两个item进行初始化,分别是docker容器的provider和管理系统链码的注册器(registry),后续VMController
会根据这个map记录的信息来启动对应的VM。// core/chaincode/chaincode_support.go type ChaincodeSupport struct { Keepalive time.Duration ExecuteTimeout time.Duration UserRunsCC bool Runtime Runtime # !!! ACLProvider ACLProvider HandlerRegistry *HandlerRegistry # !! Launcher Launcher # !!! SystemCCProvider sysccprovider.SystemChaincodeProvider Lifecycle Lifecycle appConfig ApplicationConfigRetriever }
-
在
ChaincodeSupport
结构体中,有一个HandlerRegistry
成员负责记录某个chaincode的container是否启动,此外还有两个成员Runtime
和Launcher
非常重要。Runtime
调用了core/chaincode/container_runtime.go
中的ContainerRuntime
结构体进行初始化,负责启动或者停止容器。Launcher
调用了core/chaincode/runtime_launcher.go
中的RuntimeLauncher
结构体进行初始化,Runtime
是其成员。 -
链码容器并没有随着peer启动而启动(毕竟这时候可能还没有
install
&instantiate
链码),而是等需要与链码进行交互时再启动链码容器,也就是当发起交易后,调用core/chaincode/chaincode_support.go
的Execute
函数时,通过调用Launch
函数来启动容器。 -
Launch
函数会先检查容器是否已经启动,如果没有启动,通过调用core/chaincode/runtime_launcher.go
的Launch
函数,进一步调用core/chaincode/container_runtime.go
中的Start
函数来启动链码容器。 -
Start
函数调用了我们上面提到的core/container/controller.go
中的Process
函数来处理启动容器的message。Process
函数首先通过初始化时map
记录的VMProvider
获取了一个VM实例(这里也就是容器DockerVM
),然后调用core/container/dockercontroller/dockercontroller.go
中的Start
启动了容器。
启动过程会先从本地获取需要的docker image,如果本地没有,那么需要build这个链码容器的image。
build链码容器的image
-
在调用
Start
时,如果在本地没有找到对应链码容器的image,那么会调用builder.Build()
来创建对应的Dockerfile
,这里的builder
是在core/chaincode/container_runtime.go
中的Start
函数中初始化的,其中传入了创建该Dockerfile
需要的信息,而PlatformBuilder
结构体中的platforms.Registry
是在初始化ChaincodeSupport
时传入进来的,该Registry
最早在peer/node/start.go
中调用platforms.NewRegistry
进行了初始化(支持golang
、java
、node
等)。 -
Start
通过调用core/chaincode/platforms/platforms.go
中的Build
来构建Dockerfile
,Build
会去调用对应的platform
中的GenerateDockerBuild
,并最终调用了core/chaincode/platforms/util/utils.go
中的DockerBuild
函数来构建链码容器的image。
链码容器启动后与Peer建立通信
-
容器启动后首先调用了
core/chaincode/shim/chaincode.go
中的Start
函数,该函数调用了userChaincodeStreamGetter
与peer建立了一个connection并调用protos/peer/chaincode_shim.pb.go
中的NewChaincodeSupportClient
创建了一个grpcClient获得双方通信的stream,最后Start
函数启动了chatWithPeer
向peer发送了一条ChaincodeMessage_REGISTER
消息进行注册并开始在一个for循环里不断的接收peer发来的message。 -
当链码容器的client调用
Register
时,Peer端也会调用core/chaincode/chaincode_support.go
中的Register
函数,然后调用core/chaincode/handler.go
中的ProcessStream
开始接收链码容器的message。