fabric-sdk-go不如Node.js SDK和Java SDK完善,提供的接口功能有限。例如虽然它提供了一个通过交易ID查询交易的方法,但直接返回的信息不易读,在使用中带来了不便 。为此,笔者研究了一下怎样解析交易详情并做相关记录。

本文参考了 亚楠老猎人的《令人懊恼的阉割版fabric sdk功能缺失》, 在此对其表示感谢!

本文环境为Fabric-1.4.6版本,fabric-go-sdk为v1.0.0-rc1版本。

一、QueryTransaction接口

和以太坊类似,fabric提交一笔写交易后会得到一个交易ID,我们可以根据这个交易ID去查询交易相关信息,然而fabric-sdk-go中提供的查询接口QueryTransaction返回的信息很有限(或者说不易读)。

这里是GoDoc中的该接口的相关文档,有兴趣的读者可以看一下,QueryTransaction,如果没有兴趣,接着往下看。

通过交易ID查询交易的接口如下:

func (c *Client) QueryTransaction(transactionID fab.TransactionID, options ...RequestOption) (*pb.ProcessedTransaction, error)

该函数的返回值对应的结构体为:

type ProcessedTransaction struct {// An Envelope which includes a processed transactionTransactionEnvelope *common.Envelope `protobuf:"bytes,1,opt,name=transactionEnvelope,proto3" json:"transactionEnvelope,omitempty"`// An indication of whether the transaction was validated or invalidated by committing peerValidationCode       int32    `protobuf:"varint,2,opt,name=validationCode,proto3" json:"validationCode,omitempty"`XXX_NoUnkeyedLiteral struct{} `json:"-"`XXX_unrecognized     []byte   `json:"-"`XXX_sizecache        int32    `json:"-"`
}

可以看到,有一个ValidationCode字段代表验证状态码,我们再查看一下TransactionEnvelope对应的common.Envelope结构。

type Envelope struct {// A marshaled PayloadPayload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`// A signature by the creator specified in the Payload headerSignature            []byte   `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`XXX_NoUnkeyedLiteral struct{} `json:"-"`XXX_unrecognized     []byte   `json:"-"`XXX_sizecache        int32    `json:"-"`
}

可以看到,它有一个Signature,代表签名,可以直接转化成字符串。另外一个有用字段为Payload,也就是交易数据了,但是为[]byte类型,无法直接获取其内容,需要自己解析了。

这里可以看到,fabric-sdk-go提供的QueryTransaction接口返回的直接有用的数据只有验证状态码和签名,其Payload需要解析,但其并没有提供直接解析的方法,这个就需要自己动手解决了。

二、一些有用的内部接口

经过研究,发现fabric-sdk-go其实自带了相应的一些解析接口,只是都是些内部接口,需要自己根据实际需求来组合。话不多说,直接切重点,上代码。

所有用到的接口均位于:fabric-sdk-go/internal/github.com/hyperledger/fabric/protoutil/unmarshalers.go中,例如如下代码片断:

// UnmarshalPayload unmarshals bytes to a Payload
func UnmarshalPayload(encoded []byte) (*cb.Payload, error) {payload := &cb.Payload{}err := proto.Unmarshal(encoded, payload)return payload, errors.Wrap(err, "error unmarshaling Payload")
}

这个方法就是解析上面提到的Payload的。

本文中所有解析都是基于unmarshalers.go提供的方法。

三、解析基本交易信息

有眼尖的读者可能看到了,上面提到的unmarshalers.go位于internal下面的子目录下,也就是对应的包protoutil为一个内部包,在外部无法直接引用。为此,我们需要稍微修改一下fabric-sdk-go,在fabric-sdk-go目录下添加一个自定义的包来引用这些内部包。

在该目录下建立一个myutils包,并在该包下建立一个myutils.go文件,代码如下:

