创建第一个Fabric应用

本文主要基于 Hyperledger fabric 的官方文档和网上较新的博客,在一台MacOS上创建了第一个Fabric应用fabcar。

设置开发环境

首先配置开发环境,下载fabric-samples代码以及镜像文件(与搭建网络操作一样)。
进入facar目录,有以下文件:
facar1
在开始之前,我们先做些清理工作。执行以下命令以关闭旧的或者启动着的容器:
$ docker rm -f $(docker ps -aq)
清除网络中的缓存:
$ docker network prune

安装客户端&启动网络

接下来的操作都在facar目录下
执行以下命令为应用安装Fabric依赖。fabric-ca-client允许我们的app连接CA server并且检索身份材料。fabric-client允许我们获取身份材料,并且与节点和排序服务对话。
$ npm install
这一步在此处会有延迟,耐心等待一下:
facar2
使用startFabric.sh脚本启动网络。这个命令会初始化各种Fabric实体,还会启动一个使用Golang编写的智能合约容器。
$ ./startFabric.sh
这个脚本做了以下事情(详细介绍在这里):

  • 启动一个peer节点,order节点,证书和CLI容器
  • 创建一个channel,并把peer添加到channel
  • 安装智能合约(例如chaincode)到peer的文件系统,然后初始化chaincode到channel,实例化一个chaincode容器
  • 调用initLedger方法来初始化10辆汽车到channel账本

可以通过$ docker ps命令来查看当前脚本运行后的进程情况.

登记管理员用户

当我们启动网络时,我们通过Certificate Authority注册了一个管理员用户admin。现在我们需要向CA服务器发送一个登记请求,然后为其取回一个登记证书。这里我们不深入登记的细节,只要知道这个证书是构成管理员用户的必要条件。我们随后会使用这个管理员来注册和登记新的用户。现在向CA服务器发送管理员登记请求:
$ node enrollAdmin.js

facar3
这行代码会调用一个证书签名请求(CSR),最后会在项目根目录生成一个新的文件夹hfc-key-store,里面包含了证书和密钥材料。当我们的app需要创建和读取不同身份用户时,需要定位到此文件夹。

User1的注册和登记(Register and Enroll user1)

使用刚刚生成的管理员证书,我们再一次联通CA服务器来注册和登记一个新用户。user1是我们用来查询和更新账本的用户。这里着重说明的是,admin发起了新用户的注册和登记工作(就好像admin扮演了登记员的角色)。现在为admin发起登记和注册请求:
$ node registerUser.js

facar4
和管理员登记一样,上面的指令会调用CSR然后将证书和密钥放入hfc-key-store文件夹中。现在我们有了两个用户的身份材料。

查询账本(Querying the Ledger)

查询是指如何从账本中读取数据。您可以查询单个或者多个键的值,如果账本是以类似于JSON这样的数据存储格式写入的,则可以执行更复杂的搜索(如查找包含某些关键字的所有资产)。

下图是一个查询流程的示意图:
facar5
首先,运行query.js 程序$ node query.js,返回账本上所有汽车列表。我们使用user1作为签名实体。我们的程序中已经指定了user1为签名实体:
fabric_client.getUserContext('user1', true);

user1的登记材料已经放在了hfc-key-store文件夹中,我们只需要简单的告诉程序去获取它就行了。在定义了用户对象后,我们继续读取账本的流程。queryAllCars这个方法已经被提前定义在了app中,它可以查询所有的cars.返回应如下:
facar6
这里有10辆车,一辆属于Adriana的黑色Tesla Model S、一辆属于Brad的红色Ford Mustang、一辆属于Pari的紫罗兰色Fiat Punto等等。账本是基于Key/Value 的,在这里,关键字是从CAR0到CAR9。

query.js中包括:

  1. 初始部分定义了变量,如链码,通道名称和网络端点。在我们的app中,这些变量已经定义好了,但是在真实的开发中,这些变量应该又开发者指定。
    facar7
  2. 构建查询的代码块
    facar8
    当程序运行时,它会调用节点上的fabcar链码,执行queryAllCars函数,不传任何参数。

我们再转至到fabric-samples子目录chaincode/fabcar/go,并在编辑器中打开fabcar.go,查看queryAllCars函数是如何与账本进行交互的。
facar9
这里定义了queryAllCars的范围。在CAR0和CAR999的每辆车。因此,我们理论上可以创建1,000辆汽车,queryAllCars函数将会显示出每一辆汽车的信息。

现在我们返回query.js程序并编辑请求构造函数以查询特定的车辆。为达此目的,我们将函数queryAllCars更改为queryCar并将特定的“Key” 传递给args参数。在这里,我们使用CAR4。 所以我们编辑后的query.js程序现在应该包含以下内容:
facar10
再次执行程序得到一辆车的结果:
facar11
这样,我们就从查询所有车变成了只查询一辆车:Adriana的黑色Tesla Model S。使用queryCar函数,我们可以查询任意关键字(例如CAR0),并获得与该车相对应的制造厂商、型号、颜色和所有者。

更新账本(Updating the Ledger)

facar12

  1. 账本更新是从生成交易提案的应用程序开始的。就像查询一样,我们将会构造一个请求,用来识别要进行交易的通道ID、函数以及智能合约。该程序然后调用channel.SendTransactionProposalAPI将交易建议发送给peer(s)进行认证。
  2. 网络(即endorsing peer)返回一个提案答复,应用程序以此来创建和签署交易请求。该请求通过调用channel.sendTransaction API发送到排序服务器。排序服务器将把交易打包进区块,然后将区块“发送”到通道上的所有peers进行认证。(在我们的例子中,我们只有一个endorsing peer。)
  3. 最后,应用程序使用eh.setPeerAddr API连接到peer的事务监听端口,并调用eh.registerTxEvent注册与特定交易ID相关联的事务。该API使得应用程序获得事务的结果(即成功提交或不成功)。把它当作一个通知机制。
    我们初始调用的目标是简单地创建一个新的汽车。我们有一个独立的用于这些交易的JavaScript程序 - invoke.js。就像查询一样,使用编辑器打开程序并转到构建调用的代码块:
    facar13
    我们可以调用函数createCar或者changeCarOwner。首先我们创建一个红色的Chevy Volt,并把它归属于Nick。在账本中我们的Key值已经用到了CAR9 ,所以这里我们将使用CAR10。更新代码块如下:
    facar14
    保存并运行程序,得到输出:
    facar15
    回到query.js,然后修改参数CAR4为CAR10。
    facar16
    得到以上输出说明我们CAR10创建成功。
    最后,我们来调用最后一个函数changeCarOwner。Nick很慷慨,他想把他的Chevy Volt送给Dave。所以,我们简单编辑invoke.js 如下:
    facar17
    第一个参数定义了哪辆车被变更主人。第二个参数定义了新主人姓名。
    保存并执行,得到输出结果:
    facar18
    facar19
    真实情况下,链码需要权限控制。例如只有某些具有权限的人才能创造新车,也应该只有车主才能转让汽车所有权。