Hyperkedge-区块链开发教程(三)

79 阅读1小时+

Hyperkedge 区块链开发教程(三)

原文:zh.annas-archive.org/md5/7f932e9670331dae388d1a76f72881d8

译者:飞龙

协议:CC BY-NC-SA 4.0

第八章:区块链网络的灵活性

此时,如果一切顺利,您应该拥有一个完全功能的去中心化应用程序,其中智能合约正在 Hyperledger Fabric 上运行。有了这些知识,生活会很美好,对吧?然而,就像任何事情一样,解决方案会随着时间的推移而发展。这可能是法规的变化,财团中新成员的引入,或者您的智能合约中的一个简单错误 —— 不管原因是什么,解决方案都会发展,如果没有扎实的开发和运营实践,变化将会缓慢,您的生活将会痛苦。

考虑到在 IT 组织的开发过程中保持敏捷性已经非常具有挑战性,那么在财团中如何做到呢?不同速度的各种文化公司如何汇聚在一起,以在允许他们保持网络提供的竞争优势的时间范围内交付和维护解决方案?

尽管关于 IT 敏捷性和 DevOps 的主题已经有很多文章写得很详细,但本章将重点讨论将其中一些概念应用于区块链网络。我们说 一些 是因为我们的注意力将集中在那些与区块链特定/不同的概念上。通过自动化和持续集成和交付CI 和 CD)流水线的部署,我们将讨论区块链网络对人员、流程和技术的影响。

在本章中,我们将涵盖以下主题:

  • 定义推广流程

  • 配置持续集成流水线

  • 保护源代码控制

  • 更新网络

  • 财团对团队结构的影响

定义推广流程

你可能已经意识到,推广过程定义了任何系统修改需要经历的一系列关键活动和门槛。通常,它涵盖了开发、打包、测试(例如单元测试、功能验证和集成测试)、版本控制和部署。通常,组织会有一个标准化的方法,这个方法会被记录下来,以描述项目及其支持团队所期望的内容。在 Hyperledger Fabric 网络的情况下,至少会有两个不同的推广流程,分别为以下内容:

  • 智能合约:由于这些组件是系统参与者之间业务互动的关键,因此每个参与者同意合约内容至关重要

  • 集成层:由于它们位于网络的边界上,它们的推广流程将取决于它们的所有者是谁(由财团还是特定组织拥有)

可选地,网络策略的更改过程也可能存在;但是,它将与智能合约推广流程密切相关。

但是,在直接进入管道配置之前,让我们花点时间了解这两个推广流程的考虑因素。

智能合约考虑

正如我们所提到的,智能合约对于任何区块链网络中的参与者之间的业务互动至关重要。由于它们基本上包含了交易被视为有效的规则和条件,我们需要确保每个参与者和组织都同意其有效性——否则,信任将受到损害。

晋升智能合约的条件将包括以下内容:

  • 与问题的可追溯性:这是一个修复 bug 还是一个新功能?除了这一点,组织可能需要在问题移至实施之前批准该问题。

  • 所有测试成功执行:对一些人来说可能是不言而喻的,但大多数测试应该是自动化的,并且结果应该被记录下来。

  • 来自关键方的代码审查:你会在不审查合同条款和条件的情况下签订合同吗?嗯,代码审查起到了类似的作用。

  • 影响评估:新版本的智能合约是否向后兼容?不兼容的变化将需要额外的规划。

  • 来自关键方的签署:在所有其他要点之前,您是否得到了所有相关方的祝福?你会在哪里记录?

关键方的定义将由财团定义。关键方可能是当前使用该智能合约的所有组织,或者该术语可能指的是技术负责人或创始组织成员的子集。

在晋升智能合约的条件之前,晋升频率也可能引起争议。一些组织习惯于季度周期,而其他一些组织习惯于每周部署。如果不提前讨论这一因素,摩擦不可避免,因为这将直接影响组织需要考虑的运营费用,以保持他们的参与达到财团期望的水平。还需注意的是,智能合约可能适用于整个网络或一对或一组参与者。这些智能合约的范围和各种排列组合代表了晋升所需的有趣的系统修改。

关键是修改智能合约的条件和流程应该由财团提前定义,以避免任何误解和挫折。在某种意义上,这与修改传统合同没有区别;合同修改的条件需要提前达成一致,以避免冲突。

集成层考虑

正如我们在第五章中所见,暴露网络资产和交易,组织和财团可以使用一些模式来在网络上调用交易。所选的模式将有助于推动晋升流程的管理。

如果应用程序的服务层直接调用了面料 SDK,那么应用程序的所有者将不得不管理其晋升流程。如果相反,财团强制使用 REST 网关,那么你可以期望它的部署将遵循类似智能合约的流程。

无论所有者如何,集成层提供的抽象应该将应用程序与智能合约隔离开来,因此可以期望它们独立发展。然而,这并不减弱对影响评估的重要性。

晋升流程概述

有了这些概念定义,让我们转向我们应用程序的晋升流程。由于我们正在使用 Git 作为软件配置管理工具,我们将利用其社交编码功能来支持我们的晋升流程:

  • 我们可以使用 Git issues 来记录新功能或 bug 修复

  • 我们可以使用 Git 分支来隔离建议的修改

  • Git GPG 用于对每个提交和标签进行签名

  • Pull request 用于执行治理

以下图表总结了我们将用于配置应用程序的流程:

想知道什么是 pull request 吗?

本章假定读者已经熟悉了许多 Git 概念。如果不是这种情况,最好暂停一下,探索一下 Git 提供了什么。

简要来说,pull request 是人们可以在各自的 fork(即不同的存储库)或分支(在一个存储库内)之间提交代码更改的过程。它提供了一种受控的方式来审查、评论,并最终批准所有的代码更改。

现在我们将详细介绍流程,并关注信任和代码来源的问题。正如我们一直在讨论的,由于智能合约是区块链网络的核心,我们需要确保密切跟踪其发展,以避免不幸事件。从这个角度来看,我们希望从需求(Git issues)一直到部署都能追溯到。

因此,每一次代码修改都应该从创建一个 Git issue 开始。它应该准确地确定其范围——功能请求或 bug 修复,然后描述预期的工作。

我们将在接下来的几章中涵盖治理方面,但目前可以假设问题已经被优先考虑,并且工作将根据财团的优先级进行分配。

一旦开发人员被分配到处理问题,他的第一步将是创建一个临时的 Git 分支,跟踪与该 Git 问题相关的所有代码更改。代码修改绝不能在主分支上进行,因为它代表了代码的稳定版本,新功能和 bug 修复应该在其集成到稳定流之前进行审查。

预期开发者将在自己的本地环境中运行所有适当的测试,并且只有当代码准备就绪且所有单元测试成功完成时才将其提交回分支。

当提交更改的时候,Git 提供了一个功能,允许你使用GPG对所有工作进行签名。你问什么是 GPG?它代表GNU 隐私保护,是openpgp标准的一个开放实现。它基本上提供了一个工具,帮助你使用自己的私钥签名和加密数据。Git 已经实现了 GPG 以允许开发人员对其工作进行签名。每个提交或标签都可以使用作者的 GPG 密钥进行签名,从而提供提交的不可否认性。

为什么使用 GPG 对代码修改进行签名?有些人可能会说这是一个额外的负担,但请考虑被修改的代码代表着一个法律合同,并且是网络信任的根源。从这个角度来看,确保作者的身份被证明是十分必要的。

对于普通提交来说,单因素身份验证可能不足以证明其作者身份;考虑一下互联网上关于有人伪造他人身份的报道。

没有签名的提交,我们可以想象到这样一种情况:一个不法开发者修改智能合约以谋取个人利益,并通过声称自己不是代码更改的真正作者而逃脱惩罚。这样的事件会危及网络的可行性,远远超过签署提交所带来的不便。

现在开发者已经签署了提交,他们准备提交一个拉取请求。拉取请求已经配置为检查以下标准:

  • 临时分支已经与主分支的内容保持一致。

  • 每个提交都已经签名。

  • 代码所有者已经审查并接受了代码更改。

  • 持续集成流水线已经成功完成。

当创建拉取请求时,流水线将自动触发。一旦所有条件都满足,那么其中一个代码所有者可以将代码与主分支合并并提交这些更改(当然要签署提交)。

在实际场景中,财团可能会有额外的环境(用户验收环境、演示环境等),在这些环境中,将会对完整的解决方案堆栈进行测试。

图中描述的最后一步侧重于为发布打标签。这里的想法是一个单一的发布可以由多个拉取请求系列构建而成。当财团准备发布一个新版本时,应该对其进行标记以代表正在部署的官方版本。

就是在这种情况下,流水线将再次被触发,但目标不同:构建、测试、签名并将智能合约发布到一个构件存储库。这个构件存储库可以是众多流行解决方案中的一个,但在我们的情况下,为了简单起见,我们将把智能合约附加到一个 Git 发布中。

有些人可能会想知为什么我们不直接在网络上部署。再次强调,目的是在中心化构建流程和网络的去中心化性质之间保持明确的区分。每个组织都可以收到新智能合约的部署通知,拉取归档文件,根据签名验证,然后部署它。

总结一下,以下是推广过程中的几个要点:

  • 每次代码更改都与变更请求相关联

  • 开发人员使用 GPG 签署其修改

  • 主分支完整性由拉取请求流程保留

  • 流水线为拉取请求构建和测试代码

  • 当更改被标记时,流水线将智能合约发布到存储库

  • 每个组织在新版本可用时收到通知

在接下来的部分,我们将开始配置我们刚刚定义的持续集成流水线。

配置持续集成流水线

并非所有语言都一样,并且虽然我们可以讨论强类型语言(如 Java 和 Go)与非类型语言(如 JavaScript)之间的好处,但事实上,我们需要依赖单元测试来确保代码按预期工作。这本身并不是坏事—每个代码构件都应该由一组具有充分覆盖率的测试支持。

你可能会想:这与持续交付流水线有什么关系?嗯,这一切都要看测试,对于 JavaScript 代码来说,这非常重要。而流水线需要确保以下内容:

  • 代码符合所有质量规则

  • 所有单元测试都成功

  • 所有集成测试都成功

一旦这些步骤成功,流程将能够打包并发布结果。

因此,在接下来的部分,我们将尝试使用流行的云端持续集成服务之一——Travis CI 来部署和配置我们的流水线。我们将涵盖以下内容:

  • 定制流水线过程

  • 将我们的智能合约发布到存储库

一旦所有这些都完成,我们将继续配置我们的 Git 仓库,以控制验证和集成变更的方式。所以,话不多说,让我们开始吧。

定制流水线过程

你可能还记得在我们的推广过程中,我们确定了生命周期中应该触发流水线的两个事件:

  • 拉取请求

  • 标记发布

有些人可能会想为什么只选择了这些特定事件。如果您回想一下流程,开发者预期在其本地环境上手动运行测试,因此不需要每次有人向自己的分支提交代码时触发管道。但是,当开始将代码交付到主分支时,重要的是在接受对主分支的更改之前验证代码是否可以构建、部署和测试。发布版本时也是如此——这表示已经裁剪了新版本,因此最后一次运行管道以发布部署单元(在我们的案例中是智能合约包)是有意义的。

无论如何,这是我们为管道设定的指南,但其他团队可能会选择不同的方法。读者应该将其视为指南,而不是连续交付的明确方法。

本地构建

在我们深入了解管道配置之前,让我们快速看一下构建过程是如何组织的。首先要注意的是,我们的解决方案现在技术含量丰富:Fabric、Composer、gonode.js。这些技术有很多依赖关系需要满足才能进行构建;想想 Fabric 和 Composer 的先决条件,以及 go 和其库、NVMNPMNode 和所有部署的软件包。

为了在本地和远程环境之间获得一致的构建输出,我们需要一种方法来减少和容纳依赖关系。

这就是使用 Dockermake 的方法:

  • Docker 为我们提供了一个环境,帮助我们容纳依赖关系,并使执行在不同环境之间保持一致。

  • make 帮助我们管理依赖关系,因为它内置于大多数操作系统中(遗憾的是,Windows 除外),所以减少了额外的工具部署和配置的需求。

这种组合使开发者可以在其系统上以最少的努力运行构建。如果系统已经安装了 Docker 和 make,那就可以开始了,不需要部署额外的软件包。

Windows 用户:虽然 Windows 自带 make,但我们建议您查看 GNU Make

您可以按照此网站上的安装说明进行安装:gnuwin32.sourceforge.net/packages/make.htm

正如我们提到的,Docker 提供了一个预构建的环境,存在于容器内,因此避免了在本地工作站上部署大量工具的需要。以下是 Composer 任务:

.PHONY: composer
composer:
  echo ">> Building composer package within Docker container"
  docker run --rm -v $(COMPOSER_PATH):/src -v $(DIST_DIR):/dist -w /src node:8.11 sh -c "$(COMPOSER_BUILD_CMD)"

分解 docker 运行命令:

  • --rm:在构建结束时删除容器

  • -v:挂载来自 git 克隆文件夹的 src 和 dist 目录

  • -w:将容器的 /src 目录设置为工作目录

  • node:8:11:已部署和配置了 Node 8.11 的容器镜像

  • sh -c "$(COMPOSER_BUILD_CMD)":要运行的构建命令

正如你所看到的,通过最小的配置,构建现在正在容器内进行,但是使用本地 git 克隆的文件和文件夹。很好的一点是,无论是在本地运行还是在我们的构建流水线中运行,容器的行为都是一样的。

你问为什么要使用.PHONYMakefile 是一个很棒但古老的工具。因此,它最初主要关注文件依赖关系。

如果有人曾经定义过一个名为 buildtest 的文件,make 将认为任务是最新的并且什么都不做。

.PHONY 告诉 make 不将这些标签视为文件。

欢迎探索 Makefile 的其余任务。Chaincode 将使用不同的镜像(golang:1.9.6)构建,但采用相同的方法。

Makefile 任务的角度来看,定义了以下依赖项:

在下一节中,我们将使用 make buildmake test 命令来执行我们的流水线。

配置 Travis CI

使用 Travis CI 很简单。你只需要将浏览器指向 www.travis-CI.org 网站,使用你的 GitHub 身份进行身份验证,并授权 Travis 访问你的 GitHub 帐户,Travis CI 将为你创建一个个人资料并将其与你的 Git 帐户同步。完成这些操作后,你将看到一个 Git 项目列表。你只需要在我们的项目旁边切换开关,Travis CI 将开始跟踪你的 GitHub 存储库中的事件:

使用 .travis.yml 自定义流水线

虽然 Travis CI 现在正在跟踪我们的 Git 存储库,但它还不够智能,不知道在发生事件时该做什么。要告诉 Travis CI 怎么做,我们需要在存储库的根目录下创建一个特殊文件。每当发生 Git 事件(例如 Git 拉取请求)时,.travis.yml 文件将被处理并用于编排流水线执行。

在我们的智能合约的情况下,我们的 Git 存储库的根目录中有以下 .travis.yml

sudo: required
services:
- docker
dist: trusty
cache: 
  directories:
  - node_modules
script:
- make build
- make test

由于我们的 Makefile 使用 Docker 容器进行构建,使得构建独立于其运行环境,我们需要让 Travis 知道这一点。因此,文件的前三行提供了一个指示,表明构建过程将使用 Docker。dist: trusty 修复了 Linux 分发,以确保系统行为的一致性。

重要的行代表了过程的两个主要步骤:

  • 缓存:这是构建的优化,确保 node_modules 在每次构建运行时不会被重新加载。

  • 脚本:这里提供了构建命令。在这种情况下,步骤包括以下内容:

    • make build:构建 chaincode 和 composer BNA

    • make test:执行单元测试

有关链码任务的详细任务已在之前的章节中讨论过,因此我们不会再次涵盖那些细节。但是我们将专注于 Composer 构建并探索package.json文件的 stanza:

[...]
"scripts": {
  "prepare": "mkdirp ../dist && composer archive create --sourceType dir --sourceName . -a ../dist/trade-finance-logistics.bna",
  "pretest": "npm run lint",
  "lint": "eslint .",
  "test": "nyc mocha -t 0 test/*.js && cucumber-js",
 "coverage": "nyc check-coverage",
  "posttest": "npm run coverage"
},  
[...]  

在 composer 文件夹的 trade-finance-logistics 存储库下可以找到package.json

让我们快速审查生成 composer 项目时生成的每个默认命令:

  • prepare: 此命令将我们的项目打包成 BNA 文件。此脚本运行在install之前,并将使用 Hyperledger composer 命令行界面来创建归档。我们对此任务唯一的修改是将子目录..添加到 dist 目录的创建和 BNA 文件的输出中。

  • lint: 运行eslint工具,这是我们用来分析代码并搜索模式的工具。此工具应用的规则可以通过.eslintrc.yml文件进行调整。

  • test: mocha 单元测试框架将运行位于项目测试目录中的测试,并将由nyc工具调用。nyc工具用于测量 mocha 测试的覆盖率。

然后,您需要将这两个任务添加到 package.json 中:

  • posttest: 这个任务是在测试运行后触发的触发器。在这种情况下,它将调用覆盖率任务。

  • coverage: 以报告模式运行nyc工具。此任务将评估是否有足够的单元测试来覆盖代码。如果未满足package.jsonnyc段中定义的最小值,此任务将使构建失败。以下是此config的样本:

        "nyc": {
          "lines": 99,
          "statements": 99,
          "functions": 99,
          "branches": 99
        },

通过修改package.json,我们现在有了运行测试覆盖率和代码质量验证的“门”,如果未达到最低要求,则失败。

发布我们的智能合约包

此时,在传统部署中,我们可以考虑自动化部署我们的应用程序以将其自动推送到生产环境。然而,在区块链网络的情况下,允许单个流程将生产代码推送到多个组织和位置可能会成为网络的软肋。

我们将 BNA 文件发布到受信任的存储库(在这种情况下,GitHub release),并让每个组织都拉取归档,而不是尝试将生产代码推送到多个组织。

幸运的是,Travis CI 在deploy步骤中使用了一个函数,允许我们自动将智能合约包附加到已标记的发布版本。该函数需要在我们的 GitHub 账户上配置一个OAUTH_TOKEN,并且需要将其添加到 Travis 配置以允许 Travis 将智能合约连接到发布版。

虽然可以手动进行该配置,但 Travis 有一个简单的命令行界面,可自动将令牌推送到 GitHub 并将deploy部分添加到.travis.yml中。

我们可以使用以下命令安装travis CLI:

gem install travis

安装了 CLI 后,我们运行以下命令:

$ travis setup releases
Username: ldesrosi
Password for ldesrosi: ********
File to Upload: ./dist/network.bna
Deploy only from HyperledgerHandsOn/trade-finance-logistics? |yes| 
Encrypt API key? |yes| no

工具将询问我们一些信息:我们的 GitHub 用户 ID,密码,我们要上传的文件的位置(我们的 BNA),是否只想从我们的仓库deploy,以及我们是否要加密我们的 API 密钥。对于最后一个问题,重要的是要说不。我们很快会解释为什么。

工具将在.travis.yml文件末尾添加类似以下的部分:

deploy:
  provider: releases
  api_key: 3ce1ab5452e39af3ebb74582e9c57f101df46d60
  file_glob: true
  file: ./dist/*
  on:
    repo: HyperledgerHandsOn/trade-finance-logistics

我们要做的第一件事是将 API 密钥复制到我们的工作站剪贴板上,然后回到 Travis CI 网站。在主仪表板上,您应该看到您的仓库,在右侧,您会看到一个名为更多选项的按钮。点击它,然后选择设置,您将看到一个面板,分成几个部分。

向下滚动一点,您会找到环境变量部分。按照以下步骤进行:

  1. name字段中,输入 OAUTH_TOKEN

  2. value字段中,粘贴您在.travis.yml文件中复制的 API 密钥

  3. 点击保存

结果应该如下:

你看,虽然我们本可以将 OAUTH_TOKEN 加密在我们的 .travis.yml 文件中,但那样的话它就会被存储在我们的 GitHub 仓库中供所有人查看。通过将密钥移至环境中,我们避免了这种情况。

现在我们可以修改配置文件以引用我们刚刚定义的环境变量:

deploy:
 provider: releases
 api_key: ${OAUTH_TOKEN}
 file_glob: true
 file: ./dist/*
 on:
 repo: HyperledgerHandsOn/trade-finance-logistics
 tags: true

on:部分提供了将发布过程限制为仓库上的tag事件的能力。

随着package.json.travis.yml的修改,我们只需要通过提交和推送我们的更改到主分支来更新我们的仓库。我们的流水线现在已经完全配置好了!在接下来的几节中,我们将看到网络参与者如何被通知新版本并检索归档,但现在让我们看看我们需要在 Git 中配置的内容。

配置您的 Git 仓库

在这一部分,我们将看到如何通过以下方式正确保护我们的 Git 仓库:

  • 设置我们的智能合约的代码所有者

  • 保护主分支

  • 配置 Git 进行提交签名和验证

  • 通过提交拉取请求来测试该过程

设置我们的智能合约的代码所有者

我们将首先为我们的智能合约定义代码所有者。

理想情况下,在一个大型的联盟中,代码所有者不应该是修改代码的同一组。记住,这些步骤旨在加强对网络的信任。

代码所有者在名为 CODEOWNERS 的文件中定义,该文件可以位于根目录或.Github目录中。GitHub 允许我们根据文件模式定义不同的代码所有者,因此虽然我们可以非常有创意,但我们将专注于我们 Hyperledger composer 项目的一些构件:

  • package.json:由于它控制了构建和打包过程,这代表了一个重要的控制文件。

  • header.txt:这包含许可证。因此,您可能希望有一组特定的人来监督这个(考虑律师)。

  • JavaScript 文件:这包含了智能合约的核心逻辑。根据复杂性,这可以根据文件进一步细分,但我们将保持在一个较高的水平上。

  • *.cto 文件:这应与 JavaScript 的所有者对齐。

  • *.acl 文件:这应与 JavaScript 的所有者对齐。

  • *.qry 文件:这应与 JavaScript 的所有者对齐。

  • *.md 文件:这代表了智能合约的文档。根据范围,这可以与 JavaScript 的相同所有者对齐,也可以是不同的一组人。

CODEOWNERS 的示例内容

以下是基于本书作者的一组关于 CODEOWNERS 的基本规则。随意根据你的团队进行调整。这里需要注意的重要一点是,最后匹配的模式将用于识别需要执行审查的所有者。因此,我们必须注意规则的顺序:

# In this example, documentation and Header.txt are part # of the default match. Default owners if nothing else 
# matches.
*       @ldesrosi
# Code related should be validated by Rama.  
# JavaScripts files could have been separated 
# into tests versus logic by using folder's structure
*.qry   @rama
*.acl   @rama
*.cto   @rama
*.js    @rama
# Package.json should be reviewed by everyone
package.json    @ldesrosi @rama @ODOWDAIBM

与列出团队中的每个成员的规则相反,我们可以使用 GitHub 团队的概念来分配代码所有权。

定义了 CODEOWNERS 后,我们现在可以专注于将其提交到主分支。使用命令行提示,按照以下步骤进行:

  1. 导航到你的存储库克隆的位置。

  2. 创建一个名为.Github的新目录。

  3. 切换到新创建的目录

  4. 根据上一节中定义的内容创建 CODEOWNERS 文件。

  5. 提交新文件和目录:

        Git add -A
        Git commit -m "Setting initial code ownership."
  1. 将提交推送到主分支:
        Git push

保护主分支。

正如我们先前讨论的那样,由于主分支代表着智能合约的稳定版本,我们需要正确控制代码变更的引入方式。

现在,我们将配置我们的存储库,以确保只有拉取请求可以更改主分支的内容。为了实现这一点,第一步是打开浏览器并将其指向你的 Git 存储库。

一旦网页加载完成,请按照以下步骤进行:

  1. 查看 Git 页面的顶部标签,你应该能够找到设置选项卡。

  2. 一旦你点击它,一个侧边菜单应该出现在页面的左侧。

  3. 选择“分支”菜单项,你应该能够看到“受保护的分支”部分。

  4. 从下拉菜单中选择主分支。

这将打开包含我们需要设置以正确保护主分支的所有选项的页面。

内容应设置为以下内容:

图片

第一组选项,用红圈标出,确保每个对主分支的更改都通过拉取请求进行,并且只有代码所有者可以对最新的代码进行批准。

我们已经将此部分突出显示为红色,因为虽然这在团队工作中非常重要,但应该在我们的练习中禁用。基本上,GitHub 不会让您审核您自己的拉取请求,并会阻止您完成后续步骤。

第二组选项提供了在允许代码合并之前执行的checks的能力。我们将在下一节中很快添加其中一个检查。

最后一个选项还确保即使存储库的管理员在修改代码时也需要遵循拉取请求的过程。

配置 Git 进行提交签名和验证

此时,我们已经保护了我们的 Git 分支,并确定了谁应该审查代码更改。我们还知道签署提交是开发人员证明他们是代码更改的作者的好方法。然而,除非每个人都签署他们的提交,否则您如何确定未签署的提交是有效的呢?

幸运的是,有一些正在出现的 GitHub 应用程序来解决这个问题。我们将使用一个名为probot-gpg的应用程序,可在probot.Github.io/apps/gpg/找到。

通过浏览器导航到该页面,您将能够点击安装按钮。您将被带到一个页面,允许您选择要允许应用程序选择的存储库。在我们的情况下,我们将选择yourID/trading-smart-contract/存储库。点击安装,应用程序将被授予对您存储库的访问权限。

在您的本地工作站上配置 GPG

为了确保一切都运行良好,我们现在将在我们的本地工作站上设置 GPG,并通过提交拉取请求来测试我们的存储库。在这一部分,我们将执行以下操作:

  • 安装 GPG 并生成我们的一组gpg公钥和私钥

  • 在我们的 GitHub 配置文件中导入我们的gpg公钥

  • 提交一个带有签名的拉取请求到主分支

gpg的客户端应用程序可以在www.gnupg.org网站上找到。从网站上,您可以下载源代码或预编译的二进制文件。根据您的操作系统和选择的选项(源代码或二进制文件),按照网站上提供的说明进行操作并安装客户端。

为了配置系统使用gpg密钥签署我们的 Git 提交,我们需要执行以下操作:

  1. 生成一个gpg密钥

  2. 导出公钥

  3. 在我们的 Git 中导入公钥

  4. 配置我们的 Git 客户端以使用我们的gpg密钥

要开始,请打开终端并键入以下命令:

gpg --full-generate-key

gpg工具现在将询问有关密钥特性的几个问题:

  • 密钥类型:选择默认(RSA 和 RSA)

  • 密钥大小:选择最大尺寸(4,096)

  • 密钥有效期:确保密钥不会过期

提供了与密钥相关联的身份信息后,gpg工具将询问与密钥相关联的身份信息:

  • 真实姓名

  • 电子邮件

  • 评论:您可能想使用评论框来指示此身份的目的(签署 GitHub 提交)

确保电子邮件与您的 GitHub 配置文件中的条目匹配,否则系统将无法将身份与提交进行协调。 请记住,对于 GitHub 来说,大小写很重要:yourID@email.com 和 yourID@email.com不是相同的电子邮件。

最后,工具会要求输入密码来保护私钥,并要求您通过移动鼠标来生成熵。 几秒钟后,您应该会看到以下输出:

gpg: key 3C27847E83EA997D marked as ultimately trusted
gpg: directory '/Users/yourID/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/Users/yourID/.gnupg/openpgp-revocs.d/962F9129F27847E83EA997D.rev'
public and secret key created and signed.
pub   rsa4096 2018-02-03 [SC]
      962F9129FC0B77E83EA997D
uid    Your Name (GitHub Signing Identity) <yourID@email.com>
sub   rsa4096 2018-02-03 [E]

创建了gpg之后,我们现在需要以 GitHub 能够理解的格式导出该密钥。 为此,我们运行以下命令:

gpg --armor --export <<email-you-use-to-generate-the-key>>

工具将在控制台直接输出公钥,并应如下所示:

-----BEGIN PGP PUBLIC KEY BLOCK-----mQINBFp1oSYBEACtkVIlfGR5ifhVuYUCruZ03NglnCmrlVp9Nc417qUxgigYcwYZ
[…]
vPF4Gvj2O/l+95LfI3QAH6pYOtU8ghe9a4E=
-----END PGP PUBLIC KEY BLOCK-----

将整个密钥复制到剪贴板上,包括标头,并使用您的浏览器转到您的 GitHub 配置文件,并从左侧菜单中选择SSH 和 GPG keys 选项卡。

您应该会看到两个部分—SSH 和 GPG。 单击“New GPG Key”按钮,并粘贴剪贴板中的内容到显示的输入字段中。 最后,单击“Add GPG Key”按钮,如果一切顺利,GitHub 应该显示类似的条目:

注意并复制密钥 ID 到剪贴板。 我们将重复使用该密钥来配置我们的 Git 客户端。

回到控制台,键入以下命令:

git config --global user.signingkey 3C27847E83EA997D

此时,您应该已经拥有完全配置的流水线和受保护的 Git 存储库。 我们现在准备开始测试我们的配置。

为了简化下一节中的测试步骤,我们还没有在 Git 客户端中激活gpg签名配置。 我们将在下一节中激活它。

测试端到端流程

所有配置都完成后,我们将通过一个简单的场景来测试我们的配置,并确保一切顺利运行。

场景将包括解决添加新交易的需求。 为了提供这个新功能,我们将执行以下步骤/测试:

  1. 为我们的业务网络创建一个新的交易。 完成编码后,我们将尝试执行以下操作:

    1. 直接向主分支推送一个提交

    2. 提交一个未签名的提交的拉取请求

  2. 添加测试用例以覆盖我们的新交易:

    1. 修改我们的提交以进行签名

    2. 添加我们的测试用例并提交另一个已签名的提交

  3. 发布业务网络的新版本

    1. 将拉取请求合并到主分支

    2. 创建一个新的发布版本并检查 BNA 是否已发布

创建一个新的交易

为了我们的测试目的,我们将使新交易相对简单:我们的交易将合并两个资产为一个,同时增加它们的价值。

要声明新交易,我们将编辑模型文件并添加此新声明:

transaction MergeAssets {
--> Asset mergeFrom
--> Asset mergeTo
}

定义创建完成后,让我们在/lib/logic.js文件中添加逻辑:

/**
  * Sample transaction
  * @param {org.example.biznet.MergeAssets} tx
  * @transaction
  */
function onMergeAssets(tx) {
  var assetRegistry;
  var mergeFromAsset = tx.mergeFrom;
  var mergeToAsset = tx.mergeTo;
  mergeToAsset.value += tx.mergeFrom.value;

  return getAssetRegistry('org.example.biznet.SampleAsset')
    .then(function(ar) {
      assetRegistry = ar;
      return assetRegistry.update(mergeToAsset);
    })
    .then(function() {
      return assetRegistry.remove(mergeFromAsset);
    });
}

就是这样!当然,有些人可能会说我们没有遵循良好的方法论——这段代码的单元测试在哪里?让我们继续。别担心,这都是计划的一部分!

直接将提交推送到主分支

完成代码修改后,让我们尝试将源代码添加到 Git 存储库中。为此,我们将执行以下步骤:

  1. 导航到存储库克隆的位置

  2. 提交新文件和目录:

git add -A 
git commit -m "Testing master branch protection."
  1. 将提交推送到主分支:
git push

push命令应该失败,并显示错误消息,例如以下内容:

$ git push
Counting objects: 3, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 367 bytes | 367.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: error: GH006: Protected branch update failed for refs/heads/master.
remote: error: Waiting on code owner review from ldesrosi.
To https://github.com/HyperledgerHandsOn/trade-finance-logistics.git
 ! [remote rejected] master -> master (protected branch hook declined)
error: failed to push some refs to 'https://Github.com/yourID/trading-smart-contract.Git'

如果你收到类似的消息,说明你走在正确的道路上。如果push命令成功,你应该回到保护主分支部分。

使用未签名的提交提交拉取请求

继续我们之前的尝试,我们知道我们需要一个单独的分支来存储我们的工作,然后我们才能向主分支提交拉取请求。现在我们已经提交了一个更改,我们需要小心不要丢失我们的工作。我们要做的第一件事就是通过运行以下命令撤销我们的提交:

git reset HEAD^

为了保存我们的工作,我们将使用 Git 中一个很好的功能,它会暂时存储我们的工作:

git stash

保存我们的修改后,我们可以通过运行Git checkout命令在本地创建新分支。对于那些对 Git 不太熟悉的人,-b选项指定了新分支的名称,最后一个参数表示新分支是基于主分支的:

git checkout -b Feat-1 origin/master

在本地创建了新分支后,我们可以使用以下命令恢复我们的修改:

git stash pop

最后,我们可以提交我们的代码并将其推送到Feat-1分支:

git add -A
git commit -m "Testing commit signing."
git push

执行这些命令后,我们的Feat-1分支现在应该包含额外的交易代码。让我们切换到我们的浏览器上,在 GitHub 上创建拉取请求:

  1. 选择Feat-1分支,然后点击“New pull request”按钮

  2. 确保分支可以合并,然后点击创建拉取请求按钮

下一个屏幕上的结果将显示拉取请求未通过gpg检查和 Travis 构建。构建的详细信息应该显示测试覆盖率不足以达到我们之前设定的阈值:

如果你得到相同的结果,那么你做得很好!如果你的拉取请求没有出现这样的检查失败,请确保查看为提交签名和验证配置 Git部分。

现在我们将修正我们的构建并添加必要的测试!

添加测试用例

在添加我们的测试用例之前,我们将首先启用gpg签名,并用签名修改我们之前的提交。这应该让我们走上健康拉取请求的正确道路。

使用签名提交提交拉取请求

现在我们可以完成并激活我们的gpg签名了。在控制台中,输入以下命令:

git config --global commit.gpgsign true

现在,不必再创建一个单独的分支并再次执行相同步骤,我们将简单地amend我们的commit并为其添加我们的签名:

git commit --amend -S -m "Testing commit signing."

当尝试修改您的提交时,您可能会遇到以下错误:

error: gpg failed to sign the data

fatal: failed to write commit object

如果需要,您可能需要设置以下环境变量:

export GPG_TTY=$(tty)

该命令将委托给 GPG 进行签名,然后您应该被要求输入您的gpg密码。完成后,我们可以使用以下命令将更改推送到我们的测试分支:

git push origin test --force

我们需要--force我们的更改,因为我们只是修改我们的提交。

如果您返回浏览器并查看拉取请求,您现在应该看到类似以下的内容:

我们应该已经解决了一个问题——提交的签名。如果您得到了相同的结果,现在您知道一切都配置正确了。您可以继续专注于通过为新交易添加一个测试来修正测试覆盖率。

添加合并资产单元测试

让我们将这个额外测试用例的内容添加到test/logic.js文件中:

 describe('MergeAssets()', () => {
 it('should change the value to ' + assetType + ' to 200', () => {
 const factory = businessNetworkConnection.getBusinessNetwork().getFactory();
 // Create the asset 1
 const asset1 = factory.newResource(namespace, assetType, 'ASSET_001');
 asset1.value = 100;
 // Create the asset 2
 const asset2 = factory.newResource(namespace, assetType, 'ASSET_002');
 asset2.value = 100;

 // Create a transaction to change the asset's value property
 const mergeAssetTx = factory.newTransaction(namespace, 'MergeAssets');
 mergeAssetTx.mergeFrom = factory.newRelationship(namespace, assetType, asset1.$identifier);
 mergeAssetTx.mergeTo = factory.newRelationship(namespace, assetType, asset2.$identifier);

 let assetRegistry;
 return businessNetworkConnection.getAssetRegistry(namespace + '.' + assetType).then(registry => {
   assetRegistry = registry;
   // Add the asset to the appropriate asset registry
   return assetRegistry.add(asset1);
 }).then(() => {
   return assetRegistry.add(asset2);
 }).then(() => {
   // Submit the transaction
   return businessNetworkConnection.submitTransaction(mergeAssetTx);
 }).then(() => {
  // Get the asset
  return assetRegistry.get(asset2.$identifier);
 }).then(newAsset => {
  // Assert that the asset has the new value property
  newAsset.value.should.equal(200);
 });
});
});

我们不会详细介绍此测试用例,因为在前几章中已经涵盖过。但是,如果想查看测试是否成功完成,请运行以下命令:

npm test

让我们提交这个新测试到 Git:

git add -A
git commit -S -m "Added new test case"
git push origin Feat-

这应该会自动触发我们的构建流程,该流程应该成功完成,并将我们的拉取请求留在以下状态:

这将使您能够合并拉取请求。点击合并请求按钮,确认合并,并准备创建您的第一个发布版本!

如果您的拉取请求不是绿色的并要求代码审查,您可能忘记了取消选中"在合并前需要拉取请求审查"选项,正如"保护主分支"章节所述。

发布新版本

现在我们已经准备好发布我们的新业务网络归档。转到您的网页浏览器,导航到 Git 存储库的 Code 标签。您应该会在顶部导航栏中看到"x releases"选项,如下截图所示:

点击发布然后点击"起草新发布"按钮。填写表单,类似于以下示例:

在表单底部点击"发布版本"按钮。这应该会再次触发您的构建流程,几分钟后,您应该会看到与您的发布相关联的资产列表中附有 BNA 文件:

干得好!我们已经使用 Travis CI 和 GitHub 配置了完整的流水线,并探讨了如何正确签署和保护我们的智能合约。

我们的最后一步现在将是看看各种网络参与者如何自动检索业务网络归档BNA)并deploy智能合约更新。

更新网络

随着 BNA 文件发布并标记为发布,我们现在将查看安装/更新财团中业务网络的过程。更具体地说,我们将查看以下步骤:

  • 发布通知

  • 业务网络更新

通知财团

有一些方法和技术可以应用以确保每个组织都收到业务网络准备更新的通知。

唯一确定的是手动通知不是一个选项;随着智能合约和参与者数量的增长,您需要一个可靠的通知过程。

以下图示了在交付新版本后部署业务网络的潜在过程:

正如我们之前讨论过的那样,我们不分发 BNA,因为这会为某人篡改归档提供机会。相反,通知只是通知每个组织存在新版本,并让财团检索和deploy归档。

这实际上是发布监听器概念正在做的事情:监听通知,然后向 GitHub 发出请求,以检索新发布的归档。

发布监听器是一个概念,如果财团决定遵循这种方法,就需要由财团实施。

不要寻找源代码—它不存在(尚未存在)。

发布监听器可以实现监听来自两个来源的事件:

  • GitHub Webhooks:通过提供发布监听器的 URL,可以配置 GitHub Webhooks 以在特定事件上发送 JSON 消息。在我们的情况下,将是Release事件。

  • Travis CI 通知:Travis CI 中也有类似 Webhook 的概念。还有其他机制,例如 Atom feed 和 Slack 集成,可能更适合您的团队。

机制的选择实际上取决于您的业务要求,但通常,使用 GitHub Webhooks 将是首选,因为它们由我们感兴趣的实际事件触发:智能合约的新版本发布。

即使有人向发布监听器发送错误通知,因为它只从 GitHub 检索发布的二进制文件,第三方也无法注入有害归档。

升级业务网络

此时,我们假设我们已收到通知,并且我们负责部署新版本。请记住,业务网络可以部署到多个频道中。因此,虽然不需要在每个对等体上部署 BNA,但对于期望运行这些交易的每个频道都需要部署。

我们的部署将包括两个简单步骤:

  1. 下载新版本

  2. 更新业务网络

下载新版本

鉴于我们刚刚发布了新版本,并且管道已将二进制文件添加到发布中,我们可以使用以下 curl 命令简单地下载归档文件:

curl https://Github.com/HyperledgerHandsOn/trade-finance-logistics/releases/download/v1.1.0/network.bna -L -o network.bna

-L 选项用于告诉 curl 去跟随任何重定向命令。执行此命令后,BNA 文件应该位于您的本地文件系统上。

更新业务网络

由于 BNA 内容实际上存储在世界状态中,因此可以从具有对管理证书访问权限的任何客户端提交业务网络更新。

因此,要更新网络,请提交以下命令:

composer network install -a ./network.bna -c <card-name>
composer network upgrade -n trade-finance-logistics -v 0.0.1 -c <card-name>

为了测试更新 BNA 的部署,请参考:github.com/HyperledgerHandsOn/trade-finance-logistics/tree/master/composer

请注意,其他依赖组件,如 REST 网关和应用程序,在生产部署中也需要考虑。

概要

希望本章对您理解如何使联盟围绕推广流程达成一致所需的挑战和考虑有所帮助。

持续交付流水线是提供给联盟速度的基本部分,消除手动流程,并确保每个组织可以在代码上线之前审查和批准代码更改。我们已经看到了一些关键事件,比如拉取请求和标记发布。

在本章过程中,您已经完成了完整的持续集成流水线配置,包括测试和发布业务网络存档。此外,我们已经看到了如何通过保护主分支和确保每个更改都受到组织关键参与者代码审查来保护生产就绪代码。我们还研究了如何使用 gpg 签名保留每个 Git 提交的来源。最后,我们审查了一种以可信方式部署更新的过程。

有一件事是肯定的:自动化是敏捷的关键—通过消除重复的手动任务并为我们修改代码提供结构,我们使组织能够更具敏捷性并快速响应,无论是对缺陷还是新需求。当然,本章仅是这种方法及其相关概念的一个小介绍;其中一些主题可能值得拥有自己的书籍。

第九章:区块链网络中的生活

您的 Fabric 网络现在应该已经设置并运行了通过智能合约连接不同实体并通过 Web 界面为用户提供服务的应用程序。此外,为了帮助您的开发人员和系统管理员维护代码、推送更新并管理网络配置,您应该建立了一个流程,该流程可以在保护措施的情况下进行系统测试和维护,而不会中断服务。

然而,这并不会是您应用程序的终极状态。需求和期望会不断发展,这对于涉及多个协作实体的应用程序尤其如此,每个实体在不同时间点都将具有不同的要求。此外,预计即使应用程序的性质和功能保持不变,软件本身也将不断变化和演变。最后,任何分布式服务型应用程序(这个描述可以应用于任何 Hyperledger Fabric 应用程序)都必须为终端用户的性质和数量随时间增加或减少做好准备,从而需要对硬件和软件资源分配进行更改。

在您的区块链应用程序的生命周期内,您将看到许多变化,需要对代码和配置进行更新。之前列出的变化类型并不是 Fabric 网络或甚至区块链一般所特有的,但我们将需要使用的机制以及选择这些机制的考虑因素非常具体。因此,这将是本章的主要,尽管不是唯一的焦点。我们将首先探讨您的 Fabric 应用程序可能需要进行修改的不同方式,通过示例代码和配置来说明具体的场景,并提供规划系统升级的指南。然后,我们将讨论应用程序和网络成员变化以及适用于行业规模区块链应用程序的相关考虑因素。在本章的后端,我们将深入了解系统维护:监控应用程序和系统资源的健康状况,并设计或升级系统以确保高性能。

本章将涵盖以下主题:

  • 修改或升级 Hyperledger Fabric 应用程序

  • Fabric 区块链和应用程序的生命周期

  • 将新组织添加到网络中

  • 链码逻辑的修改

  • 链码依赖升级

  • 背书策略更新

  • 系统监控和性能

  • 容器和应用程序的配置文件

  • 应用程序性能的衡量

修改或升级 Hyperledger Fabric 应用程序

第五章暴露网络资产和交易中提供的通用 Hyperledger Fabric 应用程序的设计,提供了关于其生命周期中可能需要的升级类型的提示。让我们来看看随着时间推移,Fabric 网络及其用户的需求如何发生变化的各种方式:

  • 软件更新:变更和升级是软件维护的重要组成部分。更频繁地,修改是为了修复错误、提高性能效率和解决安全漏洞(例如,想想 Windows Update 服务)。较少地,但几乎同样不可避免的是,必须对软件进行重大设计更改以应对未预料到的挑战。另外,考虑到大多数应用程序依赖于其他(第三方)软件,后者的任何升级都会触发前者的相应更改。可以把 Windows Service Packs 类比一下。

    在 Hyperledger Fabric 世界中,作为应用程序开发人员或系统管理员,你必须支持应用程序级别的升级和平台级别的升级。前者涉及错误修复和应用逻辑的更改,后者涉及底层 Fabric 软件的更改。软件更新流程是众所周知的,一些技术在第五章暴露网络资产和交易中已经讨论过;对于故障修复和常规维护,一些测试和可靠的故障转移技术同样适用。

如果你还记得我们的典型 Fabric 应用程序的 3 层架构,那么上层,包括中间件(使用 Fabric SDK)、Web 服务器和用户界面,通常由单个组织控制,因此可以通过该组织内部制定的流程进行更新。但是,正如我们在第八章中所看到的,区块链网络中的敏捷性,智能合约或链代码是一个特例,因为它是所有参与组织共同同意并开发的一段软件。因此,对链代码的任何更新也必须是基于共识的,并且不像只需测试后推送更新那么简单。我们将在本节稍后的示例中描述链代码升级过程。

最后,Fabric 软件的升级可能会影响功能和数据,因此必须小心进行。我们将在本节后面描述机制和风险。

  • 资源需求变更:在应用程序生命周期的开始阶段分配的资源,就像应用程序代码一样,不太可能满足不断变化的用户需求。随着时间的推移,你的应用程序很可能会接收到越来越多的用户流量,而硬件的限制是没有任何软件改进可以弥补的。同样地,如果我们回顾一下 RAS 的要求(参见第五章暴露网络资产和交易),一个分布式应用程序的正常运行需要在系统资源之间实现冗余、故障转移和负载均衡。

    在 Fabric 的术语中,这意味着你可能需要向你的网络添加更多节点。您可能需要更多的同行来处理交易背书请求,整个网络可能需要更多的排序节点来处理当前瓶颈的排序服务的负载和平衡(另一方面,如果流量太小,可以删除节点以节省成本)。否则,您可能需要额外的同行节点在组织中仅用于背书证实或额外的排序节点以获得更可靠的分布式共识(尽管这可能会带来性能成本)。无论添加和删除网络中的节点的原因是什么,作为 Fabric 开发人员或管理员,您必须支持这种升级,我们将在本节的后面看到如何做到这一点。

  • 更改用户成员资格:除了用户流量的变化外,我们必须为系统访问权限随时间的变化做好准备。在 Fabric 中,这意味着添加或移除可发送请求到应用程序并查看应用程序状态的用户或客户端。在一个组织内,总会有需要添加或移除被允许访问区块链的用户,并提升或降低对现有用户授予的特权。我们已经在第五章中讨论了成员创建和授权的例子,暴露网络资产和交易,在本节的后面,我们将看到如何使用运行时配置更新通道策略。

  • 更改应用程序策略:Hyperledger Fabric 应用程序中的交易(链码调用)必须满足背书策略,这些策略由参与者共同决定。这样的策略会随着时间的推移出于多种不同的原因而改变,包括性能(我们将在本章的后部分讨论)。例如,用于批准每个组织成员的背书策略可能会放宽为只需要两个组织的背书要求。另一方面,策略也可以变得更为严格,以克服区块链参与者之间缺乏信任的问题。Fabric 提供的修改背书策略的机制将通过后面本节中的示例进行讨论。

  • 更改网络配置:最后,总会有对区块链网络本身进行修改以满足增强期望的需要。随着时间的推移,更多组织可能希望参与应用,特别是如果应用的初始版本证明了其价值。一些组织可能也会因为各种原因而想要离开。即使在一个给定的组织中,也可能需要扩展或重新平衡用于特定应用的资源。尽管大多数分布式应用都面临这些需要增强和资源重新配置的情况,但由于其独特的性质,区块链应用有特殊的需求。请记住,区块链是一个共享分类账,必须由每个参与的网络对等方使用共同约定的规则进行验证和接受。因此,网络的结构和属性本身必须得到共同的认可并记录在分类账上。在 Hyperledger Fabric 术语中,一个应用是建立在一个或多个通道(区块链实例)上的,其规则和内容对应用参与者是私有的。因此,网络中的任何变化都需要将配置更改应用到一个通道上。添加具有自己对等节点集的新组织或删除一个组织将需要通道重新配置,如对等方或 orderer 地址的更改,以及组织内锚定对等方的选择。其他示例包括通道的核心属性,例如区块大小和超时;用于读取、写入和管理操作的通道访问策略;哈希机制;以及用于排序服务的共识模式。虽然对通道配置用例的全面覆盖超出了本章的范围,但我们将在本节后面的示例中看到如何通过示例推进 Fabric 网络中的重新配置。

总结,对 Fabric 应用的更改不仅需要常规的软件维护程序,包括代码和配置更改、测试和更新,还需要适用于区块链的基于共识的操作。在本节的其余部分,我们将重点介绍 Hyperledger Fabric 支持的两种主要应用更新模式。

  • 通道配置更新:这涵盖了组织的添加和移除,资源更改(对等方和 orderer 节点的添加、移除或修改),通道属性的更改(策略和区块创建规则,哈希和共识机制)。

  • 智能合约更新:这涵盖了对链码和交易背书策略的更改。

后面,我们将简要介绍对 Fabric 平台软件的升级。

要实现这种升级,我们需要通过适当的机制来扩展我们从第 3 到 7 章创建的应用程序和工具集。幸运的是,Fabric 平台的设计者已经预见到了我们在本章讨论过的这些演进类型,并且我们用来构建交易应用程序初始版本的 SDK(参见第五章暴露网络资产和交易)提供了构建这些机制所需的功能。在我们转向实施细节之前,让我们重温 Fabric 事务管道并修改它以包含更新。

Fabric 区块链和应用程序生命周期

考虑我们已经实现的作为 Fabric 应用程序的交易场景,其中在修改以包含通道和链码更新时,所示阶段在图 5.3:区块链应用程序创建和操作阶段(见第五章暴露网络资产和交易)中有所说明,在图 9.1:区块链应用程序生命周期阶段中呈现(为了方便起见,我们在图表中省略了账本和事件发出,因为这些并不是解释应用程序阶段所必需的):

图 9.1:区块链应用程序生命周期阶段

这个图表并不是要对 Fabric 应用程序的所有可能阶段进行详尽的表示,而是对最显著的阶段进行表示。

正如我们所看到的,某些类型的更新需要比其他类型的更新更多的操作。任何新增的认可对等节点,无论是在现有组织内还是在新增加的组织中,都需要将这些对等节点明确地加入到通道中,并在这些对等节点上安装当前版本的链码。这些对等节点不需要明确的实例化;网络对等体之间的八卦协议最终将在新增加的对等体上同步共享账本的最新副本。然而,智能合约的修改过程将需要在对等节点上安装新版本的链码之后进行明确的通道范围升级。这种升级步骤等同于原始实例化,尽管它作用于当前状态而不是空账本上。在某些情况下,链码升级和背书政策可能会紧随通道重新配置以添加新组织;在这种情况下,可以跳过在新对等体上安装当前版本的链码,并直接安装升级后的链码版本。我们将描述如何扩展我们的交易应用程序以实现这样一个系统升级,在接下来的小节中。

在继续之前,让我们了解系统在不同类型更改时的区块链外观。图 9.2 说明了区块链的各个部分,添加了不同类型的区块以进行不同的应用操作:

图 9.2:具有配置块、包含部署事务的区块以及常规链码交易的区块的区块链部分

正如我们所看到的,我们的区块链(或者换句话说,共享账本事务日志)从创世区块开始(通道上的第一个配置块),其中包含了通道的初始配置。下一步是部署和实例化链码的初始版本,随后是常规操作(链码调用)。在某个时刻,可以添加具有对等节点的新组织,这导致另一个配置块被添加到链中,覆盖先前的配置块。类似地,可以创建和升级链码的新版本,并记录升级过程在一个区块中。在这些配置和部署区块之间,可以发生常规链码交易,并且根据配置的区块大小,一个或多个交易可以捆绑在一个区块中并附加到链中。现在让我们看看如何增强我们的交易应用程序,以实现我们迄今讨论过的功能。

通道配置更新

正如本章前面提到的,通道配置可能需要更改的原因有很多。由于通道行为完全由其配置所决定,并且任何更新都被记录在区块链上,因此覆盖先前的配置,这是一个非常敏感的操作,必须限制在特权用户之内,就像我们应用程序创建步骤的初始部分一样,比如通道创建和加入(参见第五章公开网络资产和事务)。本书不会详尽讨论和演示通道配置更改的细节,但我们将展示更新机制以及一种将这些机制包装在我们应用程序中的方法;这种机制和过程可以应用于任何配置更改。

为了演示,我们将使用一个常见情况,即需要向应用程序添加新组织和对等体。考虑到我们的贸易情景,迄今为止,出口商及其银行共享一个组织,由后者维护其 MSP 和对等体。进口商及其银行也属于同一个组织,其逻辑是银行有更多的动机和资源来维护对等体和 MSP。但是这种逻辑可能不会持续下去。假设我们的出口商,最初是一个小规模运营商,随着时间的推移获得了更高的利润和更高的诚信度以及质量。现在,作为原材料的大规模出口商,在市场上有着巨大的现金储备和影响力,它有动机作为对等体而不是银行的依赖加入区块链贸易网络。它还在不同的银行维护银行账户,因此有需要和潜力同时参与多个区块链(通道)。它希望继续参与贸易通道和封装应用程序,但是在自己的组织中运行自己的 MSP 和自己的网络对等体,独立于银行。

我们必须创建的结果网络如图 9.3:具有组织、MSP 和对等体的增强贸易网络,适用于出口商(或出口实体) 所示:

图 9.3:具有组织、MSP 和对等体的增强贸易网络,适用于出口商(或出口实体)

我们将新组织称为 出口实体组织,其 MSP 为 ExportingEntityOrgMSP,对等体为出口实体。这是因为在我们的网络中,名为出口商、ExporterOrgExporterOrgMSP 的名称已被占用,用于表示出口商的银行;新组织和对等体必须具有唯一名称。

向网络添加新组织的先决条件

您升级网络所需的工具与在第三章中使用的工具类似,用业务场景设定舞台

  1. 克隆 Fabric 源代码存储库:

    1. 运行 make docker 以构建对等体和排序者的 Docker 镜像。

    2. 运行 make configtxlator 生成运行本节描述的网络创建命令所需的工具(当我们转向中间件代码时,我们将使用 configtxlator

  2. 此外,我们假设读者按照第三章中描述的程序进行操作,用业务场景设定舞台,并且已经为之前的 4 个组织网络创建了通道配置和加密材料文件。

如果你还记得,在第三章中,通过业务场景设定舞台,我们为四个组织创建了通道构件和加密材料,包括起始块、初始通道配置、每个组织的锚点对等配置,以及涉及对等方、客户端和 MSP 的所有网络操作的证书和签名密钥。这些配置分别在网络文件夹中的configtx.yamlcrypto-config.yaml中定义,并使用configtxgencryptogen工具处理。显然,这些配置必须被修改以添加一个新组织,但是更改配置可能会很混乱。好消息是,我们可以通过创建额外的配置文件并保持原始文件不变来逐步增加我们的网络。这样,管理员就可以轻松跟踪组织结构和资源的演变。我们的增量配置文件定义在network/add_org/文件夹中。

生成网络加密材料

crypto-config.yaml文件只包含关于新组织的信息,足以生成证书和签名密钥:

PeerOrgs: 
  # ExportingEntityOrg 
  - Name: ExportingEntityOrg 
    Domain: exportingentityorg.trade.com 
    EnableNodeOUs: true 
    Template: 
      Count: 1 
    Users: 
     Count: 1 

正如我们所见,规范与我们为初始的四个组织定义的规范相同,只是 MSP 名称和组织域反映了导出实体组织的性质。要仅为这个组织生成加密材料,可以像第五章中那样运行cryptogen命令,但这次使用add_orgs文件夹中定义的配置文件:

cryptogen generate --config=./add_org/crypto-config.yaml 

输出保存到crypto-config/peerOrganizations,你将看到一个名为exportingentityorg.trade.com的文件夹,除了现有组织的文件夹。这个文件夹包含我们新组织的密钥和证书。

生成通道构件

同样,configtx.yaml仅包含在组织部分中导出实体组织的规范,如下所示:

Organizations: 
  - &ExportingEntityOrg 
    Name: ExportingEntityOrgMSP 
    ID: ExportingEntityOrgMSP 
    MSPDir: ../crypto-config/peerOrganizations/exportingentityorg.trade.com/msp 
    AnchorPeers: 
      - Host: peer0.exportingentityorg.trade.com 
        Port: 7051

这个规范本质上复制了每个其他组织和对等方的规范;只是名称和路径被修改以识别和设置新组织(这假设当前目录中已经生成了一个crypto-config文件夹)。要构建增量通道配置,运行以下命令:

FABRIC_CFG_PATH=$PWD/add_org && configtxgen -printOrg ExportingEntityOrgMSP > ./channel-artifacts/exportingEntityOrg.json 

在这里,我们遇到了与第三章中所遵循的程序的第一个不同之处,通过业务场景设定舞台;我们不再为配置块、锚定节点等构建单独的文件,而是构建一个包含所有相关信息的 JSON 规范,包括管理员用户、CA 根、导出实体组织的 TLS 根的策略规范和证书,并将其保存到channel-artifacts文件夹中。在本节的后面,我们将在我们的通道配置更新过程中使用这个 JSON。

为了确保configtxgenadd_org目录中查找configtx.yaml,我们必须临时更改FABRIC_CFG_PATH环境变量。

在一个操作中生成配置和网络组件

你也可以使用trade.sh脚本执行所有前面的操作。只需从network文件夹内运行以下命令:

./trade.sh createneworg

通道名称默认假设为tradechannel

此命令除了创建加密材料和通道配置外,还为add_org/docker-compose-exportingEntityOrg.yaml中的新组织生成了一个 docker-compose 配置。它运行以下服务:

  • 一个导出实体组织的 Fabric peer 实例

  • 一个导出实体组织的 Fabric CA 实例

规范和依赖项与我们在第三章中遇到的docker-compose-e2e.yaml类似,通过业务场景设定舞台,如下所示:

services: 
  exportingentity-ca: 
    image: hyperledger/fabric-ca:$IMAGE_TAG 
    environment: 
      - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server 
      - FABRIC_CA_SERVER_CA_NAME=ca-exportingentityorg 
      - FABRIC_CA_SERVER_TLS_ENABLED=true 
      - FABRIC_CA_SERVER_TLS_CERTFILE=/etc/hyperledger/fabric-ca-server-config/ca.exportingentityorg.trade.com-cert.pem 
      - FABRIC_CA_SERVER_TLS_KEYFILE=/etc/hyperledger/fabric-ca-server-config/fc435ccfdaf5d67251bd850a8620cde6d97a7732f89170167a02970c754e5450_sk 
    ports: 
      - "11054:7054" 
    command: sh -c 'fabric-ca-server start --ca.certfile /etc/hyperledger/fabric-ca-server-config/ca.exportingentityorg.trade.com-cert.pem --ca.keyfile /etc/hyperledger/fabric-ca-server-config/fc435ccfdaf5d67251bd850a8620cde6d97a7732f89170167a02970c754e5450_sk -b admin:adminpw -d' 
    volumes: 
      - ../crypto-config/peerOrganizations/exportingentityorg.trade.com/ca/:/etc/hyperledger/fabric-ca-server-config 
    container_name: ca_peerExportingEntityOrg 
    networks: 
      - trade 

  peer0.exportingentityorg.trade.com: 
    container_name: peer0.exportingentityorg.trade.com 
    extends: 
      file: ../base/peer-base.yaml 
      service: peer-base 
    environment: 
      - CORE_PEER_ID=peer0.exportingentityorg.trade.com 
      - CORE_PEER_ADDRESS=peer0.exportingentityorg.trade.com:7051 
      - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.exportingentityorg.trade.com:7051 
      - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.exportingentityorg.trade.com:7051 
      - CORE_PEER_LOCALMSPID=ExportingEntityOrgMSP 
    volumes: 
        - /var/run/:/host/var/run/ 
        - ../crypto-config/peerOrganizations/exportingentityorg.trade.com/peers/peer0.exportingentityorg.trade.com/msp:/etc/hyperledger/fabric/msp 
        - ../crypto-config/peerOrganizations/exportingentityorg.trade.com/peers/peer0.exportingentityorg.trade.com/tls:/etc/hyperledger/fabric/tls 
        - peer0.exportingentityorg.trade.com:/var/hyperledger/production 
    ports: 
      - 11051:7051 
      - 11053:7053 
      - 11055:6060 
    networks: 
      - trade 

此文件是使用模板 YAMLadd_org/docker-compose-exportingEntityOrg-template.yaml生成的,其中 CA 密钥文件名(由变量EXPORTINGENTITY_CA_PRIVATE_KEY表示)在FABRIC_CA_SERVER_TLS_KEYFILE和命令中均替换为crypto-config/peerOrganizations/exportingentityorg.trade.com/ca/中的秘密密钥文件名,在我们的例子中是fc435ccfdaf5d67251bd850a8620cde6d97a7732f89170167a02970c754e5450_sk

此关键文件名将随着每次cryptogen工具的执行实例而变化。

此外,请注意环境变量exportingentity-ca:FABRIC_CA_SERVER_TLS_CERTFILE中的证书文件名和卷部分中指定的路径与使用cryptogen生成的内容匹配。ID、主机名和端口值与congfigtx.yaml文件中指定的内容匹配。最后,我们确保容器端口映射到唯一端口(在 11,000s 范围内),以避免与旧组织的 peer 和 MSP 的容器暴露的端口发生冲突。

启动新组织的网络组件

要启动我们新组织的 peer 和 MSP,只需运行以下命令:

docker-compose -f add_org/docker-compose-exportingEntityOrg.yaml up

你可以将此作为后台进程运行,并将标准输出重定向到日志文件中。否则,你将看到各容器启动并从每个容器在控制台上显示的日志。从另一个终端窗口,如果你运行docker ps -a,你将看到以下两个额外的容器:

CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES 
02343f585218    hyperledger/fabric-ca:latest    "sh -c 'fabric-ca-se..."    16 seconds ago    Up 16 seconds    0.0.0.0:11054->7054/tcp    ca_peerExportingEntityOrg 
a439ea7364a8    hyperledger/fabric-peer:latest    "peer node start"    16 seconds ago    Up 16 seconds    0.0.0.0:11055->6060/tcp, 0.0.0.0:11051->7051/tcp, 0.0.0.0:11053->7053/tcp    peer0.exportingentityorg.trade.com 

你可以使用存储库中的脚本文件启动网络,方法如下:

./trade.sh startneworg 

通道名称默认为tradechannel

这将在后台启动容器,并且你可以在logs/network-neworg.log中查看日志。现在我们的网络有 5 个对等点,5 个 MSP 和一个运行在独立容器中的订购者。我们现在准备开始重新配置通道以接受新组织的过程。

要停止与出口实体组织相关的容器,只需运行./trade.sh stopneworg

这不会清除所有卷(运行docker volume is to check)因为初始的 4 个组织网络的容器仍在运行。只有在你启动整个网络之后,才能清除剩余的活动卷。

更新通道配置

现在我们将把注意力转向中间件。在第五章公开网络资产和交易中,当我们创建tradechannel时,区块链是使用configtxgen工具初始化的创世区块。创世区块恰好是通道的第一个配置块。后续通道配置更改涉及将新的配置块附加到通道中,每个都有唯一的版本,并且最新的配置块将覆盖先前的配置块。在升级场景中,将覆盖创世区块中的配置,因为我们假设自从我们的通道创建并在第五章公开网络资产和交易中准备使用以来,没有进行其他更改。

升级通道配置的逻辑位于我们代码存储库中middleware文件夹中的upgrade-channel.js中,并且基于 Fabric SDK Node API。还需要满足以下先决条件:

  • configtxlator:这是在本章的早些时候从 Fabric 源代码构建的。请确保它在你的系统路径中。

  • jq:这是一个命令行 JSON 处理器,用于创建和解析 JSON 对象。在 Ubuntu 系统上,你可以使用apt-get install jq来安装。请确保它也在你的系统路径中。

upgradeChannel 函数中,有用于创建客户端和通道对象的样板代码,读者应该已经熟悉。通道升级过程需要从每个现有组织的管理用户(在我们的网络中为 4 个)收集对新配置的签名,就像通道创建过程中一样。但是在生成和收集签名之前需要许多额外的步骤。首先,我们需要从订购者获取最新的配置块。我们在代码中使用以下函数调用执行此操作:

channel.getChannelConfigFromOrderer(); 

这将返回一个名为 configuration_block 的区块,其中的 config 字段包含当前的通道配置。可以从配置的 sequence 字段中提取此配置的版本,方法如下:configuration_block.config.sequence。完整的配置规范在 Fabric 源代码中定义为一个 protobuf(common.Config),读者可以自行查阅。

在代码中,我们现在创建一个文件夹来存储随后步骤中将创建的临时文件。这些文件是使用 configtxlator 工具创建的,我们在 Fabric SDK Node API 中没有等效的 API 函数时使用该工具:

if(!fs.existsSync('./tmp/')) {
  fs.mkdirSync('./tmp');
}

获得配置后,我们需要将其以 protobuf 格式转储到文件中:

fs.writeFileSync('./tmp/config.pb', configuration_block.config.toBuffer()); 

接下来,我们需要使用 configtxlator 将此配置解码为 JSON 格式。我们这样做纯粹是为了方便,因为解析 JSON 并将我们的预期配置更改应用于其中更容易:

cproc.execSync('configtxlator proto_decode --input ./tmp/config.pb --type common.Config | jq . > ./tmp/config.json');

这将导致在 temporary 文件夹中创建一个名为 config.json 的文件。如果您查看此文件的内容,您将看到通道的基础配置结构以及可以更新的各种属性。

现在我们需要将新的(导出实体)组织的配置附加到其中。后者包含在文件 exportingEntityOrg.json 中,此文件在本节前面使用 configtxgen 工具创建,并保存到 network/channel-artifacts。我们使用 jq 工具如下创建新的附加配置 modified_config.json

cproc.execSync('jq -s \'.[0] * {"channel_group":{"groups":{"Application":{"groups": {"ExportingEntityOrgMSP":.[1]}}}}}\' ./tmp/config.json ../network/channel-artifacts/exportingEntityOrg.json > ./tmp/modified_config.json');

如果您查看 modified_config.json 的内容,您会发现它在结构上与 config.json 非常相似;区别在于它包含了 5 个组织的定义,而 config.json 只包含了 4 个。我们现在将此新配置转换为 protobuf 格式(modified_config.pb),以便 configtxlator 可以处理它:

cproc.execSync('configtxlator proto_encode --input ./tmp/modified_config.json --type common.Config --output ./tmp/modified_config.pb'); 

请注意,我们使用了与从订购者获取的配置解码时相同的 protobuf 架构(common.Config)。

最后,我们将使用 configtxlator 计算原始和新配置 protobuf 之间的差异(或区别):

cproc.execSync('configtxlator compute_update --channel_id ' + channel_name + ' --original ./tmp/config.pb --updated ./tmp/modified_config.pb --output ./tmp/exportingEntityOrg_update.pb'); 

生成的 protobuf exportingEntityOrg_update.pb包含exportingentityOrg的完整定义和指向现有 4 个组织的指针。对于通道配置更新而言,这已经足够了,因为其他组织的完整定义已经包含在先前配置块中(在我们的示例中为创世块)。

现在我们所要做的就是读取增量配置并从现有的四个组织中获取管理员签名。这段代码类似于我们在通道创建阶段检查的代码:

config = fs.readFileSync('./tmp/exportingEntityOrg_update.pb'); 
var signature = client.signChannelConfig(config); 
signatures.push(signature); 

现在我们所需做的就是创建一个更新请求并将其发送给订购者:

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

请求结构可以包含配置或信封字段。后者具有common.Envelope的 protobuf 格式,并且是我们刚刚创建的配置的包装器。Fabric 订购者将接受任一。使用信封而不是配置留给读者作为练习。

要推送通道配置更新,只需运行:

node run-upgrade-channel.js 

请确保来自第五章暴露网络资产和交易的原始 4 组织网络正在运行,并且已经执行了通道创建步骤(有关示例,请参见middleware/createTradeApp.js)。

将新组织添加到网络中

新组织通过配置更新逻辑地添加到通道中。要将其实际添加到我们的交易网络并使其参与共享账本交易,我们需要:

  • 将出口实体组织的对等体加入到 tradechannel

  • 在新添加的对等方上安装当前版本的链代码

好消息是这里没有什么新的要做的。我们已经为这两个过程(分别是join-channel.js中的joinChannelinstall-chaincode.js中的installChaincode)实现了功能,并且我们只需要代表新组织的资源运行它们。

在运行这些步骤之前,我们必须增强中间件使用的网络配置。早期,我们在middleware文件夹中使用config.json表示 4 组织网络。现在,我们将使用同一文件夹中的config_upgrade.json替换它。该文件中唯一包含的是在trade-network中有一个额外属性exportingentityorg(这是中间件代码将识别我们的新组织的方式),如下所示:

"exportingentityorg": { 
  "name": "peerExportingEntityOrg", 
  "mspid": "ExportingEntityOrgMSP", 
  "ca": { 
    "url": "https://localhost:11054", 
      "name": "ca-exportingentityorg" 
  }, 
  "peer1": { 
    "requests": "grpcs://localhost:11051", 
    "events": "grpcs://localhost:11053", 
    "server-hostname": "peer0.exportingentityorg.trade.com", 
    "tls_cacerts": "../network/crypto-config/peerOrganizations/exportingentityorg.trade.com/peers/peer0.exportingentityorg.trade.com/msp/tlscacerts/tlsca.exportingentityorg.trade.com-cert.pem" 
  } 
} 

请注意,先前指定的端口与我们用于启动此组织的 MSP 和对等体的docker-compose-exportingEntityOrg.yaml文件中指定的端口匹配。证书路径与此节早期使用cryptogen生成的路径匹配,名称与configtx.yaml中指定的名称匹配。该组织只有一个对等体,这正是我们在后者文件中指定的。

为了确保中间件函数加载正确的配置,我们需要将 constants.js 中的 networkConfig 变量的值从 config.json 更改为 config_upgrade.json。我们在文件 new-org-join-channel.js 中这样做:

var Constants = require('./constants.js'); 
Constants.networkConfig = './config_upgrade.json';

现在我们准备为属于出口实体组织的单个对等体运行通道加入程序。 new-org-join-channel.js 中的代码如下:

var joinChannel = require('./join-channel.js'); 
Client.addConfigFile(path.join(__dirname, Constants.networkConfig)); 
var ORGS = Client.getConfigSetting(Constants.networkId); 
joinChannel.joinChannel('exportingentityorg', ORGS, Constants); 

joinChannel 的调用会将在 config_upgrade.jstrade-network:exportingentityorg:peer1 部分中指定详细信息的对等体加入到 tradechannel 中。要执行此操作,只需运行以下命令:

node new-org-join-channel.js 

新对等体现在已经是通道的一部分,并将通过现有网络对等体使用 gossip 协议最终同步通道的共享账本内容。

类似地,我们可以通过调用 install-chaincode.js 中的 installChaincode 函数在此对等体上安装链码。但恰巧的是,此时我们想演示链码升级功能。因此,我们可以直接在所有 5 个对等体上安装新版本,而不是两次运行安装过程。我们将在下一节中描述该过程。

智能合约和策略更新

正如我们在本章的早期部分观察到的那样,绑定在共享通道上的智能合约受到多种原因的影响,从代码修复到参与者不断发展的需求。无论原因如何,Hyperledger Fabric 提供的机制和变化的语义都保持不变。我们将在本节中演示的就是这种机制。

在 Hyperledger Fabric 视图中与智能合约密切相关的是必须满足的背书策略,以使交易结果提交到共享账本中。正如我们将看到的,可以升级智能合约的相同机制也可以用于修改背书策略。

链码逻辑修改

让我们首先考虑一个需要我们更新(或升级)贸易链码的情景。在上一节中刚刚完成的添加新组织,需要在链码中进行某些更改。例如,让我们考虑 chaincode/src/github.com/trade_workflow/tradeWorkflow.goacceptTrade 函数中的以下代码片段:

// Accept a trade agreement 
func (t *TradeWorkflowChaincode) acceptTrade(stub shim.ChaincodeStubInterface, creatorOrg string, creatorCertIssuer string, args []string) pb.Response { 
  // Access control: Only an Exporter Org member can invoke this transaction 
  if !t.testMode && !authenticateExporterOrg(creatorOrg, creatorCertIssuer) { 
    return shim.Error("Caller not a member of Exporter Org. Access denied.") 
  } 

前述访问控制逻辑规定,只有出口商组织的成员才能接受贸易。在我们之前的 4 个组织网络中,这是有道理的,因为出口商和出口商的银行都是一个组织的一部分,我们依赖更高层次的访问控制来区分银行家和其客户以执行链码操作的目的。但现在,我们已经添加了一个组织来满足出口商独立于其银行的需求(现在将出口商称为出口实体),我们应相应地更改访问控制逻辑。并且这不是唯一需要对其进行修改的功能。

因此,我们需要生成链码的新版本。在我们的代码存储库中,这可以在 chaincode/src/github.com/trade_workflow_v1/ 中找到。代码内容看起来几乎与原始版本相同,只是一些访问控制过滤规则有所不同。让我们看看 chaincode/src/github.com/trade_workflow_v1/tradeWorkflow.goacceptTrade 函数的类似代码片段:

// Accept a trade agreement 
func (t *TradeWorkflowChaincode) acceptTrade(stub shim.ChaincodeStubInterface, creatorOrg string, creatorCertIssuer string, args []string) pb.Response { 
  // Access control: Only an Exporting Entity Org member can invoke this transaction 
  if !t.testMode && !authenticateExportingEntityOrg(creatorOrg, creatorCertIssuer) { 
    return shim.Error("Caller not a member of Exporting Entity Org. Access denied.") 
  } 

注意,authenticateExporterOrg 函数已被替换为 authenticateExportingEntityOrg。 如果您查看 accessControlUtils.go 文件的内容,您会注意到已添加了后者函数的定义。

在涉及各种组织的真实应用中,通过协作和咨询进行链码的更改,通过非核心机制传递给不同的利益相关者,经过检查、审核和测试,然后才准备部署到网络。

链码中的依赖升级

访问控制逻辑并不是我们在链码中需要更改的唯一内容。我们使用了一个有些刻意的场景,即当只有一个早期版本的 Fabric(例如 v1.0)可用时,创建了链码的初始版本。如果您检查逻辑以从发出交易的组织中提取 MSP 标识以及在发出链码交易的提交者的证书中提取公共名称,这是使用标准的 Go 库手动完成的。这在 chaincode/src/github.com/trade_workflow/accessControlUtils.go 中的 getTxCreatorInfo 函数中有示例代码:

creatorSerializedId := &msp.SerializedIdentity{} 
err = proto.Unmarshal(creator, creatorSerializedId) 
...... 
certASN1, _ = pem.Decode(creatorSerializedId.IdBytes) 
cert, err = x509.ParseCertificate(certASN1.Bytes) 
...... 
return creatorSerializedId.Mspid, cert.Issuer.CommonName, nil 

当 Fabric 平台升级到 v1.1 时,实施了一个名为cid的新包来执行前述操作并隐藏 protobuf 结构和证书解析的细节。为了使我们的链码更清洁,并且更符合 Fabric 的更改,有必要将我们的前述逻辑升级为使用新包。这就是我们在 chaincode/src/github.com/trade_workflow_v1/accessControlUtils.go 中的升级版本中所做的:

import ( 
  ...... 
  "github.com/hyperledger/fabric/core/chaincode/lib/cid" 
  ...... 
) 
...... 
func getTxCreatorInfo(stub shim.ChaincodeStubInterface) (string, string, error) { 
  ...... 
  mspid, err = cid.GetMSPID(stub) 
  ...... 
  cert, err = cid.GetX509Certificate(stub) 
  ...... 
  return mspid, cert.Issuer.CommonName, nil 
} 

分类帐重置

链码升级类似于实例化,两者都会执行Init函数。在链码的初始版本中,许多账本值被初始化,但除非我们改变该逻辑,否则这些初始值将覆盖账本的当前状态。因此,我们在chaincode/src/github.com/trade_workflow_v1/tradeWorkflow.goInit函数中添加代码来模拟一个空操作,但我们也保留了原始逻辑以确保在升级时可以覆盖值,如果有业务需要这样做,如以下代码片段所示:

func (t *TradeWorkflowChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { 
  ...... 
  // Upgrade Mode 1: leave ledger state as it was 
  if len(args) == 0 { 
    return shim.Success(nil) 
  } 
  // Upgrade mode 2: change all the names and account balances 
  if len(args) != 8 { 
    ...... 

背书策略更新

我们最初的事务背书策略要求每个 4 个组织的成员对链码调用事务进行背书(签名)。现在我们已经添加了一个新的组织,我们必须更新该策略以要求每个 5 个组织的成员签名。在middleware文件夹中,这个新策略在constants.js中定义如下:

var FIVE_ORG_MEMBERS_AND_ADMIN = [{ 
  role: { 
    name: 'member', 
    mspId: 'ExporterOrgMSP' 
  } 
}, { 
  role: { 
    name: 'member', 
    mspId: 'ExportingEntityOrgMSP' 
  } 
}, { 
  role: { 
    name: 'member', 
    mspId: 'ImporterOrgMSP' 
  } 
}, { 
  role: { 
    name: 'member', 
    mspId: 'CarrierOrgMSP' 
  } 
}, { 
  role: { 
    name: 'member', 
    mspId: 'RegulatorOrgMSP' 
  } 
}, { 
  role: { 
    name: 'admin', 
    mspId: 'TradeOrdererMSP' 
  } 
}]; 

var ALL_FIVE_ORG_MEMBERS = { 
  identities: FIVE_ORG_MEMBERS_AND_ADMIN, 
  policy: { 
    '5-of': [{ 'signed-by': 0 }, { 'signed-by': 1 }, { 'signed-by': 2 }, { 'signed-by': 3 }, { 'signed-by': 4 }] 
  } 
}; 

要在我们的中间件中切换背书策略,我们只需要将constants.js中的TRANSACTION_ENDORSEMENT_POLICY变量的值从ALL_FOUR_ORG_MEMBERS更改为ALL_FIVE_ORG_MEMBERS

在交易通道上升级链码和背书策略

现在我们准备执行升级过程,这将需要两个步骤:

  1. 在网络对等方上安装新版本的链码

  2. 通道上的链码和背书策略升级

执行这些步骤的代码可以在middleware/upgrade-chaincode.js中找到,并且只是简单地调用我们已经实现的函数(参见第五章公开网络资产和交易)。以下代码片段显示了我们需要在安装过程中做的事情:

var Constants = require('./constants.js'); 
var installCC = require('./install-chaincode.js'); 
Constants.networkConfig = './config_upgrade.json'; 
Constants.TRANSACTION_ENDORSEMENT_POLICY = Constants.ALL_FIVE_ORG_MEMBERS; 
installCC.installChaincode(Constants.CHAINCODE_UPGRADE_PATH, Constants.CHAINCODE_UPGRADE_VERSION, Constants); 

请注意,在上述代码中使用了 5 个组织的网络配置以及 5 个组织的背书策略。链码的新路径和版本在constants.js中设置如下:

var CHAINCODE_UPGRADE_PATH = 'github.com/trade_workflow_v1'; 
var CHAINCODE_UPGRADE_VERSION = 'v1'; 

路径相对于存储库中的链码/src 文件夹,因为GOPATH临时设置为chaincode/文件夹被复制到的位置(参见constants.jsinstall-chaincode.js)。版本设置为 v1,而不是初始版本 v0。

您选择的链码版本 ID 在链码的生命周期中必须是唯一的;也就是说,它不能被用于任何以前的版本。

触发升级是下一步,从开发者的角度来看,几乎与实例化步骤相同:

var instantiateCC = require('./instantiate-chaincode.js'); 
instantiateCC.instantiateOrUpgradeChaincode( 
  Constants.IMPORTER_ORG, 
  Constants.CHAINCODE_UPGRADE_PATH, 
  Constants.CHAINCODE_UPGRADE_VERSION, 
  'init', 
  [], 
  true, 
  Constants 
); 

正如我们所看到的,我们选择通过传递空参数列表将账本状态保留在当前状态。在 instantiate-chaincode.js 中的 instantiateOrUpgradeChaincode 函数中,构建提案后调用 channel.sendUpgradeProposal(request, 300000) 来将请求发送给订购者,而不是调用 channel.sendInstantiateProposal(request, 300000) 进行实例化。与实例化一样,我们注册事件侦听器以告诉我们请求是否成功。

要推送链代码升级,请运行:

node upgrade-chaincode.js 

要测试新的链代码,请运行:

node five-org-trade-scenario.js 

这将运行一系列交易操作(对链代码的调用和查询),涉及从交易请求到最终付款交付货物的各方。

平台升级

您的分布式区块链应用程序必须预见并支持对平台组件所做的更改。专注于我们在样本交易网络中创建和启动的组件,其中包括 Fabric peer、orderer 和 CA(或 MSP)。就像应用程序链代码可能因为错误和新要求而改变一样,平台也会随着时间的推移而改变。自 2015 年底诞生以来,Fabric 已经多次改变,每次改变都被推送为具有新版本的升级,当前版本为 1.1。每当平台组件升级时,您都需要在不干扰应用程序生命周期的情况下替换正在运行的系统中的这些组件。在本节中,我们将演示如何执行此操作。

您可以以不同的配置运行您的网络组件,一种方式是使用 docker 容器,这是我们在本书中演示的方法。要升级在 docker 容器中运行的平台组件,您需要做的第一件事是为各种组件生成新的图像。这可以通过从 Docker Hub 下载相关图像或下载源代码并使用 make docker 本地构建图像来完成;后一种方法是我们在本书中采用的方法。要查看已下载到您系统的 Hyperledger Fabric 图像的完整列表,您可以运行如下内容:

docker images | grep hyperledger/fabric 

你会看到一个很长的图像条目列表,其中大多数是重复的,最新的标签是指向具有特定标签名称的图像之一的指针。由于我们在网络文件夹(docker-compose-e2e.yamlbase/docker-compose-base.yamlbase/peer-base.yaml)中的 docker-compose YAML 文件仅依赖于 fabric-peer、fabric-orderer 和 fabric-ca 的图像,让我们仅检查这些:

hyperledger/fabric-peer    latest    f9224936c8c3    2 weeks ago    187MB 
hyperledger/fabric-peer    x86_64-1.1.1-snapshot-c257bb3    f9224936c8c3    2 weeks ago    187MB 
hyperledger/fabric-orderer    latest    5de53fad366a    2 weeks ago    180MB 
hyperledger/fabric-orderer    x86_64-1.1.1-snapshot-c257bb3    5de53fad366a    2 weeks ago    180MB 
hyperledger/fabric-ca    latest    39fdba61db00    2 weeks ago    299MB 
hyperledger/fabric-ca    x86_64-1.1.1-snapshot-e656889    39fdba61db00    2 weeks ago    299MB 

运行 docker images 命令时会看到类似于上面的内容。这里列出的 Docker 映像是从 Fabric 和 Fabric CA 源代码的 release-1.1 分支本地构建的。如果您下载了不同版本的源代码,并使用 make docker 构建映像,您将看到每个前面组件的第三个映像条目,并且您的最新映像标签将链接到您刚刚创建的映像。

我们将通过以下示例进行演示,其中交易网络的 orderer 和 peer 将进行升级。我们将升级 fabric-ca 留给用户作为练习。要在运行中的应用程序中执行此操作,您需要执行以下一系列步骤:

  1. 下载或构建平台组件映像的新版本

  2. 停止组件

  3. (可选)为了安全起见备份您的账本内容

  4. 停止运行的链码容器

  5. 从系统中删除链码容器映像

  6. 确保 docker-compose YAML 文件中引用的映像标签与组件的新版本链接在一起

  7. 启动组件

您也可以选择逐个停止、升级和启动每个组件,而不是一次性全部执行。在执行此升级时,您需要停止系统的所有传入请求,这应该只是关闭应用程序 Web 服务器的简单事情。

在代码存储库的 network/trade.sh 中的 upgradeNetwork 函数中,有以这种方式升级我们的交易网络的示例代码。在这里,我们假设用户将执行以下操作之一:

  • 使用 -i 开关作为命令行参数传递新映像标签(例如在上述列表中的 x86_64-1.1.1-snapshot-c257bb3),

  • 将最新标签链接到新映像

在调用该函数之前。现在我们必须停止 orderer 和 peer:

COMPOSE_FILE=docker-compose-e2e.yaml 
...... 
COMPOSE_FILES="-f $COMPOSE_FILE" 
...... 
docker-compose $COMPOSE_FILES stop orderer.trade.com 
...... 
for PEER in peer0.exporterorg.trade.com peer0.importerorg.trade.com peer0.carrierorg.trade.com peer0.regulatororg.trade.com; do 
  ...... 
  docker-compose $COMPOSE_FILES stop $PEER 
  ...... 
done 

正如我们在前面的代码中所看到的,用于启动网络的 docker-compose YAML 文件也必须用于停止各个组件。

上述示例假设只有前 4 个组织是网络的一部分。

一旦容器停止,我们可以选择备份分类帐数据如下:

LEDGERS_BACKUP=./ledgers-backup 
mkdir -p $LEDGERS_BACKUP 
...... 
docker cp -a orderer.trade.com:/var/hyperledger/production/orderer $LEDGERS_BACKUP/orderer.trade.com 
...... 
for PEER in peer0.exporterorg.trade.com peer0.importerorg.trade.com peer0.carrierorg.trade.com peer0.regulatororg.trade.com; do 
  ...... 
  docker cp -a $PEER:/var/hyperledger/production $LEDGERS_BACKUP/$PEER/ 
  ...... 
done 

现在将对等方以及 orderer 上的分类帐内容备份到您的本地机器上的 ledgers-backup 文件夹中。

现在我们应该删除所有的链码映像,因为新的映像需要由新的 fabric-peer 映像创建,并且旧映像的存在将阻止该创建:

for PEER in peer0.exporterorg.trade.com peer0.importerorg.trade.com peer0.carrierorg.trade.com peer0.regulatororg.trade.com; do 
  ...... 
  CC_CONTAINERS=$(docker ps | grep dev-$PEER | awk '{print $1}') 
  if [ -n "$CC_CONTAINERS" ] ; then 
    docker rm -f $CC_CONTAINERS 
  fi 
  CC_IMAGES=$(docker images | grep dev-$PEER | awk '{print $1}') 
  if [ -n "$CC_IMAGES" ] ; then 
    docker rmi -f $CC_IMAGES 
  fi 
  ...... 
done 

请注意,我们必须首先检查链码容器是否正在运行,如果正在运行,则停止它们,否则将无法删除映像。

现在我们可以重新启动已停止的 orderer 和 peer 容器。在运行 docker-compose up 时,orderer 和 peer 容器将使用新映像启动:

docker-compose $COMPOSE_FILES up --no-deps orderer.trade.com 
...... 
for PEER in peer0.exporterorg.trade.com peer0.importerorg.trade.com peer0.carrierorg.trade.com peer0.regulatororg.trade.com; do 
  ...... 
  docker-compose $COMPOSE_FILES up --no-deps $PEER 
  ...... 
done 

您可以通过以下任一方式之一来一次性运行整个升级过程:

./trade.sh upgrade [-i <imagetag>] 

如果未指定 <imagetag>,它将默认为最新版本,如前面所述。

您现在可以继续运行您的分布式交易应用程序。请注意,平台更改可能伴随着链码和 SDK API 的更改,这可能需要升级您的链码或中间件,或者两者都需要升级。正如我们在前面的部分中演示的示例一样,读者应该在应用程序和底层区块链平台的整个生命周期中随时具备升级这两者的能力。

系统监视和性能

您现在已经构建了您的应用程序,并为其寿命内的变化预期制定了各种流程和机制。另一个同样重要的过程是,您必须具备并不时进行监视和性能测量。您为现实世界的用户和机构构建的任何生产应用程序都必须满足某些性能目标,以对其用户实用,并且应用程序的利益相关者也会受到影响。因此,了解您的应用程序的性能并尝试提高其性能是一个关键的维护任务;忽视这项任务可能会导致您的应用程序寿命短暂。

系统性能测量和分析的艺术(以及科学)是一个广泛而深入的话题,我们并不打算在本书中深入或全面地涵盖这些话题。为了获得这样的涵盖,我们鼓励感兴趣的读者阅读其他关于这一主题的经典文献(例如,www.amazon.com/Systems-Performance-Enterprise-Brendan-Gregg/dp/0133390098)。相反,我们将提供性能测量和洞察到区块链应用程序所需的预览,并提供开发人员或系统管理员可以利用的工具和技术的一些建议。

广义上说,系统维护性能主要涉及三种大致顺序的任务类别,尽管这些任务可以在系统的整个生命周期中循环重复:

  • 观察和测量

  • 评估(或分析)和洞察(或理解)

  • 重构、重设计或重新实施以改进

在本节的讨论中,我们将主要关注以下一些方面:

  • 在 Fabric 应用程序中需要测量的重要内容

  • Fabric 应用程序开发人员或管理员可以用于测量的机制

  • 应用程序设计人员和开发人员应该注意 Fabric 的性能抑制方面

测量和分析

在特定讨论 Hyperledger Fabric 之前,让我们了解对于分布式系统(其中区块链应用是一个例子)而言,什么是测量和分析的意义。该过程始于对系统架构、各个组件以及这些组件之间的耦合程度和性质的全面了解。下一步是建立机制来监视各个组件并收集对性能有关联的数据属性,无论是持续地还是在定期间隔内。这些数据必须被收集和传输到一个模块,该模块随后可以分析这些数据以生成系统性能的有意义表示,并可能提供对应用程序运行和其现有效率的工作更深入的洞察。分析过的数据也可以用于确保系统正以期望的性能水平运行,并且检测当系统没有达到该水平时,这对面向用户的系统是非常重要(如果不是至关重要的)。

这些技术和流程在分布式系统分析领域以及移动分析领域(可视为前者的特例)中是众所周知的。代理可以配置为主动或被动地观察或监控系统组件:在前者中,系统可以被仪器化(例如,通过插入特殊的数据收集代码)以使其自我监视其活动并收集信息,而在后者中,数据收集可以由一个外部于被监控组件的软件来完成。存在一条管道将这些数据连续或周期性地传输到一个中央存储库,数据可以被累积以供以后处理,或立即被处理和消费。该管道可能修改数据以使其适合分析。在数据分析术语中,这种管道通常被称为提取-转换-加载ETL)。如果数据生成的数量和频率非常高,并且数据来源的数量非常大,则这种分析也被称为大数据分析

ETL 流程或大数据分析超出了本章和本书的范围,但对于严肃的区块链开发人员或管理员来说,需要牢记的一点是存在执行这样的分析的框架,不管是为了配置了服务器和数据库的分布式系统的(Fabric 区块链应用就是一个示例)比如 Splunk (www.splunk.com/en_us/solutions/solution-areas/business-analytics.html) 或 Apteligent (www.apteligent.com/),还是为了移动应用程序比如 Tealeaf (www.ibm.com/in-en/marketplace/session-replay-and-interaction-analytics)和 Google Analytics (developers.google.com/analytics/solutions/mobile)。这些相同的框架也可以用于监视和分析区块链应用。

在 Fabric 应用程序中,我们应该度量或了解什么

基于 Hyperledger Fabric 及其相关工具构建的应用实际上是一个分布式事务处理系统

区块链应用与传统交易处理应用程序

想象一下传统的交易处理系统是什么样的。您在后台会有一个数据库来存储、处理和提供数据;这个数据库可以是集中式的,也可以是分布式的,在后一种情况下,会维护副本或分区。在数据库前面,您会有一个或多个 Web 服务器或应用服务器来管理和运行您的应用逻辑;在更前面,您会有一个或多个用于与用户交互的界面。

同样,Fabric 区块链应用拥有维护共享复制账本的对等节点,相当于数据库。智能合约代码类似于传统数据库管理系统中的存储过程和视图。我们为我们的交易应用程序演示了中间件和应用服务器的架构和工作方式,这些可以作为或者甚至由传统应用服务器承载。最后,我们可以设计用户交互的 Web 界面,就像我们为传统交易处理应用程序设计的那样。当然,我们使用curl作为交易用例的测试替代方法。

用于性能分析的指标

因此,区块链应用程序的性能受到影响与影响传统的基于 DBMS 的事务处理应用程序类似的因素。首先,我们必须不断监视托管应用程序组件的硬件资源的健康状况。对于每台运行对等方、排序者或 CA 的计算机,我们需要跟踪基本健康指标,如 CPU 使用率、内存使用率、磁盘 I/O 速度、网络带宽、延迟和抖动以及可用存储空间(这不是详尽列表)。这些因素,特别是对于处理密集型系统的 CPU 使用率,决定了应用程序是否以最佳性能水平运行。

正如本书中所看到的,Fabric 网络可以以各种配置启动,从为每个对等方和排序者分别分配的单个专用机器(物理或虚拟)到在单个机器上运行每个组件的单机设置,每个组件都在一个隔离的 docker 容器中(就像本书中的交易网络设置)。在后一种情况下,您将需要监视每个容器的健康状况,而不仅仅是机器的健康状况。还要记住,每个 Fabric 链码实例始终在 docker 容器中运行,而不是在专用机器上运行。此外,当涉及理解(或分析)应用程序时,应用程序组件的 CPU、内存和 I/O 使用情况最为重要。我们将在本节后面介绍一些用于测量容器和应用程序性能的工具。

从外部因素转向应用程序本身,Fabric 应用程序的性能(就像任何其他事务处理应用程序一样)由两个特征性指标定义:

  • 吞吐量:这是系统每单位时间可以产生的交易数量。由于 Fabric 是一个松散耦合的系统,而且一个交易有多个阶段(参见 第五章暴露网络资产和交易,例如我们的交易场景),我们可以测量不同阶段的吞吐量。但是,从客户端构造交易提案以获得认可到接收到表示账本提交的事件的时间的整体吞吐量,提供了关于您的应用程序性能的最佳整体图片。另一方面,如果我们只想衡量排序者的吞吐量,我们需要仅收集客户端将经认可的交易信封发送到排序者并收到响应的部分的统计信息。

  • 延迟:由于大多数 Fabric 应用程序最终将面向用户,因此在真实场景中,不仅处理能力或容量很重要,而且每个交易所需的时间也很重要。与吞吐量的情况类似,我们可以测量不同的延迟 - 链码执行和背书、排序和区块创建、交易验证和账本提交,甚至事件发布和订阅的延迟。我们还可以测量组件间通信的延迟,以了解通信基础设施的限制。

还有其他重要的事情需要测量,比如在同行之间同步账本状态所需的时间(使用gossip协议),但从交易处理的角度来看,前面两个指标非常重要。当我们测量这些因素时,我们就能了解整体应用程序的表现,以及其组成部分,比如对等体中的 ESCC 和 VSCC,以及订购者中的 Kafka 服务。

在 Fabric 应用程序中的测量和数据收集

既然我们知道应该测量什么,让我们来看一些动手测量和数据收集的例子。我们将使用我们的单个 VM(Linux),多个 docker 容器交易网络作为示例,并让读者将这些方法推广到其他设置(借助更全面的测量文本的帮助)。

收集健康和容量信息

通过检查/proc中的信息是系统获取 CPU、内存和其他活动信息的标准方法。此外,Linux 中提供了一系列工具来获取特定的信息。sysstat包含其中许多,例如,iostat用于收集 CPU 和 I/O 统计信息,pidstat用于收集每个进程的健康统计信息,以及sarsadc用于像cron一样收集类似的统计信息。举个例子,在运行整个交易网络和链码的 VM 上运行iostat,会得到如下两个虚拟硬盘的 CPU 信息和 I/O 统计信息:

Linux 4.4.0-127-generic (ubuntu-xenial)    05/28/2018    _x86_64_    (2 CPU) 

avg-cpu:  %user    %nice    %system    %iowait    %steal    %idle 
           0.31     0.01       0.26       0.11      0.00    99.32 

Device:            tps    kB_read/s    kB_wrtn/s    kB_read    kB_wrtn 
sda               1.11        16.71        11.00     688675     453396 
sdb               0.00         0.05         0.00       2014          0

vmstat工具类似地提供了虚拟机范围内信息的摘要,内容如下:

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- 
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st 
 0  0      0 2811496 129856 779724    0    0     7     5  127  342  0  1 99  0  0 

对于持续的每个进程统计信息,您也可以使用众所周知的top命令,以及dstat,后者还会生成 CSV 格式的输出,易于消费。如果您希望将测量机制连接到 ETL 分析流水线,则可能是理想的工具,它以广为人知的格式进行全面的性能数据收集和报告,这个工具便是nmon (nmon.sourceforge.net/pmwiki.php)。

但我们还必须专门对运行应用程序组件的容器进行分析。perf 工具作为 Linux 性能计数器和分析工具非常方便。它可以按线程、进程和 CPU(或处理器)的基础进行概要文件收集。通过使用perf report命令和不同的开关进行数据收集,结果会被收集并存储在命令运行所在的文件夹中名为perf.data的文件中。此数据可以使用perf report命令进行分析。此外,可以使用bindfsbindfs.org/)将perf报告中的符号映射到在 docker 容器内运行的进程。最后,perf stat可用于收集系统范围的统计信息。perf维基(perf.wiki.kernel.org/index.php/Main_Page)提供了有关如何使用此工具的更多信息。

对容器和应用程序进行分析

我们的应用程序组件还必须进行分析,以产生我们分析所需的指令级信息和调用堆栈,这不仅用于跟踪性能,还用于调试应用程序缺陷。strace 工具可用于记录正在运行的 docker 容器所发出的系统调用。例如,获取我们的 orderer 容器的进程 ID 如下:

docker inspect --format '{{ .State.Pid }}' orderer.trade.com

回想一下,我们在 docker-compose YAML 文件中将容器命名为orderer.trade.com。输出将是一个进程 ID;我们称其为<pid>。现在在该进程上运行strace

sudo strace -T -tt -p <pid>

您应该看到持续的输出,类似于以下内容:

strace: Process 5221 attached 
18:48:49.081842 restart_syscall(<... resuming interrupted futex ...>) = -1 ETIMEDOUT (Connection timed out) <0.089393> 
18:48:49.171665 futex(0x13cd758, FUTEX_WAKE, 1) = 1 <0.000479> 
18:48:49.172253 futex(0x13cd690, FUTEX_WAKE, 1) = 1 <0.000556> 
18:48:49.174052 futex(0xc420184f10, FUTEX_WAKE, 1) = 1 <0.000035> 
18:48:49.174698 futex(0xc42002c810, FUTEX_WAKE, 1) = 1 <0.000053> 
18:48:49.175556 futex(0x13cd280, FUTEX_WAIT, 0, {1, 996752461}) = -1 ETIMEDOUT (Connection timed out) <1.999684> 

要分析输出,请阅读规范的strace文档。请注意,此工具仅在 Linux 系统上可用。另外,在您的 docker-compose YAML 文件中,您可以配置容器在内部运行strace。例如,获取network/base/docker-compose-base.yamlpeer0.exporterorg.trade.com的容器定义。您可以按如下方式扩展它以启用strace(添加的配置已用斜体表示):

peer0.exporterorg.trade.com: 
  container_name: peer0.exporterorg.trade.com 
 *cap_add: 
   - SYS_PTRACE 
  security_opt: 
    - seccomp:unconfined* 

最后,针对 Fabric 平台和您在其上开发的应用程序的更具体信息,有 Go profiling 可以参考。Fabric 组件(peer、orderer 和 CA)以及链码均是用 Golang 编写的,了解程序的哪些部分使用了更多的时间和资源对于提高应用程序质量至关重要。为了进行这种分析,我们可以使用pprofgolang.org/pkg/net/http/pprof/),Golang 的内置分析工具(blog.golang.org/profiling-go-programs)。 (请确保您已在打算运行分析器的系统上安装了go。)要捕获由各种函数的调用图和运行频率(相当于 CPU 使用情况)组成的应用程序概要文件,pprof 需要一个 Go 应用程序运行 HTTP 服务器,如下所示:

import "net/http" 
http.ListenAndServe("localhost:6060", nil)

要获取概要文件,我们可以使用go tool命中此服务器并获取数据。例如,如果您的应用程序在端口6060上运行服务器,您可以通过运行以下命令获取堆概要文件:

go tool pprof http://localhost:6060/debug/pprof/heap 

您可以在前述命令中将localhost替换为适当的主机名或 IP 地址。要获取 30 秒的 CPU 概要文件,请运行:

go tool pprof http://localhost:6060/debug/pprof/profile 

Hyperledger Fabric 提供了对此类分析的内置支持(github.com/hyperledger-archives/fabric/wiki/Profiling-the-Hyperledger-Fabric),至少在 Fabric 对等体上是这样。要启用分析(或运行 HTTP 服务器),我们需要适当地配置对等体(或在我们的案例中,运行对等体的docker容器)。回想一下,我们示例交易网络中每个对等体的核心配置在network/base/peer-base.yaml中定义。请注意以下行:

services: 
  peer-base: 
    image: hyperledger/fabric-peer:$IMAGE_TAG 
    environment: 
      ...... 
      - CORE_PEER_PROFILE_ENABLED=true 
      ...... 

还要记住,我们的对等方的容器和主机之间的端口映射在network/base/docker-compose-base.yaml中定义。出口商和进口商组织对等方的示例如下所示:

peer0.exporterorg.trade.com: 
  ...... 
  ports: 
    ...... 
    - 7055:6060 
    ...... 
peer0.importerorg.trade.com: 
  ...... 
  ports: 
    ...... 
    - 8055:6060 
    ......

虽然在它们的容器内,但概要文件服务器在端口6060上运行,在主机机器上,pprof将命中端口7055以捕获出口组织对等体的概要文件,并命中端口8055以捕获进口组织对等体的概要文件。

作为示例,让我们捕获出口组织对等体的 30 秒 CPU 概要文件。我们可以启动交易网络,并使用 middleware/createTradeApp.js 运行频道创建和链码安装步骤。在另一个终端窗口中,我们可以运行:

go tool pprof http://localhost:7055/debug/pprof/profile 

这将最终在 ~/pprof 生成一个文件,并在您的控制台上产生以下内容:

Fetching profile over HTTP from http://localhost:7055/debug/pprof/profile 
Saved profile in /home/vagrant/pprof/pprof.peer.samples.cpu.006.pb.gz 
File: peer 
Build ID: 66c7be6d1f71cb816faabc48e4a498bf8052ba1b 
Type: cpu 
Time: May 29, 2018 at 5:09am (UTC) 
Duration: 30s, Total samples = 530ms ( 1.77%) 
Entering interactive mode (type "help" for commands, "o" for options) 
(pprof) 

最后,该工具留下一个pprof shell 以运行各种分析命令,以分析获得的转储。例如,要获取前五个最活跃的函数或 goroutine:

(pprof) top5 
Showing nodes accounting for 340ms, 64.15% of 530ms total 
Showing top 5 nodes out of 200 
      flat  flat%   sum%        cum   cum% 
     230ms 43.40% 43.40%      230ms 43.40%  runtime.futex /opt/go/src/runtime/sys_linux_amd64.s 
      30ms  5.66% 49.06%       30ms  5.66%  crypto/sha256.block /opt/go/src/crypto/sha256/sha256block_amd64.s 
      30ms  5.66% 54.72%       30ms  5.66%  runtime.memmove /opt/go/src/runtime/memmove_amd64.s 
      30ms  5.66% 60.38%       30ms  5.66%  runtime.usleep /opt/go/src/runtime/sys_linux_amd64.s 
      20ms  3.77% 64.15%      110ms 20.75%  runtime.findrunnable /opt/go/src/runtime/proc.go

tree命令以文本形式显示整个调用图,其中一个部分看起来像这样:

(pprof) tree 
Showing nodes accounting for 530ms, 100% of 530ms total 
Showing top 80 nodes out of 200 
----------------------------------------------------------+------------- 
      flat  flat%   sum%        cum   cum%   calls calls% + context 
----------------------------------------------------------+------------- 
                                              70ms 30.43% |   runtime.stopm /opt/go/src/runtime/proc.go 
                                              50ms 21.74% |   runtime.notetsleep_internal /opt/go/src/runtime/lock_futex.go 
                                              40ms 17.39% |   runtime.ready /opt/go/src/runtime/proc.go 
     230ms 43.40% 43.40%      230ms 43.40%                | runtime.futex /opt/go/src/runtime/sys_linux_amd64.s 
----------------------------------------------------------+------------- 
                                              30ms   100% |   crypto/sha256.(*digest).Write /opt/go/src/crypto/sha256/sha256.go 
      30ms  5.66% 49.06%       30ms  5.66%                | crypto/sha256.block /opt/go/src/crypto/sha256/sha256block_amd64.s 
----------------------------------------------------------+------------- 

您还可以以图形方式查看图表,可以是在网页上,也可以是生成文件:

(pprof) png 
Generating report in profile001.png

下面的示例显示了生成的调用图作为 PNG 图像:

第 9.4 图:一个呈现在 30 秒内在对等节点中执行的函数的调用图部分。

这是调用图像的一个部分,其中每个框代表一个函数,框的大小表示该函数的频率(即运行该函数的概要样本数量)。有向图边表示从一个函数调用另一个函数,边表示在进行这种调用时所花费的时间。

对于更多pprof选项和分析工具,建议读者阅读文档。

测量应用程序性能

测量应用程序的吞吐量和延迟要比前面描述的许多工具更为神秘;它将涉及为您的代码添加仪器测量来收集和记录定时信息。在您的代码中,您需要添加日志记录(或通信,用于远程报告)指令以记录何时执行特定操作,或添加适当的挂钩,可以根据需要启用或禁用数据收集。

测量延迟相当简单;您可以记录各种操作的时间,例如客户端提案提交,认可返回,订单处理程序对请求的确认,分类帐的提交时间以及接收事件的时间。收集大量事务的数据将使您能够获得总体事务延迟以及单个操作中所产生的延迟。

要获取吞吐量信息,您需要生成不同容量和不同频率的事务负载。然后,您可以增加对应用程序的负载,直到观察到的事务确认频率(或事件接收)低于事务负载生成频率。除此之外,您还需要像测量事务延迟那样为代码进行仪器测量。您可以更改不同的应用程序参数和特征,并运行这样的吞吐量测量,以确定最佳性能的应用程序和资源特征。

利用本节中描述的工具所能收集的所有信息,应用程序或网络设计人员可以进行高级分析,以确定系统的哪些部分(例如,通过pprof调用图)的性能良好,以及哪些部分是瓶颈。然后可以尝试通过向“瓶颈”组件添加更多资源或重新实现系统使这些组件更有效来纠正性能限制。跨不同冗余资源的负载均衡是维持高性能水平的另一种广泛使用的技术。瓶颈的检测和分析本身就是一个非常重要的主题,鼓励读者学习文本和学术论文以获得更好的理解。

Fabric 性能工程准则

我们现在将从总体概括转向具体问题。在本节中,我们将对 Hyperledger Fabric 的性能进行评论,讨论影响性能的平台显著特征,并为开发人员制定提取其应用程序最佳性能的指南。

平台性能特征

Fabric 架构和交易流水线现在应该对本书的读者非常熟悉。这是一个复杂的分布式系统,其性能取决于许多因素,从与 Fabric 交互的应用程序的架构到共识实现、交易大小、区块大小、Fabric 网络大小,以及底层硬件和物理网络介质的能力。

在编写本书时,性能测量显示 Fabric 可以产生每秒数千个交易的吞吐量(arxiv.org/abs/1801.10228)。我们的读者需要记住的一个警告是,这些测量是使用执行非常简单操作的链码进行的,并使用可能不代表典型生产区块链网络的应用程序和网络配置。Fabric 的性能受特定用例和底层硬件的限制。例如,IBM Z 系统上的性能超过其他平台,这是由于优化的 Go 编译器利用了硬件加速功能,例如用于加密算法等。良好的性能取决于足够的资源和适当的配置;我们将在本节后面详细讨论配置。

系统瓶颈

对 Fabric 架构和交易阶段进行简单检查将揭示可能的瓶颈组件。排序服务是一个主要而明显的例子。每个交易必须通过此服务,并被包含在一个区块中才能有机会提交账本。但请记住,在提交时仍然无法保证交易不会被拒绝。因此,排序服务的性能在某种程度上为应用程序的性能设定了基准。显然,增加排序器资源,无论是通过添加更多节点还是增加每个个体节点的容量,都可能导致更好的性能。还可以使用其他排序机制来代替当前 Fabric 默认的 Kafka。随着 Fabric 平台的发展,预计会看到更好、更快的排序算法。

另一个系统瓶颈位于账本提交阶段,当交易必须被评估以确保认证背书的真实性,并通过管理读写冲突来执行数据库(账本)一致性。加密操作本质上是繁重的,而 Fabric 的最新更改(例如在 v1.1 中)已经使签名验证更有效。作为开发人员或网络工程师,您可以通过最小化由于无效签名或交易间冲突而导致的交易失败的可能性来简化性能。对于前者,在背书阶段和向排序器生成请求期间进行更好的验证应该可以减少失败的机会。

为了减少冲突,需要尝试不同的区块大小(请记住,在一个区块中检查事务之间的冲突)。虽然更大的区块可能会导致更高的吞吐量,但冲突可能会产生相反的效果。您还可以设计您的链码,以减少不同调用事务之间冲突的可能性。有关 Fabric 如何检测和处理区块中的冲突的解释,请参见第四章使用 Golang 设计数据和事务模型, 在 多版本并发控制 部分。

配置和调整

从我们之前的讨论中继续,您可以配置各种参数来优化您应用程序的性能。这些参数中的许多是系统要求的结果,例如网络大小。但是您核心 Fabric 配置中的一些参数(请参见第三章通过业务场景设置舞台, 在 网络组件配置文件* 部分)可以调整以最大化性能。其中之一是区块大小。通过实验(或调整参数直到达到最佳吞吐量和延迟)可以确定应为您的应用程序设置的精确区块大小(以字节和事务数量)。例如,在一个名为 Fabcoin 的加密货币应用程序上的测量结果显示,最佳区块大小为 2 MB (arxiv.org/abs/1801.10228)。但读者必须牢记前一节讨论中的权衡,即一个区块中的更多事务数量也可能导致更高的冲突率和事务拒绝。

你选择的交易认可策略也会对性能产生重大影响。从背书节点收集的签名越多,验证签名的时间就越长。此外,策略越复杂(即它包含的子句越多),验证速度就越慢。现在需要做出权衡。更多的背书者和更复杂的策略通常会提供更高的保证(可靠性和信任),但这会以性能(吞吐量和延迟)为代价。因此,区块链应用程序管理员必须确定所需的服务水平和信任水平,并相应调整参数。

还有各种其他因素可能会影响 Fabric 应用程序的性能:这包括由对等体之间的八卦协议引起的开销,用于您的应用程序的通道数量以及交易生成率。在硬件级别,性能取决于组件可用的 CPU 的数量和性能。一般而言,可以说增加 CPU 的数量会提高组件和整体区块链网络的性能。如果您对此感兴趣,可以阅读一篇关于这个主题的好论文:Hyperledger Fabric:面向许可的区块链的分布式操作系统,EuroSys '18 (dl.acm.org/citation.cfm?id=3190538),也可在 arxiv.org/pdf/1801.10228v1.pdf 上找到。

分类帐数据的可用性和缓存

您可以通过优化存储在分类帐中的数据的可用性(即检索时间)进一步提高分布式 Fabric 应用程序的性能。有几种策略可以做到这一点,我们在这里将概述其中的两种。

冗余的提交对等体

为了增加客户端应用程序对数据的可用性,可以在拓扑上更靠近客户端应用程序或访问数据的中间件组件部署一个或多个额外的提交对等体。提交对等体接收新创建的区块并维护最新的分类帐。它不参与背书过程,因此不会收到来自客户端的交易提案请求。因此,对等体的性能完全用于维护分类帐并响应数据请求。在网络性能和系统安全配置方面的重要考虑因素是选择和设置位置,使得提交对等体可以无阻碍地连接到通道,并且网络吞吐量允许以低延迟接收新创建的区块。

数据缓存

从对等体检索到的数据可以存储在应用程序缓存中,以便将来对该数据的请求可以更快地提供。为了保持缓存中的数据最新,应用程序必须监视底层分类帐的更改,并使用新状态修改更新缓存数据。如前所述,对等体会发出关于新提交到分类帐的事务的事件通知。客户端可以拦截通知,并通过检查事务的内容确定是否应该使用新值更新缓存。

Fabric 性能测量和基准测试

我们希望本书的这一部分能让读者理解为什么性能测量和分析是重要的,并提供一些关于如何使他/她的应用程序提供足够服务水平的线索。最后,我们将读者指向当前存在于 Hyperledger 框架中的工具,以使用样本基准应用程序来测量性能(主要是吞吐量、延迟和资源利用率)。

对于深入和全面的性能测量工具套件,您应该查看fabric-testgithub.com/hyperledger/fabric-test/)。特别是,PTE(github.com/hyperledger/fabric-test/tree/master/tools/PTE)是一个灵活的工具,可以使用样本链码驱动参数化事务负载。

Hyperledger Cellowww.hyperledger.org/projects/cello)不是一个性能测量工具,而是一个区块链供应和管理系统,它可以在不同平台(虚拟机、云和容器集群)上启动网络。它可以作为一种辅助工具,在尝试生产部署之前启动、测试和测量样本网络。

Hyperledger Caliperwww.hyperledger.org/projects/caliper)是另一个项目,目前正在开发一个基准测试框架,允许用户使用一组预定义的用例来衡量特定区块链实现的性能,并生成报告。读者应该记住,这些项目都还在进行中,应该密切关注由区块链性能基准测试领域的研究推动的进一步发展。

