看了看客戶端安裝鏈碼的部分,感覺還是比較簡單的,所以在這里記錄一下。
還是先給出安裝鏈碼所使用的命令好了,這里就使用官方的安裝鏈碼的一個例子:
#-n 指定mycc是由用戶定義的鏈碼名字,-v 指定1.0是鏈碼的版本,-p ...是指定鏈碼的路徑
peer chaincode install -n mycc -v 1.0 -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02
整個流程的切入點依舊是fabric/peer/main.go
文件中,在main()
方法中第47行:
mainCmd.AddCommand(chaincode.Cmd(nil))
這里就包含了Peer節點關於操作鏈碼的所有相關命令,點進行看一下,轉到了peer/chaincode/chaincode.go
文件中第49行:
func Cmd(cf *ChaincodeCmdFactory) *cobra.Command {
#這里的命令應該是比較熟悉的
addFlags(chaincodeCmd)
chaincodeCmd.AddCommand(installCmd(cf)) #這一個就是執行鏈碼的安裝
chaincodeCmd.AddCommand(instantiateCmd(cf)) #鏈碼的實例化
chaincodeCmd.AddCommand(invokeCmd(cf)) #鏈碼的調用,具體調用什么方法要看鏈碼是怎么寫的
chaincodeCmd.AddCommand(packageCmd(cf, nil)) #鏈碼的打包,暫時還沒有使用過
chaincodeCmd.AddCommand(queryCmd(cf)) #對鏈碼數據進行查詢,這個只是向指定的Peer節點請求查詢數據,不會生成交易最后打包區塊的
chaincodeCmd.AddCommand(signpackageCmd(cf)) #對已打包的鏈碼進行簽名操作
chaincodeCmd.AddCommand(upgradeCmd(cf)) #更新鏈碼,之前提到過 -v是指定鏈碼的版本,如果需要對鏈碼進行更新的話,使用這條命令,比較常用
chaincodeCmd.AddCommand(listCmd(cf)) #如果已指定通道的話,則查詢已實例化的鏈碼,否則查詢當前Peer節點已安裝的鏈碼
return chaincodeCmd
}
我們這里只對鏈碼的安裝部分進行相關的說明,其他的以后再說了,點進去安裝鏈碼的那條命令,轉到了peer/chaincode/install.go
文件中的第33行:
func installCmd(cf *ChaincodeCmdFactory) *cobra.Command {
chaincodeInstallCmd = &cobra.Command{
Use: "install",
Short: fmt.Sprint(installDesc),
Long: fmt.Sprint(installDesc),
ValidArgs: []string{"1"},
RunE: func(cmd *cobra.Command, args []string) error {
#定義鏈碼文件
var ccpackfile string
if len(args) > 0 {
ccpackfile = args[0]
}
#這里我們主要關注的就是這行代碼
return chaincodeInstall(cmd, ccpackfile, cf)
},
}
#這個就是可以在安裝鏈碼的命令中指定的相關參數
flagList := []string{
"lang",
"ctor",
"path",
"name",
"version",
"peerAddresses",
"tlsRootCertFiles",
"connectionProfile",
}
attachFlags(chaincodeInstallCmd, flagList)
return chaincodeInstallCmd
}
看一下chaincodeInstall()
方法:
func chaincodeInstall(cmd *cobra.Command, ccpackfile string, cf *ChaincodeCmdFactory) error {
cmd.SilenceUsage = true
var err error
if cf == nil {
#如果ChaincodeCmdFactory為空,則初始化一個,
cf, err = InitCmdFactory(cmd.Name(), true, false)
=================ChaincodeCmdFactory==================
#ChaincodeCmdFactory結構體
type ChaincodeCmdFactory struct {
EndorserClients []pb.EndorserClient #用於向背書節點發送消息
DeliverClients []api.PeerDeliverClient #用於與Order節點通信
Certificate tls.Certificate #TLS證書相關
Signer msp.SigningIdentity #用於消息的簽名
BroadcastClient common.BroadcastClient #用於廣播消息
}
=================ChaincodeCmdFactory==================
if err != nil {
return err
}
}
var ccpackmsg proto.Message
#這個地方有兩種情況,鏈碼可能是根據傳入參數從本地鏈碼源代碼文件讀取,也有可能是由其他節點簽名打包完成發送過來的,這種方式還沒有使用過
if ccpackfile == "" {
#這里是從本地鏈碼源代碼文件讀取
if chaincodePath == common.UndefinedParamValue || chaincodeVersion == common.UndefinedParamValue || chaincodeName == common.UndefinedParamValue {
return fmt.Errorf("Must supply value for %s name, path and version parameters.", chainFuncName)
}
#看一下這個方法,生成ChaincodeDeploymentSpce
ccpackmsg, err = genChaincodeDeploymentSpec(cmd, chaincodeName, chaincodeVersion)
if err != nil {
return err
}
}
genChaincodeDeploymentSpec()
這個方法在99行:
func genChaincodeDeploymentSpec(cmd *cobra.Command, chaincodeName, chaincodeVersion string) (*pb.ChaincodeDeploymentSpec, error) {
#首先根據鏈碼名稱與鏈碼版本查找當前鏈碼是否已經安裝過,如果安裝過則返回鏈碼已存在的錯誤
if existed, _ := ccprovider.ChaincodePackageExists(chaincodeName, chaincodeVersion); existed {
return nil, fmt.Errorf("chaincode %s:%s already exists", chaincodeName, chaincodeVersion)
}
#獲取鏈碼標准數據結構
spec, err := getChaincodeSpec(cmd)
if err != nil {
return nil, err
}
#獲取鏈碼部署標准數據結構
cds, err := getChaincodeDeploymentSpec(spec, true)
if err != nil {
return nil, fmt.Errorf("error getting chaincode code %s: %s", chaincodeName, err)
}
return cds, nil
}
看一下getChaincodeSpec()
方法,在peer/chaincode/common.go
文件中第69行:
func getChaincodeSpec(cmd *cobra.Command) (*pb.ChaincodeSpec, error) {
#首先定義了個鏈碼標准數據結構
===========================ChaincodeSpec===========================
type ChaincodeSpec struct {
Type ChaincodeSpec_Type `protobuf:"varint,1,opt,name=type,proto3,enum=protos.ChaincodeSpec_Type" json:"type,omitempty"`
ChaincodeId *ChaincodeID `protobuf:"bytes,2,opt,name=chaincode_id,json=chaincodeId,proto3" json:"chaincode_id,omitempty"`
Input *ChaincodeInput `protobuf:"bytes,3,opt,name=input,proto3" json:"input,omitempty"`
Timeout int32 `protobuf:"varint,4,opt,name=timeout,proto3" json:"timeout,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
===========================ChaincodeSpec===========================
spec := &pb.ChaincodeSpec{}
#檢查由用戶輸入的命令中的參數信息,比如格式,是否有沒有定義過的參數等等
if err := checkChaincodeCmdParams(cmd); err != nil {
cmd.SilenceUsage = false
return spec, err
}
#定義一個鏈碼輸入參數結構
input := &pb.ChaincodeInput{}
=======================ChaincodeInput=======================================
#該結構體主要保存用戶鏈碼中定義的功能以及參數等信息
type ChaincodeInput struct {
Args [][]byte `protobuf:"bytes,1,rep,name=args,proto3" json:"args,omitempty"`
Decorations map[string][]byte `protobuf:"bytes,2,rep,name=decorations,proto3" json:"decorations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
========================ChaincodeInput======================================
if err := json.Unmarshal([]byte(chaincodeCtorJSON), &input); err != nil {
return spec, errors.Wrap(err, "chaincode argument error")
}
chaincodeLang = strings.ToUpper(chaincodeLang)
#最后封裝為ChaincodeSpec結構體返回
spec = &pb.ChaincodeSpec{
Type: pb.ChaincodeSpec_Type(pb.ChaincodeSpec_Type_value[chaincodeLang]),
ChaincodeId: &pb.ChaincodeID{Path: chaincodePath, Name: chaincodeName, Version: chaincodeVersion},
Input: input,
}
return spec, nil
}
獲得了鏈碼標准數據結構之后,到了getChaincodeDeploymentSpec
這個方法,點進去看一下,在peer/chaincode/common.go
文件中第50行:
func getChaincodeDeploymentSpec(spec *pb.ChaincodeSpec, crtPkg bool) (*pb.ChaincodeDeploymentSpec, error) {
var codePackageBytes []byte
#首先判斷是否當前Fabric網絡處於開發模式,如果不是的話進入這里
if chaincode.IsDevMode() == false && crtPkg {
var err error
#然后對之前創建的鏈碼標准數據結構進行驗證,驗證是否為空,鏈碼類型路徑等信息
if err = checkSpec(spec); err != nil {
return nil, err
}
#獲取鏈碼信息的有效載荷
codePackageBytes, err = container.GetChaincodePackageBytes(platformRegistry, spec)
if err != nil {
err = errors.WithMessage(err, "error getting chaincode package bytes")
return nil, err
}
}
#最后封裝為ChaincodeDeploymentSpec,這里如果Fabric網絡處於開發模式下,codePackageBytes為空
chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes}
return chaincodeDeploymentSpec, nil
}
==============================ChaincodeDeploymentSpec=====================
type ChaincodeDeploymentSpec struct {
ChaincodeSpec *ChaincodeSpec `protobuf:"bytes,1,opt,name=chaincode_spec,json=chaincodeSpec,proto3" json:"chaincode_spec,omitempty"`
CodePackage []byte `protobuf:"bytes,3,opt,name=code_package,json=codePackage,proto3" json:"code_package,omitempty"`
ExecEnv ChaincodeDeploymentSpec_ExecutionEnvironment `protobuf:"varint,4,opt,name=exec_env,json=execEnv,proto3,enum=protos.ChaincodeDeploymentSpec_ExecutionEnvironment" json:"exec_env,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
==============================ChaincodeDeploymentSpec=====================
返回到最初的方法chaincodeInstall()
,如果是本地安裝的話,下一步就是鏈碼的安裝了,在此之前,我們看一下如果ccpackfile
不為空的那個分支:
func chaincodeInstall(cmd *cobra.Command, ccpackfile string, cf *ChaincodeCmdFactory) error {
...
if ccpackfile == "" {
...
ccpackmsg, err = genChaincodeDeploymentSpec(cmd, chaincodeName, chaincodeVersion)
...
} else {
var cds *pb.ChaincodeDeploymentSpec
#首先從ccpackfile中獲取數據,這個方法就不看了,主要就是從文件中讀取已定義的ChaincodeDeploymentSpec
ccpackmsg, cds, err = getPackageFromFile(ccpackfile)
if err != nil {
return err
}
#由於ccpackfile中已經定義完成了以上的數據結構,所以這里就直接獲取了
cName := cds.ChaincodeSpec.ChaincodeId.Name
cVersion := cds.ChaincodeSpec.ChaincodeId.Version
...
if chaincodeName != "" && chaincodeName != cName {
return fmt.Errorf("chaincode name %s does not match name %s in package", chaincodeName, cName)
}
...
if chaincodeVersion != "" && chaincodeVersion != cVersion {
return fmt.Errorf("chaincode version %s does not match version %s in packages", chaincodeVersion, cVersion)
}
}
#到了安裝鏈碼的地方了,我們看一下
err = install(ccpackmsg, cf)
return err
}
接下來我們看一下鏈碼的安裝過程:
install()
方法在peer/chaincode/install.go
文件中第63行:
func install(msg proto.Message, cf *ChaincodeCmdFactory) error {
#和之前分析的文章一樣,首先獲取一個用於發起提案與簽名的creator
creator, err := cf.Signer.Serialize()
if err != nil {
return fmt.Errorf("Error serializing identity for %s: %s", cf.Signer.GetIdentifier(), err)
}
#從ChaincodeDeploymentSpec中創建一個用於安裝鏈碼的Proposal
prop, _, err := utils.CreateInstallProposalFromCDS(msg, creator)
if err != nil {
return fmt.Errorf("Error creating proposal %s: %s", chainFuncName, err)
}
...
}
我們主要看一下CreateInstallProposalFromCDS()
方法,點進去一直到protos/utils/proutils.go
文件中第538行:
func createProposalFromCDS(chainID string, msg proto.Message, creator []byte, propType string, args ...[]byte) (*peer.Proposal, string, error) {
#傳入的參數說明一下:chainID為空,msg,creator由之前的方法傳入,propType為install,args為空
var ccinp *peer.ChaincodeInput
var b []byte
var err error
if msg != nil {
b, err = proto.Marshal(msg)
if err != nil {
return nil, "", err
}
}
switch propType {
#這里就判斷propTypre類型,如果是deploy,或者是upgrade需要鏈碼已經實例化完成
case "deploy":
fallthrough
#如果是deploy不跳出代碼塊,繼續執行upgrade中的代碼
case "upgrade":
cds, ok := msg.(*peer.ChaincodeDeploymentSpec)
if !ok || cds == nil {
return nil, "", errors.New("invalid message for creating lifecycle chaincode proposal")
}
Args := [][]byte{[]byte(propType), []byte(chainID), b}
Args = append(Args, args...)
#與安裝鏈碼相同,都需要定義一個ChaincodeInput結構體,該結構體保存鏈碼的基本信息
ccinp = &peer.ChaincodeInput{Args: Args}
case "install":
ccinp = &peer.ChaincodeInput{Args: [][]byte{[]byte(propType), b}}
}
#安裝鏈碼需要使用到生命周期系統鏈碼,所以這里定義了一個lsccSpce,注意這里的ChaincodeInvocationSpec在下面使用到
lsccSpec := &peer.ChaincodeInvocationSpec{
ChaincodeSpec: &peer.ChaincodeSpec{
Type: peer.ChaincodeSpec_GOLANG,
ChaincodeId: &peer.ChaincodeID{Name: "lscc"},
Input: ccinp,
},
}
#到這個方法了,根據ChaincodeInvocationSpec創建Proposal
return CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, chainID, lsccSpec, creator)
}
CreateProposalFromCIS()
這個方法在之前Fabric1.4源碼解析:Peer節點加入通道這篇文章中講過,具體可以看這里.
返回到install()
方法中,繼續往下:
prop, _, err := utils.CreateInstallProposalFromCDS(msg, creator)
if err != nil {
return fmt.Errorf("Error creating proposal %s: %s", chainFuncName, err)
}
var signedProp *pb.SignedProposal
#,到這里了,對創建的Proposal進行簽名,該方法也在上面那篇文章中說過,不再說明
signedProp, err = utils.GetSignedProposal(prop, cf.Signer)
if err != nil {
return fmt.Errorf("Error creating signed proposal %s: %s", chainFuncName, err)
}
#這個地方與之前分析的不同,這里安裝鏈碼只在指定的Peer節點,而不是所有Peer節點,依舊是調用了主要的方法ProcessProposal
proposalResponse, err := cf.EndorserClients[0].ProcessProposal(context.Background(), signedProp)
#到這里,Peer節點對提案處理完成之后,整個鏈碼安裝的過程就結束了
if err != nil {
return fmt.Errorf("Error endorsing %s: %s", chainFuncName, err)
}
if proposalResponse != nil {
if proposalResponse.Response.Status != int32(pcommon.Status_SUCCESS) {
return errors.Errorf("Bad response: %d - %s", proposalResponse.Response.Status, proposalResponse.Response.Message)
}
logger.Infof("Installed remotely %v", proposalResponse)
} else {
return errors.New("Error during install: received nil proposal response")
}
return nil
ProcessProposal()
之前在另一篇文章Fabric1.4源碼解析:Peer節點背書提案過程中都有說過,這里就不再說了,不過該方法非常非常重要,算是Fabric中使用頻率很高的一個方法。
最后總結一下:
- 首先由用戶執行安裝鏈碼的命令啟動了整個流程。
- 判斷鏈碼是從本地鏈碼源代碼文件讀取還是由接收到其他節點打包的鏈碼進行安裝
- 如果是從本地鏈碼源代碼文件讀取,首先根據傳入的鏈碼名稱和版本判斷該鏈碼是否已經存在。
- 如果存在的話直接返回錯誤信息。
- 如果不存在的話,首先對安裝鏈碼的命令中的參數進行驗證。
- 定義一個
ChaincodeInput
數據結構,保存鏈碼的功能以及參數信息 - 最后定義了個鏈碼標准數據結構
- 判斷是否處於開發環境下,定義了
ChaincodeDeploymentSpec
- 如果不是本地安裝,則從文件中直接讀取已定義的
ChaincodeDeploymentSpec
相關信息
- 如果是從本地鏈碼源代碼文件讀取,首先根據傳入的鏈碼名稱和版本判斷該鏈碼是否已經存在。
- 執行鏈碼的安裝,獲取簽名者
- 從
ChaincodeDeploymentSpec
中創建Proposal
- 對
Proposal
進行簽名 - 將已簽名的
Proposal
由Peer
節點進行處理 - 安裝鏈碼過程結束