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 EndorserServer
	Init(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 next
	for i := 0; i < len(filters)-1; i++ {
		filters[i].Init(filters[i+1])
	}

	// Last filter forwards to the endorser
	filters[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 privateDataDistributor
	s                     Support
	PlatformRegistry      *platforms.Registry
	PvtRWSetAssembler
	Metrics *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()
  // 指标相关的操作,将接收到的提案数+1
	e.Metrics.ProposalsReceived.Add(1)
	// 从上下文中获取远端 addr 的地址,这部分由grpc保存
	addr := util.ExtractRemoteAddress(ctx)
	endorserLogger.Debug("Entering: request from", addr)

	// variables to capture proposal duration metric
	var chainID string	// 这个是通道ID
	var hdrExt *pb.ChaincodeHeaderExtension
	var success bool
	defer 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,另外返回错误码500
		e.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对象prop
	prop, err := utils.GetProposal(signedProp.ProposalBytes)
	if err != nil {
		return nil, nil, nil, err
	}

	// 将prop.Header字段unmarshal为Header对象hdr
	hdr, 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 	// 提案payload
	Extension            []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字段反序列化得到chdr
	chdr, err := utils.UnmarshalChannelHeader(hdr.ChannelHeader)
	if err != nil {
		return nil, nil, err
	}
	// 将hdr的SignatureHeader字段反序列化得到shdr
	shdr, err := utils.GetSignatureHeader(hdr.SignatureHeader)
	if err != nil {
		return nil, nil, err
	}
	
  // 验证chdr ChannelHeader
	err = validateChannelHeader(chdr)
	if err != nil {
		return nil, nil, err
	}

  // 验证shdr SignatureHeader
	err = 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 int32
	Timestamp *timestamp.Timestamp 
	ChannelId string 
	TxId string 
	Epoch uint64
	Extension []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的时候一般设置为0
	if 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长度不能为0
	if sHdr.Nonce == nil || len(sHdr.Nonce) == 0 {
		return errors.New("invalid nonce specified in the header")
	}

	// 检查Creator是否为空,并且Cretor不能为0
	if 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 header
	chdr, 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 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)
	}

是对签名的验证,来看下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,返回成员服务提供者对象mspObj
	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")
	}

	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:
		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行:

// 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拿到SignatureHeader
	shdr, 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.ChannelId
	txid := chdr.TxId
	endorserLogger.Debugf("[%s][%s] processing txid: %s", chainID, shorttxid(txid), txid)

	// 判断通道ID是否为空
	if chainID != "" {
		// labels that provide context for failure metrics
		meterLabels := []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, txid
	return 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.resp
		return 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为空就返回false
	if chainID == "" {
		return false
	}

	// 如果链码是查询系统链码qscc和配置系统链码cscc。则返回fasle,否则返回true
	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)
	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
	}
  // 从提案中获取ChaincodeProposalPayload
	ccPropPayload, err := GetChaincodeProposalPayload(prop.Payload)
	if err != nil {
		return nil, err
	}
	cis := &peer.ChaincodeInvocationSpec{}
  // 将ccPropPayload.Input反序列化得到ChaincodeInvocationSpec
	err = proto.Unmarshal(ccPropPayload.Input, cis)
	return cis, errors.Wrap(err, "error unmarshaling ChaincodeInvocationSpec")
}

回到SimulateProposal()

	// 定义ChaincodeDefinition类型的cdLedger
	var cdLedger ccprovider.ChaincodeDefinition
	var version string
	
	// 判断是否是系统链码
	if !e.s.IsSysCC(cid.Name) {
    // 如果不是系统链码,获取链码的ChaincodeDefinition
		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()
	}

	// ---3. execute the proposal and get simulation results
	// 模拟执行结果
	var simResult *ledger.TxSimulationResults
	// 保存public的模拟响应结果
	var pubSimResBytes []byte
	// 响应信息
	var res *pb.Response
	// 链码事件
	var ccevent *pb.ChaincodeEvent
	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
	}

接下就又是一个重要的函数了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.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

	// 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) {
  // 链码信息对象cccid
	cccid := &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.Execute
	return 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_TRANSACTION
	cctype := pb.ChaincodeMessage_TRANSACTION

	return 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
  // 创建ChaincodeMessage
	ccMsg, 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.ChaincodeMessage
	select {
	case ccresp = <-txctx.ResponseNotifier:
		// response is sent to user or calling chaincode. ChaincodeMessage_ERROR
		// are typically treated as error
	case <-time.After(timeout):
		err = errors.New("timeout expired while executing transaction")
		ccName := cccid.Name + ":" + cccid.Version
		h.Metrics.ExecuteTimeouts.With("chaincode", ccName).Add(1)
	case <-h.streamDone():
		err = errors.New("chaincode stream terminated")
	}

	return ccresp, err
}

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

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

	// 获取到链码响应res,链码事件ccevent
	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
	}

	// 判断状态>=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.ChaincodeDeploymentSpec
		cds, 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 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
}

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" {
        // 如果链码是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()

			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 = 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
}

到这里模拟执行提案的过程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 []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 == "" {
    // 如果通道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 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
	// 设置response信息
	pResp.Response = res

	// total failed proposals = ProposalsReceived-SuccessfulProposals
	// 成功指标 + 1
	e.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 == nil
    var escc string
	// 判断是否是系统链码
	if isSysCC {
        // 如果是系统链码,则使用escc进行背书
		escc = "escc"
	} else {
        // 看官方解释这个好像也是返回escc
		escc = cd.Endorsement()
	}
    endorserLogger.Debugf("[%s][%s] escc for chaincode %s is %s", chainID, shorttxid(txid), ccid, escc)
  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)
}

看下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)
	// 判断response
	if ctx.Response == nil {
		return nil, errors.New("response is nil")
	}
	// 判断response.status
	if ctx.Response.Status >= shim.ERRORTHRESHOLD {
		return &pb.ProposalResponse{Response: ctx.Response}, nil
	}
  
  // 获取plugin
  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)
	}
	
  // 创建proposalResponsePayload bytes
	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")
	}
	
  // 执行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.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创建完以后,执行它的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从源码分析链码容器启动过程

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值