environment:
fabric v1.4.2
1. 概述
Endorser節(jié)點是peer節(jié)點所扮演的一種角色褂痰,在peer啟動時會創(chuàng)建Endorser背書服務器亩进,并注冊到本地gRPC服務器(7051端口)上對外提供服務,對請求的簽名提案消息執(zhí)行啟動鏈碼容器缩歪、模擬執(zhí)行鏈碼归薛、背書簽名等流程。所有客戶端提交到賬本的調(diào)用交易都需要背書節(jié)點背書匪蝙,當客戶端收集到足夠的背書信息之后抓狭,再將簽名提案消息聪建、模擬執(zhí)行的結(jié)果以及背書信息打包成交易信息發(fā)給orderer節(jié)點排序出塊
背書者Endorser在一個交易流中充當?shù)淖饔萌缦拢?/p>
- 客戶端發(fā)送一個背書申請(SignedProposal)到Endorser送挑。
- Endorser對申請進行背書屯远,發(fā)送一個申請應答(ProposalResponse)到客戶端。
- 客戶端將申請應答中的背書組裝到一個交易請求(SignedTransaction)中颤绕。
2. 背書服務初始化
定位到peer/node/start.go
的serve
函數(shù)幸海,這個是peer節(jié)點的啟動初始化函數(shù),下面為關鍵的背書節(jié)點啟動語句:
serverEndorser := endorser.NewEndorserServer(privDataDist, endorserSupport, pr, metricsProvider)
...
// start the peer server
auth := authHandler.ChainFilters(serverEndorser, authFilters...)
// Register the Endorser server
// 設置完之后注冊背書服務
pb.RegisterEndorserServer(peerServer.Server(), auth)
背書服務最重要的接口為,位置為protos\peer\peer.pb.go
:
// EndorserServer is the server API for Endorser service.
type EndorserServer interface {
ProcessProposal(context.Context, *SignedProposal) (*ProposalResponse, error)
}
ProcessProposal()服務接口主要功能為接收和處理簽名提案消息(SignedProposal)奥务、啟動鏈碼容器物独、執(zhí)行調(diào)用鏈碼以及進行簽名背書。
函數(shù)定義的位置為core/endorser/endorser.go
3. 背書服務
在ProcessProposal()服務中氯葬,主要存在以下流程:
- 首先對提案進行預處理
preProcess()
- 這一步主要就是對提案中的內(nèi)容進行相關驗證操作挡篓。
- 驗證Header信息
- 驗證證書信息
- 判斷調(diào)用的鏈碼類型與通道信息。
- 然后對提案進行模擬
SimulateProposal()
- 獲取調(diào)用的鏈碼的具體功能與參數(shù)溢谤。
- 判斷鏈碼類型瞻凤,用戶鏈碼需要檢查實例化策略憨攒,系統(tǒng)鏈碼只獲取版本信息世杀。
- 創(chuàng)建Tx模擬器阀参,調(diào)用
callChaincode()
方法進行模擬。 - 記錄模擬時間瞻坝,執(zhí)行鏈碼蛛壳,判斷是否調(diào)用的是lscc,功能為upgrade或者為deploy所刀。如果是的話進行鏈碼的Init衙荐。
- 對模擬完成的賬本進行快照,返回模擬結(jié)果集浮创。
- 最后進行背書操作
endorseProposal()
- 獲取進行背書操作的鏈碼
- 獲取鏈碼事件與鏈碼版本信息
- 獲取背書所需要的插件忧吟,獲取調(diào)用鏈碼的相關數(shù)據(jù)
- 通過獲取的插件進行背書操作
- 返回背書響應
提案背書主要入口函數(shù)為ProcessProposal
,后續(xù)都是圍繞此函數(shù)分析,源碼如下:
// ProcessProposal process the Proposal
func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) {
// start time for computing elapsed time metric for successfully endorsed proposals
// 首先獲取Peer節(jié)點處理提案開始的時間
startTime := time.Now()
// Peer節(jié)點接收到的提案數(shù)+1
e.Metrics.ProposalsReceived.Add(1)
// 從上下文中獲取發(fā)起提案的地址
addr := util.ExtractRemoteAddress(ctx)
// 日志輸出
endorserLogger.Debug("Entering: request from", addr)
// variables to capture proposal duration metric
// 這個不是鏈碼ID斩披,是通道ID
var chainID string
var hdrExt *pb.ChaincodeHeaderExtension
var success bool
// 這個會在方法結(jié)束的時候調(diào)用
defer func() {
// capture proposal duration metric. hdrExt == nil indicates early failure
// where we don't capture latency metric. But the ProposalValidationFailed
// counter metric should shed light on those failures.
// 判斷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)
}()
// 0 -- check and validate
// 到了第一個重要的方法,對已簽名的提案進行預處理垦沉,點進行看一下
vr, err := e.preProcess(signedProp)
if err != nil {
resp := vr.resp
return resp, err
}
prop, hdrExt, chainID, txid := vr.prop, vr.hdrExt, vr.chainID, vr.txid
// obtaining once the tx simulator for this proposal. This will be nil
// for chainless proposals
// Also obtain a history query executor for history queries, since tx simulator does not cover history
// 這里定義了一個Tx模擬器煌抒,用于后面的模擬交易過程,如果通道Id為空,那么TxSimulator也是空
var txsim ledger.TxSimulator
// 定義一個歷史記錄查詢器
var historyQueryExecutor ledger.HistoryQueryExecutor
// 判斷是否需要Tx模擬
if acquireTxSimulator(chainID, vr.hdrExt.ChaincodeId) {
// 根據(jù)通道ID獲取Tx模擬器
if txsim, err = e.s.GetTxSimulator(chainID, txid); err != nil {
return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, nil
}
// txsim acquires a shared lock on the stateDB. As this would impact the block commits (i.e., commit
// of valid write-sets to the stateDB), we must release the lock as early as possible.
// Hence, this txsim object is closed in simulateProposal() as soon as the tx is simulated and
// rwset is collected before gossip dissemination if required for privateData. For safety, we
// add the following defer statement and is useful when an error occur. Note that calling
// txsim.Done() more than once does not cause any issue. If the txsim is already
// released, the following txsim.Done() simply returns.
defer txsim.Done()
// 獲取歷史記錄查詢器
if historyQueryExecutor, err = e.s.GetHistoryQueryExecutor(chainID); err != nil {
return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, nil
}
}
// 定義一個交易參數(shù)結(jié)構體厕倍,用于下面的方法,里面的字段之前都有說過
txParams := &ccprovider.TransactionParams{
ChannelID: chainID,
TxID: txid,
SignedProp: signedProp,
Proposal: prop,
TXSimulator: txsim,
HistoryQueryExecutor: historyQueryExecutor,
}
// this could be a request to a chainless SysCC
// TODO: if the proposal has an extension, it will be of type ChaincodeAction;
// if it's present it means that no simulation is to be performed because
// we're trying to emulate a submitting peer. On the other hand, we need
// to validate the supplied action before endorsing it
// 1 -- simulate
// 對交易進行模擬
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 {
if res.Status >= shim.ERROR {
endorserLogger.Errorf("[%s][%s] simulateProposal() resulted in chaincode %s response status %d for txid: %s", chainID, shorttxid(txid), hdrExt.ChaincodeId, res.Status, txid)
var cceventBytes []byte
if ccevent != nil {
cceventBytes, err = putils.GetBytesChaincodeEvent(ccevent)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal event bytes")
}
}
pResp, err := putils.CreateProposalResponseFailure(prop.Header, prop.Payload, res, simulationResult, cceventBytes, hdrExt.ChaincodeId, hdrExt.PayloadVisibility)
if err != nil {
return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, nil
}
return pResp, nil
}
}
// 2 -- endorse and get a marshalled ProposalResponse message
var pResp *pb.ProposalResponse
// TODO till we implement global ESCC, CSCC for system chaincodes
// chainless proposals (such as CSCC) don't have to be endorsed
if chainID == "" {
pResp = &pb.ProposalResponse{Response: res}
} else {
// Note: To endorseProposal(), we pass the released txsim. Hence, an error would occur if we try to use this txsim
// 開始背書
pResp, err = e.endorseProposal(ctx, chainID, txid, signedProp, prop, res, simulationResult, ccevent, hdrExt.PayloadVisibility, hdrExt.ChaincodeId, txsim, cd)
// if error, capture endorsement failure metric
meterLabels := []string{
"channel", chainID,
"chaincode", hdrExt.ChaincodeId.Name + ":" + hdrExt.ChaincodeId.Version,
}
if err != nil {
meterLabels = append(meterLabels, "chaincodeerror", strconv.FormatBool(false))
e.Metrics.EndorsementsFailed.With(meterLabels...).Add(1)
return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, nil
}
if pResp.Response.Status >= shim.ERRORTHRESHOLD {
// the default ESCC treats all status codes about threshold as errors and fails endorsement
// useful to track this as a separate metric
meterLabels = append(meterLabels, "chaincodeerror", strconv.FormatBool(true))
e.Metrics.EndorsementsFailed.With(meterLabels...).Add(1)
endorserLogger.Debugf("[%s][%s] endorseProposal() resulted in chaincode %s error for txid: %s", chainID, shorttxid(txid), hdrExt.ChaincodeId, txid)
return pResp, nil
}
}
// Set the proposal response payload - it
// contains the "return value" from the
// chaincode invocation
pResp.Response = res
// total failed proposals = ProposalsReceived-SuccessfulProposals
e.Metrics.SuccessfulProposals.Add(1)
success = true
return pResp, nil
}
3.1 檢查和校驗簽名提案的合法性
preProcess()
方法對簽名提案消息進行預處理寡壮,主要包括驗證消息格式和簽名的合法性、驗證提案消息對應鏈碼檢查是否是系統(tǒng)鏈碼并且不為外部調(diào)用讹弯、交易的唯一性况既、驗證是否滿足對應通道的訪問控制策略。
// preProcess checks the tx proposal headers, uniqueness and ACL
func (e *Endorser) preProcess(signedProp *pb.SignedProposal) (*validateResult, error) {
vr := &validateResult{}
// at first, we check whether the message is valid
// 驗證信息是否有效
prop, hdr, hdrExt, err := validation.ValidateProposalMessage(signedProp)
if err != nil {
e.Metrics.ProposalValidationFailed.Add(1)
vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}
return vr, err
}
// 從提案的Header中獲取通道Header信息
chdr, err := putils.UnmarshalChannelHeader(hdr.ChannelHeader)
if err != nil {
vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}
return vr, err
}
//獲取簽名域的Header
shdr, err := putils.GetSignatureHeader(hdr.SignatureHeader)
if err != nil {
vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}
return vr, err
}
// block invocations to security-sensitive system chaincodes
// 根據(jù)提案消息頭部hdrExt.ChaincodeId.Name鏈碼名檢查鏈碼是否為允許外部調(diào)用的系統(tǒng)鏈碼
if e.s.IsSysCCAndNotInvokableExternal(hdrExt.ChaincodeId.Name) {
endorserLogger.Errorf("Error: an attempt was made by %#v to invoke system chaincode %s", shdr.Creator, hdrExt.ChaincodeId.Name)
err = errors.Errorf("chaincode %s cannot be invoked through a proposal", hdrExt.ChaincodeId.Name)
vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}
return vr, err
}
chainID := chdr.ChannelId
txid := chdr.TxId
endorserLogger.Debugf("[%s][%s] processing txid: %s", chainID, shorttxid(txid), txid)
if chainID != "" {
// labels that provide context for failure metrics
meterLabels := []string{
"channel", chainID,
"chaincode", hdrExt.ChaincodeId.Name + ":" + hdrExt.ChaincodeId.Version,
}
// Here we handle uniqueness check and ACLs for proposals targeting a chain
// Notice that ValidateProposalMessage has already verified that TxID is computed properly
if _, err = e.s.GetTransactionByID(chainID, txid); err == nil {
// increment failure due to duplicate transactions. Useful for catching replay attacks in
// addition to benign retries
e.Metrics.DuplicateTxsFailure.With(meterLabels...).Add(1)
err = errors.Errorf("duplicate transaction found [%s]. Creator [%x]", txid, shdr.Creator)
vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}
return vr, err
}
// check ACL only for application chaincodes; ACLs
// for system chaincodes are checked elsewhere
if !e.s.IsSysCC(hdrExt.ChaincodeId.Name) {
// check that the proposal complies with the Channel's writers
if err = e.s.CheckACL(signedProp, chdr, shdr, hdrExt); err != nil {
e.Metrics.ProposalACLCheckFailed.With(meterLabels...).Add(1)
vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}
return vr, err
}
}
} else {
// chainless proposals do not/cannot affect ledger and cannot be submitted as transactions
// ignore uniqueness checks; also, chainless proposals are not validated using the policies
// of the chain since by definition there is no chain; they are validated against the local
// MSP of the peer instead by the call to ValidateProposalMessage above
}
vr.prop, vr.hdrExt, vr.chainID, vr.txid = prop, hdrExt, chainID, txid
return vr, nil
}
3.1.1 驗證消息格式和簽名合法性
preProcess()
調(diào)用ValidateProposalMessage()
對消息進行驗證组民,主要針對消息的格式棒仍、簽名、交易id進行驗證邪乍。
在core/common/validation/msgvalidation.go
找到ValidateProposalMessage
函數(shù)
// ValidateProposalMessage checks the validity of a SignedProposal message
// this function returns Header and ChaincodeHeaderExtension messages since they
// have been unmarshalled and validated
func ValidateProposalMessage(signedProp *pb.SignedProposal) (*pb.Proposal, *common.Header, *pb.ChaincodeHeaderExtension, error) {
if signedProp == nil {
return nil, nil, nil, errors.New("nil arguments")
}
putilsLogger.Debugf("ValidateProposalMessage starts for signed proposal %p", signedProp)
// extract the Proposal message from signedProp
// 從提案中獲取Proposal內(nèi)容
prop, err := utils.GetProposal(signedProp.ProposalBytes)
if err != nil {
return nil, nil, nil, err
}
// 1) look at the ProposalHeader
// 從Proposal中獲取Header
hdr, err := utils.GetHeader(prop.Header)
if err != nil {
return nil, nil, nil, err
}
// validate the header
// 對header進行驗證
chdr, shdr, err := validateCommonHeader(hdr)
if err != nil {
return nil, nil, nil, err
}
// validate the signature
// 驗證簽名
err = checkSignatureFromCreator(shdr.Creator, signedProp.Signature, signedProp.ProposalBytes, chdr.ChannelId)
if err != nil {
// log the exact message on the peer but return a generic error message to
// avoid malicious users scanning for channels
putilsLogger.Warningf("channel [%s]: %s", chdr.ChannelId, err)
sId := &msp.SerializedIdentity{}
err := proto.Unmarshal(shdr.Creator, sId)
if err != nil {
// log the error here as well but still only return the generic error
err = errors.Wrap(err, "could not deserialize a SerializedIdentity")
putilsLogger.Warningf("channel [%s]: %s", chdr.ChannelId, err)
}
return nil, nil, nil, errors.Errorf("access denied: channel [%s] creator org [%s]", chdr.ChannelId, sId.Mspid)
}
// Verify that the transaction ID has been computed properly.
// This check is needed to ensure that the lookup into the ledger
// for the same TxID catches duplicates.
// 對交易id進行驗證降狠,驗證交易id是否與計算的交易id一致
err = utils.CheckTxID(
chdr.TxId,
shdr.Nonce,
shdr.Creator)
if err != nil {
return nil, nil, nil, err
}
// continue the validation in a way that depends on the type specified in the header
// 根據(jù)消息類型進行分類處理
switch common.HeaderType(chdr.Type) {
case common.HeaderType_CONFIG:
//which the types are different the validation is the same
//viz, validate a proposal to a chaincode. If we need other
//special validation for confguration, we would have to implement
//special validation
fallthrough
case common.HeaderType_ENDORSER_TRANSACTION:
// validation of the proposal message knowing it's of type CHAINCODE
chaincodeHdrExt, err := validateChaincodeProposalMessage(prop, hdr)
if err != nil {
return nil, nil, nil, err
}
return prop, hdr, chaincodeHdrExt, err
default:
//NOTE : we proably need a case
return nil, nil, nil, errors.Errorf("unsupported proposal type %d", common.HeaderType(chdr.Type))
}
}
validateCommonHeader()
校驗Proposal.Header
的合法性
// checks for a valid Header
func validateCommonHeader(hdr *common.Header) (*common.ChannelHeader, *common.SignatureHeader, error) {
if hdr == nil {
return nil, nil, errors.New("nil header")
}
chdr, err := utils.UnmarshalChannelHeader(hdr.ChannelHeader)
if err != nil {
return nil, nil, err
}
shdr, err := utils.GetSignatureHeader(hdr.SignatureHeader)
if err != nil {
return nil, nil, err
}
// 校驗消息類型是否屬于HeaderType_ENDORSER_TRANSACTION、HeaderType_CONFIG_UPDATE庇楞、HeaderType_CONFIG榜配、HeaderType_TOKEN_TRANSACTION,并且校驗Epoch是否為0
err = validateChannelHeader(chdr)
if err != nil {
return nil, nil, err
}
// 校驗shdr shdr.Nonce shdr.Creator是否為nil吕晌,或長度是否為0
err = validateSignatureHeader(shdr)
if err != nil {
return nil, nil, err
}
return chdr, shdr, nil
}
checkSignatureFromCreator()
對簽名進行校驗
// given a creator, a message and a signature,
// this function returns nil if the creator
// is a valid cert and the signature is valid
func checkSignatureFromCreator(creatorBytes []byte, sig []byte, msg []byte, ChainID string) error {
putilsLogger.Debugf("begin")
// check for nil argument
if creatorBytes == nil || sig == nil || msg == nil {
return errors.New("nil arguments")
}
mspObj := mspmgmt.GetIdentityDeserializer(ChainID)
if mspObj == nil {
return errors.Errorf("could not get msp for channel [%s]", ChainID)
}
// get the identity of the creator
creator, err := mspObj.DeserializeIdentity(creatorBytes)
if err != nil {
return errors.WithMessage(err, "MSP error")
}
putilsLogger.Debugf("creator is %s", creator.GetIdentifier())
// ensure that creator is a valid certificate
err = creator.Validate()
if err != nil {
return errors.WithMessage(err, "creator certificate is not valid")
}
putilsLogger.Debugf("creator is valid")
// validate the signature
err = creator.Verify(msg, sig)
if err != nil {
return errors.WithMessage(err, "creator's signature over the proposal is not valid")
}
putilsLogger.Debugf("exits successfully")
return nil
}
3.1.2 檢查是否是系統(tǒng)鏈碼并且不為外部調(diào)用
定位到core/scc/sccproviderimpl.go
的IsSysCCAndNotInvokableExternal
函數(shù)
簡單理解就是鏈碼是否可以為外部調(diào)用
// IsSysCCAndNotInvokableExternal returns true if the chaincode
// is a system chaincode and *CANNOT* be invoked through
// a proposal to this peer
func (p *Provider) IsSysCCAndNotInvokableExternal(name string) bool {
for _, sysCC := range p.SysCCs {
if sysCC.Name() == name {
return !sysCC.InvokableExternal()
}
}
if isDeprecatedSysCC(name) {
return true
}
return false
}
func isDeprecatedSysCC(name string) bool {
return name == "vscc" || name == "escc"
}
3.1.3 檢查簽名提案消息交易id的唯一性
首先查看是否存在該賬本蛋褥,然后查看賬本是否存在該交易id。
// GetTransactionByID retrieves a transaction by id
func (s *SupportImpl) GetTransactionByID(chid, txID string) (*pb.ProcessedTransaction, error) {
lgr := s.Peer.GetLedger(chid)
if lgr == nil {
return nil, errors.Errorf("failed to look up the ledger for Channel %s", chid)
}
tx, err := lgr.GetTransactionByID(txID)
if err != nil {
return nil, errors.WithMessage(err, "GetTransactionByID failed")
}
return tx, nil
}
3.1.4 驗證是否滿足對應通道的訪問控制策略
背書節(jié)點在背書過程中會檢查是否滿足應用通道的Writers
策略
// CheckACL checks the ACL for the resource for the Channel using the
// SignedProposal from which an id can be extracted for testing against a policy
func (s *SupportImpl) CheckACL(signedProp *pb.SignedProposal, chdr *common.ChannelHeader, shdr *common.SignatureHeader, hdrext *pb.ChaincodeHeaderExtension) error {
return s.ACLProvider.CheckACL(resources.Peer_Propose, chdr.ChannelId, signedProp)
}
3.2 調(diào)用鏈碼并模擬執(zhí)行提案
首先睛驳,ProcessProposal()
方法調(diào)用方法acquireTxSimulator()
根據(jù)鏈碼判斷是否需要創(chuàng)建交易模擬器TxSimulator
烙心,如果需要則創(chuàng)建交易模擬器TxSimulator
(無法查詢歷史記錄)以及歷史記錄查詢器HistoryQueryExecutor
膜廊,接著再調(diào)用SimulateProposal()
模擬執(zhí)行交易提案消息,并返回模擬執(zhí)行結(jié)果淫茵。
其中爪瓜,鏈碼qscc、cscc不需要交易模擬器匙瘪。
// determine whether or not a transaction simulator should be
// obtained for a proposal.
func acquireTxSimulator(chainID string, ccid *pb.ChaincodeID) bool {
// 如果通道ID為空,就說明不需要進行Tx的模擬
if chainID == "" {
return false
}
// ˉ\_(ツ)_/ˉ locking.
// Don't get a simulator for the query and config system chaincode.
// These don't need the simulator and its read lock results in deadlocks.
// 通道ID不為空铆铆,則判斷鏈碼的類型,如果是qscc(查詢系統(tǒng)鏈碼),cscc(配置系統(tǒng)鏈碼)丹喻,則不需要進行Tx模擬
switch ccid.Name {
case "qscc", "cscc":
return false
default:
return true
}
}
// SimulateProposal simulates the proposal by calling the chaincode
func (e *Endorser) SimulateProposal(txParams *ccprovider.TransactionParams, cid *pb.ChaincodeID) (ccprovider.ChaincodeDefinition, *pb.Response, []byte, *pb.ChaincodeEvent, error) {
endorserLogger.Debugf("[%s][%s] Entry chaincode: %s", txParams.ChannelID, shorttxid(txParams.TxID), cid)
defer endorserLogger.Debugf("[%s][%s] Exit", txParams.ChannelID, shorttxid(txParams.TxID))
// we do expect the payload to be a ChaincodeInvocationSpec
// if we are supporting other payloads in future, this be glaringly point
// as something that should change
// 獲取鏈碼調(diào)用的細節(jié)
cis, err := putils.GetChaincodeInvocationSpec(txParams.Proposal)
if err != nil {
return nil, nil, nil, nil, err
}
var cdLedger ccprovider.ChaincodeDefinition
var version string
if !e.s.IsSysCC(cid.Name) { // 不是系統(tǒng)鏈碼
// 獲取鏈碼的標準數(shù)據(jù)結(jié)構
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 {
// 如果調(diào)用的是系統(tǒng)鏈碼薄货,僅僅獲取系統(tǒng)鏈碼的版本
version = util.GetSysCCVersion()
}
// ---3. execute the proposal and get simulation results
var simResult *ledger.TxSimulationResults // 定義一個Tx模擬結(jié)果集
var pubSimResBytes []byte // 一個byte數(shù)組,保存public的模擬響應結(jié)果
var res *pb.Response // 響應信息
var ccevent *pb.ChaincodeEvent // 鏈碼事件
// 執(zhí)行鏈碼進行模擬
res, ccevent, err = e.callChaincode(txParams, version, cis.ChaincodeSpec.Input, cid)
if err != nil {
endorserLogger.Errorf("[%s][%s] failed to invoke chaincode %s, error: %+v", txParams.ChannelID, shorttxid(txParams.TxID), cid, err)
return nil, nil, nil, nil, err
}
if txParams.TXSimulator != nil {
// GetTxSimulationResults()獲取Tx模擬結(jié)果集
if simResult, err = txParams.TXSimulator.GetTxSimulationResults(); err != nil {
txParams.TXSimulator.Done()
return nil, nil, nil, nil, err
}
// 之前提到Tx模擬結(jié)果集中不僅僅只有公共讀寫集碍论,還有私有的讀寫集,接下來判斷私有的讀寫集是否為空
if simResult.PvtSimulationResults != nil {
if cid.Name == "lscc" {
// TODO: remove once we can store collection configuration outside of 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)
// To read collection config need to read collection updates before
// releasing the lock, hence txParams.TXSimulator.Done() moved down here
txParams.TXSimulator.Done()
if err != nil {
return nil, nil, nil, nil, errors.WithMessage(err, "failed to obtain collections config")
}
endorsedAt, err := e.s.GetLedgerHeight(txParams.ChannelID)
if err != nil {
return nil, nil, nil, nil, errors.WithMessage(err, fmt.Sprint("failed to obtain ledger height for channel", txParams.ChannelID))
}
// Add ledger height at which transaction was endorsed,
// `endorsedAt` is obtained from the block storage and at times this could be 'endorsement Height + 1'.
// However, since we use this height only to select the configuration (3rd parameter in distributePrivateData) and
// manage transient store purge for orphaned private writesets (4th parameter in distributePrivateData), this works for now.
// Ideally, ledger should add support in the simulator as a first class function `GetHeight()`.
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
}
3.2.1 檢查實例化策略
core/common/ccprovider/ccprovider.go/CheckInstantiationPolicy()
會調(diào)用GetChaincodeData()
嘗試從緩存或者本地文件系統(tǒng)獲取已安裝的鏈碼包CCPackage
谅猾,再解析成ChaincodeData
對象ccdata
。再與賬本中保存的對應鏈碼的實例化策略進行比較鳍悠。
func CheckInstantiationPolicy(name, version string, cdLedger *ChaincodeData) error {
ccdata, err := GetChaincodeData(name, version)
if err != nil {
return err
}
// we have the info from the fs, check that the policy
// matches the one on the file system if one was specified;
// this check is required because the admin of this peer
// might have specified instantiation policies for their
// chaincode, for example to make sure that the chaincode
// is only instantiated on certain channels; a malicious
// peer on the other hand might have created a deploy
// transaction that attempts to bypass the instantiation
// policy. This check is there to ensure that this will not
// happen, i.e. that the peer will refuse to invoke the
// chaincode under these conditions. More info on
// https://jira.hyperledger.org/browse/FAB-3156
if ccdata.InstantiationPolicy != nil {
if !bytes.Equal(ccdata.InstantiationPolicy, cdLedger.InstantiationPolicy) {
return fmt.Errorf("Instantiation policy mismatch for cc %s/%s", name, version)
}
}
return nil
}
3.2.2 調(diào)用鏈碼
在SimulateProposal()
方法中税娜,會調(diào)用callChaincode()
方法調(diào)用鏈碼。
// call specified chaincode (system or user)
func (e *Endorser) callChaincode(txParams *ccprovider.TransactionParams, version string, input *pb.ChaincodeInput, cid *pb.ChaincodeID) (*pb.Response, *pb.ChaincodeEvent, error) {
endorserLogger.Infof("[%s][%s] Entry chaincode: %s", txParams.ChannelID, shorttxid(txParams.TxID), cid)
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
// is this a system chaincode
// 執(zhí)行鏈碼贼涩,如果是用戶鏈碼具體怎么執(zhí)行的要看用戶寫的鏈碼邏輯巧涧,執(zhí)行完畢后返回響應信息與鏈碼事件
res, ccevent, err = e.s.Execute(txParams, txParams.ChannelID, cid.Name, version, txParams.TxID, txParams.SignedProp, txParams.Proposal, input)
if err != nil {
return nil, nil, err
}
// per doc anything < 400 can be sent as TX.
// fabric errors will always be >= 400 (ie, unambiguous errors )
// "lscc" will respond with status 200 or 500 (ie, unambiguous OK or ERROR)
// 狀態(tài)常量一共有三個:OK = 200 ERRORTHRESHOLD = 400 ERROR = 500 大于等于400就是錯誤信息或者被背書節(jié)點拒絕。
if res.Status >= shim.ERRORTHRESHOLD {
return res, nil, nil
}
// ----- BEGIN - SECTION THAT MAY NEED TO BE DONE IN LSCC ------
// if this a call to deploy a chaincode, We need a mechanism
// to pass TxSimulator into LSCC. Till that is worked out this
// special code does the actual deploy, upgrade here so as to collect
// all state under one TxSimulator
//
// NOTE that if there's an error all simulation, including the chaincode
// table changes in lscc will be thrown away
// 判斷調(diào)用的鏈碼是否為lscc,如果是lscc判斷傳入的參數(shù)是否大于等于3遥倦,并且調(diào)用的方法是否為deploy或者upgrade谤绳,如果是用戶鏈碼到這是方法就結(jié)束了。
// 用戶鏈碼的實例化(deploy)和升級(upgrade)就會進來這里
if cid.Name == "lscc" && len(input.Args) >= 3 && (string(input.Args[0]) == "deploy" || string(input.Args[0]) == "upgrade") {
// 獲取鏈碼部署的基本結(jié)構,deploy與upgrade都需要對鏈碼進行部署
userCDS, err := putils.GetChaincodeDeploymentSpec(input.Args[2], e.PlatformRegistry)
if err != nil {
return nil, nil, err
}
var cds *pb.ChaincodeDeploymentSpec
cds, err = e.SanitizeUserCDS(userCDS)
if err != nil {
return nil, nil, err
}
// this should not be a system chaincode
if e.s.IsSysCC(cds.ChaincodeSpec.ChaincodeId.Name) {
return nil, nil, errors.Errorf("attempting to deploy a system chaincode %s/%s", cds.ChaincodeSpec.ChaincodeId.Name, txParams.ChannelID)
}
// 執(zhí)行鏈碼的Init,具體如何執(zhí)行的這里就不再看了,不然內(nèi)容更多了
_, _, err = e.s.ExecuteLegacyInit(txParams, txParams.ChannelID, cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version, txParams.TxID, txParams.SignedProp, txParams.Proposal, cds)
if err != nil {
// increment the failure to indicate instantion/upgrade failures
meterLabels := []string{
"channel", txParams.ChannelID,
"chaincode", cds.ChaincodeSpec.ChaincodeId.Name + ":" + cds.ChaincodeSpec.ChaincodeId.Version,
}
e.Metrics.InitFailed.With(meterLabels...).Add(1)
return nil, nil, err
}
}
// ----- END -------
return res, ccevent, err
}
執(zhí)行Execute()
方法調(diào)用鏈碼袒哥,然后在針對deploy
和upgrade
操作進行處理缩筛。
首先看看Execute()
位于core/chaincode/chaincode_support.go
// Execute invokes chaincode and returns the original response.
func (cs *ChaincodeSupport) Execute(txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, input *pb.ChaincodeInput) (*pb.Response, *pb.ChaincodeEvent, error) {
// 主要是啟動鏈碼容器,調(diào)用鏈碼
resp, err := cs.Invoke(txParams, cccid, input)
// 對鏈碼執(zhí)行結(jié)果進行處理
return processChaincodeExecutionResult(txParams.TxID, cccid.Name, resp, err)
}
繼續(xù)看看Invoke
主要調(diào)用了Launch
啟動鏈碼容器堡称,和execute
給鏈碼容器grpc消息(ChaincodeMessage_TRANSACTION
)進行通信瞎抛,有興趣的童鞋們可以跟蹤下去
// Invoke will invoke chaincode and return the message containing the response.
// The chaincode will be launched if it is not already running.
func (cs *ChaincodeSupport) Invoke(txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, input *pb.ChaincodeInput) (*pb.ChaincodeMessage, error) {
// 啟動鏈碼容器
h, err := cs.Launch(txParams.ChannelID, cccid.Name, cccid.Version, txParams.TXSimulator)
if err != nil {
return nil, err
}
// TODO add Init exactly once semantics here once new lifecycle
// is available. Enforced if the target channel is using the new lifecycle
//
// First, the function name of the chaincode to invoke should be checked. If it is
// "init", then consider this invocation to be of type pb.ChaincodeMessage_INIT,
// otherwise consider it to be of type pb.ChaincodeMessage_TRANSACTION,
//
// Secondly, A check should be made whether the chaincode has been
// inited, then, if true, only allow cctyp pb.ChaincodeMessage_TRANSACTION,
// otherwise, only allow cctype pb.ChaincodeMessage_INIT,
cctype := pb.ChaincodeMessage_TRANSACTION
// 給鏈碼發(fā)送消息
return cs.execute(cctype, txParams, cccid, input, h)
}
3.2.3 處理模擬執(zhí)行結(jié)果
執(zhí)行完鏈碼后結(jié)果不會馬上寫到數(shù)據(jù)庫,而是以讀寫集的形式返回給客戶端却紧,結(jié)果寫入交易模擬器TXSimulator
中桐臊。通過調(diào)用GetTxSimulationResults()
方法可以獲取模擬執(zhí)行結(jié)果。TxSimulationResults
包含公有數(shù)據(jù)讀寫集PubSimulationResults
以及私有數(shù)據(jù)讀寫集PvtSimulationResults
晓殊。
SimulateProposal()
方法會調(diào)用GetTxSimulationResults()
方法獲取模擬執(zhí)行結(jié)果断凶。那先看看此函數(shù),位于core/ledger/kvledger/txmgmt/rwsetutil/rwset_builder.go
// GetTxSimulationResults returns the proto bytes of public rwset
// (public data + hashes of private data) and the private rwset for the transaction
func (b *RWSetBuilder) GetTxSimulationResults() (*ledger.TxSimulationResults, error) {
// 獲取交易模擬執(zhí)行結(jié)果的交易私密數(shù)據(jù)讀寫集
pvtData := b.getTxPvtReadWriteSet()
var err error
var pubDataProto *rwset.TxReadWriteSet
var pvtDataProto *rwset.TxPvtReadWriteSet
// Populate the collection-level hashes into pub rwset and compute the proto bytes for pvt rwset
// 計算私密數(shù)據(jù)hash
if pvtData != nil {
if pvtDataProto, err = pvtData.toProtoMsg(); err != nil {
return nil, err
}
// 遍歷計算私密數(shù)據(jù)hash值
for _, ns := range pvtDataProto.NsPvtRwset {
for _, coll := range ns.CollectionPvtRwset {
b.setPvtCollectionHash(ns.Namespace, coll.CollectionName, coll.Rwset)
}
}
}
// Compute the proto bytes for pub rwset
// 獲取交易模擬執(zhí)行結(jié)果的公有數(shù)據(jù)讀寫集
pubSet := b.GetTxReadWriteSet()
if pubSet != nil {
if pubDataProto, err = b.GetTxReadWriteSet().toProtoMsg(); err != nil {
return nil, err
}
}
// 構造交易模擬執(zhí)行結(jié)果
return &ledger.TxSimulationResults{
PubSimulationResults: pubDataProto,
PvtSimulationResults: pvtDataProto,
}, nil
}
3.3 簽名背書
在 ProcessProposal()
方法中巫俺,首先會判斷通道id
是否為nil
认烁,如果為nil
,則直接返回響應結(jié)果(例如install
操作)。如果不為nil
却嗡,會調(diào)用endorseProposal()
方法對模擬執(zhí)行結(jié)果進行簽名和背書舶沛。在endorseProposal()
方法中,會構造Context
對象窗价,再調(diào)用EndorseWithPlugin()
里面會調(diào)用getOrCreatePlugin()
創(chuàng)建plugin如庭,然后調(diào)用proposalResponsePayloadFromContext()
方法,在該方法中會計算背書結(jié)果hash以及封裝模擬執(zhí)行結(jié)果舌镶、鏈碼event
事件以及鏈碼響應結(jié)果等(數(shù)據(jù)結(jié)構為ProposalResponsePayload
)柱彻,在序列化成[]byte
數(shù)組豪娜,最后調(diào)用Endorse()
方法執(zhí)行簽名背書操作(由于escc
現(xiàn)在是插件形式執(zhí)行餐胀,里面會進行判斷。默認執(zhí)行escc
)
// endorse the proposal by calling the ESCC
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) {
endorserLogger.Debugf("[%s][%s] Entry chaincode: %s", chainID, shorttxid(txid), ccid)
defer endorserLogger.Debugf("[%s][%s] Exit", chainID, shorttxid(txid))
isSysCC := cd == nil
// 1) extract the name of the escc that is requested to endorse this chaincode
var escc string
// ie, "lscc" or system chaincodes
// 判斷是否是系統(tǒng)鏈碼
if isSysCC { // 如果是系統(tǒng)鏈碼瘤载,則使用escc進行背書
escc = "escc"
} else {
escc = cd.Endorsement()
}
endorserLogger.Debugf("[%s][%s] escc for chaincode %s is %s", chainID, shorttxid(txid), ccid, escc)
// marshalling event bytes
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")
}
}
// set version of executing chaincode
if isSysCC {
// if we want to allow mixed fabric levels we should
// set syscc version to ""
// 獲取系統(tǒng)鏈碼版本
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)
}
接著看EndorseWithPlugin
,位于core/endorser/plugin_endorser.go
// EndorseWithPlugin endorses the response with a plugin
func (pe *PluginEndorser) EndorseWithPlugin(ctx Context) (*pb.ProposalResponse, error) {
endorserLogger.Debug("Entering endorsement for", ctx)
if ctx.Response == nil {
return nil, errors.New("response is nil")
}
if ctx.Response.Status >= shim.ERRORTHRESHOLD {
return &pb.ProposalResponse{Response: ctx.Response}, nil
}
// 獲取或者創(chuàng)建插件
plugin, err := pe.getOrCreatePlugin(PluginName(ctx.PluginName), ctx.Channel)
if err != nil {
endorserLogger.Warning("Endorsement with plugin for", ctx, " failed:", err)
return nil, errors.Errorf("plugin with name %s could not be used: %v", ctx.PluginName, err)
}
// 從上下文中獲取提案byte數(shù)據(jù)
prpBytes, err := proposalResponsePayloadFromContext(ctx)
if err != nil {
endorserLogger.Warning("Endorsement with plugin for", ctx, " failed:", err)
return nil, errors.Wrap(err, "failed assembling proposal response payload")
}
// 進行背書操作
endorsement, prpBytes, err := plugin.Endorse(prpBytes, ctx.SignedProposal)
if err != nil {
endorserLogger.Warning("Endorsement with plugin for", ctx, " failed:", err)
return nil, errors.WithStack(err)
}
// 背書完成后否灾,封裝為提案響應結(jié)構體,最后將該結(jié)構體返回
resp := &pb.ProposalResponse{
Version: 1,
Endorsement: endorsement,
Payload: prpBytes,
Response: ctx.Response,
}
endorserLogger.Debug("Exiting", ctx)
return resp, nil
}
背書操作主要是在Endorse
進行鸣奔,位于core/handlers/endorsement/plugin/plugin.go
// Endorse signs the given payload(ProposalResponsePayload bytes), and optionally mutates it.
// Returns:
// The Endorsement: A signature over the payload, and an identity that is used to verify the signature
// The payload that was given as input (could be modified within this function)
// Or error on failure
func (e *DefaultEndorsement) Endorse(prpBytes []byte, sp *peer.SignedProposal) (*peer.Endorsement, []byte, error) {
signer, err := e.SigningIdentityForRequest(sp)
if err != nil {
return nil, nil, errors.New(fmt.Sprintf("failed fetching signing identity: %v", err))
}
// serialize the signing identity
identityBytes, err := signer.Serialize()
if err != nil {
return nil, nil, errors.New(fmt.Sprintf("could not serialize the signing identity: %v", err))
}
// sign the concatenation of the proposal response and the serialized endorser identity with this endorser's key
signature, err := signer.Sign(append(prpBytes, identityBytes...))
if err != nil {
return nil, nil, errors.New(fmt.Sprintf("could not sign the proposal response payload: %v", err))
}
endorsement := &peer.Endorsement{Signature: signature, Endorser: identityBytes}
return endorsement, prpBytes, nil
}
參考:
Fabric1.4源碼解析:Peer節(jié)點背書提案過程
Fabric 1.4 源碼分析 Endorser背書節(jié)點