package myutilsimport ("encoding/pem""fmt""time""github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric/protoutil""github.com/tjfoc/gmsm/sm2"
)//TransactionInfo 解析后的交易信息
type TransactionInfo struct {CreateTime       string   //交易创建时间ChaincodeID      string   //交易调用链码IDChaincodeVersion string   //交易调用链码版本Nonce            []byte   //随机数Mspid            string   //交易发起者MSPIDName             string   //交易发起者名称OUTypes          string   //交易发起者OU分组Args             []string //输入参数Type             int32    //交易类型TxID             string   //交易ID
}// UnmarshalTransaction 从某个交易的payload来解析它
func UnmarshalTransaction(payloadRaw []byte) (*TransactionInfo, error) {result := &TransactionInfo{}//解析成payloadpayload, err := protoutil.UnmarshalPayload(payloadRaw)if err != nil {return nil, err}//解析成ChannelHeader(包含通道ID、交易ID及交易创建时间等)channelHeader, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader)if err != nil {return nil, err}//解析成SignatureHeader(包含创建者和nonce)signHeader, err := protoutil.UnmarshalSignatureHeader(payload.Header.SignatureHeader)if err != nil {return nil, err}//解析成SerializedIdentity(包含证书和MSPID)identity, err := protoutil.UnmarshalSerializedIdentity(signHeader.GetCreator())if err != nil {return nil, err}//下面为解析证书block, _ := pem.Decode(identity.GetIdBytes())if block == nil {return nil, fmt.Errorf("identity could not be decoded from credential")}cert, err := sm2.ParseCertificate(block.Bytes)if err != nil {return nil, fmt.Errorf("failed to parse certificate: %s", err)}//解析用户名和OU分组uname := cert.Subject.CommonNameoutypes := cert.Subject.OrganizationalUnit//解析成transactiontx, err := protoutil.UnmarshalTransaction(payload.Data)if err != nil {return nil, err}//进一步从transaction中解析成ChaincodeActionPayloadchaincodeActionPayload, err := protoutil.UnmarshalChaincodeActionPayload(tx.Actions[0].Payload)if err != nil {return nil, err}//进一步解析成proposalPayloadproposalPayload, err := protoutil.UnmarshalChaincodeProposalPayload(chaincodeActionPayload.ChaincodeProposalPayload)if err != nil {return nil, err}//得到交易调用的链码信息chaincodeInvocationSpec, err := protoutil.UnmarshalChaincodeInvocationSpec(proposalPayload.Input)if err != nil {return nil, err}//得到调用的链码的ID,版本和PATH(这里PATH省略了)result.ChaincodeID = chaincodeInvocationSpec.ChaincodeSpec.ChaincodeId.Nameresult.ChaincodeVersion = chaincodeInvocationSpec.ChaincodeSpec.ChaincodeId.Version//得到输入参数var args []stringfor _, v := range chaincodeInvocationSpec.ChaincodeSpec.Input.Args {args = append(args, string(v))}result.Args = argsresult.Nonce = signHeader.GetNonce()result.Type = channelHeader.GetType()result.TxID = channelHeader.GetTxId()result.Mspid = identity.GetMspid()result.Name = unameresult.OUTypes = outypes[0]result.CreateTime = time.Unix(channelHeader.Timestamp.Seconds, 0).Format("2006-01-02 15:04:05")return result, nil
}

需要说明的是,因为使用了国密版本,所以这里导入了sm2包。

从这里可以看出,我们已经解析了几乎所有关键的交易信息。这里面也还有一些其它属性笔者并未获取或者解析,有兴趣的读者可以自己试着获取一下。

四、解析区块编号与区块哈希

从上面的解析我们可以看出,它只能解析出交易本身的信息。如果有人问,这个交易在哪个区块呢?显然,上面的解析是无法直接提供答案的,我们还需要手动去获取它。

4.1、获得交易所在区块

具体接口为QueryBlockByTxID,它在fabric-sdk-go/pkg/client/ledger/ledger.go文件中。该方法定义为:

func (c *Client) QueryBlockByTxID(txID fab.TransactionID, options ...RequestOption) (*common.Block, error)

它返回的区块基本信息(不包含区块数据)在BlockHeader结构体中。该结构体定义如下:

type BlockHeader struct {Number               uint64   `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`PreviousHash         []byte   `protobuf:"bytes,2,opt,name=previous_hash,json=previousHash,proto3" json:"previous_hash,omitempty"`DataHash             []byte   `protobuf:"bytes,3,opt,name=data_hash,json=dataHash,proto3" json:"data_hash,omitempty"`XXX_NoUnkeyedLiteral struct{} `json:"-"`XXX_unrecognized     []byte   `json:"-"`XXX_sizecache        int32    `json:"-"`
}

