Fabric 1.4源码分析 - chaincode instantiate(4)chaincode容器启动及注册

接下来回到容器启动后在容器中初始化和注册chaincode。在上一节构建docker里提到,容器的启动命令(指默认语言或者显式设置语言-l golang)为lc.Args = []string{"chaincode", fmt.Sprintf("-peer.address=%s", c.PeerAddress)}。在构建容器时fabric/core/chaincode/platforms/golang/platform.go#GenerateDockerBuild,指定了构建参数DockerBuildOptions

util.DockerBuildOptions{
  Cmd:          fmt.Sprintf("GOPATH=/chaincode/input:$GOPATH go build -tags \"%s\" %s -o /chaincode/output/chaincode %s", gotags, ldflagsOpt, pkgname),
  InputStream:  codepackage,
  OutputStream: binpackage,
}

这里指的是,将pkgname(spec.ChaincodeId.Path)指定的chaincode代码运行go build -o chaincode打包成名为chaincode的可执行文件。因此容器启动时,实际上调用了install的chaincode代码里的main方法,这也是分析的入口。在Building Your First Network官方教程,在fabric-samples/first-network/scripts的scripts.shutils.sh里找到install chaincode路径为${CC_SRC_PATH},在scripts.sh里可以得到CC_SRC_PATH="github.com/chaincode/chaincode_example02/go/". 该目录下chaincode_example02.go的main方法err := shim.Start(new(SimpleChaincode))启动。进入到chaincode.Start(cc Chaincode)方法

  1. SetupChaincodeLogging()设置日志相关
  2. 获取变量chaincode名称viper.GetString("chaincode.id.name")。回顾上节在创建chaincode容器时在container_runtime.go设置了变量append(c.CommonEnv, "CORE_CHAINCODE_ID_NAME="+cname)
  3. factory.InitFactories(factory.GetDefaultOpts())初始化BCCSP(blockchain crypto service)服务。待详述。
  4. userChaincodeStreamGetter构建与peer节点的grpc client。首先,获取peerAddress,这个在container_runtime.go设置了flag启动参数-peer.address,即endorser的peerAddress。然后,与该peer建立连接,构造/protos.ChaincodeSupport/Register的grpc stream。
  5. chatWithPeer(chaincodename string, stream PeerChaincodeStream, cc Chaincode).新建handler := newChaincodeHandler(stream, cc),(这里是shim/handler.go),这时候handler.state = created。这是在chaincode容器里的,与之相对的,在peer容器里也有handler,对等处理. 然后handler.serialSend(&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_REGISTER, Payload: payload});这里payload是&pb.ChaincodeID{Name: chaincodename}。将注册信息发送到peer节点后,启动goroutine。这个goroutine里是for循环,每次循环都新启动一个子goroutine,这个子go routine负责从stream里接受一条消息,放到两个channel(msgAvail, errc)里。msgAvail, errc = stream.Recv(),接收完一条即完成子goroutine的生命周期,下次循环再另起一个。父goroutine读取这两个channel,对接收到的消息msgAvail,则调用handler.handleMessage(in, errc)处理。最外层waitC等待终止信号,期间包含定期发送的pb.ChaincodeMessage_KEEPALIVE,直到遇到接收错误等才终止通信stream,返回异常。

再看peer端的对等handler注册。(这里是chaincode/handler.go)这部分在peer node start启动时候执行,注册pb.ChaincodeSupportServer(chaincodeSupport)。每次收到shim/handler的register方法意味着新的Register(&chaincodeSupportRegisterServer{stream},一路下去在fabric/core/chaincode/chaincode_support#HandleChaincodeStream里新建chaincode/handler,handler.state没有显式设置则为0值,即Created。然后进入主要方法chaincode/handler.ProcessStream。这方法流程跟上面的chatWithPeer非常相似,多了主动发起KeepAlive,以及在异常退出时取消注册hanlder。这也就意味着,chaincode容器与peer容器始终建立连接。hanlder从steam里读取message,然后handleMessage。这里实际上也就是在Handler.Registry(即ChaincodeSupport.HandlerRegistry)里注册该hanlder,跟上节从registry里通过chaincode.cname查找hanlder呼应。此外,在这个处理过程中,peer相应的改变registry里handler的状态,给chaincode容器回复&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_REGISTERED}。然后,handler置成established状态,对端的handler收到pb.ChaincodeMessage_REGISTERED,也将状态置为established。

chaincode/handler在置成established状态后马上执行notifyRegistry,这里主要两件事情,1.发送pb.ChaincodeMessage_READY,并且状态改为ready;2. 通知HandlerRegistry已完成注册(即从launching里delete当前chaincodename,关闭LaunchState.done channel可以判断以完成注册,这样,在core/chaincode/runtime_launcher.go#start里的select {case <-launchState.Done():可以继续往下走,因为这里调用的RuntimeLauncher.Runtime.Start是通过goroutine异步启动的),这里与上节介绍启动时注册aunching防止重复部署对应。对端收到通知后,也置状态为ready。至此完成了chaincode的启动工作,后续两端handler进入处理Init/Invoke请求流程。