Skip to content

Latest commit

 

History

History
759 lines (651 loc) · 30.2 KB

README.md

File metadata and controls

759 lines (651 loc) · 30.2 KB

Fabric Client Rest llong

1. Java-SDK简介

Java-SDK是外部应用程序与Hyperledger Fabric的交互通道,帮助Java应用程序更好的管理Fabric通道和链码的生命周期,提供了链码管理、查询通道上的区块和交易数据的接口,及通道发生事件的监控。

2. Java-SDK代码分析

官方的fabric-sdk-java下载地址为https://github.com/hyperledger/fabric-sdk-java ,目前版本为Java SDK for Hyperledger Fabric 1.3,下载源码后,使用IntelliJ IDEA导入工程,显示结构如下:

fabric-sdk-java源码
源码包括两个包:org.hyperledger.fabric.sdk和org.hyperledger.fabric_ca.sdk。
  • org.hyperledger.fabric.sdk:提供与区块链交互的接口,包括创建通道、加入通道、查询通道等通道接口;安装链码、案例化链码、发起交易、查询交易等链码接口;根据编号查询区块、根据Hash值查询区块等区块接口。
  • org.hyperledger.fabric_ca.sdk:提供与fabric ca的交互的接口,包括登记、注册、销毁证书等接口。 外部应用程序要通过fabric-java-sdk接口调用Fabric网络数据,只需在pom.xml文件中引入如下代码,即可调用:
org.hyperledger.fabric-sdk-java fabric-sdk-java 1.2.0-SNAPSHOT

外部应用程序访问区块链是通过实例化HFClient类,调用类中的接口。访问Fabric ca是通过实例化HFCAClient类,调用类中的接口。

2.1 fabric.sdk主要类关系图

类图如下:

fabric主要类关系

类说明:

类名 说明
HFClient Hyperledger fabric客户端访问类,通过实例化HFClient类,调用其中的接口与区块链交互。
Orderer Orderer类实现客户端部署、调用和查询排序(Orderer)服务。
Peer Peer类实现客户端部署、调用和查询节点(Peer)。
Channel Channel类实现客户端与通道交互。
User User是用户接口
InstallProposalRequest InstallProposalRequest是智能合约安装提案请求类。
InstantiateProposalRequest InstantiateProposalRequest是智能合约实例化提案请求类。
TransactionProposalRequest TransactionProposalRequest是智能合约交易提案请求类。
QueryByChaincodeRequest QueryByChaincodeRequest是智能合约查询提案请求类。

2.2 fabric_ca.sdk主要类关系图

类图:

fabric_ca主要类
类说明:
主要方法 说明
register register是注册用户身份方法。
enroll enroll是登记用户身份方法。
reenroll reenroll是重新登记用户身份方法。
revoke revoke是注销已签发的用户证书方法。

3 Java-SDK优化

Java-SDK直接调用对于初学者有很大的难度,为了最方便外部应用程序的调用,本节在官方Java-SDK的基础上进行优化和封装,提供简洁的、跨开发语言的调用方式。

3.1 编码思路

封装官方Java-SDK代码,需要达到两个目的:

1) 提供RESTful风格的调用方法,以http方式调用解决跨开发语言问题。

2) 提供参数在线配置界面,包括排序(Orderer)IP地址、节点(peer)IP地址、智能合约(smart contract)所在目录等参数配置。

封装的中间层工程取名为fabricClientRest,以下介绍工程的编码环境的搭建过程、开发类之间的关系及接口说明。

3.2 编码环境搭建

1) 创建工程

步骤1: 运行IntelliJ IDEA工具,点击File->New->Project...,新建工程,在New Project界面中选择“Maven”后,点击“Next”,界面如下所示:

编码环境搭建步骤1

步骤2: 设置GroupId为“com.winyeahs.fabric”,ArtifactId为“fabric”,点击“Next”,界面如下所示:

编码环境搭建步骤2

步骤3: 设置Project name为“fabricClientRest”,Project locatiion为“[目录]\fabricClientRest”,点击“Finish” ,界面如下所示:

编码环境搭建步骤3

步骤4: 创建的工程为总的工程,不需要编写代码,逻辑代码在“sdkInterface” 模块和“clientRest” 模块中编写;选择 “src”目录,右击后,在弹出的菜单中选择“Delete...”,删除“src”目录,界面如下所示:

编码环境搭建步骤4

2) 创建sdkInterface模块

步骤5: 在创建的工程fabricClientRest中右击,在显示的菜单中选择“New->Module”,界面如下所示:

编码环境搭建步骤5

步骤6: 在New Module界面中,选择“Maven”,其它默认设置,点击“Next”,界面如下所示:

编码环境搭建步骤6

步骤7: 设置GroupId为“com.winyeahs.fabric.sdkinterface”,Artifact为“sdkinterface”,点击“Nest”,界面如下所示:

编码环境搭建步骤7

步骤8: 设置Content root和Module file location,点击“Finish”,界面如下所示:

编码环境搭建步骤8

3) 生成sdkInterface模块所需类

步骤9: 在工程界面中右击com.winyeahs.fabric.sdkinterface包目录,在弹出的菜单中选择“New->Java Class”,依次创建SdkInterfaceBase、SdkInterfaceOrg、SdkInterfaceOrderer、SdkInterfacePeer、SdkInterfaceUser、SdkInterfaceChannel、SdkInterfaceChaincode等类,类中的具体代码查看源码文件,界面如下所示:

编码环境搭建步骤9

4) 创建clientRest模块

步骤10: 在创建的工程fabricClientRest中右击,在显示的菜单中选择“New->Module”,界面如下所示:

编码环境搭建步骤10

步骤11: 在New Module界面中,选择“spring initializr”,其它默认设置,点击“Next”,界面如下所示:

编码环境搭建步骤11

步骤12: 设置Group为“com.winyeahs.fabric.clientrest”,Artifact为“clientrest”,点击“Nest”,界面如下所示:

编码环境搭建步骤12

步骤13: 选择“web->web”,点击“Nest”,界面如下所示:

编码环境搭建步骤13

步骤14: 设置Content root和Module file location,点击“Finish”,界面如下所示:

编码环境搭建步骤14

5) 生成clientRest模块所需类

步骤15: 在工程界面中右击com.winyeahs.fabric.clientrest包目录,在弹出的菜单中选择“New->Package”,依次创建rest、sdk、service包,界面如下所示:

编码环境搭建步骤15

步骤16: 创建RESTful风格访问的所需类,代码结构分为控制器(controller)、服务(service)和接口(sdk)三部分。

  • 控件器(controller):分为智能合约控制器(ChainCodeController)和区块控制器(ChainCodeController),文件创建在rest包目录下面。

  • 服务(service):分为智能合约服务(ChainCodeService)和区块服务(ChainCodeService),接口文件在service包目录下面创建;实现文件在service\impl目录下面创建。

  • 接口(sdk):一端以单例方式提供给服务层调用,另一端调用sdkInterface工程中的接口。

界面如下所示:

编码环境搭建步骤16

3.3 编码类图

Java-SDK优化工程fabricClientRest分clientrest模块和sdkinterface模块。clientrest模块实现RESTful风格调用的接口;sdkinterface模块实现与官方fabric-sdk-java接口进行交互。

1) clientrest模块

clientrest模块包括控制器(controller)、服务(service)和接口(sdk),配合实现RESTful接口和调用sdkinterface模块接口功能。

  • 控制器(controller):分为智能合约控制器类(ChainCodeController)和区块控制器类(ChainCodeController),第三方应用系统通过调用控制器提供的接口实现具体业务;

  • 服务(service):分为智能合约服务接口(ChainCodeService)和区块服务接口(ChainCodeService),实现类分别为ChainCodeServiceImpl和ChainBlockServiceImpl;

  • 接口(sdk):SdkManager类以单例方式提供给服务(service)调用;SdkManager类实例化时,从数据库中读取区块链的各种配置,设置调用环境参数;功能通过实例化sdkinterface模块的SdkInterfaceOrg类,做为系统调用的统一入口。

