之前歷來沒有寫過博客,從這段時間開始纔開始寫一些本身的博客,以前總以爲寫一篇博客要耗費大量的時間,並且寫的仍是本身已經學會的,以爲沒什麼必要。可是當開始用博客記錄下來的時候,才發現有些學會的地方只是本身以爲已經學會了,仍是有太多地方須要學習,眼高手低了,因此之後會養成寫博客的好習慣,保持記錄。
今天記錄一下以前閱讀過的源碼:Peer節點背書提案過程。git
首先定位到core/endorser/endorser.go
這個文件中的ProcessProposal()
方法在第450行。其實對於Peer節點背書提案的起點,並非從源碼中找到的,參考了這裏,有興趣的能夠看一下,接下來就從ProcessProposal()
這裏開始分析:github
#該方法須要傳入的參數有context(我理解爲提案的上下文),以及已經簽名的Proposal func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) { #首先獲取Peer節點處理提案開始的時間 startTime := time.Now() #Peer節點接收到的提案數+1 e.Metrics.ProposalsReceived.Add(1) #從上下文中獲取發起提案的地址 addr := util.ExtractRemoteAddress(ctx) //日誌輸出 endorserLogger.Debug("Entering: request from", addr) #這個不是鏈碼ID,是通道ID var chainID string var hdrExt *pb.ChaincodeHeaderExtension var success bool #這個會在方法結束的時候調用 defer func() { #判斷chaincodeHeaderExtension是否爲空,若是爲空的話提案驗證失敗 if hdrExt != nil { meterLabels := []string{ "channel", chainID, "chaincode", hdrExt.ChaincodeId.Name + ":" + hdrExt.ChaincodeId.Version, "success", strconv.FormatBool(success), } e.Metrics.ProposalDuration.With(meterLabels...).Observe(time.Since(startTime).Seconds()) } endorserLogger.Debug("Exit: request from", addr) }() #到了第一個重要的方法,對已簽名的提案進行預處理,點進行看一下 vr, err := e.preProcess(signedProp)
preProcess()
preProcess()
這個方法在文件中的第366行:json
func (e *Endorser) preProcess(signedProp *pb.SignedProposal) (*validateResult, error) { #定義一個驗證結果結構體 vr := &validateResult{} #首先對MSG進行驗證是否有效,看一下這個方法 prop, hdr, hdrExt, err := validation.ValidateProposalMessage(signedProp) if err != nil { #若是報錯的話,ProposalVaildationFailed+1 e.Metrics.ProposalValidationFailed.Add(1) #返回500 vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}} return vr, err }
ValidateProposalMessage()
在core/common/validation/msgvalidation.go
文件中,第75行,看一下這個方法主要就是對消息進行驗證。數組
#把主要的代碼列舉一下 #從提案中獲取Proposal內容 ... prop, err := utils.GetProposal(signedProp.ProposalBytes) ... #從Proposal中獲取Header hdr, err := utils.GetHeader(prop.Header) #對Header進行驗證 chdr, shdr, err := validateCommonHeader(hdr)
這裏的Proposal以及Header結構體:數據結構
Proposal: type Proposal struct { #關鍵的是前兩個 提案的Header與提案的有效載荷 Header []byte `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` Extension []byte `protobuf:"bytes,3,opt,name=extension,proto3" json:"extension,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } Header: type Header struct { #在提案的Header中又包含通道的Header與簽名域的Header ChannelHeader []byte `protobuf:"bytes,1,opt,name=channel_header,json=channelHeader,proto3" json:"channel_header,omitempty"` SignatureHeader []byte `protobuf:"bytes,2,opt,name=signature_header,json=signatureHeader,proto3" json:"signature_header,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` }
看一下具體的驗證方法,在第246行,依舊只列出主流程代碼:多線程
#從提案的Header中獲取通道Header信息 chdr, err := utils.UnmarshalChannelHeader(hdr.ChannelHeader)
通道Header的結構體定義在protos/common/common.pb.go
文件中第320行:app
type ChannelHeader struct { #類型 Type int32 `protobuf:"varint,1,opt,name=type,proto3" json:"type,omitempty"` #版本 Version int32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` #時間戳 Timestamp *timestamp.Timestamp `protobuf:"bytes,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` #通道ID ChannelId string `protobuf:"bytes,4,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` #交易ID TxId string `protobuf:"bytes,5,opt,name=tx_id,json=txId,proto3" json:"tx_id,omitempty"` #該Header產生的時間 Epoch uint64 `protobuf:"varint,6,opt,name=epoch,proto3" json:"epoch,omitempty"` #額外的信息 Extension []byte `protobuf:"bytes,7,opt,name=extension,proto3" json:"extension,omitempty"` TlsCertHash []byte `protobuf:"bytes,8,opt,name=tls_cert_hash,json=tlsCertHash,proto3" json:"tls_cert_hash,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` }
仍是validateCommonHeader()
這個方法:ide
#獲取簽名域的Header shdr, err := utils.GetSignatureHeader(hdr.SignatureHeader)
SignatureHeader定義在protos/common/common.pb.go
文件中第434行:學習
type SignatureHeader struct { #消息的建立者 Creator []byte `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` #這個是爲了防止重複攻擊,具備惟一性 Nonce []byte `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` }
獲取到channelHeader
與SingatureHeader
以後,能夠對它們進行驗證操做了:ui
#驗證channelHeader err = validateChannelHeader(chdr)
該方法在core/common/validation/msgvalidation.go
文件中第214行:
#首先檢查channelHeader是否爲空 if cHdr == nil { return errors.New("nil ChannelHeader provided") } ... #而後對HeaderType進行檢查,只有HeaderType是ENDORSER_TRANSACTION、CONFIG_UPDATE、CONFIG、TOKEN_TRANSACTION中其中一種纔是有效的Header if common.HeaderType(cHdr.Type) != common.HeaderType_ENDORSER_TRANSACTION && common.HeaderType(cHdr.Type) != common.HeaderType_CONFIG_UPDATE && common.HeaderType(cHdr.Type) != common.HeaderType_CONFIG && common.HeaderType(cHdr.Type) != common.HeaderType_TOKEN_TRANSACTION { return errors.Errorf("invalid header type %s", common.HeaderType(cHdr.Type)) } ... #最後檢查ChannelHeader中的Epoch是否爲0 if cHdr.Epoch != 0 { return errors.Errorf("invalid Epoch in ChannelHeader. Expected 0, got [%d]", cHdr.Epoch) }
驗證SignatureHeader
,該方法core/common/validation/msgvalidation.go
文件中194行:
#首先驗證Header是否爲空 if sHdr == nil { return errors.New("nil SignatureHeader provided") } #Nonce是否爲空 if sHdr.Nonce == nil || len(sHdr.Nonce) == 0 { return errors.New("invalid nonce specified in the header") } #該Header建立者是否爲空 if sHdr.Creator == nil || len(sHdr.Creator) == 0 { return errors.New("invalid creator specified in the header") }
因此對ChannelHeader
的檢查主要是這三部分:
ChannelHeader
是否爲空HeaderType
是不是ENDORSER_TRANSACTION、CONFIG_UPDATE、CONFIG、TOKEN_TRANSACTION
中其中一種Epoch
是否爲空對SignatureHeader
的檢查爲:
SignatureHeader
是否爲空Nonce
是否爲空SignatureHeader
的建立者是否爲空在對ChannelHeader
與SignatureHeader
的驗證完成後,回到ValidateProposalMessage
方法:
#接下來是對Creator的Signature進行驗證: err = checkSignatureFromCreator(shdr.Creator, signedProp.Signature, signedProp.ProposalBytes, chdr.ChannelId)
點進行,該方法在core/common/validation/msgvalidation.go
文件中第153行:
#首先檢查是否有空參數 if creatorBytes == nil || sig == nil || msg == nil { return errors.New("nil arguments") } #根據通道Id獲取Identity返回mspObj(member service providere)對象 mspObj := mspmgmt.GetIdentityDeserializer(ChainID) if mspObj == nil { return errors.Errorf("could not get msp for channel [%s]", ChainID) } #而後對Creator的identity進行查找 creator, err := mspObj.DeserializeIdentity(creatorBytes) if err != nil { return errors.WithMessage(err, "MSP error") } ... #對證書進行驗證 err = creator.Validate() ... #對簽名進行驗證 err = creator.Verify(msg, sig)
最後看一下checkSignatureFromCreator
作了哪些工做:
Creator、Signature、ProposalBytes
是否有空參數ChannelId
獲取Identity
Identity
查找Creator
的Identity
Creator
的證書與簽名回到ValidateProposalMessage
方法,再向下看:
if err != nil { #當以前一步驗證失敗後進入這裏。 #這一部分作了兩件事 #1.將虛假的用戶記錄到Peer節點,防止該用戶對通道進行掃描 putilsLogger.Warningf("channel [%s]: %s", chdr.ChannelId, err) sId := &msp.SerializedIdentity{} err := proto.Unmarshal(shdr.Creator, sId) if err != nil { err = errors.Wrap(err, "could not deserialize a SerializedIdentity") putilsLogger.Warningf("channel [%s]: %s", chdr.ChannelId, err) } #2.將錯誤信息返回,這一條信息應該見過好屢次 return nil, nil, nil, errors.Errorf("access denied: channel [%s] creator org [%s]", chdr.ChannelId, sId.Mspid) } #這一步用於檢查TxId是否已經存在,防止重複攻擊 err = utils.CheckTxID( chdr.TxId, shdr.Nonce, shdr.Creator) if err != nil { return nil, nil, nil, err } #方法的最後了,判斷Header的類型 switch common.HeaderType(chdr.Type) { #從這裏能夠看到,不論Header類型爲CONFIG,仍是ENDORSER_TRANSACTION都會進入下面的validateChaincodeProposalMessage方法,若是Header類型不是以上兩種,返回不支持的proposal類型 case common.HeaderType_CONFIG: fallthrough case common.HeaderType_ENDORSER_TRANSACTION: chaincodeHdrExt, err := validateChaincodeProposalMessage(prop, hdr) if err != nil { return nil, nil, nil, err } return prop, hdr, chaincodeHdrExt, err default: return nil, nil, nil, errors.Errorf("unsupported proposal type %d", common.HeaderType(chdr.Type)) }
看一下validateChaincodeProposalMessage
方法,在core/common/validation/msgvalidation.go
中第36行:
#驗證proposal header是否爲空 if prop == nil || hdr == nil { return nil, errors.New("nil arguments") } ... #一些擴展信息,再也不解釋 chaincodeHdrExt, err := utils.GetChaincodeHeaderExtension(hdr) if err != nil { return nil, errors.New("invalid header extension for type CHAINCODE") } #鏈碼Id是否爲空 if chaincodeHdrExt.ChaincodeId == nil { return nil, errors.New("ChaincodeHeaderExtension.ChaincodeId is nil") } ... #有效載荷是否爲空 if chaincodeHdrExt.PayloadVisibility != nil { return nil, errors.New("invalid payload visibility field") }
若是沒有問題的話ValidateProposalMessage()
方法就結束了,回到preProcess()
方法中接着往下:
... #獲取通道頭信息 chdr, err := putils.UnmarshalChannelHeader(hdr.ChannelHeader) ... #獲取簽名頭信息 shdr, err := putils.GetSignatureHeader(hdr.SignatureHeader) ... 判斷是否調用的是不可被外部調用的系統鏈碼 if e.s.IsSysCCAndNotInvokableExternal(hdrExt.ChaincodeId.Name) { ... return vr, err } ... #判斷通道Id是否爲空 if chainID != "" { ... #通道ID不爲空則查找該TxID是否已經存在 if _, err = e.s.GetTransactionByID(chainID, txid); err == nil { ... } #判斷是否爲系統鏈碼 if !e.s.IsSysCC(hdrExt.ChaincodeId.Name) { #若是不是系統鏈碼,則檢查ACL(訪問權限) if err = e.s.CheckACL(signedProp, chdr, shdr, hdrExt); err != nil { ... return vr, err } } }else{ #若是通道ID爲空的話什麼也不作 } vr.prop, vr.hdrExt, vr.chainID, vr.txid = prop, hdrExt, chainID, txid return vr, nil
總結一下preProcess()
方法所作的工做:
Proposal
與Header
。Header
中獲取ChannelHeader
和SignatureHeader
。ChannelHeader
和SignatureHeader
進行驗證。
ChannelHeader
:
ChannelHeader
是否爲空。HeaderType
類型是否爲ENDORSER_TRANSACTION、CONFIG_UPDATE、CONFIG、TOKEN_TRANSACTION
中其中一種。Epoch
是否爲空。SignatureHeader
:
SignatureHeader
是否爲空。Nonce
是否爲空。SignatureHeader
的建立者是否爲空。Creator、Signature、ProposalBytes
是否爲空。Identity
。Identity
中查找Creator
的證書等信息。Creator
的證書和簽名信息。ChannelHeader
,SignatureHeader
。到這裏,預處理提案過程已經完成,回到ProcessProposal()
這個主方法,接着往下:
if err != nil { resp := vr.resp return resp, err } prop, hdrExt, chainID, txid := vr.prop, vr.hdrExt, vr.chainID, vr.txid #這裏定義了一個Tx模擬器,用於後面的模擬交易過程,若是通道Id爲空,那麼TxSimulator也是空 var txsim ledger.TxSimulator #定義一個歷史記錄查詢器 var historyQueryExecutor ledger.HistoryQueryExecutor #這裏判斷是否須要Tx模擬 if acquireTxSimulator(chainID, vr.hdrExt.ChaincodeId) { #若是須要進行模擬的話,根據通道ID獲取Tx模擬器 if txsim, err = e.s.GetTxSimulator(chainID, txid); err != nil { return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, nil } #等待Tx模擬完成,最後執行 defer txsim.Done() #獲取歷史記錄查詢器 if historyQueryExecutor, err = e.s.GetHistoryQueryExecutor(chainID); err != nil { return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, nil } }
看一下acquireTxSimulator()
方法,怎麼判斷是否須要進行TX模擬的:
func acquireTxSimulator(chainID string, ccid *pb.ChaincodeID) bool { #若是通道ID爲空,就說明不須要進行Tx的模擬 if chainID == "" { return false } #通道ID不爲空,則判斷鏈碼的類型,若是是qscc(查詢系統鏈碼),cscc(配置系統鏈碼),則不須要進行Tx模擬 switch ccid.Name { case "qscc", "cscc": return false default: return true } }
回到ProcessProposal()
方法中,接下來到了第二個重要的方法了:
#首先定義一個交易參數結構體,用於下面的方法,裏面的字段以前都有說過,這裏再也不解釋 txParams := &ccprovider.TransactionParams{ ChannelID: chainID, TxID: txid, SignedProp: signedProp, Proposal: prop, TXSimulator: txsim, HistoryQueryExecutor: historyQueryExecutor, } #這一行代碼就是對交易進行模擬,點進去看一下 cd, res, simulationResult, ccevent, err := e.SimulateProposal(txParams, hdrExt.ChaincodeId)
SimulateProposal()
該方法主要是Peer節點模擬提案過程,可是不會寫入到區塊中,當Peer節點模擬完一項提案,將模擬結果保存至讀寫集。看一下SimulateProposal()
中的具體執行流程,在core/endorser/endorser.go
文件中第216行:
func (e *Endorser) SimulateProposal(txParams *ccprovider.TransactionParams, cid *pb.ChaincodeID) (ccprovider.ChaincodeDefinition, *pb.Response, []byte, *pb.ChaincodeEvent, error) #該方法傳入的參數有TransactionParams、ChaincodeID,返回的參數有ChaincodeDefinition,Response,ChaincodeEvent,error #TransactionParams以前有提到,ChaincodeID用於肯定所調用的鏈碼,ChaincodeDefinition是鏈碼標準數據結構,Response是鏈碼的響應信息,以及鏈碼事件. type ChaincodeDefinition interface { #鏈碼名稱 CCName() string #返回的鏈碼的HASH值 Hash() []byte #鏈碼的版本 CCVersion() string #返回的是驗證鏈碼上提案的方式,一般是vscc Validation() (string, []byte) #返回的是背書鏈碼上提案的方式,一般是escc Endorsement() string }
看一下方法中的內容:
#首先獲取鏈碼調用的細節 cis, err := putils.GetChaincodeInvocationSpec(txParams.Proposal)
GetChaincodeInvocationSpec()
方法在protos/utils/proputils.go
文件中第25行:
func GetChaincodeInvocationSpec(prop *peer.Proposal) (*peer.ChaincodeInvocationSpec, error) { ... #僅僅調用了獲取Header的方法,並無去獲取Header,至關於對Header進行驗證 _, err := GetHeader(prop.Header) if err != nil { return nil, err } #從鏈碼提案中獲取有效載荷 ccPropPayload, err := GetChaincodeProposalPayload(prop.Payload) if err != nil { return nil, err } #定義一個ChaincodeInvocationSpec結構,該結構體包含鏈碼的功能與參數,在這裏至關於將提案中所調用的鏈碼功能與參數封裝成一個ChaincodeInvocationSpec結構。 cis := &peer.ChaincodeInvocationSpec{} err = proto.Unmarshal(ccPropPayload.Input, cis) #最後將其返回 return cis, errors.Wrap(err, "error unmarshaling ChaincodeInvocationSpec") }
繼續往下看,緊接着定義了一個ChaincodeDefinition
,和一個保存版本信息的字符串:
var cdLedger ccprovider.ChaincodeDefinition var version string
這裏有一個分支,判斷是不是調用的系統鏈碼:
if !e.s.IsSysCC(cid.Name) { #若是不是系統鏈碼,首先獲取鏈碼的標準數據結構 cdLedger, err = e.s.GetChaincodeDefinition(cid.Name, txParams.TXSimulator) if err != nil { return nil, nil, nil, nil, errors.WithMessage(err, fmt.Sprintf("make sure the chaincode %s has been successfully instantiated and try again", cid.Name)) } #獲取用戶鏈碼版本 version = cdLedger.CCVersion() #檢查鏈碼實例化策略 err = e.s.CheckInstantiationPolicy(cid.Name, version, cdLedger) if err != nil { return nil, nil, nil, nil, err } } else { #若是調用的是系統鏈碼,僅僅獲取系統鏈碼的版本 version = util.GetSysCCVersion() }
到這裏,模擬提案的準備工做已經完成,還定義了一些字段:
#定義一個Tx模擬結果集 var simResult *ledger.TxSimulationResults #一個byte數組,保存public的模擬響應結果 var pubSimResBytes []byte #響應信息 var res *pb.Response #鏈碼事件 var ccevent *pb.ChaincodeEvent type TxSimulationResults struct { #能夠看到Tx模擬結果集裏面保存公共的與私有的讀寫集 PubSimulationResults *rwset.TxReadWriteSet PvtSimulationResults *rwset.TxPvtReadWriteSet } #鏈碼事件結構體 type ChaincodeEvent struct { #鏈碼Id ChaincodeId string `protobuf:"bytes,1,opt,name=chaincode_id,json=chaincodeId,proto3" json:"chaincode_id,omitempty"` #交易Id TxId string `protobuf:"bytes,2,opt,name=tx_id,json=txId,proto3" json:"tx_id,omitempty"` #事件名稱 EventName string `protobuf:"bytes,3,opt,name=event_name,json=eventName,proto3" json:"event_name,omitempty"` #有效載荷 Payload []byte `protobuf:"bytes,4,opt,name=payload,proto3" json:"payload,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` }
到這裏,就開始執行鏈碼進行模擬了:
res, ccevent, err = e.callChaincode(txParams, version, cis.ChaincodeSpec.Input, cid)
callChaincode()
又是一個重要的方法,調用具體的鏈碼(包括系統鏈碼與用戶鏈碼),進去看一下執行邏輯,該方法在第133行:
func (e *Endorser) callChaincode(txParams *ccprovider.TransactionParams, version string, input *pb.ChaincodeInput, cid *pb.ChaincodeID) (*pb.Response, *pb.ChaincodeEvent, error) { ... #看名字應該是記錄鏈碼執行時間的 defer func(start time.Time) { logger := endorserLogger.WithOptions(zap.AddCallerSkip(1)) elapsedMilliseconds := time.Since(start).Round(time.Millisecond) / time.Millisecond logger.Infof("[%s][%s] Exit chaincode: %s (%dms)", txParams.ChannelID, shorttxid(txParams.TxID), cid, elapsedMilliseconds) }(time.Now()) #定義了一些字段 var err error var res *pb.Response var ccevent *pb.ChaincodeEvent #執行鏈碼,若是是用戶鏈碼具體怎麼執行的要看用戶寫的鏈碼邏輯,執行完畢後返回響應信息與鏈碼事件 res, ccevent, err = e.s.Execute(txParams, txParams.ChannelID, cid.Name, version, txParams.TxID, txParams.SignedProp, txParams.Proposal, input) #這裏說明一下,狀態常量一共有三個:OK = 200 ERRORTHRESHOLD = 400 ERROR = 500 大於等於400就是錯誤信息或者被背書節點拒絕。 if res.Status >= shim.ERRORTHRESHOLD { return res, nil, nil }
再往下看,一個if語句,判斷調用的鏈碼是否爲lscc,若是是lscc判斷傳入的參數是否大於等於3,而且調用的方法是否爲deploy或者upgrade,若是是用戶鏈碼到這是方法就結束了。
if cid.Name == "lscc" && len(input.Args) >= 3 && (string(input.Args[0]) == "deploy" || string(input.Args[0]) == "upgrade") { #獲取鏈碼部署的基本結構,deploy與upgrade都須要對鏈碼進行部署 userCDS, err := putils.GetChaincodeDeploymentSpec(input.Args[2], e.PlatformRegistry) ... #這一行代碼沒有搞清楚啥意思 cds, err = e.SanitizeUserCDS(userCDS) if err != nil { return nil, nil, err } ... #執行鏈碼的Init,具體如何執行的這裏就再也不看了,否則內容更多了 _, _, err = e.s.ExecuteLegacyInit(txParams, txParams.ChannelID, cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version, txParams.TxID, txParams.SignedProp, txParams.Proposal, cds) ... }
callChaincode()
方法到這裏結束了鏈碼的調用執行也完成了,返回響應消息與鏈碼事件,回到SimulateProposal()
:
... 若是TXSimulator不爲空,說明大部分是有帳本有關的操做 if txParams.TXSimulator != nil { #GetTxSimulationResults()獲取Tx模擬結果集 if simResult, err = txParams.TXSimulator.GetTxSimulationResults(); err != nil { txParams.TXSimulator.Done() return nil, nil, nil, nil, err } #以前提到Tx模擬結果集中不只僅只有公共讀寫集,還有私有的讀寫集,接下來判斷私有的讀寫集是否爲空: if simResult.PvtSimulationResults != nil { #判斷鏈碼Id是否爲lscc if cid.Name == "lscc" { 若是爲生命週期系統鏈碼,返回錯誤信息 txParams.TXSimulator.Done() #私有數據禁止用於實例化操做 return nil, nil, nil, nil, errors.New("Private data is forbidden to be used in instantiate") } #好像與配置有關,沒有看明白 pvtDataWithConfig, err := e.AssemblePvtRWSet(simResult.PvtSimulationResults, txParams.TXSimulator) #讀取配置信息須要在更新配置信息釋放鎖以前,等待執行完成 txParams.TXSimulator.Done() ... #獲取帳本的高度 endorsedAt, err := e.s.GetLedgerHeight(txParams.ChannelID) pvtDataWithConfig.EndorsedAt = endorsedAt #應該是更新數據了,可能理解的不對 if err := e.distributePrivateData(txParams.ChannelID, txParams.TxID, pvtDataWithConfig, endorsedAt); err != nil { return nil, nil, nil, nil, err } } txParams.TXSimulator.Done() #獲取公共模擬數據 if pubSimResBytes, err = simResult.GetPubSimulationBytes(); err != nil { return nil, nil, nil, nil, err } } #最後返回 return cdLedger, res, pubSimResBytes, ccevent, nil
到這裏提案的模擬完成了,下一步就是背書過程了,感受整個流程仍是挺長的,先回到主方法,繼續往下走:
cd, res, simulationResult, ccevent, err := e.SimulateProposal(txParams, hdrExt.ChaincodeId) if err != nil { return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, nil } //若是響應不爲空 if res != nil { //若是狀態大於等於ERROR,就是發生錯誤以後的邏輯,這裏再也不說了 if res.Status >= shim.ERROR { ... return pResp, nil } } #定義一個提案響應字段 var pResp *pb.ProposalResponse if chainID == "" { #若是通道ID爲空就直接返回了 pResp = &pb.ProposalResponse{Response: res} } else { #通道Id不爲空,開始進行背書操做了,這是到了第三個重要的方法 pResp, err = e.endorseProposal(ctx, chainID, txid, signedProp, prop, res, simulationResult, ccevent, hdrExt.PayloadVisibility, hdrExt.ChaincodeId, txsim, cd) #先把下面的說無缺了,整個流程立刻就結束了 #背書完成後定義一個標籤,保存通道與鏈碼信息 meterLabels := []string{ "channel", chainID, "chaincode", hdrExt.ChaincodeId.Name + ":" + hdrExt.ChaincodeId.Version, } #簡單來講,這裏就是發生ERROR以後的處理,再也不細看 if err != nil { ... } if pResp.Response.Status >= shim.ERRORTHRESHOLD { ... return pResp, nil } } pResp.Response = res #提案成功的數量+1 e.Metrics.SuccessfulProposals.Add(1) success = true #返回提案的響應信息 return pResp, nil } #到這裏整個提案的處理流程就結束了,最後再看一下背書流程
endorseProposal()
該方法主要就是完成Peer節點對提案的背書操做,代碼在309行:
func (e *Endorser) endorseProposal(_ context.Context, chainID string, txid string, signedProp *pb.SignedProposal, proposal *pb.Proposal, response *pb.Response, simRes []byte, event *pb.ChaincodeEvent, visibility []byte, ccid *pb.ChaincodeID, txsim ledger.TxSimulator, cd ccprovider.ChaincodeDefinition) (*pb.ProposalResponse, error)
傳入的參數比較多,分析一下:
Context
這個參數從ProcessProposal()
主方法傳入進來,應該是上下文的意思。chainID
:通道Idtxid
:交易IDSignedProposal
:簽名過的提案proposal
:提案response
:以前返回的響應消息simRes
:模擬結果集event
:鏈碼事件visibility
:這個還沒搞清楚ccid
:鏈碼Idtxsim
:交易模擬器ChaincodeDefinition
:鏈碼標準數據結構,就是調用的鏈碼功能和參數等信息... func (e *Endorser) endorseProposal(#後面參數省略)(*pb.ProposalResponse, error){ var escc string #判斷是不是系統鏈碼 if isSysCC { #若是是系統鏈碼,則使用escc進行背書 escc = "escc" } else { #看官方解釋這個好像也是返回escc escc = cd.Endorsement() } ... var err error var eventBytes []byte #若是鏈碼事件不爲空 if event != nil { #獲取鏈碼事件 eventBytes, err = putils.GetBytesChaincodeEvent(event) if err != nil { return nil, errors.Wrap(err, "failed to marshal event bytes") } } if isSysCC { #獲取系統鏈碼版本 ccid.Version = util.GetSysCCVersion() } else { #獲取用戶鏈碼版本 ccid.Version = cd.CCVersion() } #以前一直沒解釋的上下文到這裏就比較清楚了 ctx := Context{ PluginName: escc, Channel: chainID, SignedProposal: signedProp, ChaincodeID: ccid, Event: eventBytes, SimRes: simRes, Response: response, Visibility: visibility, Proposal: proposal, TxID: txid, } #這個就是背書了,看一下這個方法 return e.s.EndorseWithPlugin(ctx) }
這個方法在core/endorser/plugin_endorser.go
中第162行:
func (pe *PluginEndorser) EndorseWithPlugin(ctx Context) (*pb.ProposalResponse, error) { ... #Plugin是插件的意思,不知道在這裏怎麼解釋更合理一些,建立或者獲取插件? plugin, err := pe.getOrCreatePlugin(PluginName(ctx.PluginName), ctx.Channel) ... #從上下文中獲取提案byte數據 prpBytes, err := proposalResponsePayloadFromContext(ctx) ... #背書操做,在core/endorser/mocks/plugin.go文件中,就是調用了Plugin中的背書方法,沒啥解釋的,方法在core/endorser/mocks/plugin.go中 endorsement, prpBytes, err := plugin.Endorse(prpBytes, ctx.SignedProposal) ... #背書完成後,封裝爲提案響應結構體,最後將該結構體返回 resp := &pb.ProposalResponse{ Version: 1, Endorsement: endorsement, Payload: prpBytes, Response: ctx.Response, } ... return resp, nil } #Plugin中共有兩個方法 type Plugin interface { #背書 Endorse(payload []byte, sp *peer.SignedProposal) (*peer.Endorsement, []byte, error) #初始化 Init(dependencies ...Dependency) error }
上面的兩個方法看一下:第一個getOrCreatePlugin()
在第202行:
#根據給予的插件名與通道返回一個插件實例 func (pe *PluginEndorser) getOrCreatePlugin(plugin PluginName, channel string) (endorsement.Plugin, error) { #獲取插件工廠 pluginFactory := pe.PluginFactoryByName(plugin) if pluginFactory == nil { return nil, errors.Errorf("plugin with name %s wasn't found", plugin) } #這個就是獲取或建立一個通道映射,意思就是若是有就直接獲取,沒有就先建立再獲取。裏面就再也不解釋了,都是一些基本的操做。傳入了插件的名稱與插件工廠,返回了pluginsByChannel,結構體在下面 pluginsByChannel := pe.getOrCreatePluginChannelMapping(PluginName(plugin), pluginFactory) #根據通道建立插件,看一下這個方法 return pluginsByChannel.createPluginIfAbsent(channel) } type PluginName string #看結構體中內容 type pluginsByChannel struct { #讀寫鎖 sync.RWMutex #插件工廠 pluginFactory endorsement.PluginFactory #map集合,包含全部的Plugin channels2Plugins map[string]endorsement.Plugin #背書插件 pe *PluginEndorser }
createPluginIfAbsent()
這個方法在第103行:
func (pbc *pluginsByChannel) createPluginIfAbsent(channel string) (endorsement.Plugin, error) { #首先就是獲取一個讀鎖 pbc.RLock() #根據數組下標找須要的插件 plugin, exists := pbc.channels2Plugins[channel] #釋放讀鎖 pbc.RUnlock() #若是找到的話直接返回 if exists { return plugin, nil } #到這裏說明沒有找到,代表插件不存在,此次獲取鎖,這是與上面的鎖不一樣 pbc.Lock() #表示最後才釋放鎖 defer pbc.Unlock() #再進行一次查找,多線程下說不定有其餘線程剛剛建立了呢 plugin, exists = pbc.channels2Plugins[channel] #若是查找到的話釋放鎖後直接返回 if exists { return plugin, nil } #到這裏說明真的沒有該插件,使用插件工廠New一個 pluginInstance := pbc.pluginFactory.New() #進行初始化操做 plugin, err := pbc.initPlugin(pluginInstance, channel) if err != nil { return nil, err } #添加到數組裏,下次再查找該插件的時候就存在了 pbc.channels2Plugins[channel] = plugin #最後釋放鎖後返回 return plugin, nil }
看一下initPlugin()
方法是怎麼進行初始化的,在第127行:
func (pbc *pluginsByChannel) initPlugin(plugin endorsement.Plugin, channel string) (endorsement.Plugin, error) { var dependencies []endorsement.Dependency var err error if channel != "" { #根據給予的通道信息建立一個用於查詢的Creator query, err := pbc.pe.NewQueryCreator(channel) ... #根據給予的通道信息獲取狀態數據,也就是當前帳本中最新狀態 store := pbc.pe.TransientStoreRetriever.StoreForChannel(channel) ... #添加進數組中 dependencies = append(dependencies, &ChannelState{QueryCreator: query, Store: store}) } dependencies = append(dependencies, pbc.pe.SigningIdentityFetcher) #Plugin的初始化方法在這裏被調用 err = plugin.Init(dependencies...) ... return plugin, nil }
Plugin
這裏建立完後就開始進行背書操做了,背書完成後返回響應信息,整個流程就到這裏結束了。
最後總結一下總體的流程好了:
preProcess()
Header
信息SimulateProposal()
callChaincode()
方法進行模擬。endorseProposal()
整個過程仍是比較長的,不過還算比較清晰,下一篇文章分析一下Peer節點的啓動過程好了。