Fabric 1.4源码分析 - chaincode instantiate(5)system chaincode的初始化

接下来分析lscc的执行过程,在这之前,先看下其初始化注册过程。在peer/node/start.go#ServeregisterChaincodeSupport(ccSrv, ccEndpoint, ca, aclProvider)。进入方法,主要是scc.CreateSysCCs(ccp, sccp, aclProvider),就是引入了scc/importsysccs.go#builtInSystemChaincodes里定义的SCC,即cscc,lsccgscc(包含chaincode的Name,Path代码地址,Chaincode对象,acl,policy等)。然后逐个执行sccp.RegisterSysCC(cc)注册SCC,实际上就是加入SystemChaincodeProvider.Registrar.typeRegistry(map[string]*inprocContainer)里,key是chaincode的Name,这个后面初始化时候会使用到。

这里完成注册后,在serve()里稍后的位置执行sccp.DeploySysCCs("", ccp)进行部署。第一个参数是channelId为“”,说明这些scc是chainless的,可以供所有的channel使用。进入方法,首先构造cds&pb.ChaincodeDeploymentSpec{ExecEnv: pb.ChaincodeDeploymentSpec_SYSTEM, ChaincodeSpec: spec}.这里注意到,部署类型是ChaincodeDeploymentSpec_SYSTEM,回顾上节acc类型是ChaincodeDeploymentSpec_DOCKER,说明SCC是部署在peer内的,而acc是部署在另外新的chaincode docker容器里。然后执行ccprov.Execute(ctxt, cccid, chaincodeDeploymentSpec),最终实际上又回到前面介绍过的ChaincodeSupport.Execute方法上。后面的执行与acc是一致的,所区别的在于launch。区别于acc使用的是dockercontroller/DockerVM#start,scc使用的是inproccontroller.InprocVM#start.首先vm.registry.typeRegistry[path],这个在前面已经注册了可以获取出来。然后开始真正的launch。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func (ipc *inprocContainer) launchInProc(ctxt context.Context, id string, args []string, env []string, ccSupport ccintf.CCSupport) error {
peerRcvCCSend := make(chan *pb.ChaincodeMessage)
ccRcvPeerSend := make(chan *pb.ChaincodeMessage)
ccchan := make(chan struct{}, 1)
ccsupportchan := make(chan struct{}, 1)
go func() {
defer close(ccchan)
err := shim.StartInProc(env, args, ipc.chaincode, ccRcvPeerSend, peerRcvCCSend)
}()

go func() {
defer close(ccsupportchan)
inprocStream := newInProcStream(peerRcvCCSend, ccRcvPeerSend)
err := ccSupport.HandleChaincodeStream(ctxt, inprocStream)
}()

select {
case <-ccchan:
close(peerRcvCCSend)
case <-ccsupportchan:
close(ccRcvPeerSend)
case <-ipc.stopChan:
close(ccRcvPeerSend)
close(peerRcvCCSend)
}
}

// StartInProc is an entry point for system chaincodes bootstrap. It is not an API for chaincodes.
func StartInProc(env []string, args []string, cc Chaincode, recv <-chan *pb.ChaincodeMessage, send chan<- *pb.ChaincodeMessage) error {
stream := newInProcStream(recv, send)
err := chatWithPeer(chaincodename, stream, cc)
}

type inProcStream struct {
recv <-chan *pb.ChaincodeMessage
send chan<- *pb.ChaincodeMessage
}

这里主要有两个goroutine,分别处理chaincode shim和peer的handle stream。同时,有两个channel,peerRcvCCSend,ccRcvPeerSend,可以从名字判断出消息的流向。

启动第一个goroutineshim.StartInProc,向chaincodeSupport发送注册。用于这里的chatWithPeer跟acc介绍的是同一个都在chaincode.go下,在这里面构造了该scc的handler(设置inProcStream这个stream)发送了&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_REGISTER, Payload: payload})这个消息。这个类比于acc里在chaincode容器的handler。

启动第二个goroutineccSupport.HandleChaincodeStream(ctxt, inprocStream),用于处理注册请求,这个类比于acc里的peer的handler,所不同的是,acc里的stream是grpc server stream,是由grpc server构建的,而这里则是封装了peerRcvCCSend,ccRcvPeerSend这两个channel。总而言之,在acc内,新chaincode容器内通过grpc方式与peer通信,而scc由于都处在同一个peer容器内,是通过go channel的方法实现。其他的主要流程都是一致的。

第三部分的select-case则是在处理chaincode退出的情况。这个方法在外层InprocVM#Start是go routine启动的,最外层等待的是select-case等待launchState.Done()channel的返回,这个跟acc是一致的。

一路返回到core/chaincode/chaincode_support.go#Invoke方法,在执行完Launch后,最后执行ChaincodeSupport#execute.在前面instantiate(3)里介绍过execute方法,这里直接看发送的ChaincodeMessage。在chaincode_support.go#Invoke里指定部署ChaincodeDeploymentSpecChaincodeMessage_Typepb.ChaincodeMessage_INIT,由chaincode/handler.go#serialSendAsync,对端的shim/handler.go#handleMessage。前面初始化完成后,两端handler状态都为ready,select-case进入hanlder.state=ready,ChaincodeMessageType=ChaincodeMessage_INIT执行到#handleInit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func (handler *Handler) handleInit(msg *pb.ChaincodeMessage, errc chan error) {
// The defer followed by triggering a go routine dance is needed to ensure that the previous state transition
// is completed before the next one is triggered. The previous state transition is deemed complete only when
// the beforeInit function is exited. Interesting bug fix!!
go func() {
var nextStateMsg *pb.ChaincodeMessage
defer func() {
handler.triggerNextState(nextStateMsg, errc)
}()

// Call chaincode's Run
// Create the ChaincodeStub which the chaincode can use to callback
// 用传入的数据初始化stub
stub := new(ChaincodeStub)
err := stub.init(handler, msg.ChannelId, msg.Txid, input, msg.Proposal)

res := handler.cc.Init(stub)

// Send COMPLETED message to chaincode support and change state
nextStateMsg = &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: resBytes, Txid: msg.Txid, ChaincodeEvent: stub.chaincodeEvent, ChannelId: stub.ChannelId}
}()
}

# github.com/hyperledger/fabric/core/scc/lscc
//Init is mostly useless for SCC
func (lscc *lifeCycleSysCC) Init(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}

在这里,主要是stub.init构建stub,这个桩的概念在远程调用里常见。然后执行handler.cc.Init(stub)。这里看lscc.go#Init.对于lscc(大多数scc)来说,初始化并不需要进行特殊处理,因此这里只是简单返回shim.Success的resp.在handleInit里,defer func将结果返回,这里如果执行成功,返回pb.ChaincodeMessage_COMPLETED,否则返回pb.ChaincodeMessage_ERROR.同样的,在chaincode/handler.go#handleMessage里对pb.ChaincodeMessage_COMPLETED, pb.ChaincodeMessage_ERROR这两个调用chaincode shim的回复消息调用handler.Notify(msg),即tctx.ResponseNotifier <- msg.这个在前面的instantiate(3)小节提过,异步调用的错误以及结果都发送到txctx.ResponseNotifie这个chan内再做后续的处理。至此,完成lscc的初始化和注册,后面再介绍acc的初始化的rpc过程。

命名缩写参考
cc : chaincode
scc : system chaincode
ccp, ccprov : chaincode provider
sccp : system chaincode provider
cccid : ccprovider.CCContext