Fabric1.4源碼解析:Peer節點加入通道


      又開始新的閱讀了,這次看的是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節點加入通道的操作就已經結束了,我們總結一下之前所做的工作:

  1. 首先就是由peer channel join -b mychannel.block這條命令觸發,經過多次調用最后到executeJoin()方法。
  2. 首先獲取mychannel.block文件中的信息,封閉為ChaincodeSpec結構體。
  3. 然后再封裝為ChaincodeInvocationSpec結構體。
  4. 獲取一個用於發起提案與對提案進行簽名操作的creator
  5. 生成nonce與TxID,進而封裝為ChaincodeHeaderExtension,ChaincodeProposalPayload,HeaderProposal結構體。
  6. 對生成的Proposal結構體進行簽名操作,由Peer節點進行處理,處理完成后返回響應消息。

      對於Peer節點進行消息處理的方法ProcessProposal在這篇文章中:Fabric1.4源碼解析:Peer節點背書提案過程

      這里給出一個類圖好了,之前有太多的結構體,關系有點復雜:

該圖片來源:https://github.com/yeasy/hyperledger_code_fabric/blob/master/peer/_images/signed_proposal.png

最后給出參考文檔


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM