在之前的文章中

  • Hyperledger Fabric从源码分析链码安装过程
  • Hyperledger Fabric从源码分析链码实例化过程
  • Hyperledger Fabric从源码分析链码查询与调用

都提到了类似下面这一行的代码

proposalResponse, err := cf.EndorserClients[0].ProcessProposal(context.Background(), signedProp)

这一行代码是由客户端向背书节点发起背书提案申请,获取背书响应的过程,即客户端应用程序发送交易提案,背书节点模拟执行,并将模拟执行结果返回客户端应用程序的过程。

之前的三篇文章中,我们了解了客户端是如何创建一个交易提案并发送交易提案,那么今天这篇文章就来探讨一下背书节点是如何处理一个交易提案的。模拟执行交易提案是一个经常用到的过程,ProcessProposal()函数也是一个经常被用到的函数,因为只要是客户端调用链码发起交易提案,都会执行这个过程。

好的,下面就来看一下这个经典的背书提案过程吧


熟悉 Endorser Service

我们从 EndorserClientProcessProposal()函数入手,点进去看一下,在protos/peer/peer.pb.go的126行:

// EndorserClient is the client API for Endorser service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type EndorserClient interface {ProcessProposal(ctx context.Context, in *SignedProposal, opts ...grpc.CallOption) (*ProposalResponse, error)
}type endorserClient struct {cc *grpc.ClientConn
}func NewEndorserClient(cc *grpc.ClientConn) EndorserClient {return &endorserClient{cc}
}// 这个是EndorserClient的ProcessProposal方法
func (c *endorserClient) ProcessProposal(ctx context.Context, in *SignedProposal, opts ...grpc.CallOption) (*ProposalResponse, error) {out := new(ProposalResponse)err := c.cc.Invoke(ctx, "/protos.Endorser/ProcessProposal", in, out, opts...)if err != nil {return nil, err}return out, nil
}// EndorserServer is the server API for Endorser service.
type EndorserServer interface {ProcessProposal(context.Context, *SignedProposal) (*ProposalResponse, error)
}// 服务端注册EndorserServer的函数
func RegisterEndorserServer(s *grpc.Server, srv EndorserServer) {s.RegisterService(&_Endorser_serviceDesc, srv)
}// ............

来看一下生成该文件的 proto 文件,protos/peer/peer.proto

syntax = "proto3";option java_package = "org.hyperledger.fabric.protos.peer";
option go_package = "github.com/hyperledger/fabric/protos/peer";package protos;import "peer/proposal.proto";
import "peer/proposal_response.proto";message PeerID {string name = 1;
}message PeerEndpoint {PeerID id = 1;string address = 2;
}// Endorser服务的定义
service Endorser {rpc ProcessProposal(SignedProposal) returns (ProposalResponse) {}
}

好了,上面这两个部分就帮我们了解了 Endorser service的定义,它包含了一个ProcessProposal()方法,它接收的参数是一个SignedProposal签名提案,返回值是一个ProposalResponse提案响应。

追溯原始对象

RegisterEndorserServer()函数是用于注册EndorserServer的函数,点进去看一下它在哪里用到了,在peer/node/start.go的467行:

// start the peer server
auth := authHandler.ChainFilters(serverEndorser, authFilters...)
// Register the Endorser server
pb.RegisterEndorserServer(peerServer.Server(), auth)

也就是说,peer节点启动的时候,就已经注册好了EndorserServer了。来稍微看一下ChainFilters()这个函数,在core/handlers/auth/auth.go中:

// Filter defines an authentication filter that intercepts
// ProcessProposal methods
type Filter interface {peer.EndorserServer// Init initializes the Filter with the next EndorserServerInit(next peer.EndorserServer)
}// ChainFilters chains the given auth filters in the order provided.
// the last filter always forwards to the endorser
func ChainFilters(endorser peer.EndorserServer, filters ...Filter) peer.EndorserServer {if len(filters) == 0 {return endorser}// Each filter forwards to the nextfor i := 0; i < len(filters)-1; i++ {filters[i].Init(filters[i+1])}// Last filter forwards to the endorserfilters[len(filters)-1].Init(endorser)return filters[0]
}

看了下注释,这个函数主要是将一些 filter 串联起来,最后一个 filter 连着的是 endorser,在执行 filter 的 ProcessProposal()的方法时,最终都会调用它 next 的 ProcessProposal()方法,调用到最后一个时,调用的就是 endorser 的 ProcessProposal() 方法。因此,我们看一下最后一个 endorser 的ProcessProposal()就可以了。传给ChainFilters()函数的第一个参数就是 serverEndorser ,看下这个变量是在哪里定义的,在peer/node/start.go的322行:

serverEndorser := endorser.NewEndorserServer(privDataDist, endorserSupport, pr, metricsProvider)

看下NewEndorserServer()函数,在core/endorser/endorser.go的121行:

// NewEndorserServer creates and returns a new Endorser server instance.
func NewEndorserServer(privDist privateDataDistributor, s Support, pr *platforms.Registry, metricsProv metrics.Provider) *Endorser {e := &Endorser{distributePrivateData: privDist,s:                     s,PlatformRegistry:      pr,PvtRWSetAssembler:     &rwSetAssembler{},Metrics:               NewEndorserMetrics(metricsProv),}return e
}// Endorser provides the Endorser service ProcessProposal
type Endorser struct {distributePrivateData privateDataDistributors                     SupportPlatformRegistry      *platforms.RegistryPvtRWSetAssemblerMetrics *EndorserMetrics
}

终于追溯到原始的 Endorser 对象了,背书的时候最终会调用它的ProcessProposal()方法,那么就来看看这个方法,在core/endorser/endorser.go的423行:

// ProcessProposal process the Proposal
func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) {//...
}

这里我就暂时不展开了,函数比较长,稍后再看一下。

总结一下刚才追溯原始对象的过程,其实是一个寻找GRPC Server实例的一个过程:

  1. peer.pb.go中发现服务注册函数RegisterEndorserServer()
  2. 追溯过去,找到函数调用在peer/node/start.go
  3. 找到RegisterEndorserServer()参数来源,第一个参数是grpc.Server不用关心,第二个参数是EndorserServer,看下第二个参数auth如何获得
  4. 追溯到ChainFilters()函数中,发现主要是第一个参数endorser起到最终决定作用,再追溯这个参数如何获得
  5. 追溯到NewEndorserServer()函数,追溯进去看一下,就找到了最终的对象Endorser,找到了它的ProcessProposal()方法

这是我阅读源代码的过程,希望可以分享给大家。

话不多说,言归正传,继续开始分析。


解析大头 Endorser.ProcessProposal

接着刚才往下走

// ProcessProposal process the Proposal
func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) {// startTime用于计算完成整个提案的时间startTime := time.Now()// 指标相关的操作,将接收到的提案数+1e.Metrics.ProposalsReceived.Add(1)// 从上下文中获取远端 addr 的地址,这部分由grpc保存addr := util.ExtractRemoteAddress(ctx)endorserLogger.Debug("Entering: request from", addr)// variables to capture proposal duration metricvar chainID string    // 这个是通道IDvar hdrExt *pb.ChaincodeHeaderExtensionvar success booldefer func() {// 在函数执行完以后做一些指标的设置,用于判断是否提案验证失败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预处理

core/endorser/endorser.go的348行:

// preProcess checks the tx proposal headers, uniqueness and ACL
func (e *Endorser) preProcess(signedProp *pb.SignedProposal) (*validateResult, error) {vr := &validateResult{}// 验证提案Message,看下这个方法prop, hdr, hdrExt, err := validation.ValidateProposalMessage(signedProp)if err != nil {// 如果失败,则将错误指标+1,另外返回错误码500e.Metrics.ProposalValidationFailed.Add(1)vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}return vr, err}

ValidateProposalMessagecore/common/validation/msgvalidation.go76行:

// 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) {// 如果signedProp为nil直接返回错误if signedProp == nil {return nil, nil, nil, errors.New("nil arguments")}putilsLogger.Debugf("ValidateProposalMessage starts for signed proposal %p", signedProp)// 将signedProp的ProposalBytes字段unmarshal为Proposal对象propprop, err := utils.GetProposal(signedProp.ProposalBytes)if err != nil {return nil, nil, nil, err}// 将prop.Header字段unmarshal为Header对象hdrhdr, err := utils.GetHeader(prop.Header)if err != nil {return nil, nil, nil, err}// 验证Header hdr,看下这个方法chdr, shdr, err := validateCommonHeader(hdr)if err != nil {return nil, nil, nil, err}

先来看下这里的几个结构体:

type SignedProposal struct {ProposalBytes []byte // 提案具体信息Signature            []byte   // 签名字段XXX_NoUnkeyedLiteral struct{} XXX_unrecognized     []byte   XXX_sizecache        int32
}type Proposal struct {Header []byte    // 提案头部字段Payload []byte     // 提案payloadExtension            []byte   // 提案扩展字段XXX_NoUnkeyedLiteral struct{} XXX_unrecognized     []byte   XXX_sizecache        int32
}type Header struct {ChannelHeader        []byte   SignatureHeader      []byte   XXX_NoUnkeyedLiteral struct{} XXX_unrecognized     []byte   XXX_sizecache        int32
}

看一下validateCommonHeader()方法,用于验证Header结构体,在core/common/validation/msgvalidation.go的246行:

// checks for a valid Header
func validateCommonHeader(hdr *common.Header) (*common.ChannelHeader, *common.SignatureHeader, error) {// hdr为空直接返回错误if hdr == nil {return nil, nil, errors.New("nil header")}// 将hdr的ChannelHeader字段反序列化得到chdrchdr, err := utils.UnmarshalChannelHeader(hdr.ChannelHeader)if err != nil {return nil, nil, err}// 将hdr的SignatureHeader字段反序列化得到shdrshdr, err := utils.GetSignatureHeader(hdr.SignatureHeader)if err != nil {return nil, nil, err}// 验证chdr ChannelHeadererr = validateChannelHeader(chdr)if err != nil {return nil, nil, err}// 验证shdr SignatureHeadererr = validateSignatureHeader(shdr)if err != nil {return nil, nil, err}return chdr, shdr, nil
}

主要是验证了ChannelHeaderSignatureHeader两个 header 字段,看下这两个结构体:

// Header is a generic replay prevention and identity message to include in a signed payload
type ChannelHeader struct {Type int32 Version int32Timestamp *timestamp.Timestamp ChannelId string TxId string Epoch uint64Extension []byte TlsCertHash          []byte   XXX_NoUnkeyedLiteral struct{} XXX_unrecognized     []byte   XXX_sizecache        int32
}type SignatureHeader struct {Creator []byte Nonce                []byte   XXX_NoUnkeyedLiteral struct{} XXX_unrecognized     []byte   XXX_sizecache        int32
}

这两个 Header 的结构其实在之前的文章中已经介绍过了,这里这是再把它们放出来方便看,看下它们的验证方式,先看下validateChannelHeader(),在core/common/validation/msgvalidation.go的214行:

// checks for a valid ChannelHeader
func validateChannelHeader(cHdr *common.ChannelHeader) error {// 检查chdr是否为空if cHdr == nil {return errors.New("nil ChannelHeader provided")}// 验证HeaderType// 判断是否为ENDORSER_TRANSACTION,HeaderType_CONFIG_UPDATE,CONFIG ,TOKEN_TRANSACTION其中的一种类型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))}putilsLogger.Debugf("validateChannelHeader info: header type %d", common.HeaderType(cHdr.Type))// 检查Header中Epoch是否为0,这个值在创建Header的时候一般设置为0if cHdr.Epoch != 0 {return errors.Errorf("invalid Epoch in ChannelHeader. Expected 0, got [%d]", cHdr.Epoch)}return nil
}

再来看下validateSignatureHeader(),在core/common/validation/msgvalidation.go的194行:

// checks for a valid SignatureHeader
func validateSignatureHeader(sHdr *common.SignatureHeader) error {// 检查sHdr是否为空if sHdr == nil {return errors.New("nil SignatureHeader provided")}// 检查Nonce是否为空,并且Nonce长度不能为0if sHdr.Nonce == nil || len(sHdr.Nonce) == 0 {return errors.New("invalid nonce specified in the header")}// 检查Creator是否为空,并且Cretor不能为0if sHdr.Creator == nil || len(sHdr.Creator) == 0 {return errors.New("invalid creator specified in the header")}return nil
}

总结一下两个 Header 的验证方法

ChannelHeader

  1. ChannelHeader是否为空
  2. ChannelHeader.Type类型是否为ENDORSER_TRANSACTION、CONFIG_UPDATE、CONFIG、TOKEN_TRANSACTION中的一种
  3. 判断ChannelHeader.Epoch是否为空

SignatureHeader

  1. SignatureHeader是否为空
  2. 检查SignatureHeader.Nonce是否为空
  3. 检查SignatureHeader.Creator是否为空

validateCommonHeader()方法执行完以后,得到了两个头部字段,我们重新回到ValidateProposalMessage方法:

// validate the headerchdr, shdr, err := validateCommonHeader(hdr)if err != nil {return nil, nil, nil, err}// 验证签名err = checkSignatureFromCreator(shdr.Creator, signedProp.Signature, signedProp.ProposalBytes, chdr.ChannelId)if err != nil {// 验证失败后进入这里,输出一些warning日志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 errorerr = 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)}

是对签名的验证,来看下checkSignatureFromCreator()方法,在core/common/validation/msgvalidation.go的153行:

// 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")// 检查参数是否为空if creatorBytes == nil || sig == nil || msg == nil {return errors.New("nil arguments")}// 根据给入的ChainID获取Identity,返回成员服务提供者对象mspObjmspObj := mspmgmt.GetIdentityDeserializer(ChainID)if mspObj == nil {return errors.Errorf("could not get msp for channel [%s]", ChainID)}// 查找creator的Identitycreator, err := mspObj.DeserializeIdentity(creatorBytes)if err != nil {return errors.WithMessage(err, "MSP error")}putilsLogger.Debugf("creator is %s", creator.GetIdentifier())// 验证creator的证书,确保creator是合法的err = creator.Validate()if err != nil {return errors.WithMessage(err, "creator certificate is not valid")}putilsLogger.Debugf("creator is valid")// 对签名进行验证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
}

再继续看ValidateProposalMessage()方法:

// 用于检查TxId是否存在,用于防止重复攻击err = utils.CheckTxID(chdr.TxId,shdr.Nonce,shdr.Creator)if err != nil {return nil, nil, nil, err}// 判断ChannelHeader的类型switch common.HeaderType(chdr.Type) {// 无论是CONFIG或者是ENDORSER_TRANSACTION类型都会执行validateChaincodeProposalMessage函数// 之前文章可以看到基本上提案类型设置的都是ENDORSER_TRANSACTION类型case common.HeaderType_CONFIG:fallthroughcase common.HeaderType_ENDORSER_TRANSACTION:chaincodeHdrExt, err := validateChaincodeProposalMessage(prop, hdr)if err != nil {return nil, nil, nil, err}return prop, hdr, chaincodeHdrExt, errdefault:// 其他提案类型暂不支持return nil, nil, nil, errors.Errorf("unsupported proposal type %d", common.HeaderType(chdr.Type))}
}

看下validateChaincodeProposalMessage()函数,在core/common/validation/msgvalidation.go的36行:

// validateChaincodeProposalMessage checks the validity of a Proposal message of type CHAINCODE
func validateChaincodeProposalMessage(prop *pb.Proposal, hdr *common.Header) (*pb.ChaincodeHeaderExtension, error) {// 验证参数是否为空if prop == nil || hdr == nil {return nil, errors.New("nil arguments")}putilsLogger.Debugf("validateChaincodeProposalMessage starts for proposal %p, header %p", prop, hdr)// 获取Header中的ChaincodeHeaderExtension部分,也就是Extension字段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")}putilsLogger.Debugf("validateChaincodeProposalMessage info: header extension references chaincode %s", chaincodeHdrExt.ChaincodeId)// 判断扩展字段的PayloadVisibility是否为空if chaincodeHdrExt.PayloadVisibility != nil {return nil, errors.New("invalid payload visibility field")}// 最终返回扩展字段return chaincodeHdrExt, nil
}

ValidateProposalMessage()函数到这里就执行完了,回到preProcess()函数:

// 根据返回的Header拿到ChannelHeader
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拿到SignatureHeadershdr, err := putils.GetSignatureHeader(hdr.SignatureHeader)if err != nil {vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}return vr, err}// 阻止对系统链码的调用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.ChannelIdtxid := chdr.TxIdendorserLogger.Debugf("[%s][%s] processing txid: %s", chainID, shorttxid(txid), txid)// 判断通道ID是否为空if chainID != "" {// labels that provide context for failure metricsmeterLabels := []string{"channel", chainID,"chaincode", hdrExt.ChaincodeId.Name + ":" + hdrExt.ChaincodeId.Version,}// 检测txid是否已经存在if _, err = e.s.GetTransactionByID(chainID, txid); err == nil {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}// 判断是否为系统链码if !e.s.IsSysCC(hdrExt.ChaincodeId.Name) {// 如果不是系统链码,则检测ACL访问权限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 {// 通道ID为空什么都不做}vr.prop, vr.hdrExt, vr.chainID, vr.txid = prop, hdrExt, chainID, txidreturn vr, nil
}

到这里preProcess()方法就走完了,总结一下:

  1. 执行ValidateProposalMessage()函数,验证提案信息,得到提案信息prop,头部信息hdr,头部扩展字段信息chaincodeHdrExt

    1. 从签名提案signedProp中获取提案信息prop
    2. prop中获取提案头部字段hdr
    3. 调用validateCommonHeader()验证头部字段hdr,并拿到ChannelHeader字段chdrSignatureHeader字段shdr
      1. hdr中拿取ChannelHeader字段chdr
      2. hdr中拿取SignatureHeader字段shdr
      3. 验证chdr
        1. ChannelHeader是否为空
        2. ChannelHeader.Type类型是否为ENDORSER_TRANSACTION、CONFIG_UPDATE、CONFIG、TOKEN_TRANSACTION中的一种
        3. 判断ChannelHeader.Epoch是否为空
      4. 验证shdr
        1. SignatureHeader是否为空
        2. 检查SignatureHeader.Nonce是否为空
        3. 检查SignatureHeader.Creator是否为空
      5. 返回chdrshdr
    4. 调用checkSignatureFromCreator(shdr.Creator, signedProp.Signature, signedProp.ProposalBytes, chdr.ChannelId)验证签名
      1. 验证creator,signature,ProposalBytes是否为空
      2. 从通道ID获取成员服务对象mspObj
      3. mspObj查找creator的证书等信息
      4. 验证creator的证书和签名信息
    5. 检查是有是重复的txID防止重复攻击
    6. 判断chdr的头部类型是否为CONFIG,ENDORSER_TRANSACTION,如果不是直接返回错误
    7. 不为空则执行validateChaincodeProposalMessage()函数获取头部扩展字段chaincodeHdrExt
    8. 最终返回提案信息prop,头部信息hdr,头部扩展字段信息chaincodeHdrExt
  2. 根据hdr获取chdrshdr
  3. 判断是否调用的是不允许被外部调用的系统链码
  4. 判断通道ID是否为空,如果为空则什么也不做直接返回。
  5. 通道ID不为空则检查该TxID是否已经存在,防止重复攻击。
  6. 判断是否为系统链码,如果不是系统链码则检查提案中的权限。
  7. 最终将prop,hdrExt,chainID,txID赋值给验证结果vr返回

