Hyperledger Fabric 专题 - 编写第一个 Hyperledger Fabric 应用
注意,本示例选择 javascript 版本。
注解
如果你还不熟悉 Fabric 网络的基本架构,则可能需要先访问 关键概念 部分,然后再继续。
同样值得注意的是,本文是 Fabric 应用程序的入门教程,使用简单的智能合约和应用程序。要更深入地了解 Fabric 应用程序和智能合约,请查看我们的 开发应用程序 部分或 商业论文 教程。
在本教程中,我们将查看一些示例程序,以了解 Fabric 应用程序的工作方式。这些应用程序及其使用的智能合约统称为 FabCar
。它们为了解 Hyperledger Fabric 区块链提供了一个很好的起点。你将学习如何编写应用程序和智能合约以查询和更新帐本,以及如何使用证书颁发机构 (Certificate Authority, CA) 生成 X.509 证书,以供与许可制区块链进行交互的应用程序使用。
我们将使用应用程序 SDK (在 应用程序 主题中进行了详细描述) 来调用智能合约,该智能合约使用智能合约 SDK
来查询和更新帐本 (在 智能合约处理 部分中进行了详细介绍)。
我们将经历三个主要步骤:
- 设置开发环境。我们的应用程序需要与之交互的网络,因此我们将获得一个智能合约和应用程序将使用的基本网络。
了解智能合约示例 FabCar。我们将检查智能合约,以了解其中的交易以及应用程序如何使用它们查询和更新账本。
开发一个使用 FabCar 的示例应用程序。我们的应用程序将使用 FabCar 智能合约来查询和更新账本上的汽车资产。我们将深入研究应用程序及其创建的交易代码,包括查询汽车,查询一系列汽车以及创建新汽车。
完成本教程后,你应该对如何结合应用程序和智能合约与 Fabric 网络中对端节点上托管的账本副本进行交互有基本了解。
注解
1. 搭建区块链网络
注解
下一部分将要求你位于
fabric-samples
本地克隆的子目录first-network
中。
如果你已经完成了 构建第一个网络 的准备工作,那么你已下载 fabric-samples
并有了一个运行的网络。在运行本教程之前,必须停止该网络:
$ cd /path/to/fabric-samples/first-network
$ sudo ./byfn.sh down
如果你之前已经运行过本教程,请使用以下命令杀死任何陈旧或活动的容器。请注意,这将删除你所有与 Fabric 无关的容器。
$ sudo docker rm -f $(sudo docker ps -aq)
$ sudo docker rmi -f $(sudo docker images | grep fabcar | awk '{print $3}')
如果你没有开发环境以及网络和应用程序随附的构件,请访问 先决条件 页面,并确保在计算机上安装了必要的依赖项。
接下来,如果你还没有这样做,请访问 安装示例,二进制文件和 Docker 映像 页面,并按照提供的说明进行操作。克隆了 fabric-samples
并下载了最新的稳定 Fabric 映像和可用实用程序后,请返回本教程。
1.1 启动网络
注解
下一部分将要求你位于
fabric-samples
本地克隆的子目录fabcarc
中。
本教程演示了 FabCar 智能合约和应用程序的 JavaScript 版本,但是
fabric-samples
仓库还包含此示例的 Java 和 TypeScript 版本。要尝试 Java 或 TypeScript 版本,请将下面的./startFabric.sh
的javascript
参数更改为java
或typescript
,然后按照写入终端的说明进行操作。
使用 startFabric.sh
Shell 脚本启动网络。该命令将启动一个由对端网络,交易排序,证书颁发机构等组成的区块链网络。它还将安装并实例化 FabCar
智能合约的 JavaScript 版本,供我们的应用程序用来访问帐本。在学习本教程时,我们将详细了解这些组件。
$ cd /path/to/fabric-samples/fabcar
$ sudo ./startFabric.sh javascript
注意,上述脚本需要几分钟才能执行完成,而且还需要稳定的网络环境,建议在 AWS EC2 上实验。
同时,还需要注意权限问题,因为 docker 需要 root 权限,因此整个目录 first-samples
的所有者有可能已经是 root:root。然后你用非 root 的用户在进行写操作时有可能出现权限问题。
好了,你现在已经建立并运行了一个示例网络,并且已安装和实例化了 FabCar
智能合约。让我们先安装我们的应用程序先决条件,以便我们可以尝试一下,并了解一切如何协同工作。
1.2 安装应用程序
注解
下一部分将要求你位于
fabric-samples
本地克隆的子目录fabcarc/javascript
中。
运行以下命令以安装应用程序的 Fabric 依赖。大约需要一分钟才能完成:
$ cd /path/to/fabric-samples/fabcar/javascript
$ npm install
此过程将安装 package.json 中定义的关键应用程序依赖项。其中最重要的是 fabric-network
类。它使应用程序能够使用身份标识,钱包,和网关来连接到通道,提交交易并等待通知。本教程还使用 fabric-ca-client
类为具有各自证书颁发机构的用户注册,生成有效的身份,然后由 fabric-network
类方法使用该身份标识。
等 npm 安装完成后,一切就绪,可以运行该应用程序。对于本教程,你将主要使用 fabcar/javascript
目录中的应用程序 JavaScript 文件。让我们看一下里面的东西:
$ ls
enrollAdmin.js node_modules package.json registerUser.js
invoke.js package-lock.json query.js wallet
在 fabcar/typescript
目录中有其他程序语言的文件。使用 JavaScript 示例后,你可以阅读这些内容 - 原理相同。
2. 注册管理员用户
注解
以下两节涉及与证书颁发机构的通信。你可能会发现通过运行新的 shell 终端并运行 sudo docker logs -f ca.example.com 在运行即将到来的程序时流式传输 CA 日志很有用。
$ sudo docker logs -f ca.example.com
但示例中,CA 的 docker 容器应该是 ca_peerOrg1 和 ca_peerOrg2,而非 ca.example.com。测试下来,管理员用户 admin 使用的是 ca_peerOrg1。
$ sudo docker logs -f ca_peerOrg1
$ sudo docker logs -f ca_peerOrg2
当我们创建网络时,创建了一个管理员用户 (字面上称为 admin) 作为证书颁发机构 (Certificate Authority, CA) 的登记员 (rigistrar)。我们的第一步是使用 enroll.js
程序为 admin
生成私钥,公钥和 X.509 证书。此过程使用证书签名请求 (Certificate Signing Request, CSR) - 首先在本地生成私钥和公钥,然后将公钥发送到 CA,CA 返回编码的证书以供应用程序使用。然后将这三个凭证存储在钱包中,使我们能够充当 CA 的管理员。
随后,我们将注册并登记一个新的应用程序用户,我们的应用程序将使用该用户与区块链进行交互。
让我们注册用户 admin
:
$ cd /path/to/fabric-samples/fabcar/javascript
$ node enrollAdmin.js
此命令已将 CA 管理员的凭据存储在 wallet
目录中。
3. 注册并登记 user1
现在我们已经有了管理员的凭据,可以在电子钱包中注册一个新用户 user1
,该用户将用于查询和更新帐本:
$ cd /path/to/fabric-samples/fabcar/javascript
$ node registerUser.js
与 admin
注册类似,此程序使用 CSR 来注册 user1
并将其凭据与 admin
的凭据一起存储在钱包中。现在,我们有两个独立用户的身份 admin
和 user1
,我们的应用程序使用了这些身份。
是时候与分类帐进行交互了 ......
4. 查询账本
区块链网络中的每个对端节点都托管账本的副本,应用程序可以通过调用智能合约来查询账本,该合约查询账本的最新值并将其返回给应用程序。
这是查询工作方式的简化表示:
应用程序使用查询从帐本读取数据。最常见的查询涉及帐本中数据的当前值 - 其世界状态。世界状态表示为一组键值对,应用程序可以查询数据以获取单个键或多个键。此外,账本世界状态可以配置为使用像 CouchDB 这样的数据库,当将键值建模为 JSON 数据时,该数据库支持复杂的查询。当寻找与某些关键字匹配特定值的所有资产时,这将非常有用。例如,所有具有特定所有者的汽车。
首先,我们运行 query.js
程序以返回帐本中所有汽车的清单。该程序使用我们的第二个身份 user1
来访问帐本:
$ cd /path/to/fabric-samples/fabcar/javascript
$ node query.js
输出如下所示:
Wallet path: ...fabric-samples/fabcar/javascript/wallet
Transaction has been evaluated, result is:
[{"Key":"CAR0", "Record":{"colour":"blue","make":"Toyota","model":"Prius","owner":"Tomoko"}},
{"Key":"CAR1", "Record":{"colour":"red","make":"Ford","model":"Mustang","owner":"Brad"}},
{"Key":"CAR2", "Record":{"colour":"green","make":"Hyundai","model":"Tucson","owner":"Jin Soo"}},
{"Key":"CAR3", "Record":{"colour":"yellow","make":"Volkswagen","model":"Passat","owner":"Max"}},
{"Key":"CAR4", "Record":{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}},
{"Key":"CAR5", "Record":{"colour":"purple","make":"Peugeot","model":"205","owner":"Michel"}},
{"Key":"CAR6", "Record":{"colour":"white","make":"Chery","model":"S22L","owner":"Aarav"}},
{"Key":"CAR7", "Record":{"colour":"violet","make":"Fiat","model":"Punto","owner":"Pari"}},
{"Key":"CAR8", "Record":{"colour":"indigo","make":"Tata","model":"Nano","owner":"Valeria"}},
{"Key":"CAR9", "Record":{"colour":"brown","make":"Holden","model":"Barina","owner":"Shotaro"}}]
让我们仔细看看这个程序。使用编辑器 (例如 atom 或 Visual Studio) 打开 query.js
。
该应用程序首先从 fabric-network
模块中给作用域引入两个关键类: FileSystemWallet
和 Gateway
。这些类将用于在钱包中定位 user1
身份,并将其用于连接到网络:
const { FileSystemWallet, Gateway } = require('fabric-network');
该应用程序使用网关连接到网络:
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'user1' });
此代码创建一个新的网关,然后使用它将应用程序连接到网络。ccp
描述了网关将使用钱包中的标识 user1
访问的网络。查看如何从 ../../basic-network/connection.json
加载 ccp
并将其解析为 JSON
文件:
const ccpPath = path.resolve(__dirname, '..', '..', 'basic-network', 'connection.json');
const ccpJSON = fs.readFileSync(ccpPath, 'utf8');
const ccp = JSON.parse(ccpJSON);
如果你想进一步了解连接配置文件的结构以及它如何定义网络,请查看 连接配置文件主题。
一个网络可以分为多个通道,下一行重要的代码行将应用程序连接到网络中的特定通道 mychannel
:
const network = await gateway.getNetwork('mychannel');
在此通道中,我们可以访问智能合约 fabcar
与帐本进行交互:
const contract = network.getContract('fabcar');
fabcar
中有许多不同的交易,并且我们的应用程序最初使用 queryAllCars
交易来访问帐本世界状态数据:
const result = await contract.evaluateTransaction('queryAllCars');
evaluateTransaction
方法表示与区块链网络中智能合约的最简单交互之一。它只是选择一个在连接配置文件中定义的对端节点,然后将请求发送到该对端节点,并在此对其进行评估。智能合约会查询对端节点账本副本上的所有汽车,并将结果返回给应用程序。此交互不会导致更新帐本。
5. 智能合约 FabCar
让我们看一下智能合约 FabCar
中的交易。导航到 fabric-samples
根目录下的 chaincode/fabcar/javascript/lib
子目录,然后在编辑器中打开 fabcar.js
。
查看如何使用 Contract
类定义智能合约:
class FabCar extends Contract {...
在该类结构中,你将看到我们定义了以下交易:initLedger
,queryCar
,queryAllCars
,createCar
和 changeCarOwner
。例如:
async queryCar(ctx, carNumber) {...}
async queryAllCars(ctx) {...}
让我们仔细看看 queryAllCars
交易,看看它如何与账本交互。
async queryAllCars(ctx) {
const startKey = 'CAR0';
const endKey = 'CAR999';
const iterator = await ctx.stub.getStateByRange(startKey, endKey);
此代码定义了 queryAllCars
将要从账本中检索的汽车范围。查询将返回介于 CAR0
和 CAR999
之间的每辆汽车 - 总共 1000 辆汽车 (假设每个键均已正确标记)。其余代码遍历查询结果,并将其打包为应用程序的 JSON。
下面是应用程序如何调用智能合约中的不同交易的图示。每个交易使用一组广泛的 API (例如 getStateByRange
) 与帐本进行交互。你可以详细了解有关这些 API 的更多信息。
我们可以看到 queryAllCars
交易和另一个名为 createCar
的交易。我们将在本教程的后面部分中使用它来更新帐本,并向区块链添加一个新区块。
但首先,请返回 query
程序,然后将 evaluateTransaction
请求更改为查询 CAR4
。query
程序现在应如下所示:
const result = await contract.evaluateTransaction('queryCar', 'CAR4');
保存程序,然后导航回到 fabcar/javascript
目录。现在再次运行查询程序:
$ cd /path/to/fabric-samples/fabcar/javascript
$ node query.js
你应该看到以下内容:
Wallet path: ...fabric-samples/fabcar/javascript/wallet
Transaction has been evaluated, result is:
{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}
如果你回头查看交易 queryAllCars
的结果,你会发现 CAR4
是 Adriana 的黑色 Tesla model S,这是在此处返回的结果。
我们可以使用 queryCar
交易通过其关键字 (例如 CAR0) 查询任何汽车,并获取与该汽车相对应的任何品牌,型号,颜色和所有者。
此时,你应该熟悉智能合约中的基本查询交易和查询程序中的少数参数。
是时候更新账本了......
6. 更新账本
现在,我们已经完成了一些账本查询并添加了一些代码,我们可以更新账本了。我们可以进行很多潜在的更新,但让我们首先创建一辆新车。
从应用程序的角度来看,更新帐本很简单。应用程序将交易提交到区块链网络,并且在验证和提交交易后,应用程序会收到有关交易成功的通知。在幕后,这涉及共识过程,通过该过程,区块链网络的不同组件将共同努力,以确保对账本的每个提案更新均有效并以一致且一致的顺序执行。
在上方,你可以查看使得此过程正常运行的主要组件。除了每个托管一个帐本副本以及可选的智能合约副本的多个对端节点之外,网络还包含一个交易排序服务。交易排序服务协调网络的交易;它以明确定义的顺序创建包含交易数据的区块,这些交易源自连接到网络的所有不同应用程序。
我们对帐本的第一次更新将创建一辆新车。我们有一个单独的程序称为 invoke.js
,将用于更新帐本。与查询一样,使用编辑器打开程序并导航到代码块,我们在其中构建交易并将其提交给网络:
await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom');
了解应用程序如何调用智能合约交易 createCar
与名为 Tom
的所有者创建 black Honda Accord
。我们在这里使用 CAR12
作为识别键 (identifying key),只是为了表明我们不需要使用顺序键 (sequential keys)。
保存并运行程序:
$ cd /path/to/fabric-samples/fabcar/javascript
$ node invoke.js
如果调用成功,你将看到以下输出:
Wallet path: ...fabric-samples/fabcar/javascript/wallet
2018-12-11T14:11:40.935Z - info: [TransactionEventHandler]: _strategySuccess: strategy success for transaction "9076cd4279a71ecf99665aed0ed3590a25bba040fa6b4dd6d010f42bb26ff5d1"
Transaction has been submitted
请注意,invoke
应用程序如何使用 submitTransaction
API 而不是 evaluateTransaction
与区块链网络进行交互。
await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom');
submitTransaction
比 evaluateTransaction
更复杂。 SDK 不会与单个对端节点进行交互,而是将 submitTransaction
提案发送到区块链网络中组织需要的每个对端节点。这些对端节点中的每一个都将使用该提案执行所请求的智能合约,以生成交易响应,并签名并返回给 SDK。 SDK 将所有已签名的交易响应收集到一个交易中,然后将其发送给交易排序服务器。交易排序服务器将来自每个应用程序的交易收集并排序成一个交易区块。然后,将这些区块分配给网络中的每个对端节点,在此对每个交易进行验证和提交。最后,通知 SDK,使其可以将控制权返回给应用程序。
注解
submitTransaction
还包括一个监听器,该监听器进行检查以确保交易已通过验证并已提交到账本。应用程序应该利用提交易监听器,或者利用诸如submitTransaction
之类的 API 来完成此任务。否则,你的交易可能无法成功排序,验证并提交到账本。
submitTransaction
为应用程序完成所有这些工作!应用程序,智能合约,对端节点和交易排序服务一起工作以使帐本在整个网络中保持一致的过程称为共识,本节对此进行了详细说明。
要查看此交易已被写入帐本,请返回 query.js
并将参数从 CAR4
更改为 CAR12
。
换句话说,更改下面代码:
const result = await contract.evaluateTransaction('queryCar', 'CAR4');
到
const result = await contract.evaluateTransaction('queryCar', 'CAR12');
再次保存,然后查询:
$ cd /path/to/fabric-samples/fabcar/javascript
$ node query.js
将返回以下内容:
Wallet path: ...fabric-samples/fabcar/javascript/wallet
Transaction has been evaluated, result is:
{"colour":"Black","make":"Honda","model":"Accord","owner":"Tom"}
恭喜你!你已经创建了一辆汽车,并验证了它已记录在帐本中!
因此,既然我们已经做到了,那么就说 Tom 很慷慨,他想将 Honda Accord 送给一个叫 Dave 的人。
为此,请返回 invoke.js
并将智能合约交易从 createCar
更改为 changeCarOwner
,并在输入参数中进行相应的更改:
await contract.submitTransaction('changeCarOwner', 'CAR12', 'Dave');
第一个参数 CAR12
标识将要更改所有者的汽车。第二个参数 Dave
定义了汽车的新所有者。
保存并再次执行程序:
$ cd /path/to/fabric-samples/fabcar/javascript
$ node invoke.js
现在,让我们再次查询帐本,并确保 Dave 现在已与 CAR12 键相关联:
$ cd /path/to/fabric-samples/fabcar/javascript
$ node query.js
它应该返回以下结果:
Wallet path: ...fabric-samples/fabcar/javascript/wallet
Transaction has been evaluated, result is:
{"colour":"Black","make":"Honda","model":"Accord","owner":"Dave"}
CAR12
的所有权已从 Tom
更改为 Dave
。
注解
在实际应用中,智能合约可能会具有一些访问控制逻辑。例如,仅某些授权用户可以创建新车,而只有车主可以将车转让给其他人。
7. 总结
现在,我们已经完成了一些查询和一些更新,你应该对应用程序如何使用智能合约查询或更新账本与区块链网络进行交互有了很好的了解。你已经了解了智能合约,API 和 SDK 在查询和更新中扮演的角色的基本知识,并且应该对如何使用各种类型的应用程序执行其他业务任务和操作有所了解。
8. 额外资源
正如我们在简介中所述,我们有一整节关于 Developing Applications,其中包括有关智能合约,流程和数据设计的深入信息,使用更深入的 Commercial Paper 的教程以及与应用程序的开发相关的大量其他材料。
Reference
- Docs » Tutorials » Writing Your First Application, https://hyperledger-fabric.readthedocs.io/en/release-1.4/write_first_app.html
- Docs » Key Concepts, https://hyperledger-fabric.readthedocs.io/en/release-1.4/key_concepts.html
- Docs » Developing Applications, https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/developing_applications.html
- Docs » Tutorials » Commercial paper tutorial, https://hyperledger-fabric.readthedocs.io/en/release-1.4/tutorial/commercial_paper.html
- Docs » Developing Applications » Application, https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/application.html
- Docs » Developing Applications » Smart Contract Processing, https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/smartcontract.html
- Docs » Architecture Reference » Service Discovery, https://hyperledger-fabric.readthedocs.io/en/release-1.4/discovery-overview.html
- Docs » Key Concepts » Private data, https://hyperledger-fabric.readthedocs.io/en/release-1.4/private-data/private-data.html
- Docs » Tutorials » Building Your First Network, https://hyperledger-fabric.readthedocs.io/en/release-1.4/build_network.html
- Docs » Getting Started » Prerequisites, https://hyperledger-fabric.readthedocs.io/en/release-1.4/prereqs.html
- Docs » Getting Started » Install Samples, Binaries and Docker Images, https://hyperledger-fabric.readthedocs.io/en/release-1.4/install.html
- Docs » Developing Applications » Application design elements » Connection Profile, https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/connectionprofile.html
- Hyperledger Fabric Node.js Contract and Shim, https://fabric-shim.github.io/master/index.html?redirect=true
项目源代码
项目源代码会逐步上传到 Github,地址为 https://github.com/windstamp。
Contributor
- Windstamp, https://github.com/windstamp