跳转至

公开网络资产和交易

如果你已经走到这一步,恭喜你!您已经构建了区块链应用程序的核心和智能契约,该智能契约直接读取,更重要的是,操纵作为您的网络的记录系统的分类帐。但是,你还没有接近完成。可以想象,契约是一段敏感的代码,必须防止误用或篡改。

要生成可以安全发布给业务用户的健壮、安全的应用程序,您必须用一层或多层保护来包装智能合约,并将其设计为客户端可以通过适当的安全措施远程访问的服务。此外,希望共享分类帐和智能合同的各种利益相关者可能有独特和特定的业务逻辑需求,只有他们需要在合同之上实现,而其他人不需要。由于这个原因,运行一个智能合同的一个区块链应用程序可能最终向不同的利益相关者提供不同的视图和功能。

在本章中,您将首先学习如何使用我们的贸易应用程序作为指南和示例,从头开始构建一个完整的区块链应用程序。稍后,您将了解为您选择的场景设计该应用程序时的各种考虑因素,以及如何将该应用程序与现有系统和流程集成。

本章将涉及的主题如下:

  • 构建完整的应用程序
  • 将应用程序与现有系统和流程集成

构建完整的应用程序

在本节中,您将学习如何围绕核心智能合约构建一个完整的应用程序,该应用程序可以由已经联合在一起形成网络的业务实体随时使用。我们将从回顾 Hyperledger Fabric 交易管道开始,以提醒读者从用户(或客户)的角度来看,区块链应用程序做了什么(以及如何做)。通过使用代码示例,我们将向您展示如何围绕业务实体的需求构建、设计和组织网络,创建适当的配置,以及从开始到结束影响区块链交易的不同阶段。在这个过程的最后,读者将理解如何设计一个 Fabric 应用程序,并通过一个简单的 web 界面展示其功能。本章开始时我们需要拥有的唯一资产是契约,或链码,它是通过亲自动手的 Go 编程开发的(参见 第 4 章用 Golang 设计数据和事务模型)。

在本章的后端,我们将指导有经验的企业开发人员学习更高级的主题,如服务设计模式、可靠性和其他常见的工程问题。虽然这些问题适用于每个分布式应用程序,但我们将讨论基于区块链的应用程序的特殊需求和问题。

Hyperledger 结构应用程序的性质

在前面的章节中,我们看到了如何将 Hyperledger Fabric 视为一个分布式事务处理系统,它具有一个分阶段的操作管道,最终可能会导致由网络对等端维护的共享复制分类帐的状态发生变化。对于开发人员来说,区块链应用程序是一组进程的集合,用户可以通过这些进程向智能合约提交事务或从中读取状态。在幕后,开发人员必须将用户请求传递到事务管道的不同阶段,并提取结果以在流程结束时提供反馈。从本质上讲,应用程序开发人员的工作是围绕智能契约实现一层或多层包装器,而不管契约是手动实现的(参见 第 4 章使用 Golang 设计数据和交易模型)还是使用 Hyperledger Composer(参见 第 6 章业务网络)。

以智能契约(或资产实体模型)为核心开发的应用程序可以被视为具有一组视图或服务 API 的事务处理数据库应用程序。但是,开发人员必须记住,每个 Hyperledger Fabric 事务都是异步的,也就是说,在提交事务的同一通信会话中,事务的结果将不可用。这是因为,正如我们在前面的章节中所看到的,一个交易必须由网络中的对等体通过协商一致来集体批准。因此,达成共识可能需要无限长的时间,并且事务结果的通信被设计为发布/订阅机制。下图从开发人员的角度说明了区块链应用程序和事务管道:

图 5.1:区块链应用程序的创建和运行阶段

在下一节中,将更详细地描述此图中提到的操作,并将其映射到特定的结构机制。

申请和交易阶段

创建应用程序的第一步是实例化区块链或共享分类帐本身。按照 Fabric 的说法,区块链的一个实例被称为一个通道,因此区块链应用程序的第一步是创建一个通道,并用通道的 genesis 块引导网络订购服务。

下一步是对等网络的初始化,选择运行应用程序的所有对等节点必须加入到通道中,这个过程允许每个对等节点维护一个分类帐的副本,该副本被初始化为一个空的键值存储。加入该通道的每个对等体将拥有账户承诺特权,并且可以参与八卦协议,以便彼此同步账户状态。

在创建对等网络之后,在该网络上安装智能合约。将选择加入其之前的通道的对等方的子集来运行智能协定;换句话说,他们将拥有背书特权。契约代码将被部署到这些对等体,并为后续操作而构建。如你所知,在这一点上,合同在 Fabric 术语中被称为 chaincode,这个术语将用于本章的其余部分。

一旦链码被安装在认可对等体上,它将根据嵌入其中的逻辑进行初始化(例如,参见 第四章用 Golang 设计数据和交易模型)。

此时,除非前面的一个或多个步骤出错,否则应用程序已经启动并运行。现在,可以将事务发送到链码,以便在应用程序的生命周期内更新分类帐的状态(调用)或读取分类帐的状态(查询)。

随着时间的推移,应用程序可能会发生变化或发展,需要执行图 5.1中未包含的特殊操作:区块链应用程序的创建和运行阶段。那些将在 第九章区块链网络中的生活中描述。

在标题为构建应用程序及以后的章节中,我们将展示如何使用合适的代码和指令,围绕 第 4 章使用 Golang 设计数据和交易模型中开发的链代码来构建贸易应用程序。

应用模型和架构

编写 Fabric 应用程序的过程从链码开始,但最终开发人员必须做出明智的决定,即最终用户或软件代理必须如何与该链码交互。链码的资产以及运行该链码的区块链网络的操作应该如何向用户公开是一个应该小心处理的问题。如果这些功能不受限制地暴露,尤其是涉及区块链引导和配置的功能,可能会造成重大损害。链码本身的正确操作不仅依赖于它的内部逻辑,还依赖于建立在它之上的适当的访问控制。正如我们在上一节中看到的,设置应用程序并准备使用是一个复杂的过程。此外,分类帐更新事务的异步特性要求在链码和用户之间有一个仲裁层。为了让用户关注影响应用程序的事务,而不是网络模块的细节,所有这些复杂性应该尽可能隐藏起来。正是由于这个原因,三层架构已经发展成为 Fabric 应用程序的标准,如下图所示:

图 5.2 Hyperledger Fabric 应用程序的典型三层架构

最底层是直接在共享账本上操作的智能合约,它可以使用一个或多个链码单元来编写。这些链代码运行在网络对等体上,公开用于调用和查询的服务 API,发布事务结果的事件通知,以及通道上发生的配置更改。

中间层的功能是协调区块链应用程序的各个阶段(参见图 5.1:区块链应用程序的创建和运行阶段)。Hyperledger Fabric 提供了一个 SDK(目前在Node.js和 Java 中可用),用于执行频道创建和加入、用户注册和登记以及链码操作等功能。此外,SDK 提供了订阅来自网络的事务和配置相关事件的机制。根据应用程序的需要,为了方便起见,可以维护一个离线数据库,或者作为分类帐状态的缓存。

最顶层是面向用户的应用程序,它导出主要由特定于应用程序的功能组成的服务 API,尽管诸如通道和链码操作之类的管理操作也可能对系统管理员公开。通常情况下,还应该提供一个易于使用的用户界面,尽管如果用户是软件代理,一个定义良好的 API 可能就足够了。我们将这一层简称为应用程序,因为这是最终用户(或代理)将看到的内容。此外,考虑到任何区块链应用和网络都是不同参与者的集合,这一层通常会由针对不同参与者定制的多个应用堆栈组成。

这个建筑不应该一成不变;它纯粹是作为开发人员的指南。根据应用程序的复杂性,层的数量和垂直领域(或不同的应用程序)可能会有所不同。对于一个具有少量功能的非常简单的应用程序,开发人员甚至可以选择将中间件和应用层压缩成一层。然而,更普遍的是,这种分离使得不同的能力集合能够被暴露给不同的网络参与者。例如,在我们的贸易用例中,监管者和出口商会以不同的方式看待区块链,并有不同的需求,因此,为他们构建不同的服务集将是有用的,而不是将所有功能强行装入一个具有统一接口的单一应用程序中。然而,这两种应用程序都应该隐藏网络操作的复杂性,例如通道的创建和加入,或者特权操作,例如以类似的方式将链代码安装到对等体上,因此它们将受益于公共中间件层。

用户直接交互的应用层的设计方式呈现出许多选择和复杂性,我们将在本章的后半部分深入探讨。不过,首先,我们将描述如何实现 Fabric 应用程序的核心部分,重点放在基本元素上。出于指导性的目的,我们的最顶层将是一个简单的 web 服务器,公开一个 RESTful 的服务 API。

该架构背后的思想和驱动它的原则独立于底层的区块链技术。要在不同于 Hyperledger Fabric 的区块链平台上实现相同的应用程序,只需重新实现智能合约和中间件的某些部分。应用程序的其余部分可以保持不变,最终用户不会注意到任何差异。

构建应用程序

既然我们不仅理解了设计分层 Fabric 应用程序的方法,还理解了其背后的哲学,那么我们就可以深入实现了。在前两章中,我们讨论了如何实现和测试最底层,即链码。因此,我们可以假设读者现在已经准备好添加中间件和应用程序层,这也是我们将在下面几节中演示的。

测试中间件和应用程序代码的先决条件是运行的网络。在进入下一部分之前,请确保我们在第三章 、设置业务场景中的设置开发环境部分中配置和启动的示例四组织网络仍在运行。

中间件——包装和驱动链代码