预处理函数preProcess函数完成了,进入ProcessProposal继续往下走:

// 刚刚看到这里vr, err := e.preProcess(signedProp)if err != nil {resp := vr.respreturn resp, err}
prop, hdrExt, chainID, txid := vr.prop, vr.hdrExt, vr.chainID, vr.txid// 定义了一个交易模拟器var txsim ledger.TxSimulator// 定义了一个历史记录查询器var historyQueryExecutor ledger.HistoryQueryExecutor// 判断是否需要模拟执行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}// 最后将模拟器关闭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()函数,在core/endorser/endorser.go的569行:

// determine whether or not a transaction simulator should be
// obtained for a proposal.
func acquireTxSimulator(chainID string, ccid *pb.ChaincodeID) bool {// 如果通道ID为空就返回falseif chainID == "" {return false}// 如果链码是查询系统链码qscc和配置系统链码cscc。则返回fasle,否则返回trueswitch ccid.Name {case "qscc", "cscc":return falsedefault: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)if err != nil {// 出错返回错误return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, nil}

又是一个重要的函数SimulateProposal()函数来了,下面来看看这个函数。

SimulateProposal 模拟执行提案

core/endorser/endorser.go的213行:

// 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) {// 参数:1.txParms交易参数 2. cid ChaincodeID// 返回值:1.ChaincodeDefinition 2.pb.Response 3. result 4. ChaincodeEvent 5.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))//获取ChaincodeInvocationSpec,也就是cis,这在前几章生成交易提案的时候说过这个结构体cis, err := putils.GetChaincodeInvocationSpec(txParams.Proposal)if err != nil {return nil, nil, nil, nil, err}

看下这个方法,在protos/utils/proputils.go的24行:

// GetChaincodeInvocationSpec get the ChaincodeInvocationSpec from the proposal
func GetChaincodeInvocationSpec(prop *peer.Proposal) (*peer.ChaincodeInvocationSpec, error) {// 判断提案是否为空if prop == nil {return nil, errors.New("proposal is nil")}// 对头部进行验证_, err := GetHeader(prop.Header)if err != nil {return nil, err}// 从提案中获取ChaincodeProposalPayloadccPropPayload, err := GetChaincodeProposalPayload(prop.Payload)if err != nil {return nil, err}cis := &peer.ChaincodeInvocationSpec{}// 将ccPropPayload.Input反序列化得到ChaincodeInvocationSpecerr = proto.Unmarshal(ccPropPayload.Input, cis)return cis, errors.Wrap(err, "error unmarshaling ChaincodeInvocationSpec")
}

回到SimulateProposal()

 // 定义ChaincodeDefinition类型的cdLedgervar cdLedger ccprovider.ChaincodeDefinitionvar version string// 判断是否是系统链码if !e.s.IsSysCC(cid.Name) {// 如果不是系统链码,获取链码的ChaincodeDefinitioncdLedger, 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()}// ---3. execute the proposal and get simulation results// 模拟执行结果var simResult *ledger.TxSimulationResults// 保存public的模拟响应结果var pubSimResBytes []byte// 响应信息var res *pb.Response// 链码事件var ccevent *pb.ChaincodeEventres, 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}

接下就又是一个重要的函数了callChaincode(),它用来执行链码,包括系统链码与用户链码,下面来看下。

callChaincode 执行链码

core/endorser/endorser.go133行:

// 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) {// 下面这个INFO日志是不是经常看到,只要执行一个链码,就可以在容器的日志信息中看到这个日志信息// 包括开始执行链码的时间,和执行结束的时间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.Millisecondlogger.Infof("[%s][%s] Exit chaincode: %s (%dms)", txParams.ChannelID, shorttxid(txParams.TxID), cid, elapsedMilliseconds)}(time.Now())var err errorvar res *pb.Responsevar ccevent *pb.ChaincodeEvent// Execute执行提案,返回原始的结果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}

看看Execute()函数,在core/endorser/support.go的135行:

// Execute a proposal and return the chaincode response
func (s *SupportImpl) Execute(txParams *ccprovider.TransactionParams, cid, name, version, txid string, signedProp *pb.SignedProposal, prop *pb.Proposal, input *pb.ChaincodeInput) (*pb.Response, *pb.ChaincodeEvent, error) {// 链码信息对象cccidcccid := &ccprovider.CCContext{Name:    name,Version: version,}// 下面这块不是看的很懂,好像主要是做一个装饰操作,填充的是input的Decorations字段decorators := library.InitRegistry(library.Config{}).Lookup(library.Decoration).([]decoration.Decorator)input.Decorations = make(map[string][]byte)input = decoration.Apply(prop, input, decorators...)txParams.ProposalDecorations = input.Decorations// 最终调用ChaincodeSupport.Executereturn s.ChaincodeSupport.Execute(txParams, cccid, input)
}

看下ChaincodeSupport.Execute(),在core/chaincode/chaincode_support.go的238行:

// 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) {resp, err := cs.Invoke(txParams, cccid, input)return processChaincodeExecutionResult(txParams.TxID, cccid.Name, resp, err)
}// 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}// 将类型设置为ChaincodeMessage_TRANSACTIONcctype := pb.ChaincodeMessage_TRANSACTIONreturn cs.execute(cctype, txParams, cccid, input, h)
}