从上面可以看出,得到的有用信息只有区块编号、前一区块哈希和数据哈希,并不包含区块本身哈希(显然不能包含区块本身哈希,因为这样会形成自嵌套无法计算)。

那么怎么得到本区块哈希呢?注意到fabric是区块链,所有的区块链基本上都是后一个区块通过前一区块的哈希来链接到前一区块(区块链这个名称就是这么来的),所以说我们只要得到后一区块的信息就能得到这个区块的哈希了。

4.2、获取通道信息

然而这里还有些要注意,如果本区块为最后区块,那么就没有后一个区块了。我们首先查询通道信息,得到特定通道的区块高度。具体接口为:QueryInfo,该函数定义为:

func (c *Client) QueryInfo(options ...RequestOption) (*fab.BlockchainInfoResponse, error)

BlockchainInfoResponse的定义为:

type BlockchainInfoResponse struct {BCI      *common.BlockchainInfoEndorser stringStatus   int32
}

BlockchainInfo的定义为:

type BlockchainInfo struct {Height               uint64   `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"`CurrentBlockHash     []byte   `protobuf:"bytes,2,opt,name=currentBlockHash,proto3" json:"currentBlockHash,omitempty"`PreviousBlockHash    []byte   `protobuf:"bytes,3,opt,name=previousBlockHash,proto3" json:"previousBlockHash,omitempty"`XXX_NoUnkeyedLiteral struct{} `json:"-"`XXX_unrecognized     []byte   `json:"-"`XXX_sizecache        int32    `json:"-"`
}

从上面的定义可以看出,我们可以得到当前区块高度和当前区块哈希。

4.3、比较通道区块高度和查询的区块编号

这里要注意,区块高度和编号是两个概念,编号从0开始,是高度值-1,相当于数组长度和下标的关系。

将通道区块高度和查询的区块编号进行比较:

  1. 如果查询区块为通道最新区块,则通道当前区块哈希就是查询区块哈希

  2. 如果查询区块不为通道最新区块,后面还有区块,则查询后一区块信息来得到本区块的哈希值。具体接口为:QueryBlock

    其定义为(blockNumber为区块编号):

    func (c *Client) QueryBlock(blockNumber uint64, options ...RequestOption) (*common.Block, error)
    

    返回的内容在上面已经介绍过了。

五、解析结果示例

解析出来的结果类似如下格式(已经转化为json)。

{"validationcode": 0,"signature": "MEQCIAexXLnk2dhlxGjVLBcpNSOshb8iJhpC4V6IZ0U8R6IeAiACinGhT+Vy0TYb4G6E1Sutb5MDl0Gv2pm64ctN3BvWgg==","BlockNumber": 14,"BlockHash": "T7K7Or3fw1ZR5PUfV/kBVKSLXuDZN1LnPWmxL/1sXF4=","Info": {"CreateTime": "2020-12-09 22:33:01","ChaincodeID": "mycc","ChaincodeVersion": "","Nonce": "kxXeBesXSmxEgkejqMdiTYC/YoBh6pKs","Mspid": "Org1MSP","Name": "user1","OUTypes": "client","Args": ["invoke","a","b","10"],"Type": 3,"TxID": "a7a80a1ac6eb50cf699d21f37bf37d3a9e67d46d12bbe08174e8757816ac7358"}}

好了,我们需要的交易信息终于解析完了。以后如果有人拿着一个交易ID问,这个交易上链了没有,我们可以从容不迫的给他显示相关信息了。
注意这里链码版本得到的就是为空。

由于笔者接触fabric时间不长,能力有限,如果文章有中错误或者需要改进,欢迎大家留言提出。

