内容


Hyperledger Composer 基础,第 3 部分

在本地部署您的区块链网络,与之交互并扩展它

探索更多 Hyperledger Composer 工具、UI 和安全功能

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: Hyperledger Composer 基础,第 3 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:Hyperledger Composer 基础,第 3 部分

敬请期待该系列的后续内容。

本教程以第 1 部分为基础,您在其中学习了如何在 Hyperledger Composer Playground 的本地版本中为一个简单的业务网络建模并测试它。在第 2 部分中,您了解了如何通过向 Shipment 资产添加 GPS 读数,在船运集装箱中为一个 IoT GPS 传感器建模,然后修改链代码,以便在货物到达目标港口时发送警报。然后,您将样本 Perishable Goods 网络部署到 IBM Cloud 中的 Composer Online Playground。

现在,在第 3 部分(也是最后一部分)中,您将在您的计算机上安装 Hyperledger Fabric,并将业务网络归档 (BNA) 部署到在您的机器上运行的一个 Hyperledger Fabric 实例(被称为您的本地 Hyperledger Fabric)。您将安装更多工具并生成一个基于 Loopback 的 REST 接口,您可以使用该接口与样本网络区块链应用程序进行交互。

第 3 部分还将详细讨论 Hyperledger Composer 安全概念。这部分的最后,将指导您整合前文内容,并扩展 iot-perishable-network 来创建 Perishable Goods 网络的更“真实”版本。

前提条件

假设您已完成第 2 部分,并已在您的计算机上安装了以下软件:

1

将该网络部署到本地 Hyperledger Fabric

Hyperledger Fabric 是一个为业务用途构建区块链应用程序的框架,而您已经知道,Hyperledger Composer 是一个配套工具,它使得构建在 Hyperledger Fabric 上运行的区块链应用程序变得更容易。目前为止,您已在“纯浏览器”模式下使用了 Composer Playground(第 1 部分),还在 IBM Cloud 中使用了 Composer Online Playground(第 2 部分)。

现在,您将在自己的计算机上安装并运行 Hyperledger Fabric,使用 Composer 命令行接口 (CLI) 与之交互。

1a

获取 Hyperledger Fabric

首先,在您的计算机上创建一个您想在其中执行本地 Hyperledger Composer 开发的目录。在本教程中,我在代码中将此位置称为 $COMPOSER_ROOT,在文字描述中,我将它称为您的 Composer 根目录。此目录可以是您想要的任何位置,但我推荐在主文件夹中创建它,并在当前 shell 中始终将 COMPOSER_ROOT 环境变量设置为该目录,因为这是我在整篇教程中引用该位置的方式。

现在,使用 curl 命令将 fabric-dev-servers.zip 分发文件下载到您的 Composer 根目录,并解压它。命令序列如下所示(假设 ~/HyperledgerComposer 是本示例的根目录):

                mkdir ~/HyperledgerComposer
                export COMPOSER_ROOT=~/HyperledgerComposer
                cd $COMPOSER_ROOT
                mkdir fabric-dev-servers && cd fabric-dev-servers
                curl -O https://raw.githubusercontent.com/hyperledger/composer-tools/master/packages/fabric-dev-servers/fabric-dev-servers.zip
                unzip fabric-dev-servers.zip
1b

启动 Hyperledger Fabric

备注:在继续后面的操作之前,请确保 Docker 正在运行。

第一次运行 Hyperledger Fabric 时,请执行这个命令序列:

                cd $COMPOSER_ROOT/fabric-dev-servers
                ./downloadFabric.sh
                ./startFabric.sh
                ./createPeerAdminCard.sh

运行第一个命令会花费几分钟时间,该命令会获取所有必要的 Docker 映像。第二个命令会启动本地 Hyperledger Fabric。最后一个命令将为 Fabric 管理员(即 PeerAdmin)发放一个 ID 卡。暂时不用担心 ID 卡,本教程后面会介绍它。createPeerAdminCard.sh 脚本的输出如下所示:

                $ ./createPeerAdminCard.sh 
                Development only script for Hyperledger Fabric control
                Running 'createPeerAdminCard.sh'
                FABRIC_VERSION is unset, assuming hlfv1
                FABRIC_START_TIMEOUT is unset, assuming 15 (seconds)
                
                Using composer-cli at v0.15.0
                Successfully created business network card to /tmp/PeerAdmin@hlfv1.card
                
                Command succeeded
                
                Successfully imported business network card: PeerAdmin@hlfv1
                
                Command succeeded
                
                Hyperledger Composer PeerAdmin card has been imported
                The following Business Network Cards are available: 
                
                
                ┌─────────────────┬───────────┬─────────┐
                │ CardName        │ UserId    │ Network │
                ├─────────────────┼───────────┼─────────┤
                │ PeerAdmin@hlfv1 │ PeerAdmin │         │
                └─────────────────┴───────────┴─────────┘
                
                Issue composer card list --name <CardName>  to get details of the card
                
                Command succeeded

记下卡名称,因为您需要使用它来执行本教程中的所有 CLI 命令。

您只需运行 createPeerAdminCard.sh 脚本一次。从现在开始,无论任何时候启动 Hyperledger Fabric,只需像下面这样运行 startFabric.sh 脚本即可:

                cd $COMPOSER_ROOT/fabric-dev-servers
                ./startFabric.sh

该脚本运行完成后,Hyperledger Fabric 就会在您的计算机上运行。 要关闭本地 Hyperledger Fabric,请运行 stopFabric.sh 脚本。

为了本教程的目的,建议您暂时让 Hyperledger Fabric 保持运行。

1c

部署到 Hyperledger Fabric

在第 2 部分,您克隆了来自 GitHub 的 developerWorks 项目,并修改了 Perishable Goods 网络。我在 developerWorks/iot-perishable-network 目录中提供了该网络的一个已完成的版本。本教程的剩余部分将会使用该网络。

要将 iot-perishable-network 部署到您的本地 Fabric,请使用 Composer CLI 并执行这一系列命令:

                cd $COMPOSER_ROOT/developerWorks/iot-perishable-network
                composer network deploy -a dist/iot-perishable-network.bna -A admin -S adminpw -c PeerAdmin@hlfv1 -f networkadmin.card
                composer card import --file networkadmin.card

composer network deploy 命令将指定的网络归档 (dist/iot-perishable-network.bna) 部署到本地 Hyperledger Fabric,使用您之前在运行 createPeerAdminCard.sh 脚本时创建的 PeerAdmin 卡来执行身份验证。完成部署时,会为网络管理员发放一个 ID 卡,网络管理员的凭证(即 userid 和密码(或密钥))存储在 networkadmin.card 文件中。

输出如下所示:

                $ composer network deploy -a dist/iot-perishable-network.bna -A admin -S adminpw -c PeerAdmin@hlfv1 -f networkadmin.card
                Deploying business network from archive: dist/iot-perishable-network.bna
                Business network definition: 
                	Identifier: iot-perishable-network@0.1.12
                	Description: Shipping Perishable Goods Business Network
                
                ✔ Deploying business network definition.This may take a minute...
                Successfully created business network card to networkadmin.card
                
                Command succeeded

composer card import 命令告诉 Hyperledger Composer 导入指定的卡文件,之后可利用该文件来验证在该卡中存储了凭证的用户。在本例中,该卡是网络管理员的。稍后我会更详细地介绍各种卡。输出如下所示:

                $ composer card import --file networkadmin.card 
                Successfully imported business network card: admin@iot-perishable-network
                
                Command succeeded
2

通过 Composer REST 接口与网络进行交互

您已将 iot-perishable-network 部署到本地 Hyperledger Fabric,但是如何查看该网络的内容呢?您如何与它交互呢?Hyperledger Composer 提供了一个名为 composer-rest-server 的工具,它会生成一个基于 Loopback 的 REST 接口来访问您的网络。

2a

安装 Composer REST 接口生成器

首先,您需要安装 composer-rest-server loopback 生成器。转到命令行 (Ubuntu) 或打开一个终端窗口 (MacOS),输入此命令:

npm install -g composer-rest-server

安装完成后,运行 composer-rest-server -v 来验证 composer-rest-server 已正确安装:

                $ composer-rest-server -v
                v0.15.0
2b

生成 REST 接口

在生成 REST 接口之前,确保 Hyperledger Fabric 正在运行且 iot-perishable-network 已部署(否则会没有连接目标)。从命令行运行 composer-rest-server 命令。系统会提示您输入相关信息。您会看到类似下面这样的信息:

清单 1. 启动 REST 服务器
                $ composer-rest-server
                ? Enter the name of the business network card to use: admin@iot-perishable-network
                ? Specify if you want namespaces in the generated REST API: always use namespaces
                ? Specify if you want to enable authentication for the REST API using Passport: No
                ? Specify if you want to enable event publication over WebSockets: No
                ? Specify if you want to enable TLS security for the REST API: No
                
                To restart the REST server using the same options, issue the following command: 
                   composer-rest-server -c admin@iot-perishable-network -n always
                
                Discovering types from business network definition ...
                Discovered types from business network definition
                Generating schemas for all types in business network definition ...
                Generated schemas for all types in business network definition
                Adding schemas for all types to Loopback ...
                Added schemas for all types to Loopback
                Web server listening at: http://localhost:3000
                Browse your REST API at http://localhost:3000/explorer

REST 服务器需要知道如何向 Hyperledger Fabric 执行身份验证,以便与业务网络进行通信。您需要提供一个 ID 卡来实现此目的(第 2 行),比如本教程前面的 admin@iot-perishable-network ID 卡。

命名空间有助于避免名称冲突。在 iot-perishable-network 中,这不是什么大问题,因为只有少量资产、参与者和事务。我认为始终使用命名空间是个不错的主意(第 3 行)。测试包含大量参与者、资产和事务的真实网络非常困难;使用命名空间有助于避免与名称冲突相关的麻烦。

使用 REST 接口执行测试时,暂时无需担心身份验证(第 4 行)、事件和 WebSocket(第 5 行)或 TLS 安全性(第 6 行)。但是,如果您决定部署 REST 服务器作为生产区块链解决方案的一部分,一定要查阅这里或本教程末尾处的相关主题中的链接。

下一次您想使用相同选项来启动 REST 服务器时,只需使用显示的命令(第 9 行)并跳过身份验证的交互步骤!

2c

使用 REST 接口

要使用 REST 接口,请打开浏览器并访问清单 1 中第 18 行上给出的地址。您将看到类似下面这样的信息:

图 1. REST 接口
REST 接口
REST 接口