摘要

维护和增强区块链应用可能比创建和引导它更具挑战性,因为需要熟练掌握监控和分析,还需要评估变更的影响。

在本章中,我们描述了 Hyperledger Fabric 应用程序在其生命周期内可能发生的各种方式。我们详细描述了如何使用我们的典型贸易应用程序作为示例,如何向运行中的网络添加组织和对等方,如何增加通道配置,如何升级平台,以及如何修改智能合约(链码)而不会对应用程序状态造成不利影响。

在本章的后面部分,我们概述了开发人员或系统管理员可以使用的工具,来测量、分析和改善 Fabric 区块链应用程序的性能。我们还提供了为了更好地性能而设计系统的指导方针。

随着进一步的研究和发展,Hyperledger 套件毫无疑问将会增加更多更好的系统变更和监控机制。本章应该为典型的 Fabric 开发人员或管理员维护他们的生产应用提供一个方便的指南。

第十章:治理,受监管行业的必要之恶

对于那些经历过没有明确决策流程的项目的人来说,您会感受到因各种利益相关者的影响而不断质疑和修改决策的痛苦。政治阻碍了进程,项目的目标最终受到挑战,预算被削减,长期愿景缺失或混乱。

虽然这是您可以从传统 IT 项目中期待到的内容,但区块链项目具有更多利益相关者的特点。一个典型的商业网络将由有时竞争有时合作的组织组成。在这种情况下,很容易看出存在着高风险,即存在冲突的观点和利益。

