超级账本源码解析之链码容器

本系列目录:超级账本源码(V1.3)解析目录

本篇博客讲解Fabric如何启动链码容器并与之交互的。

启动链码容器

  1. 在peer启动的时候,peer/node/start.goserve函数中调用了startChaincodeServer函数来启动链码服务器,该服务器负责与链码容器进行交互,完成交易。

  2. 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,
    )
    
  3. 其中最关键的部分是NewVMController,该函数构造了一个VMController实例(实现了ProcessorProcess接口,该接口负责处理操作容器的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
    }
    
  4. ChaincodeSupport结构体中,有一个HandlerRegistry成员负责记录某个chaincode的container是否启动,此外还有两个成员RuntimeLauncher非常重要。

    Runtime调用了core/chaincode/container_runtime.go中的ContainerRuntime结构体进行初始化,负责启动或者停止容器。

    Launcher调用了core/chaincode/runtime_launcher.go中的RuntimeLauncher结构体进行初始化,Runtime是其成员。

  5. 链码容器并没有随着peer启动而启动(毕竟这时候可能还没有install&instantiate链码),而是等需要与链码进行交互时再启动链码容器,也就是当发起交易后,调用core/chaincode/chaincode_support.goExecute函数时,通过调用Launch函数来启动容器。

  6. Launch函数会先检查容器是否已经启动,如果没有启动,通过调用core/chaincode/runtime_launcher.goLaunch函数,进一步调用core/chaincode/container_runtime.go中的Start函数来启动链码容器。

  7. Start函数调用了我们上面提到的core/container/controller.go中的Process函数来处理启动容器的message。Process函数首先通过初始化时map记录的VMProvider获取了一个VM实例(这里也就是容器DockerVM),然后调用core/container/dockercontroller/dockercontroller.go中的Start启动了容器。

启动过程会先从本地获取需要的docker image,如果本地没有,那么需要build这个链码容器的image。

build链码容器的image

  1. 在调用Start时,如果在本地没有找到对应链码容器的image,那么会调用builder.Build()来创建对应的Dockerfile,这里的builder是在core/chaincode/container_runtime.go中的Start函数中初始化的,其中传入了创建该Dockerfile需要的信息,而PlatformBuilder结构体中的platforms.Registry是在初始化ChaincodeSupport时传入进来的,该Registry最早在peer/node/start.go中调用platforms.NewRegistry进行了初始化(支持golangjavanode等)。

  2. Start通过调用core/chaincode/platforms/platforms.go中的Build来构建DockerfileBuild会去调用对应的platform中的GenerateDockerBuild,并最终调用了core/chaincode/platforms/util/utils.go中的DockerBuild函数来构建链码容器的image。

链码容器启动后与Peer建立通信

  1. 容器启动后首先调用了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。

  2. 当链码容器的client调用Register时,Peer端也会调用core/chaincode/chaincode_support.go中的Register函数,然后调用core/chaincode/handler.go中的ProcessStream开始接收链码容器的message。

猜你喜欢

转载自blog.csdn.net/yijiull/article/details/108600435