ChaincodeSupport.Execute()调用了ChaincodeSupport.Invoke()ChaincodeSupport.Invoke()中执行一个Launch操作,拿到一个操作链码的handler,最后又会执行ChaincodeSupport.execute()方法,看下这个方法,在core/chaincode/chaincode_support.go的305行:

// execute executes a transaction and waits for it to complete until a timeout value.
func (cs *ChaincodeSupport) execute(cctyp pb.ChaincodeMessage_Type, txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, input *pb.ChaincodeInput, h *Handler) (*pb.ChaincodeMessage, error) {input.Decorations = txParams.ProposalDecorations// 创建ChaincodeMessageccMsg, err := createCCMessage(cctyp, txParams.ChannelID, txParams.TxID, input)if err != nil {return nil, errors.WithMessage(err, "failed to create chaincode message")}// 执行 handler的Execuete方法ccresp, err := h.Execute(txParams, cccid, ccMsg, cs.ExecuteTimeout)if err != nil {return nil, errors.WithMessage(err, fmt.Sprintf("error sending"))}return ccresp, nil
}

再执行handler.Execute()方法,在core/chaincode/handler.go的1240行:

func (h *Handler) Execute(txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, msg *pb.ChaincodeMessage, timeout time.Duration) (*pb.ChaincodeMessage, error) {chaincodeLogger.Debugf("Entry")defer chaincodeLogger.Debugf("Exit")txParams.CollectionStore = h.getCollectionStore(msg.ChannelId)txParams.IsInitTransaction = (msg.Type == pb.ChaincodeMessage_INIT)txctx, err := h.TXContexts.Create(txParams)if err != nil {return nil, err}defer h.TXContexts.Delete(msg.ChannelId, msg.Txid)// 设置链码提案if err := h.setChaincodeProposal(txParams.SignedProp, txParams.Proposal, msg); err != nil {return nil, err}// 这个函数很重要,它是链码容器与节点容器通信的h.serialSendAsync(msg)var ccresp *pb.ChaincodeMessageselect {case ccresp = <-txctx.ResponseNotifier:// response is sent to user or calling chaincode. ChaincodeMessage_ERROR// are typically treated as errorcase <-time.After(timeout):err = errors.New("timeout expired while executing transaction")ccName := cccid.Name + ":" + cccid.Versionh.Metrics.ExecuteTimeouts.With("chaincode", ccName).Add(1)case <-h.streamDone():err = errors.New("chaincode stream terminated")}return ccresp, err
}

这块涉及的一部分理论还没有讲到,这里如果展开来又会比较复杂,先简单解释一下,这里其实就是节点容器与链码容器之间的一个简单交互,两者的handler之间在不断地通信传输数据。

我们回到callChaincode()函数中执行Execute()函数的地方继续往下走:

 // 获取到链码响应res,链码事件cceventres, 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}// 判断状态>=ERRORTHRESHOLD,200是OK,400和500都不行if res.Status >= shim.ERRORTHRESHOLD {return res, nil, nil}// 判断调用的链码是否为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)if err != nil {return nil, nil, err}// 拿到cds对象var cds *pb.ChaincodeDeploymentSpeccds, err = e.SanitizeUserCDS(userCDS)if err != nil {return nil, nil, err}// 系统链码不能被实例化或升级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)}// 执行链码的Init函数_, _, 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 failuresmeterLabels := []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
}

callChaincode()方法到这里结束了,链码的调用执行也完成了,返回响应消息与链码事件,回到SimulateProposal():

 // 刚刚看到这里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}// 如果TXSimulator不为空,说明大部分是账本有关的操作if txParams.TXSimulator != nil {// GetTxSimulationResults()获取Tx模拟结果集,包含公共读写集和私有读写集if simResult, err = txParams.TXSimulator.GetTxSimulationResults(); err != nil {txParams.TXSimulator.Done()return nil, nil, nil, nil, err}// 对私有数据的一些操作if simResult.PvtSimulationResults != nil {if cid.Name == "lscc" {// 如果链码是lscctxParams.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()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))}pvtDataWithConfig.EndorsedAt = endorsedAtif 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
}