无论您是开发人员还是首席信息官,了解您可以从这些项目中期待什么以及治理模型如何帮助缓解一些问题可能有助于为即将到来的事情做好准备。

本章将介绍我们在各个行业中看到的一些模式,并探讨这些区块链业务网络如何形成以及基础治理模型如何运作。

本章将介绍以下主题的观点:

  • 什么是治理?

  • 各种商业模式

  • 治理在商业网络中的作用

  • 典型的治理结构和阶段

  • 要考虑的角色和流程

  • 治理对 IT 解决方案的影响

去中心化和治理

一些人可能会想知道为什么我们在区块链书籍中涵盖了治理。毕竟,区块链网络不是应该是去中心化的,因此受控于单一实体的控制吗?虽然从技术角度来看,这是正确的,但现实是我们是人类,对于一个企业级区块链网络要成功,需要在网络的生命周期中做出许多决策。

即使是比特币,这个去中心化、匿名、无需许可的网络,也必须处理重要且困难的决策。一个例子是围绕比特币区块大小的争议。在比特币的早期,将区块大小限制为 1 MB。随着网络规模的扩大,这个限制变得有问题。提出了许多提案,但是需要跨越比特币节点的共识,使得变更难以达成一致意见。这场辩论始于 2015 年,但社区不得不等到 2018 年 2 月才有了一个部分解决方案,即 SegWit。我们说部分是因为SegWit,即隔离见证,只通过将签名与交易有效负载分开来减轻问题,从而允许在一个区块中包含更多的交易 - 这需要大量的讨论和交流才能达到部分答案。