该接口非常直观。要处理一个对象,可以单击它,在它展开时,您就会看到可以调用的 REST 方法(例如 /GET/POST 等)。要调用 SetupDemo 事务(它实例化了业务模型),可以单击包含 SetupDemo 的行,该行然后会展开到 POST 方法。单击 Try it out! 按钮来调用该事务。如果成功,您会看到一个 HTTP 200 响应代码。然后,您可以浏览模型,查看 Contract 等各种对象,如图 2 所示。

图 2. 显示了 Contract 对象的 REST 接口
显示了 Contract 对象的 REST 接口
显示了 Contract 对象的 REST 接口

视频中,我将详细介绍如何使用 REST 接口,所以一定要观看视频。

2d

保护 REST 接口

这不属于本教程的介绍范畴,但是,如果您计划在生产中运行 REST 接口,则需要一种应对它的策略。单单这一个主题,就需要一整篇教程来介绍! 幸运的是,Hyperledger Composer 网站上有一些资源可提供帮助。下面给出了一些 Composer 文档的链接:

为了方便起见,教程末尾处的相关主题中也列出了这些链接。

视频:使用 Composer REST 接口

使用 composer-rest-server
使用 composer-rest-server

点击查看视频演示查看抄本

3

设置 Hyperledger Composer 安全性

Hyperledger Composer 有两个安全保护级别:

  • Hyperledger Fabric 管理员
  • 业务网络管理员

您的本地 Hyperledger Fabric 的管理员是 Peer Administrator(或者简称为 PeerAdmin),是在安装本地 Hyperledger Fabric 时创建的。每个业务网络也应该有一个管理员,该管理员是在 Hyperledger Fabric 管理员部署该网络时创建的。二者的身份验证都是使用 ID 卡来处理的,接下来您将了解 ID 卡。

3a

ID 卡

ID 卡(或者简称)是一个文件集合,其中包含允许参与者连接到业务网络所需的所有信息。该卡被称为一个身份。在使用 ID 卡之前,必须将其发放给用户,使系统能够验证或授权用户,从而允许他们使用网络。使用卡保护对 Hyperledger Fabric 网络的访问是一种非常便捷的方式。无需时刻记住一个密码(在 Hyperledger Composer 术语中称为密钥),您可以将卡导入 Hyperledger Fabric 中的一个称为钱包的卡集合中。在这以后,您就可以引用该卡对身份执行验证。

指定卡的一般形式为:userid@network,其中 userid 是用户的唯一 id,network 是要向其验证用户的网络。

要实现两种级别的 Hyperledger Composer 安全性,至少需要一张卡:(1) PeerAdmin 和 (2) 业务网络管理员。

PeerAdmin

PeerAdmin 卡是一张特殊的 ID 卡,用于管理本地 Hyperledger Fabric。在开发安装中,比如您计算机上的安装,PeerAdmin ID 卡是在您安装本地 Hyperledger Fabric 时创建的。

Hyperledger Fabric v1.0 网络的 PeerAdmin 卡的形式为 PeerAdmin@hlfv1。在本教程前面,在将 iot-perishable-network 部署到本地 Hyperledger Fabric 时,已使用了此卡。

一般而言,PeerAdmin 是一个为某些功能而保留的特殊角色,比如:

  • 部署业务网络
  • 为业务网络管理员创建、发放和撤销 ID 卡

作为开发人员,在生产 Hyperledger Fabric 安装中,您无法访问 PeerAdmin 卡。而是由 Hyperledger Fabric 管理员部署您的业务网络,创建 ID 卡等。使用本地 Hyperledger Fabric 开发和测试区块链网络时,将会使用 PeerAdmin ID 卡来执行这些功能。

业务网络管理员

当 PeerAdmin 将您的网络部署到 Hyperledger Fabric 时,会向业务网络管理员发放一张 ID 卡,然后,只要业务网络管理员需要对业务网络执行任何操作,比如使用 Composer 命令行接口(您很快会用到),就会使用这张卡。

还记得之前的 admin@iot-perishable-network 卡吗?这是在部署 iot-perishable-network 时由 PeerAdmin(即本地 Hyperledger Fabric 管理员)发放的业务网络管理员卡。

通常,业务网络管理员是一个为某些功能而保留的特殊角色,比如:

  • 更新正在运行的业务网络
  • 查询各种注册表(参与者、身份,等等)
  • 为业务网络中的参与者创建、发放和撤销 ID 卡

不错,还可以使用管理员 ID 卡为特定的参与者发放其他 ID 卡(本教程后面将展示如何实现此操作),以便所有参与者都拥有自己的 ID 卡。可以通过这些卡来控制对网络的访问。

3b

访问控制

谈到访问控制,Composer 通过内置于其架构中的权限来实现基于角色的安全概念,以便处理身份验证和授权。参与者对资源的访问是基于发放给该参与者的身份来控制的。

在本节中,您将了解如何设置访问控制规则,通过访问控制列表 (ACL) 文件 permissions.acl(在编写本文时,该文件必须使用此名称),按照参与者来锁定网络中的资源。以下是一些要点:

  • 基于与规则匹配的资源,对访问应用允许拒绝操作。默认情况下,如果一个资源与任何规则都不匹配,则拒绝对它的访问。
  • 该文件是自上而下进行处理的,所以第一条允许或拒绝访问特定资源的规则是有效的,而后续规则无法覆盖它。
  • 规则的格式非常直观。rule 关键字表示规则的起点,后跟规则名称(该名称必须是唯一的)。
  • 规则由一组定义规则属性的名称/值对组成。

清单 2 给出了来自 iot-perishable-network 业务网络的 permissions.acl 文件。

清单 2. 来自 iot-perishable-networkpermissions.acl
                /**
                 * Sample access control list.
                 */
                rule Default {
                    description: "Allow all participants access to all resources"
                    participant: "ANY"
                    operation: ALL
                    resource: "org.acme.shipping.perishable.*"
                    action: ALLOW
                }
                
                rule SystemACL {
                  description:  "System ACL to permit all access"
                  participant: "org.hyperledger.composer.system.Participant"
                  operation: ALL
                  resource: "org.hyperledger.composer.system.**"
                  action: ALLOW
                }
                
                rule NetworkAdminUser {
                    description: "Grant business network administrators full access to user resources"
                    participant: "org.hyperledger.composer.system.NetworkAdmin"
                    operation: ALL
                    resource: "**"
                    action: ALLOW
                }
                
                rule NetworkAdminSystem {
                    description: "Grant business network administrators full access to system resources"
                    participant: "org.hyperledger.composer.system.NetworkAdmin"
                    operation: ALL
                    resource: "org.hyperledger.composer.system.**"
                    action: ALLOW
                }

每个属性的一般格式为 property: "MATCH_EXPRESSION"。这些属性包括:

description— 放在双引号中的一个人类可读的规则名称。示例:description: "This is a description"

participant— 被允许或拒绝访问的参与者的完全限定名称,放在双引号中。可通过以下方式在 MATCH_EXPRESSION 中指定多个参与者:使用单个星号 (*) 通配符来表示“所有”,使用双星号 (**) 表示在一个命名空间中应用递归,或者使用 ANY 来匹配所有命名空间中的所有参与者。

示例:

  • participant: "org.acme.shipping.perishable.Grower"— 仅将规则应用于 org.acme.shipping.perishable.Grower
  • participant: "org.acme.shipping.perishable.*"—将规则应用于 org.acme.shipping.perishable 命名空间中的所有参与者。
  • participant: "org.acme.shipping.**"—将规则应用于 org.acme.shipping 命名空间中的所有参与者,并以递归方式将规则应用于它下方的所有命名空间。
  • participant: "ANY"— 将规则应用于所有命名空间中的所有参与者。

operationMATCH_EXPRESSION 可能是 CREATEREADUPDATEDELETEALL 中的一个或多个。多个值可以使用逗号分隔(例如 CREATE,READ 将同时授予对资源的 CREATEREAD 访问权)。单独使用 ALL 表示将该规则应用于所有操作。

示例:

  • operation: ALL— 将规则应用于所有 CRUD 操作。
  • operation: CREATE— 仅将规则应用于 CREATE 操作
  • operation: READ,UPDATE— 将规则应用于 READUPDATE 操作。

resource— 定义了规则所应用到的“事物”。资源可以是来自业务模型的任何类(比如资产、参与者或事务)。通过使用通配符,还可以指定多个类,方式与上面的参与者相同。

示例:

  • resource: "org.acme.shipping.perishable.TemperatureReading"— 规则仅应用于 org.acme.shipping.perishable.TemperatureReading 事务。
  • resource: "org.acme.shipping.perishable.*"—规则应用于 org.acme.shipping.perishable 命名空间中的所有类。
  • resource: "org.acme.shipping.**"—将规则应用于 org.acme.shipping 命名空间中的所有资源,并以递归方式将规则应用于它下方的所有命名空间。

action— 在触发规则时应用的操作。应用以下操作之一:ALLOWDENY,前者表示允许访问,后者表示拒绝访问指定参与者访问资源。

示例:

  • action: ALLOW— 允许访问指定资源。
  • action: DENY— 拒绝访问指定资源。

备注:如果您的网络没有 permissions.acl,则允许所有参与者访问(也就是说,访问权是完全开放的 — 没有资源级别的安全保护)。

本教程后面将介绍如何使用规则。此外,Composer ACL 文档包含大量示例,所以一定要查阅该文档。

发放新的 ID - Playground

ID 卡可能比较抽象,很难形象地表示。让我们看看 Playground 中的一张卡(事实上,您已经有一张卡,只是您可能没有意识到)。

启动 Hyperledger Composer Playground 并导入 iot-perishable-network(如果还没有创建 BNA,不要忘记运行 npm install 来创建它)。您在第 2 部分中已导入一个网络,所以如果需要复习,请查阅这一部分。

确保调用了 SetupDemo 事务来创建参与者,并将它们存储在参与者注册表中。为什么?因为不能为未包含在参与者注册表中的参与者发放 ID 卡。要处理来自业务模型的类,必须实例化该模型,SetupDemo 会为您完成此工作。如果想了解处理业务模型的更多信息,请复习本教程系列第 1 部分中的“测试业务网络”小节。

实例化模型后,单击在右上角显示的 admin 来展开下拉列表,并选择 ID Registry。在下一个屏幕上,选择 Issue new ID,然后输入 grower1 作为 ID Name,并从下拉列表中选择 farmer@email.com。参见图 3。

图 3. 在 Playground 中发放一个新 ID 卡
在 Playground 中发放一个新 ID 卡
在 Playground 中发放一个新 ID 卡

单击 Create New 按钮发放该 ID。发放 grower1 ID 卡后,它会显示在 ID 注册表中。参见图 4。

