又開始新的閱讀了,這次看的是Peer節點加入通道的過程。其實每次看源碼都會有好多沒有看懂的地方,不過相信隻要堅持下去,保持記錄,還是有很多收獲的。
對于Peer節點加入通道這一過程,從使用者角度來說也隻是簡單執行一行指令:
peer channel join -b mychannel.block
就完成了某一節點加入通道的過程。而從Fabric網絡内部來講,卻是做了很多工作,接下來看一下具體的流程:
整個流程的切入點和用戶端建立通道的流程相同在
fabric/peer/main.go
檔案中的
main()
方法,通過執行以上指令調用到
peer/channel/channel.go
中的
Cmd()
方法,然後是
peer/channel/join.go
檔案中的
joinCmd()
方法,131行的
join()
,最後就到了88行的
executeJoin()
方法,接下來就看一下該方法:
spec, err := getJoinCCSpec()
if err != nil {
return err
}
首先就是擷取需要加入的通道的具體資訊,在67行:
func getJoinCCSpec() (*pb.ChaincodeSpec, error) {
#判斷指定路徑下是否有創世區塊,創世區塊的建立流程可以看之前那篇解析用戶端建立通道的文章
if genesisBlockPath == common.UndefinedParamValue {
return nil, errors.New("Must supply genesis block file")
}
#讀取創世區塊中的内容,就是通道的一些基本資訊
gb, err := ioutil.ReadFile(genesisBlockPath)
if err != nil {
return nil, GBFileNotFoundErr(err.Error())
}
#構造一個ChaincodeSpec結構體,第一個參數為JoinChain,指定操作為加入通道,第二個參數為創世區塊的資訊
input := &pb.ChaincodeInput{Args: [][]byte{[]byte(cscc.JoinChain), gb}}
spec := &pb.ChaincodeSpec{
Type: pb.ChaincodeSpec_Type(pb.ChaincodeSpec_Type_value["GOLANG"]),
ChaincodeId: &pb.ChaincodeID{Name: "cscc"},
Input: input,
}
===================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=======================
#最後傳回該結構體
return spec, nil
}
executeJoin()
方法繼續往下看:
invocation := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec}
根據之前建立的結構體再封裝一個結構體
ChaincodeInvocationSpec
:
type ChaincodeInvocationSpec struct {
ChaincodeSpec *ChaincodeSpec `protobuf:"bytes,1,opt,name=chaincode_spec,json=chaincodeSpec,proto3" json:"chaincode_spec,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
然後擷取一個建立者的身份,用于之後的提案的建立與簽名:
creator, err := cf.Signer.Serialize()
if err != nil {
return fmt.Errorf("Error serializing identity for %s: %s", cf.Signer.GetIdentifier(), err)
}
接下來就是Proposal的建立了:
var prop *pb.Proposal
prop, _, err = putils.CreateProposalFromCIS(pcommon.HeaderType_CONFIG, "", invocation, creator)
if err != nil {
return fmt.Errorf("Error creating proposal for join %s", err)
}
具體還要看一下
CreateProposalFromCIS()
方法,該方法在
core/protos/proputils.go
檔案第466行,繼而調用了237行的
CreateChaincodeProposal()
方法,看名字應該可以了解個大概資訊,建立一個鍊碼提案。然後是243行的
CreateChaincodeProposalWithTransient()
方法:
func CreateChaincodeProposalWithTransient(typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, creator []byte, transientMap map[string][]byte) (*peer.Proposal, string, error) {
#首先就是生成一個随機數
nonce, err := crypto.GetRandomNonce()
if err != nil {
return nil, "", err
}
#計算出一個TxID,具體是根據HASH算法生成的
txid, err := ComputeTxID(nonce, creator)
if err != nil {
return nil, "", err
}
#然後調用了這個方法,将之前生成的資料傳入進去
return CreateChaincodeProposalWithTxIDNonceAndTransient(txid, typ, chainID, cis, nonce, creator, transientMap)
}
CreateChaincodeProposalWithTxIDNonceAndTransient()
方法在第282行:
首先看一下該方法傳入的值:
txid
由之前的方法生成,
typ
最初的方法傳入進來,值為
HeaderType_CONFIG
,chainID為空字元串,
cis
也是最初的方法傳入進來,值為
ChaincodeInvocationSpec
結構體,
nonce
由之前的方法生成,
creator
也是最初的方法傳入進來,
transientMap
為空,在之前的
CreateChaincodeProposal()
方法中可以看到。然後我們看一下方法中的具體流程:
func CreateChaincodeProposalWithTxIDNonceAndTransient(txid string, typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, nonce, creator []byte, transientMap map[string][]byte) (*peer.Proposal, string, error) {
#首先是構造一個ChaincodeHeaderExtension結構體
ccHdrExt := &peer.ChaincodeHeaderExtension{ChaincodeId: cis.ChaincodeSpec.ChaincodeId}
=========================ChaincodeHeaderExtension=====================
type ChaincodeHeaderExtension struct {
PayloadVisibility []byte `protobuf:"bytes,1,opt,name=payload_visibility,json=payloadVisibility,proto3" json:"payload_visibility,omitempty"`
// The ID of the chaincode to target.
ChaincodeId *ChaincodeID `protobuf:"bytes,2,opt,name=chaincode_id,json=chaincodeId,proto3" json:"chaincode_id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
=========================ChaincodeHeaderExtension=====================
#将該結構體序列化
ccHdrExtBytes, err := proto.Marshal(ccHdrExt)
if err != nil {
return nil, "", errors.Wrap(err, "error marshaling ChaincodeHeaderExtension")
}
#将ChaincodeInvocationSpec結構體序列化
cisBytes, err := proto.Marshal(cis)
if err != nil {
return nil, "", errors.Wrap(err, "error marshaling ChaincodeInvocationSpec")
}
#又是一個結構體
ccPropPayload := &peer.ChaincodeProposalPayload{Input: cisBytes, TransientMap: transientMap}
============================ChaincodeProposalPayload=====================
type ChaincodeProposalPayload struct {
Input []byte `protobuf:"bytes,1,opt,name=input,proto3" json:"input,omitempty"`
TransientMap map[string][]byte `protobuf:"bytes,2,rep,name=TransientMap,proto3" json:"TransientMap,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:"-"`
}
============================ChaincodeProposalPayload=====================
#将該結構體序列化
ccPropPayloadBytes, err := proto.Marshal(ccPropPayload)
if err != nil {
return nil, "", errors.Wrap(err, "error marshaling ChaincodeProposalPayload")
}
var epoch uint64
#建立一個時間戳
timestamp := util.CreateUtcTimestamp()
#構造Header結構體,包含兩部分ChannelHeader和SignatureHeader
hdr := &common.Header{
ChannelHeader: MarshalOrPanic(
&common.ChannelHeader{
Type: int32(typ),
TxId: txid,
Timestamp: timestamp,
ChannelId: chainID,
Extension: ccHdrExtBytes,
Epoch: epoch,
},
),
SignatureHeader: MarshalOrPanic(
&common.SignatureHeader{
Nonce: nonce,
Creator: creator,
},
),
}
#序列化
hdrBytes, err := proto.Marshal(hdr)
if err != nil {
return nil, "", err
}
#最後構造成一個Proposal
prop := &peer.Proposal{
Header: hdrBytes,
Payload: ccPropPayloadBytes,
}
#傳回Proposal,這裡一直傳回到最外面的方法
return prop, txid, nil
}
讓我們回到
executeJoin()
方法,繼續往下看:
#剛剛這行代碼傳回了建立的Proposal
prop, _, err = putils.CreateProposalFromCIS(pcommon.HeaderType_CONFIG, "", invocation, creator)
if err != nil {
return fmt.Errorf("Error creating proposal for join %s", err)
}
#定義一個被簽名的Proposal
var signedProp *pb.SignedProposal
#這個方法就是對建立的Proposal進行簽名了,具體的就不再看了,繼續往下
signedProp, err = putils.GetSignedProposal(prop, cf.Signer)
if err != nil {
return fmt.Errorf("Error creating signed proposal %s", err)
}
#定義了個提案響應
var proposalResp *pb.ProposalResponse
#重要的方法,由Peer節點對剛剛建立的提案進行處理,處理完成後傳回提案響應,之前有篇文章對這個方法進行了講解,在文章最後貼出了位址,這裡就不再說明了
proposalResp, err = cf.EndorserClient.ProcessProposal(context.Background(), signedProp)
#後面的比較簡單了,就是根據傳回的響應消息進行處理,就不再說明了
if err != nil {
return ProposalFailedErr(err.Error())
}
if proposalResp == nil {
return ProposalFailedErr("nil proposal response")
}
if proposalResp.Response.Status != 0 && proposalResp.Response.Status != 200 {
return ProposalFailedErr(fmt.Sprintf("bad proposal response %d: %s", proposalResp.Response.Status, proposalResp.Response.Message))
}
logger.Info("Successfully submitted proposal to join channel")
return nil
}
到這裡Peer節點加入通道的操作就已經結束了,我們總結一下之前所做的工作:
- 首先就是由
這條指令觸發,經過多次調用最後到peer channel join -b mychannel.block
方法。executeJoin()
- 首先擷取
檔案中的資訊,封閉為mychannel.block
結構體。ChaincodeSpec
- 然後再封裝為
結構體。ChaincodeInvocationSpec
- 擷取一個用于發起提案與對提案進行簽名操作的
。creator
- 生成nonce與TxID,進而封裝為
,ChaincodeHeaderExtension
,ChaincodeProposalPayload
,Header
結構體。Proposal
- 對生成的
結構體進行簽名操作,由Peer節點進行處理,處理完成後傳回響應消息。Proposal
對于Peer節點進行消息處理的方法
ProcessProposal
在這篇文章中:Fabric1.4源碼解析:Peer節點背書提案過程
這裡給出一個類圖好了,之前有太多的結構體,關系有點複雜:

該圖檔來源:https://github.com/yeasy/hyperledger_code_fabric/blob/master/peer/_images/signed_proposal.png
最後給出參考文檔