2) sdkinterface模块

sdkinterface模块实现官方fabric-sdk-java接口的优化,让使用者更清晰、最方便的调用。创建了SdkInterfaceOrg、SdkInterfaceChannel、SdkInterfaceOrderer、SdkInterfacePeer、SdkInterfaceChaincode和SdkInterfaceUser等类,都继承于SdkInterfaceBase类。

  • SdkInterfaceOrg类:组织类,对应Faric网络中的组织,类中包含组织名称、组织ID、排序(orderer)服务集合、节点(Peer)集合、通道和智能合约等变量;统一由该类提供接口给clientRest模块调用;

  • SdkInterfaceChannel类:通道类,负责处理通道相关接口,包括查询当前频道的链信息、根据交易Id查询区块数据、根据区块高度查询区块数据、根据交易Id查询区块数据;

  • SdkInterfaceOrderer类:排序类,负责记录排序服务名称、排序服务地址等信息;

  • SdkInterfacePeer类:节点类,负责记录节点名称、节点地址、节点事件域名和节点事件监听地址等信息;

  • SdkInterfaceChaincode类:智能合约类,负责处理智能合约记录智能合约名称、智能合约版本号、智能合约ID等信息,及实现安装链码、实例化链码、升级链码、执行链码和查询链码等接口;

  • SdkInterfaceUser类:用户类,实现官方fabric-sdk-java中的User接口,负责记录用户名称、用户规则、用户帐户、所属联盟等信息。 类关系图如下所示:

fabricClientRest工程类关系

3.4 REST接口说明

第三方系统调用的REST接口分为智能合约接口和区块信息接口。

1) 智能合约接口:智能合约接口有安装智能合约、实例化智能合约、升级智能合约、执行智能合约、查询智能合约。

  • 安装智能合约

接口调用请求说明

http请求方式: POST
http请求地址:http://{域名}/chaincode/install
参数:-
返回:
<![endif]-->
{
"result": "OK",
"txid": "",
"status": 200
}

  • 实例化智能合约

http请求方式: POST
http请求地址:http://{域名}/chaincode/instantiate
参数:{"array":["a","200","b","400"]}
返回:
<![endif]-->
{
"result": "OK",
"txid": "",
"status": 200
}

  • 升级智能合约

http请求方式: POST
http请求地址:http://{域名}/chaincode/upgrade
参数:{"array":["a","200","b","400"]}
返回:
<![endif]-->
{
"result": "OK",
"txid": "",
"status": 200
}

  • 执行智能合约

http请求方式: POST
http请求地址:http://{域名}/chaincode/invoke
参数:{"fcn":"invoke","array":["b", "a", "5"]}
返回:
<![endif]-->
{
"result": "OK",
"txid": "",
"status": 200
}

  • 查询智能合约

http请求方式: POST
http请求地址:http://{域名}/chaincode/query
参数:{"fcn":"query","array":["a"]}
返回:
<![endif]-->
{
"result": "400",
"txid": "",
"status": 200
}

2) 区块信息接口:区块信息接口有根据交易Id查询区块数据、根据哈希值查询区块数据、根据区块高度查询区块数据、查询当前区块信息。

  • 根据交易Id查询区块数据:

http请求方式: POST
http请求地址:http://{域名}/chainblock/queryBlockByTransactionID
参数:{"txId":"f30522c5db02341fa06cb0c1d662b2184d75eba1df7327af8c6327f1a11c4092"}
返回:
<![endif]-->
{
"data": {
"dataHash": "",
"blockNumber": 2,
"calculatedBlockHash": "",
"envelopeCount": 1,
"envelopes": [ {
"transactionEnvelopeInfo": {
"transactionActionInfoArray": [ {
"chaincodeInputArgsCount": 4,
"endorserInfoArray": [ {
"mspId": "Org1MSP",
"signature": "",
"id": ""
}],
"payload": "",
"argArray": [
"invoke",
"b",
"a",
"5"
],
"endorsementsCount": 1,
"rwsetInfo": {
"nsRwsetInfoArray": [
{
"writeSet": [],
"readSet": [ {
"readSetIndex": 0,
"readVersionTxNum": 0,
"readVersionBlockNum": 1,
"namespace": "lscc",
"version": "[1 : 0]",
"key": "mycc"
}]
},
{
"writeSet": [
{
"writeSetIndex": 0,
"namespace": "mycc",
"value": "205",
"key": "a"
},
{
"writeSetIndex": 1,
"namespace": "mycc",
"value": "395",
"key": "b"
}
],
"readSet": [
{
"readSetIndex": 0,
"readVersionTxNum": 0,
"readVersionBlockNum": 1,
"namespace": "mycc",
"version": "[1 : 0]",
"key": "a"
},
{
"readSetIndex": 1,
"readVersionTxNum": 0,
"readVersionBlockNum": 1,
"namespace": "mycc",
"version": "[1 : 0]",
"key": "b"
}
]
}
],
"nsRWsetCount": 2
},
"responseStatus": 200,
"responseMessageString": "",
"status": 200
}],
"txCount": 1,
"isValid": true,
"validationCode": 0
},
"createId": "",
"isValid": true,
"validationCode": 0,
"type": "TRANSACTION_ENVELOPE",
"nonce": "",
"channelId": "mychannel",
"transactionID": "",
"createMSPID": "Org1MSP",
"timestamp": "2018/11/08 22:07:16"
}],
"previousHashID": ""
},
"status": 200
}

  • 根据哈希值查询区块数据:

http请求方式: POST
http请求地址:http://{域名}/chainblock/queryBlockByHash
参数:{"hash":"ff0ceb01a804e98fae405ea075748fa149899e85bc0f03f939c9b6e7f6b89668"}
返回:
<![endif]-->
{
"data": {
"dataHash": "",
"blockNumber": 2,
"calculatedBlockHash": "",
"envelopeCount": 1,
"envelopes": [ {
"transactionEnvelopeInfo": {
"transactionActionInfoArray": [ {
"chaincodeInputArgsCount": 4,
"endorserInfoArray": [ {
"mspId": "Org1MSP",
"signature": "",
"id": ""
}],
"payload": "",
"argArray": [
"invoke",
"b",
"a",
"5"
],
"endorsementsCount": 1,
"rwsetInfo": {
"nsRwsetInfoArray": [
{
"writeSet": [],
"readSet": [ {
"readSetIndex": 0,
"readVersionTxNum": 0,
"readVersionBlockNum": 1,
"namespace": "lscc",
"version": "[1 : 0]",
"key": "mycc"
}]
},
{
"writeSet": [
{
"writeSetIndex": 0,
"namespace": "mycc",
"value": "205",
"key": "a"
},
{
"writeSetIndex": 1,
"namespace": "mycc",
"value": "395",
"key": "b"
}
],
"readSet": [
{
"readSetIndex": 0,
"readVersionTxNum": 0,
"readVersionBlockNum": 1,
"namespace": "mycc",
"version": "[1 : 0]",
"key": "a"
},
{
"readSetIndex": 1,
"readVersionTxNum": 0,
"readVersionBlockNum": 1,
"namespace": "mycc",
"version": "[1 : 0]",
"key": "b"
}
]
}
],
"nsRWsetCount": 2
},
"responseStatus": 200,
"responseMessageString": "",
"status": 200
}],
"txCount": 1,
"isValid": true,
"validationCode": 0
},
"createId": "",
"isValid": true,
"validationCode": 0,
"type": "TRANSACTION_ENVELOPE",
"nonce": "",
"channelId": "mychannel",
"transactionID": "",
"createMSPID": "Org1MSP",
"timestamp": "2018/11/08 22:07:16"
}],
"previousHashID": ""
},
"status": 200
}

  • 根据区块高度查询区块数据:

http请求方式: POST
http请求地址:http://{域名}/chainblock/queryBlockByNumber
参数:{"blockNumber":"1"}
返回:
<![endif]-->
{
"data": {
"dataHash": "",
"blockNumber": 2,
"calculatedBlockHash": "",
"envelopeCount": 1,
"envelopes": [ {
"transactionEnvelopeInfo": {
"transactionActionInfoArray": [ {
"chaincodeInputArgsCount": 4,
"endorserInfoArray": [ {
"mspId": "Org1MSP",
"signature": "",
"id": ""
}],
"payload": "",
"argArray": [
"invoke",
"b",
"a",
"5"
],
"endorsementsCount": 1,
"rwsetInfo": {
"nsRwsetInfoArray": [
{
"writeSet": [],
"readSet": [ {
"readSetIndex": 0,
"readVersionTxNum": 0,
"readVersionBlockNum": 1,
"namespace": "lscc",
"version": "[1 : 0]",
"key": "mycc"
}]
},
{
"writeSet": [
{
"writeSetIndex": 0,
"namespace": "mycc",
"value": "205",
"key": "a"
},
{
"writeSetIndex": 1,
"namespace": "mycc",
"value": "395",
"key": "b"
}
],
"readSet": [
{
"readSetIndex": 0,
"readVersionTxNum": 0,
"readVersionBlockNum": 1,
"namespace": "mycc",
"version": "[1 : 0]",
"key": "a"
},
{
"readSetIndex": 1,
"readVersionTxNum": 0,
"readVersionBlockNum": 1,
"namespace": "mycc",
"version": "[1 : 0]",
"key": "b"
}
]
}
],
"nsRWsetCount": 2
},
"responseStatus": 200,
"responseMessageString": "",
"status": 200
}],
"txCount": 1,
"isValid": true,
"validationCode": 0
},
"createId": "",
"isValid": true,
"validationCode": 0,
"type": "TRANSACTION_ENVELOPE",
"nonce": "",
"channelId": "mychannel",
"transactionID": "",
"createMSPID": "Org1MSP",
"timestamp": "2018/11/08 22:07:16"
}],
"previousHashID": ""
},
"status": 200
}

  • 查询当前区块信息:

http请求方式: POST
http请求地址:http://{域名}/chainblock/queryBlockchainInfo
参数:-
返回:
<![endif]-->
{
"data": {
"previousBlockHash": "",
"currentBlockHash": "",
"height": 2
},
"status": 200
}

4 生产环境调用介绍

生产环境中每个节点(Peer)对于一台服务器,每个节点(Peer)可能属于不同的企业,所以fabricClientRest spring boot项目在每个节点(Peer)中都部署一套,提供企业中第三方系统调用。 以下将介绍fabricClientRest spring boot项目的打包、在linux下java安装及在kafka生产环境中运行客户端调试。

4.1 项目打包

FabricClientRest工程中包括clientRest模块和sdkInterface模块,FabricClientRest的artifactId为fabric,只要在maven projects中双击package即可,生成的spring boot项目的jar文件在clientRest\target这个目录下。

步骤1: 打开FabricClientRest工程,界面显示如下:

生产环境调用介绍步骤1

步骤2: 单击界面中右边的“Maven project”项目,显示界面如下:

生产环境调用介绍步骤2

步骤3: 在Maven Projects中双击“package”菜单,工程自动生成jar文件,生成完的界面如下:

生产环境调用介绍步骤3

步骤4: 把clientRest\target目录下生成clientrest-1.0-SNAPSHOT.jar包拷贝到前一章的kafka生产环境部署的peer0.org1.example.com对应的服务器(ip:192.168.35.7)的/usr/local/clientrest某个目录下,生成jar的界面如下:

生产环境调用介绍步骤4

4.2 java环境安装

步骤5: 下载JDK8,访问地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html ,下载jdk-8u191-linux-x64.tar.gz文件,界面如下所示:

生产环境调用介绍步骤5

步骤6: 拷贝jdk-8u191-linux-x64.tar.gz文件到/usr/local/目录下,执行解压命令。 # cd /usr/local/ # tar zxvf jdk-8u191-linux-x64.tar.gz