此外,请考虑区块链业务网络旨在在不是所有参与者完全信任彼此的环境中建立信任。他们如何就如何管理网络达成共识呢?

知道会有冲突和不同的观点,我们该如何解决这个问题呢?嗯,我们需要一个涉及每个关键组织的重要决策者的过程。参与者需要就一个过程达成基本一致,并尊重结果。我们需要一种管理网络的方法——我们需要治理。

所以,治理是关于决策吗?实际上并不是。治理是提供指导决策过程的框架。它通过提供明确的角色和责任的界定,并确保存在达成和传达决策的约定流程来实现这一点。

我们一直在泛泛地谈论决策,但是什么样的决策需要通过治理流程进行管理呢?我们将在角色和流程部分正确回答这个问题,但现在,可以说涉及资金、功能路线图、系统升级和网络扩展的一切都肯定是应该由治理流程覆盖的关键话题。

商业和 IT 治理是一个长篇大论的话题。因此,您会发现许多旨在定义指导 IT 行业内实践的成熟结构的 IT 治理标准。这些标准的一些示例包括:

  • 信息技术基础设施图书馆ITIL):ITIL 主要关注 IT 如何向业务提供服务,并旨在定义支持 IT 服务管理的流程模型,本质上将 IT 服务表达为它们带来的业务利益的功能,而不是底层技术细节。

  • 信息及相关技术的控制目标COBIT):此标准分为两部分:治理和管理。COBIT 的治理部分侧重于通过一系列关于评估、指导和监控流程的控制目标来确保实现企业目标。

无论如何,标准方法都需要根据商业模式和背景进行调整和适应。

探索商业模式

商业模式侧重于创建一个描述组织在市场中创建和捕获价值流的结构。

在商业网络的背景下,看看价值链并理解价值的来源是很有趣的。从财务角度看,什么使得区块链网络如此吸引人呢?嗯,正如我们在第一章,区块链——企业和行业视角中看到的那样,区块链技术为解决时间和信任问题提供了机会,从而降低了效率低下和运营成本。

区块链的益处

处理时间和信任问题会带来什么样的利益?让我们看看在接下来的章节中这些好处可以如何实现和实施的一些例子。

供应链管理

供应链由许多参与者组成,从生产商到物流服务提供商、港口管理机构、制造商,最终到消费者。该行业必须应对各种规定,虽然不同组织之间存在许多数据交换,但要得到一个真实的版本却并非易事。

对供应链缺乏信任源于涉及的许多组织担心数据可能泄露给竞争对手。这反过来导致以下问题:

  • 可视性:我的订单在哪里?我的货柜在哪里?如果没有透明性,制造商的预测会受到影响,可能导致生产延误。

  • 行政开销:数据需要多次输入,并需要人力和详细检查过程来检测错误。

  • 纠纷:无法访问共同信息源导致参与者对不同的看法存在分歧,将这些分歧转化为争端。

  • 调查:由于纠纷的后果,多个各方必须努力收集事实并解决问题。

在这种背景下,去中心化、经许可的分类账意味着每一笔订单和每一批货物都可以实时跟踪,同时防止竞争对手访问敏感信息。该模型有助于消除重复数据输入,减少人为错误,并加快调查进程,因为每笔交易的来源可以轻松证明。

鉴于全球经济,很容易想象到潜在的储蓄。想象一个通过经许可的分类账管理的真实信息的世界,在那里所有相关参与者都可以获得信息,我们可以看到这将在整个供应链中带来的即时优势。

医疗保健

医疗行业有许多可以探索的用例,包括制药供应链、临床试验和电子健康记录。我们将关注最后一个用例,因为它与我们的心脏更密切相关。

电子健康记录的承诺一直令人向往,乍看之下,其好处似乎很多:

  • 患者历史的完整视图:通过消除基于纸张的记录固有的重复,患者应该获得更准确的诊断,并及时接受更连贯的长期护理。

  • 减少重复:无论是因为不同医生请求重复检测,还是因为每家诊所和医院都必须保留记录,医疗系统中存在资源潜在浪费。

  • 防止欺诈行为:无论是不良诊所的双重记账还是虚假处方的索赔,都存在许多情况,记录的重复创建了滥用的机会。

尽管好处似乎很明显,但来自现有电子健康记录项目的教训似乎暗示着它们是昂贵的,并且可能不会立即产生预期的好处。一些研究发现:

  • 数字化记录患者/医生会话为医生增加了额外的工作量。

  • 电子健康记录系统正在增加 IT 支出

  • 需要额外的工作量用于变更管理和培训

从那时起,最近的研究表明,这样的解决方案往往在长期内具有积极的投资回报(需要大约五年才能实现收益)。

考虑到价值和利益来自技术的广泛/标准化采用,并考虑到许多国家的医疗网络的范围,不难看出这种努力充满了政治复杂性。

区块链网络能否改善长期以来被视为集中技术创新主要领域的领域?虽然从技术上来看,我们可以设想一个优雅的区块链解决方案,在这个解决方案中,诊所和医院加入网络以获取患者的记录,但真正的挑战可能在于治理方面?

金融 - 信用证

在书的这一部分,你应该熟悉信用证的概念。然而,让我们快速回顾一下它背后的概念,如下图所示:

信用证是一种支付工具,根据买方的要求,银行将向卖方发出信用证,声明只要符合条款和条件,就会支付款项。虽然这个过程在国际贸易中非常根深蒂固,但信用证的使用是一个非常古老的过程,它的根源可以追溯到第一次十字军东征,当时圣殿骑士需要找到一种方式,让朝圣者前往耶路撒冷而不必携带现金。

如今的信用证流程非常复杂。虽然例子通常涉及两家银行,但现实情况是在这样的网络中将涉及更多参与者。这导致了一个成本高昂且受执行时间限制的流程。

区块链网络可以为优化流程创造机会;在区块链网络中,信用证存储在账本上,这可以防止双重花费场景,即信用证的所有者可能尝试再次兑现它。

该利益是通过减少时间延迟和成本来衡量的,但它还提供了减少与此类交易相关的潜在风险的主要利益。最后,银行现在也可以考虑推出新服务,例如向卖方进行增量支付的能力。

账本上的交易是最终的这一事实是吸引银行的原因。这也使我们有能力从一个较小的网络开始,获得早期价值,并在解决方案得到验证后扩展,从而在建立网络时减少早期协调所需的数量。

从利益到利润

无论市场或商业模式如何,必须有一种回报投资的方式,以便以下公式成立:

区块链创造的价值 - 网络运营成本 > 0

本质上,具有正回报并且出于共同的商业利益,可以出现网络级别的商业模式。显然,目标将是最大化价值并最小化成本,从而提供更高的利润率。可以理解,当网络能够提供高额利益时,组织将涌入网络,渴望加入。也就是说,除非商业模式偏袒少数人而损害多数人的利益。

因此,选择一个对大多数成员公平且合适的商业模式将是网络成功与否的决定因素。

网络业务模式

现在让我们来看看到目前为止使用过的各种商业模式:

  • 创始人主导的网络

  • 基于联合的网络

  • 基于社区的网络

  • 混合模型

我们将在接下来的章节中讨论这些模型。

创始人主导的网络

创始人主导的网络在许多情况下都可能很有价值,我们将很快介绍这些情况。一个正常的创始人主导的网络将具有以下架构:

然而,我们首先要提出一个警告:创始人主导的网络不应成为回避潜在网络参与者进行艰难商业讨论的方式。

在我们从事这个领域的时间里,我们与真正相信区块链网络价值的组织进行了互动,但对于去中心化控制网络的想法感到不知所措。他们最终制定了一个路线图,其中初始阶段是深入技术,并将业务讨论推迟到后续阶段。最终结果通常是在创始人基础设施中托管的面料网络,通过 API 网关公开网络。在某些情况下,甚至不向参与者提供不同的身份(即私钥和证书)。这里的风险在于,虽然解决方案在技术上是可行的,但它未能根据区块链网络的原则提供价值。

这并不意味着组织不应采用创始人主导的方法并制定分阶段的路线图,但在建立网络初期就获得潜在参与者的认可是很重要的,以避免缺乏采用或进行重大重组努力。

创始人主导的网络通常由以下类型的组织利用:

  • 初创公司:他们往往对自己的行业有独特的看法,并带来创新和新思路。他们的商业模式通常是面向行业提供增值服务。虽然创新可能推动他们获得行业认可,但他们的成功取决于可信度和资金。

  • 行业领袖:从他们的行业角度来看,他们有足够的影响力来建立自己的网络。他们得到供应商和其他组织的支持,以制定议程并支持用例。

  • 部门间区块链项目:这种模式最初可能不符合商业模式,因为它旨在为组织内部协调服务,但之所以在这里提出它,是因为这些项目是超越组织边界的良好候选者。

作为网络的创始成员,这些组织有机会定义网络的政策和重点。成功利用网络的组织将获得领导地位,并有望捕获网络的价值。

然而,这些优势也带来了说服其他组织加入的风险。他们还承担投资资金以启动项目并获得实现解决方案所需的专业知识的全部负担。他们还面临着在其他行业领袖加入之前,其他重大重组要求变更的风险。

基于联合体的网络

联合体是由两个或两个以上具有共同业务目标的组织组成的集团,通过业务网络实现。该网络的架构如下:

这些组织通常属于同一行业或密切相关的行业。重点是,他们的联系源于他们的流程中存在一定程度的协同作用,以及通过联合体合作实现的共同/共享利益。

联合体的一个关键特征是,每个成员保留其法人实体和地位。通过创建联合体,他们通常将进入合同和法律协议,这些协议将指导治理、活动和投资,以将他们的愿景变为现实。

我们区分创建者和成员,因为前者通常会面临与采用创始人主导网络模型的组织类似的情况。他们将面临与创始人主导网络相似的问题、风险和利益,但他们将通过扩大行业参与度来抵消风险。财团创始人也可能选择在其他组织加入时将网络货币化。

