经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 其他 » 区块链 » 查看文章
Fabric1.4源码解析:Peer节点背书提案过程
来源:cnblogs  作者:触不可及`  时间:2019/6/25 10:08:22  对本文有异议

以前从来没有写过博客,从这段时间开始才开始写一些自己的博客,之前总觉得写一篇博客要耗费大量的时间,而且写的还是自己已经学会的,觉得没什么必要。但是当开始用博客记录下来的时候,才发现有些学会的地方只是自己觉得已经学会了,还是有太多地方需要学习,眼高手低了,所以以后会养成写博客的好习惯,保持记录。
今天记录一下之前阅读过的源码:Peer节点背书提案过程。

1 起点

首先定位到core/endorser/endorser.go这个文件中的ProcessProposal()方法在第450行。其实对于Peer节点背书提案的起点,并不是从源码中找到的,参考了这里,有兴趣的可以看一下,接下来就从ProcessProposal()这里开始分析:

  1. #该方法需要传入的参数有context(我理解为提案的上下文),以及已经签名的Proposal
  2. func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) {
  3. #首先获取Peer节点处理提案开始的时间
  4. startTime := time.Now()
  5. #Peer节点接收到的提案数+1
  6. e.Metrics.ProposalsReceived.Add(1)
  7. #从上下文中获取发起提案的地址
  8. addr := util.ExtractRemoteAddress(ctx)
  9. //日志输出
  10. endorserLogger.Debug("Entering: request from", addr)
  11. #这个不是链码ID,是通道ID
  12. var chainID string
  13. var hdrExt *pb.ChaincodeHeaderExtension
  14. var success bool
  15. #这个会在方法结束的时候调用
  16. defer func() {
  17. #判断chaincodeHeaderExtension是否为空,如果为空的话提案验证失败
  18. if hdrExt != nil {
  19. meterLabels := []string{
  20. "channel", chainID,
  21. "chaincode", hdrExt.ChaincodeId.Name + ":" + hdrExt.ChaincodeId.Version,
  22. "success", strconv.FormatBool(success),
  23. }
  24. e.Metrics.ProposalDuration.With(meterLabels...).Observe(time.Since(startTime).Seconds())
  25. }
  26. endorserLogger.Debug("Exit: request from", addr)
  27. }()
  28. #到了第一个重要的方法,对已签名的提案进行预处理,点进行看一下
  29. vr, err := e.preProcess(signedProp)

2 preProcess()

preProcess()这个方法在文件中的第366行:

  1. func (e *Endorser) preProcess(signedProp *pb.SignedProposal) (*validateResult, error) {
  2. #定义一个验证结果结构体
  3. vr := &validateResult{}
  4. #首先对MSG进行验证是否有效,看一下这个方法
  5. prop, hdr, hdrExt, err := validation.ValidateProposalMessage(signedProp)
  6. if err != nil {
  7. #如果报错的话,ProposalVaildationFailed+1
  8. e.Metrics.ProposalValidationFailed.Add(1)
  9. #返回500
  10. vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}
  11. return vr, err
  12. }

ValidateProposalMessage()core/common/validation/msgvalidation.go文件中,第75行,看一下这个方法主要就是对消息进行验证。

  1. #把主要的代码列举一下
  2. #从提案中获取Proposal内容
  3. ...
  4. prop, err := utils.GetProposal(signedProp.ProposalBytes)
  5. ...
  6. #从Proposal中获取Header
  7. hdr, err := utils.GetHeader(prop.Header)
  8. #对Header进行验证
  9. chdr, shdr, err := validateCommonHeader(hdr)

这里的Proposal以及Header结构体:

  1. Proposal:
  2. type Proposal struct {
  3. #关键的是前两个 提案的Header与提案的有效载荷
  4. Header []byte `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
  5. Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"`
  6. Extension []byte `protobuf:"bytes,3,opt,name=extension,proto3" json:"extension,omitempty"`
  7. XXX_NoUnkeyedLiteral struct{} `json:"-"`
  8. XXX_unrecognized []byte `json:"-"`
  9. XXX_sizecache int32 `json:"-"`
  10. }
  11. Header:
  12. type Header struct {
  13. #在提案的Header中又包含通道的Header与签名域的Header
  14. ChannelHeader []byte `protobuf:"bytes,1,opt,name=channel_header,json=channelHeader,proto3" json:"channel_header,omitempty"`
  15. SignatureHeader []byte `protobuf:"bytes,2,opt,name=signature_header,json=signatureHeader,proto3" json:"signature_header,omitempty"`
  16. XXX_NoUnkeyedLiteral struct{} `json:"-"`
  17. XXX_unrecognized []byte `json:"-"`
  18. XXX_sizecache int32 `json:"-"`
  19. }

