接着继续分析acc的初始化过程,在instantiate(3)里提到endorser.callChaincode有两次的ChaincodeSupport#Execute调用,由代码分析两次不同的ChaincodeMessage的MessageType分别为MessageType_TRANSACTION(对应cis)和MessageType_INIT(对应cds),相应调用lscc的Invoke和acc的Init。具体过程可以参考上节分析。
先看lscc的Invoke.参考上节提及的execute在两端chaincode/handler.go(peer端)和shim/handler.go(chaincode容器端)的流程,lscc相当于chaincode容器(尽管是inproccontainer),使用shim/handler.go#handleReady里select-case,lscc的ChaincodeMessage_TRANSACTION进入handleTransaction方法。这里的主要流程与上节介绍的handleInit一致,不同的是这里调用的是lscc的Invoke方法。这里需要强调一点,在handleTransaction,handleInit这些handleXXX方法里,进入就是启动goroutinne来运行所有的逻辑,这样可以立刻返回并行来处理下一个请求。
1 | // Invoke implements lifecycle functions "deploy", "start", "stop", "upgrade". |
进入lscc的Invoke方法。回顾instantiate(2)里cis的数据结构"Args" : ["deploy", "${channelId}", "${chaincodDeploySpec}"],这里的select-case进入DEPLOY,UPGRADE分支。分支里主要是校验以及处理传入参数,包括指定的scc和private data相关,重要的方法是lscc.executeDeployOrUpgrade,传参就是刚获取和校验过的参数。
进入executeDeployOrUpgrade。首先执行lscc.support.GetChaincodeFromLocalStorage(chaincodeName, chaincodeVersion),获取ccprovider.CCPackage,这是安装阶段执行peer chaincode install命令时打包并且上传到该节点的chaincode定义,放在peer节点本地目录的(这个在install里具体分析其结构)。然后执行lscc.executeDeploy。
1 | // executeDeploy implements the "instantiate" Invoke transaction |
这里先检查该chaincode是否已经部署过lscc.getCCInstance(stub, chaincodeName),实际上是调用stub.GetState(ccname),构建&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: payloadBytes, Txid: txid, ChannelId: channelId}发送到对端chaincode/handler。这里会执行到handler.callPeerWithChaincodeMsg方法。其实里面就是hanlder本身维护着一个名叫responseChannel的map[string]chan pb.ChaincodeMessage, 先创建一个channel放入map中,key是由channelID和txid构造而成,即关联当前请求,在退出方法时使用defer从map中删除这个channle。而在这个过程中,在handler.sendReceive里使用handler.serialSendAsync发送ChaincodeMessage,然后select-case一直等待从channel里读取消息。
相应的,当对端回复消息pb.ChaincodeMessage_RESPONSE,shim/handler会进入fabric/core/chaincode/shim/handler.go:handleReady->handler.sendChannel(msg),这里从responseChannel这个map里拿出相应的channel,放入消息。实际上,通过这个实现了异步通信,结合上面提到的每次进入handlerXXX方法都使用goroutine处理而立刻返回,实现了其并发通信。
1 | // callPeerWithChaincodeMsg sends a chaincode message (for e.g., GetState along with the key) to the peer for a given txid and receives the response. |
chaincode/handler对该类型请求在hanldeMessage里HandleGetState一路判断最后执行res, err = txContext.TXSimulator.GetState(chaincodeName, getState.Key),从本地账本db获取namespace为lscc,key为${chaincodeName}的数据(最终执行queryHelper.txmgr.db.GetState(ns, key)。在executeDeploy稍后可以看到,执行lscc.putChaincodeData(stub, cdfs)操作时执行相对应的put state操作,即deploy之后会把该chaincode写入peer本地db的lscc的namespace里。因此,通过校验get回来的数据为空来判断该chaincode没有deploy过。
1 | // core/ledger/kvledger/txmgmt/txmgr/lockbasedtxmgr/helper.go:40 |
从上面看出,账本的value是statedb.VersionedValue结构,包含value和version(结构为*version.Height{BlockNum uint64, TxNum uint64})。获取的结果加入readSet,其中参数(namesapce:lscc,key:${chaincodeName}, ver: 版本)
然后获取并检查部署策略lscc.support.CheckInstantiationPolicy。然后执行lscc.putChaincodeData(stub, cdfs),调用的是stub.PutState(cd.Name, cdbytes)。入参是chaincode.Name和ccprovider.ChaincodeData序列化字节,这个是通过从本地存储的codepackage和刚刚传入的escc,vscc等参数构建成的。向对端chaincode/handler发送pb.ChaincodeMessage_PUT_STATE。
1 | //-------- ChaincodeData is stored on the LSCC ------- |
数据分为普通channle公开信息和private消息,这里只讨论前者。相应的,对端chaincode/handler在hanldeMessage里HandlePutState一路判断最后执行res, err = txContext.TXSimulator.SetState(chaincodeName, putState.Key, putState.Value).跟上面区别的是,这里在ockbased_tx_simulator.go文件里,清楚指明这是模拟交易。同时,只把(namespace:lscc, key:${chaincode.Name}, value:${ChaincodeData}序列化字节)的组合加入到writeSet内,然后就返回结果ChaincodeMessage_RESPONSE或者错误消息 ChaincodeMessage_ERROR,并没有真正作用于账本db。同时,没有指定version,这个是在orderer排序打包时添加的。关于这里的read-write set,可以参考官网概念Read-Write set semantics。
1 | // core/ledger/kvledger/txmgmt/txmgr/lockbasedtxmgr/lockbased_tx_simulator.go |
最后列举下Invoke lscc后从shim/handler返回到chaincode/handler的数据结构pb.ChaincodeMessage
1 | // pb.ChaincodeMessage |
这里继续直接看acc的Init,之前的步骤与lscc完全一致,可以参考上节。Tutorial里的例子的chaincode代码在github.com/hyperledger/fabric/examples/chaincode/go/example02/chaincode.go,里面的Init方法主要是读入参数,然后调用stub.PutState后返回success。细节如上,不再复述。