下图将应用和事务阶段部分中讨论的事务阶段以及图 5.1:区块链应用的创建和操作阶段与结构术语和使用结构术语对应起来:

图 5.3:区块链应用程序的创建和运行阶段

Fabric 对等体、订购者和 ca(或 MSP)使用 gRPC(https://grpc.io/)以及对等体产生的运行链代码的进程(该进程实际上是一个 Docker 容器)进行通信。该流程导出一个服务端点,该服务端点实现了用于通道和链码操作的 JSON RPC 2.0 规范(【http://www.jsonrpc.org/specification】)。我们可以编写一个包装器应用程序,使用服务规范直接与 chaincode 通信,但是我们还必须编写逻辑来解析和解释有效负载。由于 Fabric 平台及其规范可能会在未来发生变化,这不一定是编写应用程序的最佳和最易维护的方式,尤其是对于生产目的。幸运的是,Hyperledger Fabric 提供了运行链码操作的方法,同时以两种不同的方式隐藏了接口规范和通信协议的细节:

  • 命令行界面 ( CLI ): Fabric 提供可以从终端运行的命令,以执行图 5.3:区块链应用程序的创建和运行阶段中指示的各种操作。运行这些命令的工具是peer,它是在下载 Fabric 源代码并构建它时生成的(使用make,或者仅仅是make peer)。这个命令可以使用不同的开关来执行不同的通道和链码操作,您将在本节中看到一些示例。
  • 软件开发工具包 ( SDK ): Hyperledger 提供了一个工具包和一组库,用于轻松开发应用程序,以多种语言包装通道和链码操作,如 Node.js、Java、Go 和 Python。这些 SDK 还提供与 MSP 或 Fabric CA 实例交互的功能。

尽管 CLI 工具可用于测试和演示目的,但它们不足以用于应用程序开发。除了前面提到的功能之外,SDK 库还提供订阅来自网络的事件的能力,传递驱动应用逻辑所需的状态变化信息。我们将使用 Node.js SDK 演示如何构建我们的中间件和更高层的应用程序。读者可以使用其他 SDK 中的一种,用自己选择的其他语言构建等效的应用程序。

工具和依赖项的安装

我们将展示如何作为中间件的一部分来构建的功能可以在代码仓库的中间件文件夹中找到。

创建和运行中间件的先决条件

读者应该熟悉 Node.js/JavaScript 编程(尤其是Promise模式)以及 Node.js 和npm工具的用法:

  1. 安装 node . js(https://nodejs.org/en/download/)和npm(https://www.npmjs.com/get-npm)。
  2. 安装fabric-clientfabric-ca-client npm库:
    • 您可以从npm注册表安装这些包,或者通过运行npm install <package-name>手动安装,或者通过在您的package.json文件中设置名称和版本来安装。例如,中间件文件夹中的package.json在依赖项部分包含以下条目:
      • fabric-ca-client : ^1.1.0
      • fabric-client : ^1.1.0
  3. 这指示npm安装这两个包的 1.1.0 版本:
    • 或者,您可以克隆 Fabric SDK 节点(https://github.com/hyperledger/fabric-sdk-node/)源代码库,并在本地导入两个库,如下所示:
      • fabric-clientfabric-ca-client文件夹中运行npm安装
      • 将这些包作为依赖项安装,或者通过指定中间件/ package.json中前面文件夹的路径手动安装,或者使用npm link 命令将符号链接添加到中间件/ node_modules中的包

在下面的小节中,我们将使用fabric-client库来执行涉及对等体和订购者的通道和链码操作,使用fabric-ca-client库来执行涉及 CA(或 MSP)的用户注册和登记操作。

依赖项的安装

运行中间件文件夹中的npm install,安装package.json中指定的包(库)及其依赖项。你应该看到下载到node_modules文件夹的包。

安装依赖项和为常规操作配置中间件的一种更干净的方式是使用Makefile自动构建。你可以简单地运行middleware文件夹中的make;参见第 8 章区块链网络中的敏捷性,了解关于设置和构建您的开发和测试环境的更多详细信息。

创建和运行中间件

我们现在将编写函数来执行和编排图 5.3所示的阶段:区块链应用程序的创建和运行阶段。但是首先,我们将给出各种配置参数的概述,这些参数是应用程序按预期工作所必须设置的

网络结构

编写中间件的第一步是收集所有必要的配置信息,以识别和连接到我们在上一节中创建和启动的网络的各种元素。用 JSON 格式表达这样的配置非常有用,尤其是在用 JavaScript 编写代码时。在我们的示例代码中,config.json文件就是为这个目的服务的。该文件包含网络的描述,其属性包含在贸易网络对象中。该对象的每个属性都描述了作为网络一部分的每个唯一组织的配置,只有一个名为 orderer 的属性除外,它只是指 orderer 节点。(注意:这对于仅包含一个订购者节点的简单网络来说已经足够了。)让我们以Exporterorg属性为例,检查每个组织的描述中必须指定什么:

"exporterorg": {
  "name": "peerExporterOrg",
  "mspid": "ExporterOrgMSP",
  "ca": {
    "url": "https://localhost:7054",
    "name": "ca-exporterorg"
  },
  "peer1": {
    "requests": "grpcs://localhost:7051",
    "events": "grpcs://localhost:7053",
    "server-hostname": "peer0.exporterorg.trade.com",
    "tls_cacerts": "../network/crypto-config/peerOrganizations/exporterorg.trade.com/peers/peer0.exporterorg.trade.com/msp/tlscacerts/tlsca.exporterorg.trade.com-cert.pem"
  }
},

mspid值必须与network/configtx.yaml中指定的值相匹配,这样我们的中间件才能与为网络创建的通道工件和加密材料兼容。CA 的名称和端口信息必须与在network/docker-compose-e2e.yaml中指定的相匹配。因为我们在每个组织中只有一个对等体,所以为了方便起见,我们将其命名为对等体,尽管可以很容易地为多对等体组织设置定义不同的模式。注意,对等点为对等请求和事件订阅导出服务,端口与network/base/docker-compose-base.yaml中公开的端口相匹配。server-hostname还必须与configtx.yaml和 docker-compose 配置中指定的相匹配。由于我们的网络元素使用 TLS 进行连接,因此也必须在这里指定对等体的 TLS 证书的路径。

最后,如果您将前面的模式片段与其他组织的配置进行比较,您会注意到列出的端口与 docker-compose 配置中公开的端口完全匹配。例如,出口商、进口商、承运商和监管机构中的对等方分别监听端口70518051905110051上的请求。URL 中的主机名只是指本地主机,因为所有网络元素的容器都在本地主机上运行。

背书政策

下一步是为我们的链码制定一个认可政策,该链码将在实例化期间提交给分类帐。这种背书策略规定了有多少属于什么角色和组织的对等体需要背书分类帐承诺事务(或调用)。在示例代码中,constants.js中列出了不同的背书策略,其中包含了我们中间件使用的各种设置和关键字。我们将雇用的是ALL_FOUR_ORG_MEMBERS:

var FOUR_ORG_MEMBERS_AND_ADMIN = [
  { role: { name: 'member', mspId: 'ExporterOrgMSP' } },
  { role: { name: 'member', mspId: 'ImporterOrgMSP' } },
  { role: { name: 'member', mspId: 'CarrierOrgMSP' } },
  { role: { name: 'member', mspId: 'RegulatorOrgMSP' } },
  { role: { name: 'admin', mspId: 'TradeOrdererMSP' } }
];
var ALL_FOUR_ORG_MEMBERS = {
  identities: FOUR_ORG_MEMBERS_AND_ADMIN,
  policy: {
    '4-of': [{ 'signed-by': 0 }, { 'signed-by': 1 }, { 'signed-by': 2 }, { 'signed-by': 3 }]
  }
};

主体列表在策略的 identities 属性中指定,指的是四个对等组织的成员(或普通)用户,以及订购者组织的管理员用户。这里的策略属性声明需要来自四个对等组织中每一个的成员的认可;总共需要四个签名。

变量TRANSACTION_ENDORSEMENT_POLICY默认设置为constants.js中的ALL_FOUR_ORG_MEMBERS,在本节后面将用于配置渠道背书策略。

用户记录

对于渠道世界状态以及各个组织的用户密钥和证书,我们将使用基于文件的存储,如clientUtils.js中所述:

var Constants = require('./constants.js');
var tempdir = Constants.tempdir;
module.exports.KVS = path.join(tempdir, 'hfc-test-kvs');
module.exports.storePathForOrg = function(org) {
  return module.exports.KVS + '_' + org;
};

constants.js中,tempdir初始化如下:

var tempdir = "../network/client-certs";

或者,您也可以使用os.tmpdir()功能将存储位置设置在操作系统指定的临时文件夹中;您只需要在那里创建一个子文件夹(比如说<folder-name>)。)在典型的 Linux 系统上,这个存储位置将默认为/tmp/<folder-name>/,并且将在那里为每个组织创建文件夹。当我们运行各种操作时,我们将看到这些文件夹被生成,文件被添加到其中。

客户注册和登记

尽管可以使用 cryptogen 工具为组织用户静态创建加密材料,但我们必须在中间件中构建动态创建用户身份和凭证的功能,并使这些用户能够登录网络以提交交易和查询分类帐状态。这些操作需要具有特权访问权限的用户(或管理员)的中介,这些用户必须在启动fabric-ca-server时创建。默认情况下,管理用户的 ID 为 admin,密码为adminpw,这是我们在本节练习中要用到的。我们创建和启动的网络使用这些默认设置,读者可以在fabric-ca-server中修改它们,并在network/docker-compose-e2e.yaml中启动命令(以下来自exporter-ca部分):

fabric-ca-server start --ca.certfile /etc/hyperledger/fabric-ca-server-config/ca.exporterorg.trade.com-cert.pem --ca.keyfile /etc/hyperledger/fabric-ca-server-config/cc58284b6af2c33812cfaef9e40b8c911dbbefb83ca2e7564e8fbf5e7039c22e_sk -b admin:adminpw -d

通过管理员创建用户的步骤如下:

  1. 从本地存储加载管理用户凭据
  2. 如果凭证不存在,请管理员注册或登录到结构 CA 服务器,并获取他们的凭证(私钥和注册证书)
  3. 让管理用户向结构 CA 服务器注册另一个具有给定 ID 的用户,指定角色和从属关系
  4. 使用注册时返回的密码,注册新用户并获取该用户的凭据
  5. 将凭据保存到本地存储

clientUtils.js中可以找到示例代码,下面的代码片段主要来自getUserMember函数,它接受管理员凭证、用户必须注册的组织的名称以及要注册的用户的名称/ID。客户端的句柄(fabric-client的实例,或者客户端对象(https://fabric-sdk-node.github.io/Client.html)也必须传递给函数:

var cryptoSuite = client.getCryptoSuite();
if (!cryptoSuite) {
  cryptoSuite = Client.newCryptoSuite();
  if (userOrg) {
    cryptoSuite.setCryptoKeyStore(Client.newCryptoKeyStore({path: module.exports.storePathForOrg(ORGS[userOrg].name)}));
    client.setCryptoSuite(cryptoSuite);
  }
}

上述代码将客户端句柄与按组织分区的本地存储相关联,以存储动态创建的管理员和其他用户的凭据:

var member = new User(adminUser);
member.setCryptoSuite(cryptoSuite);

该代码确保管理员用户句柄将与我们的商店相关联:

var copService = require('fabric-ca-client/lib/FabricCAClientImpl.js');
var caUrl = ORGS[userOrg].ca.url;
var cop = new copService(caUrl, tlsOptions, ORGS[userOrg].ca.name, cryptoSuite);
return cop.enroll({
  enrollmentID: adminUser,
  enrollmentSecret: adminPassword
}).then((enrollment) => {
  console.log('Successfully enrolled admin user');
  return member.setEnrollment(enrollment.key, enrollment.certificate, ORGS[userOrg].mspid);
})

这里,我们使用fabric-ca-client库连接到与给定组织相关联的fabric-ca-server实例(其 URL 可以从我们的config.json获得);例如,出口商组织的caUrl将是https://localhost:7054。注册功能允许管理员登录 MSP,并获取注册密钥和证书。

现在我们有了一个成员对象形式的管理员用户句柄,我们可以用它来注册一个具有用户 ID 的新用户,用户 ID 由用户名表示,如下所示:

var enrollUser = new User(username);
return cop.register({
  enrollmentID: username,
  role: 'client',
  affiliation: 'org1.department1'
}, member).then((userSecret) => {
  userPassword = userSecret;
  return cop.enroll({
    enrollmentID: username,
    enrollmentSecret: userSecret
  });
}).then((enrollment) => {
  return enrollUser.setEnrollment(enrollment.key, enrollment.certificate, ORGS[userOrg].mspid);
}).then(() => {
  return client.setUserContext(enrollUser, false);
}).then(() => {
  return client.saveUserToStateStore();
})

在注册过程中,我们可以指定用户的角色,在前面的代码中是 client,允许用户名向 chaincode 提交调用和查询。此处指定的从属关系是在结构 CA 服务器的配置中指定的组织内的一个子部分(http://hyperledger-Fabric-CA . readthedocs . io/en/latest/server config . html)(更新此配置留给读者作为练习;这里,我们将使用默认的隶属关系)。使用返回的密码,用户名现在被注册到服务器,并且其密钥和注册证书被保存。

client.setUserContext的调用将这个用户与客户机句柄相关联,并且client.saveUserToStateStore将用户的凭证保存到文件系统上的本地存储中。

获得管理员用户句柄的类似函数有getAdmingetMember,也在clientUtils.js中定义。前者检索一个管理员用户,其凭证是使用cryptogen创建的,而后者动态创建一个新的admin成员。

创建频道

为了创建我们的贸易渠道,我们首先需要使用config.json中的配置实例化一个fabric-client实例和一个订购者句柄(参见create-channel.js中的createChannel函数):

var client = new Client();
var orderer = client.newOrderer(
  ORGS.orderer.url,
  {
    'pem': caroots,
    'ssl-target-name-override': ORGS.orderer['server-hostname']
  }
);

我们使用基于文件的键值存储来保存分类帐世界状态,如下所示(读者可以尝试其他类型的存储,比如使用CouchDBKeyValueStore.jsCouchDB):

utils.setConfigSetting('key-value-store', 'fabric-client/lib/impl/FileKeyValueStore.js');

接下来,我们必须为订购者注册一个管理员用户(使用上一节中讨论的机制)。成功注册后,必须提取我们使用configtxgen工具(参见network/channel-artifacts/channel.tx)创建的通道配置。该配置文件的路径在constants.js中设置:

let envelope_bytes = fs.readFileSync(path.join(__dirname, Constants.networkLocation, Constants.channelConfig));
config = client.extractChannelConfig(envelope_bytes);

我们现在需要为四个组织中的每一个注册一个管理员用户。这四位管理员中的每一位以及订购者管理员都必须签署渠道配置,签名收集如下:

ClientUtils.getSubmitter(client, true /*get the org admin*/, org)
.then((admin) => {
  var signature = client.signChannelConfig(config);
  signatures.push(signature);
});

getSubmitter函数在clientUtils.js中定义,它是一种将给定组织的成员(普通成员或管理员)与客户机对象相关联的间接方式。换句话说,它将客户端对象与用户的签名身份(凭证和 MSP 标识)相关联。下面,getSubmitter使用了函数getAdmingetUserMembergetMember,我们在前面的部分已经描述过了。

getOrderAdminSubmitter is analogous to getSubmitter and returns a handle to an admin user of the orderer’s organization.

最后,我们准备构建一个渠道创建请求,并将其提交给订购者:

let tx_id = client.newTransactionID();
var request = {
  config: config,
  signatures : signatures,
  name : channel_name,
  orderer : orderer,
  txId : tx_id
};
return client.createChannel(request);

通道的实际创建可能需要几秒钟,因此应用程序逻辑应该等待一段时间,然后返回成功的结果。在clientUtils.jstradechannel中设置channel_name参数,这是我们启动网络时设置的参数(参见network/trade.sh)。

通道创建步骤包括用我们在本章前面使用configtxgen创建的 genesis 块初始化区块链。genesis 块只是附加到链上的第一个配置块。一个配置块包括一个通道的规范和作为其一部分的组织,等等;这种块不包含链码事务。我们将在 第九章区块链网络中的生活中再次处理配置区块,届时我们将讨论如何增强网络。

现在,我们需要做的就是调用createChannel('tradechannel')函数并等待结果来创建一个通道。这是我们的测试代码createTradeApp.js的第一步,它执行图 5.3:区块链应用程序的创建和运行阶段中所示的基本操作序列:

var Constants = require('./constants.js');
var createChannel = require('./create-channel.js');
createChannel.createChannel(Constants.CHANNEL_NAME).then(() => { ...... })

我们用来将不同的签名标识与一个公共客户端对象相关联,然后在单个过程中对通道配置进行签名的代码纯粹是为了演示的目的。在实际生产应用程序中,属于不同组织的不同用户的签名身份是私有的,必须加以保护;因此,不存在将它们集中在一个公共位置的问题。相反,通道配置必须由不同组织的管理员独立签名,并使用某种带外机制传递,以累积签名(并验证它们。)当配置被更新时,必须采用类似的机制(参见第九章区块链网络中的生活)独立的、分散的过程也必须被用于信道加入和链码安装,尽管为了方便起见,我们使用集中的过程来演示基本的机制。

*# 加入频道

现在已经创建了tradechannel,我们的四个对等体(每个组织一个)必须加入到通道中,这一步初始化每个节点上的分类帐,并准备对等体在其上运行链代码和事务。为此,我们需要重用在上一步中创建的客户机句柄,或者使用类似的操作序列实例化一个。此外,我们必须实例化一个通道句柄,注册订购者,并获得 genesis 块(在创建步骤中使用通道配置隐式发送给订购者),如下面来自join-channel.jsjoinChannel函数的代码片段所示:

var channel = client.newChannel(channel_name);
channel.addOrderer(
  client.newOrderer(
    ORGS.orderer.url,
    {
      'pem': caroots,
      'ssl-target-name-override': ORGS.orderer['server-hostname']
    }
  )
);
tx_id = client.newTransactionID();
let request = { txId : tx_id };
return channel.getGenesisBlock(request);

在前面的getGenesisBlock调用中,事务 ID 参数是可选的。现在,对于每个组织,我们必须获得一个管理员用户的句柄,该用户随后将为属于该组织的对等方提交一个通道加入请求:

return ClientUtils.getSubmitter(client, true /* get peer org admin */, org);
for (let key in ORGS[org])
  if (ORGS[org].hasOwnProperty(key)) {
    if (key.indexOf('peer') === 0) {
      data = fs.readFileSync(path.join(__dirname, ORGS[org][key]['tls_cacerts']));
      targets.push(
        client.newPeer(
          ORGS[org][key].requests,
          {
            pem: Buffer.from(data).toString(),
            'ssl-target-name-override': ORGS[org][key]['server-hostname']
          }
        )
      );
    }
  }
}
tx_id = client.newTransactionID();
let request = {
  targets : targets,
  block : genesis_block,
  txId : tx_id
};
let sendPromise = channel.joinChannel(request, 40000);

与在通道创建过程中一样,getSubmitter函数在提交通道加入请求之前将特定组织的管理员的签名身份与客户端对象相关联。这个请求包含 genesis 块以及该组织中每个对等体的配置(从包含config.json中每个组织内的peer前缀的属性中加载,如上面的代码所示)。)

上面显示了 40 秒的等待时间,因为这个过程可能需要一段时间才能完成。这个加入过程需要由每个组织中的管理员独立执行;因此,函数joinChannel(<org-name>)在主函数processJoinChannel中被依次调用了 4 次,在createTradeApp.js的测试脚本中调用如下:

var joinChannel = require('./join-channel.js');
joinChannel.processJoinChannel();

在典型的生产网络中,每个组织将独立运行加入过程,但只针对其对等组织。我们在存储库中使用的编排代码(join-channel.js中的processJoinChannel)是为了方便和测试。

链码的安装

安装 chaincode 会导致将源代码复制到我们选择作为签署者的对等体,并且每个安装都与一个用户定义的版本相关联。主要功能installChaincodeinstall-chaincode.js中实现。该函数依次为 4 个组织调用installChaincodeInOrgPeers函数;后一个函数在给定组织的对等体上安装 chaincode。与渠道加入的情况一样,我们为给定的组织创建客户端和渠道句柄,为该组织注册一个管理员用户,并将该用户与客户端句柄相关联。下一步是创建一份安装建议书并提交给订购方,如下所示:

var request = {
  targets: targets,
  chaincodePath: chaincode_path,
  chaincodeId: Constants.CHAINCODE_ID,
  chaincodeVersion: chaincode_version
};
client.installChaincode(request);

目标指的是组织中签署对等方的配置,从config.json开始加载。chaincodeIdchaincodeVersion可以由调用者设置(默认在constants.js中分别设置为tradeccv0),但是chaincodePath必须指向包含源代码的位置。在我们的场景中,这个位置指的是本地文件系统上的一个路径:github.com/trade_workflow

在 SDK 内部,安装请求将 chaincode 的源代码打包成规定的格式,称为ChaincodeDeploymentSpec(CDS)(https://github . com/hyperledger/fabric/blob/release-1.1/protos/peer/chain code . proto)。然后这个包被签名(由与客户端对象相关联的组织管理员)以创建一个SignedChaincodeDeploymentSpec(https://github . com/hyperledger/fabric/blob/release-1.1/protos/peer/signed _ cc _ dep _ spec . proto),然后被发送到生命周期系统链码 (LSCC)以进行安装。

上面的过程描述了简单的情况,其中签名的 CDS 的每个实例只有与发出安装请求的客户端相关联的身份的签名。一个更复杂的场景由 Fabric 支持,其中 CDS 可以被传递(带外)到不同的客户端(各种组织的)并且在接收到安装请求之前由每个客户端签名。鼓励读者使用可用的 API 函数和 Fabric 数据结构来尝试这种变化(http://hyperledger-Fabric . readthedocs . io/en/latest/chain code 4 Noah . html)。

安装请求的成功通过检查来自每个目标对等方的建议响应来确定,如下所示:

if (proposalResponses && proposalResponses[i].response && proposalResponses[i].response.status === 200) {
  one_good = true;
  logger.info('install proposal was good');
}

最后,为了协调整个网络上的安装,我们调用在install-chaincode.js中定义的installChaincode函数。为了让fabric-client知道从哪里加载chaincode源代码,我们临时将流程中的GOPATH设置为指向我们项目中的正确位置,即chaincode文件夹:

这只对用 Go 写的chaincode有效

process.env.GOPATH = path.join(__dirname,Constants.chaincodeLocation);

为了成功安装,chaincode文件夹必须包含一个名为src的子文件夹,在这个子文件夹中,安装建议中发送的chaincode路径必须指向实际代码。正如你所看到的,这最终解决了我们代码库中的chaincode/src/github.com/trade_workflow,它确实包含了我们在 第 4 章用 Golang 设计数据和事务模型中开发的源代码。

在我们的createTradeApp.js脚本中,我们现在可以简单地调用:

var installCC = require('./install-chaincode.js');
installCC.installChaincode(Constants.CHAINCODE_PATH, Constants.CHAINCODE_VERSION);

在一个典型的生产网络中,每个组织将独立运行安装过程(在installChaincodeInOrgPeers函数中定义),但只针对其认可的对等方。我们在库中使用的编排代码(install-chaincode.js中的installChaincode)是为了方便和测试。

链码的实例化

既然网络中的认可对等体有了链码,我们必须通过我们的渠道实例化该链码,以确保分类帐的所有副本都用正确的数据集(或键值对)初始化。这是我们的智能合约设置的最后一步,然后我们才能打开它进行常规操作。实例化是一种事务,它调用 LSCC 来初始化通道上的链码,从而将两者绑定在一起,并将前者的状态与后者隔离。

这个操作应该由任何被授权初始化链码的组织集中触发(在我们的示例代码中,我们使用进口商组织的管理员)。同样,这遵循一个简单的场景(在前面的安装一节中描述过),其中链码包由单个组织管理员签名。

默认的通道实例化策略需要任何通道 MSP 管理员来触发操作,但是如果需要,可以在签名的 CDS 结构中设置不同的策略。)此外,触发实例化操作的实体也必须被配置为通道上的写入者。我们使用configtxgen创建通道配置的过程隐式地向 4 个组织的管理员授予了写权限。(通道配置策略的详细讨论超出了本书的范围。)

实现链码实例化的主要功能在instantiate-chaincode.js中实现为instantiateOrUpgradeChaincode。这个函数既可以用来实例化一个新部署的 chaincode,也可以用来更新一个已经在通道上运行的 chain code(参见 第九章区块链网络中的生活)和前面的阶段一样,我们必须创建客户端和通道句柄,并将通道句柄与客户端相关联。此外,必须将网络中的所有认可对等体添加到通道中,然后必须用与通道相关联的 MSP(来自四个组织中的每一个)初始化通道对象:

channel.initialize();

这建立了验证证书和签名的通道,例如,从对等体接收的背书。接下来,我们构建一个实例化的提议,并将其提交给通道上的所有支持对等体(摘自buildChaincodeProposal函数的片段):

var tx_id = client.newTransactionID();
var request = {
  chaincodePath: chaincode_path,
  chaincodeId: Constants.CHAINCODE_ID,
  chaincodeVersion: version,
  fcn: funcName,
  args: argList,
  txId: tx_id,
  'endorsement-policy': Constants.TRANSACTION_ENDORSEMENT_POLICY
};
channel.sendInstantiateProposal(request, 300000);

链码的路径、ID 和版本必须与安装方案中提供的相匹配。此外,我们必须提供将被发送到链代码并被执行的函数名和参数列表。(在我们的 chaincode 中,这将执行Init函数。)还要注意,该提案包含了我们之前设置的认可政策(Constants.TRANSACTION_ENDORSEMENT_POLICY),它要求四个组织中的每一个组织都有一名成员来认可一个链码调用。订购方返回的建议书回复(每个认可方一份)必须以与安装阶段相同的方式进行验证。使用前面的channel.sendInstantiateProposal调用的结果,我们现在必须构建一个实例化事务请求,并将其提交给订购者:

var proposalResponses = results[0];
var proposal = results[1];
var request = {
  proposalResponses: proposalResponses,
  proposal: proposal
};
channel.sendTransaction(request);

channel.sendTransaction的成功响应将允许我们的中间件在实例化成功提交的基础上继续进行。然而,这并不表示实例化将成功地以对共享分类帐的承诺而结束;为此,我们的代码必须订阅事件,我们将在本节的后面看到如何做到这一点。

我们在createTradeApp.js中的脚本如下触发链码实例化:

var instantiateCC = require('./instantiate-chaincode.js');
instantiateCC.instantiateOrUpgradeChaincode(
  Constants.IMPORTER_ORG,
  Constants.CHAINCODE_PATH,
  Constants.CHAINCODE_VERSION,
  'init',
  ['LumberInc', 'LumberBank', '100000', 'WoodenToys', 'ToyBank', '200000', 'UniversalFrieght', 'ForestryDepartment'],
  false
);

最后一个参数设置为false,表示必须执行实例化,而不是升级。第一个参数(Constants.IMPORTER_ORG)表示实例化请求必须由进口商组织的成员(在此上下文中为管理员)提交。

如果实例化成功,链码将被构建在 Docker 容器中,每个容器对应于一个认可对等体,并被部署来代表它们的对等体接收请求。如果您运行docker ps -a,除了在启动网络时创建的内容之外,您还应该看到类似这样的内容:

CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES
b5fb71241f6d     dev-peer0.regulatororg.trade.com-tradecc-v0-cbbb0581fb2b9f86d1fbd159e90f7448b256d2f7cc0e8ee68f90813b59d81bf5    "chaincode -peer.add..."    About a minute ago    Up About a minute        dev-peer0.regulatororg.trade.com-tradecc-v0
077304fc60d8    dev-peer0.importerorg.trade.com-tradecc-v0-49020d3db2f1c0e3c00cf16d623eb1dddf7b649fee2e305c4d2c3eb5603a2a9f    "chaincode -peer.add..."    About a minute ago    Up About a minute        dev-peer0.importerorg.trade.com-tradecc-v0
8793002062d7    dev-peer0.carrierorg.trade.com-tradecc-v0-ec83c1904f90a76404e9218742a0fc3985f74e8961976c1898e0ea9a7a640ed2    "chaincode -peer.add..."    About a minute ago    Up About a minute        dev-peer0.carrierorg.trade.com-tradecc-v0
9e5164bd8da1    dev-peer0.exporterorg.trade.com-tradecc-v0-dc2ed9ea732a90d6c5ffb0cd578dfb614e1ba14c2936b0ae785f30ea0f37da56    "chaincode -peer.add..."    About a minute ago    Up About a minute        dev-peer0.exporterorg.trade.com-tradecc-v0

调用链码

现在我们已经完成了通道的设置和交易链代码的安装,我们需要实现函数来执行链代码调用。我们的代码在invoke-chaincode.js中的invokeChaincode函数中。

调用chaincode的过程与我们实例化的过程相同,代码也相似。调用者必须构建一个事务提议,该提议由要调用的chaincode函数的名称和要传递给它的参数组成。只需提供chaincode ID(在我们的实现中为tradecc)就足以识别chaincode流程,以指导请求:

tx_id = client.newTransactionID();
var request = {
  chaincodeId : Constants.CHAINCODE_ID,
  fcn: funcName,
  args: argList,
  txId: tx_id,
};
channel.sendTransactionProposal(request);

与实例化提议的一个不同之处在于,该操作通常不需要组织中的管理用户;任何普通成员都可以。该提案必须发送给足够多的签署方,以收集正确的签名集来满足我们的签署政策。这是通过将我们网络中的所有四个对等体添加到 channel 对象(必须以与前面阶段相同的方式创建和初始化)来完成的。一旦以与实例化建议相同的方式收集并验证了建议响应,就必须建立交易请求并将其发送给订购方:

var request = {
  proposalResponses: proposalResponses,
  proposal: proposal
};
channel.sendTransaction(request);

我们从测试脚本的createTradeApp.js中调用invokeChaincode。我们希望执行的 chaincode 函数是requestTrade,按时间顺序,它是导入者角色的用户应该调用的第一个函数(回想一下,我们在chaincode中构建了访问控制逻辑,以确保只有导入者组织的成员可以提交requestTrade):

var invokeCC = require('./invoke-chaincode.js');
invokeCC.invokeChaincode(Constants.IMPORTER_ORG, Constants.CHAINCODE_VERSION, 'requestTrade', ['2ks89j9', '50000','Wood for Toys', 'Importer']);

最后一个参数('Importer')简单地表示进口商组织中提交该交易请求的用户的 ID。在代码中,如果用户已经向 CA 注册,则加载该用户的凭证,否则使用clientUtils.getUserMember函数注册一个具有该 ID 的新用户。

与实例化的情况一样,成功的channel.sendTransaction调用只是表明订购者接受了交易。只有订阅事件才能告诉我们交易是否成功提交到分类帐。

查询链码

链码查询的实现稍微简单一些,因为它涉及整个网络,但只需要客户端到对等端的通信。

应该像前面的阶段一样创建客户端和通道句柄,但是这一次,我们将从调用者(或客户端)的组织中选择一个或多个对等体来与通道对象相关联。然后,我们必须创建一个查询请求(与调用提议请求相同)并将其提交给选定的对等方:

var request = {
  chaincodeId : Constants.CHAINCODE_ID,
  fcn: funcName,
  args: argList
};
channel.queryByChaincode(request);

在返回给呼叫者之前,可以收集并比较对查询的响应。完整的实现可以在query-chaincode.js中的queryChaincode函数中找到。我们通过在我们的createTradeApp.js脚本中运行一个getTradeStatus链码查询来测试这个函数:

var queryCC = require('./query-chaincode.js');
queryCC.queryChaincode(Constants.EXPORTER_ORG, Constants.CHAINCODE_VERSION, 'getTradeStatus', ['2ks89j9'], 'Exporter');

与调用一样,我们指定了用户 ID ( ‘Exporter’)和组织:这里我们希望出口商组织的成员检查交易请求的状态。

由于查询是客户端及其相关对等方的本地查询,因此响应会立即返回给客户端,而不必进行订阅(与调用的情况一样)。

完成循环——订阅区块链活动

正如我们在前面的章节中看到的,在许可的区块链上对共享账本的承诺需要网络对等体之间的共识。在其 v1 版本中,Hyperledger Fabric 有一个更独特的提交到分类帐的流程:事务执行、订购和提交流程都是相互解耦的,并且被构造为管道中的阶段,在管道中,签署者、订购者和提交者相互独立地执行他们的任务。因此,任何导致块提交到分类帐的操作在事物的结构方案中都是异步的。我们在中间件中实现的三个操作属于这一类别:

  • 渠道连接
  • 链代码实例化
  • 链代码调用

在我们对这些操作的描述中,我们停留在请求被成功发送到订购者的地方。但是为了完成操作循环,任何使用我们的中间件的应用程序都需要知道请求的最终结果,以驱动应用程序逻辑前进。幸运的是,Fabric 为异步操作的结果通信提供了发布/订阅机制。这包括提交块、完成事务(成功或失败)的事件,以及可以由链代码定义和发出的自定义事件。这里,我们将研究块和事务事件,它们涵盖了我们感兴趣的操作。

Fabric 通过一个EventHub类为 SDK 中的事件订阅提供了一种机制,相关的订阅方法分别是registerBlockEventregisterTxEventregisterChaincodeEvent,当事件可用时,可以将回调函数传递给这些方法,以便在中间件层(或更高层)执行操作。

让我们看看如何在中间件代码中捕捉成功加入的事件。回到join-channel.js中的joinChannel函数,下面的代码为一个给定的对等体实例化了一个EventHub对象,其配置是从config.json加载的。例如,为了订阅来自导出组织的唯一对等体的事件,我们的fabric-client实例将监听的 URL(在幕后)是grpcs://localhost:7053:

let eh = client.newEventHub();
eh.setPeerAddr(
  ORGS[org][key].events,
  {
    pem: Buffer.from(data).toString(),
    'ssl-target-name-override': ORGS[org][key]['server-hostname']
  }
);
eh.connect();
eventhubs.push(eh);

每个块事件的监听器或回调定义如下:

var eventPromises = [];
eventhubs.forEach((eh) => {
  let txPromise = new Promise((resolve, reject) => {
    let handle = setTimeout(reject, 40000);
    eh.registerBlockEvent((block) => {
      clearTimeout(handle);
      if(block.data.data.length === 1) {
        var channel_header = block.data.data[0].payload.header.channel_header;
        if (channel_header.channel_id === channel_name) {
          console.log('The new channel has been successfully joined on peer '+ eh.getPeerAddr());
          resolve();
        }
        else {
          console.log('The new channel has not been succesfully joined');
          reject();
        }
      }
    });
  });
  eventPromises.push(txPromise);
});

每当接收到一个块事件时,代码就将预期的通道名称(在我们的场景中是tradechannel)与从块中提取的名称进行匹配。(块有效负载是使用结构源代码中的标准模式构建的,在protos文件夹中。理解和使用这些格式是留给读者的练习。)我们将在代码中设置一个超时(这里是 40 秒),以防止我们的事件订阅逻辑无限期等待并阻塞应用程序。最后,通道加入的结果是有条件的,不仅取决于channel.joinChannel调用的成功,还取决于阻塞事件的可用性,如下所示:

let sendPromise = channel.joinChannel(request, 40000);
return Promise.all([sendPromise].concat(eventPromises));

对于实例化和调用,我们不是为块而是为特定的事务注册回调,这些事务由事务提议创建期间设置的 id 来标识。订阅的代码可以在instantiateChaincodeinvokeChaincode函数中找到,分别在instantiate-chaincode.jsinvoke-chaincode.js中。后者的一段代码说明了事务事件处理的基本工作方式:

eh.registerTxEvent(deployId.toString(),
  (tx, code) => {
    eh.unregisterTxEvent(deployId);
    if (code !== 'VALID') {
      console.log('The transaction was invalid, code = ' + code);
      reject();
    } else {
      console.log('The transaction has been committed on peer '+ eh.getPeerAddr());
      resolve();
    }
  }
);

传递给回调的参数包括一个事务句柄和一个状态代码,可以通过检查来查看 chaincode 调用结果是否成功提交到分类帐。一旦接收到事件,事件侦听器就会被取消注册,以释放系统资源(我们的代码也可以侦听块事件,而不是特定的事务事件,但是它必须解析块有效负载,并找到和解释关于所提交的事务的信息)。

把所有的放在一起

前面描述的步骤序列可以通过适当编码的脚本一次性运行。如前所述,createTradeApp.js包含这样一个脚本,它导致创建tradechannel,将四个对等体加入到该通道,在所有四个对等体上安装trade_workflow链码,并随后在通道上实例化它,最后以创建从进口商到出口商的贸易请求和后续的请求状态查询结束。您可以运行以下命令,并在控制台上查看正在执行的各个步骤:

node createTradeApp.js

作为一个练习,为了测试中间件库函数和链代码,您可以完成 createTradeApp.js 脚本开始的贸易场景,从出口商接受贸易请求开始,到进口商为成功交付的货物向出口商全额付款结束。要查看运行情况,请运行以下命令:

node runTradeScenarioApp.js

用户应用程序–导出服务和 API

为我们的中间件创建一组功能的练习为我们可以在其上构建的面向用户的应用程序奠定了基础。尽管我们可以用不同的方式设计应用程序,但是它应该提供的功能集将保持不变。在展示为区块链用户构建应用程序的方法之前,我们将讨论这样一个应用程序应该具备的突出特性。

应用程序

参考图 5.2:Hyperledger Fabric 应用程序的典型三层架构以及我们在本章应用程序模型和架构部分的讨论,Hyperledger Fabric 应用程序的不同用户可能需要不同的应用程序。我们的贸易场景就是一个例子:代表贸易方、银行、运输商和政府机构的用户可能需要我们的应用程序提供不同的东西,即使他们集体参与贸易网络并认可智能合同操作。

不同组织的管理员必须能够执行一些常见的操作。这包括从创建渠道到链码实例化的各个阶段。因此,如果我们需要为每个网络参与者构建不同的应用程序,我们应该向这些应用程序的每个实例公开这些功能。一旦我们进入应用程序本身,它由链码提供的调用和查询功能组成,我们必须为差异化创造空间。为交易方和他们的银行设计的应用程序必须向用户公开交易和信用证操作。但是,没有必要向承运人公开这些操作,因此为承运人设计的应用程序可以而且应该限制那些影响承运人角色的功能,例如创建提单和记录货物位置的功能。

这里,为了简单起见,我们将把所有的应用程序合并成一个,并演示如何让它工作。基于用户角色和需求的多样化留给读者去练习。我们的合并应用程序将被实现为一个 web 服务器,松散地连接智能契约和中间件,从最终用户那里探听它。

用户和会话管理

任何面向服务的应用程序的设计都需要确定允许哪些用户访问应用程序并执行各种操作。对于 Hyperledger Fabric 应用程序,应该特别考虑用户类别之间的差异。每个结构网络都有一组特权用户(我们称之为组织的管理员)和普通成员。这种角色的区分也必须反映在面向用户的应用程序的设计中。

应用程序必须有一个身份验证层和一个会话管理机制,允许已经通过身份验证的用户在他们的角色限制下使用应用程序。在我们的示例应用程序中,我们将为此使用 JSON Web 令牌 ( JWT )。

设计 API

在构建我们的应用程序之前,我们必须设计一个服务 API 来覆盖我们的中间件所公开的功能。我们将设计我们的 API 为RESTful,如下所示:

  1. POST/login:注册新用户(管理用户或普通用户)或以现有用户身份登录
  2. POST/channel/create:创建频道

  3. POST/channel/join:将网络对等体加入到该用户会话中创建的通道中

  4. POST/chaincode/install:在对等体上安装chaincode
  5. POST/chaincode/instantiate:实例化通道上的chaincode
  6. POST/chaincode/:fcn:调用chaincode函数fcn,并传递参数(在函数体中);fcn的例子有requestTradeacceptLC等等
  7. GET/chaincode/:fcn:查询chaincode函数fcn,传递参数(在体中);fcn的例子有getTradeStatusgetLCStatus等等

总的来说,这些 API 函数涵盖了图 5.3中的事务阶段:区块链应用程序的创建和运行阶段。

创建和启动服务

我们将在 Node.js 中实现一个 express(【https://expressjs.com/】)web 应用程序来公开前面的 API。代码位于我们存储库中的应用程序文件夹中,源代码在app.js中,依赖项在package.json中定义。作为运行 web 应用程序的先决条件,必须通过运行该文件夹中的npm installmake(参见 第 8 章区块链网络中的敏捷性)来安装依赖项。

以下代码片段显示了如何实例化和运行 express 服务器:

var express = require('express');
var bodyParser = require('body-parser');
var app = express();
var port = process.env.PORT || 4000;
app.options('*', cors());
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: false
}));
var server = http.createServer(app).listen(port, function() {});

总而言之,启动一个 web 服务器来监听端口4000上的 HTTP 请求。中间件被配置为支持 CORS,自动解析 JSON 有效负载并在 POST 请求中形成数据。

我们的 web 服务器通过不安全的 HTTP 侦听请求。在生产应用程序中,我们将启动一个 HTTPS 服务器,用于与客户端进行安全、机密的通信。

现在,让我们看看如何配置各种快速路由来实现我们的服务 API 函数。

用户和会话管理

在执行网络(通道)或链码操作之前,用户必须建立一个经过验证的会话。我们将如下实现/login API 函数:

  1. 为用户创建过期时间为 60 秒的 JWT 令牌
  2. 让用户注册或登录
  3. 如果成功,将令牌返回给客户端

服务器希望在请求体中以表单数据的形式提供用于注册或登录的用户名和组织名。管理用户只是由管理员用户名来标识。请求正文格式为:

username=<username>&orgName=<orgname>[&password=<password>]

必须提供密码,但前提是<username>admin。在这种情况下,中间件将简单地检查所提供的密码是否与用于为组织的 MSP 启动fabric-ca-server的密码相匹配。正如本章前面提到的,我们的 MSP 管理员密码被设置为默认的adminpw

这是一个简单的实现,但是由于 web 应用程序安全性不是本教程的重点,所以这足以展示如何通过智能契约和中间件实现服务器和前端。

JWT 令牌创建和用户注册/登录的代码可在app.js中配置的以下快速路由中找到:

app.post('/login', async function(req, res) { ... });

读者可以尝试其他会话管理机制,如会话 cookies,来代替 JWT 令牌。

现在可以测试我们的 web 应用程序了。首先,使用docker-compose(或trade.sh)打开结构网络,如本章前面所示。

如果您使用cryptogen(或trade.sh脚本)为组织创建了新的加密密钥和证书,您必须清除中间件使用的临时文件夹来保存世界状态和用户信息,否则如果您尝试使用在您的应用程序的前一次运行中使用的 id 来注册用户,您可能会看到错误。例如:如果您机器上的临时文件夹是network/client-certs,您可以简单地从network文件夹运行rm -rf client-certs来清除内容。

在不同的终端窗口中,通过运行以下命令启动 web 应用程序:

node app.js

在第三个终端窗口中,使用curl命令向 web 服务器发送一个请求,在importerorg组织中创建一个名为Jim的普通用户(这是在middleware/config.json中指定的组织名称):

curl -s -X POST http://localhost:4000/login -H "content-type: application/x-www-form-urlencoded" -d 'username=Jim&orgName=importerorg'

您应该会看到如下所示的输出:

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjUwMDU4NTQsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6ImltcG9ydGVyb3JnIiwiaWF0IjoxNTI1MDAxNzE0fQ.yDX1PyKnpQAFC0mbo1uT1Vxgig0gXN9WNCwgp-1vj2g","success":true,"secret":"LNHaVEXHuwUf","message":"Registration successful"}

在中间件中,这里执行的函数是clientUtils.js中的getUserMember,这在本章前面已经讨论过。

要在同一组织中创建管理用户,请运行:

curl -s -X POST http://localhost:4000/login -H "content-type: application/x-www-form-urlencoded" -d 'username=admin&orgName=importerorg&password=adminpw'

您应该会看到如下输出(admin 用户已经注册,所以这是一个登录调用):

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjUwMDU4OTEsInVzZXJuYW1lIjoiYWRtaW4iLCJvcmdOYW1lIjoiaW1wb3J0ZXJvcmciLCJpYXQiOjE1MjUwMDE3NTF9.BYIEBO_MZzQa52_LW2AKVhLVag9OpSiZsI3cYHI9_oA","success":true,"message":"Login successful"}

在中间件中,这里执行的功能是clientUtils.js中的getMember

网络管理员

正如您在app.js中看到的,从通道创建到链码实例化的 API 函数都是以快速路线实现的:

app.post('/channel/create', async function(req, res) { ... });
app.post('/channel/join', async function(req, res) { ... });
app.post('/chaincode/install', async function(req, res) { ... });
app.post('/chaincode/instantiate', async function(req, res) { ... });

要使用这些路由,终端用户必须以管理员身份登录,并使用返回的令牌。根据上一次调用的输出,我们可以如下请求创建通道:

curl -s -X POST http://localhost:4000/channel/create -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjUwMDU4OTEsInVzZXJuYW1lIjoiYWRtaW4iLCJvcmdOYW1lIjoiaW1wb3J0ZXJvcmciLCJpYXQiOjE1MjUwMDE3NTF9.BYIEBO_MZzQa52_LW2AKVhLVag9OpSiZsI3cYHI9_oA"

注意,授权头的格式是Bearer <JWT token value>。web 服务器隐含地假设频道名为tradechannel,它是在middleware/constants.js中设置的。(如果愿意,您可以扩充服务器 API 以接受请求体中的通道名。)如果一切顺利,输出应该如下所示:

{"success":true,"message":"Channel created"}

类似的查询可以由管理员运行,用于信道加入、链码安装和链码实例化。例如,实例化 API 端点期望链码路径、链码版本和链码的参数列表如下:

curl -s -X POST http://localhost:4000/chaincode/instantiate -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjUwMDU4OTEsInVzZXJuYW1lIjoiYWRtaW4iLCJvcmdOYW1lIjoiaW1wb3J0ZXJvcmciLCJpYXQiOjE1MjUwMDE3NTF9.BYIEBO_MZzQa52_LW2AKVhLVag9OpSiZsI3cYHI9_oA" -H "content-type: application/json" -d '{ "ccpath": "github.com/trade_workflow", "ccversion": "v0", "args": ["LumberInc", "LumberBank", "100000", "WoodenToys", "ToyBank", "200000", "UniversalFreight", "ForestryDepartment"] }'

如果一切顺利,输出将是:

{"success":true,"message":"Chaincode instantiated"}

在每个路由的实现中,都会进行检查以确保用户(由 JWT 令牌标识)是管理用户,如下所示:

if (req.username !== 'admin') {
  res.statusCode = 403;
  res.send('Not an admin user: ' + req.username);
  return;
}

如果我们使用注册为 Jim 的用户的令牌,web 服务器将向客户端返回一个403错误代码。

运用应用程序

一旦管理用户初始化了链码,我们的应用程序就可以开始运行了。现在,任何普通用户(比如进口商组织中的 Jim)都可以请求调用或查询链码。例如,可以按如下方式提出交易请求:

curl -s -X POST http://localhost:4000/chaincode/requestTrade -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjUwMDU4NTQsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6ImltcG9ydGVyb3JnIiwiaWF0IjoxNTI1MDAxNzE0fQ.yDX1PyKnpQAFC0mbo1uT1Vxgig0gXN9WNCwgp-1vj2g" -H "content-type: application/json" -d '{ "ccversion": "v0", "args": ["2ks89j9", "50000","Wood for Toys"] }'

请注意,必须在请求正文中提供链码版本。如果一切顺利,输出将是:

{"success":true,"message":"Chaincode invoked"}

随后,可以查询交易的状态(再次通过Jim):

curl -s -X GET http://localhost:4000/chaincode/getTradeStatus -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjUwMDU4NTQsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6ImltcG9ydGVyb3JnIiwiaWF0IjoxNTI1MDAxNzE0fQ.yDX1PyKnpQAFC0mbo1uT1Vxgig0gXN9WNCwgp-1vj2g" -H "content-type: application/json" -d '{ "ccversion": "v0", "args": ["2ks89j9"] }'

现在,输出应该包含 chaincode 响应:

{"success":true,"message":"{\"Status\":\"REQUESTED\"}"}

用户/客户端交互模式

虽然运行 curl 命令足以测试我们的 web 应用程序,但是向用户展示应用程序的正确方式应该是通过一个或多个 web 页面,让用户使用小部件来触发这些命令。

正如我们在中间件实现一节中看到的,包括链代码调用在内的各种操作都是异步的。在我们的实现中,我们通过让包装器函数返回到调用者来屏蔽这种异步行为,但是只有当请求已经成功发送到订购者并且订阅的事件已经被接收和验证时。我们还可以选择向 web 应用程序客户端公开这种异步行为。使用 Web 套接字(https://developer . Mozilla . org/en-US/docs/Web/API/Web Sockets _ API),每当事件通知到达向事件中心注册的回调时,呈现给最终用户的 Web 界面的内容可以被动态更新。

设计好的 web 界面超出了本书的范围,读者可以利用其他知识来源来构建适合自己应用的界面。

测试中间件和应用程序

我们已经展示了如何使用示例脚本和curl命令来练习基于 Node JS 的中间件和应用程序功能。通过观察控制台输出,您可以发现应用程序是否按预期工作。对于一个生产应用程序,您将需要一个更加健壮和可维护的测试方法,该方法可以持续地评估库函数和 API 端点的正确性。单元测试和集成测试都应该是评估过程的一部分。这种测试的实际演示超出了本章的范围,编写单元和集成测试留给读者作为练习。Mocha 是一个功能丰富的用于异步测试的 JavaScript 框架(https://mochajs.org/),可以用于这个目的。

与现有系统和流程的集成

在与客户讨论端到端解决方案时,我们经常解释说,与区块链相关的组件只占总占地面积的很小一部分。这仍然是一组非常重要的组件,但尽管如此,它们所占的空间很小。

本节将重点介绍我们的传统系统与 Hyperledger Fabric 和 Composer API 之间的接触点。

我们将探索我们利用的各种集成模式,并了解一些非功能性需求如何影响集成部署。最后,我们将探讨集成商在设计他们的集成层时需要记住的一些额外的考虑。

简而言之,在本节中,您将:

  • 理解集成层的设计考虑
  • 回顾集成设计模式
  • 探索非功能性需求对集成的影响

设计考虑

到目前为止,您已经有了使用 Fabric SDK 的经验,到第 7 章商业网络示例结束时,您将已经有了使用Composer REST网关的经验。当谈到集成时,这些当然是主要的交易工具,但是它们是生态系统的一部分,并且需要企业的业务流程的一致性,以确保集成有意义。

*根据设计考虑,我们将关注以下方面:

  • 权力下放的影响
  • 流程调整
  • 消息相似性
  • 服务发现
  • 身份映射

分散

已经进行了许多尝试来标准化 IT 功能和能力,但是现实是没有两个组织具有相同的 IT 环境。即使对于那些选择了同一家企业资源规划系统供应商的人来说,这些系统也是为满足组织的流程和需求而定制的。

这意味着,在规划您的集成设计时,您应该记住,每个组织可能有自己调用智能合约的方式,并且可能没有相同的 IT 功能或策略。

例如,通过 Web Socket 公开事件对于熟悉基于云的技术的组织来说可能是有意义的,但是其他组织可能不具备这种技能,或者他们的 IT 安全策略可能不允许他们使用该协议。

虽然有些人可能会感到惊讶,但请记住,一个网络可能是财富 500 强企业和初创企业的混合体。考虑一下供应链行业;您会发现一些几乎没有 IT 基础设施的货运公司,一直到行业巨头。显然,一种尺寸不可能适合所有人。

话虽如此,从网络的角度来看,您应该考虑网络希望为加入组织提供的支持程度。有两种可能的方法:

  • 网络提供了集成资产:这可以采取网关的形式,每个参与者在自己的基础设施中部署网关。网关是每个人的标准,以一致的方式管理智能合约的调用。 这可以提供加速入职流程的好处,但需要考虑谁拥有、管理和支持这一 it 组件。此外,由于信任问题,一些组织可能不想部署这种基础设施。
  • 每个参与者构建他们自己的集成层:这种方法的明显缺点是所有参与者都要重新创建轮子,但是它减少了在每个组织中部署通用组件所带来的潜在支持问题。 这也可能是需要深度系统集成的用例的首选方法,以获得流程优化的好处。

流程调整

集成层必须处理两种不同的观点:

  • 组织 IT 系统和业务流程观点:一个组织的业务流程可能托管在一个 ERP 中,比如 SAP。在这种情况下,当特定的业务事件需要调用智能合同时,可以通过 SAP 系统中的业务 API ( BAPI )调用来发布。来自 ERP 的 API 调用可能包含各种数据结构,其中一些与区块链网络完全无关。
  • 智能契约观点:这种观点的特殊性在于其数据表示与应用程序无关。这意味着网络的所有参与者都将了解正在处理的数据的性质。

由集成层来协调这两者,并确保在两个系统中维护正确的事务语义。这可能意味着:

  • 映射:将数据从一个字段名移动到另一个字段名
  • 转换:根据输入聚合、拆分或计算新值
  • 交叉引用:利用引用表将特定于应用的代码映射到网络认可的值

这里的要点是,即使您的网络同意使用在第 7 章商业网络示例中介绍的 Hyperledger Composer REST 网关,每个参与者仍然需要完成一些工作,以确保集成符合组织的整体业务流程。

消息相似性

虽然这不是一个经常讨论的问题,但是忽略它会导致严重的问题,这些问题通常会在集成或性能测试期间出现。

我们将消息相似性称为当系统发出一系列相互依赖的事务时发生的情况,这些事务是在短时间内发出的。因为每笔交易都是单独发出的,所以它们的处理顺序与客户发出的顺序不同。

结果可能是不可预测的,如下例所示。具体来说,让我们看一个发出三个独立交易的订单流程,如下图所示:

图 5.4:按顺序处理服务请求

因为服务提供者是多线程的,所以处理的顺序可以根据当时的负载而变化。下图显示了一个潜在的结果:

图 5.5:潜在的服务处理结果

无序处理的第一个项目将被拒绝,因为订单对象尚未创建。但是,这两个后续对象会成功,并使系统处于订单被记录为只有一个项目而不是两个项目的状态。

这种情况的挑战是很难排除故障。不知情的开发人员可能无法在他/她的开发平台上重现这种行为。

现在,你可能想知道,这与区块链和 Hyperledger Fabric 有什么关系?考虑到 Fabric 事务是异步处理的,并且它们是针对每个世界状态进行验证的,所以会出现这种情况。客户端将发出事务,并可能异步接收一条消息,说明该事务无效,因为它不符合世界状态。

这个故事的寓意是,当设计一个 API 时,确保它们在一个完整描述业务事件的粒度级别。太多的细粒度事务只会导致消息相似性、增加延迟和潜在的问题,如本文所述。

服务发现

在集成到 Hyperledger Fabric 和 Composer 的上下文中,服务发现的概念集中在记录和向调用应用程序公开 Fabric 中的构件:CA、对等体和订购者。

正如我们现在所经历的,为了让应用程序获得交易认可并添加到分类帐中,它需要能够与这些类型的许多组件进行交互。将这些信息作为服务配置元素来维护,将使团队能够快速适应网络的发展。

目前,当使用 Fabric SDK 开发客户端应用程序时,开发人员负责管理和应用此服务配置。

Hyperledger Fabric 路线图的一部分旨在促进这种配置。

依赖于像Composer REST网关这样的组件的好处之一是服务发现是由网关提供的。具体地说,您很快就会发现,它提供了名片的概念,名片包含身份信息和连接配置文件,连接配置文件包含可用于执行事务的 Hyperledger Fabric 服务列表。

身份映射

身份映射是将个人或组织的身份转换为网络上可识别的身份的过程。

当从业务网络的角度来看解决方案时,需要识别的身份的粒度是多少?其他组织会关心是来自 ACME 的 Bob 还是 Ann 发布了事务吗?在大多数情况下,答案是否定的。知道事务是由 ACME 发出的就足够了。

你可能会想,这是为什么。它与信任的概念直接相关。还记得 第一章区块链——企业与行业视角中提出的概念吗;区块链解决了时间和信任的问题。了解信任问题的来源有助于我们合理化在网络上应该使用什么身份进行交易。在大多数情况下,我们的经验是信任问题发生在组织之间。

如果您考虑一个用例,银行客户通过他们的银行门户进行交易,客户将不会关心后端系统;他们信任银行的安全系统。

话虽如此,还是有需要映射身份的情况:

  • 通过组织的集成层进行交易的业务伙伴
  • 不同部门拥有不同级别的特权
  • 具有不同角色的用户驱动不同的访问权限

在这种情况下,集成层需要将入站凭证(API 密钥、用户 ID 和密码、JTW 令牌等)转换为 Hyperledger Fabric 身份。

使用 Hyperledger Composer REST网关时,您可以将其配置为支持多个用户。服务器利用节点 passport 框架来管理这种身份验证。这提供了支持不同模型的灵活性(例如,用户 ID/密码、JWT 和 OAUTH)。

一旦客户端通过了服务器的身份验证,还有一个额外的步骤,就是将 Hyperledger Composer 名片加载到服务器的用户存储库中。客户机和服务器之间需要有隐含的信任,因为名片包含私钥。

集成设计模式

现在,我们将看看我们在行业中看到的一些可行的集成模式。该列表绝非详尽无遗,鉴于我们仍处于 Hyperledger Fabric 和 Composer 解决方案的早期阶段,我们预计随着人们和组织对该技术越来越熟悉,新的模式将会出现。

企业系统集成

在这个类别中,我们考虑任何组织在加入网络之前已经存在的系统。因此,这些系统有它们自己的概念和范例,我们需要一种抽象的形式来调和这两个世界。

与现有记录系统集成

下图说明了现有记录系统的区块链网络:

图 5.6:将区块链网络整合到现有的记录系统中

大多数寻求加入商业网络的大型企业最终将致力于整合他们的记录系统,以确保他们从交易的实时透明分配中受益。在这种情况下,我们之前提到的过程一致性将会非常重要。

如上图所示,该方法将利用适配器模式作为两个世界之间的数据映射器。适配器将采用企业系统应用协议和数据结构来接收交易请求。或者,它还可以利用现有的基础(如消息服务)来传播分类帐事件。

这里需要注意的重要一点是,这种类型的集成将特定于一个组织,很少有重用的可能。

作为这种模式的变体,一些组织将适配器分成两部分:

  • REST 网关:公开与 Fabric 智能契约一致的 REST 接口
  • 集成总线:映射字段,连接企业系统

虽然在这个变体中重用性更高,但是相同的考虑事项只向下移动了一层。

与运营数据存储集成

下图说明了如何将区块链网络集成到运营数据存储中:

图 5.7:将区块链网络集成到运营数据存储中

很多时候,组织都在寻找对分类帐中的信息进行分析的方法。然而,对组织的对等方发出多个/大型查询只会影响系统的在线性能。通常,企业系统设计中公认的方法是将数据移动到可操作的数据存储中。然后可以很容易地查询数据。通过使用不同的数据源丰富数据,可以创建数据的附加视图。

在这个模式中,事件侦听器订阅 Fabric 组织事件。因此,它可以从组织有权访问的所有渠道接收交易。如果保持数据的完整性很重要,事件侦听器可以计算每个记录的散列,并将它们与记录一起存储。

您会注意到,该模式还考虑了一个syncAll函数,该函数允许事件监听器用世界状态的最新视图重新同步数据存储。请记住,这个syncAll函数的实现需要非常小心,并且很可能需要该函数支持结果集的分页。

微服务和事件驱动架构

下图说明了区块链应用的微服务和事件驱动架构:

图 5.8:区块链应用的微服务和事件驱动架构

我们将这种模式标记为微服务和事件驱动,因为这是这些类型的架构中最常见的模式。但是,这种模式的特殊性来自于网关。这样的系统将不执行任何数据映射;它将利用通用的通信协议(HTTP)和数据格式(通常是 JSON,但也可能是 XML)。还期望服务已经被设计为理解正在处理的数据的语义。事件也通过相同的协议和数据格式传播。

同样,微服务应用往往是更新的应用,它们受益于更细粒度的接口。因此,它们往往进化得更快,并且能够适应和坚持来自网络的交易。类似地,事件驱动的应用程序将受益于它们与系统其他组件的低耦合,因此是这种模式的良好候选。

考虑可靠性、可用性和可维护性

对于任何工业应用来说,软件或硬件组件的故障都是不可避免的,因此您必须将应用设计为对故障具有鲁棒性,并将停机的可能性降至最低。我们将讨论业内广泛用于构建和维护系统的三个关键准则,并简要分析它们如何应用于使用 Fabric 或 Composer 工具构建的应用程序。

可靠性

一个可靠的系统是一个在面对故障时能确保正确操作的系统。这需要做以下事情:

  • 系统的持续自我监控
  • 检测组件中的故障或损坏
  • 修复问题和/或故障转移到工作组件

尽管业界已经发展出各种实践来确保可靠性,但是冗余和故障转移是普遍使用的。

在我们在第一节中构建的 Fabric 应用程序的上下文中,这有一定的含义。回想一下,Fabric 有许多不同的组件,它们必须协同工作(尽管是以松耦合的方式)才能确保成功运行。订购服务就是这样一个关键组件,如果它出现故障,将会完全停止事务管道。因此,当构建一个产品版本时,比如说,我们的贸易应用程序,您必须确保订购者有足够的内置冗余。实际上,如果您的订购者是一个 Kafka 集群,这意味着要确保有足够的 Kafka 节点(代理)在一个或多个节点出现故障时填补空缺。

同样,同行认可和承诺的可靠性是确保交易完整性的关键。尽管作为共享复制分类帐的区块链被设计成在某种程度上对对等故障具有鲁棒性,但是它们的弱点可能根据应用而变化。如果签署对等方失败,并且其签名是满足事务签署策略所必需的,则不能创建事务请求。如果签署对等体行为不当,并产生不正确的执行结果,则事务将无法提交。在任一情况下,系统的吞吐量将减少或降至零。为了防止这种情况发生,您应该确保在每个组织内的对等组中有足够的冗余,尤其是对于满足认可策略至关重要的那些。下图说明了一种可能的机制,通过这种机制,向多个对等方提出事务建议,并使用多数规则丢弃缺少或不正确的响应:

图 5.9 可靠交易背书的冗余对等点

系统的可靠性水平取决于用于监控和故障转移的资源数量。例如,上图中的五个对等体足以应对两个对等体故障,但现在组织中需要的对等体比我们在示例网络中使用的多四个。为了确定并确保您的网络达到预期的可靠性水平,您需要在一段时间内对整个系统进行集成测试。

有效

可用性标准与可靠性密切相关,但它更多的是确保系统高概率正常运行,或者作为推论,最小化系统停机的概率。与可靠性一样,检测故障节点并确保充分的故障转移是确保应用程序保持运行的关键,即使一个或多个组件出现故障也是如此。确定所需的可用性级别,以冗余和自我纠正组件的形式分配足够数量的资源,并在生产环境中进行测试,这些都是确保您的应用程序获得所需性能所必需的。

适用性

可维护性或可维护性是指在不影响整个系统的情况下,更换或升级系统部件的难易程度。

考虑这样一种情况,您必须升级一个或多个订购服务节点上的操作系统,或者如果您需要替换组织内有故障的对等节点。与可靠性或可用性一样,在工业规模的系统中,拥有应用程序操作可以无缝切换到的冗余(或并行)资源是处理这一问题的方法。所谓的蓝绿部署是用于此目的的流行机制之一。简而言之,您有两个并行的环境(比方说,对于订购服务),一个称为蓝色,一个称为绿色,其中蓝色环境正在接收实时流量。您可以升级绿色机器上的操作系统,充分测试它们,然后将流量从蓝色切换到绿色。现在,当绿色服务于请求时,您可以用同样的方式升级蓝色。

在具有松散耦合组件的区块链应用程序中,建议每个组件(订购方、对等方和 MSP)都有蓝色和绿色的环境,并分阶段或一次一个组件集群地进行升级和测试,以最大限度地减少发生事故的可能性。

摘要

构建一个完整的区块链应用程序是一个雄心勃勃且富有挑战性的项目,这不仅是因为它需要一系列技能(系统、网络、安全和 web 应用程序开发等),还因为它需要跨多个安全领域的多个组织协同开发、测试和部署。

在这一章中,我们从一个简单的智能合约开始,以一个四对等区块链网络结束,该网络已准备好驱动交易场景,并将记录存储在一个防篡改、共享、复制的分类账中。在此过程中,我们学习了如何设计组织结构和配置结构网络。我们学习了如何使用 Fabric SDK 构建通道或结构区块链的实例,让网络中的对等体加入通道,并在通道上安装和实例化智能合约。我们学习了如何通过 web 应用程序向最终用户公开我们的网络和智能合约的功能,公开服务 API。我们还了解了 Hyperledger Fabric 事务管道是如何工作的,以及块提交操作的异步特性是如何被考虑到端到端应用程序的实现中的。

在本章的后半部分,我们学习了各种设计模式和最佳实践,可以用来构建行业规模的区块链应用程序。我们还了解了在将这些应用程序与现有系统和流程集成时需要牢记的注意事项。最后,我们探讨了运行可操作 Fabric 网络的性能方面,并了解了 CAP 定理以及 Fabric 如何在分布式环境中实现数据一致性。

毫无疑问,Hyperledger 平台和工具将随着时间的推移而发展,以满足行业和开发人员的需求,但我们在应用程序构建练习中描述的架构和方法,以及设计和集成模式,应该继续作为长期的教育指南。

到目前为止,我们的旅程已经将我们带到了 Hyperledger Fabric 框架的基础。我们已经使用 chaincode 和 Fabric SDK API 集成了一个应用程序。这些都是必备技能。

在接下来的两章中,我们将探索一种不同的建模和实现业务网络的方法。**


我们一直在努力

apachecn/AiLearning

【布客】中文翻译组