图 4. Playground 中的新 grower1 ID 卡
Playground 中的新 grower1 ID 卡
Playground 中的新 grower1 ID 卡

在本教程后面,我将展示如何使用 Composer CLI 发放 ID 卡。

4

通过 Composer CLI 与网络交互

我在第 2 部分中已经展示了如何安装 Composer 命令行接口 (CLI),而且您在本教程的前面已使用它部署 iot-perishable-network 并导入 admin@iot-perishable-network 卡。现在将使用该 CLI 与 iot-perishable-network 业务网络进行交互。如您所见,该 CLI 非常便捷,因为它适用于脚本,且易于操作。要使用该 CLI,需要转到命令行 (Ubuntu) 或打开终端窗口 (MacOS)。

向网络发送 ping

部署 iot-perishable-network 网络后,可以向它发送 ping,这不会有任何副作用。这是一种查看网络是否已启动并运行的安全方式。输入此命令:

composer network ping --card admin@iot-perishable-network

如果 ping 子命令成功运行,您会看到类似这样的输出:

                $ composer network ping --card admin@iot-perishable-network
                The connection to the network was successfully tested: iot-perishable-network
                	version: 0.15.0
                	participant: org.hyperledger.composer.system.NetworkAdmin#admin
                
                Command succeeded

调用 SetupDemo 事务

SetupDemo 事务被用于实例化模型。 输入此命令:

                composer transaction submit --card admin@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.SetupDemo"}'

输出如下所示:

                $ composer transaction submit --card admin@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.SetupDemo"}'
                Transaction Submitted.
                
                Command succeeded

调用 TemperatureReading 事务

每次获取一个温度读数并需要记录在区块链中时,船运集装箱中的 IoT 传感器就会调用 TemperatureReading 事务。输入此命令:

                composer transaction submit --card admin@iot-perishable-network -d '{ "$class": "org.acme.shipping.perishable.TemperatureReading", "centigrade": 0, "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'

输出如下所示:

                $ composer transaction submit --card admin@iot-perishable-network -d '{ "$class": "org.acme.shipping.perishable.TemperatureReading", "centigrade": 0, "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
                Transaction Submitted.
                
                Command succeeded

调用 ShipmentReceived 事务

收到货物时,Importer 会调用 ShipmentReceived 事务。输入此命令:

                composer transaction submit --card admin@iot-perishable-network -d '{ "$class": "org.acme.shipping.perishable.ShipmentReceived", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'

输出如下所示:

                $ composer transaction submit --card admin@iot-perishable-network -d '{ "$class": "org.acme.shipping.perishable.ShipmentReceived", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
                Transaction Submitted.
                
                Command succeeded

更新网络

在开发期间做了更改并需要更新部署的网络时,更新网络很容易完成。运行 npm install 来重新构建 BNA 后,输入此命令:

                composer network update -a dist/iot-perishable-network.bna --card admin@iot-perishable-network

输出如下所示:

                $ composer network update -a dist/iot-perishable-network.bna --card admin@iot-perishable-network
                Deploying business network from archive: dist/iot-perishable-network.bna
                Business network definition: 
                	Identifier: iot-perishable-network@0.1.12
                	Description: Shipping Perishable Goods Business Network
                
                ✔ Updating business network definition.This may take a few seconds...
                Successfully created business network card to undefined
                
                Command succeeded

一定要查阅 CLI 文档,获得命令、示例等的完整参考资料。

5

融会贯通

借助您目前为止学到的所有知识:您现在可以将 iot-perishable-network 修改为一个更加实用的区块链应用程序。在本节中,您将完成以下任务:

  • 更改网络模型:
    • 添加新参与者来表示船运集装箱中的 IoT 传感器
    • 添加新事务来更实际地对一个船运工作流进行建模
    • 添加新事件,这些事件在工作流推进到某些确定的节点时进行广播
  • 为新事务添加链代码
  • 修改网络的权限
  • 添加 Cucumber 特性测试,以便对更改执行单元测试
  • 构建网络并运行单元测试
  • 将网络部署到本地 Hyperledger Fabric
  • 为 Grower、温度和 GPS 传感器、Shipper 和 Importer 发放 ID
  • 通过 CLI 运行事务

完成对 iot-perishable-network 的修改后,您将学会使用 Hyperledger Composer 所需的基本技能;您将胜任真实的区块链应用程序项目;您将令所有好友艳羡;您的 Hyperledger Composer 和 Fabric 造诣将成为无人不知的传奇(好吧,在最后两条上我可能有点夸张)。

如果您在任何时刻遇到困难并需要帮助,一定要检查解决方案代码,该代码位于名为 iot-perishable-network-advanced 的项目中,在从 GitHub 克隆 developerWorks 项目时,您已将该项目克隆到您的计算机上。

备注:您可以使用任何您熟悉的编辑器,但是我将使用 VSCode(已在第 2 部分中安装)执行本节中的所有更改,下面的操作说明也将反映这一点。如果使用的不是 VSCode,请进行相应调整。

5a

更改模型

要执行上面列出的更改,首先需要有一个模型。在 VSCode 中打开 models/perishable.cto 模型文件并执行以下更改。备注:我省略了下面模型代码段中的注解,以节省本教程的空间。如果查看 iot-perishable-network-advanced,您会看到我为添加的每个模型元素都添加了注解,建议您也这么做。

首先,修改 ShipmentReceived 事务,以便添加一个名为 receivedDateTime 的可选属性,然后添加 3 个扩展 ShipmentTransaction 的新事务,以便在以下时刻更新区块链(账本):货物被 (1) 打包,(2) 装车和 (3) 装载到集装箱船上的时候。

                transaction ShipmentReceived extends ShipmentTransaction {
                  o DateTime receivedDateTime optional
                }
                
                transaction ShipmentPacked extends ShipmentTransaction {
                }
                
                transaction ShipmentPickup extends ShipmentTransaction {
                }
                
                transaction ShipmentLoaded extends ShipmentTransaction {
                }

接下来,修改 Shipment 资产,为与货物相关的事务添加 4 个新属性,以便在这些事务运行时,将它们存储在 Shipment 的区块链中。下面突出显示的行是需要添加的行(第 9-12 行)。

                asset Shipment identified by shipmentId {
                  o String shipmentId
                  o ProductType type
                  o ShipmentStatus status
                  o Long unitCount
                  --> Contract contract
                  o TemperatureReading[] temperatureReadings optional
                  o GpsReading[] gpsReadings optional
                  o ShipmentPacked shipmentPacked optional
                  o ShipmentPickup shipmentPickup optional
                  o ShipmentLoaded shipmentLoaded optional
                  o ShipmentReceived shipmentReceived optional
                }

现在添加一个抽象参与者来表示一个由 String 属性 deviceId 标识的 IoT 设备,并添加它的两个子类:一个表示温度传感器,另一个表示 GPS 传感器。这些参与者将使用各自的读数来更新区块链。设备是网络参与者吗?是的,区块链网络中的参与者不一定非要是人。

                abstract participant IoTDevice identified by deviceId {
                  o String deviceId
                }
                
                participant TemperatureSensor extends IoTDevice {
                }
                
                participant GpsSensor extends IoTDevice {
                }

最后,在真实的区块链应用程序中,会在工作流的每个重要时刻发出事件:打包货物、货物装车等时刻。添加 4 个新事件来表示装运工作流中的这些时刻。

                event ShipmentPackedEvent {
                  o String message
                  --> Shipment shipment
                }
                
                event ShipmentPickupEvent {
                  o String message
                  --> Shipment shipment
                }
                
                event ShipmentLoadedEvent {
                  o String message
                  --> Shipment shipment
                }
                
                event ShipmentReceivedEvent {
                  o String message
                  --> Shipment shipment
                }

这就是对模型执行的更改。接下来,需要对现有 JavaScript 事务做一些修改,并添加新事务的链代码。

5b

添加事务链代码

目前为止,您使用的网络的所有链代码都包含在 lib/logic.js 中。显然,真实的区块链应用程序可能有许多事务,会导致 logic.js 变得混乱,最终变得很难维护。

只要所有事务代码都位于模型的 lib 目录中,Hyperledger Composer 就可以正确解析函数调用。为了演示这一点,我们将对 logic.js 做一些更改,并添加更多 JavaScript 源文件。

在 VSCode 中,右键单击 lib 目录并选择 New File,然后输入 instantiateModelForTesting.js 作为文件名。现在,在文件中粘贴以下标头,否则构建的“许可检查”步骤将会失败,并在您以后构建代码时抛出错误消息:

清单 3. Apache 2.0 许可标头。确保将此内容粘贴到您在项目中创建的每个新 JavaScript 文件中
                /*
                 * Licensed under the Apache License, Version 2.0 (the "License");
                 * you may not use this file except in compliance with the License.
                 * You may obtain a copy of the License at
                 *
                 * http://www.apache.org/licenses/LICENSE-2.0
                 *
                 * Unless required by applicable law or agreed to in writing, software
                 * distributed under the License is distributed on an "AS IS" BASIS,
                 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                 * See the License for the specific language governing permissions and
                 * limitations under the License.
                 */

现在添加以下代码行,以便在设置测试的过程中实例化一个 TemperatureSensor 和 GpsSensor。仅添加突出显示的行。 我已提供了现有代码前后的行作为上下文,以便可以在代码中找到它们:

                .
                .
                // create the shipper
                var shipper = factory.newResource(NS, 'Shipper', 'shipper@email.com');
                var shipperAddress = factory.newConcept(NS, 'Address');
                shipperAddress.country = 'Panama';
                shipper.address = shipperAddress;
                shipper.accountBalance = 0;
            
                // create the Temperature sensor
                var temperatureSensor = factory.newResource(NS, 'TemperatureSensor', 'SENSOR_TEMP001');
                // create the GPS sensor
                var gpsSensor = factory.newResource(NS, 'GpsSensor', 'SENSOR_GPS001');
                
                // create the contract
                var contract = factory.newResource(NS, 'Contract', 'CON_001');
                contract.grower = factory.newRelationship(NS, 'Grower', 'farmer@email.com');
                contract.importer = factory.newRelationship(NS, 'Importer', 'supermarket@email.com');
                .
                .
                .then(function() {
                    return getParticipantRegistry(NS + '.Shipper');
                })
                .then(function(shipperRegistry) {
                    // add the shippers
                    return shipperRegistry.addAll([shipper]);
                })
                .then(function() {
                    return getParticipantRegistry(NS + '.TemperatureSensor');
                })
                .then(function(temperatureSensorRegistry) {
                    // add the temperature sensors
                    return temperatureSensorRegistry.addAll([temperatureSensor]);
                })
                .then(function() {
                    return getParticipantRegistry(NS + '.GpsSensor');
                })
                .then(function(gpsSensorRegistry) {
                    // add the GPS sensors
                    return gpsSensorRegistry.addAll([gpsSensor]);
                })
                .then(function() {
                    return getAssetRegistry(NS + '.Contract');
                })
                .then(function(contractRegistry) {
                    // add the contracts
                    return contractRegistry.addAll([contract]);
                })
                .
                .

