本文介绍了fabric中的一个示例应用balance-transfer,fabric版本是1.4.1。本文基于上一章hyperledger基础培训-创建第一个fabric网络的基础上,如果还未了解,请先了解上一章节内容。
balance-transfer是什么
balance-transfer是Hyperledger fabric Node SDK的一个示例应用,主要使用了SDK中fabric-client 和 fabric-ca-client 模块中的API,实现了与Fabric网络交互的各种操作。
1. 预装环境
预装环境在上一章节hyperledger基础培训-创建第一个fabric网络已有详细介绍,可跳转至该文章查看。
- Docker - v1.12或更高版本
- Docker Compose - v1.8或更高版本
- Git客户端
- Node.js v8.4.0或更高版本
- 下载Docker镜像
2. 运行示例程序
balance-transfer可通过脚本运行,构建一个本地的Fabric网络,所有节点包括:
- 两个CA节点
- 一个orderer节点
- 四个peer节点 (每个组织各两个peer节点)
balance-transfer的目录结构如下:
balance-transfer
├── app // 与fabric网络交互的实现
│ ├── create-channel.js // 创建通道
│ ├── helper.js
│ ├── install-chaincode.js // 安装链码
│ ├── instantiate-chaincode.js // 实例化链码
│ ├── invoke-transaction.js // 执行(invoke)链码
│ ├── join-channel.js // 加入通道
│ ├── update-anchor-peers.js // 更新anchor peer节点
│ └── query.js // 查询(query)链码
├── app.js // 定义与fabric网络交互的API
├── artifacts // 启动fabric网络需要的配置文件
│ ├── base.yaml
│ ├── channel
│ ├── docker-compose.yaml
│ ├── network-config-aws.json
│ ├── network-config.json
│ └── src
├── config.js
├── config.json
├── node_modules
│ └── .......
├── package.json
├── package-lock.json
├── README.md
├── runApp.sh // 启动应用程序脚本
├── typescript
└── testAPIs.sh // 测试API脚本
打开终端窗口1,打开上一章节克隆下来的fabric-samples项目,执行cd fabric-samples
。
执行cd balance-transfer
进入balance-transfer文件夹下,
下面介绍两种运行方式:
2.1 方式一
1)终端窗口1,使用docker-compose启动网络
$ docker-compose -f artifacts/docker-compose.yaml up
2)打开终端窗口2,安装fabric-client 和 fabric-ca-client 模块
$ npm install
3)启动应用程序,监听4000端口
$ PORT=4000 node app
4)打开终端窗口3,通过curl命令进行测试。在第三部分会具体介绍。
2.2 方式二
1)终端窗口1,执行shell脚本启动应用程序
$ ./runApp.sh
执行shell脚本之后会:
- 启动本地fabric网络
- 下载fabric-client 和 fabric-ca-client 模块
- 在PORT 4000启动应用程序
2)打开终端窗口2,执行brew install jq
安装jq,使shell脚本能正确解析JSON
$ brew install jq
如果出现-bash: brew: command not found
,执行/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
安装brew ,brew是包管理工具,可以很方便地进行安装/卸载/更新各种软件包。
安装完成后,如果还是出现-bash: brew: command not found
,则需要修改环境变量:
执行vim ~/.bash_profile
打开.bash_profile
vim ~/.bash_profile
在文本编辑框编辑:
export PATH=/usr/local/bin:$PATH
编辑之后保存,然后执行source命令,使之生效:
$ source ~/.bash_profile
3)在终端窗口2,执行shell测试脚本
-如果使用golang版本,则执行./testAPIs.sh -l golang
命令
-如果使用nodejs版本,则执行./testAPIs.sh -l node
命令
测试脚本实现了以下的功能:
- 注册用户返回token
- 创建通道以及将节点加入到通道
- 安装链码和实例化链码
- invoke链码
- query(查询)链码
3. REST API请求
下面具体介绍上面测试脚本实现的功能,这边是用curl模拟POST请求进行测试,同样也可以用postman(用于网页调试、发送网页HTTP请求的Chrome插件)进行接口测试。
3.1 登录
示例是在组织Org1上注册新用户Jim
POST请求,包含两个重要参数:
- username用户名
- orgName组织名
$ curl -s -X POST http://localhost:4000/users -H "content-type: application/x-www-form-urlencoded" -d 'username=Jim&orgName=Org1'
{"success": true,"secret": "","message": "Jim enrolled Successfully","token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjE0ODI4NjcsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1NjE0NDY4Njd9.V8LPaLY-bnD_h6788FMqnYkCfxZFNDmiiMqQuMFhfAc"}
注意:响应包含成功/失败状态,secret和token[JSON Web令牌(JWT)],Header 授权必须包含这边返回的token。
3.2 创建通道
示例是创建通道mychannel
POST请求,包含两个重要参数:
- channel名称
- channel配置文件路径(../artifacts/channel/mychannel.tx)
$ curl -s -X POST \
http://localhost:4000/channels \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjE0ODI4NjcsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1NjE0NDY4Njd9.V8LPaLY-bnD_h6788FMqnYkCfxZFNDmiiMqQuMFhfAc" \
-H "content-type: application/json" \
-d '{
"channelName":"mychannel",
"channelConfigPath":"../artifacts/channel/mychannel.tx"
}'
{"success":true,"message":"Channel 'mychannel' created Successfully"}
注意:这边请求头必需附上第一步用户登录成功的token,authorization: Bearer ${your token}
。
3.3 节点加入到通道
示例是将org1中的两个peer节点加入通道中
POST请求,需要指定以下参数:
- channelName 加入到哪个channel
- peers 把哪些节点加进去
$ curl -s -X POST \
http://localhost:4000/channels/mychannel/peers \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjE0ODI4NjcsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1NjE0NDY4Njd9.V8LPaLY-bnD_h6788FMqnYkCfxZFNDmiiMqQuMFhfAc" \
-H "content-type: application/json" \
-d '{
"peers": ["peer0.org1.example.com","peer1.org1.example.com"]
}'
{"success":true,"message":"Successfully joined peers in organization Org1 to the channel:mychannel"}
3.4 安装链码
示例是安装golang/nodejs类型的链码,链码名称为mycc,链码版本号是v0,chaincodePath指定链码路径。
POST请求,使用sdk安装chaincode时需要指定以下参数:
- chaincode名称
- chaincode版本
- chaincode文件路径
- chaincode类型
- 目标节点列表
1)golang版本(chaincodeType: golang)
$ curl -s -X POST \
http://localhost:4000/chaincodes \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjE0ODI4NjcsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1NjE0NDY4Njd9.V8LPaLY-bnD_h6788FMqnYkCfxZFNDmiiMqQuMFhfAc" \
-H "content-type: application/json" \
-d '{
"peers": ["peer0.org1.example.com","peer1.org1.example.com"],
"chaincodeName":"mycc",
"chaincodePath":"github.com/example_cc/go",
"chaincodeType": "golang",
"chaincodeVersion":"v0"
}'
{"success":true,"message":"Successfully install chaincode"}
2)nodejs版本(chaincodeType: node)
$ curl -s -X POST \
http://localhost:4000/chaincodes \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjE0ODI4NjcsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1NjE0NDY4Njd9.V8LPaLY-bnD_h6788FMqnYkCfxZFNDmiiMqQuMFhfAc" \
-H "content-type: application/json" \
-d '{
"peers": ["peer0.org1.example.com","peer1.org1.example.com"],
"chaincodeName":"mycc",
"chaincodePath":"$PWD/artifacts/src/github.com/example_cc/node",
"chaincodeType": "node",
"chaincodeVersion":"v0"
}'
{"success":true,"message":"Successfully install chaincode"}
安装chaincode会根据本地的链码文件生成chaincode镜像。
注意:请求传递的参数chaincodeType表示链码类型(golang还是node)
3.5 实例化链码
示例是在mychannel通道上实例化链码
POST请求,需要指定以下参数:
- channel名称
- chaincode名称
- chaincode版本
- 实例化要执行的方法(示例没加,默认为Init)
- 方法参数
$ curl -s -X POST \
http://localhost:4000/channels/mychannel/chaincodes \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjE0ODI4NjcsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1NjE0NDY4Njd9.V8LPaLY-bnD_h6788FMqnYkCfxZFNDmiiMqQuMFhfAc" \
-H "content-type: application/json" \
-d '{
"chaincodeName":"mycc",
"chaincodeVersion":"v0",
"chaincodeType": "golang",
"args":["a","100","b","200"]
}'
{"success":true,"message":"Successfully instantiate chaincode in organization Org1 to the channel 'mychannel'"}
这边初始化a,b两个账户,a账户有100,b账户有200,为了后面3.6转账。
实例化chaincode则会启动该镜像,使链码在docker容器中运行。
注意:这边的chaincodeType主要看链码是nodejs版本的链码还是golang版本的链码。
3.6 invoke(执行)链码
链码安装和实例化之后就可以调用chaincode执行交易。
POST请求,需要指定以下参数:
- channel名称
- chaincode名称
- peers执行交易所在的节点列表
- fcn链码方法名
- args方法参数
$ curl -s -X POST \
http://localhost:4000/channels/mychannel/chaincodes/mycc \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjE0ODI4NjcsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1NjE0NDY4Njd9.V8LPaLY-bnD_h6788FMqnYkCfxZFNDmiiMqQuMFhfAc" \
-H "content-type: application/json" \
-d '{
"peers": ["peer0.org1.example.com","peer0.org2.example.com"],
"fcn":"move",
"args":["a","b","10"]
}'
{"success":true,"message":"Successfully invoked the chaincode in organization Org1 to the channel 'mychannel' transacton ID: 7f844a363d8343e25e76ea878ab582ecd82ade09bc7f1541728d33793eab49c8"}
这里实现了简单的转账交易 a->b,调用链码中的move方法,返回transaction id。
3.7 query(查询)链码
balance-transfer 提供了很多查询接口,包括链码查询,根据区块号查询区块数据,根据交易ID查询交易信息,查询链上的区块数,查询已安装或已实例化的链码,查询通道。
1)链码查询
3.6已经成功执行了转账(A->B),下面我们来查询下A账户现在有多少资产
GET请求,需要指定以下参数:
- channel名称
- chaincode名称
- peer节点
- fcn链码函数名
- args方法参数
$ curl -s -X GET \
"http://localhost:4000/channels/mychannel/chaincodes/mycc?peer=peer0.org1.example.com&fcn=query&args=%5B%22a%22%5D" \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjE0ODI4NjcsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1NjE0NDY4Njd9.V8LPaLY-bnD_h6788FMqnYkCfxZFNDmiiMqQuMFhfAc" \
-H "content-type: application/json"
a now has 90 after the move
返回a在执行3.6 move方法转账给b之后,还剩90。
2)按区块号查询
根据块号查询区块信息,示例中展示的是org1组织下的peer0节点上第一个区块的信息
GET请求,需要指定以下参数:
- channel名称
- 块号
- peer节点
$ curl -s -X GET \
"http://localhost:4000/channels/mychannel/blocks/1?peer=peer0.org1.example.com" \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjE0ODI4NjcsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1NjE0NDY4Njd9.V8LPaLY-bnD_h6788FMqnYkCfxZFNDmiiMqQuMFhfAc" \
-H "content-type: application/json"
3)按Transaction ID查询
根据交易id查询交易详细信息,这边的Transaction ID可以用3.6返回的Transaction ID
GET请求,需要指定以下参数:
- channel名称
- transaction id
- peer节点
$ curl -s -X GET http://localhost:4000/channels/mychannel/transactions/7f844a363d8343e25e76ea878ab582ecd82ade09bc7f1541728d33793eab49c8?peer=peer0.org1.example.com \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjE0ODI4NjcsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1NjE0NDY4Njd9.V8LPaLY-bnD_h6788FMqnYkCfxZFNDmiiMqQuMFhfAc" \
-H "content-type: application/json"
4) 查询链信息
查询链信息,示例是查询通道mychannel的链信息。
GET请求,需要指定以下参数:
- channel名称
- peer节点
$ curl -s -X GET \
"http://localhost:4000/channels/mychannel?peer=peer0.org1.example.com" \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjE0ODI4NjcsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1NjE0NDY4Njd9.V8LPaLY-bnD_h6788FMqnYkCfxZFNDmiiMqQuMFhfAc" \
-H "content-type: application/json"
{"height":{"low":5,"high":0,"unsigned":true},"currentBlockHash":{"buffer":{"type":"Buffer","data":[8,5,18,32,163,72,1,73,191,44,71,104,60,120,71,152,13,14,242,156,232,50,172,247,192,249,112,117,234,155,227,173,200,112,22,106,26,32,201,184,203,75,83,121,30,9,79,164,131,36,71,98,59,141,69,146,166,48,193,40,165,79,80,246,193,202,86,14,169,43]},"offset":4,"markedOffset":-1,"limit":36,"littleEndian":true,"noAssert":false},"previousBlockHash":{"buffer":{"type":"Buffer","data":[8,5,18,32,163,72,1,73,191,44,71,104,60,120,71,152,13,14,242,156,232,50,172,247,192,249,112,117,234,155,227,173,200,112,22,106,26,32,201,184,203,75,83,121,30,9,79,164,131,36,71,98,59,141,69,146,166,48,193,40,165,79,80,246,193,202,86,14,169,43]},"offset":38,"markedOffset":-1,"limit":70,"littleEndian":true,"noAssert":false}}
5) 查询已安装的链码
示例是org1下的peer0节点已经安装的链码信息。
GET请求,需要指定以下参数:
- peer节点
- 链码类型(installed)
$ curl -s -X GET \
"http://localhost:4000/chaincodes?peer=peer0.org1.example.com&type=installed" \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjE0ODI4NjcsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1NjE0NDY4Njd9.V8LPaLY-bnD_h6788FMqnYkCfxZFNDmiiMqQuMFhfAc" \
-H "content-type: application/json"
["name: mycc, version: v0, path: github.com/example_cc/go"]
返回org1下的peer1节点已经安装的链码信息:链码名称,链码版本号,链码路径。
6) 查询实例化的链码(跟5)类似,type不同)
示例是查询org1的peer0节点已经实例化的链码信息
GET请求,需要指定以下参数:
- peer节点
- 链码类型(instantiated)
$ curl -s -X GET \
"http://localhost:4000/chaincodes?peer=peer0.org1.example.com&type=instantiated" \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjE0ODI4NjcsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1NjE0NDY4Njd9.V8LPaLY-bnD_h6788FMqnYkCfxZFNDmiiMqQuMFhfAc" \
-H "content-type: application/json"
["name: mycc, version: v0, path: github.com/example_cc/go"]
返回org1下的peer1节点已经实例化的链码信息:链码名称,链码版本号,链码路径。
7)查询通道
查询组织org1的peer0节点加入的channel
GET请求,需要指定的参数如下:
- peer节点
$ curl -s -X GET \
"http://localhost:4000/channels?peer=peer0.org1.example.com" \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjE0ODI4NjcsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1NjE0NDY4Njd9.V8LPaLY-bnD_h6788FMqnYkCfxZFNDmiiMqQuMFhfAc" \
-H "content-type: application/json"
{"channels":[{"channel_id":"mychannel"}]}
返回组织org1的peer0节点加入的channel列表
4 清理网络
$ docker rm -f $(docker ps -aq)
$ docker rmi -f $(docker images | grep dev | awk '{print $3}')
$ rm -rf fabric-client-kv-org[1-2]
docker rm -f $(docker ps -aq)
作用是:清除所有容器
docker rmi -f $(docker images | grep dev | awk '{print $3}')
作用是:删除所有chaincode镜像
rm -rf fabric-client-kv-org[1-2]
作用是:删除用户注册和登录的数据,如私钥和证书
有疑问加站长微信联系(非本文作者)