在之前的文章中
都提到了类似下面这一行的代码
proposalResponse, err := cf.EndorserClients[0].ProcessProposal(context.Background(), signedProp)
这一行代码是由客户端向背书节点发起背书提案申请,获取背书响应的过程,即客户端应用程序发送交易提案,背书节点模拟执行,并将模拟执行结果返回客户端应用程序的过程。
之前的三篇文章中,我们了解了客户端是如何创建一个交易提案并发送交易提案,那么今天这篇文章就来探讨一下背书节点是如何处理一个交易提案的。模拟执行交易提案是一个经常用到的过程,ProcessProposal()
函数也是一个经常被用到的函数,因为只要是客户端调用链码发起交易提案,都会执行这个过程。
好的,下面就来看一下这个经典的背书提案过程吧
熟悉 Endorser Service
我们从 EndorserClient
的ProcessProposal()
函数入手,点进去看一下,在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
实例的一个过程:
- 从
peer.pb.go
中发现服务注册函数RegisterEndorserServer()
- 追溯过去,找到函数调用在
peer/node/start.go
中 - 找到
RegisterEndorserServer()
参数来源,第一个参数是grpc.Server
不用关心,第二个参数是EndorserServer
,看下第二个参数auth
如何获得 - 追溯到
ChainFilters()
函数中,发现主要是第一个参数endorser
起到最终决定作用,再追溯这个参数如何获得 - 追溯到
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
}
ValidateProposalMessage
在core/common/validation/msgvalidation.go
76行:
// 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
}
主要是验证了ChannelHeader
和SignatureHeader
两个 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
}