步骤7: 设置环境变量 # vi /etc/profile 添加如下内容 export JAVA_HOME=/usr/local/jdk1.8.0_191 export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export PATH=$PATH:$JAVA_HOME/bin

步骤8: 环境变量设置立即生效 # source /etc/profile

步骤9: 查看JDK版本 # java -version

4.3 生产环境部署

步骤10: 根据第十一章 Fabric kafka生产环境部署启动Fabric网络,kafka运行验证由java-sdk客户端处理。

步骤11: 运行clientrest的spring boot系统。

# cd /usr/local/clientrest

# netstat -lanp|grep 8080

# kill -9 XXXX

# nohup java -jar clientrest-1.0-SNAPSHOT.jar >springboot.log 2>&1 &

# tail -f springboot.log

步骤12: 拷贝整个crypto-config目录和Fabric-sdk-soapui-project.xml到/usr/local/clientrest目录中。

步骤13: 验证是否正常运行,通过浏览器访问http://192.168.235.7:8080 ,出现如下界面表示系统已正确部署。

步骤14: 安装SoapUI测试工具(在光盘中有安装软件),安装完后运行的界面如下:

生产环境调用介绍步骤14

步骤15: 点击File->import Project,在出现的界面中加载Fabric-sdk-soapui-project.xml(在光盘中有该文件),界面如下所示:

生产环境调用介绍步骤15

4.4 客户端验证

在SoapUI工具上通过调用FabricClientRest提供的REST接口验证各功能,界面如下所示:

SoapUI工具

1) 安装链码

打开安装链码测试项,以POST请求方式调用http://192.168.235.7:8080/chaincode/install 接口,如果调用成功,以json方式返回结果,返回字符串及界面如下所示: 调用返回: { "result": "OK", "txid": "7cc546ba6eb5f3af5d4bf2a4191616f45b2c99f4c966c6b342b22a05f0a635eb", "status": 200 } 界面:

安装链码

2) 实例化链码

打开安装链码测试项,以POST请求方式调用http://192.168.235.7:8080/chaincode/instantiate 接口,如果调用成功,以json方式返回结果,请求参数、返回字符串及界面如下所示: 请求参数: {"array":["a","200","b","400"]} 返回字符串: { "result": "", "txid": "67031002ca3ed1e2f2f30824707313d0143c20199af024c06a868b726f160965", "status": 200 } 界面:

实例化链码

3) 执行链码

打开安装链码测试项,以POST请求方式调用http://192.168.235.7:8080/chaincode/invoke 接口,如果调用成功,以json方式返回结果,请求参数、返回字符串及界面如下所示:

界面:

执行链码

4) 查询链码

打开安装链码测试项,以POST请求方式调用http://192.168.235.7:8080/chaincode/query 接口,如果调用成功,以json方式返回结果,请求参数、返回字符串及界面如下所示:

界面:

查询链码

5) 查询当前区块

打开安装链码测试项,以POST请求方式调用http://192.168.235.7:8080/chainblock/queryBlockchainInfo 接口,如果调用成功,以json方式返回结果,返回字符串及界面如下所示:

界面:

查询当前区块

6) 根据高度查询区块

打开安装链码测试项,以POST请求方式调用http://192.168.235.7:8080/chainblock/queryBlockByNumber 接口,如果调用成功,以json方式返回结果,请求参数、返回字符串及界面如下所示:

界面:

根据高度查询区块

7) 根据交易ID查询区块

打开安装链码测试项,以POST请求方式调用http://192.168.235.7:8080/chainblock/queryBlockByTransactionID 接口,如果调用成功,以json方式返回结果,请求参数、返回字符串及界面如下所示:

界面:

根据交易ID查询区块

8) 根据HASH查询区块

打开安装链码测试项,以POST请求方式调用http://192.168.235.7:8080/chainblock/queryBlockByHash 接口,如果调用成功,以json方式返回结果,请求参数、返回字符串及界面如下所示:

界面:

根据HASH查询区块