看一下具体的验证方法,在第246行,依旧只列出主流程代码:

  1. #从提案的Header中获取通道Header信息
  2. chdr, err := utils.UnmarshalChannelHeader(hdr.ChannelHeader)

通道Header的结构体定义在protos/common/common.pb.go文件中第320行:

  1. type ChannelHeader struct {
  2. #类型
  3. Type int32 `protobuf:"varint,1,opt,name=type,proto3" json:"type,omitempty"`
  4. #版本
  5. Version int32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"`
  6. #时间戳
  7. Timestamp *timestamp.Timestamp `protobuf:"bytes,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
  8. #通道ID
  9. ChannelId string `protobuf:"bytes,4,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"`
  10. #交易ID
  11. TxId string `protobuf:"bytes,5,opt,name=tx_id,json=txId,proto3" json:"tx_id,omitempty"`
  12. #该Header产生的时间
  13. Epoch uint64 `protobuf:"varint,6,opt,name=epoch,proto3" json:"epoch,omitempty"`
  14. #额外的信息
  15. Extension []byte `protobuf:"bytes,7,opt,name=extension,proto3" json:"extension,omitempty"`
  16. TlsCertHash []byte `protobuf:"bytes,8,opt,name=tls_cert_hash,json=tlsCertHash,proto3" json:"tls_cert_hash,omitempty"`
  17. XXX_NoUnkeyedLiteral struct{} `json:"-"`
  18. XXX_unrecognized []byte `json:"-"`
  19. XXX_sizecache int32 `json:"-"`
  20. }

还是validateCommonHeader()这个方法:

  1. #获取签名域的Header
  2. shdr, err := utils.GetSignatureHeader(hdr.SignatureHeader)

SignatureHeader定义在protos/common/common.pb.go文件中第434行:

  1. type SignatureHeader struct {
  2. #消息的创建者
  3. Creator []byte `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"`
  4. #这个是为了防止重复攻击,具有唯一性
  5. Nonce []byte `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
  6. XXX_NoUnkeyedLiteral struct{} `json:"-"`
  7. XXX_unrecognized []byte `json:"-"`
  8. XXX_sizecache int32 `json:"-"`
  9. }

获取到channelHeaderSingatureHeader之后,可以对它们进行验证操作了:

  1. #验证channelHeader
  2. err = validateChannelHeader(chdr)

该方法在core/common/validation/msgvalidation.go文件中第214行:

  1. #首先检查channelHeader是否为空
  2. if cHdr == nil {
  3. return errors.New("nil ChannelHeader provided")
  4. }
  5. ...
  6. #然后对HeaderType进行检查,只有HeaderType是ENDORSER_TRANSACTION、CONFIG_UPDATE、CONFIG、TOKEN_TRANSACTION中其中一种才是有效的Header
  7. if common.HeaderType(cHdr.Type) != common.HeaderType_ENDORSER_TRANSACTION &&
  8. common.HeaderType(cHdr.Type) != common.HeaderType_CONFIG_UPDATE &&
  9. common.HeaderType(cHdr.Type) != common.HeaderType_CONFIG &&
  10. common.HeaderType(cHdr.Type) != common.HeaderType_TOKEN_TRANSACTION {
  11. return errors.Errorf("invalid header type %s", common.HeaderType(cHdr.Type))
  12. }
  13. ...
  14. #最后检查ChannelHeader中的Epoch是否为0
  15. if cHdr.Epoch != 0 {
  16. return errors.Errorf("invalid Epoch in ChannelHeader. Expected 0, got [%d]", cHdr.Epoch)
  17. }

验证SignatureHeader,该方法core/common/validation/msgvalidation.go文件中194行:

  1. #首先验证Header是否为空
  2. if sHdr == nil {
  3. return errors.New("nil SignatureHeader provided")
  4. }
  5. #Nonce是否为空
  6. if sHdr.Nonce == nil || len(sHdr.Nonce) == 0 {
  7. return errors.New("invalid nonce specified in the header")
  8. }
  9. #该Header创建者是否为空
  10. if sHdr.Creator == nil || len(sHdr.Creator) == 0 {
  11. return errors.New("invalid creator specified in the header")
  12. }

所以对ChannelHeader的检查主要是这三部分:

  • ChannelHeader是否为空
  • HeaderType是否是ENDORSER_TRANSACTION、CONFIG_UPDATE、CONFIG、TOKEN_TRANSACTION中其中一种
  • Epoch是否为空

SignatureHeader的检查为:

  • SignatureHeader是否为空
  • Nonce是否为空
  • SignatureHeader的创建者是否为空

在对ChannelHeaderSignatureHeader的验证完成后,回到ValidateProposalMessage方法:

  1. #接下来是对Creator的Signature进行验证:
  2. err = checkSignatureFromCreator(shdr.Creator, signedProp.Signature, signedProp.ProposalBytes, chdr.ChannelId)

点进行,该方法在core/common/validation/msgvalidation.go文件中第153行:

  1. #首先检查是否有空参数
  2. if creatorBytes == nil || sig == nil || msg == nil {
  3. return errors.New("nil arguments")
  4. }
  5. #根据通道Id获取Identity返回mspObj(member service providere)对象
  6. mspObj := mspmgmt.GetIdentityDeserializer(ChainID)
  7. if mspObj == nil {
  8. return errors.Errorf("could not get msp for channel [%s]", ChainID)
  9. }
  10. #然后对Creator的identity进行查找
  11. creator, err := mspObj.DeserializeIdentity(creatorBytes)
  12. if err != nil {
  13. return errors.WithMessage(err, "MSP error")
  14. }
  15. ...
  16. #对证书进行验证
  17. err = creator.Validate()
  18. ...
  19. #对签名进行验证
  20. err = creator.Verify(msg, sig)

最后看一下checkSignatureFromCreator做了哪些工作:

  • 验证Creator、Signature、ProposalBytes是否有空参数
  • 根据ChannelId获取Identity
  • 根据获取到的Identity查找CreatorIdentity
  • 验证Creator的证书与签名

回到ValidateProposalMessage方法,再向下看:

  1. if err != nil {
  2. #当之前一步验证失败后进入这里。
  3. #这一部分做了两件事
  4. #1.将虚假的用户记录到Peer节点,防止该用户对通道进行扫描
  5. putilsLogger.Warningf("channel [%s]: %s", chdr.ChannelId, err)
  6. sId := &msp.SerializedIdentity{}
  7. err := proto.Unmarshal(shdr.Creator, sId)
  8. if err != nil {
  9. err = errors.Wrap(err, "could not deserialize a SerializedIdentity")
  10. putilsLogger.Warningf("channel [%s]: %s", chdr.ChannelId, err)
  11. }
  12. #2.将错误信息返回,这一条信息应该见过好多次
  13. return nil, nil, nil, errors.Errorf("access denied: channel [%s] creator org [%s]", chdr.ChannelId, sId.Mspid)
  14. }
  15. #这一步用于检查TxId是否已经存在,防止重复攻击
  16. err = utils.CheckTxID(
  17. chdr.TxId,
  18. shdr.Nonce,
  19. shdr.Creator)
  20. if err != nil {
  21. return nil, nil, nil, err
  22. }
  23. #方法的最后了,判断Header的类型
  24. switch common.HeaderType(chdr.Type) {
  25. #从这里可以看到,不论Header类型为CONFIG,还是ENDORSER_TRANSACTION都会进入下面的validateChaincodeProposalMessage方法,如果Header类型不是以上两种,返回不支持的proposal类型
  26. case common.HeaderType_CONFIG:
  27. fallthrough
  28. case common.HeaderType_ENDORSER_TRANSACTION:
  29. chaincodeHdrExt, err := validateChaincodeProposalMessage(prop, hdr)
  30. if err != nil {
  31. return nil, nil, nil, err
  32. }
  33. return prop, hdr, chaincodeHdrExt, err
  34. default:
  35. return nil, nil, nil, errors.Errorf("unsupported proposal type %d", common.HeaderType(chdr.Type))
  36. }

看一下validateChaincodeProposalMessage方法,在core/common/validation/msgvalidation.go中第36行:

  1. #验证proposal header是否为空
  2. if prop == nil || hdr == nil {
  3. return nil, errors.New("nil arguments")
  4. }
  5. ...
  6. #一些扩展信息,不再解释
  7. chaincodeHdrExt, err := utils.GetChaincodeHeaderExtension(hdr)
  8. if err != nil {
  9. return nil, errors.New("invalid header extension for type CHAINCODE")
  10. }
  11. #链码Id是否为空
  12. if chaincodeHdrExt.ChaincodeId == nil {
  13. return nil, errors.New("ChaincodeHeaderExtension.ChaincodeId is nil")
  14. }
  15. ...
  16. #有效载荷是否为空
  17. if chaincodeHdrExt.PayloadVisibility != nil {
  18. return nil, errors.New("invalid payload visibility field")
  19. }

如果没有问题的话ValidateProposalMessage()方法就结束了,回到preProcess()方法中接着往下:

  1. ...
  2. #获取通道头信息
  3. chdr, err := putils.UnmarshalChannelHeader(hdr.ChannelHeader)
  4. ...
  5. #获取签名头信息
  6. shdr, err := putils.GetSignatureHeader(hdr.SignatureHeader)
  7. ...
  8. 判断是否调用的是不可被外部调用的系统链码
  9. if e.s.IsSysCCAndNotInvokableExternal(hdrExt.ChaincodeId.Name) {
  10. ...
  11. return vr, err
  12. }
  13. ...
  14. #判断通道Id是否为空
  15. if chainID != "" {
  16. ...
  17. #通道ID不为空则查找该TxID是否已经存在
  18. if _, err = e.s.GetTransactionByID(chainID, txid); err == nil {
  19. ...
  20. }
  21. #判断是否为系统链码
  22. if !e.s.IsSysCC(hdrExt.ChaincodeId.Name) {
  23. #如果不是系统链码,则检查ACL(访问权限)
  24. if err = e.s.CheckACL(signedProp, chdr, shdr, hdrExt); err != nil {
  25. ...
  26. return vr, err
  27. }
  28. }
  29. }else{
  30. #如果通道ID为空的话什么也不做
  31. }
  32. vr.prop, vr.hdrExt, vr.chainID, vr.txid = prop, hdrExt, chainID, txid
  33. return vr, nil

总结一下preProcess()方法所做的工作:

  1. 从签名的提案中获取ProposalHeader
  2. 从获取的Header中获取ChannelHeaderSignatureHeader
  3. ChannelHeaderSignatureHeader进行验证。
    1. 验证ChannelHeader
      1. ChannelHeader是否为空。
      2. HeaderType类型是否为ENDORSER_TRANSACTION、CONFIG_UPDATE、CONFIG、TOKEN_TRANSACTION中其中一种。
      3. Epoch是否为空。
    2. 验证SignatureHeader
      1. SignatureHeader是否为空。
      2. Nonce是否为空。
      3. SignatureHeader的创建者是否为空。
  4. 验证Creator、Signature、ProposalBytes是否为空。
  5. 根据通道Id获取Identity
  6. Identity中查找Creator的证书等信息。
  7. 验证Creator的证书和签名信息。
  8. 检查该TxID是否已经存在,防止重复攻击。
  9. 验证链码提案消息。
  10. 获取ChannelHeader,SignatureHeader
  11. 判断是否调用的是不允许被外部调用的系统链码。
  12. 判断通道ID是否为空,如果为空则什么也不做直接返回。
  13. 通道ID不为空则检查该TxID是否已经存在,防止重复攻击。
  14. 判断是否为系统链码,如果不是系统链码则检查提案中的权限。

到这里,预处理提案过程已经完成,回到ProcessProposal()这个主方法,接着往下:

  1. if err != nil {
  2. resp := vr.resp
  3. return resp, err
  4. }
  5. prop, hdrExt, chainID, txid := vr.prop, vr.hdrExt, vr.chainID, vr.txid
  6. #这里定义了一个Tx模拟器,用于后面的模拟交易过程,如果通道Id为空,那么TxSimulator也是空
  7. var txsim ledger.TxSimulator
  8. #定义一个历史记录查询器
  9. var historyQueryExecutor ledger.HistoryQueryExecutor
  10. #这里判断是否需要Tx模拟
  11. if acquireTxSimulator(chainID, vr.hdrExt.ChaincodeId) {
  12. #如果需要进行模拟的话,根据通道ID获取Tx模拟器
  13. if txsim, err = e.s.GetTxSimulator(chainID, txid); err != nil {
  14. return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, nil
  15. }
  16. #等待Tx模拟完成,最后执行
  17. defer txsim.Done()
  18. #获取历史记录查询器
  19. if historyQueryExecutor, err = e.s.GetHistoryQueryExecutor(chainID); err != nil {
  20. return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, nil
  21. }
  22. }

看一下acquireTxSimulator()方法,怎么判断是否需要进行TX模拟的:

  1. func acquireTxSimulator(chainID string, ccid *pb.ChaincodeID) bool {
  2. #如果通道ID为空,就说明不需要进行Tx的模拟
  3. if chainID == "" {
  4. return false
  5. }
  6. #通道ID不为空,则判断链码的类型,如果是qscc(查询系统链码),cscc(配置系统链码),则不需要进行Tx模拟
  7. switch ccid.Name {
  8. case "qscc", "cscc":
  9. return false
  10. default:
  11. return true
  12. }
  13. }

回到ProcessProposal()方法中,接下来到了第二个重要的方法了:

  1. #首先定义一个交易参数结构体,用于下面的方法,里面的字段之前都有说过,这里不再解释
  2. txParams := &ccprovider.TransactionParams{
  3. ChannelID: chainID,
  4. TxID: txid,
  5. SignedProp: signedProp,
  6. Proposal: prop,
  7. TXSimulator: txsim,
  8. HistoryQueryExecutor: historyQueryExecutor,
  9. }
  10. #这一行代码就是对交易进行模拟,点进去看一下
  11. cd, res, simulationResult, ccevent, err := e.SimulateProposal(txParams, hdrExt.ChaincodeId)

3 SimulateProposal()

该方法主要是Peer节点模拟提案过程,但是不会写入到区块中,当Peer节点模拟完一项提案,将模拟结果保存至读写集。看一下SimulateProposal()中的具体执行流程,在core/endorser/endorser.go文件中第216行:

  1. func (e *Endorser) SimulateProposal(txParams *ccprovider.TransactionParams, cid *pb.ChaincodeID) (ccprovider.ChaincodeDefinition, *pb.Response, []byte, *pb.ChaincodeEvent, error)
  2. #该方法传入的参数有TransactionParams、ChaincodeID,返回的参数有ChaincodeDefinition,Response,ChaincodeEvent,error
  3. #TransactionParams之前有提到,ChaincodeID用于确定所调用的链码,ChaincodeDefinition是链码标准数据结构,Response是链码的响应信息,以及链码事件.
  4. type ChaincodeDefinition interface {
  5. #链码名称
  6. CCName() string
  7. #返回的链码的HASH值
  8. Hash() []byte
  9. #链码的版本
  10. CCVersion() string
  11. #返回的是验证链码上提案的方式,通常是vscc
  12. Validation() (string, []byte)
  13. #返回的是背书链码上提案的方式,通常是escc
  14. Endorsement() string
  15. }

看一下方法中的内容:

  1. #首先获取链码调用的细节
  2. cis, err := putils.GetChaincodeInvocationSpec(txParams.Proposal)

GetChaincodeInvocationSpec()方法在protos/utils/proputils.go文件中第25行:

  1. func GetChaincodeInvocationSpec(prop *peer.Proposal) (*peer.ChaincodeInvocationSpec, error) {
  2. ...
  3. #仅仅调用了获取Header的方法,并没有去获取Header,相当于对Header进行验证
  4. _, err := GetHeader(prop.Header)
  5. if err != nil {
  6. return nil, err
  7. }
  8. #从链码提案中获取有效载荷
  9. ccPropPayload, err := GetChaincodeProposalPayload(prop.Payload)
  10. if err != nil {
  11. return nil, err
  12. }
  13. #定义一个ChaincodeInvocationSpec结构,该结构体包含链码的功能与参数,在这里相当于将提案中所调用的链码功能与参数封装成一个ChaincodeInvocationSpec结构。
  14. cis := &peer.ChaincodeInvocationSpec{}
  15. err = proto.Unmarshal(ccPropPayload.Input, cis)
  16. #最后将其返回
  17. return cis, errors.Wrap(err, "error unmarshaling ChaincodeInvocationSpec")
  18. }

继续往下看,紧接着定义了一个ChaincodeDefinition,和一个保存版本信息的字符串:

  1. var cdLedger ccprovider.ChaincodeDefinition
  2. var version string

这里有一个分支,判断是否是调用的系统链码:

  1. if !e.s.IsSysCC(cid.Name) {
  2. #如果不是系统链码,首先获取链码的标准数据结构
  3. cdLedger, err = e.s.GetChaincodeDefinition(cid.Name, txParams.TXSimulator)
  4. if err != nil {
  5. return nil, nil, nil, nil, errors.WithMessage(err, fmt.Sprintf("make sure the chaincode %s has been successfully instantiated and try again", cid.Name))
  6. }
  7. #获取用户链码版本
  8. version = cdLedger.CCVersion()
  9. #检查链码实例化策略
  10. err = e.s.CheckInstantiationPolicy(cid.Name, version, cdLedger)
  11. if err != nil {
  12. return nil, nil, nil, nil, err
  13. }
  14. } else {
  15. #如果调用的是系统链码,仅仅获取系统链码的版本
  16. version = util.GetSysCCVersion()
  17. }

到这里,模拟提案的准备工作已经完成,还定义了一些字段:

  1. #定义一个Tx模拟结果集
  2. var simResult *ledger.TxSimulationResults
  3. #一个byte数组,保存public的模拟响应结果
  4. var pubSimResBytes []byte
  5. #响应信息
  6. var res *pb.Response
  7. #链码事件
  8. var ccevent *pb.ChaincodeEvent
  9. type TxSimulationResults struct {
  10. #可以看到Tx模拟结果集里面保存公共的与私有的读写集
  11. PubSimulationResults *rwset.TxReadWriteSet
  12. PvtSimulationResults *rwset.TxPvtReadWriteSet
  13. }
  14. #链码事件结构体
  15. type ChaincodeEvent struct {
  16. #链码Id
  17. ChaincodeId string `protobuf:"bytes,1,opt,name=chaincode_id,json=chaincodeId,proto3" json:"chaincode_id,omitempty"`
  18. #交易Id
  19. TxId string `protobuf:"bytes,2,opt,name=tx_id,json=txId,proto3" json:"tx_id,omitempty"`
  20. #事件名称
  21. EventName string `protobuf:"bytes,3,opt,name=event_name,json=eventName,proto3" json:"event_name,omitempty"`
  22. #有效载荷
  23. Payload []byte `protobuf:"bytes,4,opt,name=payload,proto3" json:"payload,omitempty"`
  24. XXX_NoUnkeyedLiteral struct{} `json:"-"`
  25. XXX_unrecognized []byte `json:"-"`
  26. XXX_sizecache int32 `json:"-"`
  27. }

到这里,就开始执行链码进行模拟了:

  1. res, ccevent, err = e.callChaincode(txParams, version, cis.ChaincodeSpec.Input, cid)

4 callChaincode()

又是一个重要的方法,调用具体的链码(包括系统链码与用户链码),进去看一下执行逻辑,该方法在第133行:

  1. func (e *Endorser) callChaincode(txParams *ccprovider.TransactionParams, version string, input *pb.ChaincodeInput, cid *pb.ChaincodeID) (*pb.Response, *pb.ChaincodeEvent, error) {
  2. ...
  3. #看名字应该是记录链码执行时间的
  4. defer func(start time.Time) {
  5. logger := endorserLogger.WithOptions(zap.AddCallerSkip(1))
  6. elapsedMilliseconds := time.Since(start).Round(time.Millisecond) / time.Millisecond
  7. logger.Infof("[%s][%s] Exit chaincode: %s (%dms)", txParams.ChannelID, shorttxid(txParams.TxID), cid, elapsedMilliseconds)
  8. }(time.Now())
  9. #定义了一些字段
  10. var err error
  11. var res *pb.Response
  12. var ccevent *pb.ChaincodeEvent
  13. #执行链码,如果是用户链码具体怎么执行的要看用户写的链码逻辑,执行完毕后返回响应信息与链码事件
  14. res, ccevent, err = e.s.Execute(txParams, txParams.ChannelID, cid.Name, version, txParams.TxID, txParams.SignedProp, txParams.Proposal, input)
  15. #这里说明一下,状态常量一共有三个:OK = 200 ERRORTHRESHOLD = 400 ERROR = 500 大于等于400就是错误信息或者被背书节点拒绝。
  16. if res.Status >= shim.ERRORTHRESHOLD {
  17. return res, nil, nil
  18. }

再往下看,一个if语句,判断调用的链码是否为lscc,如果是lscc判断传入的参数是否大于等于3,并且调用的方法是否为deploy或者upgrade,如果是用户链码到这是方法就结束了。

  1. if cid.Name == "lscc" && len(input.Args) >= 3 && (string(input.Args[0]) == "deploy" || string(input.Args[0]) == "upgrade") {
  2. #获取链码部署的基本结构,deploy与upgrade都需要对链码进行部署
  3. userCDS, err := putils.GetChaincodeDeploymentSpec(input.Args[2], e.PlatformRegistry)
  4. ...
  5. #这一行代码没有搞清楚啥意思
  6. cds, err = e.SanitizeUserCDS(userCDS)
  7. if err != nil {
  8. return nil, nil, err
  9. }
  10. ...
  11. #执行链码的Init,具体如何执行的这里就不再看了,不然内容更多了
  12. _, _, err = e.s.ExecuteLegacyInit(txParams, txParams.ChannelID, cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version, txParams.TxID, txParams.SignedProp, txParams.Proposal, cds)
  13. ...
  14. }

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

  1. ...
  2. 如果TXSimulator不为空,说明大部分是有账本有关的操作
  3. if txParams.TXSimulator != nil {
  4. #GetTxSimulationResults()获取Tx模拟结果集
  5. if simResult, err = txParams.TXSimulator.GetTxSimulationResults(); err != nil {
  6. txParams.TXSimulator.Done()
  7. return nil, nil, nil, nil, err
  8. }
  9. #之前提到Tx模拟结果集中不仅仅只有公共读写集,还有私有的读写集,接下来判断私有的读写集是否为空:
  10. if simResult.PvtSimulationResults != nil {
  11. #判断链码Id是否为lscc
  12. if cid.Name == "lscc" {
  13. 如果为生命周期系统链码,返回错误信息
  14. txParams.TXSimulator.Done()
  15. #私有数据禁止用于实例化操作
  16. return nil, nil, nil, nil, errors.New("Private data is forbidden to be used in instantiate")
  17. }
  18. #好像与配置有关,没有看明白
  19. pvtDataWithConfig, err := e.AssemblePvtRWSet(simResult.PvtSimulationResults, txParams.TXSimulator)
  20. #读取配置信息需要在更新配置信息释放锁之前,等待执行完成
  21. txParams.TXSimulator.Done()
  22. ...
  23. #获取账本的高度
  24. endorsedAt, err := e.s.GetLedgerHeight(txParams.ChannelID)
  25. pvtDataWithConfig.EndorsedAt = endorsedAt
  26. #应该是更新数据了,可能理解的不对
  27. if err := e.distributePrivateData(txParams.ChannelID, txParams.TxID, pvtDataWithConfig, endorsedAt); err != nil {
  28. return nil, nil, nil, nil, err
  29. }
  30. }
  31. txParams.TXSimulator.Done()
  32. #获取公共模拟数据
  33. if pubSimResBytes, err = simResult.GetPubSimulationBytes(); err != nil {
  34. return nil, nil, nil, nil, err
  35. }
  36. }
  37. #最后返回
  38. return cdLedger, res, pubSimResBytes, ccevent, nil

到这里提案的模拟完成了,下一步就是背书过程了,感觉整个流程还是挺长的,先回到主方法,继续往下走:

  1. cd, res, simulationResult, ccevent, err := e.SimulateProposal(txParams, hdrExt.ChaincodeId)
  2. if err != nil {
  3. return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, nil
  4. }
  5. //如果响应不为空
  6. if res != nil {
  7. //如果状态大于等于ERROR,就是发生错误之后的逻辑,这里不再说了
  8. if res.Status >= shim.ERROR {
  9. ...
  10. return pResp, nil
  11. }
  12. }
  13. #定义一个提案响应字段
  14. var pResp *pb.ProposalResponse
  15. if chainID == "" {
  16. #如果通道ID为空就直接返回了
  17. pResp = &pb.ProposalResponse{Response: res}
  18. } else {
  19. #通道Id不为空,开始进行背书操作了,这是到了第三个重要的方法
  20. pResp, err = e.endorseProposal(ctx, chainID, txid, signedProp, prop, res, simulationResult, ccevent, hdrExt.PayloadVisibility, hdrExt.ChaincodeId, txsim, cd)
  21. #先把下面的说完好了,整个流程马上就结束了
  22. #背书完成后定义一个标签,保存通道与链码信息
  23. meterLabels := []string{
  24. "channel", chainID,
  25. "chaincode", hdrExt.ChaincodeId.Name + ":" + hdrExt.ChaincodeId.Version,
  26. }
  27. #简单来说,这里就是发生ERROR之后的处理,不再细看
  28. if err != nil {
  29. ...
  30. }
  31. if pResp.Response.Status >= shim.ERRORTHRESHOLD {
  32. ...
  33. return pResp, nil
  34. }
  35. }
  36. pResp.Response = res
  37. #提案成功的数量+1
  38. e.Metrics.SuccessfulProposals.Add(1)
  39. success = true
  40. #返回提案的响应信息
  41. return pResp, nil
  42. }
  43. #到这里整个提案的处理流程就结束了,最后再看一下背书流程

5 endorseProposal()

这个方法在309行:
未完待续.......

原文链接:http://www.cnblogs.com/cbkj-xd/p/11077490.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号