编写第一个Fabric应用
Table of Contents
背景知识
Fabric CA
Fabric CA是Hyperledger Fabric的证书颁发机构, 提供如下功能:
- 身份注册
- 发放登记证书(Ecerts)
- 发布交易证书(Tcerts), 在区块链上进行交易时提供匿名性和不可链接性
- 证书更新和撤销
Fabric CA包含一个Client和一个Server. Fabric 1.0以后, CA可脱离Docker镜像, 作为一个独立的服务来运行. 如果使用Docker启动, 则所有的CA服务都在一个专门的镜像中执行.
Fabric CA调用Server的方式: 通过Client调用, 或通过SDK调用.
SDK方式的API在 fabric-ca/swagger/swagger-fabric-ca.json
.
这边介绍通过Client调用的方式. Fabric CA运行流程:
- Server初始化
- 生成CA根证书
- 启动Server
- Client向Server
请求登记
- Server向Client返回
登记证书ECert
- Client向Server请求
注册节点
- Server向Client返回
节点注册信息结果
- Client向Server请求
登记节点
- Server生成
TCert
, 存入数据库 - Server向Client返回
登记结果
一些组件
Peer
Fabric网络中的节点(Peer), 表现为一个运行着的Docker容器. Peer可分两种:
- endorsing peer/endoser
安装和执行chaincode等一系列操作都离不开peer, 通常会说: chaincode安装在某个peer上. channel内同一个chaincode可以安装到多个peer上, 但只能实例化一次.
- anchor peer
锚节点是一个channel或org的代表, 它从orderer获取信息, 并在组内广播给其他peer, 其他peer可以不直接跟orderer打交道.
Org
Org由若干个Peer组成. 可在 crypto-config.yaml
中设置一个Block中的Peer数量.
Channel
Channel有点像"子网", 可以作为两个或多个特定网络成员间的专门以机密交易为目的而建立的. 一个Channel可包含多个Org. 每个Channel拥有一个账本, 并共享给内部的Peer.
同一个Channel内的Org可部署到多台机器上. Fabric为Channel间数据通信和同步提供了多套解决方案, 如基于Zookeeper的Kafka.
编写第一个应用程序(v1.1.0 版本)
主要学习如何与CA交互, 申请登记证书. 然后使用这些生成的实体来查询和更新账本.
主要步骤如下:
- 配置开发环境. 我们的应用要通过网络进行交互, 首先下载一个脚本, 使用这个脚本下载一些组件, 以便进行注册, 登记, 查询, 更新.
- 学习一些App需要用到的智能合约提供的参数. 智能合约里有很多方法, 使我们可以通过多种方式来与账本交互.
- 开发能够查询及更新资产的App. 我们要进入到App的代码内部, 手动修改变量来进行不同方式的查询和更新.
配置开发环境
Fabric官方案例first-network中写了如何配置环境.
# 进入fabcar, 查看里面的文件 cd fabric-samples/fabcar && ls # 做一些清理工作 docker rm -f $(docker ps -qa) # 清除网络中的缓存 docker network prune # 如果之前运行过, 可能要删除Chaincode镜像 docker rmi dev-peer0.org1.example.com-fabcar-1.0-5c906e402ed29f20260ae42283216aa75549c571e2e380f3615826365d8269ba
安装客户端 & 启动网络
首先为App安装Fabric依赖. 可以打开 package.json
查看依赖. 如图所示:
fabric-ca-client: 它使得App可以与CA服务器交互, 获得相关证书. fabric-client: 它使得App可以使用获得的证书与Peers和Ordering Service交互.
# 安装依赖 npm install ./startFabric.sh
startFabric.sh
脚本会初始化各种Fabric实体, 启动一个使用Golang编写的智能合约窗口.
也可以使用 node.js
编写的Chaincode, 只要把命令改成: ./startFabric.sh node
. Node.js的方案会更慢一些.
App如何与网络交互?
更详细的内容在Understanding the Fabcar Network, 现在我们只要知道, App是通过SDK调用API来查询, 更新账本的.
登记管理员用户
接下来的两小节内容, 都是与CA交互的, 可以新开一个命令行窗口, 通过如下命令查看CA日志.
docker logs -f ca.example.com
当我们启动网络时, 我们通过CA注册了一个管理员用户 admin
.
现在需要向CA Server发送一个登记请求, 然后为 admin
取回一个登记证书. 这个登记证书是构成管理员用户的必要条件. 随后需要使用这个管理员来注册和登记新用户.
# 向CA Server发送管理员登记请求 node enrollAdmin.js
enrollAdmin.js
会调用一个证书签名请求(CSR), 最后在项目根目录生成 hfc-key-store
目录, 里面包含了证书和密钥. 当App需要创建和读取不同身份用户时, 需要定位到此文件夹.
注册和登记User1
连通CA Server, 使用刚刚生成的管理员证书, 注册和登记一个新用户.
user1
是用来查询和更新账本的用户. admin
发起了新用户的注册和登记工作(就好像 admin
扮演了登记员的角色). admin
发起登记和注册请求:
node registerUser.js
和管理员登记一样, registerUser.js
调用CSR, 将证书和密钥放入 hfc-key-store
. 现在有两个用户的身份材料了.
查询账本
我们使用 user1
作为签名实体, 查询账本上的汽车列表. 在 query.js
中, 通过代码 fabric_client.getUserContext('user1', true)
指定使用 user1
作为签名实体, 通过代码 var store_path = path.join(__dirname, 'hfc-key-store')
告诉程序 user1
的登记材料的存储位置. 通过 query.js
中的 queryAllCars()
来查询所有的汽车.
node query.js
分析 query.js
:
var channel = fabric_client.newChannel('mychannel'); var peer = fabric_client.newPeer('grpc://localhost:7051'); channel.addPeer(peer); var member_user = null; var store_path = path.join(__dirname, 'hfc-key-store'); console.log('Store path:'+store_path); var tx_id = null;
这段代码告诉程序使用哪些Channel, Peer, 证书和密钥的存放位置.
构建查询的代码块:
// queryCar chaincode function - requires 1 argument, ex: args: ['CAR4'], // queryAllCars chaincode function - requires no arguments , ex: args: [''], const request = { //targets : --- letting this default to the peers assigned to the channel chaincodeId: 'fabcar', fcn: 'queryAllCars', args: [''] };
指定Peer上的ChaincodeID为 fabcar
, 执行 queryAllCars
函数, 没有传递参数.
要查看Chaincode的内容, 可以在 fabric-samples/chaincode/fabcar
中查看, 里面有 go
和 Node.js
两个版本.
如, Chaincode中定义的, 我们之前使用过的 queryAllCars()
, 内容如下:
func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response { startKey := "CAR0" endKey := "CAR999" resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
如果我们要查询 CAR4
的信息, 我们可以将 queryAllCars()
改成 queryCar()
, 并传递参数 CAR4
.
修改后的 request
内容如下:
const request = { //targets : --- letting this default to the peers assigned to the channel chaincodeId: 'fabcar', fcn: 'queryCar', args: ['CAR4'] };
使用 node query.js
运行. 返回内容为:
Store path:/home/pinvon/go/src/github.com/hyperledger/fabric-samples/fabcar/hfc-key-store Successfully loaded user1 from persistence Query has completed, checking results Response is {"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}
更新账本
更新账本的流程:
账本更新是从生成交易提案的App开始的. 具体关于交易提案, 可参考交易流程一文.
- 构造
request
, 内容包括Channel ID, func, Chaincode Name. 然后调用APIchannel.SendTransactionProposal
将交易提案发送给Peers进行背书. - Endorsing Peers各返回一个提案答复. App根据所有提案答复来创建交易请求, 并为其签名. 通过调用API
channelsendTransaction
将交易请求发送到Ordering Service. - Ordering Service把交易打包到区块, 将区块发送到Channel上的所有Peers进行认证, 查看是否满足背书策略.
- App使用API
eh.setPeerAddr
连接到Peer的事务监听端口, 调用APIeh.registerTxEvent
注册与特定交易ID相关的事务. 该API使得App获得事务的结果(提交成功与否).
在代码中, 首先创建一个新汽车. invoke.js
是专门用于交易的JavaScript程序. 将其打开, 找到构建调用的代码块. 即request的填充处. 程序并没有填充要调用的函数字段和参数字段, 我们可以把函数 createCar
填进去, 表示要创建一辆汽车. 并填充该汽车的参数. 如下所示:
var request = { //targets: let default to the peer assigned to the client chaincodeId: 'fabcar', fcn: 'createCar', args: ['CAR12', 'Honda', 'Accord', 'Black', 'Tom'], chainId: 'mychannel', txId: tx_id };
保存并运行: node invoke.js
.
输出内容如下:
Store path:/home/pinvon/go/src/github.com/hyperledger/fabric-samples/fabcar/hfc-key-store Successfully loaded user1 from persistence Assigning transaction_id: 2a15883b896e8be844c20be92188178f8592fdb145bbf1532d27cc6c08c59d2f Transaction proposal was good Successfully sent Proposal and received ProposalResponse: Status - 200, message - "OK" The transaction has been committed on peer localhost:7053 Send transaction promise and event listener promise have completed Successfully sent transaction to the orderer. Successfully committed the change to the ledger by the peer
看到 The transaction has been committed on peer localhost:7053
, 且后面提示 Successfully
, 则表示交易已经被确认.
这时, 再去修改 query.js
中的 request
的内容, 查询 CAR12
:
const request = { //targets : --- letting this default to the peers assigned to the channel chaincodeId: 'fabcar', fcn: 'queryCar', args: ['CAR12'] };
保存后运行, 输出:
Store path:/home/pinvon/go/src/github.com/hyperledger/fabric-samples/fabcar/hfc-key-store Successfully loaded user1 from persistence Query has completed, checking results Response is {"colour":"Black","make":"Honda","model":"Accord","owner":"Tom"}
说明账本已经更新了.
在实际应用中, Chaincode需要权限控制, 只有某些具有权限的人才能创造汽车.
官方例子中还有关于车主转让汽车所有权的例子, 就不具体介绍了.
Generated by Emacs 25.x(Org mode 8.x)
Copyright © 2014 - Pinvon - Powered by EGO