fabric-sdk-go解析交易数据详解相关推荐

  1. java解析json数据_java解析JSON数据详解

    JSON是目前最流行的轻量级数据交换语言(没有之一).尽管他是javaScript的一个子集.但由于其是独立与语言的文本格式,它几乎可以被所有编程语言所支持. 以下是对java语言中解析json数据的 ...

  2. JSONObject 和 GSON 解析 JSON 数据详解(转)

    转载:http://www.jianshu.com/p/f99de3ec0636 点此进入:从零快速构建APP系列目录导图 点此进入:UI编程系列目录导图 点此进入:四大组件系列目录导图 点此进入:数 ...

  3. android json格式解析,android之解析json数据格式详解

    1.JSON解析 (1).解析Object之一: 解析方法: 1 JSONObject demoJson =newJSONObject(jsonString); 2 String url = demo ...

  4. dicom多帧转换_Python解析多帧dicom数据详解

    概述 pydicom是一个常用python DICOM parser.但是,没有提供解析多帧图的示例.本文结合相关函数和DICOM知识做一个简单说明. DICOM多帧数据存储 DICOM标准中关于多帧 ...

  5. 爬虫解析利器PyQuery详解及使用实践

    作者:叶庭云 整理:Lemon 爬虫解析利器 PyQuery详解及使用实践 之前跟大家分享了 selenium.Scrapy.Pyppeteer 等工具的使用. 今天来分享另一个好用的爬虫解析工具 P ...

  6. InheritableThreadLocal类原理简介使用 父子线程传递数据详解 多线程中篇(十八)...

    上一篇文章中对ThreadLocal进行了详尽的介绍,另外还有一个类: InheritableThreadLocal 他是ThreadLocal的子类,那么这个类又有什么作用呢? 测试代码 publi ...

  7. 浏览器解析html全过程详解

    前端文摘:深入解析浏览器的幕后工作原理 关于浏览器解析html全过程详解 输入URL到浏览器接收返回的数据的整个过程 TCP报文格式详解 IP报文格式详解 Linux IO模式及 select.pol ...

  8. php xml 实例教程,php解析xml方法实例详解,解析xml实例详解_PHP教程

    php解析xml方法实例详解,解析xml实例详解 本文以实例形式详细讲述了php解析xml方法.分享给大家供大家参考.具体分析如下: books.xml文件如下: Harry Potter J K. ...

  9. python爬取app中的音频_Python爬取喜马拉雅音频数据详解

    码农公社  210.net.cn  210是何含义?10月24日是程序员节,1024 =210.210既 210 之意. Python爬取喜马拉雅音频数据详解 一.项目目标 爬取喜马拉雅音频数据 受害 ...

最新文章

  1. 不上全站https的网站你们就等着被恶心死吧
  2. 我给这个Python库打101分!
  3. 给定两个字符串,确定其中一个字符串的字符重新排列后,能否变成另一个字符串...
  4. [Windows Server 2012] Discuz X3安全设置
  5. 前端,我为什么不要你(转)
  6. ggplot 非常难调的参数
  7. zookeeper3.3.6 伪分布式安装
  8. 普元云计算-你适合微服务么:实施微服务的4个先决条件和重点工作
  9. 【数据获取】1:25万全国基础地理公开数据库(水系、道路、村庄)下载整理
  10. Pr:用 Au 协作处理音频
  11. php物联网智能家居系统源代码,基于物联网技术的智能家居控制系统设计方案
  12. XRD测试的68个问题(一)
  13. windows删除大量文件的优秀方式
  14. Linux 如何检测硬盘坏道?
  15. 计算机网络各层协议说明及常见协议
  16. 学习笔记21--高精地图技术概述
  17. Ubuntu18.04鼠标闪烁、无线网卡不识别问题
  18. 数学建模16(阻滞增长模型、BP神经网络)
  19. 毕业设计——>基于SSM的健身房管理系统
  20. 解决ubuntu12.04安装air微博错误

热门文章

  1. Python基础 类型转换str()函数,int()函数与float()函数
  2. 1.9 Illustrator参考线的使用 [Illustrator CC教程]
  3. Protege Tutorial
  4. 计算机网络思维导图-第一章-计算机网络体系结构
  5. 引流产品的关键是什么?营销流程的打造,前端的一个环节就是引流 聪少 聪哥爱学堂 今天
  6. ChatGPT:新晋CV工程师
  7. Oracle存储过程语法和基本使用
  8. 小公司的程序员,老想跳槽怎么办?
  9. 有效跳槽 VS 无效跳槽
  10. 【读书笔记】《王道论坛计算机考研机试指南》第五章