Fabric1.4原始碼解析:鏈碼例項化過程
阿新 • • 發佈:2019-07-08
之前說完了鏈碼的安裝過程,接下來說一下鏈碼的例項化過程好了,再然後是鏈碼的呼叫過程。其實這幾個過程內容已經很相似了,都是涉及到Proposal
,不過整體流程還是要說一下的。
同樣,切入點仍然是fabric/peer/main.go
檔案中的main()
方法:
#這一句定義了關於通過Peer節點操作鏈碼的命令
mainCmd.AddCommand(chaincode.Cmd(nil))
然後是fabric/peer/chaincode/chaincode.go
檔案中的Cmd()
方法,這裡則是具體的操作鏈碼的命令,其中就有對鏈碼進行例項化的命令:
chaincodeCmd.AddCommand(instantiateCmd(cf))
最後呼叫到了fabric/peer/chaincode/instantiate.go
檔案中的第57行的instantiate()
方法。也就是說,當我們在Peer
節點執行以下命令時,最終會到這個方法:
#以官方的例項化鏈碼的方法為例 peer chaincode instantiate -o orderer.example.com:7050 --tls true --cafile $ORDERER_CA -C mychannel -n mycc -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P "OR ('Org1MSP.member','Org2MSP.member')"
接下來就看一下instantiate()
方法:
#首先獲取要例項化的鏈碼的資訊
spec, err := getChaincodeSpec(cmd)
if err != nil {
return nil, err
}
getChaincodeSpec()
方法在peer/chaincode/common.go
檔案中第69行:
#將使用者例項化鏈碼所執行的命令作為引數傳入進去 func getChaincodeSpec(cmd *cobra.Command) (*pb.ChaincodeSpec, error) { #定義一個ChaincodeSpec結構體 spec := &pb.ChaincodeSpec{} ====================ChaincodeSpec=========================== type ChaincodeSpec struct { #Type表示鏈碼的型別 有GOLANG,NODE,CAR,JAVA,UNDEFINED五種型別 Type ChaincodeSpec_Type `protobuf:"varint,1,opt,name=type,proto3,enum=protos.ChaincodeSpec_Type" json:"type,omitempty"` #ChaincodeId也是一個結構體,定義了鏈碼的路徑資訊,鏈碼的名稱以及版本資訊 ChaincodeId *ChaincodeID `protobuf:"bytes,2,opt,name=chaincode_id,json=chaincodeId,proto3" json:"chaincode_id,omitempty"` #ChaincodeInput結構體中定義鏈碼的功能以及函式引數資訊 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=========================== #對使用者輸入的命令進行檢查 if err := checkChaincodeCmdParams(cmd); err != nil { // unset usage silence because it's a command line usage error cmd.SilenceUsage = false return spec, err } #定義ChaincodeInput結構體,就是上面說過的那個 input := &pb.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 }
看一下checkChaincodeCmdParams()
方法做了哪些工作,在219行:
func checkChaincodeCmdParams(cmd *cobra.Command) error {
#檢查使用者輸入的鏈碼名稱是否為空字串
if chaincodeName == common.UndefinedParamValue {
return errors.Errorf("must supply value for %s name parameter", chainFuncName)
}
#呼叫的方法是否為instantiate,install,upgrade,package其中的一個
if cmd.Name() == instantiateCmdName || cmd.Name() == installCmdName ||
cmd.Name() == upgradeCmdName || cmd.Name() == packageCmdName {
if chaincodeVersion == common.UndefinedParamValue {
return errors.Errorf("chaincode version is not provided for %s", cmd.Name())
}
if escc != common.UndefinedParamValue {
logger.Infof("Using escc %s", escc)
} else {
logger.Info("Using default escc")
escc = "escc"
}
if vscc != common.UndefinedParamValue {
logger.Infof("Using vscc %s", vscc)
} else {
logger.Info("Using default vscc")
vscc = "vscc"
}
if policy != common.UndefinedParamValue {
#獲取定義的策略,就比如 OR ('Org1MSP.member','Org2MSP.member')這條資訊是否有誤
p, err := cauthdsl.FromString(policy)
if err != nil {
return errors.Errorf("invalid policy %s", policy)
}
policyMarshalled = putils.MarshalOrPanic(p)
}
#如果定義了配置檔案,則從配置檔案中讀取配置資訊
if collectionsConfigFile != common.UndefinedParamValue {
var err error
collectionConfigBytes, err = getCollectionConfigFromFile(collectionsConfigFile)
if err != nil {
return errors.WithMessage(err, fmt.Sprintf("invalid collection configuration in file %s", collectionsConfigFile))
}
}
}
#對使用者傳入的例項化引數比如:-c '{"Args":["init","a","100","b","200"]}'
if chaincodeCtorJSON != "{}" {
...
}
return nil
}
回到instantiate()
方法:
cds, err := getChaincodeDeploymentSpec(spec, false)
if err != nil {
return nil, fmt.Errorf("error getting chaincode code %s: %s", chaincodeName, err)
}
獲取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"`
#鏈碼的執行環境,有兩種,Docker容器或者直接在系統中執行
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
結構體:
#定義了ChaincodeDeploymentSpec中的CodePackage
var codePackageBytes []byte
#判斷是否為開發模式
if chaincode.IsDevMode() == false && crtPkg {
var err error
#如果不是則檢查鏈碼是否為空,以及路徑是否正確
if err = checkSpec(spec); err != nil {
return nil, err
}
#將鏈碼轉換為Byte資料
codePackageBytes, err = container.GetChaincodePackageBytes(platformRegistry, spec)
...
}
#構造chaincodeDeploymentSpec並返回
chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes}
return chaincodeDeploymentSpec, nil
回到instantiate()
方法:
#獲取一全個簽名者,需要對建立例項化鏈碼的Proposal進行簽名
creator, err := cf.Signer.Serialize()
if err != nil {
return nil, fmt.Errorf("error serializing identity for %s: %s", cf.Signer.GetIdentifier(), err)
}
#要建立用於例項化鏈碼的Proposal了
prop, _, err := utils.CreateDeployProposalFromCDS(channelID, cds, creator, policyMarshalled, []byte(escc), []byte(vscc), collectionConfigBytes)
if err != nil {
return nil, fmt.Errorf("error creating proposal %s: %s", chainFuncName, err)
}
看一下CreateDeployProposalFromCDS()
方法,看名字瞭解到是根據chaincodeDeploymentSpec
建立用於部署鏈碼的Proposal
:
func CreateDeployProposalFromCDS(
#通道Id
chainID string,
cds *peer.ChaincodeDeploymentSpec,
#簽名者
creator []byte,
#具體的策略
policy []byte,
#endorser system chaincode
escc []byte,
#Verification System ChainCode
vscc []byte,
collectionConfig []byte) (*peer.Proposal, string, error) {
#下面的兩個方法呼叫的是同一個,只是傳入的引數不同,點進去
if collectionConfig == nil {
return createProposalFromCDS(chainID, cds, creator, "deploy", policy, escc, vscc)
}
return createProposalFromCDS(chainID, cds, creator, "deploy", policy, escc, vscc, collectionConfig)
}
該方法在538行,接下來的部分與客戶端安裝鏈碼所執行的流程基本是相同的,只有下面的一部分不同:
#對於例項化鏈碼來說,執行的是deploy與upgrade這兩部分,而安裝鏈碼則是install這部分,差異就在於ChaincodeInput結構體內的引數不同
case "deploy":
fallthrough
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...)
ccinp = &peer.ChaincodeInput{Args: Args}
case "install":
ccinp = &peer.ChaincodeInput{Args: [][]byte{[]byte(propType), b}}
}
// wrap the deployment in an invocation spec to lscc...
lsccSpec := &peer.ChaincodeInvocationSpec{
ChaincodeSpec: &peer.ChaincodeSpec{
Type: peer.ChaincodeSpec_GOLANG,
ChaincodeId: &peer.ChaincodeID{Name: "lscc"},
Input: ccinp,
},
}
剩下的部分就不再重複看了,可以參考Fabric1.4原始碼解析:客戶端安裝鏈碼這篇文章。
總的來說,整個流程共有以下幾部分:
- 根據使用者執行例項化鏈碼的命令啟動全過程
- 獲取需要例項化鏈碼的基本資訊
- 建立
ChaincodeDeploymentSpec
結構體. - 獲取用於對
Proposal
進行簽名的Creator
。 - 建立
Proposal
,Proposal
的Header
定義為ENDORSER_TRANSACTION
,表示是一個需要背書的交易。 - 由之前獲取的
Creator
進行簽名操作。 - 由
Peer
節點呼叫ProcessProposal()
方法進行處理,該方法的解析在這裡。這是一個很重要的方法。 - 接收到由
Peer
節點處理完成所返回的Response
訊息後傳送到Orderer
節點。 Orderer
節點接收到訊息後進行排序操作,如果是SOLO
模式則由Orderer
節點生成區塊,最後將區塊廣播至Peer
節點,Peer
節點接收到區塊訊息後驗證有效性,最後更新賬本資料。
最後附上參考連結:1.傳送門
2.傳送