到这里模拟执行提案的过程SimulateProposal()就完了,接下来应该就是背书的过程了,回到ProcessProposal()方法:

 // 刚刚看到这里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 []byteif 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 messagevar pResp *pb.ProposalResponse// TODO till we implement global ESCC, CSCC for system chaincodes// chainless proposals (such as CSCC) don't have to be endorsedif chainID == "" {// 如果通道ID为空就直接返回了,比如 qscc,cscc,不需要背书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// 执行endorseProposal函数进行背书pResp, err = e.endorseProposal(ctx, chainID, txid, signedProp, prop, res, simulationResult, ccevent, hdrExt.PayloadVisibility, hdrExt.ChaincodeId, txsim, cd)// if error, capture endorsement failure metricmeterLabels := []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 metricmeterLabels = 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// 设置response信息pResp.Response = res// total failed proposals = ProposalsReceived-SuccessfulProposals// 成功指标 + 1e.Metrics.SuccessfulProposals.Add(1)success = true// 返回提案响应信息return pResp, nil
}

背书过程endorseProposal

来看看最后一个函数endorseProposal(),在core/endorser/endorser.go的297行:

// 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)

看下参数先:

  • Context上下文,没有用到

  • chainID:通道ID

  • txid:交易ID

  • SignedProposal:签名过的提案

  • proposal:提案

  • response:之前返回的响应消息

  • simRes:模拟结果集

  • event:链码事件

  • visibility:头部扩展字段的payload部分

  • ccid:链码Id

  • txsim:交易模拟器

  • cd:链码标准数据结构,就是调用的链码功能和参数等信息

func (e *Endorser) endorseProposal(#后面参数省略)(*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 == nilvar escc string// 判断是否是系统链码if isSysCC {// 如果是系统链码,则使用escc进行背书escc = "escc"} else {// 看官方解释这个好像也是返回esccescc = cd.Endorsement()}endorserLogger.Debugf("[%s][%s] escc for chaincode %s is %s", chainID, shorttxid(txid), ccid, escc)var err errorvar 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)
}

看下EndorseWithPlugin()方法,在core/endorser/plugin_endorser.go的162行:

// EndorseWithPlugin endorses the response with a plugin
func (pe *PluginEndorser) EndorseWithPlugin(ctx Context) (*pb.ProposalResponse, error) {endorserLogger.Debug("Entering endorsement for", ctx)// 判断responseif ctx.Response == nil {return nil, errors.New("response is nil")}// 判断response.statusif ctx.Response.Status >= shim.ERRORTHRESHOLD {return &pb.ProposalResponse{Response: ctx.Response}, nil}// 获取pluginplugin, 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)}// 创建proposalResponsePayload bytesprpBytes, 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")}// 执行plugin.Endorse执行背书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)}resp := &pb.ProposalResponse{Version:     1,Endorsement: endorsement,Payload:     prpBytes,Response:    ctx.Response,}endorserLogger.Debug("Exiting", ctx)return resp, nil
}

看下getOrCreatePlugin():