现在打开 logic.js,选择 setupDemo 的整个函数主体,然后从 logic.js 剪切它并粘贴到新文件中。最后,将该函数重命名为 instantiateModelForTesting

接下来,在 logic.js 中,将 payOut 函数重命名为 receiveShipment,让它与模型中的事务更加匹配。然后在计算付款金额的代码块后面添加以下代码,以便发出 ShipmentReceivedEvent。突出显示的行是应该修改和/或添加的行(修改第 6 行,添加第 17-27 行)。我提供了现有代码前后的行作为上下文。

                /**
                 * A shipment has been received by an importer
                 * @param {org.acme.shipping.perishable.ShipmentReceived} shipmentReceived - the ShipmentReceived transaction
                 * @transaction
                 */
                function receiveShipment(shipmentReceived) {
                    .
                    .
                    //console.log('Payout: ' + payOut);
                    contract.grower.accountBalance += payOut;
                    contract.importer.accountBalance -= payOut;
                
                    //console.log('Grower: ' + contract.grower.$identifier + ' new balance: ' + contract.grower.accountBalance);
                    //console.log('Importer: ' + contract.importer.$identifier + ' new balance: ' + contract.importer.accountBalance);
                
                    var NS = 'org.acme.shipping.perishable';
                    // Store the ShipmentReceived transaction with the Shipment asset it belongs to
                    shipment.shipmentReceived = shipmentReceived;
                
                    var factory = getFactory();
                    var shipmentReceivedEvent = factory.newEvent(NS, 'ShipmentReceivedEvent');
                    var message = 'Shipment ' + shipment.$identifier + ' received';
                    //console.log(message);
                    shipmentReceivedEvent.message = message;
                    shipmentReceivedEvent.shipment = shipment;
                    emit(shipmentReceivedEvent);
                
                    return getParticipantRegistry('org.acme.shipping.perishable.Grower')
                        .then(function (growerRegistry) {
                            // update the grower's balance
                            return growerRegistry.update(contract.grower);
                        })
                    .
                    .

现在,您需要为添加到模型的 3 个新事务添加 JavaScript 链代码,而且您将为此创建一个新的 JavaScript 源文件。在 VSCode 中,右键单击 lib 目录并选择 New File,然后输入 shipment.js 作为文件名。将以下函数添加到此文件。确保在文件顶部粘贴 Apache 2.0 注解标头。参见清单 3。

事务:ShipmentPacked 链代码

首先,添加一个函数来处理 ShipmentPacked 事务。当 Grower 参与者(或它授权的代理之一)打包货物供装车和运输时,它调用 ShipmentPacked 区块链事务将此事实记录在账本中。

调用此事务时,会发出一个 ShipmentPackedEvent,向所有相关方通知此事件,然后链代码会更新账本。

使用“动词/对象”函数命名样式,将新函数命名为 packShipment。在 JavaScript 代码中,必须添加下面所示的注解,以便 Hyperledger Composer 将该函数识别为事务。

复制下面的所有代码并粘贴到 shipment.js 中。

                /**
                 * ShipmentPacked transaction - invoked when the Shipment is packed and ready for pickup.
                 * 
                 * @param {org.acme.shipping.perishable.ShipmentPacked} shipmentPacked - the ShipmentPacked transaction
                 * @transaction
                 */
                function packShipment(shipmentPacked) {
                    var shipment = shipmentPacked.shipment;
                    var NS = 'org.acme.shipping.perishable';
                    var contract = shipment.contract;
                    var factory = getFactory();
                
                    // Add the ShipmentPacked transaction to the ledger (via the Shipment asset)
                    shipment.shipmentPacked = shipmentPacked;
                
                    // Create the message
                    var message = 'Shipment packed for shipment ' + shipment.$identifier;
                
                    // Log it to the JavaScript console
                    //console.log(message);
                
                    // Emit a notification telling subscribed listeners that the shipment has been packed
                    var shipmentPackedEvent = factory.newEvent(NS, 'ShipmentPackedEvent');
                    shipmentPackedEvent.shipment = shipment;
                    shipmentPackedEvent.message = message;
                    emit(shipmentPackedEvent);
                
                    // Update the Asset Registry
                    return getAssetRegistry(NS + '.Shipment')
                        .then(function (shipmentRegistry) {
                            // add the temp reading to the shipment
                            return shipmentRegistry.update(shipment);
                        });
                }

事务:ShipmentPickup 链代码

接下来,当 Shipper 参与者(或它授权的代理之一)将打包的货物装车运输时,它会调用 ShipmentPickup 区块链事务,以便将此事实记录到账本中。添加一个名为 pickupShipment 的函数来处理 ShipmentPickup 事务。

调用此事务时,会发出一个 ShipmentPickupEvent,向所有相关方通知此事件,然后链代码将更新账本。

复制下面的所有代码并粘贴到 shipment.js 中。

                /**
                 * ShipmentPickup - invoked when the Shipment has been picked up from the packer.
                 * 
                 * @param {org.acme.shipping.perishable.ShipmentPickup} shipmentPickup - the ShipmentPickup transaction
                 * @transaction
                 */
                function pickupShipment(shipmentPickup) {
                    var shipment = shipmentPickup.shipment;
                    var NS = 'org.acme.shipping.perishable';
                    var contract = shipment.contract;
                    var factory = getFactory();
                
                    // Add the ShipmentPacked transaction to the ledger (via the Shipment asset)
                    shipment.shipmentPickup = shipmentPickup;
                
                    // Create the message
                    var message = 'Shipment picked up for shipment ' + shipment.$identifier;
                
                    // Log it to the JavaScript console
                    //console.log(message);
                
                    // Emit a notification telling subscribed listeners that the shipment has been packed
                    var shipmentPickupEvent = factory.newEvent(NS, 'ShipmentPickupEvent');
                    shipmentPickupEvent.shipment = shipment;
                    shipmentPickupEvent.message = message;
                    emit(shipmentPickupEvent);
                
                    // Update the Asset Registry
                    return getAssetRegistry(NS + '.Shipment')
                        .then(function (shipmentRegistry) {
                            // add the temp reading to the shipment
                            return shipmentRegistry.update(shipment);
                        });
                }

事务:ShipmentLoaded 链代码

最后,当 Shipper 参与者(或它的授权代理之一)将货物装到集装箱船上时,它会调用 ShipmentLoaded 区块链事务,以便将此事实记录到账本中。添加一个名为 loadShipment 的函数来处理 ShipmentLoaded 事件。

调用此事务时,会发出一个 ShipmentLoadedEvent,向所有相关方通知此事件,然后链代码将更新账本。

复制下面的所有代码并粘贴到 shipment.js 中。

                /**
                 * ShipmentLoaded - invoked when the Shipment has been loaded onto the container ship.
                 * 
                 * @param {org.acme.shipping.perishable.ShipmentLoaded} shipmentLoaded - the ShipmentLoaded transaction
                 * @transaction
                 */
                function loadShipment(shipmentLoaded) {
                    var shipment = shipmentLoaded.shipment;
                    var NS = 'org.acme.shipping.perishable';
                    var contract = shipment.contract;
                    var factory = getFactory();
                
                    // Add the ShipmentPacked transaction to the ledger (via the Shipment asset)
                    shipment.shipmentLoaded = shipmentLoaded;
                
                    // Create the message
                    var message = 'Shipment loaded for shipment ' + shipment.$identifier;
                
                    // Log it to the JavaScript console
                    //console.log(message);
                
                    // Emit a notification telling subscribed listeners that the shipment has been packed
                    var shipmentLoadedEvent = factory.newEvent(NS, 'ShipmentLoadedEvent');
                    shipmentLoadedEvent.shipment = shipment;
                    shipmentLoadedEvent.message = message;
                    emit(shipmentLoadedEvent);
                
                    // Update the Asset Registry
                    return getAssetRegistry(NS + '.Shipment')
                        .then(function (shipmentRegistry) {
                            // add the temp reading to the shipment
                            return shipmentRegistry.update(shipment);
                        });
                }
5c

更改访问控制权限

在真实的区块链应用程序中,向所有参与者授予相应权限,使他们能执行想要的任何操作不是理想的做法。例如,Grower 不能调用仅对 Shipper 有意义的事务,而且不应允许 GPS 传感器将温度读数记录到区块链中。

那么如何预防出现这种情况呢?换言之,您如何构建您的网络来控制对其资源的访问呢?通过访问控制列表 (ACL) 文件来实现访问控制。

您已在本系列的第 1 和第 2 部分中看到过访问控制,但那时我只是一带而过,而且我承诺会在以后更详细地介绍它。 好吧,这个时刻已经到了。

是时候讲讲访问控制了,首先看看 permissions.acl。您之前看到过这个文件,现在是时候应用这些知识了。您将修改 iot-perishable-network,以确保只有适合访问资源的参与者才能访问它。作为演示,我在表 1 中总结了适合 iot-perishable-network 的访问控制设置。

您可能注意到了规则名称,并认为它们看起来有点奇怪。我希望在名称中包含尽可能多的信息,而不会让它过长或完全没有意义。我的想法是这样的:将规则命名为 Participant_Operation_Resource。所以 Grower_R_Grower 表示“授予 Grower 参与者对 Grower 实例的 READ 访问权。”

表 1. 参与者访问控制 — 访问类型位于括号中:R = READ、U = UPDATE、C = CREATE
参与者访问的参与者访问的资产访问的事务
GrowerGrower (R)Shipment (RU)、Contract (RU)ShipmentPacked (C)
ShipperShipper (R)Shipment (RU)、Contract (RU)ShipmentPickup、ShipmentLoaded (C)
ImporterImporter (R)、Grower (RU)Shipment (RU)、ContractShipmentReceived (C)
TemperatureSensorShipment (RU)、Contract (RU)TemperatureReading (C)
GpsSensorShipment (RU)、Contract (RU)GpsReading (C)

下面编写了这些规则。打开 permissions.acl(在网络项目的根目录中),删除其当前内容,将它们替换为下面的清单。完整的清单非常长,所以我将一次一小节地介绍它。

前两条规则分别授予 Hyperledger Composer 系统 ParticipantNetworkAdmin 参与者访问 org.hyperledger.composer.system 命名空间中的所有内容和网络中的每个类的访问权。

复制下面的所有代码并粘贴到 permissions.acl 中。

                /**
                 * System and Network Admin access rules
                 */
                rule SystemACL {
                  description:  "System ACL to permit all access"
                  participant: "org.hyperledger.composer.system.Participant"
                  operation: ALL
                  resource: "org.hyperledger.composer.system.**"
                  action: ALLOW
                }
                
                rule NetworkAdminUser {
                    description: "Grant business network administrators full access to user resources"
                    participant: "org.hyperledger.composer.system.NetworkAdmin"
                    operation: ALL
                    resource: "**"
                    action: ALLOW
                }

接下来的一组规则授予 iot-perishable-network 中的参与者访问其他参与者(包括它们自己)的权限。此访问权有所不同,因为这样才适合网络的业务功能。例如,Grower 只能访问 Grower 类,但是因为 Importer 执行 ShipmentReceived 事务(用于更新 Importer 和 Grower 的帐户余额),所以 Importer 需要拥有对 Importer 和 Grower 的 READ 和 UPDATE 访问权。

复制下面的所有代码并粘贴到 permissions.acl 中。

                /**
                 * Rules for Participant registry access
                 */
                rule Grower_R_Grower {
                    description: "Grant Growers access to Grower resources"
                    participant: "org.acme.shipping.perishable.Grower"
                    operation: READ
                    resource: "org.acme.shipping.perishable.Grower"
                    action: ALLOW
                }
                
                rule Shipper_R_Shipper {
                    description: "Grant Shippers access to Shipper resources"
                    participant: "org.acme.shipping.perishable.Shipper"
                    operation: READ
                    resource: "org.acme.shipping.perishable.Shipper"
                    action: ALLOW
                }
                
                rule Importer_RU_Importer {
                    description: "Grant Importers access to Importer resources"
                    participant: "org.acme.shipping.perishable.Importer"
                    operation: READ,UPDATE
                    resource: "org.acme.shipping.perishable.Importer"
                    action: ALLOW
                }
                
                rule Importer_RU_Grower {
                    description: "Grant Importers access to Grower participant"
                    participant: "org.acme.shipping.perishable.Importer"
                    operation: READ,UPDATE
                    resource: "org.acme.shipping.perishable.Grower"
                    action: ALLOW
                }

接下来是访问网络资产的规则。在本例中,我想向所有参与者授予对 Shipment 和 Contract 资源的访问权。

复制下面的所有代码并粘贴到 permissions.acl 中。

                /**
                 * Rules for Asset registry access
                 */
                rule ALL_RU_Shipment {
                    description: "Grant All Participants in org.acme.shipping.perishable namespace READ/UPDATE access to Shipment assets"
                    participant: "org.acme.shipping.perishable.*"
                    operation: READ,UPDATE
                    resource: "org.acme.shipping.perishable.Shipment"
                    action: ALLOW
                }
                
                rule ALL_RU_Contract {
                    description: "Grant All Participants in org.acme.shipping.perishable namespace READ/UPDATE access to Contract assets"
                    participant: "org.acme.shipping.perishable.*"
                    operation: READ,UPDATE
                    resource: "org.acme.shipping.perishable.Contract"
                    action: ALLOW
                }

接下来是调用事务的规则(这需要 CREATE 访问权)。 如您所见,应该逐个案例地应用这些规则,因为这样做才有意义。例如,Grower 参与者不需要访问 Shipper 事务(反之亦然)。

复制下面的所有代码并粘贴到 permissions.acl 中。

                /**
                 * Rules for Transaction invocations
                 */
                rule Grower_C_ShipmentPacked {
                    description: "Grant Growers access to invoke ShipmentPacked transaction"
                    participant: "org.acme.shipping.perishable.Grower"
                    operation: CREATE
                    resource: "org.acme.shipping.perishable.ShipmentPacked"
                    action: ALLOW
                }
                
                rule Shipper_C_ShipmentPickup {
                    description: "Grant Shippers access to invoke ShipmentPickup transaction"
                    participant: "org.acme.shipping.perishable.Shipper"
                    operation: CREATE
                    resource: "org.acme.shipping.perishable.ShipmentPickup"
                    action: ALLOW
                }
                
                rule Shipper_C_ShipmentLoaded {
                    description: "Grant Shippers access to invoke ShipmentLoaded transaction"
                    participant: "org.acme.shipping.perishable.Shipper"
                    operation: CREATE
                    resource: "org.acme.shipping.perishable.ShipmentLoaded"
                    action: ALLOW
                }
                
                rule GpsSensor_C_GpsReading {
                    description: "Grant IoT GPS Sensor devices full access to the appropriate transactions"
                    participant: "org.acme.shipping.perishable.GpsSensor"
                    operation: CREATE
                    resource: "org.acme.shipping.perishable.GpsReading"
                    action: ALLOW
                }
                
                rule TemperatureSensor_C_TemperatureReading {
                    description: "Grant IoT Temperature Sensor devices full access to the appropriate transactions"
                    participant: "org.acme.shipping.perishable.TemperatureSensor"
                    operation: CREATE
                    resource: "org.acme.shipping.perishable.TemperatureReading"
                    action: ALLOW
                }
                
                rule Importer_C_ShipmentReceived {
                    description: "Grant Importers access to invoke the ShipmentReceived transaction"
                    participant: "org.acme.shipping.perishable.Importer"
                    operation: CREATE
                    resource: "org.acme.shipping.perishable.ShipmentReceived"
                    action: ALLOW
                }

最后一条规则表明(已生效),“如果我们上面的代码中没有显式授予对某个资源的访问权,则拒绝访问它。”这是 Hyperledger Composer 目前的默认行为,但是如果后续版本中的默认行为发生改变,此规则可以确保无论默认行为是什么,网络都具有与此类似的行为。

复制下面的所有代码并粘贴到 permissions.acl 中。

                /**
                 * Make sure all resources are locked down by default.
                 * If permissions need to be granted to certain resources, that should happen
                 * above this rule.Anything not explicitly specified gets locked down.
                 */
                rule Default {
                    description: "Deny all participants access to all resources"
                    participant: "ANY"
                    operation: ALL
                    resource: "org.acme.shipping.perishable.*"
                    action: DENY
                }

这是 permissions.acl 的全部内容。保存该文件,准备好对这些权限执行单元测试。

5d

添加特性测试

等等,要对权限执行单元测试?不错。permissions.acl 中的所有权限都可以使用 Cucumber 执行单元测试。我在第 2 部分中介绍了 Cucumber,现在您将使用 Cucumber 对上一节添加到 permissions.acl 中的新事务逻辑和 ACL 规则执行单元测试。

Cucumber 的最棒的一点是,它的语法 (Gherkin) 很直观,所以我不需要解释您将看到的大量测试。

基本测试方案

在 VSCode 中,打开 iot-perishable.feature,然后删除其内容,将它们替换为您阅读本节时在下文中看到的清单。

首先,您将设置特性测试,如下所示。请注意第 14-16 行,该测试发放了指定参与者的身份(即 ID 卡)。后面的单元测试将使用这些身份来测试上一节中添加到 permissions.acl 的安全权限。

复制下面的所有代码并粘贴到 iot-perishable.feature 中。

                Feature: Basic Test Scenarios
                    Background: 
                        Given I have deployed the business network definition ..
                        And I have added the following participants
                        """
                        [
                        {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":0},
                        {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":0}
                        ]
                        """
                        And I have added the following participants of type org.acme.shipping.perishable.TemperatureSensor
                            | deviceId |
                            | TEMP_001 |
                        And I have issued the participant org.acme.shipping.perishable.Grower#grower@email.com with the identity grower1
                        And I have issued the participant org.acme.shipping.perishable.Importer#supermarket@email.com with the identity importer1
                        And I have issued the participant org.acme.shipping.perishable.TemperatureSensor#TEMP_001 with the identity sensor_temp1
                        And I have added the following asset of type org.acme.shipping.perishable.Contract
                            | contractId | grower           | shipper               | importer           | arrivalDateTime  | unitPrice | minTemperature | maxTemperature | minPenaltyFactor | maxPenaltyFactor |
                            | CON_001    | grower@email.com | supermarket@email.com | supermarket@email.com | 10/26/2018 00:00 | 0.5       | 2              | 10             | 0.2              | 0.1              | 
                        And I have added the following asset of type org.acme.shipping.perishable.Shipment
                            | shipmentId | type    | status     | unitCount | contract |
                            | SHIP_001   | BANANAS | IN_TRANSIT | 5000      | CON_001  |
                        And I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
                            | shipment | centigrade |
                            | SHIP_001 | 4          |
                            | SHIP_001 | 5          |
                            | SHIP_001 | 10         |
                        When I use the identity importer1

请注意上面清单中第 28 行。建议始终在 Cucumber 测试中放入一个“默认身份”,以确保您的安全权限始终是经过测试的(参见边栏)。如果您愿意的话,可以始终在特定的方案测试中将此行替换为后续的“When I use the identity xyz”,就像您在本节中将看到的那样。

您将运行的第一个方案是,测试没有超出所商定范围的温度读数时的情况。在第 2 行上可以注意到,此测试将以 importer1 身份(这是一个 Importer)运行。

复制下面的所有代码并粘贴到 iot-perishable.feature 中。

                    Scenario: When the temperature range is within the agreed-upon boundaries
                        When I use the identity importer1
                        And I submit the following transaction of type org.acme.shipping.perishable.ShipmentReceived
                            | shipment |
                            | SHIP_001 |
                        Then I should have the following participants
                        """
                        [
                        {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":2500},
                        {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":-2500}
                        ]
                        """

下一个方案将测试温度读数低于商定的阈值时的情况。支付金额应该更少(由于合约中的低温罚金)。

请注意,该测试覆盖了默认身份,而且首先以 sensor_temp1 身份(一个 TemperatureSensor 参与者)运行(第 2 行),然后当前身份切换为 importer1(一个 Importer)(第 6 行)。如果测试未像这样运行,则会尝试以 importer1 身份(已在 Background 小节中设置)执行 TemperatureReading 事务,该身份没有调用该事务的权限。然后该身份必须切换回来,否则 ShipmentReceived 事务调用会失败,因为 TemperatureSensor 参与者没有调用该事务的权限。

复制下面的所有代码并粘贴到 iot-perishable.feature 中。

                    Scenario: When the low/min temperature threshold is breached by 2 degrees C
                        When I use the identity sensor_temp1
                        And I submit the following transaction of type org.acme.shipping.perishable.TemperatureReading
                            | shipment | centigrade |
                            | SHIP_001 | 0          |
                        Then I use the identity importer1
                        And I submit the following transaction of type org.acme.shipping.perishable.ShipmentReceived
                            | shipment |
                            | SHIP_001 |
                        Then I should have the following participants
                        """
                        [
                        {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":500},
                        {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":-500}
                        ]
                        """

下一个方案将测试温度读数高于阈值时的情况。像上一个方案一样,这个方案也必须使用两个不同的身份来运行。

复制下面的所有代码并粘贴到 iot-perishable.feature 中。

                    Scenario: When the hi/max temperature threshold is breached by 2 degrees C
                        When I use the identity sensor_temp1
                        And I submit the following transaction of type org.acme.shipping.perishable.TemperatureReading
                            | shipment | centigrade |
                            | SHIP_001 | 12          |
                        Then I use the identity importer1
                        When I submit the following transaction of type org.acme.shipping.perishable.ShipmentReceived
                            | shipment |
                            | SHIP_001 |
                        Then I should have the following participants
                        """
                        [
                        {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":1500},
                        {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":-1500}
                        ]
                        """

最后,ShipmentReceived 事务必须以 Importer(唯一有权调用它的参与者)身份运行,而且在这么做时会发出一个事件。已在 Background 小节中为此方案设置了该身份。

复制下面的所有代码并粘贴到 iot-perishable.feature 中。

                    Scenario: When shipment is received a ShipmentReceivedEvent should be broadcast
                        When I submit the following transaction of type org.acme.shipping.perishable.ShipmentReceived
                            | shipment |
                            | SHIP_001 |
                        Then I should have received the following event of type org.acme.shipping.perishable.ShipmentReceivedEvent
                            | message                    | shipment |
                            | Shipment SHIP_001 received | SHIP_001 |

这是 iot-perishable.feature 的全部内容。保存该文件。

测试 IoT 设备

剩余的特性测试的工作原理与刚看到的测试基本相同,所以我不再反复解释。完成本节后,只需按照操作说明进行操作,将这些清单全部粘贴到新的 .feature 文件中。

在 VSCode 中,单击 features 目录,选择 New File,并将该文件称为 sensors.feature。在编辑器窗口中打开该文件后,将下面的清单复制并粘贴到新的空文件中:

复制下面的所有代码并粘贴到 sensors.feature 中。

                Feature: Tests related to IoT Devices
                
                    Background: 
                        Given I have deployed the business network definition ..
                        And I have added the following participants
                        """
                        [
                        {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":0},
                        {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Panama"}, "accountBalance":0},
                        {"$class":"org.acme.shipping.perishable.Importer", "email":"importer@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":0}
                        ]
                        """
                        And I have added the following participants of type org.acme.shipping.perishable.TemperatureSensor
                            | deviceId |
                            | TEMP_001 |
                        And I have added the following participant of type org.acme.shipping.perishable.GpsSensor
                            | deviceId |
                            | GPS_001  |
                        And I have issued the participant org.acme.shipping.perishable.Grower#grower@email.com with the identity grower1
                        And I have issued the participant org.acme.shipping.perishable.Shipper#shipper@email.com with the identity shipper1
                        And I have issued the participant org.acme.shipping.perishable.TemperatureSensor#TEMP_001 with the identity sensor_temp1
                        And I have issued the participant org.acme.shipping.perishable.GpsSensor#GPS_001 with the identity sensor_gps1
                        And I have added the following asset of type org.acme.shipping.perishable.Contract
                            | contractId | grower           | shipper               | importer              | arrivalDateTime  | unitPrice | minTemperature | maxTemperature | minPenaltyFactor | maxPenaltyFactor |
                            | CON_001    | grower@email.com | shipper@email.com     | supermarket@email.com | 10/26/2018 00:00 | 0.5       | 2              | 10             | 0.2              | 0.1              | 
                        And I have added the following asset of type org.acme.shipping.perishable.Shipment
                            | shipmentId | type    | status     | unitCount | contract |
                            | SHIP_001   | BANANAS | IN_TRANSIT | 5000      | CON_001  |
                
                    Scenario: Test TemperatureThresholdEvent is emitted when the max temperature threshold is violated
                        When I use the identity sensor_temp1
                        When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
                            | shipment | centigrade |
                            | SHIP_001 | 11         |
                        
                        Then I should have received the following event of type org.acme.shipping.perishable.TemperatureThresholdEvent
                            | message                                                                          | temperature | shipment |
                            | Temperature threshold violated! Emitting TemperatureEvent for shipment: SHIP_001 | 11          | SHIP_001 |    
                
                    Scenario: Test TemperatureThresholdEvent is emitted when the min temperature threshold is violated
                        When I use the identity sensor_temp1
                        When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
                            | shipment | centigrade |
                            | SHIP_001 | 0          |
                        
                        Then I should have received the following event of type org.acme.shipping.perishable.TemperatureThresholdEvent
                            | message                                                                          | temperature | shipment |
                            | Temperature threshold violated! Emitting TemperatureEvent for shipment: SHIP_001 | 0           | SHIP_001 |
                    
                    Scenario: Test ShipmentInPortEvent is emitted when GpsReading indicates arrival at destination port
                        When I use the identity sensor_gps1
                        When I submit the following transaction of type org.acme.shipping.perishable.GpsReading
                            | shipment | readingTime | readingDate | latitude | latitudeDir | longitude | longitudeDir |
                            | SHIP_001 | 120000      | 20171025    | 40.6840  | N           | 74.0062   | W            |
                
                        Then I should have received the following event of type org.acme.shipping.perishable.ShipmentInPortEvent
                            | message                                                                  | shipment |
                            | Shipment has reached the destination port of /LAT:40.6840N/LONG:74.0062W | SHIP_001 |
                
                    Scenario: GpsSensor sensor_gps1 can invoke GpsReading transaction
                        When I use the identity sensor_gps1
                        When I submit the following transaction of type org.acme.shipping.perishable.GpsReading
                            | shipment | readingTime | readingDate | latitude | latitudeDir | longitude | longitudeDir |
                            | SHIP_001 | 120000      | 20171025    | 40.6840  | N           | 74.0062   | W            |
                        Then I should have received the following event of type org.acme.shipping.perishable.ShipmentInPortEvent
                            | message                                                                  | shipment |
                            | Shipment has reached the destination port of /LAT:40.6840N/LONG:74.0062W | SHIP_001 |
                    
                    Scenario: Temperature Sensor cannot invoke GpsReading transaction
                        When I use the identity sensor_temp1
                        When I submit the following transaction of type org.acme.shipping.perishable.GpsReading
                            | shipment | readingTime | readingDate | latitude | latitudeDir | longitude | longitudeDir |
                            | SHIP_001 | 120000      | 20171025    | 40.6840  | N           | 74.0062   | W            |
                        Then I should get an error matching /Participant .* does not have 'CREATE' access to resource/
                
                    Scenario: Gps Sensor cannot invoke TemperatureReading transaction
                        When I use the identity sensor_gps1
                        When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
                            | shipment | centigrade |
                            | SHIP_001 | 11         |        
                        Then I should get an error matching /Participant .* does not have 'CREATE' access to resource/
                
                    Scenario: Grower cannot invoke TemperatureReading transaction
                        When I use the identity sensor_gps1
                        When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
                            | shipment | centigrade |
                            | SHIP_001 | 11         |        
                        Then I should get an error matching /Participant .* does not have 'CREATE' access to resource/

现在保存此文件。

与 Grower 相关的测试

在 VSCode 中,单击 features 目录,选择 New File,并将该文件称为 grower.feature。在编辑器窗口中打开该文件后,将下面的清单复制并粘贴到新的空文件中:

复制下面的所有代码并粘贴到 grower.feature 中。

                Feature: Tests related to Growers
                
                    Background: 
                        Given I have deployed the business network definition ..
                        And I have added the following participants
                        """
                        [
                        {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":0},
                        {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Paname"}, "accountBalance":0}
                        ]
                        """
                        And I have issued the participant org.acme.shipping.perishable.Grower#grower@email.com with the identity grower1
                        And I have issued the participant org.acme.shipping.perishable.Shipper#shipper@email.com with the identity shipper1
                        And I have added the following asset of type org.acme.shipping.perishable.Contract
                            | contractId | grower           | shipper               | importer              | arrivalDateTime  | unitPrice | minTemperature | maxTemperature | minPenaltyFactor | maxPenaltyFactor |
                            | CON_001    | grower@email.com | shipper@email.com     | supermarket@email.com | 10/26/2018 00:00 | 0.5       | 2              | 10             | 0.2              | 0.1              | 
                        And I have added the following asset of type org.acme.shipping.perishable.Shipment
                            | shipmentId | type    | status     | unitCount | contract |
                            | SHIP_001   | BANANAS | IN_TRANSIT | 5000      | CON_001  |
                        When I use the identity grower1
                
                    Scenario: grower1 can read Grower assets
                        Then I should have the following participants
                        """
                        [
                        {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":0}
                        ]
                        """
                    
                    Scenario: grower1 invokes the ShipmentPacked transaction
                        And I submit the following transaction of type org.acme.shipping.perishable.ShipmentPacked
                            | shipment |
                            | SHIP_001 |
                        Then I should have received the following event of type org.acme.shipping.perishable.ShipmentPackedEvent
                            | message                               | shipment |
                            | Shipment packed for shipment SHIP_001 | SHIP_001 |
                
                    Scenario: shipper1 cannot read Grower assets
                        When I use the identity shipper1
                        And I should have the following participants
                        """
                        [
                        {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":0}
                        ]
                        """
                        Then I should get an error matching /Object with ID .* does not exist/
                    
                    Scenario: shipper1 cannot invoke the ShipmentPacked transaction
                        When I use the identity shipper1
                        And I submit the following transaction of type org.acme.shipping.perishable.ShipmentPacked
                            | shipment |
                            | SHIP_001 |
                        Then I should get an error matching /Participant .* does not have 'CREATE' access to resource/

现在保存此文件。请注意使用的默认身份(突出显示的行)。 只有在特定方案需要一个不同身份时,才需要为它设置一个身份。

在 VSCode 中,单击 features 目录,选择 New File,并将该文件称为shipper.feature。在编辑器窗口中打开该文件后,将下面的清单复制并粘贴到新的空文件中:

复制下面的所有代码并粘贴到 shipper.feature 中。

                Feature: Tests related to Shippers
                
                    Background: 
                        Given I have deployed the business network definition ..
                        And I have added the following participants
                        """
                        [
                        {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":0},
                        {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Paname"}, "accountBalance":0}
                        ]
                        """
                        And I have issued the participant org.acme.shipping.perishable.Grower#grower@email.com with the identity grower1
                        And I have issued the participant org.acme.shipping.perishable.Shipper#shipper@email.com with the identity shipper1
                        And I have added the following asset of type org.acme.shipping.perishable.Contract
                            | contractId | grower           | shipper               | importer              | arrivalDateTime  | unitPrice | minTemperature | maxTemperature | minPenaltyFactor | maxPenaltyFactor |
                            | CON_001    | grower@email.com | shipper@email.com     | supermarket@email.com | 10/26/2018 00:00 | 0.5       | 2              | 10             | 0.2              | 0.1              | 
                        And I have added the following asset of type org.acme.shipping.perishable.Shipment
                            | shipmentId | type    | status     | unitCount | contract |
                            | SHIP_001   | BANANAS | IN_TRANSIT | 5000      | CON_001  |
                        When I use the identity shipper1
                
                    Scenario: shipper1 can read Shipper assets
                        Then I should have the following participants
                        """
                        [
                        {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Paname"}, "accountBalance":0}
                        ]
                        """
                    
                    Scenario: shipper1 invokes the ShipmentPickup transaction
                        And I submit the following transaction of type org.acme.shipping.perishable.ShipmentPickup
                            | shipment |
                            | SHIP_001 |
                        Then I should have received the following event of type org.acme.shipping.perishable.ShipmentPickupEvent
                            | message                                  | shipment |
                            | Shipment picked up for shipment SHIP_001 | SHIP_001 |
                
                    Scenario: shipper1 invokes the ShipmentLoaded transaction
                        And I submit the following transaction of type org.acme.shipping.perishable.ShipmentLoaded
                            | shipment |
                            | SHIP_001 |
                        Then I should have received the following event of type org.acme.shipping.perishable.ShipmentLoadedEvent
                            | message                               | shipment |
                            | Shipment loaded for shipment SHIP_001 | SHIP_001 |
                
                    Scenario: grower1 cannot invoke the ShipmentPickup transaction
                        When I use the identity grower1
                        And I submit the following transaction of type org.acme.shipping.perishable.ShipmentPickup
                            | shipment |
                            | SHIP_001 |
                        Then I should get an error matching /Participant .* does not have 'CREATE' access to resource/
                
                    Scenario: grower1 cannot invoke the ShipmentPickup transaction
                        When I use the identity grower1
                        And I submit the following transaction of type org.acme.shipping.perishable.ShipmentLoaded
                            | shipment |
                            | SHIP_001 |
                        Then I should get an error matching /Participant .* does not have 'CREATE' access to resource/

现在保存此文件。

这就是 Cucumber 特性测试。

5e

构建网络并执行单元测试

现在是时候构建您的网络并执行单元测试了。转到命令行 (Ubuntu) 或打开终端窗口 (MacOS),输入此命令:

npm install && npm test

您在本教程系列的第 2 部分中已运行了这些命令。 您将看到大量输出,但在运行单元测试后,您会看到此信息:

                  ✔ And I have issued the participant org.acme.shipping.perishable.Shipper#shipper@email.com with the identity shipper1
                  ✔ And I have added the following asset of type org.acme.shipping.perishable.Contract
                      | contractId | grower           | shipper           | importer              | arrivalDateTime  | unitPrice | minTemperature | maxTemperature | minPenaltyFactor | maxPenaltyFactor |
                      | CON_001    | grower@email.com | shipper@email.com | supermarket@email.com | 10/26/2018 00:00 | 0.5       | 2              | 10             | 0.2              | 0.1              |
                  ✔ And I have added the following asset of type org.acme.shipping.perishable.Shipment
                      | shipmentId | type    | status     | unitCount | contract |
                      | SHIP_001   | BANANAS | IN_TRANSIT | 5000      | CON_001  |
                  ✔ When I use the identity shipper1
                  ✔ When I use the identity grower1
                  ✔ And I submit the following transaction of type org.acme.shipping.perishable.ShipmentLoaded
                      | shipment |
                      | SHIP_001 |
                  ✔ Then I should get an error matching /Participant .* does not have 'CREATE' access to resource/
                
                20 scenarios (20 passed)
                229 steps (229 passed)
                0m09.497s

上面的第 15-16 行显示,20 个方案(包含 229 个步骤)都已成功运行。您现在已准备好部署网络!

6

部署您对网络的更改

如果您一直在跟随教程操作,那么您已将 iot-perishable-network 部署到本地 Hyperledger Fabric 并实例化了该模型。不幸的是,您需要将 TemperatureSensor 和 GpsSensor 参与者添加到参与者注册表。 此任务可通过 CLI 完成,但我想展示一下如何拆卸并清理本地 Hyperledger Fabric,您需要时不时地这么做。

当您使用下面介绍的过程拆卸 Hyperledger Fabric 时,将会删除该网络和您目前生成的所有密码材料。现在,出于显而易见的原因,您绝不希望在生产中这么做,但目前处于开发环境中,所以您需要知道如何这么做。

导航到 $COMPOSER_ROOT 目录并输入 ./teardownFabric.sh 命令。您会看到类似这样的输出:

                $ ./teardownFabric.sh 
                Development only script for Hyperledger Fabric control
                Running 'teardownFabric.sh'
                .
                .                
                # Shut down the Docker containers for the system tests.
                cd "${DIR}"/composer
                ARCH=$ARCH docker-compose -f docker-compose.yml kill && docker-compose -f docker-compose.yml down
                Killing peer0.org1.example.com ... done
                Killing ca.org1.example.com    ... done
                Killing orderer.example.com    ... done
                Killing couchdb                ... done
                WARNING: The ARCH variable is not set.Defaulting to a blank string.
                Removing peer0.org1.example.com ... done
                Removing ca.org1.example.com    ... done
                Removing orderer.example.com    ... done
                Removing couchdb                ... done
                Removing network composer_default
                .
                .                
                # Your system is now clean

现在擦除 ~/.composer 目录,这将删除您目前生成的所有卡和密码材料。然后再次运行 createPeerAdmin.sh 脚本:

                $ rm -Rf ~/.composer
                $ ./createPeerAdminCard.sh 
                Development only script for Hyperledger Fabric control
                Running 'createPeerAdminCard.sh'
                FABRIC_VERSION is unset, assuming hlfv1
                FABRIC_START_TIMEOUT is unset, assuming 15 (seconds)
                
                Using composer-cli at v0.15.0
                Successfully created business network card to /tmp/PeerAdmin@hlfv1.card
                
                Command succeeded
                
                Successfully imported business network card: PeerAdmin@hlfv1
                
                Command succeeded
                
                Hyperledger Composer PeerAdmin card has been imported
                The following Business Network Cards are available: 
                
                
                ┌─────────────────┬───────────┬─────────┐
                │ CardName        │ UserId    │ Network │
                ├─────────────────┼───────────┼─────────┤
                │ PeerAdmin@hlfv1 │ PeerAdmin │         │
                └─────────────────┴───────────┴─────────┘
                
                Issue composer card list --name <CardName>  to get details of the card
                
                Command succeeded

接下来,运行 startFabric.sh 脚本来启动本地 Hyperledger Fabric,然后导航到 iot-perishable-network 目录,部署网络,并再次导入 admin@iot-perishable-network 卡:

                $ cd $COMPOSER_ROOT/fabric-tools
                $./startFabric.sh
                .
                .
                $ composer network deploy -a dist/iot-perishable-network.bna -A admin -S adminpw -c PeerAdmin@hlfv1 -f networkadmin.card
                Deploying business network from archive: dist/iot-perishable-network.bna
                Business network definition: 
                	Identifier: iot-perishable-network@0.1.12
                	Description: Shipping Perishable Goods Business Network
                
                ✔ Deploying business network definition.This may take a minute...
                Successfully created business network card to networkadmin.card
                
                Command succeeded
                
                $ composer card import --file networkadmin.card 
                Successfully imported business network card: admin@iot-perishable-network
                
                Command succeeded

最后,您需要执行 SetupDemo 事务来实例化该模型,否则将没有要为其发放 ID 卡的参与者!

                $ composer transaction submit --card admin@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.SetupDemo"}'
                Transaction Submitted.
                
                Command succeeded

从现在开始,如果您更改了网络(只要不涉及到修改区块链中的任何条目),只需更新它即可:

                $ composer network update -a dist/iot-perishable-network.bna --card admin@iot-perishable-network
                Deploying business network from archive: dist/iot-perishable-network.bna
                Business network definition: 
                    Identifier: iot-perishable-network@0.1.12
                    Description: Shipping Perishable Goods Business Network
                
                ✔ Updating business network definition.This may take a few seconds...
                Successfully created business network card to undefined
                
                Command succeeded
6a

发放 ID

现在是时候发放一些 ID 了。之前是在 Playground 中完成这项工作的。现在将使用 CLI 为表 2 中所示的参与者发放 ID 卡。

表 2. 要发放的参与者身份
参与者身份ID 卡文件名
Growergrower1grower1.card
Shippershipper1shipper1.card
Importerimporter1importer1.card
TemperatureSensorsensor_temp1sensor_temp1.card
GpsSensorsensor_gps1sensor_gps1.card

在 CLI 小节中,您调用了 SetupDemo 事务,该事务实例化了网络。请记住,不能为未包含在参与者注册表中的参与者发放 ID。

要为参与者发放 ID,首先要执行 composer identity issue 命令,指定卡文件,然后将卡文件导入到本地钱包中。在执行 composer identity issue 命令时,使用 admin@iot-perishable-network 卡执行身份验证。

用于 iot-perishable-network 的命令的一般格式为:

                composer identity issue --card admin@iot-perishable-network --file ID_CARD_FILE --newUserId IDENTITY --participantId 'resource:org.acme.shipping.perishable.PARTICIPANT#PARTICIPANT_ID'

其中:

ID_CARD_FILE— 是用于存储 ID 卡的文件名(参见表 2)。

IDENTITY— 是要发放的身份(参见表 2)。

PARTICIPANT_CLASS— 是参与者类(例如 Grower)。

PARTICIPANT_ID— 是在注册表中实例化的参与者的 ID(例如 farmer@email.com)。

ID 卡:Grower

                $ composer identity issue --card admin@iot-perishable-network --file grower1.card --newUserId grower1 --participantId 'resource:org.acme.shipping.perishable.Grower#farmer@email.com'
                
                Command succeeded

                $ composer card import --file grower1.card 
                Successfully imported business network card: grower1@iot-perishable-network
                
                Command succeeded

ID 卡:Shipper

                $ composer identity issue --card admin@iot-perishable-network --file shipper1.card --newUserId shipper1 --participantId 'resource:org.acme.shipping.perishable.Shipper#shipper@email.com'
                
                Command succeeded
                
                Ix:~/HyperledgerComposer/developerWorks/iot-perishable-network sperry$ composer card import --file shipper1.card 
                Successfully imported business network card: shipper1@iot-perishable-network
                
                Command succeeded

ID 卡:Importer

                $ composer identity issue --card admin@iot-perishable-network --file importer1.card --newUserId importer1 --participantId 'resource:org.acme.shipping.perishable.Importer#supermarket@email.com'
                
                Command succeeded
                
                Ix:~/HyperledgerComposer/developerWorks/iot-perishable-network sperry$ composer card import --file importer1.card 
                Successfully imported business network card: importer1@iot-perishable-network
                
                Command succeeded

ID 卡:TemperatureSensor

                $ composer identity issue --card admin@iot-perishable-network --file sensor_temp1.card --newUserId sensor_temp1 --participantId 'resource:org.acme.shipping.perishable.TemperatureSensor#SENSOR_TEMP001'
                
                Command succeeded

                $ composer card import --file sensor_temp1.card 
                Successfully imported business network card: sensor_temp1@iot-perishable-network
                
                Command succeeded

ID 卡:GpsSensor

                $ composer identity issue --card admin@iot-perishable-network --file sensor_gps1.card --newUserId sensor_gps1 --participantId 'resource:org.acme.shipping.perishable.GpsSensor#SENSOR_GPS001'
                
                Command succeeded
                
                Ix:~/HyperledgerComposer/developerWorks/iot-perishable-network sperry$ composer card import --file sensor_gps1.card 
                Successfully imported business network card: sensor_gps1@iot-perishable-network
                
                Command succeeded

为网络中的所有参与者发放 ID 卡,并将这些卡导入到本地 Hyperledger Fabric 钱包中后,您可以使用 CLI 从命令行模拟 IoT Perishable Goods 业务网络的工作流。

要查看发放的卡,可以运行 composer card list 命令:

                $ composer card list
                The following Business Network Cards are available: 
                
                
                ┌─────────────────────────────────────┬──────────────┬────────────────────────┐
                │ CardName                            │ UserId       │ Network                │
                ├─────────────────────────────────────┼──────────────┼────────────────────────┤
                │ admin@iot-perishable-network        │ admin        │ iot-perishable-network │
                ├─────────────────────────────────────┼──────────────┼────────────────────────┤
                │ importer1@iot-perishable-network    │ importer1    │ iot-perishable-network │
                ├─────────────────────────────────────┼──────────────┼────────────────────────┤
                │ grower1@iot-perishable-network      │ grower1      │ iot-perishable-network │
                ├─────────────────────────────────────┼──────────────┼────────────────────────┤
                │ sensor_temp1@iot-perishable-network │ sensor_temp1 │ iot-perishable-network │
                ├─────────────────────────────────────┼──────────────┼────────────────────────┤
                │ sensor_gps1@iot-perishable-network  │ sensor_gps1  │ iot-perishable-network │
                ├─────────────────────────────────────┼──────────────┼────────────────────────┤
                │ shipper1@iot-perishable-network     │ shipper1     │ iot-perishable-network │
                ├─────────────────────────────────────┼──────────────┼────────────────────────┤
                │ PeerAdmin@hlfv1                     │ PeerAdmin    │                        │
                └─────────────────────────────────────┴──────────────┴────────────────────────┘
                
                Issue composer card list --name <CardName>  to get details of the card
                
                Command succeeded
6b

提交事务

composer transaction submit 命令的一般格式为:

                composer transaction submit --card CARD_NAME -d 'DATA'

其中:

CARD_NAME— 是要使用的卡的名称(参见表 2)。

DATA 是包含事务数据的 JSON 对象。您可以复制来自 Playground 的任何事务的数据格式,删除换行符,并替换特定的数据值(您将在本节看到,这就是我提交事务数据的方式)。

提交事务

在本节中,您将通过 CLI 提交以下事务:

  • ShipmentPacked
  • ShipmentPickup
  • ShipmentLoaded
  • TemperatureReading
  • GpsReading
  • ShipmentReceived

每个事务都对应于将易腐货物从 Grower 转移到 Importer 的工作流中的一个步骤。

提交一个 ShipmentPacked 事务,使用 grower1 ID 卡向网络执行身份验证和授权:

                $ composer transaction submit --card grower1@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.ShipmentPacked", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
                Transaction Submitted.
                
                Command succeeded

此事务之所以成功了,是因为 Grower 参与者有权执行 ShipmentPacked 事务。为了增加点乐趣,可尝试使用 shipper1 ID 卡执行该事务:

                $ composer transaction submit --card shipper1@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.ShipmentPacked", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
                Error: Error trying invoke business network.Error: chaincode error (status: 500, message: Error: Participant 'org.acme.shipping.perishable.Shipper#shipper@email.com' does not have 'CREATE' access to resource 'org.acme.shipping.perishable.ShipmentPacked#8db39906c73c0821021489834f9e5fb37f29bab4253840cb756038b77da4dc00')
                Command failed

Shipper 参与者无法访问 ShipmentPacked 事务,所以尝试失败,这表明本教程前面编写的权限发挥了预期作用(当然,您在运行单元测试时就已经知道这一点,但看到它实际发挥作用总是让人感觉良好)。

将一批易腐货物(Perishable Goods)从 Grower 转移到 Importer 的工作流中的第二个步骤是,对打包的货物进行装车,这一步由 Shipper 执行:

                $ composer transaction submit --card shipper1@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.ShipmentPickup", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
                Transaction Submitted.
                
                Command succeeded

将货物装车后,Shipper 会将货物装到集装箱船上,并执行 ShipmentLoaded 事务将此事实记录到账本中:

                $ composer transaction submit --card shipper1@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.ShipmentLoaded", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
                Transaction Submitted.
                
                Command succeeded

在此过程中,TemperatureSensor 参与者正在获取货物集装箱内的读数并将它们记录到账本中。可以像下面这样模拟一些读数:

                $ composer transaction submit --card sensor_temp1@iot-perishable-network -d '{ "$class": "org.acme.shipping.perishable.TemperatureReading", "centigrade": 2, "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
                Transaction Submitted.
                
                Command succeeded
                $ composer transaction submit --card sensor_temp1@iot-perishable-network -d '{ "$class": "org.acme.shipping.perishable.TemperatureReading", "centigrade": 3, "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
                Transaction Submitted.
                
                Command succeeded
                
                Ix:~/HyperledgerComposer/developerWorks/iot-perishable-network sperry$ composer transaction submit --card sensor_temp1@iot-perishable-network -d '{ "$class": "org.acme.shipping.perishable.TemperatureReading", "centigrade": 11, "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
                Transaction Submitted.
                
                Command succeeded

这是 3 个 TemperatureReading 事务,最后一个为 11 摄氏度,比合约阈值高 1 度,所以在收到货物时,将会产生高温罚金。

现在提交一个 GPS 事务来表明集装箱船已到达目的地,即纽约/新泽西州港口:

                $ composer transaction submit --card sensor_gps1@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.GpsReading", "readingTime": "2200", "readingDate": "20171118", "latitude": "40.6840", "latitudeDir": "N", "longitude": "74.0062", "longitudeDir": "W", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}' 
                Transaction Submitted.
                
                Command succeeded

工作流中的最后一步是 Importer 接收货物,并调用 ShipmentReceived 事务将此事实记录到区块链中:

                $ composer transaction submit --card importer1@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.ShipmentReceived", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
                Transaction Submitted.
                
                Command succeeded

这似乎不是很令人兴奋,对吧?继续触发 REST 服务器:

                $ composer-rest-server
                ? Enter the name of the business network card to use: admin@iot-perishable-network
                ? Specify if you want namespaces in the generated REST API: always use namespaces
                ? Specify if you want to enable authentication for the REST API using Passport: No
                ? Specify if you want to enable event publication over WebSockets: No
                ? Specify if you want to enable TLS security for the REST API: No
                Discovered types from business network definition
                Generating schemas for all types in business network definition ...
                Generated schemas for all types in business network definition
                Adding schemas for all types to Loopback ...
                Added schemas for all types to Loopback
                Web server listening at: http://localhost:3000
                Browse your REST API at http://localhost:3000/explorer                
                To restart the REST server using the same options, issue the following command: 
                   composer-rest-server -c admin@iot-perishable-network -n always
                
                Discovering types from business network definition ...

现在,在浏览器中访问 localhost:3000,找到 Shipment 资产,并执行 /Get 方法。您将看到您提交的所有事务,它们都作为 Shipment 资产的一部分记录在区块链中。图 5 展示了应该在 /Get 请求的响应主体中看到的信息。

图 5. 显示了通过 CLI 输入的事务的 Shipment 资产
显示了通过 CLI 输入的事务的 Shipment 资产
显示了通过 CLI 输入的事务的 Shipment 资产

数据有很多,图 5 仅展示了其中的一小部分,但您可以继续自行触发 REST 服务器,滚动浏览 Shipment 资产并查看结果。

视频:综合演示

来自上一节的所有内容和 Playground(如果适用)都包含在这个视频中:

融会贯通
融会贯通

点击查看视频演示查看抄本

第 3 部分小结

本教程介绍了大量内容。首先,您安装、启动了 iot-perishable-network,然后将它部署到一个本地 Hyperledger Fabric 实例。

然后,您安装并运行了 REST 接口,可以用它来访问该网络。希望您有机会观看视频,我在其中进行了更详细的演示。如果未观看视频,请您一定要看一下。

然后,您了解了 Hyperledger Composer 中的访问控制的工作原理,包括 ACL 规则和它们在网络源代码中的位置。

然后,您使用了 Composer 命令行接口 (CLI) 向正在运行的网络发送 ping,执行了 SetupDemo 和其他事务,并在开发中执行更改后更新了该网络。

最后,最关键的是:您修改了 iot-perishable-network,以便将它转换为一个真实的区块链应用程序,还编写了 Cucumber 特性测试,为所有参与者发放了 ID,并通过 CLI 执行了每个事务。

此刻您应该已经掌握了开始使用 Hyperledger Composer 开发自己的区块链应用程序所需的所有知识。祝您好运!


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Cloud computing, 物联网
ArticleID=1059028
ArticleTitle=Hyperledger Composer 基础,第 3 部分: 在本地部署您的区块链网络,与之交互并扩展它
publish-date=04192018