此外,财团成员可能会享受税收优惠,有助于改善行业的监管状况,并产生较大的影响力。然而,他们也可能面临潜在的责任和不履行义务,其中一位创始人可能无法像其他创始人一样做出同等水平的贡献。

社区式网络

基于社区的网络本质上是一种更加非正式的、志同道合的组织的联合体。他们共同构筑一个商业生态系统,旨在促进不同行业之间的合作,创造新的商机。该网络的框架如下:

img/0cf86c05-9a6b-4378-80c7-a0d527c2da48.png

在这个模型中,解决方案可能会发展成一个市场,每个成员都可以努力提供增值服务。这种模式的力量源自隐含自由结构和最好的理念自然浮现的自由。这是自然支持去中心化网络和治理概念的最佳模型。然而,如果成员的贡献不够一致,而潜在的责任被忽略,它可能会产生与财团相同的问题。

混合模式

商业模式并非静态的,会随时间推移而演变。因此,一个网络虽然可能从一个社区开始,但有可能演变为一个财团。此外,我们将讨论的两种混合模式对这些模型都可能有益。

合资企业

在合资企业模型中,少数组织同意组建一个共同拥有的新法律实体。每个组织可以为资金和股权做出贡献,收入和运营费用由各方共享。合资企业的控制权在组成它的各方手中,而不是合资企业本身。

新公司

新公司模型本质上类似于合资企业模型,但是完全从企业或财团中分离出来。这个新公司(NewCo)可能为参与其创建的各方提供服务;然而,利润和亏损完全归新公司所有。

商业网络中治理的作用

综合审查了各种商业模式后,我们可以看到每个参与者的控制权会根据该模型而异。通过正确理解每个方当事者的模型和利益,我们可以创造一个对每个人都有意义的决策过程。

因此,尽管我们了解治理是关于达成决定的过程,但是是否应该由治理流程管理和跟踪每一个业务、运营和技术决策?有人会认为,只有重要的主题应该由治理流程涵盖,但那么什么是重要的主题呢?这就是治理模型的作用:定义每个决策领域,并确保每个人都理解对每个决策类别的正式和官方过程的程度。对智能合约的错误修复可能不需要太多关注,但对区块链技术的升级可能需要更高度的关注。事先就如何处理这些类别中的每一项决定达成一致意见,将有助于当前和未来参与者理解他们将被提出的期望。

独立于流程复杂性之外,需要考虑的另一个因素将是决策的集中与分散。分散决策权可能会使流程显得公平,减少不当控制的风险,并鼓励自由思考,但这样做可能会导致达成共识的延迟。

尽管这在社区驱动型网络的背景下是有道理的,但在创始人主导的网络中会奏效吗?

可能不会。如果创始人正在投资资金和资源,他们可能不想分享对网络的控制权。请记住,这不是绝对的规则。决策的重要性将在施加的控制程度中起着重要作用。回到我们之前关于智能合约上的错误修复的例子,可以预期部署的决定应该是分散的,但是下一个要实施的功能的决定应该是集中的。

下表显示了治理与商业模型之间的关系,以及(一般而言)业务模型将如何推动治理结构。基本上,我们可以看到在天平的两端,我们有基于社区的网络,这往往是一个完全分散的商业模式,因此只能在分散的治理中生存:

试图将治理集中化可能会损害其存在,因为社区成员可能会拒绝控制或推动创建一个联合体。在光谱的另一端,我们有创始人主导的网络,它本身的性质倾向于在创始组织中保持控制权。联合体商业模式往往是多变的,并且在很大程度上取决于它们自己的性质。高度受监管的行业可能需要与确保所有方遵守既定标准相等高度的集中化。另一方面,联合体可以通过强加规则或采用决策制定的共识机制来实现分散的治理。

为了总结我们对企业网络治理角色的考察,让我们快速看一下企业网络需要解决的决策类型:

  • 成员生命周期:与将参与者引入网络和使其退出网络的过程相关的决策。

  • 资金和费用:围绕着网络将如何获得资金做出的决策。这可能涵盖诸如中心化基础设施、共同服务、人员配备等领域。

  • 监管:大多数行业需要符合特定的常常地域性的规定。这一类别关注的是确保满足和执行这些规定的关键决策。

  • 教育:关于为成员和外部组织提供何种程度的培训,以便了解并融入网络的决策。

  • 服务生命周期:涉及到 IT 组件的所有决策,涵盖了从部署新智能合约到系统更新等方面。

  • 争议:因为争议几乎总是不可避免的,这些决策涉及解决争议的过程。

在接下来的部分中,我们将深入研究这些领域,并探讨其中的一些复杂性。然而,值得注意的是,在每个决策类别中,都将需要在以下方面进行权衡:

  • 成本与风险

  • 竞争与合作

  • 形式主义与敏捷性

业务领域和流程

在本节中,我们将考虑治理模型应该致力于解决的流程范围。任何网络都应该考虑这些决策领域,以避免不良的意外。并非每个决策都需要受到正式流程的约束,但考虑这些因素将有助于避免未来的不良意外。

成员生命周期

我们知道,区块链网络旨在实现完全去中心化。因此,参与者的扩展是我们在一个健康的网络中所期望看到的正常现象。

然而,由于这是一个受规则和法规约束的企业级网络,因此在网络形成和新参与者入网过程中需要事先确定一些事项:

  • 谁拥有邀请组织加入网络的特权?

    这应该包括考虑谁可以提交提议创建新组织的建议,但也应该考虑到渠道级别的邀请。在入网过程中是否需要考虑隐私和保密约束?

  • 组织需要满足哪些最低安全要求?

    一个无法适当保护其对等方的组织将面临着暴露账本数据和损害私钥的风险。处理欺诈交易将导致混乱和痛苦的调查。清晰地阐明安全要求将有助于新参与者了解他们需要做出的投资水平。

  • 参与者应该接受什么标准合同协议?

    正如我们在前几章中提到的,智能合约应该被接受为网络内的法律,但这需要通过合同协议来界定,这些协议不仅承认这一事实,还陈述了参与者的期望和争议处理流程。

  • 参与者需要遵守哪些 IT 服务水平协议?

    正如我们在第八章中所见,《区块链网络中的灵活性》,达成智能合约推广频率和集成层隐含演变的协议很重要。现在这只是一个例子,但从服务水平协议的角度来看,还有其他方面,比如可用性、性能和吞吐量,这些都可能影响网络。

通过入职流程,组织将需要部署自己的基础设施,将其交易整合到自己的企业系统中,并在实际开始交易之前完成一轮测试。在他们在网络上的生命周期内,管理机构可能会要求对参与者的基础设施进行一些审核,以证明他们遵守了条款和条件。

经常被忽视的情况是组织退出网络的事件。可能会有两种情况导致发生这种情况:

  • 参与者对网络的兴趣发生变化,不再希望进行交易

  • 合同违约或争议导致参与者被移除

无论原因是什么,如果没有针对此事件的规定,可能会出现与组织数据所有权相关的问题。虽然交易数据在法律协议的背景下共享,但各方可能会同意将分布式分类账存储在每个人的节点中,但一旦该协议终止,会发生什么?

资金和费用

网络不会自行运营。需要开发智能合约,部署共同基础设施(例如有序节点),撰写法律协议等等。

此处采用的模型将根据选择的商业模式而大不相同。创始人主导的网络可能会承担所有资金成本,但反过来可能会收取一项费用,这不仅会覆盖成本,还会产生利润。另一方面,社区驱动的网络可能选择让参与者支付这些共同要素的成本。

无论如何,治理不仅应该定义资金和费用结构,还应考虑如何监控使用情况以及如何进行计费。

法规

这个领域将在网络运行的行业和地理位置大部分取决于网络,但在这个层面上,应该确定符合要求和参与者应该遵守的法规。

一个很好的例子是通用数据保护条例GDPR),它最近已经生效。GDPR 是欧洲委员会提出的一项法规,旨在加强和强化数据隐私规定。根据新法律,用户可以要求将他们的个人数据永久从任何组织中删除。忽视这样的法规可能导致智能合约保留个人信息,当收到删除请求时,给网络的所有参与者带来重大问题。

在这个领域,重点应放在以下方面:

  • 确定相关法规

  • 审计智能合约和参与者(适用时)以确保合规性达标

教育

这可能不适用于所有类型的商业模式。例如,一个由社区驱动的模式可能选择不提供教育服务,让他们的参与者自行管理,而创始人领导的网络可能决定投资于教育,以加速入职流程并更快地收回投资。

服务生命周期

服务生命周期专门涉及网络的技术方面。需要从最初的设计和实施到网络操作的前期投入大量考虑。

在网络的初期阶段,关键决策将包括以下领域:

  • 设计权威和标准

  • 数据治理

  • 配置管理

  • 关键管理

  • 测试流程

一旦网络准备好投入实际运行,运营方面将迅速浮出水面:

  • 基础设施运营(网络、服务器、存储)

  • 变更、升级、发布管理、维护

  • 业务连续性计划、归档、备份

  • 安全、控制、政策执行

  • 容量、可扩展性和性能

  • 事故和问题管理

争端

没有人愿意考虑到争端,就像他们不愿意考虑到退出流程一样;然而,定义一个处理这些争端的流程很重要。在这种情况下,治理应覆盖以下领域:

  • 提出抱怨:这些问题应该在哪里提出?我们将在下一节介绍治理结构,但如果您在一个真正分散的模式下工作怎么办?您有一个论坛可以提出这个问题吗?

  • 调查:如何收集事实?问题如何记录?如果质疑智能合约交易的输出,是否(及其相应的客户)将其从分类账中提取出来?

  • 解决方案:争端并不总是有愉快的结局,但解决这些问题的流程是什么?是否有一部分参与者应该决定这个问题?这是否应该成为法律起诉?

治理结构

到目前为止,我们已经涵盖了各种商业模式,审视了集中化与分散化的影响,并探讨了各种决策类型,以及支持这些决策所需的角色和责任。

我们现在将看到组织如何构建自己,以提供处理决策者侧重不同层次的一致方法,这取决于他们的角色。

虽然集中式和分散式治理的表现在现实应用中看起来非常不同,但在实际应用中,有灰色地带,某些功能可能集中,而其他功能可能分散。再次强调,这在很大程度上取决于推动网络的商业模式和需要。

集中式治理

尽管一个网络可能采用集中式或分散式的治理,但每个组织也将有他们自己的机制来控制谁做决定。通常情况下,组织会内部依赖集中式治理。这意味着我们不仅需要考虑网络治理,还需要考虑每个组织的结构,如下图所示:

在集中式模型中,决策往往从上到下流动,只有组织底层的未解决问题才会上升到顶层。这种模式创造了一个明确处理问题和愿景的框架,但留下很少的改变结构的空间。

在这种模型中,我们通常看到三个主要的治理层:

  • 战略治理

  • 运营治理

  • 战术治理

接下来的小节将定义每一部分并探讨每个层次的决策者类型。

战略治理

战略治理代表了决策金字塔的顶部。这一治理层需要来自各个组织和业务单位的高管支持,并负责确保愿景和战略与网络目标保持一致。它还应专注于确保实现业务利益。

战略治理将专注于以下内容:

  • 创造一个共同的业务愿景

  • 确定一个明确的授权和治理结构(利益相关者驱动)

  • 设定网络优先事项的议程

  • 确保实现业务目标

  • 开发和演进网络能力

运营治理

运营治理的重点是将愿景转化为满足网络需求的里程碑计划。这通常牵涉到业务利益相关者、董事、IT 架构师、法律顾问等。

出于这些考虑,重点将放在以下内容上:

  • 定义所有权

  • 开发和维护标准、隐私要求和法规

  • 为服务和智能合约创建一个统一的方法

  • 管理定义业务和技术需求的共同方法

  • 公共技术基础设施

战术治理

战术治理侧重于围绕网络的运行和操作的日常活动。在这个层面上,重点将放在网络的设计、构建和运营等方面。它将包括来自商业、法律和技术团队的各种利益相关者。任务将包括以下元素:

  • 强制执行标准

  • 智能合约代码审查

  • 部署规划

  • 组织入职

  • 安全审计

  • 报告

去中心化治理

治理的去中心化是为了为决策过程带来透明度和公平性。现在要记住,每个组织都有自己的治理结构(三个层级),这些治理机构需要就决策达成一致意见。考虑到每个组织的战略治理可能有不同的迫切性,这并不是一件微不足道的任务。这意味着决策需要通过一种共识形成的方式达成一致意见——一种公平、透明且将网络中每个组织的治理机构汇聚在一起的投票过程。

它还保留了与中心化网络相同的治理层级(战略、运营和战术),但所有事情都将在一个开放模式下进行,所有主题都在社区电话/活动中讨论。在这样的模型中,决策的文档化更加重要,以确保适当的透明度水平。没有公开的审计追踪,人们怎么能知道决策过程是公平的呢?

应该注意,虽然模型是去中心化的,并且可能更轻量级/敏捷,但适当地记录模型并获得参与者的认可同样重要。请注意,去中心化并不意味着更容易。事实上,虽然去中心化的网络治理可能更加符合区块链技术的本质,但它也带来了一些有趣的挑战。

例如,由于没有控制战略决策的中央机构,网络如何朝着共同的目标迈进?你如何避免强势接管或网络分裂?

这样的模型在业务目标一致时会运作良好。然而,当一个公司的议程被延迟,因为大多数社区在投票不同的优先事项时,这势必会产生紧张、争议和延迟。正如我们在比特币区块大小辩论中看到的那样,达成共识需要时间,并且为分裂创造了机会。这并不是说解决方案在于中央化模型——事实上,该模型中也存在类似的风险——但是去中心化模型的去中心化性质可能意味着参与者的业务目标联系更为松散。

治理和 IT 解决方案

到目前为止,在本章中,我们主要关注了治理的人为方面。我们已经研究了商业模式对治理的影响,需要考虑的业务流程以及各种潜在结构,但技术呢?治理模式对技术的影响是什么,技术又如何影响治理?

尽管区块链项目可能主要集中在解决商业和企业问题上,但其基础仍然依赖于技术。在本节中,我们将查看网络生命周期的主要阶段,从初始阶段到运行阶段,看看其中一些活动如何可以通过技术自动化和支持。

我们将重点放在入职主题上。正如您现在所知,系统分类帐用于存储构成网络的组织、策略和通道。在分类帐上存储配置意味着任何修改都需要签名和批准。从审计的角度来看,这是很好的,因为它使配置具有区块链方法本身的特性:

  • 共识:根据定义的政策,配置更改由网络成员背书和验证。

  • 溯源:配置更改由发起更改的人和所有其他背书者签名,从而保留更改的溯源信息。

  • 不可变:一旦配置块添加到区块链网络中,就无法修改。需要后续交易来进一步更改配置。

  • 最终性:由于交易记录在系统分类帐上并分布到网络的所有对等方,因此它提供了一个独特而最终的地方来断言网络的配置。无需查看配置文件即可了解你的锚点应与哪个对等方通信。

尽管这是一个非常有价值的功能,但它带来了一定的复杂性。修改配置的高级流程如下:

  1. 检索最新的配置块

  2. 解码配置块并相应地更改配置

  3. 对块进行编码,并计算与前一个块的差异/变化以建立 RW 集

  4. 签署交易并与其他参与者共享,以便他们根据网络政策签署

  5. 将已签署的交易提交回网络

这些步骤需要对 Hyperledger Fabric 的基础有很好的理解,并且需要一种跟踪和管理其他方签署的方法。鉴于其分散性质,可能需要涉及许多不同的参与方。这是规划入职流程非常重要的原因之一。

网络应确保他们及早定义此过程和所需的自动化。虽然组织可以构建自己的解决方案,但他们也可以依赖预构建的解决方案。在 IBM 的情况下,IBM 区块链平台提供了简化网络治理的能力。在下一部分,我们将看看如何使用 IBM 区块链平台进行入职。

管理入职

为了跟进这个练习,您可以:

  1. 在此处注册 IBM Cloud: console.bluemix.net/

  2. 使用此链接将 IBM 区块链平台服务添加到您的账户:

    console.bluemix.net/catalog/services/blockchain

应选择起始计划,并且读者应该审阅条款和条件,了解潜在的费用。

由于网络是去中心化的,网络中的任何组织都可以发出邀请,除非政策另有规定。

这个过程始于通过仪表板的成员菜单访问的以下表单发出的邀请:

在提交此表单后,系统将向新组织的操作员发送一个唯一的 URL。在幕后,它还在网络的根 fabric-ca 上创建了一个注册请求。

为接受邀请,操作员在平台上注册,提供组织名称,并在接受邀请后,系统将根据定义的政策自动更改网络配置,并包括新组织的定义。从这个角度来看,新组织的操作员可以访问操作仪表板,并开始加入频道和部署智能合约。仪表板将如下图所示:

操作化的仪表板

现在,由于网络上的所有交互都经过许可,平台提供了一个投票机制,允许参与者接受或拒绝更改,如下面的屏幕截图所示:

允许参与者接受或拒绝更改的投票机制

在这种情况下,当邀请新组织加入一个频道时,其他组织将有权对修改的接受进行投票。他们将能够在其通知门户中审查请求并批准或拒绝,如下面的屏幕截图所示:

审查请求

尽管 IBM 区块链平台还有许多其他优势和好处,但这里的意图是展示 IT 解决方案如何支持和促进与组织入职相关的一些关键治理流程。

摘要

从某种意义上说,治理是商业网络的人性化方面。这是关于人们如何聚集在一起并结构化决策过程,以确保所有相关方要么被征询意见,要么对决策负责。治理需要涵盖广泛的主题。

与其他人相比,技术人员可能对这个话题不太感兴趣,但对其基本了解对于理解我们的工作环境是有用的。

总的来说,在本章中,我们探讨了商业模式如何对治理产生深远影响。然后,我们使用这些模式,看看如何通过解决关键的业务流程来设计满足业务需求的结构。我们已经看到组织需要考虑集中式与分散式治理模式的方法。最后,我们了解到治理需要支持 IT 解决方案,但反过来,IT 解决方案需要支持治理过程。

最后需要记住的一点是,商业模式可能是灵活的。虽然一个倡议可能起初是由创始人领导的网络,但它可以发展成一个财团或一个基于社区的项目。这很重要,因为虽然我们孤立地看待了每个模型,但现实情况是它们注定会随着时间的推移而发展,但需要保持与网络提供的业务价值保持一致。