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   
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值