接着继续分析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。细节如上,不再复述。