6. Hyperledger Fabric 专题 - 编写第一个 Hyperledger Fabric 应用

Hyperledger Fabric 专题 - 编写第一个 Hyperledger Fabric 应用

注意,本示例选择 javascript 版本。

注解

如果你还不熟悉 Fabric 网络的基本架构,则可能需要先访问 关键概念 部分,然后再继续。

同样值得注意的是,本文是 Fabric 应用程序的入门教程,使用简单的智能合约和应用程序。要更深入地了解 Fabric 应用程序和智能合约,请查看我们的 开发应用程序 部分或 商业论文 教程。

在本教程中,我们将查看一些示例程序,以了解 Fabric 应用程序的工作方式。这些应用程序及其使用的智能合约统称为 FabCar。它们为了解 Hyperledger Fabric 区块链提供了一个很好的起点。你将学习如何编写应用程序和智能合约以查询和更新帐本,以及如何使用证书颁发机构 (Certificate Authority, CA) 生成 X.509 证书,以供与许可制区块链进行交互的应用程序使用。

我们将使用应用程序 SDK (在 应用程序 主题中进行了详细描述) 来调用智能合约,该智能合约使用智能合约 SDK
来查询和更新帐本 (在 智能合约处理 部分中进行了详细介绍)。

我们将经历三个主要步骤:

  1. 设置开发环境。我们的应用程序需要与之交互的网络,因此我们将获得一个智能合约和应用程序将使用的基本网络。
image
  1. 了解智能合约示例 FabCar。我们将检查智能合约,以了解其中的交易以及应用程序如何使用它们查询和更新账本。

  2. 开发一个使用 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.shjavascript 参数更改为 javatypescript,然后按照写入终端的说明进行操作。

使用 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 的凭据一起存储在钱包中。现在,我们有两个独立用户的身份 adminuser1,我们的应用程序使用了这些身份。

是时候与分类帐进行交互了 ......

4. 查询账本

区块链网络中的每个对端节点都托管账本的副本,应用程序可以通过调用智能合约来查询账本,该合约查询账本的最新值并将其返回给应用程序。

这是查询工作方式的简化表示:


image

应用程序使用查询从帐本读取数据。最常见的查询涉及帐本中数据的当前值 - 其世界状态。世界状态表示为一组键值对,应用程序可以查询数据以获取单个键或多个键。此外,账本世界状态可以配置为使用像 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 模块中给作用域引入两个关键类: FileSystemWalletGateway。这些类将用于在钱包中定位 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 {...

在该类结构中,你将看到我们定义了以下交易:initLedgerqueryCarqueryAllCarscreateCarchangeCarOwner。例如:

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 将要从账本中检索的汽车范围。查询将返回介于 CAR0CAR999 之间的每辆汽车 - 总共 1000 辆汽车 (假设每个键均已正确标记)。其余代码遍历查询结果,并将其打包为应用程序的 JSON。

下面是应用程序如何调用智能合约中的不同交易的图示。每个交易使用一组广泛的 API (例如 getStateByRange) 与帐本进行交互。你可以详细了解有关这些 API 的更多信息。

image

我们可以看到 queryAllCars 交易和另一个名为 createCar 的交易。我们将在本教程的后面部分中使用它来更新帐本,并向区块链添加一个新区块。

但首先,请返回 query 程序,然后将 evaluateTransaction 请求更改为查询 CAR4query 程序现在应如下所示:

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. 更新账本

现在,我们已经完成了一些账本查询并添加了一些代码,我们可以更新账本了。我们可以进行很多潜在的更新,但让我们首先创建一辆新车。

从应用程序的角度来看,更新帐本很简单。应用程序将交易提交到区块链网络,并且在验证和提交交易后,应用程序会收到有关交易成功的通知。在幕后,这涉及共识过程,通过该过程,区块链网络的不同组件将共同努力,以确保对账本的每个提案更新均有效并以一致且一致的顺序执行。

image

在上方,你可以查看使得此过程正常运行的主要组件。除了每个托管一个帐本副本以及可选的智能合约副本的多个对端节点之外,网络还包含一个交易排序服务。交易排序服务协调网络的交易;它以明确定义的顺序创建包含交易数据的区块,这些交易源自连接到网络的所有不同应用程序。

我们对帐本的第一次更新将创建一辆新车。我们有一个单独的程序称为 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');

submitTransactionevaluateTransaction 更复杂。 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

项目源代码

项目源代码会逐步上传到 Github,地址为 https://github.com/windstamp

Contributor

  1. Windstamp, https://github.com/windstamp
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容