源码分析-Fabric 1.4.2 lscc启动用户链码的过程
lscc负责管理链码的生命周期,其就是说链码容器的启动应该是lscc触发的。但是链码容器是如何从peer chaincode instantiate 触发实例化命令到peer服务完成链码的实例化,即链码容器的启动,看了一些网上的资料机会没有讲这块的。也因为又这块二次开发的需求,所以追踪了一下Fabric源码从lscc到用户链码容器启动的全过程,在此记录一下。
客户端peer chaincode instantiate部分
fabric/peer/chaincode/instantiate.go
func instantiate(cmd *cobra.Command, cf *ChaincodeCmdFactory) (*protcommon.Envelope, error) {
...
// instantiate is currently only supported for one peer
proposalResponse, err := cf.EndorserClients[0].ProcessProposal(context.Background(), signedProp
/*
EndorserClients[0]就是这个peer本身(但是这是在客户端里呀,配置中设置好的地址,详细看cli的配置 CORE_PEER_ADDRESS=peer0.org1.example.com:7051)
ProcessProposal是grpc定义的函数,可以在peer.pb.go中看到定义的EndorserClient和EndorserServern内容,这里是客户端调用ProcessProposal把相关内容发送给Peer Server
Peer chaincode instantiate是客户端,但客户端的endorseClient在哪里处理的?InitCmdFactory中
*/
...
}
ProcessProposal可以定位到grpc的定义,这是双方交互的接口
fabric/protos/peer/peer.go
message PeerID {
string name = 1;
}
message PeerEndpoint {
PeerID id = 1;
string address = 2;
}
service Endorser {
rpc ProcessProposal(SignedProposal) returns (ProposalResponse) {}
}
接下来,看下Endorser客户端是如何处理的,如何请求服务的。
先找到cf.EndorserClients是在哪里实例化的,定位到InitCmdFactory
fabric/peer/chaincode/common.go
// InitCmdFactory init the ChaincodeCmdFactory with default clients
func InitCmdFactory(cmdName string, isEndorserRequired, isOrdererRequired bool) (*ChaincodeCmdFactory, error) {
var err error
var endorserClients []pb.EndorserClient
var deliverClients []api.PeerDeliverClient
if isEndorserRequired {
if err = validatePeerConnectionParameters(cmdName); err != nil {
return nil, errors.WithMessage(err, "error validating peer connection parameters")
}
for i, address := range peerAddresses {
var tlsRootCertFile string
if tlsRootCertFiles != nil {
tlsRootCertFile = tlsRootCertFiles[i]
}
//找到EndorserClient实例化的代码
endorserClient, err := common.GetEndorserClientFnc(address, tlsRootCertFile)
//GetEndorserClientFnc的实例是在common/common.go/init()中构建
if err != nil {
return nil, errors.WithMessage(err, fmt.Sprintf("error getting endorser client for %s", cmdName))
}
endorserClients = append(endorserClients, endorserClient)
deliverClient, err := common.GetPeerDeliverClientFnc(address, tlsRootCertFile)
if err != nil {
return nil, errors.WithMessage(err, fmt.Sprintf("error getting deliver client for %s", cmdName))
}
deliverClients = append(deliverClients, deliverClient)
}
if len(endorserClients) == 0 {
return nil, errors.New("no endorser clients retrieved - this might indicate a bug")
}
}
...
return &ChaincodeCmdFactory{
EndorserClients: endorserClients,
DeliverClients: deliverClients,
Signer: signer,
BroadcastClient: broadcastClient,
Certificate: certificate,
}, nil
}
通过GetEndorserClientFnc找到func GetEndorserClient(address, tlsRootCertFile string) (pb.EndorserClient, error)
fabric/peer/common/peerclient.go
// GetEndorserClient returns a new endorser client. If the both the address and
// tlsRootCertFile are not provided, the target values for the client are taken
// from the configuration settings for "peer.address" and
// "peer.tls.rootcert.file"
func GetEndorserClient(address, tlsRootCertFile string) (pb.EndorserClient, error) {
var peerClient *PeerClient
var err error
if address != "" {
peerClient, err = NewPeerClientForAddress(address, tlsRootCertFile)
} else {
peerClient, err = NewPeerClientFromEnv()
}
if err != nil {
return nil, err
}
//又引出peerClient
return peerClient.Endorser()
}
定位到peerclient.go,发现peerClient的许多方法,对应这不同的grpc客户端,这里是fabric中grpc客户端封装处理的地方,Endorser grpc客户端生成的方法func (pc *PeerClient) Endorser() (pb.EndorserClient, error)
endorser grpc client实例,fabric/peer/common/peerclient.go
// Endorser returns a client for the Endorser service
func (pc *PeerClient) Endorser() (pb.EndorserClient, error) {
conn, err := pc.commonClient.NewConnection(pc.address, pc.sn)
if err != nil {
return nil, errors.WithMessage(err, fmt.Sprintf("endorser client failed to connect to %s", pc.address))
}
return pb.NewEndorserClient(conn), nil
}
到此,找到了endorser grpc客户端实例的创建,以及grpc的请求,调用接口ProcessProposal
我们看到实例化是启动了背书的grpc,实际上其他交易也是启动背书过程,这是fabric,区块链的特性,大多数的处理都会在背书上,所以待会我们主要看服务端的背书处理上
peer chaincode instantiate 链码实例化客户端部分已经完成
peer chaincode instantiate 链码实例化server端
服务端首先处理的是lscc的调用处理,就是lscc Invoke的地方,但如果继续看下去,就会发现,这里只是将作为参数的用户链码,存了起来,也就是说,将链码存储的是这里实现的。
fabric/core/scc/lscc/lscc.go/Invoke
// Invoke implements lifecycle functions "deploy", "start", "stop", "upgrade".
// Deploy's arguments - {[]byte("deploy"), []byte(<chainname>), <unmarshalled pb.ChaincodeDeploymentSpec>}
//
// Invoke also implements some query-like functions
// Get chaincode arguments - {[]byte("getid"), []byte(<chainname>), []byte(<chaincodename>)}
func (lscc *LifeCycleSysCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
args := stub.GetArgs()
if len(args) < 1 {
return shim.Error(InvalidArgsLenErr(len(args)).Error())
}
function := string(args[0])
// Handle ACL:
// 1. get the signed proposal
sp, err := stub.GetSignedProposal()
if err != nil {
return shim.Error(fmt.Sprintf("Failed retrieving signed proposal on executing %s with error %s", function, err))
}
switch function {
case INSTALL:
...
case DEPLOY, UPGRADE:
// we expect a minimum of 3 arguments, the function
// name, the chain name and deployment spec
if len(args) < 3 {
return shim.Error(InvalidArgsLenErr(len(args)).Error())
}
// channel the chaincode should be associated with. It
// should be created with a register call
channel := string(args[1])
if !lscc.isValidChannelName(channel) {
return shim.Error(InvalidChannelNameErr(channel).Error())
}
ac, exists := lscc.SCCProvider.GetApplicationConfig(channel)
if !exists {
logger.Panicf("programming error, non-existent appplication config for channel '%s'", channel)
}
// the maximum number of arguments depends on the capability of the channel
if !ac.Capabilities().PrivateChannelData() && len(args) > 6 {
return shim.Error(PrivateChannelDataNotAvailable("").Error())
}
if ac.Capabilities().PrivateChannelData() && len(args) > 7 {
return shim.Error(InvalidArgsLenErr(len(args)).