又開始新的閱讀了,此次看的是Peer節點加入通道的過程。其實每次看源碼都會有好多沒有看懂的地方,不過相信只要堅持下去,保持記錄,仍是有不少收穫的。
對於Peer節點加入通道這一過程,從用戶角度來講也只是簡單執行一行命令:html
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()
方法,接下來就看一下該方法:git
spec, err := getJoinCCSpec() if err != nil { return err }
首先就是獲取須要加入的通道的具體信息,在67行:github
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
:json
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的建立了:dom
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()
方法:ide
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()
方法中能夠看到。而後咱們看一下方法中的具體流程:ui
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()
方法,繼續往下看:code
#剛剛這行代碼返回了建立的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
。ChaincodeHeaderExtension
,ChaincodeProposalPayload
,Header
,Proposal
結構體。Proposal
結構體進行簽名操做,由Peer節點進行處理,處理完成後返回響應消息。 對於Peer節點進行消息處理的方法ProcessProposal
在這篇文章中:Fabric1.4源碼解析:Peer節點背書提案過程
這裏給出一個類圖好了,以前有太多的結構體,關係有點複雜:
該圖片來源:https://github.com/yeasy/hyperledger_code_fabric/blob/master/peer/_images/signed_proposal.png
最後給出參考文檔