// getAndStorePlugin returns a plugin instance for the given plugin name and channel
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 := pe.getOrCreatePluginChannelMapping(PluginName(plugin), pluginFactory)// 根据通道创建插件return pluginsByChannel.createPluginIfAbsent(channel)
}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
}func (pbc *pluginsByChannel) initPlugin(plugin endorsement.Plugin, channel string) (endorsement.Plugin, error) {var dependencies []endorsement.Dependencyvar err errorif channel != "" {// 根据给予的通道信息创建一个用于查询的Creatorquery, 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创建完以后,执行它的Endose函数,就执行背书了,最终返回了一个ProposalResponse对象,作为提案相应返回给客户端。

这里提一句的是,如果链码设置了 event,even t会在执行链码以后返回,并最终写到 ChaincodeAction 的 Events 对象中,并最终作为 ProposalResponsePayload 的 Extension 扩展字段赋值,作为ProposalResponse 的 Payload 字段返回给客户端。

客户端将 ProposalResponse 最终会打包成一个交易,发送给排序节点并最终给记账节点计入到账本中。因此事件最终是记录到了账本中。

peer 节点有一个 DeliverServer 会查找账本上的事件,如果有客户端或者 sdk 注册了链码事件,就会将对应的事件返回。

这是 Fabric 的事件的整体逻辑,后续会专门写一篇文章解析,这里暂时提一笔。

总结

写了好长好长的东西,总结一下:

  1. 执行preProcess()对提案进行预处理,主要就是验证的一些过程,具体流程前面有写
  2. 执行SimulateProposal()模拟执行交易提案
    1. 最终调用callChaincode()方法执行链码,并获取链码执行结果
    2. 判断调用的是否是lscc,是否是实例化或升级过程,如果是的话执行链码的Init函数
    3. 返回模拟执行结果集
  3. 执行endorseProposal()完成背书,将链码事件,响应等信息放在Payload字段中
  4. 将背书好的ProposalResponse结构体返回给客户端应用程序

如果想看更详细完整的总结,可以看一下我的下一篇文章的整体流程总结——Hyperledger Fabric从源码分析链码容器启动过程

Hyperledger Fabric从源码分析背书提案过程相关推荐

  1. Hyperledger Fabric从源码分析交易

    在上一章Hyperledger Fabric从源码分析区块结构中提到了区块的概念,并从源码角度对区块的结构进行了剖析,知道一个简单的区块包含了下面几个部分 BlockHeader, 区块头 Block ...

  2. WebRTC源码分析-呼叫建立过程之五(创建Offer,CreateOffer,上篇)

    目录 1. 引言 2 CreateOffer声明 && 两个参数 2.1 CreateOffer声明 2.2 参数CreateSessionDescriptionObserver 2. ...

  3. MyBatis 源码分析 - 配置文件解析过程

    文章目录 * 本文速览 1.简介 2.配置文件解析过程分析 2.1 配置文件解析入口 2.2 解析 properties 配置 2.3 解析 settings 配置 2.3.1 settings 节点 ...

  4. modelandview使用过程_深入源码分析SpringMVC执行过程

    本文主要讲解 SpringMVC 执行过程,并针对相关源码进行解析. 首先,让我们从 Spring MVC 的四大组件:前端控制器(DispatcherServlet).处理器映射器(HandlerM ...

  5. SOFA 源码分析 —— 服务发布过程

    前言 SOFA 包含了 RPC 框架,底层通信框架是 bolt ,基于 Netty 4,今天将通过 SOFA-RPC 源码中的例子,看看他是如何发布一个服务的. 示例代码 下面的代码在 com.ali ...

  6. WebRTC源码分析-呼叫建立过程之四(上)(创建并添加本地音频轨到PeerConnection)

    目录 1. 引言 2. 音频轨创建和添加 2.1 音频源AudioSource的创建 2.1.1 音频源继承树 2.1.2 近端音频源LocalAudioSource 2.1.3 远端音频源Remot ...

  7. Nimbus三Storm源码分析--Nimbus启动过程

    Nimbus server, 首先从启动命令开始, 同样是使用storm命令"storm nimbus"来启动 看下源码, 此处和上面client不同, jvmtype=" ...

  8. 飞鸽传书源码分析-程序启动过程

    本文章是在飞鸽传书的2.06源码基础上分析 飞鸽传书源码运行流程如下,本篇文章只说明了飞鸽传书的启动过程,对于飞鸽伟书的消息机制及菜单加载等功能都不在本篇文章范围之内. 1. WinMain函数 [c ...

  9. golang源码分析:编译过程词法解析的流程

    golang编译 由于golang作为静态语言,当使用go build时就会生成对应的编译完成之后的文件,那这个编译过程大致会做什么事情呢,在golang中的编译大致有哪些流程. golang示例代码 ...

最新文章

  1. 收藏!一篇文章搞懂甘特图
  2. 爆笑!论文中,这些话的实际意思是...
  3. nginc+memcache
  4. 各种汇编器masm masm32 fasm nasm yasm gas的区别
  5. 易语言 图片插入超级列表框_是谁说图片排版很难?掌握这4个PPT图片排版技巧,1分钟全部搞定...
  6. 《DSP using MATLAB》Problem 6.20
  7. php显示时间,php实现用已经过去多长时间的方式显示时间
  8. 深度学习(2)--常见概率分布(2)
  9. 对乱糟糟的日志说再见
  10. 信息学奥赛C++语言:小玉家的电费
  11. java 排序方法详解_java中关于排序方式的实例讲解
  12. esp启动是什么感觉_第九章 ESP32上电后的启动过程
  13. python与pexpect实现ssh操作
  14. sql like不包括_SQL Like – SQL不喜欢
  15. 软件测试之编写测试用例
  16. AsposeWord转pdf的正确姿势
  17. 多喝牛奶少吃糖 正确饮食摆脱经前综合征
  18. 山东济南计算机比赛,第十二届齐鲁软件大赛及首届济南市计算机科技奖颁奖盛典举行...
  19. 最大元和最小元(直接求解法和分治法)
  20. 数据结构—线性表(第三章)—基本知识点总结

热门文章

  1. No power supply specified for netVCC in Power Rail Confiquration.
  2. 【源码】应用于各类工业控制的通用PID调谐器仿真设计
  3. Genbank的gbff格式转gff3格式(补充)
  4. 【总结,持续更新】java常见的线程不安全,你以为的线程安全
  5. 台式计算机开关电源的电压规格,台式计算机ATX开关电源检修技巧
  6. weblogic下java程序占用cpu过高的问题排查
  7. 积分竟然比微分早了1300年!一文讲清积分的历史
  8. 前端传来的图片并保存_前端处理后端接口传递过来的图片文件
  9. 【Turing Award】Robin Milner And Butler W. Lampson
  10. 计算机网络简要复习纲要(前六章)