以前從來沒有寫過博客,從這段時間開始才開始寫一些自己的博客,之前總覺得寫一篇博客要耗費大量的時間,而且寫的還是自己已經學會的,覺得沒什么必要。但是當開始用博客記錄下來的時候,才發現有些學會的地方只是自己覺得已經學會了,還是有太多地方需要學習,眼高手低了,所以以后會養成寫博客的好習慣,保持記錄。
今天記錄一下之前閱讀過的源碼:Peer節點背書提案過程。
1 起點
首先定位到core/endorser/endorser.go
這個文件中的ProcessProposal()
方法在第450行。其實對於Peer節點背書提案的起點,並不是從源碼中找到的,參考了這里,有興趣的可以看一下,接下來就從ProcessProposal()
這里開始分析:
#該方法需要傳入的參數有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)
2 preProcess()
preProcess()
這個方法在文件中的第366行:
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行:
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()
這個方法:
#獲取簽名域的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
之后,可以對它們進行驗證操作了:
#驗證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
是否為空。 - 根據通道Id獲取
Identity
。 - 從
Identity
中查找Creator
的證書等信息。 - 驗證
Creator
的證書和簽名信息。 - 檢查該TxID是否已經存在,防止重復攻擊。
- 驗證鏈碼提案消息。
- 獲取
ChannelHeader
,SignatureHeader
。 - 判斷是否調用的是不允許被外部調用的系統鏈碼。
- 判斷通道ID是否為空,如果為空則什么也不做直接返回。
- 通道ID不為空則檢查該TxID是否已經存在,防止重復攻擊。
- 判斷是否為系統鏈碼,如果不是系統鏈碼則檢查提案中的權限。
到這里,預處理提案過程已經完成,回到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)
3 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)
4 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
}
#到這里整個提案的處理流程就結束了,最后再看一下背書流程
5 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()
- 獲取調用的鏈碼的具體功能與參數。
- 判斷鏈碼類型,用戶鏈碼需要檢查實例化策略,系統鏈碼只獲取版本信息。
- 創建Tx模擬器,調用
callChaincode()
方法進行模擬。 - 記錄模擬時間,執行鏈碼,判斷是否調用的是lscc,功能為upgrade或者為deploy。如果是的話進行鏈碼的Init。
- 對模擬完成的賬本進行快照,返回模擬結果集。
- 最后進行背書操作
endorseProposal()
- 獲取進行背書操作的鏈碼
- 獲取鏈碼事件與鏈碼版本信息
- 獲取背書所需要的插件,獲取調用鏈碼的相關數據
- 通過獲取的插件進行背書操作
- 返回背書響應
6小結
整個過程還是比較長的,不過還算比較清晰,下一篇文章分析一下Peer節點的啟動過程好了。